diff --git a/source/ecs/id_manager.d b/source/ecs/id_manager.d index 007611b..5fee718 100644 --- a/source/ecs/id_manager.d +++ b/source/ecs/id_manager.d @@ -7,19 +7,22 @@ struct IDManager { EntityID getNewID() { - if(m_next_id >= m_ids_array.length)m_ids_array.add(Data()); + if (m_next_id >= m_ids_array.length) + m_ids_array.add(Data()); EntityID id; id.id = m_next_id; id.counter = ++m_ids_array[m_next_id].counter; m_next_id = m_ids_array[m_next_id].next_id; - if(m_next_id == uint.max)m_next_id = cast(uint)m_ids_array.length; + if (m_next_id == uint.max) + m_next_id = cast(uint) m_ids_array.length; return id; } void releaseID(EntityID id) { Data* data = &m_ids_array[id.id]; - if(data.counter != id.counter)return; + if (data.counter != id.counter) + return; data.next_id = m_next_id; data.entity = null; m_next_id = id.id; @@ -33,8 +36,10 @@ struct IDManager Entity* getEntityPointer(EntityID id) { Data* data = &m_ids_array[id.id]; - if(data.counter != id.counter)return null; - else return data.entity; + if (data.counter != id.counter) + return null; + else + return data.entity; } bool isExist(EntityID id) @@ -61,9 +66,9 @@ unittest EntityID id2 = manager.getNewID(); EntityID id3 = manager.getNewID(); - assert(id1 == EntityID(0,1)); - assert(id2 == EntityID(1,1)); - assert(id3 == EntityID(2,1)); + assert(id1 == EntityID(0, 1)); + assert(id2 == EntityID(1, 1)); + assert(id3 == EntityID(2, 1)); manager.releaseID(id2); manager.releaseID(id1); @@ -71,10 +76,10 @@ unittest id2 = manager.getNewID(); id1 = manager.getNewID(); - assert(id1 == EntityID(1,2)); - assert(id2 == EntityID(0,2)); - assert(id3 == EntityID(2,1)); + assert(id1 == EntityID(1, 2)); + assert(id2 == EntityID(0, 2)); + assert(id3 == EntityID(2, 1)); assert(manager.isExist(id3)); - assert(!manager.isExist(EntityID(0,1))); + assert(!manager.isExist(EntityID(0, 1))); -} \ No newline at end of file +} diff --git a/source/ecs/manager.d b/source/ecs/manager.d index 7b33448..1089e50 100644 --- a/source/ecs/manager.d +++ b/source/ecs/manager.d @@ -39,11 +39,11 @@ class EntityManager static string genCall()() { - string ret = "s.update("; - foreach (i, param; Parameters!(Sys.update)) + string ret = "s.update(*cast(Entity*)data_pointer,"; + foreach (i; 1 .. (Parameters!(Sys.update)).length) { - ret ~= "*cast(types[" ~ i.to!string - ~ "]*)(data_pointer + data.deltas[" ~ i.to!string ~ "]),"; + ret ~= "*cast(types[" ~ i.to!string ~ "]*)(data_pointer + data.deltas[" ~ (i - 1) + .to!string ~ "]),"; } ret ~= ");"; return ret; @@ -52,10 +52,10 @@ class EntityManager static string genCompList()() { string ret; - foreach (i, param; Parameters!(Sys.update)) + foreach (i; 1 .. (Parameters!(Sys.update)).length) { - ret ~= "system.m_components[" ~ i.to!string - ~ "] = components_map.get(types[" ~ i.to!string ~ "].stringof);\n"; + ret ~= "system.m_components[" ~ (i - 1) + .to!string ~ "] = components_map.get(types[" ~ i.to!string ~ "].stringof);\n"; } return ret; } @@ -112,7 +112,7 @@ class EntityManager system.m_system_pointer = cast(void*) Mallocator.instance.make!Sys; - system.m_components = Mallocator.instance.makeArray!uint(types.length); + system.m_components = Mallocator.instance.makeArray!uint(types.length - 1); mixin(genCompList()); systems.add(system); @@ -126,12 +126,18 @@ class EntityManager void registerComponent(Comp)() { + static if (!(hasMember!(Comp, "component_id")) || !is(typeof(Comp.component_id) == ushort)) + { + static assert(0, "Component should have \"__gshared ushort component_id"); + } + ushort size = Comp.sizeof; ComponentInfo info; info.size = size; info.aligment = Comp.alignof; //8; components.add(info); + Comp.component_id = cast(ushort)(components.length - 1); components_map.add(Comp.stringof, cast(uint)(components.length - 1)); } @@ -149,27 +155,68 @@ class EntityManager static void alignNum(ref ushort num, ushort aligment) { - num += aligment - (num & (aligment - 1)); + num = cast(ushort)((num + aligment - 1) & (-cast(int) aligment)); //num += aligment - (num & (aligment - 1)); } EntityTemplate* allocateTemplate(ushort[] components_ids) { - EntityInfo* info = entities_infos.get(components_ids, null); + extern (C) static int compareUShorts(const void* a, const void* b) + { + ushort _a = *cast(ushort*) a; + ushort _b = *cast(ushort*) b; + if (_a < _b) + return -1; + else if (_a == _b) + return 0; + else + return 1; + } + + ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * components_ids.length))[0 + .. components_ids.length]; + ids[0 .. $] = components_ids[]; + qsort(ids.ptr, ids.length, ushort.sizeof, &compareUShorts); + { + uint j = 1; + foreach (i; 1 .. ids.length) + { + if (ids[i] != ids[j - 1]) + { + ids[j] = ids[i]; + j++; + } + else + debug assert(0, "Duplicated components in template!!!"); + } + ids = ids[0 .. j]; + } + + EntityInfo* info = getEntityInfo(ids); + + EntityTemplate* temp = Mallocator.instance.make!EntityTemplate; + temp.entity_data = Mallocator.instance.makeArray!ubyte(info.size); + temp.info = info; + return temp; + } + + EntityInfo* getEntityInfo(ushort[] ids) + { + EntityInfo* info = entities_infos.get(ids, null); if (info is null) { info = Mallocator.instance.make!EntityInfo; - info.components = Mallocator.instance.makeArray(components_ids); - info.deltas = Mallocator.instance.makeArray!ushort(components_ids.length); + info.components = Mallocator.instance.makeArray(ids); + info.deltas = Mallocator.instance.makeArray!ushort(ids[$ - 1] + 1); info.size = EntityID.sizeof; info.alignment = EntityID.alignof; - foreach (i, id; components_ids) + foreach (i, id; ids) { info.alignment = max(info.alignment, components[id].aligment); alignNum(info.size, components[id].aligment); - info.deltas[i] = info.size; + info.deltas[id] = info.size; info.size += components[id].size; } alignNum(info.size, info.alignment); @@ -183,11 +230,7 @@ class EntityManager entities_infos.add(info.components, info); } - - EntityTemplate* temp = Mallocator.instance.make!EntityTemplate; - temp.entity_data = Mallocator.instance.makeArray!ubyte(info.size); - temp.info = info; - return temp; + return info; } void addEntityCaller(ref EntityInfo entity, ref System system) @@ -202,7 +245,7 @@ class EntityManager { if (id2 == id) { - deltas[i] = entity.deltas[i2]; + deltas[i] = entity.deltas[id2]; break; } } @@ -215,10 +258,110 @@ class EntityManager if (deltas) { call_data.deltas = Mallocator.instance.makeArray(deltas); //Mallocator.instance.makeArray!ushort(system.m_components.length); + entity.callers.add(call_data); } } + Entity* getEntity(EntityID id) + { + return cast(Entity*) id_manager.getEntityPointer(id); + } + + void addComponents(Components...)(EntityID entity_id, Components comps) + { + const uint num = Components.length; + Entity* entity = id_manager.getEntityPointer(entity_id); + EntitiesBlock* block = getMetaData(entity); + EntityInfo* info = block.type_data; + ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * (info.components.length + num)))[0 + .. info.components.length + num]; + ushort[num] new_ids; + + static foreach (i, comp; Components) + { + new_ids[i] = comp.component_id; + } + + void*[num] pointers; + + static foreach (i, comp; comps) + { + pointers[i] = ∁ + } + + uint j = 0; + uint k = 0; + foreach (ref id; ids) + { + if (k >= new_ids.length) + { + id = info.components[j++]; + continue; + } + if (j >= info.components.length) + { + id = new_ids[k++]; + continue; + } + debug if (new_ids[k] == info.components[j]) + assert(0, "Trying to add already existing component!"); + if (new_ids[k] < info.components[j]) + { + id = new_ids[k++]; + } + else + id = info.components[j++]; + } + + EntityInfo* new_info = getEntityInfo(ids); + + EntitiesBlock* new_block = findBlockWithFreeSpace(new_info); + + void* start = new_block.dataBegin() + new_block.entities_count * new_info.size; + + Entity* new_entity = cast(Entity*) start; + new_entity.id = entity.id; + new_entity.updateID(); + new_block.entities_count++; + + //removeEntityNoID(entity, block); + + j = 0; + k = 0; + foreach (ref id; ids) + { + if (k >= new_ids.length) + { + memcpy(cast(void*) new_entity + new_info.deltas[id], + cast(void*) entity + info.deltas[info.components[j]], + components[info.components[j]].size); //id = info.components[j++]; + j++; + } + else if (j >= info.components.length) + { + memcpy(cast(void*) new_entity + new_info.deltas[id], + pointers[k], components[new_ids[k]].size); //id = new_ids[k++]; + k++; + } + else if (id == new_ids[k]) + { + memcpy(cast(void*) new_entity + new_info.deltas[id], + pointers[k], components[new_ids[k]].size); //id = new_ids[k++]; + k++; + } + else + { + memcpy(cast(void*) new_entity + new_info.deltas[id], + cast(void*) entity + info.deltas[info.components[j]], + components[info.components[j]].size); //id = info.components[j++]; + j++; + } + } + + removeEntityNoID(entity, block); + } + void freeTemplate(EntityTemplate* template_) { Mallocator.instance.dispose(template_.entity_data); @@ -227,44 +370,11 @@ class EntityManager ref Entity addEntity(EntityTemplate* tmpl) { - EntitiesBlock* previous_block; - EntitiesBlock* block = tmpl.info.first_with_free_space; //tmpl.info.first_block; - - // find block with enought space - while (1) - { - if (block is null) - { - block = cast(EntitiesBlock*) AlignedMallocator.instance.alignedAllocate(page_size, - page_size); - *block = EntitiesBlock(tmpl.info); - if (previous_block is null) - { - tmpl.info.first_block = block; - block.id = 0; - } - else - { - previous_block.next_block = block; - block.id = previous_block.id + 1; - } - tmpl.info.first_with_free_space = block; - break; // new block certainly has free space - } - // check if there is enought space - if (block.dataDelta() + (block.entities_count + 1) * tmpl.info.size > page_size) - { - previous_block = block; - block = block.next_block; - continue; - } - - tmpl.info.first_with_free_space = block; - break; // block exists and bounds check passed - } + EntitiesBlock* block = findBlockWithFreeSpace(tmpl.info); void* start = block.dataBegin() + block.entities_count * tmpl.info.size; memcpy(start, tmpl.entity_data.ptr, tmpl.info.size); + Entity* entity = cast(Entity*) start; entity.id = id_manager.getNewID(); entity.updateID(); @@ -272,31 +382,90 @@ class EntityManager return *entity; } + EntitiesBlock* findBlockWithFreeSpace(EntityInfo* info) + { + EntitiesBlock* previous_block; + EntitiesBlock* block = info.first_with_free_space; + + while (1) + { + if (block is null) + { + block = cast(EntitiesBlock*) AlignedMallocator.instance.alignedAllocate(page_size, + page_size); + *block = EntitiesBlock(info); + if (previous_block is null) + { + info.first_block = block; + block.id = 0; + } + else + { + previous_block.next_block = block; + block.id = previous_block.id + 1; + } + info.first_with_free_space = block; + break; // new block certainly has free space + } + // check if there is enought space + if (block.dataDelta() + (block.entities_count + 1) * info.size > page_size) + { + previous_block = block; + block = block.next_block; + continue; + } + + info.first_with_free_space = block; + break; // block exists and bounds check passed + } + + return block; + } + void removeEntity(EntityID id) { + //get entity and block meta data pointers Entity* entity = id_manager.getEntityPointer(id); - if(entity is null)return; + if (entity is null) + return; //return if entity doesn't exist EntitiesBlock* block = getMetaData(entity); - id_manager.releaseID(id); - + id_manager.releaseID(id); //release id from manager + + removeEntityNoID(entity, block); + } + + private void removeEntityNoID(Entity* entity, EntitiesBlock* block) + { + //pos is Entity number in block void* data_begin = block.dataBegin(); - uint pos = cast(int)(cast(void*)entity - data_begin) / block.type_data.size; + uint pos = cast(int)(cast(void*) entity - data_begin) / block.type_data.size; block.entities_count--; - if(block.type_data.first_with_free_space.id > block.id)block.type_data.first_with_free_space = block; + //set "first_with_free_space" if should it be + if (block.type_data.first_with_free_space.id > block.id) + block.type_data.first_with_free_space = block; - if(pos == block.entities_count)return; + if (pos == block.entities_count) + return; + //copy memory of last entity to position of removed entity void* src = data_begin + block.entities_count * block.type_data.size; void* dst = data_begin + pos * block.type_data.size; - memcpy(dst,src,block.type_data.size); + memcpy(dst, src, block.type_data.size); + //update pointer for moved entity ID entity = cast(Entity*) dst; entity.updateID(); } + /************************************************************************************************************************ + *functions return MetaData of page. + * + *params: + *pointer = pointer to any data of entity (i.e. component data pointer) + */ EntitiesBlock* getMetaData(void* pointer) { return cast(EntitiesBlock*)(cast(size_t) pointer & (~cast(size_t)(page_size - 1))); @@ -308,19 +477,33 @@ class EntityManager ushort aligment; } + /************************************************************************************************************************ + *Entity type info. + */ struct EntityInfo { + ///entity components ushort[] components; + ///deltas in memory for components ushort[] deltas; + ///aligment of whole entity ushort alignment; + ///size of entity (with alignment respect) ushort size; + ///pointer to first block/page EntitiesBlock* first_block; + ///a hint for allocations EntitiesBlock* first_with_free_space; // a hint for allocations, should have empty space in it but doesn't have to + ///array of CallData. Contain data for System calls. Vector!(CallData) callers; } + /************************************************************************************************************************ + *Meta data of every block of entities (contained at the begining of block). + */ struct EntitiesBlock { + ///return distance (in bytes) from begin of block to data uint dataDelta() { ushort dif = EntitiesBlock.sizeof; @@ -328,6 +511,7 @@ class EntityManager return dif; } + ///return pointer to first element in block void* dataBegin() { ushort dif = EntitiesBlock.sizeof; @@ -335,17 +519,27 @@ class EntityManager return cast(void*)&this + dif; } + ///pointer to Entity type info EntityInfo* type_data; + ///number of entities in block uint entities_count; + ///block id uint id; + ///pointer to next block/page EntitiesBlock* next_block; - ///there is a loooot of data (4kB, pure magic) + //there is a loooot of data (4kB, pure magic) } + /************************************************************************************************************************ + *Structure with data used to calling System calls. + */ struct CallData { + ///pointer to used system System* system; + ///poiner to Entity type info EntityManager.EntityInfo* info; + ///deltas for components ushort[] deltas; } @@ -365,3 +559,12 @@ class EntityManager __gshared EntityManager instance; } +/* +static ulong defaultHashFunc(T)(auto ref T t) +{ + ulong ret = 0; + foreach(id;t) + { + ret = ret + } +}*/ diff --git a/source/ecs/system.d b/source/ecs/system.d index 9979341..4e5fd21 100644 --- a/source/ecs/system.d +++ b/source/ecs/system.d @@ -12,13 +12,15 @@ struct System void enable() { - if(!m_enabled && m_enable)m_enable(m_system_pointer); + if (!m_enabled && m_enable) + m_enable(m_system_pointer); m_enabled = true; } void disable() { - if(m_enabled && m_disable)m_disable(m_system_pointer); + if (m_enabled && m_disable) + m_disable(m_system_pointer); m_enabled = false; } diff --git a/tests/tests.d b/tests/tests.d index a82d138..15b6075 100644 --- a/tests/tests.d +++ b/tests/tests.d @@ -10,11 +10,11 @@ import std.stdio; int main() { - alias SerializeVector = ubyte[]; + alias SerializeVector = ubyte[]; - struct TestComp + struct TestComp { - __gshared static int component_id; + __gshared ushort component_id; int a; ulong b; @@ -31,9 +31,9 @@ int main() struct TestComp2 { - __gshared static int component_id; - short b; - ubyte a; + __gshared ushort component_id; + int b; + int a; static void serializeComponent(ref TestComp comp, SerializeVector output) { @@ -46,25 +46,60 @@ int main() } } - struct TestSystem + struct TestComp3 + { + __gshared ushort component_id; + uint gg; //good game + uint bg; //bad game + + static void serializeComponent(ref TestComp comp, SerializeVector output) + { + + } + + static void deserializeComponent(ref TestComp comp, ubyte[] data) + { + + } + } + + struct TestComp4 + { + __gshared ushort component_id; + uint gg; //good game + uint bg; //bad game + + static void serializeComponent(ref TestComp comp, SerializeVector output) + { + + } + + static void deserializeComponent(ref TestComp comp, ubyte[] data) + { + + } + } + + struct TestSystem { - void initialize(ref TestComp comp) - { - - } - - void update(ref TestComp test, ref TestComp2 test2)//ref TestComp comp) + void initialize(ref Entity entity, ref TestComp comp) { - assert( cast(ptrdiff_t)&test % TestComp.alignof == 0 ); - assert( cast(ptrdiff_t)&test2 % TestComp2.alignof == 0 ); + + } + + void update(ref Entity entity, ref TestComp test, ref TestComp2 test2) //ref TestComp comp) + { + assert(cast(size_t)&test % TestComp.alignof == 0); + assert(cast(size_t)&test2 % TestComp2.alignof == 0); import std.stdio; - //writeln("Jakis tekst! ",test.b); - test.a+=1000; - test.b+=2000; - //writeln("Jakis tekst! ",test.b); + + //writeln("Jakis tekst! ",test.b); + test.a += 1000; + test.b += 2000; + //writeln("Jakis tekst! ",test.b); test2.b += 2; - test2.a = 1; + test2.a = 8; //writeln("Jakis tekst! ",test2.b); } @@ -80,23 +115,26 @@ int main() void onEnable() { import std.stdio; + writeln("TestSystem2 enabled"); } void onDisable() { import std.stdio; + writeln("TestSystem2 disabled"); } - void initialize(ref TestComp comp) - { - - } - - void update(ref TestComp2 test2)//ref TestComp comp) + void initialize(ref Entity entity, ref TestComp comp) { - test2.b += 1000; + + } + + void update(ref Entity entity, ref TestComp3 test) //ref TestComp comp) + { + writeln("TestSystem2 update"); + test.gg += 14; } void handleEvent(Event event, ref TestComp comp) @@ -105,16 +143,18 @@ int main() } } - EntityManager.initialize(); - assert(gEM !is null); + EntityManager.initialize(); + assert(gEM !is null); MonoTime time = MonoTime.currTime; gEM.registerComponent!TestComp2; - gEM.registerComponent!TestComp; + gEM.registerComponent!TestComp4; + gEM.registerComponent!TestComp; + gEM.registerComponent!TestComp3; ulong dur = (MonoTime.currTime - time).total!"usecs"; - writeln("Components register: ",dur," usecs"); + writeln("Components register: ", dur, " usecs"); time = MonoTime.currTime; @@ -122,16 +162,17 @@ int main() //gEM.registerSystem!TestSystem2(0); dur = (MonoTime.currTime - time).total!"usecs"; - writeln("Systems register: ",dur," usecs"); + writeln("Systems register: ", dur, " usecs"); time = MonoTime.currTime; - ushort[2] ids = [0,1]; - EntityTemplate* tmpl = gEM.allocateTemplate(ids); - *cast(EntityID*)tmpl.entity_data.ptr = EntityID(1,1); + ushort[2] ids = [TestComp2.component_id, TestComp.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + //writeln(tmpl.info.components[]); + *cast(EntityID*) tmpl.entity_data.ptr = EntityID(1, 1); dur = (MonoTime.currTime - time).total!"usecs"; - writeln("Template allocating: ",dur," usecs"); + writeln("Template allocating: ", dur, " usecs"); time = MonoTime.currTime; @@ -141,30 +182,34 @@ int main() EntityID[1000] idss; - foreach(i; 0..1_000) + foreach (i; 0 .. 1_000) { - foreach(j; 0..1_000)idss[j] = gEM.addEntity(tmpl).id; - foreach(j; 0..1_000)gEM.removeEntity(idss[j]); + foreach (j; 0 .. 1_000) + idss[j] = gEM.addEntity(tmpl).id; + foreach (j; 0 .. 1_000) + gEM.removeEntity(idss[j]); } uint blocks = 0; - foreach(info; &gEM.entities_infos.byValue) + foreach (info; &gEM.entities_infos.byValue) { EntityManager.EntitiesBlock* block = info.first_block; - while(block !is null) + while (block !is null) { block = block.next_block; blocks++; } } - writeln("Entities blocks: ",blocks); + writeln("Entities blocks: ", blocks); /*Entity entity = gEM.addEntity(tmpl); gEM.removeEntity(entity.id); gEM.addEntity(tmpl);*/ dur = (MonoTime.currTime - time).total!"usecs"; - writeln("Entities adding: ",dur," usecs"); + writeln("Entities adding: ", dur, " usecs"); + + //foreach(j; 0..1_000)gEM.addEntity(tmpl); gEM.registerSystem!TestSystem2(0); @@ -173,13 +218,29 @@ int main() time = MonoTime.currTime; - gEM.update(); + //gEM.update(); dur = (MonoTime.currTime - time).total!"usecs"; - writeln("Update: ",dur," usecs"); + writeln("Update: ", dur, " usecs"); + + Entity entity = gEM.addEntity(tmpl); + + gEM.update(); + + Entity* pp = gEM.getEntity(entity.id); + writeln((cast(uint*) pp)[0 .. 14], " ", pp); + + gEM.addEntity(tmpl); + + gEM.addComponents(entity.id, TestComp3()); + pp = gEM.getEntity(entity.id); + + gEM.update(); + + writeln((cast(uint*) pp)[0 .. 14], " ", pp); //import std.stdio; //writeln((cast(uint*)tmpl.info.first_block)[0..48]); gEM.freeTemplate(tmpl); return 0; -} \ No newline at end of file +}