/************************************************************************************************************************
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;
export alias gEM = EntityManager.instance;
export alias gEntityManager = EntityManager.instance;
alias SerializeVector = bubel.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_writable_components)
Mallocator.dispose(system.m_writable_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
{
//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"
|| 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 = 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++;
}
}
}
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;
}
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 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;
}
else
{
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));
}
}
}
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;
}
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_writable_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);
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 \"" ~ Sys.stringof
~ "\" due to non existing dependency.");
else
assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof
~ "\" 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 \"" ~ Sys.stringof
~ "\" due to non existing dependency.");
else
assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof
~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\".");
system.m_writable_dependencies[iii] = comp;
}
ushort sys_id = systems_map.get(cast(char[]) Sys.stringof, ushort.max);
if (sys_id < systems.length)
{
systems[sys_id].disable();
if(systems[sys_id].m_destroy)(cast(void function(void*)) systems[sys_id].m_destroy)(systems[sys_id].m_system_pointer);
if (system.m_create)
(cast(void function(void*)) system.m_create)(system.m_system_pointer);
system.enable();
system.m_id = sys_id;
system.m_name = systems[sys_id].m_name;
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);
}
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;
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.willExecute)
{
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.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;
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 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.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;
}
/************************************************************************************************************************
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)
{
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_) @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)
{
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();
id_manager.optimize();
updateBlocks();
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;
///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
{
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;
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;
}
}
export __gshared EntityManager* instance = null;
}