1
0
Эх сурвалжийг харах

added clone functionality

Michele Caini 7 жил өмнө
parent
commit
c986a6c4dd

+ 15 - 15
README.md

@@ -381,8 +381,7 @@ with the registry nor with the entity-component system at all.
 The following sections will explain in short how to use the entity-component
 system, the core part of the whole framework.<br/>
 In fact, the framework is composed of many other classes in addition to those
-describe below. For more details, please refer to the
-[online documentation](https://skypjack.github.io/entt/).
+describe below. For more details, please refer to the inline documentation.
 
 ## The Registry, the Entity and the Component
 
@@ -397,16 +396,22 @@ Entities are represented by _entity identifiers_. An entity identifier is an
 opaque type that users should not inspect or modify in any way. It carries
 information about the entity itself and its version.
 
-A registry can be used both to construct and to destroy entities:
+A registry can be used both to construct and destroy entities, as well as to
+clone already existing entities:
 
 ```cpp
 // constructs a naked entity with no components and returns its identifier
 auto entity = registry.create();
 
+// clones an entity and assigns all its components by copy to the newly created one
+auto other = registry.clone(entity);
+
 // destroys an entity and all its components
 registry.destroy(entity);
 ```
 
+Be aware that cloning an entity can lead to unexpected results under certain
+conditions. Please refer to the inline documentation for more details.<br/>
 Once an entity is deleted, the registry can freely reuse it internally with a
 slightly different identifier. In particular, the version of an entity is
 increased each and every time it's destroyed.<br/>
@@ -766,8 +771,8 @@ In fact, there are two functions that respond to slightly different needs:
   ```
 
   There exists also the possibility to use a custom sort function object, as
-  long as it adheres to the requirements described in the
-  [official documentation](https://skypjack.github.io/entt/).<br/>
+  long as it adheres to the requirements described in the inline
+  documentation.<br/>
   This is possible mainly because users can get much more with a custom sort
   function object if the pattern of usage is known. As an example, in case of an
   almost sorted pool, quick sort could be much, much slower than insertion sort.
@@ -1205,8 +1210,7 @@ To easily iterate entities and components, all the views offer the common
 `begin` and `end` member functions that allow users to use a view in a typical
 range-for loop. Almost all the views offer also a *more functional* `each`
 member function that accepts a callback for convenience.<br/>
-Continue reading for more details or refer to the
-[official documentation](https://skypjack.github.io/entt/).
+Continue reading for more details or refer to the inline documentation.
 
 ### Standard View
 
@@ -1233,8 +1237,7 @@ underlying data structures directly and avoid superfluous checks.<br/>
 They offer a bunch of functionalities to get the number of entities they are
 going to return and a raw access to the entity list as well as to the component
 list. It's also possible to ask a view if it contains a given entity.<br/>
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all
-the details.
+Refer to the inline documentation for all the details.
 
 There is no need to store views around for they are extremely cheap to
 construct, even though they can be copied without problems and reused freely. In
@@ -1279,8 +1282,7 @@ component. In particular, a multi component standard view exposes utility
 functions to get the estimated number of entities it is going to return and to
 know whether it's empty or not. It's also possible to ask a view if it contains
 a given entity.<br/>
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all
-the details.
+Refer to the inline documentation for all the details.
 
 There is no need to store views around for they are extremely cheap to
 construct, even though they can be copied without problems and reused freely. In
@@ -1357,8 +1359,7 @@ entities it's going to return, a raw access to the entity list and the
 possibility to sort the underlying data structures according to the order of one
 of the components for which it has been constructed. It's also possible to ask a
 view if it contains a given entity.<br/>
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all
-the details.
+Refer to the inline documentation for all the details.
 
 To iterate a persistent view, either use it in range-for loop:
 
@@ -1401,8 +1402,7 @@ accessed via an entity identifier.<br/>
 They offer a bunch of functionalities to get the number of instances they are
 going to return and a raw access to the entity list as well as to the component
 list.<br/>
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all
-the details.
+Refer to the inline documentation for all the details.
 
 Raw views can be used only to iterate components for a single type. To create
 this kind of views, the tag `raw_t` must also be used in order to disambiguate

+ 0 - 1
TODO

@@ -8,7 +8,6 @@
 * create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
 * does it worth it to add an optional functor to the member functions of snapshot so as to filter out instances and entities?
 * ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
-* reintroduce meaningful clone functionalities into the registry
 * improve CMake interface, see mail from Malte
 * is registry/utility.hpp really required?
 * "singleton mode" for tags (see #66)

+ 55 - 4
src/entt/entity/registry.hpp

@@ -376,10 +376,10 @@ public:
      * There are two kinds of entity identifiers:
      *
      * * Newly created ones in case no entities have been previously destroyed.
-     * * Recycled one with updated versions.
+     * * Recycled ones with updated versions.
      *
      * Users should not care about the type of the returned entity identifier.
-     * In case entity identifers are stored around, the `current` member
+     * In case entity identifers are stored around, the `valid` member
      * function can be used to know if they are still valid or the entity has
      * been destroyed and potentially recycled.
      *
@@ -407,15 +407,65 @@ public:
         return entity;
     }
 
+    /**
+     * @brief Clones an entity and returns the newly created one.
+     *
+     * There are two kinds of entity identifiers:
+     *
+     * * Newly created ones in case no entities have been previously destroyed.
+     * * Recycled ones with updated versions.
+     *
+     * Users should not care about the type of the returned entity identifier.
+     * In case entity identifers are stored around, the `valid` member
+     * function can be used to know if they are still valid or the entity has
+     * been destroyed and potentially recycled.
+     *
+     * @warning
+     * In case there are listeners that observe the construction of components
+     * and assign other components to the entity in their bodies, the result of
+     * invoking this function may not be as expected. In the worst case, it
+     * could lead to undefined behavior. An assertion will abort the execution
+     * at runtime in debug mode if a violation is detected.
+     *
+     * @warning
+     * Attempting to clone an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
+     *
+     * @param entity A valid entity identifier
+     * @return A valid entity identifier.
+     */
+    entity_type clone(const entity_type entity) ENTT_NOEXCEPT {
+        assert(valid(entity));
+        const auto other = create();
+
+        for(auto pos = pools.size(); pos; --pos) {
+            auto &cpool = std::get<0>(pools[pos-1]);
+
+            if(cpool && cpool->has(entity)) {
+                cpool->clone(other, entity);
+            }
+        }
+
+        return other;
+    }
+
     /**
      * @brief Destroys an entity and lets the registry recycle the identifier.
      *
      * When an entity is destroyed, its version is updated and the identifier
      * can be recycled at any time. In case entity identifers are stored around,
-     * the `current` member function can be used to know if they are still valid
+     * the `valid` member function can be used to know if they are still valid
      * or the entity has been destroyed and potentially recycled.
      *
      * @warning
+     * In case there are listeners that observe the destruction of components
+     * and assign other components to the entity in their bodies, the result of
+     * invoking this function may not be as expected. In the worst case, it
+     * could lead to undefined behavior. An assertion will abort the execution
+     * at runtime in debug mode if a violation is detected.
+     *
+     * @warning
      * Attempting to use an invalid entity results in undefined behavior.<br/>
      * An assertion will abort the execution at runtime in debug mode in case of
      * invalid entity.
@@ -445,6 +495,7 @@ public:
             }
         };
 
+        // just a way to protect users from listeners that attach components
         assert(orphan(entity));
 
         const auto entt = entity & traits_type::entity_mask;
@@ -1106,7 +1157,7 @@ public:
      *
      * Destroys all the entities. After a call to `reset`, all the entities
      * still in use are recycled with a new version number. In case entity
-     * identifers are stored around, the `current` member function can be used
+     * identifers are stored around, the `valid` member function can be used
      * to know if they are still valid.
      */
     void reset() {

+ 40 - 0
src/entt/entity/sparse_set.hpp

@@ -377,6 +377,28 @@ public:
         direct.push_back(entity);
     }
 
+    /**
+     * @brief Assigns an entity to a sparse set by cloning another entity.
+     *
+     * @warning
+     * Attempting to clone an entity that doesn't belong to the sparse set or to
+     * assign an entity that already belongs to the sparse set results in
+     * undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * sparse set doesn't contain the entity to clone or if it already contains
+     * the given entity.
+     *
+     * @param entity A valid entity identifier.
+     * @param source A valid entity identifier from which to clone.
+     */
+    inline virtual void clone(const entity_type entity, const entity_type source) {
+        assert(has(source));
+        assert(!has(entity));
+        construct(entity);
+        // useful to suppress warnings when asserts are disabled
+        (void)source;
+    }
+
     /**
      * @brief Removes an entity from a sparse set.
      *
@@ -820,6 +842,24 @@ public:
         return instances.back();
     }
 
+    /**
+     * @brief Assigns an entity to a sparse set by cloning another entity.
+     *
+     * @warning
+     * Attempting to clone an entity that doesn't belong to the sparse set or to
+     * assign an entity that already belongs to the sparse set results in
+     * undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * sparse set doesn't contain the entity to clone or if it already contains
+     * the given entity.
+     *
+     * @param entity A valid entity identifier.
+     * @param source A valid entity identifier from which to clone.
+     */
+    inline void clone(const entity_type entity, const entity_type source) override {
+        construct(entity, get(source));
+    }
+
     /**
      * @brief Removes an entity from a sparse set and destroies its object.
      *

+ 17 - 0
test/entt/entity/registry.cpp

@@ -354,6 +354,23 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
     ASSERT_EQ(registry.current(pre), registry.current(post));
 }
 
+TEST(DefaultRegistry, CloneEntities) {
+    entt::DefaultRegistry registry;
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity, 42);
+    registry.assign<char>(entity, 'c');
+
+    const auto other = registry.clone(entity);
+
+    ASSERT_TRUE(registry.has<int>(other));
+    ASSERT_TRUE(registry.has<char>(other));
+    ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
+    ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
+    ASSERT_EQ(registry.get<int>(other), 42);
+    ASSERT_EQ(registry.get<char>(other), 'c');
+}
+
 TEST(DefaultRegistry, AttachSetRemoveTags) {
     entt::DefaultRegistry registry;
     const auto &cregistry = registry;

+ 37 - 0
test/entt/entity/sparse_set.cpp

@@ -54,6 +54,23 @@ TEST(SparseSetNoType, Functionalities) {
     other = std::move(set);
 }
 
+TEST(SparseSetNoType, Clone) {
+    entt::SparseSet<unsigned int> set;
+
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(42));
+
+    set.construct(0);
+
+    ASSERT_TRUE(set.has(0));
+    ASSERT_FALSE(set.has(42));
+
+    set.clone(42, 0);
+
+    ASSERT_TRUE(set.has(0));
+    ASSERT_TRUE(set.has(42));
+}
+
 TEST(SparseSetNoType, DataBeginEnd) {
     entt::SparseSet<unsigned int> set;
 
@@ -307,6 +324,26 @@ TEST(SparseSetWithType, Functionalities) {
     other = std::move(set);
 }
 
+TEST(SparseSetWithType, Clone) {
+    entt::SparseSet<unsigned int, int> set;
+
+    ASSERT_FALSE(set.has(0));
+    ASSERT_FALSE(set.has(42));
+
+    set.construct(0, 3);
+
+    ASSERT_TRUE(set.has(0));
+    ASSERT_FALSE(set.has(42));
+    ASSERT_EQ(set.get(0), 3);
+
+    set.clone(42, 0);
+
+    ASSERT_TRUE(set.has(0));
+    ASSERT_TRUE(set.has(42));
+    ASSERT_EQ(set.get(0), set.get(42));
+    ASSERT_EQ(set.get(42), 3);
+}
+
 TEST(SparseSetWithType, AggregatesMustWork) {
     struct AggregateType { int value; };
     // the goal of this test is to enforce the requirements for aggregate types