@@ -2,6 +2,7 @@
|
||||
#define ComponentInfo_h__
|
||||
|
||||
#include "../Common.h"
|
||||
#include <boost/shared_array.hpp>
|
||||
|
||||
struct ComponentInfo
|
||||
{
|
||||
@@ -27,8 +28,9 @@ struct ComponentInfo
|
||||
std::string Name;
|
||||
std::unordered_map<std::string, Field_t> Fields;
|
||||
std::vector<std::string> FieldsInOrder;
|
||||
std::vector<std::string> StringFields;
|
||||
unsigned int Stride = 0;
|
||||
std::shared_ptr<char> Defaults = nullptr;
|
||||
boost::shared_array<char> Defaults = nullptr;
|
||||
std::shared_ptr<Meta_t> Meta = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef ComponentPool_h__
|
||||
#define ComponentPool_h__
|
||||
|
||||
#include <set>
|
||||
#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; }
|
||||
|
||||
@@ -2,11 +2,29 @@
|
||||
#define ComponentWrapper_h__
|
||||
|
||||
#include <boost/shared_array.hpp>
|
||||
#include <boost/any.hpp>
|
||||
#include "../Common.h"
|
||||
#include "Entity.h"
|
||||
#include "ComponentInfo.h"
|
||||
#include "Util/Any.h"
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct ComponentField { };
|
||||
|
||||
template <typename T>
|
||||
struct ComponentField<T, typename std::enable_if<std::is_trivially_copyable<T>::value>::type>
|
||||
{
|
||||
static T& Get(const ComponentInfo::Field_t& info, char* data) { return *reinterpret_cast<T*>(data); }
|
||||
static void Set(const ComponentInfo::Field_t& info, char* data, const T& value) { Get(data) = value; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ComponentField<std::string, void>
|
||||
{
|
||||
static std::string& Get(const ComponentInfo::Field_t& info, char* data) { return **reinterpret_cast<std::string**>(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<const std::string*>(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<std::string*>(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<ComponentInfo::Meta_t>();
|
||||
m_ComponentInfo.Meta->Allocation = allocation;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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<char>(new char[m_ComponentInfo.Stride]);
|
||||
m_ComponentInfo.Defaults = boost::shared_array<char>(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<std::string*>(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<Any> m_DefaultValues;
|
||||
std::vector<std::pair<ComponentInfo::Field_t, Any>> m_DefaultValues;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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<T>& other) = delete;
|
||||
MemoryPool(const MemoryPool<T>& 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<T>&& 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define Util_Any_h__
|
||||
|
||||
#include <memory>
|
||||
#include <boost/shared_array.hpp>
|
||||
|
||||
struct Any
|
||||
{
|
||||
@@ -10,7 +11,7 @@ struct Any
|
||||
template <typename T>
|
||||
Any(const T& value)
|
||||
{
|
||||
Data = std::shared_ptr<char>(new char[sizeof(T)]);
|
||||
Data = boost::shared_array<char>(new char[sizeof(T)]);
|
||||
Size = sizeof(T);
|
||||
memcpy(Data.get(), &value, Size);
|
||||
}
|
||||
@@ -18,7 +19,7 @@ struct Any
|
||||
template <typename T>
|
||||
Any(T&& value)
|
||||
{
|
||||
Data = std::shared_ptr<char>(new char[sizeof(T)]);
|
||||
Data = boost::shared_array<char>(new char[sizeof(T)]);
|
||||
Size = sizeof(T);
|
||||
memcpy(Data.get(), &value, Size);
|
||||
}
|
||||
@@ -35,7 +36,7 @@ struct Any
|
||||
return Any(value);
|
||||
}
|
||||
|
||||
std::shared_ptr<char> Data = nullptr;
|
||||
boost::shared_array<char> Data = nullptr;
|
||||
std::size_t Size = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
: m_EventBroker(eventBroker)
|
||||
{ }
|
||||
~World();
|
||||
World(const World& other);
|
||||
|
||||
// Create empty entity
|
||||
EntityID CreateEntity(EntityID parent = 0);
|
||||
|
||||
@@ -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<EntityID*>(&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));
|
||||
}
|
||||
|
||||
@@ -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<char>(new char[ci.second.Stride]);
|
||||
ci.second.Defaults = boost::shared_array<char>(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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user