/************************************************************************************************************************ *Most important module. Almost every function is called from EntityManager. */ module 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 ecs.system; //not ordered as forward reference bug workaround import ecs.block_allocator; import ecs.entity; import ecs.events; import ecs.hash_map; import ecs.id_manager; import ecs.simple_vector; import ecs.std; import ecs.traits; import ecs.vector; import ecs.atomic; export alias gEM = EntityManager.instance; export alias gEntityManager = EntityManager.instance; alias SerializeVector = ecs.vector.Vector!ubyte; /************************************************************************************************************************ *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: EntityManager.instance or gEM 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. */ export struct EntityManager { /************************************************************************************************************************ *Initialize ECS. */ export static void initialize(uint threads_count, uint page_size = 32768, uint block_pages_count = 128) { if (instance is null) { //instance = Mallocator.make!EntityManager(threads_count); instance = Mallocator.make!EntityManager(threads_count, page_size, block_pages_count); with (instance) { 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 (instance is null) return; with (instance) { foreach (ref system; systems) { system.disable(); if (system.m_destroy) (cast(void function(void*)) system.m_destroy)(system.m_system_pointer); if (system.jobs) Mallocator.dispose(system.jobs); if (system.m_read_only_components) Mallocator.dispose(system.m_read_only_components); if (system.m_modified_components) Mallocator.dispose(system.m_modified_components); if (system.m_components) Mallocator.dispose(system.m_components); if (system.m_excluded_components) Mallocator.dispose(system.m_excluded_components); if (system.m_optional_components) Mallocator.dispose(system.m_optional_components); if (system.m_name) Mallocator.dispose(system.m_name); if (system.m_event_callers) Mallocator.dispose(system.m_event_callers); if (system.m_system_pointer) Mallocator.dispose(system.m_system_pointer); } foreach (EntityInfo* info; &entities_infos.byValue) { //if(info.components)Mallocator.dispose(info.components); 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(instance); instance = 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."); System system; system.m_pass = pass; static if (!(hasMember!(Sys, "system_id")) || !is(typeof(Sys.system_id) == ushort)) { static assert(0, "Add \"mixin ECS.System;\" in top of system structure;"); } 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(gEM.getEntity(event.entity_id), *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 = Unqual!(EventParamType).stringof; ushort evt = events_map.get(cast(char[]) EventName, ushort.max); assert(evt != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" due to non existing event \"" ~ EventName ~ "\"."); callers[i].callback = cast(void*)&callEventHandler!(EventParamType); callers[i].id = EventParamType.event_id; i++; } } system.m_event_callers = Mallocator.makeArray(callers[0 .. i]); } static if (__traits(hasMember, Sys, "handleEvent")) { setEventCallers!(Sys)(system); } } static struct CompInfo { string name; string type; } static struct ComponentsCounts { uint readonly; uint mutable; uint excluded; uint optional; uint req; } 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" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { continue; } string name; static if (isArray!MemberType) { // Workaround. This code is never called with: not an array type, but compiler prints an error name = Unqual!(ForeachType!MemberType).stringof; } 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++; } } } 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]; } 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; } 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; uint m_readonly_counter; uint m_mutable_counter; uint m_excluded_counter; uint m_optional_counter; uint m_req_counter; string entites_array; } 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 modified = components_info.mutable.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 (modified > 0) system.m_modified_components = Mallocator.makeArray!ushort(modified); } 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" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) components_info.entites_array = member; continue; } string name; static if (isArray!MemberType) { // Workaround. This code is never called with: not an array type, but compiler prints an error name = Unqual!(ForeachType!MemberType).stringof; } 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, str.stringof)); } } } return components_info; } enum ComponentsIndices!component_counts components_info = getComponentsInfo(); 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 (!(isArray!(MemberType))) static assert(0, "EntitiesData members should be arrays of elements!"); } //enum ComponentsIndices components_info = getComponentsInfo(); allocateSystemComponents!(components_info)(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 \"" ~ Sys.stringof ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" 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 \"" ~ Sys.stringof ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" 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 \"" ~ Sys.stringof ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" 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 \"" ~ Sys.stringof ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" 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 \"" ~ Sys.stringof ~ "\" due to non existing component."); else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_modified_components[iii] = comp; } } static void fillInputData(ref Sys.EntitiesData input_data, EntityInfo* info, 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(); }//*/ static foreach (iii, comp_info; components_info.req) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)( cast(void*) block + info.deltas[system.m_components[iii]]))[offset .. entities_count]; } static foreach (iii, comp_info; components_info.optional) { if (system.m_optional_components[iii] < info.deltas.length && info.deltas[system.m_optional_components[iii]] != 0) { __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name)))*)(cast( void*) block + info.deltas[system.m_optional_components[iii]]))[offset .. entities_count]; } } } /*bool checkOnUpdateParams()() { bool ret = false; foreach (func; __traits(getOverloads, Sys, "onUpdate")) { if ((Parameters!(func)).length == 1 && is(Parameters!(func)[0] == Sys.EntitiesData)) { ret = true; break; } } return ret; }*/ 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 if (!IsEmpty) { static void callUpdate(ref CallData data) { Sys* s = cast(Sys*) data.system.m_system_pointer; Sys.EntitiesData input_data; EntityInfo* info = data.info; //block.type_info; System* system = data.system; EntitiesBlock* block; if (data.first_block) block = data.first_block; else block = info.first_block; uint offset = data.begin; uint entities_count; uint blocks; if (data.blocks) blocks = data.blocks; else blocks = uint.max; while (block !is null && blocks > 0) { if (blocks == 1) { if (data.end) entities_count = data.end; else entities_count = block.entities_count; } else entities_count = block.entities_count; assert(entities_count <= block.entities_count && offset <= block.entities_count); fillInputData(input_data, info, block, offset, entities_count, system); static if (hasMember!(Sys.EntitiesData, "thread_id")) { input_data.thread_id = cast(typeof(input_data.thread_id)) data .thread_id; } //s.onUpdate(input_data); (cast(typeof(&__traits(getOverloads, s, "onUpdate")[OnUpdateOverloadNum])) data.update_delegate)(input_data); block = block.next_block; offset = 0; blocks--; } } } else { static void callUpdate(ref CallData data) { Sys* s = cast(Sys*) data.system.m_system_pointer; Sys.EntitiesData input_data; /*static if (hasMember!(Sys.EntitiesData, "length")) { input_data.length = 0; }//*/ static if (hasMember!(Sys.EntitiesData, "thread_id")) { input_data.thread_id = cast(typeof(input_data.thread_id)) data.thread_id; } (cast(typeof(&__traits(getOverloads, s, "onUpdate")[OnUpdateOverloadNum])) data.update_delegate)(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; fillInputData(input_data, data.block.type_info, data.block, data.begin, data.end, 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; } } } } 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); system.m_system_pointer = cast(void*) Mallocator.make!Sys; system.m_priority = priority; //(cast(Sys*) system.m_system_pointer).__ecsInitialize(); //system.jobs = (cast(Sys*) system.m_system_pointer)._ecs_jobs; system.jobs = Mallocator.makeArray!(Job)((cast(Sys*) system.m_system_pointer) .__ecs_jobs_count); static if (OnUpdateOverloadNum != -1) { Sys* s = cast(Sys*) system.m_system_pointer; system.m_update_delegate = cast(void delegate())&__traits(getOverloads, s, "onUpdate")[OnUpdateOverloadNum]; } genCompList(system, components_map); ushort sys_id = systems_map.get(cast(char[]) Sys.stringof, ushort.max); if (sys_id < systems.length) { system.enable(); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); system.m_id = sys_id; systems[sys_id] = system; } else { system.m_name = Mallocator.makeArray(cast(char[]) Sys.stringof); 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(); } Sys.system_id = system.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 (Sys.system_id >= systems.length) return null; return cast(Sys*) systems[Sys.system_id].m_system_pointer; } export ushort registerPass(const(char)[] name) { UpdatePass* pass = Mallocator.make!UpdatePass; pass.name = Mallocator.makeArray(cast(char[]) name); /*pass.name = Mallocator.makeArray!char(name.length); pass.name[0..$] = name[0..$];*/ passes.add(pass); passes_map.add(name, cast(ushort)(passes.length - 1)); return cast(ushort)(passes.length - 1); } /************************************************************************************************************************ *Register component into EntityManager. */ void registerComponent(Comp)() { ComponentInfo info; static if (!(hasMember!(Comp, "component_id")) || !is(typeof(Comp.component_id) == ushort)) { static assert(0, "Add \"mixin ECS.Component;\" in top of component structure;"); } 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; } 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(); ushort comp_id = components_map.get(cast(char[]) Comp.stringof, ushort.max); if (comp_id < components.length) { Comp.component_id = comp_id; components[comp_id] = info; } else { components.add(info); Comp.component_id = cast(ushort)(components.length - 1); char[] name = Mallocator.makeArray(cast(char[]) Comp.stringof); /*char[] name = Mallocator.makeArray!char(Comp.stringof.length); name[0..$] = Comp.stringof;*/ components_map.add(name, cast(ushort)(components.length - 1)); } } 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); if (event_id < events.length) { Ev.event_id = event_id; } else { events.add(info); Ev.event_id = cast(ushort)(events.length - 1); events_map.add(Ev.stringof, cast(ushort)(events.length - 1)); } } export void callEntitiesFunction(Sys, T)(T func) { 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."); 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."); static assert(__traits(hasMember, Sys, "system_id"), "Sys must be system type."); System* system = getSystem(Sys.system_id); assert(system != null, "System must be registered in EntityManager before any funcion can be called."); foreach (info; system.m_any_system_caller.infos) { CallData data = CallData(system.id, system, info, cast(void delegate()) func); 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.execute) { if (sys.m_empty) { CallData data = CallData(caller.system_id, sys, null, sys.m_update_delegate); data.update(); } else foreach (info; caller.infos) { CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate); 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.execute) { 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; job_id++; } if (sys.m_empty) { tmp_datas.add(CallData(caller.system_id, sys, null, sys.m_update_delegate)); 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 ((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 (reamaining_entities >= 0) { 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_blocks_count * info.max_entities + entities_count + ( first_block.entities_count - first_elem) >= entities_per_job) { CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate, 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; CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate, 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; assert(first_elem <= block.entities_count); first_block = block; } } else { uint last_elem = entities_per_job - entities_count; CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate, 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); } nextJob(); entities_count = 0; goto begin; } else { CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate, 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; } /*export void setJobDispachFunc(void delegate(JobGroup) @nogc nothrow func) nothrow @nogc { m_dispatch_jobs = func; }*/ /************************************************************************************************************************ *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) { 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) { 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) { memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, 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(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.deltas.length && base_tmpl.info.deltas[comp] != ushort.max) //copy data from base component { 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 { memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } } return temp; } /************************************************************************************************************************ *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) { 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*)(instance.components.length); info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(instance.components.length, info); 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_: } 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]; if (system.m_excluded_components) { foreach (id; system.m_excluded_components) { foreach (id2; info.components) { if (id == id2) return; } } } foreach (id; system.m_components) { foreach (i2, id2; info.components) { if (id2 == id) goto is_; } return; is_: } info.systems[system_id] = true; 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.change_entities_list.add(0); data.change_entities_list.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); data.change_entities_list.add((cast(ubyte*)&num)[0 .. uint.sizeof]); data.change_entities_list.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); 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, ind + 1); } } } foreach (comp; new_info.components) { uint comp_size = components[comp].size; 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); } 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, new_block.entities_count + 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, new_block.entities_count + 1, del_ids); } } } new_block.entities_count++; 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] = comp.component_id; } 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); 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); //new_entity.updateID(); 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, ind + 1); } } } foreach (id; new_info.components) //ids[0 .. len]) { void* dst = cast(void*) new_block + new_info.deltas[id] + ( new_block.entities_count) * components[id].size; uint size = components[id].size; if (k >= new_ids.length) { memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } else if (j >= info.components.length || id == new_ids[k]) { memcpy(dst, data_pointers[k], size); k++; } else { assert(id != new_ids[0]); memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } } 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, new_block.entities_count + 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, new_block.entities_count + 1, new_ids); } } } new_block.entities_count++; 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; //Entity* entity = id_manager.getEntityPointer(entity_id); //EntitiesBlock* block = getMetaData(entity); //EntityInfo* info = block.type_info; /*ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * (info.components.length + num)))[0 .. info.components.length + num];*/ ushort[num] new_ids; static foreach (i, comp; Components) { new_ids[i] = comp.component_id; } ThreadData* data = &threads[threadID]; data.change_entities_list.add(cast(ubyte) 1u); data.change_entities_list.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); data.change_entities_list.add((cast(ubyte*)&num)[0 .. uint.sizeof]); data.change_entities_list.add(cast(ubyte[]) new_ids); static foreach (i, comp; comps) { data.change_entities_list.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); } //__addComponents(entity_id, new_ids, pointers); } /************************************************************************************************************************ *Free template memory. * *Params: *template_ = pointer entity template allocated by EntityManager. */ export void freeTemplate(EntityTemplate* template_) { 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) { memcpy(cast(void*) new_block + info.deltas[comp] + components[comp].size * new_id, cast(void*) block + info.deltas[comp] + components[comp].size * index, components[comp].size); if (components[comp].create_callback) { components[comp].create_callback(cast( void*) block + info.deltas[comp] + new_id * components[comp].size); } } if (new_index == 1) threads[threadID].blocks_to_update.add(new_block); Entity* new_entity = cast(Entity*) start; //add_mutex.lock_nothrow(); new_entity.id = id_manager.getNewID(); //add_mutex.unlock_nothrow(); id_manager.update(*new_entity); //new_entity.updateID(); 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) { 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 (i, comp; info.components) { memcpy(cast(void*) block + info.deltas[comp] + components[comp].size * id, tmpl.entity_data.ptr + info.tmpl_deltas[comp], components[comp].size); if (components[comp].create_callback) { components[comp].create_callback( cast(void*) block + info.deltas[comp] + id * components[comp].size); } } if (index == 1) threads[threadID].blocks_to_update.add(block); Entity* entity = cast(Entity*) start; //add_mutex.lock_nothrow(); entity.id = id_manager.getNewID(); //add_mutex.unlock_nothrow(); id_manager.update(*entity); //entity.updateID(); 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; } 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; } 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; } 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; } 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].entities_to_remove.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, 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; 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) { void* src = cast(void*) info.last_block + info.deltas[comp]; void* dst = cast(void*) block + info.deltas[comp]; uint size = components[comp].size; 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); //entity.updateID(); } block = info.last_block; if (block.entities_count == 0) { 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; block.prev_block.added_count = 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 void changeEntities() { foreach (ref thread; threads) { uint index = 0; uint len = cast(uint) thread.change_entities_list.length; void*[32] pointers; // = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; while (index < len) { if (!thread.change_entities_list[index++]) { EntityID id = *cast(EntityID*)&thread.change_entities_list[index]; index += EntityID.sizeof; uint num = *cast(uint*)&thread.change_entities_list[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; ids = (cast(ushort*)&thread.change_entities_list[index])[0 .. num]; index += ushort.sizeof * num; __removeComponents(id, ids); } else { EntityID id = *cast(EntityID*)&thread.change_entities_list[index]; index += EntityID.sizeof; uint num = *cast(uint*)&thread.change_entities_list[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; ids = (cast(ushort*)&thread.change_entities_list[index])[0 .. num]; index += ushort.sizeof * num; //void*[] pointers = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; foreach (i; 0 .. num) { pointers[i] = &thread.change_entities_list[index]; index += components[ids[i]].size; } __addComponents(id, ids, pointers[0 .. num]); } } thread.change_entities_list.clear(); } } private void callAddEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { foreach (listener; info.add_listeners) { System* system = &systems[listener]; callAddEntityListener(system, info, block, begin, end); } } private void callAddEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { ListenerCallData data; data.system = system; data.block = block; data.begin = begin; data.end = end; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_add_entity)(data); } private void callRemoveEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { foreach (listener; info.remove_listeners) { System* system = &systems[listener]; callRemoveEntityListener(system, info, block, begin, end); } } private void callRemoveEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { ListenerCallData data; data.system = system; data.block = block; data.begin = begin; data.end = end; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_remove_entity)(data); } private void callChangeEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int end, 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.end = end; (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_change_entity)(data); } private void updateBlocks() { foreach (ref thread; threads) { foreach (block; thread.blocks_to_update) { 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); } } thread.blocks_to_update.clear(); } } private void removeEntities() nothrow @nogc { foreach (i, ref thread; threads) { foreach (id; thread.entities_to_remove) { __removeEntity(id); } thread.entities_to_remove.clear(); } } private void updateEvents() nothrow @nogc { 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) 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 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); foreach (caller; events[i].callers) { if ( call_data.block.type_info.systems[caller.system.m_id] == false) 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; } block = block.next; } } } if (empty) break; empty = true; } } export void commit() { id_manager.optimize(); updateBlocks(); changeEntities(); updateEvents(); 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); } 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; out_for: foreach (caller2; pass.system_callers) { if (caller is caller2) continue; foreach (cmp; caller.system.m_read_only_components) { foreach (cmp2; caller2.system.m_modified_components) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } } foreach (cmp; caller.system.m_modified_components) { foreach (cmp2; caller2.system.m_read_only_components) { if (cmp == cmp2) { exclusion[index++] = caller2; continue out_for; } } foreach (cmp2; caller2.system.m_modified_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; } } } /************************************************************************************************************************ *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 { EntitiesBlock* block; void* system_pointer; void* event; ushort id; } struct EventInfo { ushort size; ushort alignment; EventCaller[] callers; void function(void* pointer) nothrow @nogc destroy_callback; } /************************************************************************************************************************ *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*)( instance.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 = instance.getEntityInfo(ids); comp_add_info[id] = new_info; return new_info; } EntityInfo* getNewInfoRemove(ushort id) { if (comp_rem_info.length <= id) { EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)( instance.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; } } if (len == components.length) return &this; assert(len == components.length - 1); EntityInfo* new_info = instance.getEntityInfo(ids[0 .. len]); comp_rem_info[id] = new_info; return new_info; } export ~this() @nogc nothrow { if (components) Mallocator.dispose(components); if (deltas) Mallocator.dispose(deltas); if (tmpl_deltas) Mallocator.dispose(tmpl_deltas); 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 ///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; } /************************************************************************************************************************ *Meta data of every block of entities (contained at the begining of block). */ struct EntitiesBlock { ///return distance (in bytes) from begin of block to data ///TODO: probably to remove. It's used by old code if I remeber correctly. /*export uint dataDelta() nothrow @nogc pure { ushort dif = EntitiesBlock.sizeof; alignNum(dif, type_info.alignment); return dif; }*/ ///return pointer to first element in block export void* dataBegin() nothrow @nogc pure { ushort dif = EntitiesBlock.sizeof; return cast(void*)&this + dif; } export ushort entityIndex(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); } ///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 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; ///index of first element in first block ushort begin; ///index of last element in last block ushort end; ///current thread index uint thread_id; } struct ListenerCallData { System* system; EntitiesBlock* block; uint begin; uint end; } struct Job { CallData[] callers; export void execute() nothrow @nogc { //EntityManager.instance.getThreadID(); foreach (ref caller; callers) { caller.thread_id = EntityManager.instance.threadID(); 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 { Vector!EntityID entities_to_remove; //Vector!ubyte change_entities_list; SimpleVector change_entities_list; Vector!(EntitiesBlock*) blocks_to_update; } 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; } /*uint thread_id() @nogc nothrow { if (m_thread_id_func) return (cast(uint delegate() nothrow @nogc) m_thread_id_func)(); else return 0; } void thread_id(uint) @nogc nothrow { }*/ //static uint thread_id; 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; 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; } } export __gshared EntityManager* instance = null; }