module ecs.manager; import std.experimental.allocator.mallocator : Mallocator, AlignedMallocator; import std.experimental.allocator; import std.traits; import std.algorithm : max; import std.conv : to; import core.stdc.string; import ecs.system; import ecs.entity; import ecs.vector; import ecs.hash_map; alias gEM = EntityManager.instance; class EntityManager { static void initialize() { instance = Mallocator.instance.make!EntityManager; } void registerSystem(Sys)(int priority) { alias types = Parameters!(Sys.update); static string genCall()() { string ret = "s.update("; foreach(i,param;Parameters!(Sys.update)) { ret ~= "*cast(types["~i.to!string~"]*)(data_pointer + data.deltas["~i.to!string~"]),"; } ret ~= ");"; return ret; } static string genCompList()() { string ret; foreach(i,param;Parameters!(Sys.update)) { ret ~= "system.components["~i.to!string~"] = components_map.get(types["~i.to!string~"].stringof);\n"; } return ret; } static void callUpdate(ref CallData data, void* entity) { static if (hasMember!(Sys, "update")) { Sys* s = cast(Sys*) data.system.system_pointer; EntitiesBlock* block = data.info.first_block; void* data_pointer = block.dataBegin(); mixin(genCall()); } } System system; static if (hasMember!(Sys, "update")) { system.update = &callUpdate; } system.system_pointer = cast(void*) Mallocator.instance.make!Sys; system.components = Mallocator.instance.makeArray!uint(types.length); mixin(genCompList()); if (systems is null) { systems = Mallocator.instance.makeArray!System(1); systems[0] = system; } else { Mallocator.instance.expandArray(systems, 1); systems[$ - 1] = system; } } void registerComponent(Comp)() { ushort size = Comp.sizeof; ComponentInfo info; info.size = size; info.aligment = Comp.alignof;//8; if (components is null) { components = Mallocator.instance.makeArray!ComponentInfo(1); components[0] = info; } else { Mallocator.instance.expandArray(components, 1); components[$ - 1] = info; } components_map.add(Comp.stringof,cast(uint)(components.length-1)); } void update() { foreach(info;&entities_infos.byValue) { foreach(data;info.callers) { data.system.update(data,null);//caller(call_data,null); } } } static void alignNum(ref ushort num, ushort aligment) { int reminder = num % aligment; if (reminder != 0) { num += aligment - reminder; } } EntityTemplate* allocateTemplate(ushort[] components_ids) { EntityInfo* info = entities_infos.get(components_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.size = EntityID.sizeof; info.alignment = EntityID.alignof; foreach (i, id; components_ids) { info.alignment = max(info.alignment,components[id].aligment); alignNum(info.size, components[id].aligment); info.deltas[i] = info.size; info.size += components[id].size; } alignNum(info.size, info.alignment); foreach(ref system;systems) { if(system.update is null)continue; CallData call_data = CallData(&system,info, null); call_data.deltas = Mallocator.instance.makeArray!ushort(system.components.length); foreach(i,id;system.components) { foreach(i2,id2;info.components) { if(id2 == id) { call_data.deltas[i] = info.deltas[i2]; break; } } } info.callers.add(call_data); } 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; } void freeTemplate(EntityTemplate* template_) { Mallocator.instance.dispose(template_.entity_data); Mallocator.instance.dispose(template_); } void 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(4096,4096); *block = EntitiesBlock(tmpl.info); if(previous_block is null) { tmpl.info.first_block=block; } else { previous_block.next_block=block; } 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 > 4096 ) { previous_block=block; block=block.next_block; continue; } tmpl.info.first_with_free_space=block; break; // block exists and bounds check passed } void* start = block.dataBegin() + block.entities_count * tmpl.info.size; memcpy(start, tmpl.entity_data.ptr, tmpl.info.size); block.entities_count++; } struct CallData { System* system; EntityInfo* info; ushort[] deltas; } struct ComponentInfo { ushort size; ushort aligment; } struct EntityInfo { ushort[] components; ushort[] deltas; ushort alignment; ushort size; EntitiesBlock* first_block; EntitiesBlock* first_with_free_space; // a hint for allocations, should have empty space in it but doesn't have to Vector!(CallData) callers; } struct EntitiesBlock { uint dataDelta() { ushort dif = EntitiesBlock.sizeof; alignNum(dif,type_data.alignment); return dif; } void* dataBegin() { ushort dif = EntitiesBlock.sizeof; alignNum(dif,type_data.alignment); return cast(void*)&this + dif; } EntityInfo* type_data; uint entities_count; EntitiesBlock* next_block; ///there is a loooot of data (4kB, pure magic) } alias SytemFuncType = void function(ref EntityManager.CallData data, void* entity); HashMap!(ushort[], EntityInfo*) entities_infos; HashMap!(string,uint) components_map; System[] systems; ComponentInfo[] components; __gshared EntityManager instance; }