From d3d2e594fefe1846ce6278639b4e32b43e60da8d Mon Sep 17 00:00:00 2001 From: sippeangelo Date: Thu, 3 Dec 2015 15:07:35 +0100 Subject: [PATCH] Basic and working entity system with a really ugly test case --- include/Engine/Core/ComponentInfo.h | 11 ++- include/Engine/Core/ComponentPool.h | 93 +++++++------------------- include/Engine/Core/ComponentWrapper.h | 29 ++++---- include/Engine/Core/Entity.h | 12 ---- include/Engine/Core/EntityFactory.h | 8 +-- include/Engine/Core/EntityWrapper.h | 15 +++++ include/Engine/Core/World.h | 35 +++++----- src/Engine/Core/ComponentPool.cpp | 79 ++++++++++++++++++++++ src/Engine/Core/World.cpp | 54 +++++++++++++++ src/Tests/ComponentPoolTest.cpp | 44 ++++++------ src/Tests/WorldTest.cpp | 41 ++++++++++++ 11 files changed, 286 insertions(+), 135 deletions(-) delete mode 100644 include/Engine/Core/Entity.h create mode 100644 include/Engine/Core/EntityWrapper.h create mode 100644 src/Engine/Core/ComponentPool.cpp create mode 100644 src/Engine/Core/World.cpp create mode 100644 src/Tests/WorldTest.cpp diff --git a/include/Engine/Core/ComponentInfo.h b/include/Engine/Core/ComponentInfo.h index 6453bf32..ee5f1a11 100644 --- a/include/Engine/Core/ComponentInfo.h +++ b/include/Engine/Core/ComponentInfo.h @@ -16,7 +16,16 @@ struct ComponentInfo std::unordered_map FieldTypes; std::unordered_map FieldOffsets; Meta_t Meta; - std::shared_ptr Defaults = nullptr; + std::shared_ptr Defaults = nullptr; +}; + +template<> +struct std::hash +{ + inline std::size_t operator()(const ComponentInfo& v) const + { + return std::hash()(v.Name); + } }; #endif \ No newline at end of file diff --git a/include/Engine/Core/ComponentPool.h b/include/Engine/Core/ComponentPool.h index fe1c00db..f0d53864 100644 --- a/include/Engine/Core/ComponentPool.h +++ b/include/Engine/Core/ComponentPool.h @@ -9,51 +9,21 @@ class ComponentPoolForwardIterator : public std::iterator { public: - ComponentPoolForwardIterator(const ComponentInfo& componentInfo, const MemoryPool::iterator begin, const MemoryPool::iterator end) - : m_ComponentInfo(componentInfo) + ComponentPoolForwardIterator(const ComponentInfo& componentInfo, const MemoryPool::iterator begin, const MemoryPool::iterator end) + : m_ComponentInfo(componentInfo) , m_MemoryPoolIterator(begin) , m_MemoryPoolEnd(end) - { } + { } ComponentPoolForwardIterator(const ComponentPoolForwardIterator& other) = default; ComponentPoolForwardIterator(ComponentPoolForwardIterator&& other) = default; ~ComponentPoolForwardIterator() = default; - ComponentPoolForwardIterator& operator= (const ComponentPoolForwardIterator& other) = default; - - ComponentPoolForwardIterator& operator++() - { - ++m_MemoryPoolIterator; - return *this; - } - - ComponentPoolForwardIterator& operator++(int) - { - ComponentPoolForwardIterator copyIter(*this); - operator++(); - return copyIter; - } - - bool operator!= (const ComponentPoolForwardIterator& other) const - { - return m_MemoryPoolIterator != other.m_MemoryPoolIterator; - } - - bool operator== (const ComponentPoolForwardIterator& other) const - { - return m_MemoryPoolIterator == other.m_MemoryPoolIterator; - } - - ComponentWrapper operator* () const - { - char* data = &(*m_MemoryPoolIterator); - ComponentWrapper wrapper(m_ComponentInfo, data); - return wrapper; - } - - //ComponentWrapper* operator-> () const - //{ - // return &(*m_MemoryPoolIterator); - //} + ComponentPoolForwardIterator& operator=(const ComponentPoolForwardIterator& other) = default; + ComponentPoolForwardIterator& operator++(); + ComponentPoolForwardIterator& operator++(int); + bool operator!=(const ComponentPoolForwardIterator& other) const; + bool operator==(const ComponentPoolForwardIterator& other) const; + ComponentWrapper operator*() const; private: const ComponentInfo& m_ComponentInfo; @@ -71,55 +41,40 @@ public: typedef ComponentWrapper* pointer; typedef ComponentWrapper& reference; - ComponentPool(const ComponentInfo& ci) + ComponentPool(const ::ComponentInfo& ci) : m_ComponentInfo(ci) - , m_Pool(ci.Meta.Allocation, ci.Meta.Stride) + , m_Pool(ci.Meta.Allocation, sizeof(EntityID) + ci.Meta.Stride) { } - ComponentPool(const ComponentPool& other) = delete; ComponentPool(const ComponentPool&& other) = delete; - ComponentWrapper New() - { - char* data = m_Pool.Allocate(); - return ComponentWrapper(m_ComponentInfo, data); - } + const ::ComponentInfo& ComponentInfo() const { return m_ComponentInfo; } - void Delete(ComponentWrapper& component) - { - m_Pool.Free(component.Data); - } + // Allocate space for a component and store which entity it belongs to in internal structure + ComponentWrapper Allocate(EntityID entity); + // Get the component belonging to a specific entity + ComponentWrapper GetByEntity(EntityID ent); + // Delete a component and free its memory + void Delete(ComponentWrapper& wrapper); - iterator begin() const - { - return iterator(m_ComponentInfo, m_Pool.begin(), m_Pool.end()); - } - - iterator end() const - { - return iterator(m_ComponentInfo, m_Pool.end(), m_Pool.end()); - } + iterator begin() const; + iterator end() const; //Dumps information about what the pool memory looks like right now //into an output stream (e.g. file/std::cout, anything that has an operator<<) //Interpret the data in the memory as InterpretType. template - void Dump(OutStream& out) const - { - m_Pool.Dump(out); - } + void Dump(OutStream& out) const; //Dumps information about what the pool memory looks like right now //into std::cout. Interpret the data in the memory as InterpretType. template - void Dump() const - { - m_Pool.Dump(); - } + void Dump() const; private: - const ComponentInfo m_ComponentInfo; + ::ComponentInfo m_ComponentInfo; MemoryPool m_Pool; + std::unordered_map m_EntityToComponent; }; #endif \ No newline at end of file diff --git a/include/Engine/Core/ComponentWrapper.h b/include/Engine/Core/ComponentWrapper.h index 5efef105..7b388267 100644 --- a/include/Engine/Core/ComponentWrapper.h +++ b/include/Engine/Core/ComponentWrapper.h @@ -1,20 +1,20 @@ -#ifndef Component_h__ -#define Component_h__ +#ifndef ComponentWrapper_h__ +#define ComponentWrapper_h__ #include "../Common.h" -#include "Entity.h" +#include "EntityWrapper.h" #include "ComponentInfo.h" struct ComponentWrapper { ComponentWrapper(const ComponentInfo& componentInfo, char* data) - : EntityID(*reinterpret_cast<::EntityID*>(data)) - , Info(componentInfo) - , Data(data) + : Info(componentInfo) + , EntityID(*reinterpret_cast<::EntityID*>(data)) + , Data(data + sizeof(EntityID)) { } - ::EntityID& EntityID; const ComponentInfo& Info; + const ::EntityID EntityID; char* Data; template @@ -25,13 +25,13 @@ struct ComponentWrapper } template - void SetProperty(std::string name, T& value) { Property(name)=value; } - template - void SetProperty(std::string name, const T value) { Property(name)=value; } + void SetProperty(std::string name, const T value) { Property(name) = value; } + //template + //void SetProperty(std::string name, T& value) { Property(name) = value; } // Specialization for string literals template - void SetProperty(std::string name, const char(&value)[N]) { Property(name)=std::string(value); } + void SetProperty(std::string name, const char(&value)[N]) { Property(name) = std::string(value); } struct SubscriptProxy { @@ -51,8 +51,9 @@ struct ComponentWrapper template void operator=(const T val) { m_Component->SetProperty(m_PropertyName, val); } - template - void operator=(T& val) { m_Component->SetProperty(m_PropertyName, val); } + // TODO: Pass by reference and rvalue (universal reference?) + //template + //void operator=(T& val) { m_Component->SetProperty(m_PropertyName, val); } // Specialization for string literals template @@ -61,4 +62,4 @@ struct ComponentWrapper SubscriptProxy operator[](std::string propertyName) { return SubscriptProxy(this, propertyName); } }; -#endif \ No newline at end of file +#endif diff --git a/include/Engine/Core/Entity.h b/include/Engine/Core/Entity.h deleted file mode 100644 index 111ea117..00000000 --- a/include/Engine/Core/Entity.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef Entity_h__ -#define Entity_h__ - -typedef unsigned int EntityID; - -struct Entity -{ - unsigned int EntityID = 0; - -}; - -#endif \ No newline at end of file diff --git a/include/Engine/Core/EntityFactory.h b/include/Engine/Core/EntityFactory.h index 39a9ab02..d1dbce03 100644 --- a/include/Engine/Core/EntityFactory.h +++ b/include/Engine/Core/EntityFactory.h @@ -27,7 +27,7 @@ #include #include -#include "Entity.h" +#include "EntityWrapper.h" #include "ComponentWrapper.h" class EntityPreprocessorXMLErrorHandler : public xercesc::DOMErrorHandler @@ -231,7 +231,7 @@ private: std::map m_ComponentInfo; public: std::map m_ComponentStore; - std::vector m_Entities; + std::vector m_Entities; private: /* @@ -380,8 +380,8 @@ private: // Calculate component size unsigned int stride = 0; // Reserve space for Entity pointer - stride += sizeof(Entity*); - std::cout << " Entity " << " (" << sizeof(Entity*) << " byte)" << std::endl; + stride += sizeof(EntityWrapper*); + std::cout << " Entity " << " (" << sizeof(EntityWrapper*) << " byte)" << std::endl; // Add size of fields for (auto& field : ci.FieldTypes) { std::cout << " " << field.second << " " << field.first << " (" << getTypeStride(field.second) << " byte)" << std::endl; diff --git a/include/Engine/Core/EntityWrapper.h b/include/Engine/Core/EntityWrapper.h new file mode 100644 index 00000000..d5e723ca --- /dev/null +++ b/include/Engine/Core/EntityWrapper.h @@ -0,0 +1,15 @@ +#ifndef Entity_h__ +#define Entity_h__ + +typedef unsigned int EntityID; + +struct EntityWrapper +{ + EntityWrapper(EntityID entityID) + : ID(entityID) + { } + + EntityID ID; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/World.h b/include/Engine/Core/World.h index 0eac4550..20bbe288 100644 --- a/include/Engine/Core/World.h +++ b/include/Engine/Core/World.h @@ -2,32 +2,35 @@ #define World_h__ #include "../Common.h" -#include "Entity.h" +#include "EntityWrapper.h" +#include "ObjectPool.h" #include "ComponentPool.h" class World { public: - World() - { + World() = default; + ~World(); - } + EntityID CreateEntity(EntityID parent = 0); - ~World() - { - // foreach (m_ComponentPools... - } - - void AllocateComponentPool(ComponentInfo& ci) - { - auto pool = new ComponentPool(ci); - m_ComponentPools[ci.Name] = pool; - } - - void AddComponent(EntityID entity, std::string componentType); + // Register a component type and allocate space for it + void RegisterComponent(ComponentInfo& ci); + // Attach a component to an entity and fill it with default values + ComponentWrapper AttachComponent(EntityID entity, std::string componentType); + // Get a component of an entity + ComponentWrapper GetComponent(EntityID entity, std::string componentType); + // Get all components of the specified type + const ComponentPool& GetComponents(std::string componentType); private: + EntityID m_CurrentEntityID = 0; + + std::unordered_map m_EntityParents; + std::unordered_multimap m_EntityChildren; std::unordered_map m_ComponentPools; + + EntityID generateEntityID(); }; #endif \ No newline at end of file diff --git a/src/Engine/Core/ComponentPool.cpp b/src/Engine/Core/ComponentPool.cpp new file mode 100644 index 00000000..146b33f6 --- /dev/null +++ b/src/Engine/Core/ComponentPool.cpp @@ -0,0 +1,79 @@ +#include "Core/ComponentPool.h" + + + +ComponentWrapper ComponentPoolForwardIterator::operator*() const +{ + char* data = &(*m_MemoryPoolIterator); + ComponentWrapper wrapper(m_ComponentInfo, data); + return wrapper; +} + +bool ComponentPoolForwardIterator::operator==(const ComponentPoolForwardIterator& other) const +{ + return m_MemoryPoolIterator == other.m_MemoryPoolIterator; +} + +bool ComponentPoolForwardIterator::operator!=(const ComponentPoolForwardIterator& other) const +{ + return m_MemoryPoolIterator != other.m_MemoryPoolIterator; +} + +ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++(int) +{ + ComponentPoolForwardIterator copyIter(*this); + operator++(); + return copyIter; +} + +ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++() +{ + ++m_MemoryPoolIterator; + return *this; +} + +//const ::ComponentInfo& ComponentPool::ComponentInfo() const +//{ +// return m_ComponentInfo; +//} + +ComponentWrapper ComponentPool::Allocate(EntityID entity) +{ + char* data = m_Pool.Allocate(); + memcpy(data, &entity, sizeof(EntityID)); + m_EntityToComponent[entity] = data; + return ComponentWrapper(m_ComponentInfo, data); +} + +ComponentWrapper ComponentPool::GetByEntity(EntityID ent) +{ + return ComponentWrapper(m_ComponentInfo, m_EntityToComponent.at(ent)); +} + +void ComponentPool::Delete(ComponentWrapper& wrapper) +{ + m_EntityToComponent.erase(wrapper.EntityID); + m_Pool.Free(wrapper.Data); +} + +ComponentPool::iterator ComponentPool::begin() const +{ + return iterator(m_ComponentInfo, m_Pool.begin(), m_Pool.end()); +} + +ComponentPool::iterator ComponentPool::end() const +{ + return iterator(m_ComponentInfo, m_Pool.end(), m_Pool.end()); +} + +template +void ComponentPool::Dump() const +{ + m_Pool.Dump(); +} + +template +void ComponentPool::Dump(OutStream& out) const +{ + m_Pool.Dump(out); +} diff --git a/src/Engine/Core/World.cpp b/src/Engine/Core/World.cpp new file mode 100644 index 00000000..26d3f1e4 --- /dev/null +++ b/src/Engine/Core/World.cpp @@ -0,0 +1,54 @@ +#include "Core/World.h" + +World::~World() +{ + for (auto& pool : m_ComponentPools) { + delete pool.second; + } +} + +EntityID World::CreateEntity(EntityID parent /*= 0*/) +{ + EntityID newEntity = generateEntityID(); + m_EntityParents[newEntity] = parent; + if (parent != 0) { + m_EntityChildren.insert(std::make_pair(parent, newEntity)); + } + return newEntity; +} + +void World::RegisterComponent(ComponentInfo& ci) +{ + m_ComponentPools[ci.Name] = new ComponentPool(ci); +} + +ComponentWrapper World::AttachComponent(EntityID entity, std::string componentType) +{ + ComponentPool* pool = m_ComponentPools.at(componentType); + const ComponentInfo& ci = pool->ComponentInfo(); + + // Allocate space for the component + ComponentWrapper c = pool->Allocate(entity); + // Write default values + memcpy(c.Data, ci.Defaults.get(), ci.Meta.Stride); + + return c; +} + +ComponentWrapper World::GetComponent(EntityID entity, std::string componentType) +{ + ComponentPool* pool = m_ComponentPools.at(componentType); + return pool->GetByEntity(entity); +} + +const ComponentPool& World::GetComponents(std::string componentType) +{ + return *m_ComponentPools.at(componentType); +} + +EntityID World::generateEntityID() +{ + // TODO: Make EntityID generation smarter + return m_CurrentEntityID++; +} + diff --git a/src/Tests/ComponentPoolTest.cpp b/src/Tests/ComponentPoolTest.cpp index 0656d9aa..25bdf1d3 100644 --- a/src/Tests/ComponentPoolTest.cpp +++ b/src/Tests/ComponentPoolTest.cpp @@ -4,25 +4,31 @@ BOOST_AUTO_TEST_CASE(ComponentPoolTest) { - ComponentInfo ci; - ci.Name = "Test"; - ci.FieldTypes["Field"] = "int"; - ci.FieldOffsets["Field"] = sizeof(EntityID); - ci.Meta.Allocation = 4; - ci.Meta.Stride = sizeof(EntityID) + sizeof(int); + // TODO: Write an updated test for component pool + BOOST_CHECK(false); + //ComponentInfo ci; + //ci.Name = "Test"; + //ci.FieldTypes["Field"] = "int"; + //ci.FieldOffsets["Field"] = 0; + //ci.Meta.Allocation = 3; + //ci.Meta.Stride = sizeof(EntityID) + sizeof(int); - ComponentPool pool(ci); - for (int i = 0; i < 3; i++) { - ComponentWrapper c = pool.New(); - c.EntityID = i; - unsigned int offset = c.Info.FieldOffsets.at("Field"); - memcpy(&c.Data[offset], &i, sizeof(int)); - } + //std::vector wrappers; + //ComponentPool pool(ci); + //for (int i = 0; i < 4; i++) { + // ComponentWrapper c = pool.New(); + // c.EntityID = i; + // unsigned int offset = c.Info.FieldOffsets.at("Field"); + // memcpy(&c.Data[offset], &i, sizeof(int)); + // wrappers.push_back(c); + //} - int i = 0; - for (auto& c : pool) { - BOOST_CHECK(c.EntityID == i); - BOOST_CHECK((int)c["Field"] == i); - i++; - } + //int i = 0; + //for (auto& c : pool) { + // BOOST_CHECK(c.EntityID == i); + // BOOST_CHECK((int)c["Field"] == i); + // i++; + //} + + //pool.Delete(wrappers[1]); } \ No newline at end of file diff --git a/src/Tests/WorldTest.cpp b/src/Tests/WorldTest.cpp new file mode 100644 index 00000000..663c243f --- /dev/null +++ b/src/Tests/WorldTest.cpp @@ -0,0 +1,41 @@ +#include + +#include "Common.h" +#include "Core/World.h" + +BOOST_AUTO_TEST_CASE(WorldTest) +{ + ComponentInfo ci; + ci.Name = "Test"; + ci.FieldTypes["Field"] = "int"; + ci.FieldOffsets["Field"] = 0; + ci.Meta.Stride = sizeof(int); + ci.Meta.Allocation = 3; + + ci.Defaults = std::shared_ptr(new char[ci.Meta.Stride]); + int default_Field = 1337; + memcpy(ci.Defaults.get(), &default_Field, ci.Meta.Stride); + + World w; + w.RegisterComponent(ci); + + std::vector ids; + for (int i = 0; i < 6; i++) { + EntityID e = w.CreateEntity(); + ids.push_back(e); + w.AttachComponent(e, "Test"); + ComponentWrapper c = w.GetComponent(e, "Test"); + BOOST_CHECK(c.EntityID == e); + BOOST_CHECK((int)c["Field"] == 1337); + c.SetProperty("Field", i); + BOOST_CHECK((int)c["Field"] == i); + } + + int i = 0; + for (auto& c : w.GetComponents("Test")) { + EntityID e = ids.at(i); + BOOST_CHECK(c.EntityID == e); + BOOST_CHECK((int)c["Field"] == i); + i++; + } +}