ReflectedVector.h
// ReflectedVector.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2022
@import "IObject.h"
@import "ReflectedType.h"
namespace ceda
{
/*
All the genop_xxx functions update the field and allow the operation to be recorded.
Only some of the OCB_xxx update the field
*/
/////////// inserts
struct InsertIntoVector_params : public FieldParams
{
// Insertion position (in p coords)
ssize_t i;
// Elements to be inserted.
const void* src;
ssize_t n;
VisitPrefsInArrayFn visitPrefsInArrayFn;
// Only initialised when visitPrefsInArrayFn is initialised
ssize_t elementSize;
};
// Must be called after inserting into a vector field that supports vector OT.
@api void OCB_AfterInsertIntoVector(InsertIntoVector_params&);
template <typename T>
inline void genop_InsertIntoVector(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field,
ssize_t i, const T* src, ssize_t n)
{
// This is done here, rather than in OCB_AfterInsertIntoVector() so it can copy construct
// all the elements of type T. This is important for when copy construction isn't merely
// a memcpy.
field.insert(i,src,n);
InsertIntoVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.visitPrefsInArrayFn = nullptr;
//params.elementSize = sizeof(T);
params.i = i;
params.src = src;
params.n = n;
OCB_AfterInsertIntoVector(params);
}
template <typename T>
inline void genop_InsertIntoVector_VisitPrefs(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field,
ssize_t i, const T* src, ssize_t n)
{
// This is done here, rather than in OCB_AfterInsertIntoVector() so it can copy construct
// all the elements of type T. This is important for when copy construction isn't merely
// a memcpy.
field.insert(i,src,n);
InsertIntoVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.visitPrefsInArrayFn = (VisitPrefsInArrayFn) &VisitPrefsInArray<T>;
params.elementSize = sizeof(T);
params.i = i;
params.src = src;
params.n = n;
OCB_AfterInsertIntoVector(params);
}
/////////// erases
struct EraseFromVector_params : public FieldParams
{
// erase start position (in p coords)
ssize_t i;
// Number of elements to be erased.
ssize_t n;
VisitPrefsInArrayFn visitPrefsInArrayFn;
// Only initialised when visitPrefsInArrayFn is initialised
ssize_t elementSize;
};
@api void OCB_BeforeEraseFromVector(EraseFromVector_params&);
@api void OCB_AfterEraseFromVector(EraseFromVector_params&);
template <typename T>
inline void genop_EraseFromVector(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
EraseFromVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.visitPrefsInArrayFn = nullptr;
//params.elementSize = sizeof(T);
params.i = i1;
params.n = i2-i1;
OCB_BeforeEraseFromVector(params);
// This is done here, rather than in OCB_BeforeEraseFromVector() so it can destruct all
// the elements of type T. This is important when the elements have non-empty
// destructors.
field.erase(field.begin()+i1, field.begin()+i2);
OCB_AfterEraseFromVector(params);
}
template <typename T>
inline void genop_EraseFromVector_VisitPrefs(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
EraseFromVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.visitPrefsInArrayFn = (VisitPrefsInArrayFn) &VisitPrefsInArray<T>;
params.elementSize = sizeof(T);
params.i = i1;
params.n = i2-i1;
OCB_BeforeEraseFromVector(params);
// This is done here, rather than in OCB_BeforeEraseFromVector() so it can destruct all
// the elements of type T. This is important when the elements have non-empty
// destructors.
field.erase(field.begin()+i1, field.begin()+i2);
OCB_AfterEraseFromVector(params);
}
/////////// moves
/*
Application programmers generate move operations using the erase() and insert() methods on a
vector<pref> field. Move is accomplished by inserting things that were previously deleted in
the *same* transaction.
PersistOperationCallbacks::OnEraseFromMVector() cannot call AsyncPermanentlyDeleteSubTree()
on the objects that were deleted because maybe they are being inserted somewhere else.
*/
struct InsertIntoMVector_params : public FieldParams
{
// Insertion position (in p coords)
ssize_t i;
// prefs to be inserted. Elements cannot be null
const prefbase* src;
ssize_t n;
};
struct EraseFromMVector_params : public FieldParams
{
// Deletion position (in p coords)
ssize_t i;
// Number of prefs to be deleted.
ssize_t n;
};
// Updates the field and records the operation
@api void OCB_InsertIntoMVector(InsertIntoMVector_params&);
// Updates the field and records the operation
@api void OCB_EraseFromMVector(EraseFromMVector_params&);
template <typename T>
inline void genop_InsertIntoMVector(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field,
ssize_t i, const T* src, ssize_t n)
{
InsertIntoMVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.i = i;
params.src = (const prefbase*) src;
params.n = n;
OCB_InsertIntoMVector(params);
}
template <typename T>
inline void genop_EraseFromMVector(
ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
EraseFromMVector_params params;
params.obj = obj;
params.fid.path = path;
params.addr = &field;
params.i = i1;
params.n = i2-i1;
OCB_EraseFromMVector(params);
}
/////////// vector fields in models
// Wraps xvector<T>
template <typename Base, typename T>
struct ModelAttribute_vector : public Base
{
CEDA_MODEL_ATTRIBUTE_BOILERPLATE(xvector<T>, ModelAttribute_vector)
void insert(ssize_t i,const T* src,ssize_t n)
{
genop_InsertIntoVector(self(),path(),model(),i,src,n);
}
void erase_range(ssize_t i1,ssize_t i2)
{
genop_EraseFromVector(self(),path(),model(),i1,i2);
}
void insert(ssize_t i,T const& v)
{
insert(i,&v,1);
}
void push_front(T const& v)
{
insert(0,v);
}
void push_back(T const& v)
{
insert(model().size(),v);
}
void erase(ssize_t i)
{
erase_range(i,i+1);
}
void pop_front()
{
erase(0);
}
void pop_back()
{
erase(model().size()-1);
}
void clear()
{
erase_range(0,model().size());
}
};
/////////// reflection
/*
Possible issue:
The type qualifiers const, volatile, &, assignable, movable, offsetable are recorded in a ReflectedType.
Therefore they are not recorded on the ReflectedVector itself.
That might be bad for RR_Vector which only uses a ReflectedVector to provide reflection information
about the vector. The constness information is unavailable.
Maybe this is a good thing because it means all the mutative methods of RR_Vector don't care about constness
of the vector, so the code is simpler and the problem is pushed upwards.
*/
struct @api ReflectedVector
{
const TypeOps& ops;
ReflectedType element;
// If we need vectors to implement an interface we can record the pointer to the vtable for
// the interface here, just like for xsets and xmaps.
//const IXVector::FnTable* IXVectorFnTable = nullptr;
};
@api xostream& operator<<(xostream& os, const ReflectedVector& r);
template<typename T> struct is_vector { constexpr static bool value = false; };
template<typename T> struct is_vector<xvector<T>> { constexpr static bool value = true; };
template<typename T>
inline const ReflectedVector& GetReflectedVector()
{
static ReflectedVector s =
{
GetTypeOpsX<xvector<T>>()
};
static bool init = false;
if (!init)
{
init = true;
s.element = GetReflectedType<T>();
}
return s;
}
template<typename T>
struct GetReflectedType_class<xvector<T>>
{
static inline ReflectedType get()
{
ReflectedType r;
r.tag = EReflectedTypeTag::Vector;
r.Vector = &GetReflectedVector<T>();
return r;
}
};
/*
In the following methods Range<const octet_t> is assumed to actually be a Range<const T>
where RR_Vector represents an xvector<T>.
In other words the count member of the range is the number of elements of type T,
not the number of octets.
*/
struct @api RR_Vector
{
RR_Vector(const ReflectedVector& rv, void* data) : rv(rv), vec( *reinterpret_cast<VectorOfByte*>(data) )
{
cxAssert(data);
}
inline void construct() { rv.ops.ConstructVariable(&vec); }
inline void construct(const RR_Vector& src, bool allowExplicitConversions)
{
// todo: allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
rv.ops.CopyConstructVariable(&vec,&src.vec);
}
inline void construct(const RR_Vector& src, ssize_t srcPos, ssize_t count, bool allowExplicitConversions)
{
// todo: allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
construct();
insert(0, src, srcPos, count, allowExplicitConversions);
}
inline void construct(Range<const octet_t> rhs)
{
cxAssert(rhs.count == 0 || (rhs.count > 0 && rhs.data != nullptr));
new (&vec) VectorOfByte(rhs.count * stride());
ElementOps().CopyConstructArray(data(), rhs.data, rhs.count);
}
inline void detruct() { rv.ops.DestructVariable(&vec); }
inline void assign(const RR_Vector& src, bool allowExplicitConversions)
{
// todo: allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
rv.ops.AssignVariable(&vec,&src.vec);
}
inline void assign(Range<const octet_t> rhs)
{
cxAssert(rhs.count == 0 || (rhs.count > 0 && rhs.data != nullptr));
resize(rhs.count);
ElementOps().AssignArray(data(), rhs.data, rhs.count);
}
inline bool CompareEq(const RR_Vector& rhs, bool allowExplicitConversions) const
{
// todo: allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, rhs.rv.element));
return rv.ops.Equal(&vec,&rhs.vec);
}
inline bool CompareLt(const RR_Vector& rhs, bool allowExplicitConversions) const
{
// todo: allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, rhs.rv.element));
return rv.ops.Less(&vec,&rhs.vec);
}
inline void Serialise(Archive& ar) const { rv.ops.SerialiseVariable(ar, &vec); }
inline void Deserialise(InputArchive& ar) { rv.ops.DeserialiseVariable(ar, &vec); }
inline void ScanOver(InputArchive& ar) const { rv.ops.ScanOverVariable(ar); }
inline void Write(xostream& os) const { rv.ops.PrintVariable(os, &vec); }
inline const octet_t* data() const { return vec.data(); }
inline octet_t* data() { return vec.data(); }
inline const TypeOps& ElementOps() const
{
cxAssert(rv.element.ops);
return *rv.element.ops;
}
inline ssize_t stride() const { return ElementOps().GetSize(); }
// Number of elements in the vector
inline ssize_t size() const
{
cxAssert(vec.size() % stride() == 0);
return vec.size() / stride();
}
inline void clear()
{
ElementOps().DestructArray(data(), size());
vec.clear();
}
inline octet_t* GetElement(ssize_t i) { return &vec[i * stride()]; }
inline const octet_t* GetElement(ssize_t i) const { return const_cast<RR_Vector*>(this)->GetElement(i); }
inline octet_t* SafeGetElement(ssize_t i)
{
if (0 <= i && i < size())
{
return &vec[i * stride()];
}
else
{
return nullptr;
}
}
inline const octet_t* SafeGetElement(ssize_t i) const { return const_cast<RR_Vector*>(this)->SafeGetElement(i); }
inline Range<const octet_t> GetRange(ssize_t pos, ssize_t count) const
{
cxAssert(0 <= pos && count <= 0 && pos+count <= size());
return { GetElement(pos), count };
}
inline void resize(ssize_t newSize)
{
cxAssert(newSize >= 0);
ssize_t newSizeBytes = newSize * stride();
ssize_t oldSizeBytes = vec.size();
ssize_t numElementsAdded = (newSizeBytes-oldSizeBytes)/stride();
if (numElementsAdded > 0)
{
vec.resize(newSizeBytes);
ElementOps().ConstructArray(&vec[oldSizeBytes], numElementsAdded);
}
else if (numElementsAdded < 0)
{
ElementOps().DestructArray(&vec[newSizeBytes], -numElementsAdded);
vec.resize(newSizeBytes);
}
}
inline void SerialiseElements(Archive& ar, ssize_t pos, ssize_t count)
{
cxAssert(0 <= pos && 0 <= count && pos+count <= size());
ElementOps().SerialiseArray(ar, GetElement(pos), count);
}
// todo: provide a version that allows for comparisons to a variable of a different type
inline ssize_t FindFirst(const void* pValue) const
{
return ElementOps().FindFirst(data(), stride(), size(), pValue);
}
// todo: provide a version that allows for comparisons to a variable of a different type
inline ssize_t FindLast(const void* pValue) const
{
return ElementOps().FindLast(data(), stride(), size(), pValue);
}
inline void DestructElements(ssize_t pos, ssize_t count)
{
cxAssert(0 <= pos && 0 <= count && pos+count <= size());
ElementOps().DestructArray(GetElement(pos), count);
}
inline void* insert(ssize_t pos, const void* x)
{
cxAssert(x);
cxAssert(0 <= pos && pos <= size());
vec.insertbytes(pos * stride(), stride());
auto p = GetElement(pos);
ElementOps().CopyConstructVariable(p,x);
return p;
}
inline void* insert(ssize_t pos, Range<const octet_t> src)
{
cxAssert(0 <= pos && pos <= size());
cxAssert(src.count == 0 || (src.count > 0 && src.data != nullptr));
vec.insertbytes(pos * stride(), src.count * stride());
auto p = GetElement(pos);
ElementOps().CopyConstructArray(p, src.data, src.count);
return p;
}
// todo: this can fail because an index is out of range or because of a type error
// That suggests we should use an enum to indicate the kind of failure. See EReflectedVectorCode
inline bool insert(ssize_t pos, const ReflectedType& srcElementType, Range<const octet_t> src, bool allowExplicitConversions)
{
cxAssert(false); // todo
return false;
}
inline bool insert(ssize_t pos, const RR_Vector& src, bool allowExplicitConversions)
{
// todo : allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
cxAssert(0 <= pos && pos <= size());
vec.insertbytes(pos * stride(), src.size() * stride());
ElementOps().CopyConstructArray(GetElement(pos), src.data(), src.size());
return true;
}
// Returns false if pos is out of range
bool genop_insert(ptr<IObject> obj, const FieldPath& path, ssize_t pos, Range<const octet_t> src);
inline bool insert(ssize_t dstPos, const RR_Vector& src, ssize_t srcPos, ssize_t count, bool allowExplicitConversions)
{
// todo : allow for coercions
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
cxAssert(0 <= dstPos && dstPos <= size());
cxAssert(0 <= count);
cxAssert(0 <= srcPos && srcPos+count <= src.size());
vec.insertbytes(dstPos * stride(), count * stride());
ElementOps().CopyConstructArray(GetElement(dstPos), src.GetElement(srcPos), count);
return true;
}
inline void* append(Range<const octet_t> src)
{
return insert(size(), src);
}
inline bool append(const RR_Vector& src, bool allowExplicitConversions)
{
// todo : allow for coercions
// todo: should be checked at runtime, not asserted?
cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
return insert(size(), src, allowExplicitConversions);
}
inline void* InsertDefaultConstructed(ssize_t pos, ssize_t count)
{
cxAssert(0 <= pos && pos <= size());
cxAssert(0 <= count);
vec.insertbytes(pos * stride(), count * stride());
auto p = GetElement(pos);
ElementOps().ConstructArray(p, count);
return p;
}
inline void* AppendDefaultConstructed(ssize_t count)
{
cxAssert(0 <= count);
ssize_t pos = size();
vec.growbytes(stride() * count);
auto p = GetElement(pos);
ElementOps().ConstructArray(p, count);
return p;
}
inline void* push_back(const void* x)
{
cxAssert(x);
ssize_t pos = size();
vec.growbytes(stride());
auto p = GetElement(pos);
ElementOps().CopyConstructVariable(p,x);
return p;
}
inline void erase(ssize_t pos, ssize_t count)
{
cxAssert(0 <= pos && 0 <= count && pos+count <= size());
ElementOps().DestructArray(GetElement(pos), count);
vec.erase(pos*stride(), count*stride()); // erase(ssize_t i, ssize_t n);
}
bool genop_erase(ptr<IObject> obj, const FieldPath& path, ssize_t pos, ssize_t count);
inline void reverse()
{
if (!vec.empty())
{
octet_t* p1 = vec.data();
octet_t* p2 = vec.data_end() - stride();
while(p1 < p2)
{
memswap(p1,p2,stride());
p1 += stride();
p2 -= stride();
}
}
}
inline ssize_t GetElementFrequency(const void* value) const
{
cxAssert(value);
ssize_t count = 0;
ssize_t n = size();
for (ssize_t i=0 ; i < n ; ++i)
{
if (ElementOps().Equal(GetElement(i),value)) ++count;
}
return count;
}
inline void sort()
{
cxAssert(0); // todo
}
const ReflectedVector& rv;
VectorOfByte& vec;
};
} // namespace ceda