A ReflectedByteCode allows for the following fundamental operations to be performed on a reflected variable:
construction destruction copy construction copy assignment writing to an archive reading from an archive comparison operators < and == writing to an xostream visiting IObjects visiting prefs
However this is not particularly efficient (because of the switch statements when processing byte code), and the problem is particularly severe for large arrays of variables where there is a tendency to process the bytecode over and over again for every element of the array. Note that there is no reason not to make the function
void ConstructReflectedArrayVariable(ReflectionByteCode rbc, ssize_t size, void* data) { cxAssert(data); cxAssert(size >= 0); ssize_t elementSize = rbc.GetTypeSize(); octet_t* p = reinterpret_cast(data); for (ssize_t i=0 ; i < size ; ++i) { ConstructReflectedVariable(rbc,p); p += elementSize; } }
instead use its own switch statement, instead of calling ConstructReflectedVariable() for each element. That would be much better for arrays.
Nonetheless switch statements for these fundamental operations can be an overhead too great to bear, such as in an implementation of a B+Tree. This overhead can be reduced substantially by assuming the datatype has dedicated functions for these operations, and we record pointers to these functions.
Furthermore, to avoid the overheads of function calls, these fundamental operations are typically array based. For flexibility we allow for an array of fixed size tuples where each tuple contains a variable reflected in this way. This leads to the concept of a stride parameter, used to advance from one variable to the next within the array of tuples.
This allows array processing of an array of (key,payload) pairs recorded in leaf nodes of a
B+Tree, even though we reflect the key and payload datatypes but not the pair
Note that a TypeOps can be implemented in terms of a ReflectionByteCode, so although not terribly efficient with JIT compilation, it would allow a scripting language to define xmaps!
In order to use enable_if we need static information about the capabilties of type T. E.g.
Currently we have this:
template< typename T > struct ClassTypeTraits { static const bool has_reflected_default_ctor = false; static const bool has_reflected_dtor = false; static const bool has_reflected_copy_ctor = false; static const bool has_reflected_equality = false; static const bool has_reflected_compare = false; static const bool has_reflected_write_xostream = false; static const bool has_reflected_serialise = false; static const bool has_reflected_visit_IObjects = false; static const bool has_reflected_visit_Prefs = false; .... };
Xcpp provides this information for classes. Not so for variants, so it's incomplete.
Consider this model
$model { int32 X; xstring Y[2]; };
It might be appropriate for a ReflectedModelField to record a const TypeOps* for the field. This might be very useful in frameworks that want to perform operations on fields of models, without needing to use the byte code.
$struct+ ReflectedModelField { ConstStringZ m_name; ssize_t m_offset; const octet_t* m_bc; RSN m_rsn1; RSN m_rsn2; const PtrMapOrSetFns* m_mapOrSetFns; // Proposal : add this const TypeOps* m_typeOps; };
For example, m_typeOps->m_size provides very fast access to the size of the field.
It should be possible to use &GetTypeOps
typedef void (__stdcall *ConstructVariableFn)(void* p);
typedef void (__stdcall *DestructVariableFn)(void* p);
typedef void (__stdcall *SerialiseVariableFn)(Archive& ar, const void* p);
typedef InputArchive (__stdcall *DeserialiseVariableFn)(InputArchive ar, void* p);
typedef void (__stdcall *VisitIObjectsFn)(IObjectVisitor& v, const void* p);
typedef void (__stdcall *VisitPrefsFn)(IPrefVisitor& v, const void* p);
typedef void (__stdcall *CopyConstructVariableFn)(void* p1, const void* p2);
typedef void (__stdcall *AssignVariableFn)(void* p1, const void* p2);
typedef bool (__stdcall *CompareVariableFn)(const void* p1, const void* p2);
typedef void (__stdcall *PrintVariableFn)(xostream& os, const void* p);
Contruct/Assign lhs so it holds the value recorded in rhs. Returns false if lhs cannot hold the value of rhs.
If 'allowExplicitConversions' is true then coercions are enabled.
If allowExplicitConversions is false then the function succeeds if and only if the set of values of the type of lhs contains the current value of rhs. For example if lhs is of type int8 and the rhs is an int32 with the value 100 then the function succeeds. The way a value in rhs happens to be encoded should be irrelevant.
The type of one of the variables 'self' is implicit, as when these functions pointers appear in a TypeOps. The type of the other variable is defined by a given ReflectionByteCode
typedef bool (__stdcall *ConstructVariableFromSelfFn)(
ReflectionByteCode lhs_rbc, const void* lhs,
const void* rhs, // self
bool allowExplicitConversions);
typedef bool (__stdcall *ConstructSelfFromVariableFn)(
void* lhs, // self
ReflectionByteCode rhs_rbc, const void* rhs,
bool allowExplicitConversions);
typedef bool (__stdcall *AssignVariableFromSelfFn)(
ReflectionByteCode lhs_rbc, const void* lhs,
const void* rhs, // self
bool allowExplicitConversions);
typedef bool (__stdcall *AssignSelfFromVariableFn)(
void* lhs, // self
ReflectionByteCode rhs_rbc, const void* rhs,
bool allowExplicitConversions);
The following functions are typically parameterised in
typedef void (__stdcall *ConstructArrayFn)( void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *DestructArrayFn)( void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *SerialiseArrayFn)(Archive& ar, const void* A, ssize_t s, ssize_t n);
typedef InputArchive (__stdcall *DeserialiseArrayFn)(InputArchive ar, void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *VisitObjectsInArrayFn)(IObjectVisitor& v, const void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *VisitPrefsInArrayFn)(IPrefVisitor& v, const void* A, ssize_t s, ssize_t n);
typedef ssize_t (__stdcall *FindInArrayFn)(const void* A, ssize_t s, ssize_t n, const void* v);
typedef void (__stdcall *CopyConstructArrayFn)(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n);
typedef void (__stdcall *AssignArrayFn)(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n);
// Used to generate an implementation of a function of type VisitPrefsFn that is appropriate for
// a variable of type T. This is used to fill in the 'visitPrefsFn' member of FieldParams.
template
void __stdcall MakeVisitPrefsFunction(IPrefVisitor& v, const void* p)
{
v << * (T*) p;
}
struct TypeOps
{
ssize_t GetSize() const
{
return m_size;
}
bool HasConstruct() const
{
return m_ConstructArrayFn != nullptr;
}
void ConstructArray(void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
cxAssert(m_ConstructArrayFn);
m_ConstructArrayFn(A,s,n);
}
void ConstructVariable(void* p) const
{
cxAssert(p);
cxAssert(m_ConstructArrayFn);
m_ConstructArrayFn(p,0,1);
}
bool HasDestruct() const
{
return m_DestructArrayFn != nullptr;
}
void DestructArray(void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
if (m_DestructArrayFn) m_DestructArrayFn(A,s,n);
}
void DestructVariable(void* p) const
{
cxAssert(p);
if (m_DestructArrayFn) m_DestructArrayFn(p,0,1);
}
bool HasSerialise() const
{
return m_SerialiseArrayFn != nullptr;
}
void SerialiseArray(Archive& ar, const void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
cxAssert(m_SerialiseArrayFn);
m_SerialiseArrayFn(ar,A,s,n);
}
void SerialiseVariable(Archive& ar, const void* p) const
{
cxAssert(p);
cxAssert(m_SerialiseArrayFn);
m_SerialiseArrayFn(ar,p,0,1);
}
bool HasDeserialise() const
{
return m_DeserialiseArrayFn != nullptr;
}
InputArchive DeserialiseArray(InputArchive ar, void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
cxAssert(m_DeserialiseArrayFn);
return m_DeserialiseArrayFn(ar,A,s,n);
}
InputArchive DeserialiseVariable(InputArchive ar, void* p) const
{
cxAssert(p);
cxAssert(m_DeserialiseArrayFn);
return m_DeserialiseArrayFn(ar,p,0,1);
}
bool HasVisitObjects() const
{
return m_VisitObjectsInArrayFn != nullptr;
}
void VisitObjectsInArray(IObjectVisitor& v, const void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
if (m_VisitObjectsInArrayFn) m_VisitObjectsInArrayFn(v,A,s,n);
}
void VisitObjectsInVariable(IObjectVisitor& v, const void* p) const
{
cxAssert(p);
if (m_VisitObjectsInArrayFn) m_VisitObjectsInArrayFn(v,p,0,1);
}
bool HasVisitPrefs() const
{
return m_VisitPrefsInArrayFn != nullptr;
}
void VisitPrefsInArray(IPrefVisitor& v, const void* A, ssize_t s, ssize_t n) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
if (m_VisitPrefsInArrayFn) m_VisitPrefsInArrayFn(v,A,s,n);
}
void VisitPrefsInVariable(IPrefVisitor& v, const void* p) const
{
cxAssert(p);
if (m_VisitPrefsInArrayFn) m_VisitPrefsInArrayFn(v,p,0,1);
}
bool HasCopyConstruct() const
{
return m_CopyConstructArrayFn != nullptr;
}
void CopyConstructArray(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n) const
{
cxAssert(0 <= n);
cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
cxAssert(m_CopyConstructArrayFn);
m_CopyConstructArrayFn(A1,s1,A2,s2,n);
}
void CopyConstructVariable(void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_CopyConstructArrayFn);
m_CopyConstructArrayFn(p1,0,p2,0,1);
}
bool HasAssign() const
{
return m_AssignArrayFn != nullptr;
}
void AssignArray(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n) const
{
cxAssert(0 <= n);
cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
cxAssert(m_AssignArrayFn);
m_AssignArrayFn(A1,s1,A2,s2,n);
}
void AssignVariable(void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_AssignArrayFn);
m_AssignArrayFn(p1,0,p2,0,1);
}
/*
todo: it may be appropriate to support lexicographic compare on arrays. Maybe a version
that returns -1, 0 or 1
*/
bool HasLess() const
{
return m_lessFn != nullptr;
}
bool Less(const void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_lessFn);
return m_lessFn(p1,p2);
}
bool LessOrEqual(const void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_lessFn);
return !m_lessFn(p2,p1);
}
bool Greater(const void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_lessFn);
return m_lessFn(p2,p1);
}
bool GreaterOrEqual(const void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_lessFn);
return !m_lessFn(p1,p2);
}
bool HasEqual() const
{
return m_equalsFn != nullptr;
}
bool Equal(const void* p1, const void* p2) const
{
cxAssert(p1);
cxAssert(p2);
cxAssert(m_equalsFn);
return m_equalsFn(p1,p2);
}
bool HasPrint() const
{
return m_PrintVariableFn != nullptr;
}
void PrintVariable(xostream& os, const void* p) const
{
cxAssert(p);
cxAssert(m_PrintVariableFn);
m_PrintVariableFn(os,p);
}
bool HasLowerBound() const
{
return m_LowerBoundFn != nullptr;
}
ssize_t LowerBound(const void* A, ssize_t s, ssize_t n, const void* p) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
cxAssert(p);
cxAssert(m_LowerBoundFn);
return m_LowerBoundFn(A,s,n,p);
}
bool HasUpperBound() const
{
return m_UpperBoundFn != nullptr;
}
ssize_t UpperBound(const void* A, ssize_t s, ssize_t n, const void* p) const
{
cxAssert(0 <= s);
cxAssert(0 <= n);
cxAssert(n == 0 || (A != nullptr && s > 0));
cxAssert(p);
cxAssert(m_UpperBoundFn);
return m_UpperBoundFn(A,s,n,p);
}
ssize_t ReverseLowerBound(const void* A, ssize_t s, ssize_t n, const void* p) const
{
return UpperBound(A,s,n,p)-1;
}
ssize_t ReverseUpperBound(const void* A, ssize_t s, ssize_t n, const void* p) const
{
return LowerBound(A,s,n,p)-1;
}
// Returns index 0..n-1 or else n if not found
ssize_t FindFirst(const void* A, ssize_t s, ssize_t n, const void* p) const
{
ssize_t i = LowerBound(A, s, n, p);
return (i != n && Equal(offset_ptr(A, i*s), p)) ? i : n;
}
// Returns index 0..n-1 or else -1 if not found
ssize_t FindLast(const void* A, ssize_t s, ssize_t n, const void* p) const
{
ssize_t i = UpperBound(A, s, n, p) - 1;
return (i != -1 && Equal(offset_ptr(A, i*s), p)) ? i : -1;
}
//////////////////////////// state /////////////////////////////
ssize_t m_size;
CompareVariableFn m_equalsFn;
CompareVariableFn m_lessFn;
PrintVariableFn m_PrintVariableFn;
ConstructArrayFn m_ConstructArrayFn;
CopyConstructArrayFn m_CopyConstructArrayFn;
AssignArrayFn m_AssignArrayFn;
DestructArrayFn m_DestructArrayFn;
SerialiseArrayFn m_SerialiseArrayFn;
DeserialiseArrayFn m_DeserialiseArrayFn;
VisitObjectsInArrayFn m_VisitObjectsInArrayFn;
VisitPrefsInArrayFn m_VisitPrefsInArrayFn;
FindInArrayFn m_LowerBoundFn;
FindInArrayFn m_UpperBoundFn;
ConstructVariableFromSelfFn m_ConstructVariableFromSelfFn;
ConstructSelfFromVariableFn m_ConstructSelfFromVariableFn;
AssignVariableFromSelfFn m_AssignVariableFromSelfFn;
AssignSelfFromVariableFn m_AssignSelfFromVariableFn;
};