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;
};