IPersistable.h

// IPersistable.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006

@import "cxPersistStore.h"
@import "OID.h"
@import "Ceda/cxObject/Object.h"
@import "Ceda/cxObject/CSpace.h"

namespace ceda
{
struct PSpace;

///////////////////////////////////////////////////////////////////////////////////////////////////
// PersistObjState

@def bool CEDA_VALIDATE_MARK_AS_DIRTY = false

// System required state in every persistable object
$struct+ PersistObjState
{
    PersistObjState() {}

    // Redefine copy semantics to comply with creation of a new distinct object
    // Don't copy the OID.  Instead the copy has a null OID.
    PersistObjState(const PersistObjState& rhs) {}
    PersistObjState& operator=(const PersistObjState& rhs) { return *this; }

    @if (CEDA_VALIDATE_MARK_AS_DIRTY)
    {
        uint32 m_crc = 0;   // CRC of serialised state used to validate calls to MarkAsDirty()
    }
    OID m_oid;   // Uniquely identifies the object within the PersistStore
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// IPersistable

$interface+ IPersistable : IObject
{
    void VisitPrefs(IPrefVisitor& v) const;
    bool RepresentsSubTree() const;
    void Serialise(Archive& ar) const;
    void Deserialise(InputArchive& ar);
    void OnCreate(bool local);
    PersistObjState& GetPersistObjState() const;
};

$function+ inline OID GetOid(ptr<const IPersistable> po) 
{ 
    return po->GetPersistObjState().m_oid; 
}

// todo: why is this in a public header?  Seems too dangerous.
inline void SetOid(ptr<const IPersistable> po, OID oid) { po->GetPersistObjState().m_oid = oid; }

@if (CEDA_VALIDATE_MARK_AS_DIRTY)
{
    inline uint32 GetCrc32(ptr<const IPersistable> po) { return po->GetPersistObjState().m_crc; }
    inline void SetCrc32(ptr<const IPersistable> po, uint32 crc) { po->GetPersistObjState().m_crc = crc; }
}

// It is vital that the programmer call this for each persistable object that is modified 
// as part of a transaction.  
//
// Failure to call this function may result in changes not being written to disk.
// Note that this function doesn't allocate OIDs to objects reachable from po.
//
// It is not necessary to set the PSpace in thread local storage
$function+ void MarkPersistentAsDirtyWithoutTrace(ptr<const IPersistable> po);

// Declares that 'child' is reachable from 'parent', so 'child' should have an OID allocated and be
// added to the PSpace (if not already)
//
// It is not necessary to set the PSpace in thread local storage
$function+ void DeclareReachable(ptr<const IPersistable> parent, ptr<const IPersistable> child, bool trace = true);

// If po has an associated PSpace then this function requires it to have been locked.
// It is not necessary to set either the CSpace or PSpace in thread local storage.
$function+ void OnGarbageCollectPersistable(ptr<const IPersistable> po);

$function+ ptr<IPersistable> BindObjectGivenOid(OID oid);
$function+ ptr<IPersistable> BindObjectInMemoryGivenOid(OID oid);
$function+ ptr<IPersistable> TryBindObjectGivenOid(OID oid);

/*
Async bind to the IPersistable object for the given oid which must not be null.
*/
$function+ ptr<IPersistable> AsyncBindObjectInMemoryGivenOid(OID oid);

// Makes a deep copy of the given object which may or may not be persistent (i.e. have an OID)
// The copy is created synchronously and is assumed to fit entirely in memory
// returns null if any object was found which is not reflected or is has no reflected create 
// function.
$function+ ptr<IPersistable> SynchronousDeepCopyPersistableObject( ptr<IPersistable> po );

// Called after inserting the given variable 'child' under 'parent' in order to ensure oids are 
// allocated (for when the variable contain prefs to freshly allocated objects).
cxPersistStore_API void DeclareVariableReachable(const TypeOps& rt, ptr<const IPersistable> parent,  const void* child);

$mixin PersistableMixin
{
public:
    ~PersistableMixin()
    {
        cxAssert( !IsDirty() );
    }

    // Implementation of IPersistable
    PersistObjState& GetPersistObjState() const { return m_persistObjState; }
    
    void OnGarbageCollect() const
    {
        OnGarbageCollectPersistable($this);
        BaseClass::OnGarbageCollect();
    }

    bool IsDirty() const { return $this->GetIObjectSysState().GetFlag(DBP_PO_DIRTY); }

    void OnCreate(bool local) {}
    void VisitPrefs(IPrefVisitor& v) const {}
    bool RepresentsSubTree() const { return true; }

    // It is not necessary to set the PSpace in thread local storage
    void MarkAsDirtyWithoutTrace() const 
    { 
        @if (CEDA_VALIDATE_MARK_AS_DIRTY)
        {
            if (m_persistObjState.m_oid)
            {
                $this->GetIObjectSysState().SetFlag(DBP_PO_MARKED_DIRTY_CRC);
            }
        }

        if (!IsDirty())
        {
            MarkPersistentAsDirtyWithoutTrace($this); 
        }
    }

    PSpace* GetParentPSpace() const 
    { 
        if (CSpace* cs = BaseClass::GetParentCSpace())
        {
            return (PSpace*) GetPtrSlot(cs,PTR_SLOT_PSpace);
        }
        else
        {
            return nullptr;
        }
    }
protected:
    mutable PersistObjState m_persistObjState;
};

// This overload is consistent with GetOid(ptr<const IPersistable> po) and allows for 
// better performance where virtual calls are not required
template <typename Base>
inline OID GetOid(const PersistableMixin<Base>* p) { return p->GetPersistObjState().m_oid; }

} // namespace ceda