IObject.h

// IObject.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2007

@import "MacroUtils.h"
@import "cxObject.h"
@import "FieldPath.h"
@import "TypeTraits.h"
@import "TypeOps.h"
@import "EnumBase.h"
@import "Interfaces.h"
@import "ObjSysState.h"
#include "Ceda/cxUtils/IException.h"
#include "Ceda/cxUtils/xvector.h"
#include "Ceda/cxUtils/xostream.h"
#include "Ceda/cxUtils/Archive.h"
#include "Ceda/cxUtils/ArchiveCollection.h"
#include "Ceda/cxUtils/xstring.h"
#include "Ceda/cxUtils/BasicTypes.h"
#include <cstddef>
#include <atomic>

@def bool CEDA_ENABLE_STRING_TABLES = true

#ifdef _MSC_VER
    // 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
    #pragma warning(disable:4251)
#endif

#ifdef __clang__
    // todo: don't inherit state m_objSysState using IObjectBaseMixin, instead change Xcpp to include this
    // data member variable at same time other data member variables are defined, so that we tend to 
    // generate classes with standard layout
    #pragma clang diagnostic ignored "-Winvalid-offsetof"
#endif

#define CONST_FIELD_TO_CLASS_CAST(className,fieldName,fieldptr)  \
    reinterpret_cast<const className*>(reinterpret_cast<const ceda::octet_t*>(fieldptr) - offsetof(className,fieldName))

#define FIELD_TO_CLASS_CAST(className,fieldName,fieldptr)  \
    reinterpret_cast<className*>(reinterpret_cast<ceda::octet_t*>(fieldptr) - offsetof(className,fieldName))

#define CONST_ATTRIB_CAST(name,param)  reinterpret_cast<const name*>(reinterpret_cast<const ceda::octet_t*>(this) - offsetof(name,param))
#define ATTRIB_CAST(name,param)  reinterpret_cast<name*>(reinterpret_cast<ceda::octet_t*>(this) - offsetof(name,param))

#define CEDA_MAX1(a) (a)
#define CEDA_MAX2(a,b) ((a) > (b) ? (a) : (b))
#define CEDA_MAX3(a,b,c) CEDA_MAX2(CEDA_MAX2(a,b), c)
#define CEDA_MAX4(a,b,c,d) CEDA_MAX2(CEDA_MAX3(a,b,c), d)
#define CEDA_MAX5(a,b,c,d,e) CEDA_MAX2(CEDA_MAX4(a,b,c,d), e)
#define CEDA_MAX6(a,b,c,d,e,f) CEDA_MAX2(CEDA_MAX5(a,b,c,d,e), f)
#define CEDA_MAX7(a,b,c,d,e,f,g) CEDA_MAX2(CEDA_MAX6(a,b,c,d,e,f), g)
#define CEDA_MAX8(a,b,c,d,e,f,g,h) CEDA_MAX2(CEDA_MAX7(a,b,c,d,e,f,g), h)
#define CEDA_MAX9(a,b,c,d,e,f,g,h,i) CEDA_MAX2(CEDA_MAX8(a,b,c,d,e,f,g,h), i)
#define CEDA_MAX10(a,b,c,d,e,f,g,h,i,j) CEDA_MAX2(CEDA_MAX9(a,b,c,d,e,f,g,h,i), j)
#define CEDA_MAX11(a,b,c,d,e,f,g,h,i,j,k) CEDA_MAX2(CEDA_MAX10(a,b,c,d,e,f,g,h,i,j), k)
#define CEDA_MAX12(a,b,c,d,e,f,g,h,i,j,k,l) CEDA_MAX2(CEDA_MAX11(a,b,c,d,e,f,g,h,i,j,k), l)
#define CEDA_MAX13(a,b,c,d,e,f,g,h,i,j,k,l,m) CEDA_MAX2(CEDA_MAX12(a,b,c,d,e,f,g,h,i,j,k,l), m)
#define CEDA_MAX14(a,b,c,d,e,f,g,h,i,j,k,l,m,n) CEDA_MAX2(CEDA_MAX13(a,b,c,d,e,f,g,h,i,j,k,l,m), n)
#define CEDA_MAX15(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o) CEDA_MAX2(CEDA_MAX14(a,b,c,d,e,f,g,h,i,j,k,l,m,n), o)
#define CEDA_MAX16(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) CEDA_MAX2(CEDA_MAX15(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o), p)
#define CEDA_MAX17(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q) CEDA_MAX2(CEDA_MAX16(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p), q)
#define CEDA_MAX18(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r) CEDA_MAX2(CEDA_MAX17(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q), r)
#define CEDA_MAX19(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s) CEDA_MAX2(CEDA_MAX18(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r), s)
#define CEDA_MAX20(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t) CEDA_MAX2(CEDA_MAX19(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s), t)

#define VARIANT_TAG(t) _mapvalue_<t >::tag

namespace ceda
{
$struct ReflectedInterface;
$struct ReflectedClass;
$struct ReflectedVariant;
$struct ReflectedEnum;
$struct ReflectedFunctor;
$struct ReflectedTypedef;
$struct ReflectedGlobalFunction;
$struct ReflectedGlobalVariable;

$extern+ IObjectVisitor;
$extern+ IPrefVisitor;
$extern+ FieldPath;
$extern+ InputArchive;
$extern+ Archive;
 
@def mWriteArchiveOperatorsForStruct(name,members) =
{
    template<typename Archive>
    void Serialise(Archive& ar, const name& x)
    {
        ar @for(i in members){ << x.i};
    }
    template<typename Archive>
    void Deserialise(Archive& ar, name& x)
    {
        ar @for(i in members){ >> x.i};
    }
}

@def mWriteOstreamOperatorForStruct(name,members) =
{
    inline ceda::xostream& operator<<(ceda::xostream& os, const name& x)
    {
        os @for(i in members){ << @str( i=) << x.i};
	    return os;
    }
}

@def mWriteOstreamOperatorForStruct2(name,members) =
{
    inline ceda::xostream& operator<<(ceda::xostream& os, const name& x)
    {
        os.flush();
        os.Indent(2);
        os @for(i in members){ << '\n' << @str(i=) << x.i};
        os.flush();
        os.Indent(-2);
	    return os;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*
@platform           @system_name        @system_processor       @size_of_pointer
--------------------------------------------------------------------------------
 windows-x86         Windows                  AMD64                    4
 windows-x64         Windows                  AMD64                    8
 linux-x86           Linux                    x86                      4
 linux-x86_64        Linux                    x86_64                   8
 darwen-x86          Darwen                   x86
 darwen-x64          Darwen
 android-arm64-v8a   Android                  aarch64                  8
*/

@def+ bool CEDA_IS_WINDOWS_PLATFORM =
{
    @str(@system_name) == "Windows"
}

@def+ bool CEDA_IS_LINUX_PLATFORM =
{
    @str(@system_name) == "Linux"
}

@def+ bool CEDA_IS_MACOSX_PLATFORM =
{
    @str(@system_name) == "Darwen"
}

@def+ bool CEDA_IS_IOS_PLATFORM =
{
    @str(@system_name) == "iOS"
}

@def+ bool CEDA_IS_ANDROID_PLATFORM =
{
    @str(@system_name) == "Android"
}

@def+ bool CEDA_IS_X86_MACHINE =
{
    @str(@system_processor) == "x86"
}

@def+ bool CEDA_IS_X64_MACHINE =
{
    @str(@system_processor) == "x86_64" ||
    @str(@system_processor) == "AMD64"
}

@def+ bool CEDA_32_BIT_POINTERS =
{
    @str(@size_of_pointer) == "4"
}

@def+ bool CEDA_64_BIT_POINTERS =
{
    @str(@size_of_pointer) == "8"
}

/*
ssize_t is defined in BasicTypes.h.  For gcc based compilers it is the same as __PTRDIFF_TYPE__
which can be a platform dependent type like long.  Therefore we need to map it to an appropriate
platform independent type int32/int64.  We use <<extern>> to avoid complaints from the compiler
about an inconsistent redefinition of a typedef.
*/
@if (CEDA_32_BIT_POINTERS)
{
    $typedef+ <<extern>> int32 ssize_t;
}
@elseif (CEDA_64_BIT_POINTERS)
{
    $typedef+ <<extern>> int64 ssize_t;
}
@else
{
    @assert(false)
}

$typedef+ const char8* ConstString8Z;
$typedef+ const char16* ConstString16Z;

$typedef+ char8 xchar;
$typedef+ string8 xstring;
$typedef+ const char8* ConstStringZ;

///////////////////////////////////////////////////////////////////////////////////////////////////
// $variant helper functions
//
// These functions assume that a $variant variable consists of an int32 tag field followed by
// the data.

inline int32 GetVariantTag(const void* v)
{
    return *reinterpret_cast<const int32*>( v );
}

// NOTE existing variant data may need to be destructed before changing the tag
//      and the new data will need to be constructed before using the variant after this call.
inline void SetVariantTag(void* v, int32 tag)
{
    *reinterpret_cast<int32*>( v ) = tag;
}

inline const void* GetVariantDataAddress(const void* v)
{
    return reinterpret_cast<const int32*>( v ) + 1;
}

inline void* GetVariantDataAddress(void* v)
{
    return reinterpret_cast<int32*>( v ) + 1;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// TIN, RSN, Support for schema evolution

// At run time each target that registers with the TargetRegistry is assigned its own unique 
// Target Index Number (TIN)
$typedef+ ssize_t TIN;

// Each target has its own Release Sequence Number (RSN) to uniquely identify each formal release
// of the target. RSNs start at 0.
$typedef+ ssize_t RSN;

$adt XTarget;


///////////////////////////////////////////////////////////////////////////////////////////////////
// IObject

// Whereever it makes sense, information about the type is obtained via ReflectedClass, 
// not IObject. This helps make IObject as small as possible.

$interface+ IObject
{
    // Query this physical object for the given interface.  Returns null if interface not supported.
    // It must be possible to query any supported interface to find any other supported interface.
    // The supported interfaces may not change during the execution of the process.
    // QueryInterface must respect physical identity.
    // Formally we have idempotency, symmetry and transitivity requirements.
    AnyInterface QueryInterface(const ReflectedInterface& ri);

    // Bind to an interface that is logically supported by the object.  It is possible that a
    // physically different object is returned.
    // Bind is not necessarily idempotent, transitive or symmetric.
    AnyInterface BindInterface(const ReflectedInterface& ri);

    const ReflectedClass* GetReflectedClass() const;
    void VisitObjects(IObjectVisitor& v) const;
    void OnGarbageCollect() const;

    // Called without a CSpace lock
    void Destroy() const;

    // GetIObjectSysState() returns a reference to the ObjSysState, allowing a client to only make one 
    // virtual call in order to access all the system state.
    ObjSysState& GetIObjectSysState() const;
};

///////////////////////////////////////////////////////////////////////////////////////////////////

inline uint32 GetIObjectFlags(ptr<const IObject> obj)
{ 
    return obj->GetIObjectSysState().oss_objflags;
}

inline bool GetIObjectFlag(ptr<const IObject> obj, int bitPos)
{ 
    return (GetIObjectFlags(obj) & (1 << bitPos)) != 0;
}

inline void SetIObjectFlag(ptr<const IObject> obj, int bitPos, bool state)
{ 
    uint32& flags = obj->GetIObjectSysState().oss_objflags;
    if (state) flags |= (1 << bitPos);
    else       flags &= ~(1 << bitPos);
}

/*
// Called soon just after $newing an object to mark it as a ROD object.  
$function+ inline void SetReplicateOnDemand(ptr<const IObject> obj)
{
    SetIObjectFlag(obj, DBP_REPLICATE_ON_DEMAND, true);
}
*/

///////////////////////////////////////////////////////////////////////////////////////////////////
// Registration of object with CSpace using $new

$function+ void RegisterGcObject(ptr<const IObject> object);

/*
RegisterNonGcObject() sets the given object to be associated with the CSpace in thread local storage.

The CSpace must be set in thread local storage, but it doesn't need to be locked.

This allows objects in the CSpace to avoid being deleted by the GC.  This is appropriate for the
following:

    -   global variables
    -   member variables 
    -   frame variables
    
These objects can take part in the trace algorithm but they don't take part in the sweep phase.
The implementation avoids recording them in the GC-extent.  It also avoids marking them as visited
(avoiding the problem of how to reset the visited flag at the start of each trace).  However that
means that a cycle amongst non-gc objects must be avoided or else the gc trace gets stuck in an 
infinite loop.
*/
$function+ void RegisterNonGcObject(ptr<const IObject> object);

template <typename T>
T* DollarNew(T* p)
{
    RegisterGcObject(p);
    return p;
}

template <typename T>
T gc(T p)
{
    if (p) RegisterGcObject(p);
    return p;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

// is_reflected<T>::value equals true if and only _GetReflected(const T*) exists
// Xcpp generates _GetReflected(const T*) for reflected interfaces, classes, variants, enums and functors.
template <typename T> using is_reflected_t = decltype(_GetReflected(std::declval<T const *>()));
template <typename T> using is_reflected = is_detected<is_reflected_t, typename std::remove_cv<T>::type>;

template<typename T>
using IsReflectedInterface = std::is_same<decltype(_GetReflected((const T*)nullptr)), const ReflectedInterface&>;

//template<typename T>
//using IsReflectedClass = std::conjunction<is_reflected<T>, std::is_same<decltype(_GetReflected(std::declval<T const *>())), const ReflectedClass&>>;

template<typename T, typename Enable=void>
struct IsReflectedClass : std::false_type {};

template<typename T>  
struct IsReflectedClass<T, std::enable_if_t<is_reflected<T>::value && std::is_same<decltype(_GetReflected(std::declval<T const *>())), const ReflectedClass&>::value>>
    : std::true_type {};

template<typename T>
using IsReflectedVariant = std::is_same<decltype(_GetReflected((const T*)nullptr)), const ReflectedVariant&>;

template<typename T>
using IsReflectedEnum = std::is_same<decltype(_GetReflected((const T*)nullptr)), const ReflectedEnum&>;

template<typename T>
using IsReflectedFunctor = std::is_same<decltype(_GetReflected((const T*)nullptr)), const ReflectedFunctor&>;

@for (rType in [Interface,Class,Variant,Enum,Functor])  // not Typedef,GlobalFunction,GlobalVariable
{
    @def R = Reflected@@rType

    template <typename T>
    const R& Get@@R()
    {
        static_assert(Is@@R<T>::value);
        return _GetReflected((T*)0);
    }
    template <typename T>
    ConstStringZ Get@@rType@@Name()
    {
        return Get@@R<T>().GetName();
    }
    
}

// Ipc interfaces are typically not reflected, so we provide a function that gives the qualified name
// without any need for a ReflectedInterface.
template <typename T>
ConstStringZ GetIpcInterfaceName()
{
    return _GetIpcInterfaceName((T*) 0);
}

template <typename I> 
typename std::enable_if<!std::is_const<I>::value, ptr<I> >::type qicast(ptr<IObject> obj)
{
    if (obj)
    {
        AnyInterface a = obj->QueryInterface(GetReflectedInterface<I>());
        return ptr<I>(a.m_self, a.m_table);

        // g++ 4.8: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
        //return reinterpret_cast<ptr<I>&>(a);
    }
    else
    {
        return ptr<I>();
    }
}
template <typename I> 
typename std::enable_if<std::is_const<I>::value, ptr<I> >::type qicast(ptr<const IObject> obj)
{
    if (obj)
    {
        AnyInterface a = const_interface_cast<IObject>(obj)->QueryInterface(GetReflectedInterface<I>());
        return ptr<I>(a.m_self, a.m_table);

        // g++ 4.8: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
        //return reinterpret_cast<ptr<I>&>(a);
    }
    else
    {
        return ptr<I>();
    }
}

template <typename I> ptr<I> bicast(ptr<IObject> obj)
{
    if (obj)
    {
        AnyInterface a = obj->BindInterface(GetReflectedInterface<I>());
        return ptr<I>(a.m_self, a.m_table);

        // g++ 4.8: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
        //return reinterpret_cast<ptr<I>&>(a);
    }
    else
    {
        return ptr<I>();
    }
}

// qccast should be used with extreme caution, the programmer must know that the object pointed 
// to by p is of type C.  
// If I is a subtype of IObject and C is reflected then in debug builds qccast is validated
// Note that qccast cannot cast away constness.
#ifdef CEDA_CHECK_ASSERTIONS
    template <typename C, typename I>
    typename std::enable_if<!(is_superinterface_of<const IObject,I>::value && is_reflected<C>::value), C*>::type qccast(ptr<I> p)
    {
        // This implementation doesn't allow qccast to cast away constness.  This is achieved by 
        //    1) using self() on the ptr<I> which either returns a void* or const void* depending 
        //       on whether I is const qualified
        //    2) using reinterpret_cast<C*>() which cannot cast away constness
        return reinterpret_cast<C*>(p.self());
    }

    template <typename C, typename I>
    typename std::enable_if<is_superinterface_of<const IObject,I>::value && is_reflected<C>::value, C*>::type qccast(ptr<I> p)
    {
        if (p && p->GetReflectedClass() == &GetReflectedClass<C>())
            return reinterpret_cast<C*>(p.self());
        else
            return nullptr;
    }
#else
    template <typename C, typename I>
    C* qccast(ptr<I> p)
    {
        return reinterpret_cast<C*>(p.self());
    }
#endif

template <typename C, typename I> C* tryqccast(ptr<I> p)
{
    if (p && p->GetReflectedClass() == &GetReflectedClass<C>())
    {
        return reinterpret_cast<C*>(p.self());
    }
    return nullptr;
}

template <typename T>
ptr<IObject> CreateInstance()
{
    ptr<IObject> p = GetReflectedClass<T>().createFn();
    RegisterGcObject(p);
    return p;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

class prefbase;
struct FieldId;

struct PseudoOid
{
    bool IsNull() const
    {
        return low_ == 0 && high_ == 0;
    }
    
    bool operator==(const PseudoOid& other) const
    {
        return low_ == other.low_ && high_ == other.high_; 
    }
    bool operator!=(const PseudoOid& other) const
    {
        return low_ != other.low_ || high_ != other.high_; 
    }
    bool operator<(const PseudoOid& other) const
    { 
        return high_ < other.high_ || (high_ == other.high_ && low_ < other.low_);
    }

    int32 low_;
    int32 high_;
};

struct PseudoFieldId
{
    FieldId& cast() { return (FieldId&) *this; }
    const FieldId& cast() const { return (const FieldId&) *this; }

    PseudoOid oid;
    FieldPath path;
};

// When this is generated the oid member is not initialised.
struct FieldParams
{
    ptr<IObject> obj;
    PseudoFieldId fid;
    void* addr;
};

///////////////////////////////////////////////////////////////////////////////////////////////////

// Read/write barriers for an independent field that takes part in the Dependency Graph System 
// (DGS)
// obj is an IObject that contains a field located at 'fieldAddress'.
// obj is (only) passed in order to allow the DGS to visit the object containing the field so 
// that it is strongly reachable and therefore protected from GC.
// fieldAddress must not be nullptr.  It is permissible to call with obj = null, if visiting of
// the containing object is not required.
// The type of the field is irrelevant to the DGS - its identity is simply associated with its
// address in memory.  Note therefore that it cannot be relocated (e.g. be an element of an
// xvector).
@api void DataSourceEvict(const void* fieldAddress);
@api void DataSourceReadBarrier(const void* fieldAddress, ptr<const IObject> obj, const char* name);
@api void DataSourceWriteBarrier(const void* fieldAddress, ptr<const IObject> obj, const char* name);

/*
These functions all assume obj is not null and obj->GetReflectedClass() is not null
The path identifies the field which is accessed.
If the path is empty then DataSourceEvict() evicts all DGS nodes under the given object.
*/
@api void EvictIndepFieldsUnderObject(ptr<const IObject> obj);
@api void IndepFieldEvict(ptr<const IObject> obj, const FieldPath& path);
@api void IndepFieldReadBarrier(ptr<const IObject> obj, const FieldPath& path);
@api void IndepFieldWriteBarrier(ptr<const IObject> obj, const FieldPath& path);

// Convenience functions for when we have a path to a model or array and an index into an element of the model or array
@api void IndepFieldReadBarrier(ptr<const IObject> obj, const FieldPath& pathToModelOrArray, ssize_t elementIndex);
@api void IndepFieldWriteBarrier(ptr<const IObject> obj, const FieldPath& pathToModelOrArray, ssize_t elementIndex);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Operation call backs

/*
To receive all notifications to changes to objects in the UT

The parameters for each operation is recorded in a struct (rather than directly as a 
list of args for the function call), to make forwarding of function calls more 
efficient.

For a rather messy optmisation, a null OID is included near the path, so the pair can
be treated as a FieldId once the OID has been initialised.  The whole point is simply
to avoid the need to copy the path in order to initialise a FieldId!
*/

struct AssignValue_params : public FieldParams
{
    /*
    Allows the framework to visit the prefs in the old value in order to delete objects and 
    visit the prefs in the new value in order to allocate oids etc.

    visitPrefsFn can be nullptr if the type supporting assignment never contains prefs.
    */
    VisitPrefsFn visitPrefsFn;
};

struct AssignPref_params : public FieldParams
{
    const prefbase* src;
};

struct OffsetValue_params : public FieldParams
{
    int64 offset;
};

///////////////////////////////////////////////////////////////////////////////////////////////////

@api void OCB_SetThreadLocalStorage(CSpace* cspace);
@api void PrepareThreadLocalStorage(ptr<const IObject> p);

@api void OCB_BeforeAssignValue(AssignValue_params&);
@api void OCB_AfterAssignValueWithoutPrefs(AssignValue_params&);
@api void OCB_AfterAssignValueWithPrefs(AssignValue_params&);
@api void OCB_AssignPref(AssignPref_params&);
@api void OCB_OffsetValue(OffsetValue_params&);

///////////////////////////////////////////////////////////////////////////////////////////////////

/*
genop_xxx functions

These calls are generated by xcpp for operations on a model supporting:

    -   DGS write barriers
    
    -   Allocation of OIDs to new persistent objects
    
    -   Deletion of persistent objects
    
    -   Recording of operations on a working set, allowing for data replication using
        Operational Transformation.

genop_Assign or genop_Assign_VisitPrefs are the function calls generated by 
xcpp for assignments to a field of a model.  genop_Assign_VisitPrefs is used if T may 
contain prefs.

TODO: Currently xcpp generates a WAccess() method which doesn't account for visiting of
prefs in the source of the assignment.  It seems that WAccess() shouldn't be generated when
the type being assigned may contains prefs - or perhaps WAccess() shouldn't be generated at
all.
*/

template <typename T>
inline void genop_Assign(ptr<IObject> obj, const FieldPath& path, T& field, const T& val)
{
    AssignValue_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsFn = nullptr;
    OCB_BeforeAssignValue(params);
    field = val;
    OCB_AfterAssignValueWithoutPrefs(params);
}

/*
OCB_BeforeAssignValue() is called before the assignment is performed. This allows the 
implementation to visit the prefs in the old value in order to delete objects from the store.

OCB_AfterAssignValueWithPrefs() is called after the assignment is performed.  This allows the 
implementation to visit the prefs in the new value in order to allocate oids etc.

Consider that the implementation tries to avoid the call to OCB_AfterAssignValueWithPrefs(), by instead 
passing the address of 'val' in the call to OCB_BeforeAssignValue().  This assumes visiting the
prefs under 'val' is equivalent to visiting the prefs under 'field' after the assignment.  
However, that assumption is not always valid, because the assignment 'field = val' can cause new 
objects to be allocated (e.g. xset<> assignment involves $new)
*/

template <typename T>
inline void genop_Assign_VisitPrefs(ptr<IObject> obj, const FieldPath& path, T& field, const T& val)
{
    AssignValue_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsFn = &MakeVisitPrefsFunction<T>;
    OCB_BeforeAssignValue(params);
    field = val;
    OCB_AfterAssignValueWithPrefs(params);
}

// For assignments to movable prefs
inline void genop_AssignPref(
    ptr<IObject> obj, const FieldPath& path, prefbase& field, const prefbase& newValue)
{
    AssignPref_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.src = &newValue;
    OCB_AssignPref(params);
}

template <typename T>
inline void genop_OffsetValue(ptr<IObject> obj, const FieldPath& path, T& field, const T& offset)
{
    OffsetValue_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.offset = offset;
    OCB_OffsetValue(params);
    field += offset;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/*
Returning const values to avoid calling non-const methods
---------------------------------------------------------

This is important for supporting const for operator[] on wrapped arrays, where the element 
is wrapped - meaning it is of some type returned by value.

    struct X
    {
        X foo() { return *this; }
        const X bar() const { return *this; }
    };

    X f1() { return X(); }
    const X f2() { return X(); }

    // The lines commented out below generate compiler errors
    void test()
    {
        f1().foo();
        //f2().foo();

        f1().bar();
        f2().bar();

        f1().foo().foo();
        f1().foo().bar();

        //f2().foo().foo();
        //f2().foo().bar();

        //f1().bar().foo();
        f1().bar().bar();

        //f2().bar().foo();
        f2().bar().bar();
    }
*/


/*
Common code to be injected into every ModelAttribute_XXX template
*/
@def CEDA_MODEL_ATTRIBUTE_BOILERPLATE(ModelType, ThisClass) =
{
    cxNotCloneable(ThisClass)
public:
    typedef ModelType Type;
    typedef typename Base::FinalClass FinalClass;
    
    // Non-const versions of self() and model()
    using Base::self;
    using Base::model;

    using Base::path;
    
    // Add const versions of self() and model()
    FinalClass const* self() const
    {
        return const_cast<ThisClass*>(this)->self();
    }
    Type const& model() const
    {
        return const_cast<ThisClass*>(this)->model();
    }
    
    // read() invokes the read barrier
    Type const& read() const 
    { 
        DataSourceReadBarrier(&model(),self(),nullptr);
        return model(); 
    }
    
    // Implicit conversion to the underlying type of the variable in the model
    operator Type const&() const { return read(); }
}

/*
There are a number of templates named ModelAttribute_XXX used for wrapping attributes
of various types.  The purpose is to provide the DGS read/writer barriers on access to
to the attribute, and also to provide the update operators support by Operational
Transformation.

Each template ModelAttribute_XXX is parameterised in its base class.  It assumes Base 
implements the following methods:

    struct Base
    {
        typedef ? FinalClass;
        
        FinalClass* self();
        Type& model();
        FieldPath path() const;
    };

FinalClass must implement IObject.
*/

// Wraps assignable<T>
template <typename Base, typename T>
struct ModelAttribute_assign : public Base
{
    CEDA_MODEL_ATTRIBUTE_BOILERPLATE(T,ModelAttribute_assign)
    
    void operator=(T const& v)
    { 
        genop_Assign(self(),path(),model(),nullptr,v);
    }
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// EmptyBase

struct EmptyBase 
{
    void VisitObjects(IObjectVisitor&) const {}
    void VisitPrefs(IPrefVisitor&) const {}
    //void VisitDgsNodes(IDgsNodeVisitor&) const {}
    void EvictDgsNodes() const {}
};

///////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef CEDA_CHECK_ASSERTIONS
    @api void AddInstance(ConstStringZ name, void* p);
    @api void RemoveInstance(ConstStringZ name, void* p);
    @api void ReportLeaks(xostream& os);
#endif

///////////////////////////////////////////////////////////////////////////////////////////////////
// BaseMixin

template <typename FC, typename BC>
struct BaseMixin : public BC
{
    typedef BC BaseClass;
    typedef FC FinalClass;
    
    FinalClass* GetThisAsFinalClass() 
    { 
        return static_cast<FinalClass*>(this); 
    }
    const FinalClass* GetThisAsFinalClass() const 
    { 
        return static_cast<const FinalClass*>(this); 
    }
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// IObjectBaseMixin

/*
Unfortunately this approach of inheriting ObjSysState in a base class tends to mean all our classes
are not standard layout.

We really should make Xcpp tend to put all the state into a single structure, to make it possible
to create standard layout classes/structs.
*/

template <typename FC, typename BC>
class IObjectBaseMixin : public BC
{    
public:
    typedef BC BaseClass;
    typedef FC FinalClass;

    inline FinalClass* GetThisAsFinalClass() 
    { 
        return static_cast<FinalClass*>(this); 
    }
    inline const FinalClass* GetThisAsFinalClass() const 
    { 
        return static_cast<const FinalClass*>(this); 
    }

    // By default BindInterface() calls QueryInterface().  This behaviour can easily be overridden.
    inline AnyInterface BindInterface(const ReflectedInterface& ri) { return static_cast<FinalClass*>(this)->QueryInterface(ri); }

    inline void OnGarbageCollect() const {}
    inline void Destroy() const 
    { 
        delete static_cast<const FinalClass*>(this); 
    }
    inline ObjSysState& GetIObjectSysState() const { return m_objSysState; }
    inline CSpace* GetParentCSpace() const { return m_objSysState.oss_cspace; }

private:
    mutable ObjSysState m_objSysState;
};

} // namespace ceda