Efficient way of matching entities to systems in an entity component system

后端 未结 6 636
再見小時候
再見小時候 2021-01-31 04:53

I\'m working on a data-oriented entity component system where component types and system signatures are known at compile-time.


An entity

6条回答
  •  天命终不由人
    2021-01-31 05:08

    Another option which is a bit influenced from @Marwan Burelle idea.

    Each component will hold a sorted container of entities which have that component.

    When looking for entities that match a signature you need to iterate over the component container of entities.

    Adding or removing is O(nlogn) since it needs to be sorted. but you only need to add/remove it to/from a single container which will also contain fewer items.

    Iterating over the items is a bit heavier since it is a factor of the amount of components and the number of entities in each component. You still have an element of multiplying but the number of elements is again smaller.

    I wrote down a simplified version as a POC.

    Edit: My previous version had some bugs, now hopefully they are fixed.

    // Example program
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    struct ComponentBase
    {
    };
    
    struct Entity
    {
        Entity(std::string&& name, uint id)
            : _id(id),
            _name(name)
        {
        }
    
        uint _id;
        std::string _name;
        std::map> _components;
    };
    
    template 
    struct Component : public ComponentBase
    {
        static const uint _id;
        static std::map _entities;
    };
    
    template 
    std::map Component::_entities;
    
    template 
    const uint Component::_id = ID;
    
    using Comp0 = Component<0>;
    using Comp1 = Component<1>;
    using Comp2 = Component<2>;
    using Comp3 = Component<3>;
    
    template 
    struct Enumerator 
    {
    };
    
    template 
    struct Enumerator
    {
        std::map::iterator it;
        Enumerator()
        {
            it = TComponent::_entities.begin();
        }
    
        bool AllMoveTo(Entity& entity)
        {
            while (HasNext() && Current()->_id < entity._id)
            {
                MoveNext();
            }
    
            if (!Current())
                return false;
            return Current()->_id == entity._id;
        }
    
        bool HasNext() const
        {
            auto it_next = it;
            ++it_next;
            bool has_next = it_next != TComponent::_entities.end();
            return has_next;
        }
    
        void MoveNext()
        {
            ++it;
        }
    
        Entity* Current() const
        {
            return it != TComponent::_entities.end() ? it->second : nullptr;
        }
    };
    
    template 
    struct Enumerator
    {
        std::map::iterator it;
        Enumerator rest;
    
        Enumerator()
        {
            it = TComponent::_entities.begin();
        }
    
        bool AllMoveTo(Entity& entity)
        {
            if (!rest.AllMoveTo(entity))
                return false;
    
            while (HasNext() && Current()->_id < entity._id)
            {
                MoveNext();
            }
            if (!Current())
                return false;
            return Current()->_id == entity._id;
        }
    
        bool HasNext() const
        {
            auto it_next = it;
            ++it_next;
            bool has_next = it_next != TComponent::_entities.end();
            return has_next;
        }
    
        void MoveNext()
        {
            ++it;
        }
    
        Entity* Current() const
        {
            return it != TComponent::_entities.end() ? it->second : nullptr;
        }
    };
    
    template 
    struct Requires
    {
    
    };
    
    template 
    struct Requires
    {
        static void run_on_matching_entries(const std::function& fun)
        {
            for (Enumerator enumerator; enumerator.Current(); enumerator.MoveNext())
            {
                if (!enumerator.AllMoveTo(*enumerator.Current()))
                    continue;
                fun(*enumerator.Current());
            }
        }
    };
    
    template 
    struct Requires
    {
        static void run_on_matching_entries(const std::function& fun)
        { 
            for (Enumerator enumerator; enumerator.Current(); enumerator.MoveNext())
            {
                if (!enumerator.AllMoveTo(*enumerator.Current()))
                    continue;
                fun(*enumerator.Current());
            }
        }
    };
    
    using Sig0 = Requires;
    using Sig1 = Requires;
    using Sig2 = Requires;
    
    struct Manager
    {
        uint _next_entity_id;
    
        Manager()
        {
            _next_entity_id = 0;
        }
    
        Entity createEntity() 
        { 
            uint id = _next_entity_id++;
            return Entity("entity " + std::to_string(id), id); 
        };
    
        template 
        void add(Entity& e)
        {
            e._components[Component::_id] = std::make_shared();
            Component::_entities.emplace(e._id, &e);
        }
    
        template 
        void remove(Entity& e)
        {
            e._components.erase(Component::_id);
            Component::_entities.erase(e._id);
        }
    
        template 
        void for_entities_with_signature(const std::function& fun)
        {
            Signature::run_on_matching_entries(fun);
        }
    
    };
    
    int main()
    {
        Manager m;
        uint item_count = 100000;
        std::vector entities;
        for (size_t item = 0; item < item_count; ++item)
        {
            entities.push_back(m.createEntity());
        }
    
        for (size_t item = 0; item < item_count; ++item)
        {
            //if (rand() % 2 == 0)
                m.add(entities[item]);
            //if (rand() % 2 == 0)
                m.add(entities[item]);
            //if (rand() % 2 == 0)
                m.add(entities[item]);
            //if (rand() % 2 == 0)
                m.add(entities[item]);
        }
    
        size_t sig0_count = 0;
        size_t sig1_count = 0;
        size_t sig2_count = 0;
    
        auto start = std::chrono::system_clock::now();
        m.for_entities_with_signature([&](Entity& e) { ++sig0_count; });    
        m.for_entities_with_signature([&](Entity& e) { ++sig1_count; });
        m.for_entities_with_signature([&](Entity& e) { ++sig2_count; });
    
        auto duration = std::chrono::duration_cast(std::chrono::system_clock::now() - start);
        std::cout << "first run took " << duration.count() << " milliseconds: " << sig0_count << " " << sig1_count << " " << sig2_count << std::endl;
    
        for (size_t item = 0; item < item_count; ++item)
        {
            if (rand() % 2 == 0)
                m.remove(entities[item]);
            if (rand() % 2 == 0)
                m.remove(entities[item]);
            if (rand() % 2 == 0)
                m.remove(entities[item]);
            if (rand() % 2 == 0)
                m.remove(entities[item]);
        }
    
        sig0_count = sig1_count = sig2_count = 0;
    
        start = std::chrono::system_clock::now();
        m.for_entities_with_signature([&](Entity& e) { ++sig0_count; });    
        m.for_entities_with_signature([&](Entity& e) { ++sig1_count; });
        m.for_entities_with_signature([&](Entity& e) { ++sig2_count; });
    
        duration = std::chrono::duration_cast(std::chrono::system_clock::now() - start);
        std::cout << "second run took " << duration.count() << " milliseconds: " << sig0_count << " " << sig1_count << " " << sig2_count << std::endl;
    }
    

提交回复
热议问题