0

I'm working on a hierarchical Entity-Component System. It's called hierarchical because an Entity might be composed of several Entities.

My hierarchy structure is implemented as several linked lists. Although, I'm not relying on several std::list or std::forward_list. Actually, I have two vectors: 1) maps an Entity to its first child; 2) maps a child to its next sibling.

I'd like to create a ranges::view on this structure, but it seems I'm forgetting something.

I'd like to use the range this way (Complete Code on GitHub):

TEST_CASE("Range adaptors")
{
    auto parentSystem = System<Test::Parent>{};
    auto childSystem  = System<Test::Child>{};
    auto parent0 = parentSystem.add();
    auto parent1 = parentSystem.add();
    LeftMapped<Test::Parent, System, Test::Child, System> leftMapped(parentSystem, childSystem);
    leftMapped.addChild(parent0, childSystem.add());
    leftMapped.addChild(parent0, childSystem.add());
    leftMapped.addChild(parent0, childSystem.add());
    leftMapped.addChild(parent1, childSystem.add());
    // HERE \/
    ranges::for_each(leftMapped.children(parent0), [](Test::Child child)
    {
        std::cout << static_cast<Base>(child).id() << std::endl;
    });
}

And of course, make it pipeable for working with views and actions compatible with forward range (I'm not so used to the Concepts idiom).

This is the part of the code I want to adapt:

// A composition should inherit Left Mapped when it is necessary O(1) mapping from parent to children.
template <typename ParentType, template <typename> class ParentSystemType, typename ChildType, template <typename> class ChildSystemType>
class LeftMapped
{
public:
    LeftMapped(ParentSystemType<ParentType>& parent, ChildSystemType<ChildType>& child):
        m_firstChild(makeProperty<ChildType>(parent)),
        m_nextSibling(makeProperty<ChildType>(child))
    {

    }
    ChildType firstChild(ParentType parent) const
    {
        return m_firstChild[parent];
    }
    ChildType nextSibling(ChildType child) const
    {
        return m_nextSibling[child];
    }
    void firstChild(ParentType parent, ChildType child)
    {
        m_firstChild[parent] = child;
    }
    void nextSibling(ChildType child, ChildType next)
    {
        m_nextSibling[child] = next;
    }
    void addChild(ParentType parent, ChildType child)
    {
        m_nextSibling[child] = m_firstChild[parent];
        m_firstChild[parent] = child;
    }

    // HERE \/ I don't know how to properly adapt my container.
    class ChildrenView : public ranges::view_facade<ChildrenView> {
        friend ranges::range_access;
        const LeftMapped& mapped;
        const ParentType parent;
        struct cursor
        {
            const LeftMapped& mapped;
            ChildType current;
            decltype(auto) read() const
            {
                return current;
            }
            void next()
            {
                current = mapped.nextSibling(current);
            }
            bool equal(ranges::default_sentinel) const {
                return current == ChildType{};
            }
        };
        cursor begin_cursor() {
            return {mapped, mapped.firstChild(parent)};
        }
    public:
        ChildrenView() = default;
        explicit ChildrenView(const LeftMapped& mapped, ParentType parent)
          : mapped(mapped),
            parent(parent)
        {}
    };

    auto children(ParentType parent) const
    {
        return ChildrenView(*this, parent);
    }

private:
    Property<ParentType, ChildType, ParentSystemType> m_firstChild;
    Property<ChildType, ChildType, ChildSystemType> m_nextSibling;
};
csguth
  • 569
  • 3
  • 18

1 Answers1

1

The first thing that jumped out at me was that you have reference data members both in ChildrenView and in ChildrenView::cursor. That makes those types non-assignable, which range-v3 requires. Try changing them to pointers or std::reference_wrappers, and see if that gets you any closer.

Eric Niebler
  • 5,927
  • 2
  • 29
  • 43
  • Thanks Eric. You just got the problem. Although I didn't have enough time to make std::reference_wrapper to work, it continues failing on Cursor concept. For now, I just changed the references with raw pointers. Now, I would like my structure to model a specific concept, like ForwardRange. Do I have to create begin()/end() methods as well as a forward Iterator? I thought the range concept was supposed to replace that iterator boilerplating... – csguth Apr 12 '17 at 07:57
  • 1
    @csguth `view_facade` adds `begin` and `end` for you. The default `begin` returns roughly `basic_iteratorbegin_cursor())>{this->begin_cursor()}`, and the default `end` returns `default_sentinel{}`. If you want your range to model `ForwardRange`, then your cursor needs to model `ForwardCursor`. Notably, it's missing `bool equal(const cursor& that) const` that `basic_iterator` needs to implement iterator equality. – Casey Apr 12 '17 at 19:31