20 \$cache functions using DGKeyedAsyncNodeOnInput
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(int a, int b) const
with int c = 7;
{
return a+b+c;
}
};
Xcpp generates the following C++ code:
struct X : public ceda::IObjectBaseMixin<X,ceda::EmptyBase>
{
typedef ceda::IObjectBaseMixin<X,ceda::EmptyBase> BaseClass;
void _calc_y_input(int& c,int a,int b) const
{
c = 7;
}
int _calc_y(int a,int b,int c) const
{
return a+b+c;
}
struct _depnode_y : public ceda::DGKeyedAsyncNodeOnInput<_depnode_y,X,std::pair<int,int>,int,int>
{
typedef ceda::DGKeyedAsyncNodeOnInput<_depnode_y,X,std::pair<int,int>,int,int> base;
using typename base::key_type;
using typename base::output_type;
using typename base::map_type;
using typename base::input_type;
static void CalcOutput(const X* _self, output_type& _output, const key_type& _key, const input_type& _input)
{
_output = _self->_calc_y(_key.first,_key.second,_input);
}
static void CalcInput(const X* _self, input_type& _input, const key_type& _key)
{
_self->_calc_y_input(_input,_key.first,_key.second);
}
virtual ceda::xstring Name() const
{
std::pair<int,int> const& _key = _GetKey();
return cxMakeString("X::y(" << _key.first << ',' << _key.second << ')');
}
map_type& _GetMap() const { return _self->_map_y; }
X const* _GetFinalSelf() const { return _self; }
};
mutable typename _depnode_y::map_type _map_y;
int const& y(int a,int b) const
{
return _map_y[std::make_pair(a,b)].read(this);
}
void EvictDgsNodes() const
{
BaseClass::EvictDgsNodes();
ceda::TryEvict(_map_y);
}
};
There is a nested struct named _depnode_y which represents an asynchronously calculated variable.
_depnode_y is a subclass of ceda::DGKeyedAsyncNodeOnInput which is defined in DGAsyncNode.h
DGKeyedAsyncNodeOnInput
template <typename FinalClass, typename Self, typename Key, typename Input, typename Output>
class DGKeyedAsyncNodeOnInput : public DGDepNode
{
public:
typedef Key key_type;
typedef Input input_type;
typedef Output output_type;
typedef std::map<key_type,FinalClass> map_type;
DGKeyedAsyncNodeOnInput() :
DGDepNode(DF_ASYNC),
_self(nullptr)
{
}
virtual ssize_t ByteSize() const
{
// Note that we don't add CacheValueAdditionalSize(input_) because we clear the input
// at the time the output is updated
const ssize_t MapElementSize = sizeof(typename map_type::value_type);
return StdMapElementOverhead + MapElementSize + CacheValueAdditionalSize(_output);
}
key_type const& _GetKey() const
{
return GetKeyFromValueInPair<key_type,FinalClass>(static_cast<const FinalClass*>(this));
}
virtual void VisitContainingObject(IObjectVisitor& _v) const
{
_v << input_ << _output << _self;
}
virtual void OnEvict() const
{
OnEvictCacheValue(_output);
auto& m = static_cast<const FinalClass*>(this)->_GetMap();
cxVerify(m.erase(_GetKey()) == 1);
}
virtual bool IsEvictable() const
{
return !Enabled(DF_ASYNC_RUNNING) && CacheValueIsEvictable(_output);
}
virtual bool RecalcCache() const
{
FinalClass::CalcInput(_self, input_, _GetKey());
return false; // Pretend input not changed to avoid propagating dirtiness into the out-nodes
}
virtual void OnInvalidate() const
{
if (Enabled(DF_ASYNC_RUNNING))
{
// Already running an async task, don't post another
}
else
{
SetFlag(DF_ASYNC_RUNNING);
PostAsyncTask(GetThreadPtr<CSpace>(), [this]()
{
PrepareThreadLocalStorage(_self);
auto& key = _GetKey();
// Calculate the inputs
{
CSpaceTxn txn;
cxAssert(Enabled(DF_ASYNC_RUNNING));
ReadBarrierDep(); // This cleans this node
}
while(1)
{
// Calculate output from input and key without a CSpace lock, hold output in a local variable
output_type localOutput;
FinalClass::CalcOutput(_self, localOutput, key, input_);
// Apply the output and invalidate out-nodes
{
CSpaceTxn txn;
cxAssert(Enabled(DF_ASYNC_RUNNING));
UpdateOutput(_output, localOutput);
input_ = input_type(); // Clear the input - might allow some memory reclamation
MarkOutNodesAsSoftAndHardDirty();
if (!Enabled(DF_SOFT_DIRTY))
{
// Finished
ClearFlag(DF_ASYNC_RUNNING);
if (TypeHasAdditionalSize<output_type>()) UpdateByteSize();
return;
}
else
{
// This DGS node is dirty indicating that the inputs have changed again, so we need to recalculate again
// Recalculate the input. This cleans this node
ReadBarrierDep();
// continue in the while loop
}
}
}
});
}
}
const output_type& read(Self const* self) const
{
if (!_self)
{
_self = self;
OnInvalidate();
}
ReadBarrierIndep(); // Attach out-nodes
return _output;
}
protected:
mutable Self const* _self;
///////////////////////////// All state below is protected by a CSpace lock
mutable input_type input_;
mutable output_type _output;
};