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; import ecs.id_manager; alias gEM = EntityManager.instance; class EntityManager { static void initialize() { instance = Mallocator.instance.make!EntityManager; } static void destory() { Mallocator.instance.dispose(instance); instance = null; } 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; while (block !is null) { void* data_pointer = block.dataBegin(); foreach (i; 0 .. block.entities_count) { mixin(genCall()); data_pointer += data.info.size; } block = block.next_block; } } } 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) { num += aligment - (num & (aligment - 1)); } 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(page_size, page_size); *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 > page_size) { 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); Entity* entity = cast(Entity*)start; entity.id = id_manager.getNewID(); entity.updateID(); 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) } enum page_size = 4096; enum pages_in_block = 128; alias SytemFuncType = void function(ref EntityManager.CallData data, void* entity); IDManager id_manager; HashMap!(ushort[], EntityInfo*) entities_infos; HashMap!(string, uint) components_map; System[] systems; ComponentInfo[] components; __gshared EntityManager instance; }