From fa9b1817fe4977268fb1e5de261d3634cdd76708 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Sun, 23 Dec 2018 23:18:05 +0100 Subject: [PATCH] Start working on ECS --- CMakeLists.txt | 6 + src/body/Body.cpp | 4 +- src/body/Body.h | 18 ++- src/body/CollisionBody.cpp | 4 +- src/body/CollisionBody.h | 2 +- src/body/RigidBody.cpp | 4 +- src/body/RigidBody.h | 2 +- src/components/TransformComponents.cpp | 190 +++++++++++++++++++++++++ src/components/TransformComponents.h | 140 ++++++++++++++++++ src/configuration.h | 6 +- src/engine/CollisionWorld.cpp | 15 +- src/engine/CollisionWorld.h | 9 ++ src/engine/DynamicsWorld.cpp | 11 +- src/engine/Entity.cpp | 60 ++++++++ src/engine/Entity.h | 144 +++++++++++++++++++ src/engine/EntityManager.cpp | 75 ++++++++++ src/engine/EntityManager.h | 80 +++++++++++ 17 files changed, 756 insertions(+), 14 deletions(-) create mode 100644 src/components/TransformComponents.cpp create mode 100644 src/components/TransformComponents.h create mode 100644 src/engine/Entity.cpp create mode 100644 src/engine/Entity.h create mode 100644 src/engine/EntityManager.cpp create mode 100644 src/engine/EntityManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 873f78e5..0df07e6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,8 @@ SET (REACTPHYSICS3D_HEADERS "src/constraint/HingeJoint.h" "src/constraint/Joint.h" "src/constraint/SliderJoint.h" + "src/engine/Entity.h" + "src/engine/EntityManager.h" "src/engine/CollisionWorld.h" "src/engine/ConstraintSolver.h" "src/engine/ContactSolver.h" @@ -138,6 +140,7 @@ SET (REACTPHYSICS3D_HEADERS "src/engine/OverlappingPair.h" "src/engine/Timer.h" "src/engine/Timer.cpp" + "src/components/TransformComponents.h" "src/collision/CollisionCallback.h" "src/collision/OverlapCallback.h" "src/mathematics/mathematics.h" @@ -224,6 +227,9 @@ SET (REACTPHYSICS3D_SOURCES "src/engine/Material.cpp" "src/engine/OverlappingPair.cpp" "src/engine/Timer.cpp" + "src/engine/Entity.cpp" + "src/engine/EntityManager.cpp" + "src/components/TransformComponents.cpp" "src/collision/CollisionCallback.cpp" "src/mathematics/mathematics_functions.cpp" "src/mathematics/Matrix2x2.cpp" diff --git a/src/body/Body.cpp b/src/body/Body.cpp index 34ebe067..a9b1d0eb 100644 --- a/src/body/Body.cpp +++ b/src/body/Body.cpp @@ -35,8 +35,8 @@ using namespace reactphysics3d; /** * @param id ID of the new body */ -Body::Body(bodyindex id) - : mID(id), mIsAlreadyInIsland(false), mIsAllowedToSleep(true), mIsActive(true), +Body::Body(Entity entity, bodyindex id) + : mID(id), mEntity(entity), mIsAlreadyInIsland(false), mIsAllowedToSleep(true), mIsActive(true), mIsSleeping(false), mSleepTime(0), mUserData(nullptr) { #ifdef IS_LOGGING_ACTIVE diff --git a/src/body/Body.h b/src/body/Body.h index 8c17fd08..00cccac7 100644 --- a/src/body/Body.h +++ b/src/body/Body.h @@ -29,6 +29,7 @@ // Libraries #include #include "configuration.h" +#include "engine/Entity.h" /// Namespace reactphysics3d namespace reactphysics3d { @@ -50,8 +51,12 @@ class Body { // -------------------- Attributes -------------------- // /// ID of the body + // TODO : Remove this bodyindex mID; + /// Identifier of the entity in the ECS + Entity mEntity; + /// True if the body has already been added in an island (for sleeping technique) bool mIsAlreadyInIsland; @@ -88,7 +93,7 @@ class Body { // -------------------- Methods -------------------- // /// Constructor - Body(bodyindex id); + Body(Entity entity, bodyindex id); /// Deleted copy-constructor Body(const Body& body) = delete; @@ -102,6 +107,9 @@ class Body { /// Return the ID of the body bodyindex getId() const; + /// Return the corresponding entity of the body + Entity getEntity() const; + /// Return whether or not the body is allowed to sleep bool isAllowedToSleep() const; @@ -157,6 +165,14 @@ inline bodyindex Body::getId() const { return mID; } +// Return the corresponding entity of the body +/** + * @return The entity of the body + */ +inline Entity Body::getEntity() const { + return mEntity; +} + // Return whether or not the body is allowed to sleep /** * @return True if the body is allowed to sleep and false otherwise diff --git a/src/body/CollisionBody.cpp b/src/body/CollisionBody.cpp index 855869d5..642264fe 100644 --- a/src/body/CollisionBody.cpp +++ b/src/body/CollisionBody.cpp @@ -39,8 +39,8 @@ using namespace reactphysics3d; * @param world The physics world where the body is created * @param id ID of the body */ -CollisionBody::CollisionBody(const Transform& transform, CollisionWorld& world, bodyindex id) - : Body(id), mType(BodyType::DYNAMIC), mTransform(transform), mProxyCollisionShapes(nullptr), +CollisionBody::CollisionBody(const Transform& transform, CollisionWorld& world, Entity entity, bodyindex id) + : Body(entity, id), mType(BodyType::DYNAMIC), mTransform(transform), mProxyCollisionShapes(nullptr), mNbCollisionShapes(0), mContactManifoldsList(nullptr), mWorld(world) { #ifdef IS_PROFILING_ACTIVE diff --git a/src/body/CollisionBody.h b/src/body/CollisionBody.h index 4123a055..19f55594 100644 --- a/src/body/CollisionBody.h +++ b/src/body/CollisionBody.h @@ -118,7 +118,7 @@ class CollisionBody : public Body { // -------------------- Methods -------------------- // /// Constructor - CollisionBody(const Transform& transform, CollisionWorld& world, bodyindex id); + CollisionBody(const Transform& transform, CollisionWorld& world, Entity entity, bodyindex id); /// Destructor virtual ~CollisionBody() override; diff --git a/src/body/RigidBody.cpp b/src/body/RigidBody.cpp index 27fd75d5..a9d31746 100644 --- a/src/body/RigidBody.cpp +++ b/src/body/RigidBody.cpp @@ -39,8 +39,8 @@ using namespace reactphysics3d; * @param world The world where the body has been added * @param id The ID of the body */ -RigidBody::RigidBody(const Transform& transform, CollisionWorld& world, bodyindex id) - : CollisionBody(transform, world, id), mArrayIndex(0), mInitMass(decimal(1.0)), +RigidBody::RigidBody(const Transform& transform, CollisionWorld& world, Entity entity, bodyindex id) + : CollisionBody(transform, world, entity, id), mArrayIndex(0), mInitMass(decimal(1.0)), mCenterOfMassLocal(0, 0, 0), mCenterOfMassWorld(transform.getPosition()), mIsGravityEnabled(true), mMaterial(world.mConfig), mLinearDamping(decimal(0.0)), mAngularDamping(decimal(0.0)), mJointsList(nullptr), mIsCenterOfMassSetByUser(false), mIsInertiaTensorSetByUser(false) { diff --git a/src/body/RigidBody.h b/src/body/RigidBody.h index e9e78eee..8ab06b13 100644 --- a/src/body/RigidBody.h +++ b/src/body/RigidBody.h @@ -134,7 +134,7 @@ class RigidBody : public CollisionBody { // -------------------- Methods -------------------- // /// Constructor - RigidBody(const Transform& transform, CollisionWorld& world, bodyindex id); + RigidBody(const Transform& transform, CollisionWorld& world, Entity entity, bodyindex id); /// Destructor virtual ~RigidBody() override; diff --git a/src/components/TransformComponents.cpp b/src/components/TransformComponents.cpp new file mode 100644 index 00000000..3c702ead --- /dev/null +++ b/src/components/TransformComponents.cpp @@ -0,0 +1,190 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "TransformComponents.h" +#include "engine/EntityManager.h" +#include +#include + +// We want to use the ReactPhysics3D namespace +using namespace reactphysics3d; + + +// Constructor +TransformComponents::TransformComponents(MemoryAllocator& allocator) + :mMemoryAllocator(allocator), mNbComponents(0), mNbAllocatedComponents(0), + mBuffer(nullptr), mMapEntityToComponentIndex(allocator) { + + // Allocate memory for the components data + allocate(INIT_ALLOCATED_COMPONENTS); +} + +// Destructor +TransformComponents::~TransformComponents() { + + if (mNbAllocatedComponents > 0) { + + // Destroy all the remaining components + for (uint32 i = 0; i < mNbComponents; i++) { + destroyComponent(i); + } + + // Size for the data of a single component (in bytes) + const size_t totalSizeBytes = mNbAllocatedComponents * COMPONENT_DATA_SIZE; + + // Release the allocated memory + mMemoryAllocator.release(mBuffer, totalSizeBytes); + } +} + +// Allocate memory for a given number of components +void TransformComponents::allocate(uint32 nbComponentsToAllocate) { + + assert(nbComponentsToAllocate > mNbAllocatedComponents); + + // Size for the data of a single component (in bytes) + const size_t totalSizeBytes = nbComponentsToAllocate * COMPONENT_DATA_SIZE; + + // Allocate memory + void* newBuffer = mMemoryAllocator.allocate(totalSizeBytes); + assert(newBuffer != nullptr); + + // New pointers to components data + Entity* newEntities = static_cast(newBuffer); + Transform* newTransforms = reinterpret_cast(newEntities + mNbAllocatedComponents); + + // If there was already components before + if (mNbAllocatedComponents > 0) { + + // Copy component data from the previous buffer to the new one + memcpy(newTransforms, mTransforms, mNbComponents * sizeof(Transform)); + memcpy(newEntities, mEntities, mNbComponents * sizeof(Entity)); + + // Deallocate previous memory + mMemoryAllocator.release(mBuffer, mNbAllocatedComponents * COMPONENT_DATA_SIZE); + } + + mBuffer = newBuffer; + mEntities = newEntities; + mTransforms = newTransforms; + mNbAllocatedComponents = nbComponentsToAllocate; +} + +// Add a component +void TransformComponents::addComponent(Entity entity, const TransformComponent& component) { + + // If we need to allocate more components + if (mNbComponents == mNbAllocatedComponents) { + allocate(mNbAllocatedComponents * 2); + } + + // Map the entity with the new component lookup index + mMapEntityToComponentIndex.add(Pair(entity, mNbComponents)); + + // Insert the new component data + new (mEntities + mNbComponents) Entity(entity); + new (mTransforms + mNbComponents) Transform(component.transform); + + mNbComponents++; +} + +// Perform garbage collection to remove unused components +void TransformComponents::garbageCollection(const EntityManager& entityManager) { + + // TODO : Make sure we call this method each frame + + // We use lazy garbage collection. The idea is to pick random components and destroy + // them if their corresponding entities have been destroyed. We do this until we hit + // GARBAGE_COLLECTION_MAX_VALID_ENTITIES in a row. Therefore, it cost almost nothing + // if there are no destroyed entities and it very quickly destroys components where there + // are a lot of destroyed entities. + + uint32 nbHitValidEntitiesInARow = 0; + + // For random number generation + std::random_device rd; + std::mt19937 eng(rd()); + + while (mNbComponents > 0 && nbHitValidEntitiesInARow < GARBAGE_COLLECTION_MAX_VALID_ENTITIES) { + + // Select a random index in the components array + std::uniform_int_distribution distr(0, mNbComponents - 1); + uint32 i = distr(eng); + + // If the corresponding entity is valid + if (entityManager.isValid(mEntities[i])) { + nbHitValidEntitiesInARow++; + + continue; + } + + nbHitValidEntitiesInARow = 0; + + // Destroy the component + removeComponent(i); + } +} + +// Remove a component at a given index +void TransformComponents::removeComponent(uint32 index) { + + assert(index < mNbComponents); + + // We want to keep the arrays tightly packed. Therefore, when a component is removed, + // we replace it with the last element of the array + + const uint32 lastIndex = mNbComponents - 1; + + Entity entity = mEntities[index]; + Entity lastEntity = mEntities[lastIndex]; + + if (mNbComponents > 1 && index != lastIndex) { + + // Replace the data of the component to destroy by the data of the last component + mEntities[index] = mEntities[lastIndex]; + mTransforms[index] = mTransforms[lastIndex]; + + // Update the entity to component index mapping + mMapEntityToComponentIndex[lastEntity] = index; + } + else { + + // Call the destructors on the component values + destroyComponent(index); + } + + // Update the entity to component index mapping + mMapEntityToComponentIndex.remove(entity); + + mNbComponents--; +} + +// Destroy a component at a given index +void TransformComponents::destroyComponent(uint32 index) { + + mEntities[index].~Entity(); + mTransforms[index].~Transform(); +} diff --git a/src/components/TransformComponents.h b/src/components/TransformComponents.h new file mode 100644 index 00000000..3212c846 --- /dev/null +++ b/src/components/TransformComponents.h @@ -0,0 +1,140 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef REACTPHYSICS3D_TRANSFORM_COMPONENTS_H +#define REACTPHYSICS3D_TRANSFORM_COMPONENTS_H + +// Libraries +#include "mathematics/Transform.h" +#include "engine/Entity.h" +#include "containers/Map.h" + +// ReactPhysics3D namespace +namespace reactphysics3d { + +// Class declarations +class MemoryAllocator; +class EntityManager; + +// Class TransformComponents +/** + * This class represent the component of the ECS that contains the transforms of the + * different entities. The position and orientation of the bodies are stored there. + */ +class TransformComponents { + + private: + + // -------------------- Constants -------------------- // + + /// Number of components to allocated at the beginning + const uint32 INIT_ALLOCATED_COMPONENTS = 10; + + /// Number of valid entities to hit before stopping garbage collection + const uint32 GARBAGE_COLLECTION_MAX_VALID_ENTITIES = 5; + + const size_t COMPONENT_DATA_SIZE = sizeof(Entity) + sizeof(Transform); + + // -------------------- Attributes -------------------- // + + /// Memory allocator + MemoryAllocator& mMemoryAllocator; + + /// Current number of components + uint32 mNbComponents; + + /// Number of allocated components + uint32 mNbAllocatedComponents; + + /// Allocated memory for all the data of the components + void* mBuffer; + + /// Map an entity to the index of its component in the array + Map mMapEntityToComponentIndex; + + /// Array of entities of each component + Entity* mEntities; + + /// Array of transform of each component + Transform* mTransforms; + + // -------------------- Methods -------------------- // + + /// Remove a component at a given index + void removeComponent(uint32 index); + + /// Destroy a component at a given index + void destroyComponent(uint32 index); + + public: + + /// Structure for the data of a transform component + struct TransformComponent { + + const Transform& transform; + + /// Constructor + TransformComponent(const Transform& transform) : transform(transform) { + + } + }; + + // -------------------- Methods -------------------- // + + /// Constructor + TransformComponents(MemoryAllocator& allocator); + + /// Destructor + ~TransformComponents(); + + /// Allocate memory for a given number of components + void allocate(uint32 nbComponentsToAllocate); + + /// Add a component + void addComponent(Entity entity, const TransformComponent& component); + + /// Perform garbage collection to remove unused components + void garbageCollection(const EntityManager& entityManager); + + /// Return the transform of an entity + Transform& getTransform(Entity entity) const; + + /// Set the transform of an entity + void setTransform(Entity entity, const Transform& transform); +}; + +// Return the transform of an entity +inline Transform& TransformComponents::getTransform(Entity entity) const { + return mTransforms[mMapEntityToComponentIndex[entity]]; +} + +// Set the transform of an entity +inline void TransformComponents::setTransform(Entity entity, const Transform& transform) { + mTransforms[mMapEntityToComponentIndex[entity]] = transform; +} + +} + +#endif diff --git a/src/configuration.h b/src/configuration.h index 60b670e7..2686635a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -53,8 +53,6 @@ using uint = unsigned int; using uchar = unsigned char; using ushort = unsigned short; using luint = long unsigned int; -using bodyindex = luint; -using bodyindexpair = Pair; using int8 = std::int8_t; using uint8 = std::uint8_t; @@ -63,6 +61,10 @@ using uint16 = std::uint16_t; using int32 = std::int32_t; using uint32 = std::uint32_t; +// TODO : Delete this +using bodyindex = luint; +using bodyindexpair = Pair; + // ------------------- Enumerations ------------------- // /// Position correction technique used in the constraint solver (for joints). diff --git a/src/engine/CollisionWorld.cpp b/src/engine/CollisionWorld.cpp index 0dcab550..15530583 100644 --- a/src/engine/CollisionWorld.cpp +++ b/src/engine/CollisionWorld.cpp @@ -37,7 +37,9 @@ uint CollisionWorld::mNbWorlds = 0; // Constructor CollisionWorld::CollisionWorld(const WorldSettings& worldSettings, Logger* logger, Profiler* profiler) - : mConfig(worldSettings), mCollisionDetection(this, mMemoryManager), mBodies(mMemoryManager.getPoolAllocator()), mCurrentBodyId(0), + : mConfig(worldSettings), mEntityManager(mMemoryManager.getPoolAllocator()), + mTransformComponents(mMemoryManager.getBaseAllocator()), + mCollisionDetection(this, mMemoryManager), mBodies(mMemoryManager.getPoolAllocator()), mCurrentBodyId(0), mFreeBodiesIds(mMemoryManager.getPoolAllocator()), mEventListener(nullptr), mName(worldSettings.worldName), mIsProfilerCreatedByUser(profiler != nullptr), mIsLoggerCreatedByUser(logger != nullptr) { @@ -138,16 +140,22 @@ CollisionWorld::~CollisionWorld() { */ CollisionBody* CollisionWorld::createCollisionBody(const Transform& transform) { + // Create a new entity for the body + Entity entity = mEntityManager.createEntity(); + // Get the next available body ID bodyindex bodyID = computeNextAvailableBodyId(); // Largest index cannot be used (it is used for invalid index) assert(bodyID < std::numeric_limits::max()); + // Add a transform component + mTransformComponents.addComponent(entity, TransformComponents::TransformComponent(transform)); + // Create the collision body CollisionBody* collisionBody = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, sizeof(CollisionBody))) - CollisionBody(transform, *this, bodyID); + CollisionBody(transform, *this, entity, bodyID); assert(collisionBody != nullptr); @@ -189,6 +197,9 @@ void CollisionWorld::destroyCollisionBody(CollisionBody* collisionBody) { // Reset the contact manifold list of the body collisionBody->resetContactManifoldsList(); + // Destroy the corresponding entity + mEntityManager.destroyEntity(collisionBody->getEntity()); + // Call the destructor of the collision body collisionBody->~CollisionBody(); diff --git a/src/engine/CollisionWorld.h b/src/engine/CollisionWorld.h index 3a7fb87a..932a3739 100644 --- a/src/engine/CollisionWorld.h +++ b/src/engine/CollisionWorld.h @@ -32,6 +32,8 @@ #include "collision/CollisionDetection.h" #include "constraint/Joint.h" #include "memory/MemoryManager.h" +#include "engine/EntityManager.h" +#include "components/TransformComponents.h" /// Namespace reactphysics3d namespace reactphysics3d { @@ -66,6 +68,12 @@ class CollisionWorld { /// Configuration of the physics world WorldSettings mConfig; + /// Entity Manager for the ECS + EntityManager mEntityManager; + + /// Transform Components + TransformComponents mTransformComponents; + /// Reference to the collision detection CollisionDetection mCollisionDetection; @@ -190,6 +198,7 @@ class CollisionWorld { friend class CollisionDetection; friend class CollisionBody; friend class RigidBody; + friend class ProxyShape; friend class ConvexMeshShape; }; diff --git a/src/engine/DynamicsWorld.cpp b/src/engine/DynamicsWorld.cpp index 7b078677..a9615d51 100644 --- a/src/engine/DynamicsWorld.cpp +++ b/src/engine/DynamicsWorld.cpp @@ -417,15 +417,21 @@ void DynamicsWorld::solvePositionCorrection() { */ RigidBody* DynamicsWorld::createRigidBody(const Transform& transform) { + // Create a new entity for the body + Entity entity = mEntityManager.createEntity(); + // Compute the body ID bodyindex bodyID = computeNextAvailableBodyId(); // Largest index cannot be used (it is used for invalid index) assert(bodyID < std::numeric_limits::max()); + // Add a transform component + mTransformComponents.addComponent(entity, TransformComponents::TransformComponent(transform)); + // Create the rigid body RigidBody* rigidBody = new (mMemoryManager.allocate(MemoryManager::AllocationType::Pool, - sizeof(RigidBody))) RigidBody(transform, *this, bodyID); + sizeof(RigidBody))) RigidBody(transform, *this, entity, bodyID); assert(rigidBody != nullptr); // Add the rigid body to the physics world @@ -471,6 +477,9 @@ void DynamicsWorld::destroyRigidBody(RigidBody* rigidBody) { // Reset the contact manifold list of the body rigidBody->resetContactManifoldsList(); + // Destroy the corresponding entity + mEntityManager.destroyEntity(rigidBody->getEntity()); + // Call the destructor of the rigid body rigidBody->~RigidBody(); diff --git a/src/engine/Entity.cpp b/src/engine/Entity.cpp new file mode 100644 index 00000000..aa5e2401 --- /dev/null +++ b/src/engine/Entity.cpp @@ -0,0 +1,60 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "Entity.h" +#include + +using namespace reactphysics3d; + +// Static members initialization +const uint32 Entity::ENTITY_INDEX_BITS = 24; +const uint32 Entity::ENTITY_INDEX_MASK = (1 << Entity::ENTITY_INDEX_BITS) - 1; +const uint32 Entity::ENTITY_GENERATION_BITS = 8; +const uint32 Entity::ENTITY_GENERATION_MASK = (1 << Entity::ENTITY_GENERATION_BITS) - 1; +const uint32 Entity::MINIMUM_FREE_INDICES = 1024; + +// Constructor +Entity::Entity(uint32 index, uint32 generation) + :id((index & ENTITY_INDEX_MASK) | ((generation & ENTITY_GENERATION_MASK) << ENTITY_INDEX_BITS)) { + + uint32 test1 = index & ENTITY_INDEX_MASK; + uint32 test2 = (generation & ENTITY_INDEX_MASK) << ENTITY_INDEX_BITS; + uint32 test3 = test1 | test2; + uint32 test = getIndex(); + assert(getIndex() == index); + assert(getGeneration() == generation); +} + +// Assignment operator +Entity& Entity::operator=(const Entity& entity) { + + if (&entity != this) { + + id = entity.id; + } + + return *this; +} diff --git a/src/engine/Entity.h b/src/engine/Entity.h new file mode 100644 index 00000000..85f3d0a9 --- /dev/null +++ b/src/engine/Entity.h @@ -0,0 +1,144 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef REACTPHYSICS3D_ENTITY_H +#define REACTPHYSICS3D_ENTITY_H + +// Libraries +#include "configuration.h" + +/// Namespace reactphysics3d +namespace reactphysics3d { + +// Structure Entity +/** + * This class is used to identify an entity in the Entity-Component-System. + * Entities are used for bodies in the physics engine. The id of an entity is + * a 32 bits integer that is separated in two parts. The index and the generation + * parts. The idea is that the index part directly gives us the index of the entity + * in a lookup array. However, we want to support deletion of the entities. That's why + * we have the generation part. This number is used to distinguish the entities created + * at the same index in the array. If it is the case, we will increase the generation number + * each time. + * + * We use 24 bits for the index. Therefore, we support 16 millions simultaneous entries. + * We use 8 bits for the generation. Therefore, we support 256 entries created at the same index. + * To prevent reaching the 256 entries too fast, we make sure that we do not reuse the same index + * slot too ofen. To do that, we put recycled indices in a queue and only reuse values from that + * queue when it contains at least MINIMUM_FREE_INDICES values. It means, an id will reappear until + * its index has run 256 laps through the queue. It means that we must create and destroy at least + * 256 * MINIMUM_FREE_INDICES entities until an id can reappear. This seems reasonably safe. + * + * This implementation is based on the following article: + * http://bitsquid.blogspot.com/2014/08/building-data-oriented-entity-system.html + */ +struct Entity { + + private: + + /// Number of bits reserved for the index + static const uint32 ENTITY_INDEX_BITS; + + /// Mask for the index part of the id + static const uint32 ENTITY_INDEX_MASK; + + /// Number of bits reserved for the generation number + static const uint32 ENTITY_GENERATION_BITS; + + /// Mask for the generation part of the id + static const uint32 ENTITY_GENERATION_MASK; + + /// Minimum of free indices in the queue before we reuse one from the queue + static const uint32 MINIMUM_FREE_INDICES; + + public: + + // -------------------- Attributes -------------------- // + + /// Id of the entity + uint32 id; + + // -------------------- Methods -------------------- // + + /// Constructor + Entity(uint32 index, uint32 generation); + + /// Return the lookup index of the entity in a array + uint32 getIndex() const; + + /// Return the generation number of the entity + uint32 getGeneration() const; + + /// Assignment operator + Entity& operator=(const Entity& entity); + + /// Equality operator + bool operator==(const Entity& entity) const; + + /// Inequality operator (it != end()) + bool operator!=(const Entity& entity) const; + + // -------------------- Friendship -------------------- // + + friend class EntityManager; + +}; + +// Return the lookup index of the entity in a array +inline uint32 Entity::getIndex() const { + return id & ENTITY_INDEX_MASK; +} + +// Return the generation number of the entity +inline uint32 Entity::getGeneration() const { + return (id >> ENTITY_INDEX_BITS) & ENTITY_GENERATION_MASK; +} + +// Equality operator +inline bool Entity::operator==(const Entity& entity) const { + + return entity.id == id; +} + +// Inequality operator (it != end()) +inline bool Entity::operator!=(const Entity& entity) const { + return entity.id != id; +} + +} + +// Hash function for a reactphysics3d Entity +namespace std { + + template <> struct hash { + + size_t operator()(const reactphysics3d::Entity& entity) const { + + return entity.id; + } + }; +} + +#endif diff --git a/src/engine/EntityManager.cpp b/src/engine/EntityManager.cpp new file mode 100644 index 00000000..f2653ad6 --- /dev/null +++ b/src/engine/EntityManager.cpp @@ -0,0 +1,75 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +// Libraries +#include "EntityManager.h" +#include "Entity.h" + +using namespace reactphysics3d; + +// Constructor +EntityManager::EntityManager(MemoryAllocator& allocator) + :mGenerations(allocator), mFreeIndices(allocator) { + +} + +// Create a new entity +Entity EntityManager::createEntity() { + + uint32 index; + + // If there are already enough free indices to start using them + if (mFreeIndices.size() > Entity::MINIMUM_FREE_INDICES) { + + // Recycle an index from the free indices + index = mFreeIndices.getFront(); + mFreeIndices.popFront(); + } + else { + + // We start at generation 0 + mGenerations.add(0); + + // Create a new indice + index = static_cast(mGenerations.size()) - 1; + + assert(index < (1 << Entity::ENTITY_INDEX_BITS)); + } + + // Return a new entity + return Entity(index, mGenerations[index]); +} + +// Destroy an entity +void EntityManager::destroyEntity(Entity entity) { + + const uint32 index = entity.getIndex(); + + // Increment the generation of this index + mGenerations[index]++; + + // Add the index into the deque of free indices + mFreeIndices.addBack(index); +} diff --git a/src/engine/EntityManager.h b/src/engine/EntityManager.h new file mode 100644 index 00000000..9c1bacc3 --- /dev/null +++ b/src/engine/EntityManager.h @@ -0,0 +1,80 @@ +/******************************************************************************** +* ReactPhysics3D physics library, http://www.reactphysics3d.com * +* Copyright (c) 2010-2018 Daniel Chappuis * +********************************************************************************* +* * +* This software is provided 'as-is', without any express or implied warranty. * +* In no event will the authors be held liable for any damages arising from the * +* use of this software. * +* * +* Permission is granted to anyone to use this software for any purpose, * +* including commercial applications, and to alter it and redistribute it * +* freely, subject to the following restrictions: * +* * +* 1. The origin of this software must not be misrepresented; you must not claim * +* that you wrote the original software. If you use this software in a * +* product, an acknowledgment in the product documentation would be * +* appreciated but is not required. * +* * +* 2. Altered source versions must be plainly marked as such, and must not be * +* misrepresented as being the original software. * +* * +* 3. This notice may not be removed or altered from any source distribution. * +* * +********************************************************************************/ + +#ifndef REACTPHYSICS3D_ENTITY_MANAGER_H +#define REACTPHYSICS3D_ENTITY_MANAGER_H + +// Libraries +#include "configuration.h" +#include "containers/List.h" +#include "containers/Deque.h" +#include "engine/Entity.h" + +/// Namespace reactphysics3d +namespace reactphysics3d { + +// Class EntityManager +/** + * This class is responsible to manage the entities of the ECS. + */ +class EntityManager { + + private: + + // -------------------- Attributes -------------------- // + + /// List storing the generations of the created entities + List mGenerations; + + /// Deque with the indices of destroyed entities that can be reused + Deque mFreeIndices; + + // -------------------- Methods -------------------- // + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + EntityManager(MemoryAllocator& allocator); + + /// Create a new entity + Entity createEntity(); + + /// Destroy an entity + void destroyEntity(Entity entity); + + /// Return true if the entity is still valid (not destroyed) + bool isValid(Entity entity) const; +}; + +// Return true if the entity is still valid (not destroyed) +inline bool EntityManager::isValid(Entity entity) const { + return mGenerations[entity.getIndex()] == entity.getGeneration(); +} + +} + +#endif