diff --git a/README.md b/README.md index 6ab9dde11..bcd3a903c 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ Once you have created a registry, the followings are the exposed member function * `reset()`: resets the pool and destroys all the entities and their components. * `view()`: gets a view of the entities that have the given components (see below for further details). -Note that entities are numbers and nothing more. They are not classes and they have no memeber functions at all. +Note that entities are numbers and nothing more. They are not classes and they have no member functions at all. #### The View @@ -229,7 +229,7 @@ for(auto entity: view) { ``` Iterators are extremely poor, they are meant exclusively to be used to iterate over a set of entities.
-Exposed member functions are: +Guaranteed exposed member functions are: * `operator++()` * `operator++(int)` @@ -250,24 +250,10 @@ All the views can be used more than once. They return newly created and correctl I'd suggest not to store them anywhere and to invoke the `Registry::view` member function at each iteration to get a properly initialized view over which to iterate. -**Note**: An important feature (usually missed by other well known ECS) is that users can create and destroy entities, as -well as assign or remove components while iterating and neither the views nor the iterators will be invalidated. +**Note**: If underlying sets are modified, iterators are invalidated and using them is undefined behaviour.
+Do not try to assign or remove components on which you are iterating to entities. There are no guarantees. -There are a few exceptions to the rule: - -* Trying to access a destroyed entity through an iterator that hasn't been advanced results in an undefined behavior. -* Destroying an entity that isn't the one returned by the iterator in use results in an undefined behavior. -* Removing a component from an entity that isn't the one returned by the iterator in use results in an undefined behavior. - -In other therms, users can freely interact with the registry and keep views and iterators consistent as long as: - -* They create new entities with their set of components or assign components of any type to an already existent entity. -* They destroy the current entity (that is the one returned by the iterator in use) or remove its components. - -In any other case the behavior is undefined and additions and deletions should be managed externally in a batch or whatever. -As an example, whenever there exists a parent-child relationship, one can incurr in the problem above mentioned. - -Note also that iterators aren't thread safe. Do no try to iterate over a set of components and modify them concurrently. +**Note**: Iterators aren't thread safe. Do no try to iterate over a set of components and modify them concurrently. That being said, as long as a thread iterates over the entities that have the component `X` or assign and removes that component from a set of entities and another thread does something similar with components `Y` and `Z`, it shouldn't be a problem at all.
@@ -293,7 +279,10 @@ A custom pool should expose at least the following member functions: * `bool empty() const noexcept;` * `size_type capacity() const noexcept;` * `size_type size() const noexcept;` -* `const entity_type * entities() const noexcept;` +* `iterator_type begin() noexcept;` +* `const_iterator_type begin() const noexcept;` +* `iterator_type end() noexcept;` +* `const_iterator_type end() const noexcept;` * `bool has(entity_type entity) const noexcept;` * `const component_type & get(entity_type entity) const noexcept;` * `component_type & get(entity_type entity) noexcept;` @@ -322,7 +311,10 @@ A generic pool should expose at least the following memeber functions: * `template bool empty() const noexcept;` * `template size_type capacity() const noexcept;` * `template size_type size() const noexcept;` -* `template const entity_type * entities() const noexcept;` +* `template iterator_type begin() noexcept;` +* `template const_iterator_type begin() const noexcept;` +* `template iterator_type end() noexcept;` +* `template const_iterator_type end() const noexcept;` * `template bool has(entity_type entity) const noexcept;` * `template const Comp & get(entity_type entity) const noexcept;` * `template Comp & get(entity_type entity) noexcept;` diff --git a/src/component_pool.hpp b/src/component_pool.hpp index 58a8110dd..06f7815f6 100644 --- a/src/component_pool.hpp +++ b/src/component_pool.hpp @@ -21,6 +21,8 @@ public: using entity_type = Entity; using pos_type = entity_type; using size_type = typename std::vector::size_type; + using iterator_type = typename std::vector::iterator; + using const_iterator_type = typename std::vector::const_iterator; private: inline bool valid(entity_type entity) const noexcept { @@ -53,8 +55,20 @@ public: return data.size(); } - const entity_type * entities() const noexcept { - return direct.data(); + iterator_type begin() noexcept { + return direct.begin(); + } + + const_iterator_type begin() const noexcept { + return direct.begin(); + } + + iterator_type end() noexcept { + return direct.end(); + } + + const_iterator_type end() const noexcept { + return direct.end(); } bool has(entity_type entity) const noexcept { @@ -122,6 +136,8 @@ public: using entity_type = typename Pool::entity_type; using pos_type = typename Pool::pos_type; using size_type = typename Pool::size_type; + using iterator_type = typename Pool::iterator_type; + using const_iterator_type = typename Pool::const_iterator_type; explicit ComponentPool(size_type dim = 4098) noexcept #ifdef _MSC_VER @@ -155,8 +171,23 @@ public: } template - const entity_type * entities() const noexcept { - return Pool::entities(); + iterator_type begin() noexcept { + return Pool::begin(); + } + + template + const_iterator_type begin() const noexcept { + return Pool::begin(); + } + + template + iterator_type end() noexcept { + return Pool::end(); + } + + template + const_iterator_type end() const noexcept { + return Pool::end(); } template diff --git a/src/registry.hpp b/src/registry.hpp index c2ff07cb8..8721d9141 100644 --- a/src/registry.hpp +++ b/src/registry.hpp @@ -25,17 +25,19 @@ class View, Type, Types...> final { using pool_type = Pool; using entity_type = typename pool_type::entity_type; using mask_type = std::bitset; + using underlying_iterator_type = typename pool_type::const_iterator_type; class ViewIterator; public: using iterator_type = ViewIterator; + using const_iterator_type = iterator_type; using size_type = typename pool_type::size_type; private: class ViewIterator { inline bool valid() const noexcept { - return ((mask[entities[pos]] & bitmask) == bitmask); + return ((mask[*begin] & bitmask) == bitmask); } public: @@ -45,25 +47,26 @@ private: using pointer = entity_type *; using iterator_category = std::input_iterator_tag; - ViewIterator(const mask_type &bitmask, const entity_type *entities, const mask_type *mask, size_type pos, size_type last) noexcept - : bitmask{bitmask}, entities{entities}, mask{mask}, pos{pos}, last{last} + ViewIterator(underlying_iterator_type begin, underlying_iterator_type end, const mask_type &bitmask, const mask_type *mask) noexcept + : begin{begin}, end{end}, bitmask{bitmask}, mask{mask} { - while(this->pos != last && !valid()) { ++this->pos; } + if(begin != end && !valid()) { + ++(*this); + } } ViewIterator & operator++() noexcept { - ++pos; - while(pos != last && !valid()) { ++pos; } + while(++begin != end && !valid()); return *this; } ViewIterator operator++(int) noexcept { ViewIterator orig = *this; - return this->operator++(), orig; + return ++(*this), orig; } bool operator==(const ViewIterator &other) const noexcept { - return other.pos == pos && other.entities == entities; + return other.begin == begin; } bool operator!=(const ViewIterator &other) const noexcept { @@ -71,62 +74,72 @@ private: } value_type operator*() const noexcept { - return entities[pos]; + return *begin; } private: + underlying_iterator_type begin; + underlying_iterator_type end; const mask_type bitmask; - const entity_type *entities; const mask_type *mask; - size_type pos; - size_type last; }; template - void prefer() noexcept { + void prefer(size_type &size) noexcept { auto sz = pool.template size(); if(sz < size) { - entities = pool.template entities(); + from = pool.template begin(); + to = pool.template end(); size = sz; } } public: - explicit View(const pool_type &pool, const mask_type *mask) noexcept - : entities{pool.template entities()}, + explicit View(pool_type &pool, const mask_type *mask) noexcept + : from{pool.template begin()}, + to{pool.template end()}, pool{pool}, - mask{mask}, - size{pool.template size()} + mask{mask} { using accumulator_type = int[]; + size_type size = pool.template size(); bitmask.set(ident.template get()); accumulator_type types = { 0, (bitmask.set(ident.template get()), 0)... }; - accumulator_type pref = { 0, (prefer(), 0)... }; + accumulator_type pref = { 0, (prefer(size), 0)... }; (void)types, (void)pref; } - iterator_type begin() const noexcept { - return ViewIterator{bitmask, entities, mask, 0, size}; + const_iterator_type begin() const noexcept { + return ViewIterator{from, to, bitmask, mask}; } - iterator_type end() const noexcept { - return ViewIterator{bitmask, entities, mask, size, size}; + iterator_type begin() noexcept { + return const_cast(this)->begin(); + } + + const_iterator_type end() const noexcept { + return ViewIterator{to, to, bitmask, mask}; + } + + iterator_type end() noexcept { + return const_cast(this)->end(); } void reset() noexcept { using accumulator_type = int[]; - entities = pool.template entities(); - size = pool.template size(); - accumulator_type accumulator = { 0, (prefer(), 0)... }; + from = pool.template begin(); + to = pool.template end(); + size_type size = pool.template size(); + accumulator_type accumulator = { 0, (prefer(size), 0)... }; (void)accumulator; } private: - const entity_type *entities; - const pool_type &pool; + underlying_iterator_type from; + underlying_iterator_type to; + pool_type &pool; const mask_type *mask; - size_type size; mask_type bitmask; }; @@ -134,64 +147,30 @@ private: template class Pool, typename Entity, typename... Components, typename Type> class View, Type> final { using pool_type = Pool; - using entity_type = typename pool_type::entity_type; - - struct ViewIterator; public: using size_type = typename pool_type::size_type; - using iterator_type = ViewIterator; + using iterator_type = typename pool_type::const_iterator_type; + using const_iterator_type = iterator_type; -private: - struct ViewIterator { - using value_type = entity_type; - using difference_type = std::ptrdiff_t; - using reference = entity_type &; - using pointer = entity_type *; - using iterator_category = std::input_iterator_tag; - - ViewIterator(const entity_type *entities, size_type pos) noexcept - : entities{entities}, pos{pos} - {} - - ViewIterator & operator++() noexcept { - ++pos; - return *this; - } - - ViewIterator operator++(int) noexcept { - ViewIterator orig = *this; - return this->operator++(), orig; - } - - bool operator==(const ViewIterator &other) const noexcept { - return other.pos == pos && other.entities == entities; - } - - bool operator!=(const ViewIterator &other) const noexcept { - return !(*this == other); - } - - value_type operator*() const noexcept { - return entities[pos]; - } - - private: - const entity_type *entities; - size_type pos; - }; - -public: - explicit View(const pool_type &pool) noexcept + explicit View(pool_type &pool) noexcept : pool{pool} {} - iterator_type begin() const noexcept { - return ViewIterator{pool.template entities(), 0}; + const_iterator_type begin() const noexcept { + return pool.template begin(); } - iterator_type end() const noexcept { - return ViewIterator{pool.template entities(), pool.template size()}; + iterator_type begin() noexcept { + return const_cast(this)->begin(); + } + + const_iterator_type end() const noexcept { + return pool.template end(); + } + + iterator_type end() noexcept { + return const_cast(this)->end(); } size_type size() const noexcept { @@ -199,7 +178,7 @@ public: } private: - const pool_type &pool; + pool_type &pool; }; diff --git a/test/component_pool.cpp b/test/component_pool.cpp index 8856a0792..f654d27b2 100644 --- a/test/component_pool.cpp +++ b/test/component_pool.cpp @@ -13,8 +13,8 @@ TEST(ComponentPool, Functionalities) { ASSERT_EQ(pool.capacity(), pool_type::size_type{0}); ASSERT_EQ(pool.size(), pool_type::size_type{0}); ASSERT_EQ(pool.size(), pool_type::size_type{0}); - ASSERT_EQ(pool.entities(), pool.entities() + pool.size()); - ASSERT_EQ(pool.entities(), pool.entities() + pool.size()); + ASSERT_EQ(pool.begin(), pool.end()); + ASSERT_EQ(pool.begin(), pool.end()); ASSERT_FALSE(pool.has(0)); ASSERT_FALSE(pool.has(0)); } @@ -126,7 +126,7 @@ TEST(ComponentPool, HasGet) { ASSERT_NO_THROW(pool.destroy(0)); } -TEST(ComponentPool, EntitiesReset) { +TEST(ComponentPool, BeginEndReset) { using pool_type = entt::ComponentPool; pool_type pool{2}; @@ -137,17 +137,17 @@ TEST(ComponentPool, EntitiesReset) { ASSERT_EQ(pool.construct(1, 1), 1); ASSERT_EQ(pool.size(), decltype(pool.size()){4}); - ASSERT_EQ(pool.entities()[0], typename pool_type::entity_type{0}); - ASSERT_EQ(pool.entities()[1], typename pool_type::entity_type{2}); - ASSERT_EQ(pool.entities()[2], typename pool_type::entity_type{3}); - ASSERT_EQ(pool.entities()[3], typename pool_type::entity_type{1}); + ASSERT_EQ(*(pool.begin()+0), typename pool_type::entity_type{0}); + ASSERT_EQ(*(pool.begin()+1), typename pool_type::entity_type{2}); + ASSERT_EQ(*(pool.begin()+2), typename pool_type::entity_type{3}); + ASSERT_EQ(*(pool.begin()+3), typename pool_type::entity_type{1}); pool.destroy(2); ASSERT_EQ(pool.size(), decltype(pool.size()){3}); - ASSERT_EQ(pool.entities()[0], typename pool_type::entity_type{0}); - ASSERT_EQ(pool.entities()[1], typename pool_type::entity_type{1}); - ASSERT_EQ(pool.entities()[2], typename pool_type::entity_type{3}); + ASSERT_EQ(*(pool.begin()+0), typename pool_type::entity_type{0}); + ASSERT_EQ(*(pool.begin()+1), typename pool_type::entity_type{1}); + ASSERT_EQ(*(pool.begin()+2), typename pool_type::entity_type{3}); ASSERT_EQ(pool.construct(0, 'c'), 'c');