diff --git a/include/Engine/Core/ComponentInfo.h b/include/Engine/Core/ComponentInfo.h index d9799059..137a44d8 100644 --- a/include/Engine/Core/ComponentInfo.h +++ b/include/Engine/Core/ComponentInfo.h @@ -2,6 +2,7 @@ #define ComponentInfo_h__ #include "../Common.h" +#include struct ComponentInfo { @@ -27,8 +28,9 @@ struct ComponentInfo std::string Name; std::unordered_map Fields; std::vector FieldsInOrder; + std::vector StringFields; unsigned int Stride = 0; - std::shared_ptr Defaults = nullptr; + boost::shared_array Defaults = nullptr; std::shared_ptr Meta = nullptr; }; diff --git a/include/Engine/Core/ComponentPool.h b/include/Engine/Core/ComponentPool.h index 957b8756..aedfd06b 100644 --- a/include/Engine/Core/ComponentPool.h +++ b/include/Engine/Core/ComponentPool.h @@ -1,6 +1,7 @@ #ifndef ComponentPool_h__ #define ComponentPool_h__ +#include #include "MemoryPool.h" #include "ComponentInfo.h" #include "ComponentWrapper.h" @@ -45,7 +46,8 @@ public: : m_ComponentInfo(ci) , m_Pool(ci.Meta->Allocation, sizeof(EntityID) + ci.Stride) { } - ComponentPool(const ComponentPool& other) = delete; + ~ComponentPool(); + ComponentPool(const ComponentPool& other); ComponentPool(const ComponentPool&& other) = delete; const ::ComponentInfo& ComponentInfo() const { return m_ComponentInfo; } diff --git a/include/Engine/Core/ComponentWrapper.h b/include/Engine/Core/ComponentWrapper.h index 0b9f7357..7897e874 100644 --- a/include/Engine/Core/ComponentWrapper.h +++ b/include/Engine/Core/ComponentWrapper.h @@ -2,11 +2,29 @@ #define ComponentWrapper_h__ #include +#include #include "../Common.h" #include "Entity.h" #include "ComponentInfo.h" #include "Util/Any.h" +template +struct ComponentField { }; + +template +struct ComponentField::value>::type> +{ + static T& Get(const ComponentInfo::Field_t& info, char* data) { return *reinterpret_cast(data); } + static void Set(const ComponentInfo::Field_t& info, char* data, const T& value) { Get(data) = value; } +}; + +template <> +struct ComponentField +{ + static std::string& Get(const ComponentInfo::Field_t& info, char* data) { return **reinterpret_cast(data); } + static void Set(const ComponentInfo::Field_t& info, char* data, const std::string& value) { Get(info, data) = value; } +}; + struct ComponentWrapper { ComponentWrapper(const ComponentInfo& componentInfo, char* data) @@ -47,7 +65,31 @@ struct ComponentWrapper void Copy(ComponentWrapper& destination) { - memcpy(destination.Data, this->Data, Info.Stride); + // Copy trivial data + memcpy(destination.Data, Data, Info.Stride); + // Duplicate strings + SolidifyStrings(destination); + } + + // When component data has been copied, strings need to be reconstructed or they'll refer to the same data! + static void SolidifyStrings(ComponentWrapper& component) + { + for (auto& name : component.Info.StringFields) { + std::size_t offset = component.Info.Fields.at(name).Offset; + std::string value = *reinterpret_cast(component.Data + offset); + new (component.Data + offset) std::string(value); + } + } + + // This needs to be called to properly free component data, because strings. + static void Destroy(ComponentInfo info, char* data) + { + // Call std::string destructors + for (auto& name : info.StringFields) { + std::size_t offset = info.Fields.at(name).Offset; + auto field = reinterpret_cast(data + offset); + field->~basic_string(); + } } struct SubscriptProxy @@ -102,26 +144,38 @@ public: ComponentWrapperFactory(std::string componentTypeName, unsigned int allocation = 0) { m_ComponentInfo.Name = componentTypeName; + m_ComponentInfo.Meta = std::make_shared(); m_ComponentInfo.Meta->Allocation = allocation; } template void AddProperty(std::string fieldName, T defaultValue) { - m_DefaultValues.push_back(defaultValue); - m_ComponentInfo.Fields[fieldName].Type = typeid(T).name(); - m_ComponentInfo.Fields[fieldName].Offset = m_ComponentInfo.Stride; - m_ComponentInfo.Fields[fieldName].Stride = sizeof(T); + auto& field = m_ComponentInfo.Fields[fieldName]; + field.Name = fieldName; + field.Type = typeid(T).name(); + field.Offset = m_ComponentInfo.Stride; + field.Stride = sizeof(T); + m_ComponentInfo.FieldsInOrder.push_back(field.Name); + if (field.Type == typeid(std::string).name()) { + field.Type = "string"; + m_ComponentInfo.StringFields.push_back(field.Name); + } m_ComponentInfo.Stride += sizeof(T); + m_DefaultValues.push_back(std::make_pair(field, defaultValue)); } - + ComponentInfo& Finalize() { - m_ComponentInfo.Defaults = std::shared_ptr(new char[m_ComponentInfo.Stride]); + m_ComponentInfo.Defaults = boost::shared_array(new char[m_ComponentInfo.Stride]); std::size_t offset = 0; - for (auto& val : m_DefaultValues) { - memcpy(m_ComponentInfo.Defaults.get() + offset, val.Data.get(), val.Size); - offset += val.Size; + for (auto& pair : m_DefaultValues) { + if (pair.first.Type == "string") { + new (m_ComponentInfo.Defaults.get() + offset) std::string(*reinterpret_cast(pair.second.Data.get())); + } else { + memcpy(m_ComponentInfo.Defaults.get() + offset, pair.second.Data.get(), pair.second.Size); + } + offset += pair.second.Size; } return m_ComponentInfo; @@ -131,7 +185,7 @@ public: private: ComponentInfo m_ComponentInfo; - std::vector m_DefaultValues; + std::vector> m_DefaultValues; }; #endif diff --git a/include/Engine/Core/MemoryPool.h b/include/Engine/Core/MemoryPool.h index f4073294..053e75aa 100644 --- a/include/Engine/Core/MemoryPool.h +++ b/include/Engine/Core/MemoryPool.h @@ -66,9 +66,25 @@ public: , m_LowestAllocatedSlot(m_NumSlots) { } - //We may get problems with memory being released - //prematurely, etc. if we allow copies. - MemoryPool(const MemoryPool& other) = delete; + MemoryPool(const MemoryPool& other) + : m_StartAddress(new char[other.m_NumSlots*other.m_Stride]) + , m_SlotIsAllocated(other.m_SlotIsAllocated) + , m_ExtraMemory() + , m_NumSlots(other.m_NumSlots) + , m_LowestAllocatedSlot(other.m_LowestAllocatedSlot) + , m_NumAllocatedSlots(other.m_NumAllocatedSlots) + , m_Stride(other.m_Stride) + , m_CurrentAllocSlot(other.m_CurrentAllocSlot) + { + // Copy statically allocated pool + memcpy(m_StartAddress, other.m_StartAddress, m_NumSlots*m_Stride); + // Copy dynamically allocated memory + for (char* otherAddr : other.m_ExtraMemory) { + char* addr = (char*)malloc(m_Stride); + memcpy(addr, otherAddr, m_Stride); + m_ExtraMemory.push_back(addr); + } + } MemoryPool(const MemoryPool&& other) = delete; //Free all memory that has been allocated. @@ -78,8 +94,9 @@ public: delete[] m_StartAddress; m_StartAddress = nullptr; } - for (char* addr : m_ExtraMemory) - free(addr); + for (char* addr : m_ExtraMemory) { + free(addr); + } m_ExtraMemory.clear(); } diff --git a/include/Engine/Core/Util/Any.h b/include/Engine/Core/Util/Any.h index f0f90737..e7b93dfb 100644 --- a/include/Engine/Core/Util/Any.h +++ b/include/Engine/Core/Util/Any.h @@ -2,6 +2,7 @@ #define Util_Any_h__ #include +#include struct Any { @@ -10,7 +11,7 @@ struct Any template Any(const T& value) { - Data = std::shared_ptr(new char[sizeof(T)]); + Data = boost::shared_array(new char[sizeof(T)]); Size = sizeof(T); memcpy(Data.get(), &value, Size); } @@ -18,7 +19,7 @@ struct Any template Any(T&& value) { - Data = std::shared_ptr(new char[sizeof(T)]); + Data = boost::shared_array(new char[sizeof(T)]); Size = sizeof(T); memcpy(Data.get(), &value, Size); } @@ -35,7 +36,7 @@ struct Any return Any(value); } - std::shared_ptr Data = nullptr; + boost::shared_array Data = nullptr; std::size_t Size = 0; }; diff --git a/include/Engine/Core/World.h b/include/Engine/Core/World.h index 35394b9f..1604df37 100644 --- a/include/Engine/Core/World.h +++ b/include/Engine/Core/World.h @@ -15,6 +15,7 @@ public: : m_EventBroker(eventBroker) { } ~World(); + World(const World& other); // Create empty entity EntityID CreateEntity(EntityID parent = 0); diff --git a/src/Engine/Core/ComponentPool.cpp b/src/Engine/Core/ComponentPool.cpp index 7b465fbc..059b2e38 100644 --- a/src/Engine/Core/ComponentPool.cpp +++ b/src/Engine/Core/ComponentPool.cpp @@ -1,7 +1,5 @@ #include "Core/ComponentPool.h" - - ComponentWrapper ComponentPoolForwardIterator::operator*() const { char* data = &(*m_MemoryPoolIterator); @@ -32,6 +30,34 @@ ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++() return *this; } +ComponentPool::ComponentPool(const ComponentPool& other) + : m_ComponentInfo(other.m_ComponentInfo) + , m_Pool(other.m_Pool) + , m_EntityToComponent() +{ + // Update EntityToComponent pointers + for (char& ptr : m_Pool) { + EntityID entity = *reinterpret_cast(&ptr); + m_EntityToComponent[entity] = &ptr; + } + + // Duplicate strings + for (auto& name : m_ComponentInfo.StringFields) { + for (auto& c : *this) { + std::string& val = c[name]; + ComponentWrapper::SolidifyStrings(c); + } + } +} + +ComponentPool::~ComponentPool() +{ + // Destroy component data + for (auto& c : *this) { + ComponentWrapper::Destroy(c.Info, c.Data); + } +} + //const ::ComponentInfo& ComponentPool::ComponentInfo() const //{ // return m_ComponentInfo; @@ -39,10 +65,19 @@ ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++() ComponentWrapper ComponentPool::Allocate(EntityID entity) { + // Allocate pool data char* data = m_Pool.Allocate(); + // Copy EntityID memcpy(data, &entity, sizeof(EntityID)); + m_EntityToComponent[entity] = data; - return ComponentWrapper(m_ComponentInfo, data); + ComponentWrapper component(m_ComponentInfo, data); + + // Copy defaults + memcpy(component.Data, m_ComponentInfo.Defaults.get(), m_ComponentInfo.Stride); + ComponentWrapper::SolidifyStrings(component); + + return component; } ComponentWrapper ComponentPool::GetByEntity(EntityID ent) @@ -57,6 +92,7 @@ bool ComponentPool::KnowsEntity(EntityID ent) void ComponentPool::Delete(ComponentWrapper& wrapper) { + ComponentWrapper::Destroy(wrapper.Info, wrapper.Data); m_EntityToComponent.erase(wrapper.EntityID); m_Pool.Free(wrapper.Data - sizeof(EntityID)); } diff --git a/src/Engine/Core/EntityFilePreprocessor.cpp b/src/Engine/Core/EntityFilePreprocessor.cpp index a1370dd2..592daedb 100644 --- a/src/Engine/Core/EntityFilePreprocessor.cpp +++ b/src/Engine/Core/EntityFilePreprocessor.cpp @@ -185,6 +185,9 @@ void EntityFilePreprocessor::parseComponentInfo() field.Offset = fieldOffset; field.Stride = stride; compInfo.FieldsInOrder.push_back(name); + if (field.Type == "string") { + compInfo.StringFields.push_back(name); + } fieldOffset += stride; } @@ -201,7 +204,7 @@ void EntityFilePreprocessor::parseDefaults() for (auto& ci : m_ComponentInfo) { // Allocate memory for default values - ci.second.Defaults = std::shared_ptr(new char[ci.second.Stride]); + ci.second.Defaults = boost::shared_array(new char[ci.second.Stride], std::bind(&ComponentWrapper::Destroy, ci.second, std::placeholders::_1)); memset(ci.second.Defaults.get(), 0, ci.second.Stride); std::string componentName = ci.first; diff --git a/src/Engine/Core/World.cpp b/src/Engine/Core/World.cpp index 69c25f61..8788a92e 100644 --- a/src/Engine/Core/World.cpp +++ b/src/Engine/Core/World.cpp @@ -9,7 +9,20 @@ World::~World() } } -EntityID World::CreateEntity(EntityID parent /*= 0*/) +World::World(const World& other) + : m_EventBroker(other.m_EventBroker) + , m_CurrentEntityID(other.m_CurrentEntityID) + , m_EntityParents(other.m_EntityParents) + , m_EntityChildren(other.m_EntityChildren) + , m_EntityNames(other.m_EntityNames) +{ + // Deep copy component pools + for (auto& kv : other.m_ComponentPools) { + m_ComponentPools[kv.first] = new ComponentPool(*kv.second); + } +} + +EntityID World::CreateEntity(EntityID parent /*= EntityID_Invalid*/) { EntityID newEntity = generateEntityID(); if (newEntity == parent) { @@ -44,10 +57,8 @@ ComponentWrapper World::AttachComponent(EntityID entity, const std::string& comp ComponentPool* pool = m_ComponentPools.at(componentType); const ComponentInfo& ci = pool->ComponentInfo(); - // Allocate space for the component + // Allocate component with default values ComponentWrapper c = pool->Allocate(entity); - // Write default values - memcpy(c.Data, ci.Defaults.get(), ci.Stride); return c; } diff --git a/src/Tests/WorldTest.cpp b/src/Tests/WorldTest.cpp index 03008562..633e617a 100644 --- a/src/Tests/WorldTest.cpp +++ b/src/Tests/WorldTest.cpp @@ -69,3 +69,47 @@ BOOST_AUTO_TEST_CASE(WorldTestMultipleAllocations, * utf::tolerance(0.00001)) i++; } } + +BOOST_AUTO_TEST_CASE(WorldCopy, *utf::tolerance(0.00001)) +{ + World w1; + + // Create a test component + auto testComponent = ComponentWrapperFactory("Test", 2); + testComponent.AddProperty("TestInteger", 1337); + testComponent.AddProperty("TestDouble", 13.37); + testComponent.AddProperty("TestString", std::string("DefaultString")); + testComponent.AddProperty("TestVec3", glm::vec3(1.f, 2.f, 3.f)); + w1.RegisterComponent(testComponent); + + // Create a test entity + EntityID w1_e1 = w1.CreateEntity(); + auto w1_c1 = w1.AttachComponent(w1_e1, "Test"); + + // Create a child + EntityID w1_e2 = w1.CreateEntity(w1_e1); + auto w1_c2 = w1.AttachComponent(w1_e2, "Test"); + w1_c2["TestString"] = "NonDefaultString"; + + // Copy the world! + World w2 = w1; + + // Fetch the components + auto w2_c1 = w2.GetComponent(w1_e1, "Test"); + auto w2_c2 = w2.GetComponent(w1_e2, "Test"); + + // Check that built-in types are copied but don't reside in the same memory + BOOST_CHECK((int)w1_c1["TestInteger"] == (int)w2_c1["TestInteger"]); + BOOST_CHECK(&(int&)w1_c1["TestInteger"] != &(int&)w2_c1["TestInteger"]); + BOOST_CHECK((double)w1_c1["TestDouble"] == (double)w2_c1["TestDouble"]); + BOOST_CHECK(&(int&)w1_c1["TestDouble"] != &(int&)w2_c1["TestDouble"]); + BOOST_CHECK((int)w1_c2["TestInteger"] == (int)w2_c2["TestInteger"]); + BOOST_CHECK(&(int&)w1_c2["TestInteger"] != &(int&)w2_c2["TestInteger"]); + BOOST_CHECK((double)w1_c2["TestDouble"] == (double)w2_c2["TestDouble"]); + BOOST_CHECK(&(int&)w1_c2["TestDouble"] != &(int&)w2_c2["TestDouble"]); + // Check that specially handled strings are fine + BOOST_CHECK((std::string)w1_c1["TestString"] == (std::string)w2_c1["TestString"]); + BOOST_CHECK(&(std::string&)w1_c1["TestString"] != &(std::string&)w2_c1["TestString"]); + BOOST_CHECK((std::string)w1_c2["TestString"] == (std::string)w2_c2["TestString"]); + BOOST_CHECK(&(std::string&)w1_c2["TestString"] != &(std::string&)w2_c2["TestString"]); +}