17 \$cache functions using DGAsyncIndepNode
Parameterless $cache functions are treated specially because there is no need for a cache map. Instead they are more like an individual async dependent variable.
Consider the following example:
@import "Ceda/cxObject/DGAsyncNode.h"
$struct X isa ceda::IObject
{
$cache <<async>> int y() const
{
return 7;
}
};
Xcpp generates the following C++ code:
struct X : public ceda::IObjectBaseMixin<X,ceda::EmptyBase>
{
typedef ceda::IObjectBaseMixin<X,ceda::EmptyBase> BaseClass;
int _calc_y() const
{
return 7;
}
struct _depnode_y : public ceda::DGAsyncIndepNode<_depnode_y,X,int>
{
typedef ceda::DGAsyncIndepNode<_depnode_y,X,int> base;
using typename base::output_type;
static void CalcOutput(const X* _self, output_type& _output)
{
_output = _self->_calc_y();
}
X const* _GetSelf() const { return CONST_ATTRIB_CAST(X,_dn_y); }
X const* _GetFinalSelf() const { return _GetSelf(); }
virtual ceda::xstring Name() const { return "X::y()"; }
} _dn_y;
int const& y() const
{
return _dn_y.read();
}
void EvictDgsNodes() const
{
BaseClass::EvictDgsNodes();
_dn_y.TryEvict(false);
}
ceda::AnyInterface QueryInterface(const ceda::ReflectedInterface&);
ceda::ReflectedClass const* GetReflectedClass() const;
};
There is a nested struct named _depnode_y which represents an asynchronously calculated variable.
_depnode_y is a subclass of ceda::DGAsyncIndepNode which is defined in DGNode.h
DGAsyncIndepNode
template <typename FinalClass, typename Self, typename Output>
class DGAsyncIndepNode : public DGIndepNode
{
public:
typedef Output output_type;
virtual ssize_t ByteSize() const
{
// This is the *overhead* of caching the output, it is assumed when OnEvict() calls
// ClearCacheValue(_output), CacheValueAdditionalSize() gives the number of bytes that is recovered.
return CacheValueAdditionalSize(_output);
}
virtual void VisitContainingObject(IObjectVisitor& _v) const
{
_v << _output << static_cast<const FinalClass*>(this)->_GetFinalSelf();
}
virtual void OnEvict() const
{
OnEvictCacheValue(_output);
ClearCacheValue(_output);
}
virtual bool IsEvictable() const
{
return !Enabled(DF_ASYNC_RUNNING) && CacheValueIsEvictable(_output);
}
const output_type& read() const
{
if (Enabled(DF_CALLED_READ))
{
if (Enabled(DF_ASYNC_RUNNING))
{
// read() has already been called, and an async output is currently being calculated.
// Attach out-nodes so when the output is updated the out-nodes will be invalidated
ReadBarrier();
}
else
{
// Calls to read() don't invoke the read barrier once the output has been
// calculated, because it cannot change once it is calculated.
}
}
else
{
// read() has been called for the first time.
SetFlag(DF_CALLED_READ);
// Attach out-nodes - these will be invalidated when the async output has been calculated.
ReadBarrier();
CSpace* cspace = GetThreadPtr<CSpace>();
cxAssert(!Enabled(DF_ASYNC_RUNNING));
SetFlag(DF_ASYNC_RUNNING);
// _output hasn't been calculated before so post a task to calculate it.
PostAsyncTask(cspace, [this]()
{
PrepareThreadLocalStorage(static_cast<const FinalClass*>(this)->_GetFinalSelf());
// Calculate output from key without a CSpace lock, hold output in a local variable
output_type localOutput;
FinalClass::CalcOutput(static_cast<const FinalClass*>(this)->_GetSelf(), localOutput);
{
// Use CSpace lock to swap in the output, then invalidate the out-nodes
CSpaceTxn txn;
cxAssert(Enabled(DF_ASYNC_RUNNING));
UpdateOutput(_output, localOutput);
OnChange();
ClearFlag(DF_ASYNC_RUNNING);
if (TypeHasAdditionalSize<output_type>()) UpdateByteSize();
}
});
}
return _output;
}
protected:
mutable output_type _output; // Protected by a CSpace lock
};