ReflectedVector.h

// ReflectedVector.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2022

@import "IObject.h"
@import "ReflectedType.h"

namespace ceda
{
/*
All the genop_xxx functions update the field and allow the operation to be recorded.
Only some of the OCB_xxx update the field
*/

/////////// inserts

struct InsertIntoVector_params : public FieldParams
{
    // Insertion position (in p coords)
    ssize_t i;

    // Elements to be inserted.
    const void* src;
    ssize_t n;
    
    VisitPrefsInArrayFn visitPrefsInArrayFn;

    // Only initialised when visitPrefsInArrayFn is initialised
    ssize_t elementSize;
};

// Must be called after inserting into a vector field that supports vector OT.
@api void OCB_AfterInsertIntoVector(InsertIntoVector_params&);

template <typename T>
inline void genop_InsertIntoVector(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field, 
    ssize_t i, const T* src, ssize_t n)
{
    // This is done here, rather than in OCB_AfterInsertIntoVector() so it can copy construct
    // all the elements of type T.  This is important for when copy construction isn't merely 
    // a memcpy.
    field.insert(i,src,n);

    InsertIntoVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsInArrayFn = nullptr;
    //params.elementSize = sizeof(T);
    params.i = i;
    params.src = src;
    params.n = n;
    OCB_AfterInsertIntoVector(params);
}

template <typename T>
inline void genop_InsertIntoVector_VisitPrefs(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field,
    ssize_t i, const T* src, ssize_t n)
{
    // This is done here, rather than in OCB_AfterInsertIntoVector() so it can copy construct
    // all the elements of type T.  This is important for when copy construction isn't merely 
    // a memcpy.
    field.insert(i,src,n);

    InsertIntoVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsInArrayFn = (VisitPrefsInArrayFn) &VisitPrefsInArray<T>;
    params.elementSize = sizeof(T);
    params.i = i;
    params.src = src;
    params.n = n;
    OCB_AfterInsertIntoVector(params);
}

/////////// erases

struct EraseFromVector_params : public FieldParams
{
    // erase start position (in p coords)
    ssize_t i;
    
    // Number of elements to be erased.
    ssize_t n;

    VisitPrefsInArrayFn visitPrefsInArrayFn;
    
    // Only initialised when visitPrefsInArrayFn is initialised
    ssize_t elementSize;
};

@api void OCB_BeforeEraseFromVector(EraseFromVector_params&);
@api void OCB_AfterEraseFromVector(EraseFromVector_params&);

template <typename T>
inline void genop_EraseFromVector(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
    EraseFromVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsInArrayFn = nullptr;
    //params.elementSize = sizeof(T);
    params.i = i1;
    params.n = i2-i1;
    OCB_BeforeEraseFromVector(params);

    // This is done here, rather than in OCB_BeforeEraseFromVector() so it can destruct all
    // the elements of type T.  This is important when the elements have non-empty 
    // destructors.
    field.erase(field.begin()+i1, field.begin()+i2);

    OCB_AfterEraseFromVector(params);
}

template <typename T>
inline void genop_EraseFromVector_VisitPrefs(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
    EraseFromVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.visitPrefsInArrayFn = (VisitPrefsInArrayFn) &VisitPrefsInArray<T>;
    params.elementSize = sizeof(T);
    params.i = i1;
    params.n = i2-i1;
    OCB_BeforeEraseFromVector(params);

    // This is done here, rather than in OCB_BeforeEraseFromVector() so it can destruct all
    // the elements of type T.  This is important when the elements have non-empty 
    // destructors.
    field.erase(field.begin()+i1, field.begin()+i2);

    OCB_AfterEraseFromVector(params);
}

/////////// moves

/*
Application programmers generate move operations using the erase() and insert() methods on a 
vector<pref> field.   Move is accomplished by inserting things that were previously deleted in 
the *same* transaction.

PersistOperationCallbacks::OnEraseFromMVector() cannot call AsyncPermanentlyDeleteSubTree() 
on the objects that were deleted because maybe they are being inserted somewhere else.
*/

struct InsertIntoMVector_params : public FieldParams
{
    // Insertion position (in p coords)
    ssize_t i;

    // prefs to be inserted.  Elements cannot be null
    const prefbase* src;
    ssize_t n;
};

struct EraseFromMVector_params : public FieldParams
{
    // Deletion position (in p coords)
    ssize_t i;
    
    // Number of prefs to be deleted.
    ssize_t n;
};

// Updates the field and records the operation
@api void OCB_InsertIntoMVector(InsertIntoMVector_params&);

// Updates the field and records the operation
@api void OCB_EraseFromMVector(EraseFromMVector_params&);

template <typename T>
inline void genop_InsertIntoMVector(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field, 
    ssize_t i, const T* src, ssize_t n)
{
    InsertIntoMVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.i = i;
    params.src = (const prefbase*) src;
    params.n = n;
    OCB_InsertIntoMVector(params);
}

template <typename T>
inline void genop_EraseFromMVector(
    ptr<IObject> obj, const FieldPath& path, xvector<T>& field, ssize_t i1, ssize_t i2)
{
    EraseFromMVector_params params;
    params.obj = obj;
    params.fid.path = path;
    params.addr = &field;
    params.i = i1;
    params.n = i2-i1;
    OCB_EraseFromMVector(params);
}

/////////// vector fields in models

// Wraps xvector<T>
template <typename Base, typename T>
struct ModelAttribute_vector : public Base
{
    CEDA_MODEL_ATTRIBUTE_BOILERPLATE(xvector<T>, ModelAttribute_vector)

    void insert(ssize_t i,const T* src,ssize_t n)
    {
        genop_InsertIntoVector(self(),path(),model(),i,src,n);
    }
    void erase_range(ssize_t i1,ssize_t i2)
    {
        genop_EraseFromVector(self(),path(),model(),i1,i2);
    }

    void insert(ssize_t i,T const& v)
    {
        insert(i,&v,1);
    }
    void push_front(T const& v)
    {
        insert(0,v);
    }
    void push_back(T const& v)
    {
        insert(model().size(),v);
    }
    void erase(ssize_t i)
    {
        erase_range(i,i+1);
    }
    void pop_front()
    {
        erase(0);
    }
    void pop_back()
    {
        erase(model().size()-1);
    }
    void clear()
    {
        erase_range(0,model().size());
    }
};

/////////// reflection

/*
Possible issue:

The type qualifiers const, volatile, &, assignable, movable, offsetable are recorded in a ReflectedType.
Therefore they are not recorded on the ReflectedVector itself.
That might be bad for RR_Vector which only uses a ReflectedVector to provide reflection information
about the vector.  The constness information is unavailable.

Maybe this is a good thing because it means all the mutative methods of RR_Vector don't care about constness
of the vector, so the code is simpler and the problem is pushed upwards.
*/

struct @api ReflectedVector
{
    const TypeOps& ops;
    ReflectedType element;

    // If we need vectors to implement an interface we can record the pointer to the vtable for 
    // the interface here, just like for xsets and xmaps.
    //const IXVector::FnTable* IXVectorFnTable = nullptr;
};

@api xostream& operator<<(xostream& os, const ReflectedVector& r);

template<typename T> struct is_vector { constexpr static bool value = false; };
template<typename T> struct is_vector<xvector<T>> { constexpr static bool value = true; };

template<typename T>
inline const ReflectedVector& GetReflectedVector()
{
    static ReflectedVector s =
    {
        GetTypeOpsX<xvector<T>>()
    };
    static bool init = false;
    if (!init)
    {
        init = true;
        s.element = GetReflectedType<T>();
    }
    return s;
}

template<typename T>
struct GetReflectedType_class<xvector<T>> 
{
    static inline ReflectedType get()
    {
        ReflectedType r;
        r.tag = EReflectedTypeTag::Vector;
        r.Vector = &GetReflectedVector<T>();
        return r;
    }
};

/*
In the following methods Range<const octet_t> is assumed to actually be a Range<const T>
where RR_Vector represents an xvector<T>.
In other words the count member of the range is the number of elements of type T, 
not the number of octets.
*/

struct @api RR_Vector
{
    RR_Vector(const ReflectedVector& rv, void* data) : rv(rv), vec( *reinterpret_cast<VectorOfByte*>(data) ) 
    {
        cxAssert(data);
    }

    inline void construct() { rv.ops.ConstructVariable(&vec); }

    inline void construct(const RR_Vector& src, bool allowExplicitConversions) 
    { 
        // todo: allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        rv.ops.CopyConstructVariable(&vec,&src.vec); 
    }

    inline void construct(const RR_Vector& src, ssize_t srcPos, ssize_t count, bool allowExplicitConversions) 
    { 
        // todo: allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        construct();
        insert(0, src, srcPos, count, allowExplicitConversions);
    }

    inline void construct(Range<const octet_t> rhs)
    { 
        cxAssert(rhs.count == 0 || (rhs.count > 0 && rhs.data != nullptr));

        new (&vec) VectorOfByte(rhs.count * stride());
        ElementOps().CopyConstructArray(data(), rhs.data, rhs.count);
    }

    inline void detruct() { rv.ops.DestructVariable(&vec); }

    inline void assign(const RR_Vector& src, bool allowExplicitConversions) 
    { 
        // todo: allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        rv.ops.AssignVariable(&vec,&src.vec);
    }
    inline void assign(Range<const octet_t> rhs) 
    { 
        cxAssert(rhs.count == 0 || (rhs.count > 0 && rhs.data != nullptr));
        resize(rhs.count);
        ElementOps().AssignArray(data(), rhs.data, rhs.count);
    }

    inline bool CompareEq(const RR_Vector& rhs, bool allowExplicitConversions) const 
    { 
        // todo: allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, rhs.rv.element));
        return rv.ops.Equal(&vec,&rhs.vec); 
    }
    inline bool CompareLt(const RR_Vector& rhs, bool allowExplicitConversions) const 
    { 
        // todo: allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, rhs.rv.element));
        return rv.ops.Less(&vec,&rhs.vec); 
    }

    inline void Serialise(Archive& ar) const { rv.ops.SerialiseVariable(ar, &vec); }
    inline void Deserialise(InputArchive& ar) { rv.ops.DeserialiseVariable(ar, &vec); }
    inline void ScanOver(InputArchive& ar) const { rv.ops.ScanOverVariable(ar); }
    inline void Write(xostream& os) const { rv.ops.PrintVariable(os, &vec); }

    inline const octet_t* data() const { return vec.data(); }
    inline octet_t* data() { return vec.data(); }

    inline const TypeOps& ElementOps() const
    { 
        cxAssert(rv.element.ops);
        return *rv.element.ops;
    }

    inline ssize_t stride() const { return ElementOps().GetSize(); }

    // Number of elements in the vector
    inline ssize_t size() const 
    {
        cxAssert(vec.size() % stride() == 0);
        return vec.size() / stride();
    }

    inline void clear()
    {
        ElementOps().DestructArray(data(), size());
        vec.clear();
    }

    inline octet_t* GetElement(ssize_t i) { return &vec[i * stride()]; }
    inline const octet_t* GetElement(ssize_t i) const { return const_cast<RR_Vector*>(this)->GetElement(i); }

    inline octet_t* SafeGetElement(ssize_t i)
    {
        if (0 <= i && i < size())
        {
            return &vec[i * stride()];
        }
        else
        {
            return nullptr;
        }
    }
    inline const octet_t* SafeGetElement(ssize_t i) const { return const_cast<RR_Vector*>(this)->SafeGetElement(i); }

    inline Range<const octet_t> GetRange(ssize_t pos, ssize_t count) const
    {
        cxAssert(0 <= pos && count <= 0 && pos+count <= size());
        return { GetElement(pos), count };
    }

    inline void resize(ssize_t newSize)
    {
        cxAssert(newSize >= 0);
        ssize_t newSizeBytes = newSize * stride();
        ssize_t oldSizeBytes = vec.size();
        ssize_t numElementsAdded = (newSizeBytes-oldSizeBytes)/stride();
        if (numElementsAdded > 0)
        {
            vec.resize(newSizeBytes);
            ElementOps().ConstructArray(&vec[oldSizeBytes], numElementsAdded);
        }
        else if (numElementsAdded < 0)
        {
            ElementOps().DestructArray(&vec[newSizeBytes], -numElementsAdded);
            vec.resize(newSizeBytes);
        }
    }

    inline void SerialiseElements(Archive& ar, ssize_t pos, ssize_t count)
    {
        cxAssert(0 <= pos && 0 <= count && pos+count <= size());
        ElementOps().SerialiseArray(ar, GetElement(pos), count);
    }

    // todo: provide a version that allows for comparisons to a variable of a different type
    inline ssize_t FindFirst(const void* pValue) const
    {
        return ElementOps().FindFirst(data(), stride(), size(), pValue);
    }

    // todo: provide a version that allows for comparisons to a variable of a different type
    inline ssize_t FindLast(const void* pValue) const
    {
        return ElementOps().FindLast(data(), stride(), size(), pValue);
    }

    inline void DestructElements(ssize_t pos, ssize_t count)
    {
        cxAssert(0 <= pos && 0 <= count && pos+count <= size());
        ElementOps().DestructArray(GetElement(pos), count);
    }

    inline void* insert(ssize_t pos, const void* x)
    {
        cxAssert(x);
        cxAssert(0 <= pos && pos <= size());
        vec.insertbytes(pos * stride(), stride());
        auto p = GetElement(pos);
        ElementOps().CopyConstructVariable(p,x);
        return p;
    }

    inline void* insert(ssize_t pos, Range<const octet_t> src)
    {
        cxAssert(0 <= pos && pos <= size());
        cxAssert(src.count == 0 || (src.count > 0 && src.data != nullptr));
        vec.insertbytes(pos * stride(), src.count * stride());
        auto p = GetElement(pos);
        ElementOps().CopyConstructArray(p, src.data, src.count);
        return p;
    }

    // todo: this can fail because an index is out of range or because of a type error
    // That suggests we should use an enum to indicate the kind of failure.  See EReflectedVectorCode
    inline bool insert(ssize_t pos, const ReflectedType& srcElementType, Range<const octet_t> src, bool allowExplicitConversions)
    {
        cxAssert(false);    // todo
        return false;
    }

    inline bool insert(ssize_t pos, const RR_Vector& src, bool allowExplicitConversions)
    {
        // todo : allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        cxAssert(0 <= pos && pos <= size());
        vec.insertbytes(pos * stride(), src.size() * stride());
        ElementOps().CopyConstructArray(GetElement(pos), src.data(), src.size());
        return true;
    }

    // Returns false if pos is out of range
    bool genop_insert(ptr<IObject> obj, const FieldPath& path, ssize_t pos, Range<const octet_t> src);

    inline bool insert(ssize_t dstPos, const RR_Vector& src, ssize_t srcPos, ssize_t count, bool allowExplicitConversions)
    {
        // todo : allow for coercions
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        cxAssert(0 <= dstPos && dstPos <= size());
        cxAssert(0 <= count);
        cxAssert(0 <= srcPos && srcPos+count <= src.size());
        vec.insertbytes(dstPos * stride(), count * stride());
        ElementOps().CopyConstructArray(GetElement(dstPos), src.GetElement(srcPos), count);
        return true;
    }

    inline void* append(Range<const octet_t> src)
    {
        return insert(size(), src);
    }

    inline bool append(const RR_Vector& src, bool allowExplicitConversions)
    {
        // todo : allow for coercions
        // todo: should be checked at runtime, not asserted?
        cxAssert(IsEquivVariableTypes(rv.element, src.rv.element));
        return insert(size(), src, allowExplicitConversions);
    }

    inline void* InsertDefaultConstructed(ssize_t pos, ssize_t count)
    {
        cxAssert(0 <= pos && pos <= size());
        cxAssert(0 <= count);

        vec.insertbytes(pos * stride(), count * stride());
        auto p = GetElement(pos);
        ElementOps().ConstructArray(p, count);
        return p;
    }

    inline void* AppendDefaultConstructed(ssize_t count)
    {
        cxAssert(0 <= count);

        ssize_t pos = size();
        vec.growbytes(stride() * count);
        auto p = GetElement(pos);
        ElementOps().ConstructArray(p, count);
        return p;
    }

    inline void* push_back(const void* x)
    {
        cxAssert(x);
        ssize_t pos = size();
        vec.growbytes(stride());
        auto p = GetElement(pos);
        ElementOps().CopyConstructVariable(p,x);
        return p;
    }

    inline void erase(ssize_t pos, ssize_t count)
    {
        cxAssert(0 <= pos && 0 <= count && pos+count <= size());
        ElementOps().DestructArray(GetElement(pos), count);
        vec.erase(pos*stride(), count*stride());      // erase(ssize_t i, ssize_t n);
    }

    bool genop_erase(ptr<IObject> obj, const FieldPath& path, ssize_t pos, ssize_t count);

    inline void reverse()
    {
        if (!vec.empty())
        {
            octet_t* p1 = vec.data();
            octet_t* p2 = vec.data_end() - stride();
            while(p1 < p2)
            {
                memswap(p1,p2,stride());
                p1 += stride();
                p2 -= stride();
            }
        }
    }

    inline ssize_t GetElementFrequency(const void* value) const
    {
        cxAssert(value);

        ssize_t count = 0;
        ssize_t n = size();
        for (ssize_t i=0 ; i < n ; ++i)
        {
            if (ElementOps().Equal(GetElement(i),value)) ++count;
        }
        return count;
    }

    inline void sort()
    {
        cxAssert(0);     // todo
    }

    const ReflectedVector& rv;
    VectorOfByte& vec;
};

} // namespace ceda