/************************************************************************************************************************ Most important module. Almost every function is called from EntityManager. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ module bubel.ecs.manager; import std.algorithm : max; import std.conv : to; import std.traits; //import core.atomic; //import core.stdc.stdlib : qsort; //import core.stdc.string; import bubel.ecs.system; //not ordered as forward reference bug workaround import bubel.ecs.block_allocator; import bubel.ecs.entity; import bubel.ecs.events; import bubel.ecs.hash_map; import bubel.ecs.id_manager; import bubel.ecs.simple_vector; import bubel.ecs.std; import bubel.ecs.traits; import bubel.ecs.vector; import bubel.ecs.atomic; alias SerializeVector = bubel.ecs.vector.Vector!ubyte; ///Global EntityManager used for everything. export __gshared EntityManager* gEntityManager = null; /************************************************************************************************************************ Entity manager is responsible for everything. Entity manager can be in three states: - registration: time between beginRegister() and endRegister() calls. - update: time between being() and end() calls. - default: when it's not in registration or update time Manager can be only in one state simultaneously. Manager must be initialized before use. There is global instance of EntityManager: gEntityManager or gEntityManager as alias. Registration process consist of registration of passes, systems, entities and events. Pass is group of system which should be used inside one update() call. Passes are added as name (string) and can be referenced by name or id. System is structure responsible for update of specified group of entities. System consist of EntitiesData structure which contain components used by system and some callback. Main callback is onUpdate() which is called by update() entity manager function. Other callbacks are used as listeners for adding entites, tracking system lifetime and events handling. Component is basicly small fraction of data which is considered to be used as whole. In best scenario every byte of component is used when it's refered to. In practice sometimes it's better to join data into one component even if it's can be accessed separetly. Events are structures with data used to handle events. Event can contain for exmaple one floating point number used as damage dealt to entity. Entity is group of components. In memory entity is only ID which makes it's possible to take it's components. Components are grouped into chunks, and grouped by component type so entity can be fracted in big memory chunk. There is two types of update: - update(): function used to call update pass. - updateMT(): function used to call update pass multithreaded. This call only generates jobs which must be called by user. WARNING! No fileds from EntityManager should be used directly, they are not private for special, advanced things which are not implemented in library. So if you don't need anything special, simply use only EntityManager methods. */ export struct EntityManager { /************************************************************************************************************************ Initialize ECS. */ export static void initialize(uint threads_count = 1, uint page_size = 32768, uint block_pages_count = 128) { if (gEntityManager is null) { //gEntityManager = Mallocator.make!EntityManager(threads_count); gEntityManager = Mallocator.make!EntityManager(threads_count, page_size, block_pages_count); with (gEntityManager) { UpdatePass* pass = Mallocator.make!UpdatePass; pass.name = Mallocator.makeArray(cast(char[]) "update"); //pass.name = Mallocator.makeArray!char("update".length); //pass.name[0..$] = "update"; passes.add(pass); passes_map.add(cast(string) pass.name, cast(ushort)(passes.length - 1)); } } } /************************************************************************************************************************ Deinitialize and destroy ECS. This function release whole memory. */ export static void destroy() { if (gEntityManager is null) return; with (gEntityManager) { foreach (ref system; systems) { system.destroy(); } foreach (EntityInfo* info; &entities_infos.byValue) { Mallocator.dispose(info); } foreach (UpdatePass* pass; passes) { Mallocator.dispose(pass); } passes.clear(); foreach (ComponentInfo info; components) { if (info.init_data) Mallocator.dispose(info.init_data); } foreach (EventInfo info; events) { if (info.callers) Mallocator.dispose(info.callers); } foreach (name; &components_map.byKey) { if (name) Mallocator.dispose(name); } } Mallocator.dispose(gEntityManager); gEntityManager = null; } /************************************************************************************************************************ Begin registering process. Every register function should be called between beginRegister() and endRegister(). */ export void beginRegister() nothrow @nogc { assert(!register_state, "beginRegister() can't be called twice before endRegister();"); register_state = true; foreach (pass; passes) { foreach (caller; pass.system_callers) { Mallocator.dispose(caller); } pass.system_callers.clear(); } } /************************************************************************************************************************ End registering process. Every register function should be called between beginRegister() and endRegister(). */ export void endRegister() { assert(register_state, "beginRegister() should be called before endRegister();"); register_state = false; foreach (ref info; &entities_infos.byValue) { if (info.systems) Mallocator.dispose(info.systems); info.systems = Mallocator.makeArray!bool(systems.length); } foreach (ref system; systems) { if (system.m_empty) { if (system.m_update) addSystemCaller(system.id); continue; } if (system.m_update is null) { if (system.m_add_entity || system.m_remove_entity || system.m_change_entity || system.m_event_callers.length) { foreach (info; &entities_infos.byValue) { connectListenerToEntityInfo(*info, system.id); } } continue; } /*bool added = false; foreach (i, caller; passes[system.m_pass].system_callers) { if (systems[caller.system_id].priority > system.priority) { SystemCaller* sys_caller = Mallocator.make!SystemCaller; sys_caller.system_id = system.id; sys_caller.job_group.caller = sys_caller; system.m_any_system_caller = sys_caller; passes[system.m_pass].system_callers.add(sys_caller, i); added = true; break; } } if (!added) { SystemCaller* sys_caller = Mallocator.make!SystemCaller; sys_caller.system_id = system.id; sys_caller.job_group.caller = sys_caller; system.m_any_system_caller = sys_caller; passes[system.m_pass].system_callers.add(sys_caller); }*/ addSystemCaller(system.id); foreach (info; &entities_infos.byValue) { addSystemCaller(*info, system.id); //info.systems[system.id] = true; } } event_manager.allocateData(cast(uint) threads.length); foreach (ref info; events) { Mallocator.dispose(info.callers); } ushort[] event_callers = (cast(ushort*) alloca(ushort.sizeof * events.length))[0 .. events.length]; foreach (ref caller; event_callers) caller = 0; foreach (ref system; systems) { foreach (caller; system.m_event_callers) { event_callers[caller.id]++; } } foreach (i, ref info; events) { info.callers = Mallocator.makeArray!(EventCaller)(event_callers[i]); } foreach (ref caller; event_callers) caller = 0; foreach (ref system; systems) { foreach (caller; system.m_event_callers) { events[caller.id].callers[event_callers[caller.id]].callback = caller.callback; events[caller.id].callers[event_callers[caller.id]].system = &system; event_callers[caller.id]++; } } extern (C) static int comapreEventCaller(const void* a, const void* b) nothrow @nogc { EventCaller _a = *cast(EventCaller*) a; EventCaller _b = *cast(EventCaller*) b; if (_a.system.priority < _b.system.priority) return -1; else if (_a.system.priority == _b.system.priority) return 0; else return 1; } foreach (ref event; events) { qsort(event.callers.ptr, event.callers.length, EventCaller.sizeof, &comapreEventCaller); } //qsort(event_callers.ptr, event_callers.length, EventInfo.sizeof, &compareUShorts); foreach (EntityInfo* info; &entities_infos.byValue) { generateListeners(info); } generateDependencies(); } /************************************************************************************************************************ *Default constructor. */ export this(uint threads_count, uint page_size, uint block_pages_count) nothrow @nogc { if (threads_count == 0) threads_count = 1; threads = Mallocator.makeArray!ThreadData(threads_count); //foreach(ref thread;threads)thread = ThreadData().init; m_page_size = page_size; m_pages_in_block = block_pages_count; id_manager.initialize(); event_manager.initialize(&this); allocator = BlockAllocator(m_page_size, m_pages_in_block); //add_mutex = Mallocator.make!Mutex; entity_block_alloc_mutex = Mallocator.make!Mutex; entity_block_alloc_mutex.initialize(); //event_manager = EventManager(this); //event_manager.manager = this; } export ~this() nothrow @nogc { id_manager.deinitialize(); event_manager.destroy(); if (threads) Mallocator.dispose(threads); if (entity_block_alloc_mutex) { entity_block_alloc_mutex.destroy(); Mallocator.dispose(entity_block_alloc_mutex); entity_block_alloc_mutex = null; } allocator.freeMemory(); } /************************************************************************************************************************ Same as "void registerSystem(Sys)(int priority, int pass = 0)" but use pass name instead of id. */ void registerSystem(Sys)(int priority, const(char)[] pass_name) { ushort pass = passes_map.get(pass_name, ushort.max); version (D_BetterC) assert(pass != ushort.max, "Update pass doesn't exist."); else assert(pass != ushort.max, "Update pass (Name " ~ pass_name ~ ") doesn't exist."); registerSystem!(Sys)(priority, pass); } /************************************************************************************************************************ Register new System into EntityManager. This funcion generate glue between EntityManager and System. Systems can be registered from external dynamic library, and can be registered after adding entities too. System mustn't be registered before components which system want to use, in this case functions call assertion. Params: priority = system priority. Priority determines order of execution of systems updates pass = index of UpdatePass which sholud call system update */ void registerSystem(Sys)(int priority, ushort pass = 0) { //alias STC = ParameterStorageClass; assert(register_state, "registerSystem must be called between beginRegister() and endRegister()."); version (D_BetterC) assert(pass < passes.length, "Update pass doesn't exist."); else assert(pass < passes.length, "Update pass (ID " ~ pass.to!string ~ ") doesn't exist."); enum SystemName = fullName!Sys; System system; system.m_pass = pass; alias SystemData = SystemEntityData!Sys; static if (!(hasMember!(Sys, "EntitiesData"))) { static assert(0, "System should gave \"EntitiesData\" struct for input components"); } static if (hasMember!(Sys, "handleEvent")) { static void callEventHandler(Type)(ref EventCallData data) { Sys* data_system = cast(Sys*) data.system_pointer; Type* event = cast(Type*) data.event; data_system.handleEvent(data.entity, *event); } void setEventCallers(Sys)(ref System system) { enum event_handlers_num = __traits(getOverloads, Sys, "handleEvent").length; System.EventCaller[] callers = (cast(System.EventCaller*) alloca( event_handlers_num * System.EventCaller.sizeof))[0 .. event_handlers_num]; int i = 0; foreach (j, func; __traits(getOverloads, Sys, "handleEvent")) { alias Params = Parameters!(__traits(getOverloads, Sys, "handleEvent")[j]); static if (Params.length == 2 && is(Params[0] == Entity*)) { alias EventParamType = Params[1]; enum EventName = fullName!(Unqual!(EventParamType)); ushort evt = events_map.get(cast(char[]) EventName, ushort.max); assert(evt != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing event \"" ~ EventName ~ "\"."); callers[i].callback = cast(void*)&callEventHandler!(EventParamType); callers[i].id = becsID!EventParamType; i++; } } system.m_event_callers = Mallocator.makeArray(callers[0 .. i]); } static if (__traits(hasMember, Sys, "handleEvent")) { setEventCallers!(Sys)(system); } } alias components_info = SystemData.components_info; static void allocateSystemComponents/*(ComponentsIndices!component_counts components_info)*/( ref System system) { size_t req = components_info.req.length; size_t opt = components_info.optional.length; size_t excluded = components_info.excluded.length; size_t read_only = components_info.readonly.length; size_t writable = components_info.mutable.length; size_t read_only_deps = components_info.readonlyDeps.length; size_t writable_deps = components_info.writableDeps.length; if (req > 0) system.m_components = Mallocator.makeArray!ushort(req); if (opt > 0) system.m_optional_components = Mallocator.makeArray!ushort(opt); if (excluded > 0) system.m_excluded_components = Mallocator.makeArray!ushort(excluded); if (read_only > 0) system.m_read_only_components = Mallocator.makeArray!ushort(read_only); if (writable > 0) system.m_writable_components = Mallocator.makeArray!ushort(writable); if (read_only_deps > 0) system.m_readonly_dependencies = Mallocator.makeArray!ushort(read_only_deps); if (writable_deps > 0) system.m_writable_dependencies = Mallocator.makeArray!ushort(writable_deps); } static void genCompList(ref System system, ref HashMap!(char[], ushort) components_map) { foreach (member; __traits(allMembers, Sys.EntitiesData)) { alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); static if (isFunction!(__traits(getMember, Sys.EntitiesData, member))) static assert(0, "EntitiesData can't have any function!"); else static if (member == "length") { static assert(isIntegral!(MemberType), "EntitiesData 'length' member must be integral type."); static assert(MemberType.sizeof > 1, "EntitiesData 'length' member can't be byte or ubyte."); } else static if (member == "thread_id") { static assert(isIntegral!(MemberType), "EntitiesData 'thread_id' member must be integral type."); static assert(MemberType.sizeof > 1, "EntitiesData 'thread_id' member can't be byte or ubyte."); } else static if (member == "job_id") { static assert(isIntegral!(MemberType), "EntitiesData 'job_id' member must be integral type."); static assert(MemberType.sizeof > 1, "EntitiesData 'job_id' member can't be byte or ubyte."); } else static if (!(isArray!(MemberType))) static assert(0, "EntitiesData members should be arrays of elements!"); } //enum ComponentsIndices components_info = getComponentsInfo(); allocateSystemComponents(system); foreach (iii, comp_info; components_info.req) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_components[iii] = comp; } foreach (iii, comp_info; components_info.excluded) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_excluded_components[iii] = comp; } foreach (iii, comp_info; components_info.optional) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_optional_components[iii] = comp; } foreach (iii, comp_info; components_info.readonly) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_read_only_components[iii] = comp; } foreach (iii, comp_info; components_info.mutable) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_writable_components[iii] = comp; } } int getOnUpdateOverload()() { int ret = -1; foreach (i, func; __traits(getOverloads, Sys, "onUpdate")) { if ((Parameters!(func)).length == 1 && is(Parameters!(func)[0] == Sys.EntitiesData)) { ret = i; break; } } return ret; } static if (hasMember!(Sys, "onUpdate")) enum OnUpdateOverloadNum = getOnUpdateOverload(); else enum OnUpdateOverloadNum = -1; //enum HasOnUpdate = (hasMember!(Sys, "onUpdate") && checkOnUpdateParams()); enum IsEmpty = components_info.req.length == 0 && components_info.optional.length == 0 && components_info.excluded.length == 0 && components_info.entites_array.length == 0; static if (IsEmpty) system.m_empty = true; static if (OnUpdateOverloadNum != -1) { static void callUpdate(SystemCallData* data) { Sys* s = cast(Sys*) data.context; Sys.EntitiesData input_data; static if (!IsEmpty)SystemData.fillInputData(input_data, data.info, data.block, data.begin, data.count);//, system); static if (hasMember!(Sys.EntitiesData, "thread_id")) { input_data.thread_id = cast( typeof(input_data.thread_id)) data.thread_id; } static if (hasMember!(Sys.EntitiesData, "job_id")) { input_data.job_id = cast(typeof(input_data.job_id)) data.job_id; } s.onUpdate(input_data); } system.m_update = &callUpdate; } static void catchFunction(string func_name, RetType = void)(void** member) { static if (hasMember!(Sys, func_name)) { foreach (func; __traits(getOverloads, Sys, func_name)) { static if ((Parameters!(func)).length == 0 && is(ReturnType!(func) == RetType)) { static RetType callFunc(void* system_pointer) { Sys* s = cast(Sys*) system_pointer; static if (is(RetTyp == void)) mixin("s." ~ func_name ~ "()"); else return mixin("s." ~ func_name ~ "()"); } *member = cast(void*)&callFunc; break; } } } } static void catchEntityFunction(string func_name, RetType = void)(void** member) { static if (hasMember!(Sys, func_name)) { foreach (func; __traits(getOverloads, Sys, func_name)) { static if ((Parameters!(func)).length == 1 && is(Parameters!(func)[0] == Sys.EntitiesData) && is(ReturnType!(func) == RetType)) { static RetType callFunc(ref ListenerCallData data) { Sys* s = cast(Sys*) data.system.m_system_pointer; Sys.EntitiesData input_data; SystemData.fillInputData(input_data, data.block.type_info, data.block, data.begin, data.count);//, data.system); static if (is(RetTyp == void)) mixin("s." ~ func_name ~ "(input_data)"); else return mixin("s." ~ func_name ~ "(input_data)"); } *member = cast(void*)&callFunc; break; } } } } static void catchEntityFilterFunction(string func_name, RetType = void)(void** member) { static if (hasMember!(Sys, func_name)) { foreach (func; __traits(getOverloads, Sys, func_name)) { static if ((Parameters!(func)).length == 1 && is(Parameters!(func)[0] == EntityInfo*) && is(ReturnType!(func) == RetType)) { static RetType callFunc(void* system_pointer, EntityInfo* info) { Sys* s = cast(Sys*) system_pointer; static if (is(RetTyp == void)) mixin("s." ~ func_name ~ "(info)"); else return mixin("s." ~ func_name ~ "(info)"); } *member = cast(void*)&callFunc; break; } } } } catchFunction!("onEnable")(&system.m_enable); catchFunction!("onDisable")(&system.m_disable); catchFunction!("onCreate")(&system.m_create); catchFunction!("onDestroy")(&system.m_destroy); catchFunction!("onBegin", bool)(&system.m_begin); catchFunction!("onEnd")(&system.m_end); catchEntityFunction!("onAddEntity")(&system.m_add_entity); catchEntityFunction!("onRemoveEntity")(&system.m_remove_entity); catchEntityFunction!("onChangeEntity")(&system.m_change_entity); catchEntityFilterFunction!("filterEntity", bool)(&system.m_filter_entity); system.m_system_pointer = cast(void*) Mallocator.make!Sys; system.m_priority = priority; static if(__traits(hasMember, Sys ,"__becs_jobs_count"))system.jobs = Mallocator.makeArray!(Job)(Sys.__becs_jobs_count); else system.jobs = Mallocator.makeArray!(Job)(32); genCompList(system, components_map); foreach (iii, comp_info; components_info.readonlyDeps) { ushort comp = external_dependencies_map.get(cast(const(char)[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing dependency."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\"."); system.m_readonly_dependencies[iii] = comp; } foreach (iii, comp_info; components_info.writableDeps) { ushort comp = external_dependencies_map.get(cast(char[]) comp_info.type, ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing dependency."); else assert(comp != ushort.max, "Can't register system \"" ~ SystemName ~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\"."); system.m_writable_dependencies[iii] = comp; } ushort sys_id = systems_map.get(cast(char[]) SystemName, ushort.max); if (sys_id < systems.length) { system.m_name = systems[sys_id].m_name; systems[sys_id].m_name = null; systems[sys_id].destroy(); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); system.enable(); system.m_id = sys_id; systems[sys_id] = system; } else { system.m_name = Mallocator.makeArray(cast(char[]) SystemName); systems_map.add(system.m_name, cast(ushort) systems.length); system.m_id = cast(ushort)(systems.length); systems.add(system); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); systems[$ - 1].enable(); } becsID!Sys = system.id; } export ushort registerSystem(const (char)[] name, System system) { ushort sys_id = systems_map.get(cast(char[]) name, ushort.max); if (sys_id < systems.length) { system.m_name = systems[sys_id].m_name; systems[sys_id].m_name = null; systems[sys_id].destroy(); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); system.enable(); system.m_id = sys_id; systems[sys_id] = system; } else { system.m_name = Mallocator.makeArray(cast(char[]) name); systems_map.add(system.m_name, cast(ushort) systems.length); system.m_id = cast(ushort)(systems.length); systems.add(system); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); systems[$ - 1].enable(); } return system.m_id; } /************************************************************************************************************************ Return system ECS api by id */ export System* getSystem(ushort id) nothrow @nogc { if (id >= systems.length) return null; return &systems[id]; } /************************************************************************************************************************ Return pointer to system registered in manager */ Sys* getSystem(Sys)() nothrow @nogc { if (becsID!Sys >= systems.length) return null; return cast(Sys*) systems[becsID!Sys].m_system_pointer; } export ushort registerPass(const(char)[] name) { UpdatePass* pass = Mallocator.make!UpdatePass; pass.name = Mallocator.makeArray(cast(char[]) name); passes.add(pass); passes_map.add(name, cast(ushort)(passes.length - 1)); return cast(ushort)(passes.length - 1); } export void registerDependency(const(char)[] name) { return external_dependencies_map.add(name, cast(ushort) external_dependencies_map.length); } /************************************************************************************************************************ Register component into EntityManager. */ void registerComponent(Comp)() { ComponentInfo info; enum ComponentName = fullName!Comp; static if (hasMember!(Comp, "onDestroy") && isFunction!(Comp.onDestroy) && is(ReturnType!(Comp.onDestroy) == void) && Parameters!(Comp.onDestroy).length == 0) { static void callDestroy(void* pointer) nothrow @nogc { (cast(void delegate() nothrow @nogc)&(cast(Comp*) pointer).onDestroy)(); } info.destroy_callback = &callDestroy; } static if (hasMember!(Comp, "onCreate") && isFunction!(Comp.onCreate) && is(ReturnType!(Comp.onCreate) == void) && Parameters!(Comp.onCreate).length == 0) { static void callCreate(void* pointer) nothrow @nogc { (cast(void delegate() nothrow @nogc)&(cast(Comp*) pointer).onCreate)(); } info.create_callback = &callCreate; } static if (Comp.sizeof == 1 && Fields!(Comp).length == 0) info.size = 0; else info.size = Comp.sizeof; info.alignment = Comp.alignof; //8; info.init_data = Mallocator.makeArray!ubyte(Comp.sizeof); *cast(Comp*) info.init_data.ptr = Comp.init; // = Comp(); becsID!Comp = registerComponent(ComponentName, info); } ushort registerComponent(const (char)[] name, ComponentInfo info) { ushort comp_id = components_map.get(cast(char[]) name, ushort.max); if (comp_id < components.length) { // becsID!Comp = comp_id; if (components[comp_id].init_data) Mallocator.dispose(components[comp_id].init_data); components[comp_id] = info; } else { components.add(info); // becsID!Comp = cast(ushort)(components.length - 1); comp_id = cast(ushort)(components.length - 1); char[] name_copy = Mallocator.makeArray(cast(char[]) name); components_map.add(name_copy, cast(ushort)(components.length - 1)); } return comp_id; } void registerEvent(Ev)() { EventInfo info; // static if (!(hasMember!(Ev, "event_id")) || !is(typeof(Ev.event_id) == ushort)) // { // static assert(0, "Add \"mixin ECS.Event;\" in top of event structure;"); // } static if (hasMember!(Ev, "onDestroy") && isFunction!(Ev.onDestroy) && is(ReturnType!(Ev.onDestroy) == void) && Parameters!(Ev.onDestroy).length == 0) { static void callDestroy(void* pointer) { (cast(Ev*) pointer).onDestroy(); } info.destroy_callback = cast(void function(void*) nothrow @nogc)&callDestroy; } info.size = Ev.sizeof; info.alignment = Ev.alignof; //ushort event_id = events_map.get(Ev.stringof, ushort.max); ushort event_id = events_map.get(fullName!Ev, ushort.max); if (event_id < events.length) { becsID!Ev = event_id; } else { events.add(info); becsID!Ev = cast(ushort)(events.length - 1); // events_map.add(Ev.stringof, cast(ushort)(events.length - 1)); events_map.add(fullName!Ev, cast(ushort)(events.length - 1)); } } export ushort registerEvent(const (char)[] name, EventRegisterInfo refister_info) { EventInfo info; info.size = refister_info.size; info.alignment = refister_info.alignment; ushort event_id = events_map.get(name, ushort.max); if (event_id >= events.length) { events.add(info); event_id = cast(ushort)(events.length - 1); events_map.add(name, cast(ushort)(events.length - 1)); } return event_id; } export void callEntitiesFunction(Sys, T)(T func) { //TODO: check if onUpdate function is good Sys* s; static assert(isDelegate!func, "Function must be delegate."); static assert(__traits(hasMember, Sys, "EntitiesData"), "Can't call function with system which hasn't EntitesData structure."); ///TODO: make possibly to call function to group without system with onUpdate function static assert(__traits(hasMember, Sys, "onUpdate"), "Can't call function with system which hasn't onUpdate function callback."); // static assert(is(SetFunctionAttributes!(T, functionLinkage!(s.onUpdate), // functionAttributes!(s.onUpdate)) == typeof(&s.onUpdate)), // "Function must match system update function."); FIXME: It's lead to crash on android build // static assert(__traits(hasMember, Sys, "system_id"), "Sys must be system type."); System* system = getSystem(becsID!Sys); assert(system != null, "System must be registered in EntityManager before any funcion can be called."); if (!system.m_any_system_caller) return; /* struct Delegate { void* context; void* func; }*/ /*static void fillInputData_dyn(ref Sys.EntitiesData input_data, EntityInfo* info, EntitiesBlock* block, uint offset, uint entities_count) { System* system = gEntityManager.getSystem(becsID!Sys); input_data.length = cast(typeof(input_data.length))entities_count; // input_data.thread_id = 0; foreach(size_t i, ushort component; system.m_components) { *(&block.data_pointer + i) = cast(void*) block + info.deltas[component] + offset * gEntityManager.components[component].size; } foreach(size_t i, ushort component; system.m_optional_components) { if(component < info.deltas.length && info.deltas[component] != 0)*(&input_data.data_pointer + i + system.m_components.length) = cast(void*) block + info.deltas[component] + offset * gEntityManager.components[component].size; } }*/ static void callUpdate(SystemCallData* data) { //Sys* s = cast(Sys*) data.system_pointer; Sys.EntitiesData input_data; EntityInfo* info = data.info; alias SystemData = SystemEntityData!Sys; SystemData.fillInputData(input_data, info, data.block, data.begin, data.count); //fillInputData(input_data, info, data.block, data.begin, data.end - data.begin); //fillInputData_dyn(input_data, info, data.block, data.begin, data.end - data.begin); (*cast(void delegate(Sys.EntitiesData)*)data.context)(input_data); } foreach (info; system.m_any_system_caller.infos) { CallData data = CallData(system.id, system, info, cast(void*)&func, cast(void*)&callUpdate); data.update(); } } /************************************************************************************************************************ Same as "void update(int pass = 0)" but use pass name instead of id. */ export void update(const(char)[] pass_name) nothrow @nogc { ushort pass = passes_map.get(pass_name, ushort.max); assert(pass != ushort.max); update(pass); } /************************************************************************************************************************ Update systems. Should be called only between begin() and end(). */ export void update(ushort pass = 0) nothrow @nogc { assert(!register_state); assert(pass < passes.length); foreach (caller; passes[pass].system_callers) { System* sys = &systems[caller.system_id]; if (sys.enabled && sys.willExecute) { if (sys.m_empty) { CallData data = CallData(caller.system_id, sys, null, sys.m_system_pointer, sys.m_update); data.update(); } else foreach (info; caller.infos) { CallData data = CallData(caller.system_id, sys, info, sys.m_system_pointer, sys.m_update); data.update(); } } } } /************************************************************************************************************************ Same as "void updateMT(int pass = 0)" but use pass name instead of id. */ export void updateMT(const(char)[] pass_name) nothrow @nogc { ushort pass = passes_map.get(pass_name, ushort.max); assert(pass != ushort.max); updateMT(pass); } export void updateMT(ushort pass = 0) nothrow @nogc { assert(!register_state); assert(pass < passes.length); assert(m_dispatch_jobs, "Can't update with multithreading without JobDispatch function. Please use setJobDispatchFunc()."); Vector!CallData tmp_datas; tmp_datas.reserve(8); foreach (caller; passes[pass].system_callers) { System* sys = &systems[caller.system_id]; if (sys.enabled && sys.willExecute) { uint job_id = 0; void nextJob() { CallData[] callers = m_call_data_allocator.getCallData( cast(uint) tmp_datas.length); //callers[0 .. $] = tmp_datas[0 .. $]; memcpy(callers.ptr, &tmp_datas[0], CallData.sizeof * tmp_datas.length); tmp_datas.clear(); sys.jobs[job_id].callers = callers; sys.jobs[job_id].id = job_id; job_id++; } if (sys.m_empty) { tmp_datas.add(CallData(caller.system_id, sys, null, sys.m_system_pointer, sys.m_update)); nextJob(); caller.job_group.jobs = sys.jobs[0 .. 1]; (cast(void delegate(JobGroup) nothrow @nogc) m_dispatch_jobs)(caller.job_group); continue; } uint entities_count = 0; foreach (info; caller.infos) { uint blocks_count = info.nonEmptyBlocksCount(); if (blocks_count == 0) continue; if (blocks_count > 1) entities_count += (blocks_count - 1) * info.max_entities; entities_count += info.last_block.entities_count; } if (!entities_count) continue; uint jobs_count = cast(uint) sys.jobs.length; uint entities_per_job = entities_count / jobs_count + 1; if (entities_per_job <= 4) { jobs_count = entities_count / 4; if (jobs_count == 0) jobs_count = 1; entities_per_job = entities_count / jobs_count + 1; } entities_count = 0; foreach (info; caller.infos) { uint blocks_count = info.nonEmptyBlocksCount(); EntitiesBlock* first_block = info.first_block; uint first_elem = 0; begin: if (first_block is null || blocks_count == 0) continue; //if this info will fill job if ((blocks_count - 1) * info.max_entities + entities_count + info.last_block.entities_count - first_elem >= entities_per_job) { int reamaining_entities = (entities_per_job - entities_count - ( first_block.entities_count - first_elem)); //if first block don't fill job if (reamaining_entities > 0) { //take as many full blocks as possible int full_blocks_count = reamaining_entities / info.max_entities; EntitiesBlock* block = first_block; foreach (i; 0 .. full_blocks_count + 1) block = block.next_block; //if full block + actual contained entities + remaining entities form first block > entities count per job if (full_blocks_count * info.max_entities + entities_count + ( first_block.entities_count - first_elem) >= entities_per_job) { assert(entities_per_job == full_blocks_count * info.max_entities + entities_count + ( first_block.entities_count - first_elem)); CallData data = CallData(caller.system_id, sys, info, sys.m_system_pointer, sys.m_update, first_block, cast(ushort)(full_blocks_count + 1), cast(ushort) first_elem, 0); tmp_datas.add(data); first_elem = 0; blocks_count -= full_blocks_count + 1; first_block = block; } else { entities_count += full_blocks_count * info.max_entities + ( first_block.entities_count - first_elem); // - first_elem; uint last_elem = entities_per_job - entities_count; // + first_elem - 1; assert(last_elem > 0); assert(last_elem <= block.entities_count); CallData data = CallData(caller.system_id, sys, info, sys.m_system_pointer, sys.m_update, first_block, cast(ushort)(full_blocks_count + 2), cast(ushort) first_elem, cast(ushort) last_elem); tmp_datas.add(data); first_elem = last_elem; blocks_count -= full_blocks_count + 1; first_block = block; if (last_elem == block.entities_count) { assert(block.next_block == null); first_block = null; } } } else { uint last_elem = entities_per_job - entities_count; assert(last_elem > 0); CallData data = CallData(caller.system_id, sys, info, sys.m_system_pointer, sys.m_update, first_block, 1, cast(ushort) first_elem, cast(ushort)(first_elem + last_elem)); tmp_datas.add(data); first_elem += last_elem; assert(first_elem <= first_block.entities_count); //if job takes every entity, take next block if (first_elem == first_block.entities_count) { first_elem = 0; first_block = first_block.next_block; blocks_count--; } } nextJob(); entities_count = 0; goto begin; } else { //take whole info blocks CallData data = CallData(caller.system_id, sys, info, sys.m_system_pointer, sys.m_update, first_block, cast(ushort) blocks_count, cast(ushort) first_elem); tmp_datas.add(data); entities_count += (blocks_count - 1) * info.max_entities + info.last_block.entities_count - first_elem; } } nextJob(); caller.job_group.jobs = sys.jobs[0 .. job_id]; (cast(void delegate(JobGroup) nothrow @nogc) m_dispatch_jobs)(caller.job_group); //sys.jobs[0 .. job_id]); } } } export void setMultithreadingCallbacks(void delegate(JobGroup) dispatch_callback, uint delegate() get_id_callback) { m_dispatch_jobs = cast(void delegate(JobGroup jobs) nothrow @nogc) dispatch_callback; m_thread_id_func = cast(uint delegate() nothrow @nogc) get_id_callback; } /************************************************************************************************************************ Return size of single page (block). Every entity data block has size of page. */ uint pageSize() { return m_page_size; } /************************************************************************************************************************ Return number of pages in single block allocation. Library allocate defined number of pages at once and assign it's for entities. */ uint pagesInBlock() { return m_pages_in_block; } static void alignNum(ref ushort num, ushort alignment) nothrow @nogc pure { num = cast(ushort)((num + alignment - 1) & (-cast(int) alignment)); //num += alignment - (num & (alignment - 1)); } extern (C) static int compareUShorts(const void* a, const void* b) nothrow @nogc { ushort _a = *cast(ushort*) a; ushort _b = *cast(ushort*) b; if (_a < _b) return -1; else if (_a == _b) return 0; else return 1; } /************************************************************************************************************************ Allocate EntityTemplate with all components from entity witch it's data and returns pointer to it. Params: entity_id = ID of entity from which should be created template fill_default = if true, components will be filled with default data, instead entity data will be taken */ export EntityTemplate* allocateTemplate(EntityID entity_id, bool fill_default = false) { Entity* entity = getEntity(entity_id); EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; EntityTemplate* temp = Mallocator.make!EntityTemplate; temp.entity_data = Mallocator.makeArray!ubyte(info.size); temp.info = info; if (fill_default) { //fill components with default data foreach (comp; info.components) { if (components[comp].size == 0) continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } } else { ushort index = block.entityIndex(entity); foreach (comp; info.components) { if (components[comp].size == 0) continue; memcpy(cast(void*) temp.entity_data.ptr + info.tmpl_deltas[comp], cast(void*) block + info.deltas[comp] + components[comp].size * index, components[comp].size); } } return temp; } /************************************************************************************************************************ Allocate EntityTemplate with specifed components and returns pointer to it. Params: components_ids = array of components allocated with template */ export EntityTemplate* allocateTemplate(ushort[] components_ids) { ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * components_ids.length))[0 .. components_ids.length]; memcpy(ids.ptr, components_ids.ptr, ushort.sizeof * components_ids.length); //ids[0 .. $] = components_ids[]; qsort(ids.ptr, ids.length, ushort.sizeof, &compareUShorts); { uint j = 1; foreach (i; 1 .. ids.length) { assert(ids[i] != ushort.max); 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.make!EntityTemplate; temp.entity_data = Mallocator.makeArray!ubyte(info.size); temp.info = info; //fill components with default data foreach (comp; info.components) { if (components[comp].size == 0) continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } return temp; } /************************************************************************************************************************ Allocate EntityTemplate from basic Template with modifications by adding and removing some components and returns pointer to it. Arrays of components needen't to be checked for repeated components, as function itself check if components exist in base template. Params: base_tmpl = template from which components sould be copied components_ids = array of new components to add remove_components_ids = array of components to remove from base template */ export EntityTemplate* allocateTemplate(EntityTemplate* base_tmpl, ushort[] components_ids, ushort[] remove_components_ids = null) { size_t len = base_tmpl.info.components.length + components_ids.length; ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * len))[0 .. len]; memcpy(ids.ptr, base_tmpl.info.components.ptr, ushort.sizeof * base_tmpl.info.components.length); memcpy(ids.ptr + base_tmpl.info.components.length, components_ids.ptr, ushort.sizeof * components_ids.length); qsort(ids.ptr, ids.length, ushort.sizeof, &compareUShorts); qsort(remove_components_ids.ptr, remove_components_ids.length, ushort.sizeof, &compareUShorts); { uint k = 0; uint j = 1; foreach (i; 1 .. ids.length) { assert(ids[i] != ushort.max); if (k < remove_components_ids.length) { while (k < remove_components_ids.length && remove_components_ids[k] < ids[i]) { k++; } if (k < remove_components_ids.length) { if (remove_components_ids[k] == ids[i]) continue; } } 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.make!EntityTemplate; temp.entity_data = Mallocator.makeArray!ubyte(info.size); temp.info = info; //fill components with default data and copy from base template foreach (comp; info.components) { if (comp < base_tmpl.info.tmpl_deltas.length && base_tmpl.info.tmpl_deltas[comp] != ushort.max) //copy data from base component { if (components[comp].size == 0) continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], base_tmpl.entity_data.ptr + base_tmpl.info.tmpl_deltas[comp], components[comp].size); } else //fill with default data { if (components[comp].size == 0) continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } } return temp; } /************************************************************************************************************************ Allocate EntityTemplate copy. Params: copy_tmpl = template which should be copied */ export EntityTemplate* allocateTemplate(EntityTemplate* copy_tmpl) { EntityTemplate* tmpl = Mallocator.make!EntityTemplate; tmpl.info = copy_tmpl.info; tmpl.entity_data = Mallocator.makeArray(copy_tmpl.entity_data); return tmpl; } /************************************************************************************************************************ Returns entity type info. Params: ids = array of components */ export EntityInfo* getEntityInfo(ushort[] ids) { EntityInfo* info = entities_infos.get(ids, null); if (info is null) { info = Mallocator.make!EntityInfo; info.components = Mallocator.makeArray(ids); /*info.components = Mallocator.makeArray!ushort(ids.length); info.components[0 .. $] = ids[0 .. $];*/ info.deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1); info.size = EntityID.sizeof; info.alignment = EntityID.alignof; info.tmpl_deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1, ushort.max); uint components_size = EntityID.sizeof; foreach (i, id; ids) { info.alignment = max(info.alignment, components[id].alignment); alignNum(info.size, components[id].alignment); info.tmpl_deltas[id] = info.size; info.size += components[id].size; components_size += components[id].size; } alignNum(info.size, info.alignment); uint block_memory = cast(uint)( m_page_size - EntitiesBlock.sizeof - (info.size - components_size)); //uint entity_comps_size = EntityID.sizeof; uint mem_begin = EntitiesBlock.sizeof; uint entites_in_block = block_memory / info.size; //entity_comps_size; info.max_entities = cast(ushort) entites_in_block; ushort current_delta = cast(ushort)(mem_begin + entites_in_block * EntityID.sizeof); foreach (i, id; ids) { if (current_delta == 0) current_delta = ushort.max; alignNum(current_delta, components[id].alignment); info.deltas[id] = cast(ushort) current_delta; current_delta += entites_in_block * components[id].size; } info.systems = Mallocator.makeArray!bool(systems.length); foreach (i, ref system; systems) { if (system.m_empty) continue; if (system.m_update is null) { if (system.m_add_entity || system.m_remove_entity || system.m_change_entity || system.m_event_callers.length) connectListenerToEntityInfo(*info, cast(uint) i); continue; } addSystemCaller(*info, cast(uint) i); } info.comp_add_info = Mallocator.makeArray!(EntityInfo*)(gEntityManager.components.length); //info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(gEntityManager.components.length); info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(info.deltas.length); foreach (comp; info.components) { info.comp_add_info[comp] = info; info.comp_rem_info[comp] = null; } entities_infos.add(info.components, info); generateListeners(info); } return info; } private void generateListeners(EntityInfo* info) nothrow { if (info.add_listeners) { Mallocator.dispose(info.add_listeners); info.add_listeners = null; } if (info.remove_listeners) { Mallocator.dispose(info.remove_listeners); info.remove_listeners = null; } if (info.change_listeners) { Mallocator.dispose(info.change_listeners); info.change_listeners = null; } //allocate local data ushort[] tmp_add = (cast(ushort*) alloca(systems.length * ushort.sizeof))[0 .. systems.length]; ushort[] tmp_rem = (cast(ushort*) alloca(systems.length * ushort.sizeof))[0 .. systems.length]; ushort[] tmp_ch = (cast(ushort*) alloca(systems.length * ushort.sizeof))[0 .. systems .length]; int add_len = 0; int rem_len = 0; int ch_len = 0; //assign listeners to lists foreach (i; 0 .. systems.length) { if (info.systems[i]) { System* system = &systems[i]; //onAddEntity listener if (system.m_add_entity) { //find listener position by priority int j; for (j = 0; j < add_len; j++) { if (systems[i].priority < systems[tmp_add[j]].priority) break; } add_len++; //move elements after new listener if (add_len < tmp_add.length) for (int k = add_len; k > j; k--) { tmp_add[k] = tmp_add[k - 1]; } //assign listener tmp_add[j] = cast(ushort) i; } //onRemoveEntity listener if (system.m_remove_entity) { //find listener position by priority int j; for (j = 0; j < rem_len; j++) { if (systems[i].priority < systems[tmp_rem[j]].priority) break; } rem_len++; //move elements after new listener if (rem_len < tmp_add.length) for (int k = rem_len; k > j; k--) { tmp_rem[k] = tmp_rem[k - 1]; } //assign listener tmp_rem[j] = cast(ushort) i; } //onChangeEntity listener if (system.m_change_entity) { //find listener position by priority int j; for (j = 0; j < ch_len; j++) { if (systems[i].priority < systems[tmp_ch[j]].priority) break; } ch_len++; //move elements after new listener if (ch_len < tmp_add.length) for (int k = ch_len; k > j; k--) { tmp_ch[k] = tmp_ch[k - 1]; } //assign listener tmp_ch[j] = cast(ushort) i; } } } if (add_len) { info.add_listeners = Mallocator.makeArray!ushort(add_len); memcpy(info.add_listeners.ptr, tmp_add.ptr, add_len * ushort.sizeof); } if (rem_len) { info.remove_listeners = Mallocator.makeArray!ushort(rem_len); memcpy(info.remove_listeners.ptr, tmp_rem.ptr, rem_len * ushort.sizeof); } if (ch_len) { info.change_listeners = Mallocator.makeArray!ushort(ch_len); memcpy(info.change_listeners.ptr, tmp_ch.ptr, ch_len * ushort.sizeof); } } export void connectListenerToEntityInfo(ref EntityInfo entity, uint system_id) nothrow @nogc { System* system = &systems[system_id]; if (system.m_excluded_components) { foreach (id; system.m_excluded_components) { foreach (id2; entity.components) { if (id == id2) return; } } } foreach (id; system.m_components) { foreach (i2, id2; entity.components) { if (id2 == id) goto is_; } return; is_: } ///call Custom Entity Filter test if function exists if(system.m_filter_entity && !(cast(bool function(void* system_pointer, EntityInfo* info) @nogc nothrow)system.m_filter_entity)(system, &entity))return; entity.systems[system_id] = true; } export void addSystemCaller(uint system_id) nothrow @nogc { System* system = &systems[system_id]; uint index = 0; for (; index < passes[system.m_pass].system_callers.length; index++) { if (passes[system.m_pass].system_callers[index].system_id == system_id) return; } bool added = false; foreach (i, caller; passes[system.m_pass].system_callers) { if (systems[caller.system_id].priority > system.priority) { SystemCaller* sys_caller = Mallocator.make!SystemCaller; sys_caller.system_id = system.id; sys_caller.job_group.caller = sys_caller; system.m_any_system_caller = sys_caller; passes[system.m_pass].system_callers.add(sys_caller, i); added = true; break; } } if (!added) { SystemCaller* sys_caller = Mallocator.make!SystemCaller; sys_caller.system_id = system.id; sys_caller.job_group.caller = sys_caller; system.m_any_system_caller = sys_caller; passes[system.m_pass].system_callers.add(sys_caller); } } export void addSystemCaller(ref EntityInfo info, uint system_id) nothrow @nogc { System* system = &systems[system_id]; connectListenerToEntityInfo(info, system_id); if(!info.systems[system_id])return; uint index = 0; for (; index < passes[system.m_pass].system_callers.length; index++) { if (passes[system.m_pass].system_callers[index].system_id == system_id) break; } if (index < passes[system.m_pass].system_callers.length) { passes[system.m_pass].system_callers[index].infos.add(&info); } } /************************************************************************************************************************ Returns pointer to entity. Params: id = ID of entity */ export Entity* getEntity(EntityID id) nothrow @nogc { return cast(Entity*) id_manager.getEntityPointer(id); } /************************************************************************************************************************ Remove components from entity by IDs. Components will be removed on end of frame. Params: entity_id = ID of entity del_ids = array of components IDs */ export void removeComponents(EntityID entity_id, ushort[] del_ids) nothrow @nogc { ThreadData* data = &threads[threadID]; uint num = cast(uint) del_ids.length; data.changeEntitiesList.add(0); data.changeEntitiesList.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); data.changeEntitiesList.add((cast(ubyte*)&num)[0 .. uint.sizeof]); data.changeEntitiesList.add((cast(ubyte*) del_ids.ptr)[0 .. num * 2]); } private void __removeComponents(EntityID entity_id, ushort[] del_ids) { Entity* entity = id_manager.getEntityPointer(entity_id); if (!entity) return; EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; //remove non-existing components uint num = cast(uint) del_ids.length; foreach_reverse (i; 0 .. num) { if (info.deltas.length <= del_ids[i] || info.deltas[del_ids[i]] == 0) { num--; del_ids[i] = del_ids[num]; } } if (num == 0) return; del_ids = del_ids[0 .. num]; //sort components qsort(del_ids.ptr, del_ids.length, ushort.sizeof, &compareUShorts); EntityInfo* new_info = info; foreach (id; del_ids) { new_info = new_info.getNewInfoRemove(id); } /*if (new_info == info) return;*/ EntitiesBlock* new_block = findBlockWithFreeSpace(new_info); updateEntityInfoBlocks(new_info); assert(new_block.added_count == 0); void* start = new_block.dataBegin() + new_block.entities_count * EntityID.sizeof; Entity* new_entity = cast(Entity*) start; new_entity.id = entity.id; id_manager.update(*new_entity); uint ind = block.entityIndex(entity); if (info.remove_listeners) { foreach (listener; info.remove_listeners) { if (!new_info.systems[listener]) { callRemoveEntityListener(&systems[listener], info, block, ind, 1); } } } foreach (comp; new_info.components) { uint comp_size = components[comp].size; if (comp_size == 0) continue; memcpy(cast(void*) new_block + new_info.deltas[comp] + new_block.entities_count * comp_size, cast(void*) block + info.deltas[comp] + ind * comp_size, comp_size); } new_block.entities_count++; if (new_block != new_info.update_block) new_info.update_block = new_block; if (new_info.add_listeners) { foreach (listener; new_info.add_listeners) { if (!info.systems[listener]) { callAddEntityListener(&systems[listener], new_info, new_block, new_block.entities_count - 1, 1); } } } if (new_info.change_listeners) { foreach (listener; new_info.change_listeners) { if (info.systems[listener]) { callChangeEntityListener(&systems[listener], new_info, new_block, new_block.entities_count - 1, 1, del_ids); } } } removeEntityNoID(entity, block); } /************************************************************************************************************************ Remove coponents from entity. Params: Components = components types to remove entity_id = ID of entity */ void removeComponents(Components...)(EntityID entity_id) { const uint num = Components.length; ushort[num] del_ids; static foreach (i, comp; Components) { del_ids[i] = becsID!comp; } removeComponents(entity_id, del_ids); } private void __addComponents(EntityID entity_id, ushort[] new_ids, void*[] data_pointers) { uint num = cast(uint) new_ids.length; Entity* entity = id_manager.getEntityPointer(entity_id); if (!entity) return; EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; foreach_reverse (i; 0 .. num) { if (info.deltas.length > new_ids[i] && info.deltas[new_ids[i]] != 0) { num--; new_ids[i] = new_ids[num]; data_pointers[i] = data_pointers[num]; } } if (num == 0) return; new_ids = new_ids[0 .. num]; foreach (int i; 0 .. num) { ushort min = new_ids[i]; int pos = i; foreach (int j; i .. num) { if (new_ids[j] < min) { min = new_ids[j]; pos = j; } } if (pos != i) { ushort id = new_ids[i]; new_ids[i] = new_ids[pos]; new_ids[pos] = id; void* ptr = data_pointers[i]; data_pointers[i] = data_pointers[pos]; data_pointers[pos] = ptr; } } EntityInfo* new_info = info; foreach (id; new_ids) { new_info = new_info.getNewInfoAdd(id); } assert(new_info != info); /*if (new_info == info) return;*/ //EntityInfo* new_info = getEntityInfo(ids[0 .. len]); EntitiesBlock* new_block = findBlockWithFreeSpace(new_info); updateEntityInfoBlocks(new_info); assert(new_block.added_count == 0); void* start = new_block.dataBegin() + new_block.entities_count * EntityID.sizeof; Entity* new_entity = cast(Entity*) start; new_entity.id = entity.id; id_manager.update(*new_entity); uint j = 0; uint k = 0; uint ind = block.entityIndex(entity); if (info.remove_listeners) { foreach (listener; info.remove_listeners) { if (!new_info.systems[listener]) { callRemoveEntityListener(&systems[listener], info, block, ind, 1); } } } foreach (id; new_info.components) //ids[0 .. len]) { uint size = components[id].size; void* dst = void; if (size != 0) dst = cast(void*) new_block + new_info.deltas[id] + (new_block.entities_count) * size; if (k >= new_ids.length) { if (size != 0) memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } else if (j >= info.components.length || id == new_ids[k]) { if (size != 0) memcpy(dst, data_pointers[k], size); k++; } else { assert(id != new_ids[0]); if (size != 0) memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } } new_block.entities_count++; if (new_block != new_info.update_block) new_info.update_block = new_block; if (new_info.add_listeners) { foreach (listener; new_info.add_listeners) { if (!info.systems[listener]) { callAddEntityListener(&systems[listener], new_info, new_block, new_block.entities_count - 1, 1); } } } if (new_info.change_listeners) { foreach (listener; new_info.change_listeners) { if (info.systems[listener]) { callChangeEntityListener(&systems[listener], new_info, new_block, new_block.entities_count - 1, 1, new_ids); } } } removeEntityNoID(entity, block); } /************************************************************************************************************************ Add components to entity. Components will be added on end of frame. Params: entity_id = ID of entity to remove comps = components to add */ void addComponents(Components...)(const EntityID entity_id, Components comps) nothrow @nogc { const uint num = Components.length; /*ushort[num] new_ids; static foreach (i, comp; Components) { new_ids[i] = comp.component_id; } ThreadData* data = &threads[threadID]; data.changeEntitiesList.add(cast(ubyte) 1u); data.changeEntitiesList.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); data.changeEntitiesList.add((cast(ubyte*)&num)[0 .. uint.sizeof]); data.changeEntitiesList.add(cast(ubyte[]) new_ids); static foreach (i, comp; comps) { data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); }*/ //__addComponents(entity_id, new_ids, pointers); ComponentRef[num] _comps; static foreach (i, comp; comps) { _comps[i] = comp.ref_; } addComponents(entity_id, _comps); } export void addComponents(const EntityID entity_id, ComponentRef[] comps) nothrow @nogc { uint num = cast(uint) comps.length; ThreadData* data = &threads[threadID]; data.changeEntitiesList.add(cast(ubyte) 1u); data.changeEntitiesList.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); data.changeEntitiesList.add((cast(ubyte*)&num)[0 .. uint.sizeof]); foreach (ref_; comps) { data.changeEntitiesList.add((cast(ubyte*)&ref_.component_id)[0 .. ushort.sizeof]); } foreach (ref_; comps) { if (components[ref_.component_id].size != 0) data.changeEntitiesList.add( (cast(ubyte*) ref_.ptr)[0 .. components[ref_.component_id].size]); } /*data.changeEntitiesList.add(cast(ubyte[]) new_ids); static foreach (i, comp; comps) { data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); }*/ } /************************************************************************************************************************ Free template memory. Params: template_ = pointer entity template allocated by EntityManager. */ export void freeTemplate(EntityTemplate* template_) @nogc nothrow { Mallocator.dispose(template_.entity_data); Mallocator.dispose(template_); } /************************************************************************************************************************ Add copy of entity to system and returns pointer to it. Added copy has same data as copied entity. Returen pointer is valid only before one from commit(), begin() or end() will be called. To save entity to further use you should save ID instead of pointer. * *Params: *id = ID of entity to be copyied. */ export Entity* addEntityCopy(EntityID id) { Entity* entity = getEntity(id); EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; ushort index = block.entityIndex(entity); ushort new_index = 0; EntitiesBlock* new_block; do { new_block = findBlockWithFreeSpaceMT(info); new_index = new_block.added_count.atomicOp!"+="(1); } while (new_block.entities_count + new_index > info.max_entities); ushort new_id = cast(ushort)(new_block.entities_count + new_index - 1); const void* data_begin = new_block.dataBegin(); const void* start = data_begin + EntityID.sizeof * new_id; foreach (i, comp; info.components) { ushort size = components[comp].size; if (size != 0) memcpy(cast(void*) new_block + info.deltas[comp] + new_id * size, cast(void*) block + info.deltas[comp] + size * index, size); if (components[comp].create_callback) { components[comp].create_callback( cast(void*) new_block + info.deltas[comp] + new_id * size); } } if (new_index == 1 && info.update_block == new_block) threads[threadID].infosToUpdate.add(info); Entity* new_entity = cast(Entity*) start; new_entity.id = id_manager.getNewID(); id_manager.update(*new_entity); return new_entity; } /************************************************************************************************************************ Add entity to system. Returen pointer is valid only before one from commit(), begin() or end() will be called. To save entity to further use you should save ID instead of pointer. Params: tmpl = pointer entity template allocated by EntityManager. */ export Entity* addEntity(EntityTemplate* tmpl) { return addEntity(tmpl, null); } /************************************************************************************************************************ Add entity to system. Returen pointer is valid only before one from commit(), begin() or end() will be called. To save entity to further use you should save ID instead of pointer. Params: tmpl = pointer entity template allocated by EntityManager. replacement = list of components references to used. Memory form list replace data from template inside new entity. Should be used only for data which vary between most entities (like 3D position etc.) */ export Entity* addEntity(EntityTemplate* tmpl, ComponentRef[] replacement) { EntityInfo* info = tmpl.info; ushort index = 0; EntitiesBlock* block; do { block = findBlockWithFreeSpaceMT(info); index = block.added_count.atomicOp!"+="(1); } while (block.entities_count + index > info.max_entities); uint id = (block.entities_count + index - 1); //block.added_count); void* data_begin = block.dataBegin(); void* start = data_begin + EntityID.sizeof * id; foreach (comp; info.components) { uint size = components[comp].size; if (size != 0) memcpy(cast(void*) block + info.deltas[comp] + size * id, tmpl.entity_data.ptr + info.tmpl_deltas[comp], size); } foreach (comp; replacement) { if (comp.component_id < info.deltas.length) { ushort delta = info.deltas[comp.component_id]; if (delta != 0) { uint size = components[comp.component_id].size; if (size != 0) memcpy(cast(void*) block + delta + size * id, comp.ptr, size); } } } foreach (i, comp; info.components) { if (components[comp].create_callback) { components[comp].create_callback( cast(void*) block + info.deltas[comp] + id * components[comp].size); } } if (index == 1 && info.update_block == block) threads[threadID].infosToUpdate.add(info); Entity* entity = cast(Entity*) start; entity.id = id_manager.getNewID(); id_manager.update(*entity); return entity; } /************************************************************************************************************************ Return block with free space for selected EntityInfo. */ private EntitiesBlock* findBlockWithFreeSpace(EntityInfo* info) nothrow @nogc { EntitiesBlock* block = info.last_block; if (block is null) { block = cast(EntitiesBlock*) allocator.getBlock(); *block = EntitiesBlock(info); block.id = 0; info.first_block = block; info.last_block = block; info.update_block = block; } else if (block.entities_count >= info.max_entities) { EntitiesBlock* new_block = cast(EntitiesBlock*) allocator.getBlock(); *new_block = EntitiesBlock(info); new_block.prev_block = block; block.next_block = new_block; new_block.id = cast(ushort)(block.id + 1); block = new_block; info.last_block = block; ///make sure that update_block point to unfilled block if (info.update_block.entities_count == info.max_entities) { assert(!info.update_block.added_count); info.update_block = block; } } return block; } /************************************************************************************************************************ Return block with free space for selected EntityInfo. Additional this function is multithread safe. */ private EntitiesBlock* findBlockWithFreeSpaceMT(EntityInfo* info) { EntitiesBlock* block = info.last_block; if (block is null) { entity_block_alloc_mutex.lock(); scope (exit) entity_block_alloc_mutex.unlock(); if (info.last_block != null) return info.last_block; block = cast(EntitiesBlock*) allocator.getBlock(); *block = EntitiesBlock(info); block.id = 0; info.first_block = block; info.last_block = block; info.update_block = block; } else if (block.entities_count + block.added_count >= info.max_entities) { EntitiesBlock* last_block = info.last_block; entity_block_alloc_mutex.lock(); scope (exit) entity_block_alloc_mutex.unlock(); if (info.last_block !is last_block) return info.last_block; EntitiesBlock* new_block = cast(EntitiesBlock*) allocator.getBlock(); *new_block = EntitiesBlock(info); new_block.prev_block = block; block.next_block = new_block; new_block.id = cast(ushort)(block.id + 1); block = new_block; info.last_block = block; ///make sure that update_block point to unfilled block if (info.update_block.entities_count == info.max_entities) { assert(!info.update_block.added_count); info.update_block = block; } } return block; } /************************************************************************************************************************ Remove entity by ID. Entity will be removed on frame end. Params: id = id of entity to remove */ export void removeEntity(EntityID id) { threads[threadID].entitesToRemove.add(id); } private void __removeEntity(EntityID id) nothrow @nogc { //get entity and block meta data pointers Entity* entity = id_manager.getEntityPointer(id); if (entity is null) return; //return if entity doesn't exist EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; if (info.remove_listeners) { uint pos = block.entityIndex(entity); callRemoveEntityListeners(info, block, pos, 1); } id_manager.releaseID(id); //release id from manager removeEntityNoID(entity, block, true); } private void removeEntityNoID(Entity* entity, EntitiesBlock* block, bool call_destructors = false) nothrow @nogc { EntityInfo* info = block.type_info; updateEntityInfoBlocks(info); assert(info.last_block.added_count == 0); assert(info.last_block.entities_count > 0); info.last_block.entities_count--; uint pos = block.entityIndex(entity); if (call_destructors) { foreach (comp; info.components) { if (components[comp].destroy_callback) { components[comp].destroy_callback(cast( void*) block + info.deltas[comp] + pos * components[comp].size); } } } if (block !is info.last_block || pos != block.entities_count) { foreach (comp; info.components) { uint size = components[comp].size; if (size == 0) continue; void* src = cast(void*) info.last_block + info.deltas[comp]; void* dst = cast(void*) block + info.deltas[comp]; memcpy(dst + pos * size, src + info.last_block.entities_count * size, size); } block = info.last_block; entity.id = *cast(EntityID*)(block.dataBegin() + block.entities_count * EntityID.sizeof); id_manager.update(*entity); } block = info.last_block; if (block.entities_count == 0) { assert(info.update_block is block); info.last_block = block.prev_block; if (info.first_block is block) { info.first_block = null; } if (block.prev_block) { block.prev_block.next_block = null; info.update_block = block.prev_block; assert(block.prev_block.added_count == 0); //block.prev_block.added_count.atomicStore(cast(ushort)0); } allocator.freeBlock(block); } } /************************************************************************************************************************ functions return MetaData of page. Params: pointer = pointer to any data of entity (i.e. component data pointer) */ export EntitiesBlock* getMetaData(const void* pointer) nothrow @nogc { return cast(EntitiesBlock*)(cast(size_t) pointer & (~cast(size_t)(m_page_size - 1))); } private bool changeEntities() { bool has_work = false; //foreach (ref ThreadData thread; threads)thread.swapToChange(); foreach (ref ThreadData thread; threads) { uint index = 0; uint len = cast(uint) thread.changeEntitiesListPrev.length; if (len) has_work = true; void*[32] pointers; // = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; while (index < len) { if (!thread.changeEntitiesListPrev[index++]) { EntityID id = *cast(EntityID*)&thread.changeEntitiesListPrev[index]; index += EntityID.sizeof; uint num = *cast(uint*)&thread.changeEntitiesListPrev[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; ids = (cast(ushort*)&thread.changeEntitiesListPrev[index])[0 .. num]; index += ushort.sizeof * num; __removeComponents(id, ids); } else { EntityID id = *cast(EntityID*)&thread.changeEntitiesListPrev[index]; index += EntityID.sizeof; uint num = *cast(uint*)&thread.changeEntitiesListPrev[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; ids = (cast(ushort*)&thread.changeEntitiesListPrev[index])[0 .. num]; index += ushort.sizeof * num; //void*[] pointers = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; foreach (i; 0 .. num) { pointers[i] = &thread.changeEntitiesListPrev[index]; index += components[ids[i]].size; } __addComponents(id, ids, pointers[0 .. num]); } } thread.changeEntitiesListPrev.clear(); } return has_work; } private void callAddEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, int count) @nogc nothrow { foreach (listener; info.add_listeners) { System* system = &systems[listener]; callAddEntityListener(system, info, block, begin, count); } } private static void callAddEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int count) @nogc nothrow { ListenerCallData data; data.system = system; data.block = block; data.begin = begin; data.count = count; data.info = block.type_info; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_add_entity)(data); } private void callRemoveEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, int count) @nogc nothrow { foreach (listener; info.remove_listeners) { System* system = &systems[listener]; callRemoveEntityListener(system, info, block, begin, count); } } private static void callRemoveEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int count) @nogc nothrow { ListenerCallData data; data.system = system; data.block = block; data.begin = begin; data.count = count; data.info = block.type_info; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_remove_entity)(data); } private void callChangeEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int count, ushort[] ch_ids) @nogc nothrow { int i = 0; int j = 0; bool is_ = false; while (1) { if (ch_ids[i] == system.m_optional_components[j]) { is_ = true; break; } else if (ch_ids[i] > system.m_optional_components[j]) { j++; if (j >= system.m_optional_components.length) break; } else { i++; if (i >= ch_ids.length) break; } } if (!is_) return; ListenerCallData data; data.system = system; data.block = block; data.begin = begin; data.count = count; data.info = block.type_info; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_change_entity)(data); } private void updateEntityInfoBlocks(EntityInfo* info) nothrow @nogc { while (info.last_block.added_count) { EntitiesBlock* block = info.update_block; assert(block !is null); if (block.entities_count == info.max_entities) { assert(!block.added_count); block = block.next_block; } assert(!block.prev_block || !block.prev_block.added_count); info.update_block = info.last_block; while (block) { assert(block.added_count.atomicLoad() > 0); updateBlock(block); block = block.next_block; } } assert(info.last_block is info.update_block); } private void updateBlock(EntitiesBlock* block) @nogc nothrow { //if(block.added_count == 0)return; assert(block.added_count != 0); EntityInfo* info = block.type_info; ushort entities_count = block.entities_count; block.entities_count += block.added_count; if (block.entities_count > block.type_info.max_entities) { block.entities_count = block.type_info.max_entities; } block.added_count.atomicStore(cast(ushort) 0); if (info.add_listeners) { callAddEntityListeners(info, block, entities_count, block.entities_count - entities_count); } } private bool updateBlocks() { bool has_work = false; foreach (ref ThreadData thread; threads) { if (thread.infosToUpdatePrev.length) has_work = true; foreach (info; thread.infosToUpdatePrev) { updateEntityInfoBlocks(info); } thread.infosToUpdatePrev.clear(); } return has_work; } private bool removeEntities() nothrow @nogc { bool has_work = false; //foreach (ref ThreadData thread; threads)thread.swapToRemove(); foreach (ref ThreadData thread; threads) { if (thread.entitiesToRemovePrev.length) has_work = true; foreach (id; thread.entitiesToRemovePrev) { __removeEntity(id); } thread.entitiesToRemovePrev.clear(); } return has_work; } private bool updateEvents() nothrow @nogc { bool has_work = false; // bool empty = true; //while (1) //{ //event_manager.swapCurrent(); uint current_index; if (event_manager.current_index == 0) current_index = cast(uint) threads.length; else current_index = 0; foreach (i, event; event_manager.events) { foreach (first_block; event.first_blocks[current_index .. current_index + threads .length]) { EventManager.EventBlock* block = first_block; if (block) has_work = true; // { // has_work = true; // //empty = false; // } while (block) { EventCallData call_data; void* event_pointer = cast(void*) block + event.data_offset; foreach (j; 0 .. block.count) { call_data.event = event_pointer + EntityID.sizeof; EntityID entity_id = *cast(EntityID*)(event_pointer); Entity* entity = id_manager.getEntityPointer(entity_id); if (entity) { call_data.block = getMetaData(entity); call_data.id = call_data.block.entityIndex(entity); call_data.entity = entity; foreach (caller; events[i].callers) { if (call_data.block.type_info.systems[caller.system.m_id] == false || !caller.system.enabled || !caller.system.willExecute) continue; call_data.system_pointer = caller.system.m_system_pointer; (cast(void function(ref EventCallData) nothrow @nogc) caller .callback)(call_data); } } if (events[i].destroy_callback) events[i].destroy_callback(event_pointer); event_pointer += events[i].size + EntityID.sizeof; } block = block.next; } } } // if (empty) // break; // empty = true; //} return has_work; } private void swapData() nothrow @nogc { event_manager.swapCurrent(); foreach (ref ThreadData thread; threads) { thread.swapData(); } } export void commit() { bool has_work = true; while (has_work) { swapData(); has_work = false; has_work |= updateBlocks(); // has_work |= changeEntities(); // has_work |= removeEntities(); has_work |= updateEvents(); id_manager.optimize(); has_work |= updateBlocks(); has_work |= changeEntities(); has_work |= removeEntities(); } event_manager.clearEvents(); } /************************************************************************************************************************ Begin of update process. Should be called before any update is called. */ export void begin() { commit(); m_call_data_allocator.clear(); foreach (ref system; systems) { if (system.enabled && system.m_begin) system.m_execute = (cast(bool function(void*)) system.m_begin)( system.m_system_pointer); } } /************************************************************************************************************************ End of update process. Should be called after every update function. */ export void end() { foreach (ref system; systems) { if (system.enabled && system.m_end) (cast(void function(void*)) system.m_end)(system.m_system_pointer); } commit(); } /*private void getThreadID() nothrow @nogc { if (m_thread_id_func) thread_id = (cast(uint delegate() nothrow @nogc) m_thread_id_func)(); else thread_id = 0; }*/ void sendEvent(Ev)(EntityID id, Ev event) nothrow @nogc { event_manager.sendEvent(id, event, threadID); } void sendEvent(EntityID id, EventRef event) nothrow @nogc { event_manager.sendEvent(id, event, threadID); } private void generateDependencies() nothrow @nogc { foreach (pass_id, pass; passes) { foreach (caller; pass.system_callers) { caller.system = &systems[caller.system_id]; if (caller.exclusion) Mallocator.dispose(caller.exclusion); if (caller.dependencies) Mallocator.dispose(caller.dependencies); } uint index = 0; SystemCaller*[] exclusion; exclusion = (cast(SystemCaller**) alloca((SystemCaller*) .sizeof * pass.system_callers.length))[0 .. pass.system_callers.length]; foreach (caller; pass.system_callers) { index = 0; ///gets systems which are excluding each other out_for: foreach (caller2; pass.system_callers) { if (caller is caller2) continue; ///check for external dependencies foreach (cmp; caller.system.m_readonly_dependencies) { foreach (cmp2; caller2.system.m_writable_dependencies) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } } foreach (cmp; caller.system.m_writable_dependencies) { foreach (cmp2; caller2.system.m_readonly_dependencies) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } foreach (cmp2; caller2.system.m_writable_dependencies) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } } ///check for component dependencies foreach (cmp; caller.system.m_read_only_components) { foreach (cmp2; caller2.system.m_writable_components) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } } foreach (cmp; caller.system.m_writable_components) { foreach (cmp2; caller2.system.m_read_only_components) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } foreach (cmp2; caller2.system.m_writable_components) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } } } if (index > 0) { caller.exclusion = Mallocator.makeArray(exclusion[0 .. index]); /*caller.exclusion = Mallocator.makeArray!(SystemCaller*)(index); caller.exclusion[0..$] = exclusion[0 .. index];*/ } else caller.exclusion = null; } extern (C) static int compareSystems(const void* a, const void* b) { SystemCaller* _a = *cast(SystemCaller**) a; SystemCaller* _b = *cast(SystemCaller**) b; if (_a.system.priority < _b.system.priority) return -1; else if (_a.system.priority == _b.system.priority) { if (_a.exclusion.length < _b.exclusion.length) return -1; else if (_a.exclusion.length == _b.exclusion.length) return 0; else return 1; } else return 1; } qsort(pass.system_callers.array.ptr, pass.system_callers.length, (SystemCaller*).sizeof, &compareSystems); foreach (i, caller; pass.system_callers) caller.job_group.id = cast(uint) i; int priority = int.min; uint beg = 0; index = 0; foreach (i, caller; pass.system_callers) { index = 0; foreach (ex; caller.exclusion) { if (ex.job_group.id > caller.job_group.id) continue; exclusion[index++] = ex; } if (index > 0) { caller.dependencies = Mallocator.makeArray(exclusion[0 .. index]); /*caller.dependencies = Mallocator.makeArray!(SystemCaller*)(index); caller.dependencies[0..$] = exclusion[0 .. index];*/ caller.job_group.dependencies = Mallocator.makeArray!(JobGroup*)(index); foreach (j, dep; caller.dependencies) { caller.job_group.dependencies[j] = &dep.job_group; } } else caller.dependencies = null; } } } const(UpdatePass)* getPass(const(char)[] name) { ushort id = getPassID(name); if (id == ushort.max) return null; return passes[id]; } ushort getPassID(const(char)[] name) { return passes_map.get(name, ushort.max); } /************************************************************************************************************************ Component info; */ struct ComponentInfo { /*export ~this() nothrow @nogc { }*/ ///Component size ushort size; ///Component data alignment ushort alignment; ///Initialization data ubyte[] init_data; ///Pointer to component destroy callback void function(void* pointer) nothrow @nogc destroy_callback; //void* destroy_callback; ///Pointer to component create callback void function(void* pointer) nothrow @nogc create_callback; //void* create_callback; } struct EventCaller { System* system; void* callback; } struct EventCallData { Entity* entity; void* event; void* system_pointer; ushort id; EntitiesBlock* block; } struct EventInfo { ushort size; ushort alignment; void function(void* pointer) nothrow @nogc destroy_callback; EventCaller[] callers; } struct EventRegisterInfo { ushort size; ushort alignment; } /************************************************************************************************************************ Entity type info. */ struct EntityInfo { ///Returns number of blocks uint blocksCount() nothrow @nogc { if (last_block) return last_block.id + 1; else return 0; } ///Returns number of non empty blocks uint nonEmptyBlocksCount() nothrow @nogc { EntitiesBlock* block = last_block; while (1) { if (block is null) return 0; if (block.entities_count == 0) block = block.prev_block; else return block.id + 1; } } EntityInfo* getNewInfoAdd(ushort id) { if (comp_add_info.length <= id) { EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)( gEntityManager.components.length); if (comp_add_info !is null) { //new_infos[0 .. comp_add_info.length] = comp_add_info[0 .. $]; memcpy(new_infos.ptr, comp_add_info.ptr, (EntityInfo*) .sizeof * comp_add_info.length); Mallocator.dispose(comp_add_info); } comp_add_info = new_infos; } if (comp_add_info[id]) return comp_add_info[id]; ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * (components.length + 1)))[0 .. components.length + 1]; uint len = 0; foreach (comp; components) { if (id > comp) { ids[len++] = comp; } else { ids[len++] = id; ids[len++] = comp; foreach (comp2; components[len - 1 .. $]) { ids[len++] = comp2; } break; } } if (id > components[$ - 1]) ids[len++] = id; assert(len == components.length + 1); EntityInfo* new_info = gEntityManager.getEntityInfo(ids); comp_add_info[id] = new_info; return new_info; } EntityInfo* getNewInfoRemove(ushort id) return { /*if (comp_rem_info.length <= id) { EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)( gEntityManager.components.length, &this); if (comp_rem_info !is null) { //new_infos[0 .. comp_rem_info.length] = comp_rem_info[0 .. $]; memcpy(new_infos.ptr, comp_rem_info.ptr, (EntityInfo*) .sizeof * comp_rem_info.length); Mallocator.dispose(comp_rem_info); } comp_rem_info = new_infos; }*/ if (comp_rem_info[id]) return comp_rem_info[id]; ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * (components.length - 1)))[0 .. components.length - 1]; uint len = 0; foreach (comp; components) { if (id != comp) { ids[len++] = comp; } } assert(len != components.length); //if (len == components.length) // return &this; assert(len == components.length - 1); EntityInfo* new_info = gEntityManager.getEntityInfo(ids[0 .. len]); comp_rem_info[id] = new_info; return new_info; } export bool hasComponent(ushort component_id) { if(component_id >= deltas.length || !deltas[component_id])return false; return true; } export ~this() @nogc nothrow { if (components) Mallocator.dispose(components); if (deltas) Mallocator.dispose(deltas); if (tmpl_deltas) Mallocator.dispose(tmpl_deltas); if (comp_add_info) Mallocator.dispose(comp_add_info); if (comp_rem_info) Mallocator.dispose(comp_rem_info); if (systems) Mallocator.dispose(systems); if (add_listeners) Mallocator.dispose(add_listeners); if (remove_listeners) Mallocator.dispose(remove_listeners); if (change_listeners) Mallocator.dispose(change_listeners); } ///entity components ushort[] components; ///deltas in memory for components in EntitiesBlock ushort[] deltas; ///deltas in memory for components in EntityTemplate ushort[] tmpl_deltas; ///cached new infos after adding component EntityInfo*[] comp_add_info; ///cached new infos after removing component EntityInfo*[] comp_rem_info; ///alignment of whole entity ushort alignment; //unused in linear-layout TODO: to remove ///size of entity (with alignment respect) ushort size; ///max number of entities in block ushort max_entities; ///array of systems which will update this entity bool[] systems; ///systems which are listening for added entities ushort[] add_listeners; ///systems which are listening for removed entities ushort[] remove_listeners; ///systems which are listening for changed entities (changed in term of contained components) ushort[] change_listeners; ///pointer to first block/page EntitiesBlock* first_block; ///pointer to last block EntitiesBlock* last_block; ///pointer to last updated block EntitiesBlock* update_block; } /************************************************************************************************************************ Meta data of every block of entities (contained at the begining of block). */ struct EntitiesBlock { ///return pointer to first element in block export void* dataBegin() nothrow @nogc pure return { ushort dif = EntitiesBlock.sizeof; return cast(void*)&this + dif; } export ushort entityIndex(const(Entity)* entity) nothrow @nogc pure { static if (EntityID.sizeof == 8) return cast(ushort)((cast(void*) entity - dataBegin()) >> 3); else return cast(ushort)((cast(void*) entity - dataBegin()) / EntityID.sizeof()); } ///pointer to Entity type info EntityInfo* type_info = null; ///number of entities in block ushort entities_count = 0; ///number of new entities in block shared ushort added_count = 0; //ushort added_count = 0; ///block id ushort id = 0; ///maximum number of entities in block //ushort max_entities = 0; ///pointer to next block/page EntitiesBlock* next_block = null; ///pointer to next block/page EntitiesBlock* prev_block = null; //there is a loooot of data (some kB of memory, pure magic) } /************************************************************************************************************************ Structure with data used to calling System calls. first_block, begin, end, blocks parameters are used to call partial info update */ struct CallData { export void update() nothrow @nogc { //(cast(SytemFuncType) system.m_update)(this); if(!system.m_empty) { // Sys* s = cast(Sys*) system.m_system_pointer; //Sys.EntitiesData input_data; // EntityInfo* info = info; //block.type_info; // System* system = system; EntitiesBlock* block; if (first_block) block = first_block; else block = info.first_block; uint offset = begin; uint last_entity; uint blocks; if (blocks_count) blocks = blocks_count; else blocks = uint.max; SystemCallData call_data = SystemCallData(0, thread_id, job_id, context, info); while (block !is null && blocks > 0) { if (blocks == 1) { if (end) last_entity = end; else last_entity = block.entities_count; } else last_entity = block.entities_count; if (last_entity > 0) { call_data.block = block; call_data.begin = offset; call_data.count = last_entity - offset; assert(last_entity <= block.entities_count && offset < block.entities_count); assert(last_entity > offset); (cast(void function(SystemCallData*) @nogc nothrow) update_func)(&call_data); } block = block.next_block; offset = 0; blocks--; } } else { SystemCallData call_data = SystemCallData(0, thread_id, job_id, context, info); (cast(void function(SystemCallData*) @nogc nothrow) update_func)(&call_data); } } ///system ID. Used to update system pointer after system reload. uint system_id; ///pointer to used system System* system; ///poiner to Entity type info EntityManager.EntityInfo* info; ///delegate function to call (by default it's delegate to onUpdate call) void* context; void* update_func; //void delegate() update_delegate; ///pointer to first block into process (if 0 then first block will be used) EntitiesBlock* first_block; ///number of blocks to update (if 0 then update all) ushort blocks_count; ///index of first element in first block ushort begin; ///index of last element in last block ushort end; ///current thread index uint thread_id; //current job index uint job_id; } /************************************************************************************************************************ Structure with data used to call single System call. first_block, begin, end, blocks parameters are used to call partial info update */ struct SystemCallData { uint count; uint thread_id; uint job_id; void* context; EntityInfo* info; EntitiesBlock* block; uint begin; } struct ListenerCallData { uint count; System* system; EntityInfo* info; EntitiesBlock* block; uint begin; } struct Job { CallData[] callers; uint id; export void execute() nothrow @nogc { //gEntityManager.getThreadID(); foreach (ref caller; callers) { caller.thread_id = gEntityManager.threadID(); caller.job_id = id; caller.update(); } } } struct JobGroup { Job[] jobs; JobGroup*[] dependencies; uint id; SystemCaller* caller; //uint max_jobs; } struct SystemCaller { export ~this() nothrow @nogc { if (dependencies) { Mallocator.dispose(dependencies); } if (exclusion) { Mallocator.dispose(exclusion); } if (job_group.dependencies) Mallocator.dispose(job_group.dependencies); } uint system_id; System* system; Vector!(EntityInfo*) infos; SystemCaller*[] dependencies; SystemCaller*[] exclusion; JobGroup job_group; } struct ThreadData { ref Vector!EntityID entitesToRemove() @nogc nothrow return { return entities_to_remove[data_index]; } ref SimpleVector changeEntitiesList() @nogc nothrow return { return change_entities_list[data_index]; } ref Vector!(EntityInfo*) infosToUpdate() @nogc nothrow return { return infos_to_update[data_index]; } ref Vector!EntityID entitiesToRemovePrev() @nogc nothrow return { return entities_to_remove[1 - data_index]; } ref SimpleVector changeEntitiesListPrev() @nogc nothrow return { return change_entities_list[1 - data_index]; } ref Vector!(EntityInfo*) infosToUpdatePrev() @nogc nothrow return { return infos_to_update[1 - data_index]; } private: void swapData() @nogc nothrow { data_index = cast(ubyte)(1 - data_index); } Vector!EntityID[2] entities_to_remove; SimpleVector[2] change_entities_list; Vector!(EntityInfo*)[2] infos_to_update; ubyte data_index = 0; } export struct UpdatePass { export ~this() nothrow @nogc { assert(name); if (name) Mallocator.dispose(name); foreach (caller; system_callers) { Mallocator.dispose(caller); } system_callers.clear(); } char[] name; Vector!(SystemCaller*) system_callers; } export uint threadID() @nogc nothrow { if (m_thread_id_func) return m_thread_id_func(); else return 0; } ThreadData[] threads; Vector!(UpdatePass*) passes; bool register_state = false; alias SytemFuncType = void function(ref EntityManager.CallData data) nothrow @nogc; ///Single page size. Must be power of two. int m_page_size = 32768; //32768; //4096; ///Number of pages in block. int m_pages_in_block = 128; IDManager id_manager; BlockAllocator allocator; EventManager event_manager; void delegate(JobGroup jobs) nothrow @nogc m_dispatch_jobs; uint delegate() nothrow @nogc m_thread_id_func; HashMap!(ushort[], EntityInfo*) entities_infos; HashMap!(char[], ushort) systems_map; HashMap!(char[], ushort) components_map; HashMap!(const(char)[], ushort) events_map; HashMap!(const(char)[], ushort) passes_map; HashMap!(const(char)[], ushort) external_dependencies_map; Vector!System systems; Vector!ComponentInfo components; Vector!EventInfo events; //Mutex add_mutex; Mutex* entity_block_alloc_mutex; CallDataAllocator m_call_data_allocator; struct CallDataAllocator { struct Block { CallData[256] data; uint allocated = 0; } export ~this() nothrow @nogc { foreach (block; blocks) { Mallocator.dispose(block); } blocks.clear(); } Vector!(Block*) blocks; uint id; void clear() nothrow @nogc { if (blocks.length > 0) foreach (block; blocks[0 .. id + 1]) { block.allocated = 0; } id = 0; //blocks.clear(); } CallData[] getCallData(uint num) nothrow @nogc { if (blocks.length == 0) { Block* new_block = Mallocator.make!Block; blocks.add(new_block); } Block* block = blocks[id]; if (block.allocated + num >= 256) { id++; if (id == blocks.length) { Block* new_block = Mallocator.make!Block; blocks.add(new_block); } block = blocks[id]; } CallData[] ret = block.data[block.allocated .. block.allocated + num]; block.allocated += num; return ret; } } } /*static void fillInputData(Sys)(ref Sys.EntitiesData input_data, EntityManager.EntityInfo* info, EntityManager.EntitiesBlock* block, uint offset, uint entities_count)//, System* system) { //enum ComponentsIndices components_info = getComponentsInfo(); static if (components_info.entites_array) { __traits(getMember, input_data, components_info.entites_array) = ( cast(Entity*) block.dataBegin())[offset .. entities_count]; } static if (hasMember!(Sys.EntitiesData, "length")) { input_data.length = cast(typeof(input_data.length))(entities_count - offset); } // static if (hasMember!(Sys.EntitiesData, "thread_id")) // { // input_data.thread_id = cast(typeof(input_data.thread_id))threadID(); // } ///FIXME: should be "components_info.req()" but it's not compile with GCC static foreach(comp; __traits(Stru)) static foreach (iii, comp_info; components_info.m_req[0 .. components_info.m_req_counter]) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)( cast(void*) block + info.deltas[becsID!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))]))[offset .. entities_count]; } static foreach (iii, comp_info; components_info.m_optional[0 .. components_info.m_optional_counter]) { ushort system_id = becsID!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))); if (system_id < info.deltas.length && info.deltas[system_id] != 0) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)(cast( void*) block + info.deltas[system_id]))[offset .. entities_count]; } } }*/ struct SystemEntityData(Sys) { static struct CompInfo { string name; string type; } static struct ComponentsCounts { //one more than should be to prevent null arrays (zero length arrays) uint readonly = 1; uint mutable = 1; uint excluded = 1; uint optional = 1; uint req = 1; uint readonly_dep = 1; uint writable_dep = 1; } static ComponentsCounts getComponentsCounts() { ComponentsCounts components_counts; bool checkExcludedComponentsSomething(Sys)() { return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string, typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents); } foreach (member; __traits(allMembers, Sys.EntitiesData)) { alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); if (member == "length" || member == "thread_id" || member == "job_id" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { //continue; } else { string name; static if (isArray!MemberType) { // Workaround. This code is never called with: not an array type, but compiler prints an error name = fullName!(Unqual!(ForeachType!MemberType)); } bool is_optional; bool is_read_only; if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) { is_read_only = true; } foreach (att; __traits(getAttributes, __traits(getMember, Sys.EntitiesData, member))) { if (att == "optional") { is_optional = true; } if (att == "readonly") { is_read_only = true; } } if (is_read_only) { components_counts.readonly++; } else { components_counts.mutable++; } if (is_optional) { components_counts.optional++; } else { components_counts.req++; } } } static if (__traits(hasMember, Sys, "ExcludedComponents")) { static if (is(Sys.ExcludedComponents == enum)) { foreach (str; __traits(allMembers, Sys.ExcludedComponents)) { components_counts.excluded++; } } else //static if (checkExcludedComponentsSomething!Sys) { foreach (str; Sys.ExcludedComponents) { components_counts.excluded++; } } } static if (__traits(hasMember, Sys, "ReadOnlyDependencies")) { foreach (str; Sys.ReadOnlyDependencies) { components_counts.readonly_dep++; } } static if (__traits(hasMember, Sys, "WritableDependencies")) { foreach (str; Sys.WritableDependencies) { components_counts.writable_dep++; } } return components_counts; } enum ComponentsCounts component_counts = getComponentsCounts(); static struct ComponentsIndices(ComponentsCounts counts) { CompInfo[] readonly() { return m_readonly[0 .. m_readonly_counter]; } CompInfo[] mutable() { return m_mutable[0 .. m_mutable_counter]; } CompInfo[] excluded() { return m_excluded[0 .. m_excluded_counter]; } CompInfo[] optional() { return m_optional[0 .. m_optional_counter]; } CompInfo[] req() { return m_req[0 .. m_req_counter]; } CompInfo[] readonlyDeps() { return m_readonly_dep[0 .. m_readonly_dep_counter]; } CompInfo[] writableDeps() { return m_writable_dep[0 .. m_writable_dep_counter]; } void addReadonly(CompInfo info) { m_readonly[m_readonly_counter++] = info; } void addMutable(CompInfo info) { m_mutable[m_mutable_counter++] = info; } void addExcluded(CompInfo info) { m_excluded[m_excluded_counter++] = info; } void addOptional(CompInfo info) { m_optional[m_optional_counter++] = info; } void addReq(CompInfo info) { m_req[m_req_counter++] = info; } void addReadonlyDep(CompInfo info) { m_readonly_dep[m_readonly_dep_counter++] = info; } void addWritableDep(CompInfo info) { m_writable_dep[m_writable_dep_counter++] = info; } CompInfo[counts.readonly] m_readonly; CompInfo[counts.mutable] m_mutable; CompInfo[counts.excluded] m_excluded; CompInfo[counts.optional] m_optional; CompInfo[counts.req] m_req; CompInfo[counts.readonly_dep] m_readonly_dep; CompInfo[counts.writable_dep] m_writable_dep; uint m_readonly_counter; uint m_mutable_counter; uint m_excluded_counter; uint m_optional_counter; uint m_req_counter; uint m_readonly_dep_counter; uint m_writable_dep_counter; string entites_array; } enum ComponentsIndices!component_counts components_info = getComponentsInfo(); static ComponentsIndices!component_counts getComponentsInfo() { ComponentsIndices!component_counts components_info; bool checkExcludedComponentsSomething(Sys)() { return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string, typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents); } foreach (member; __traits(allMembers, Sys.EntitiesData)) { alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); if (member == "length" || member == "thread_id" || member == "job_id" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) components_info.entites_array = member; //continue; } else { string name; static if (isArray!MemberType) { // Workaround. This code is never called with: not an array type, but compiler prints an error name = fullName!(Unqual!(ForeachType!MemberType)); } bool is_optional; bool is_read_only; if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) { is_read_only = true; } foreach (att; __traits(getAttributes, __traits(getMember, Sys.EntitiesData, member))) { if (att == "optional") { is_optional = true; } if (att == "readonly") { is_read_only = true; } } if (is_read_only) { components_info.addReadonly(CompInfo(member, name)); } else { components_info.addMutable(CompInfo(member, name)); } if (is_optional) { components_info.addOptional(CompInfo(member, name)); } else { components_info.addReq(CompInfo(member, name)); } } } static if (__traits(hasMember, Sys, "ExcludedComponents")) { static if (is(Sys.ExcludedComponents == enum)) { foreach (str; __traits(allMembers, Sys.ExcludedComponents)) { components_info.addExcluded(CompInfo(str, str)); } } else //static if (checkExcludedComponentsSomething!Sys) { foreach (str; Sys.ExcludedComponents) { components_info.addExcluded(CompInfo(str.stringof, fullName!str)); // components_info.addExcluded(CompInfo(str.stringof, str.stringof)); } } } static if (__traits(hasMember, Sys, "ReadOnlyDependencies")) { foreach (str; Sys.ReadOnlyDependencies) { components_info.addReadonlyDep(CompInfo(str, str)); } } static if (__traits(hasMember, Sys, "WritableDependencies")) { foreach (str; Sys.WritableDependencies) { components_info.addWritableDep(CompInfo(str, str)); } } return components_info; } static void fillInputData(ref Sys.EntitiesData input_data, EntityManager.EntityInfo* info, EntityManager.EntitiesBlock* block, uint offset, uint entities_count)//, System* system) { //enum ComponentsIndices components_info = getComponentsInfo(); uint last_entity = entities_count + offset; static if (components_info.entites_array) { __traits(getMember, input_data, components_info.entites_array) = ( cast(Entity*) block.dataBegin())[offset .. last_entity]; } static if (hasMember!(Sys.EntitiesData, "length")) { input_data.length = cast(typeof(input_data.length))(entities_count); } // static if (hasMember!(Sys.EntitiesData, "thread_id")) // { // input_data.thread_id = cast(typeof(input_data.thread_id))threadID(); // } ///FIXME: should be "components_info.req()" but it's not compile with GCC static foreach (iii, comp_info; components_info.m_req[0 .. components_info.m_req_counter]) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)( cast(void*) block + info.deltas[becsID!(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))))]))[offset .. last_entity]; } static foreach (iii, comp_info; components_info.m_optional[0 .. components_info.m_optional_counter]) { if (becsID!(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))) < info.deltas.length && info.deltas[becsID!(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))))] != 0) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)(cast( void*) block + info.deltas[becsID!(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))))]))[offset .. last_entity]; } } } }