From 845f468d59dcfef52c86c4551b15182deac186ef Mon Sep 17 00:00:00 2001 From: Mergul Date: Sat, 21 Mar 2020 13:27:14 +0100 Subject: [PATCH] -added more documentation -remove update() function from entity -currently max supported components count is 64 per type --- source/ecs/atomic.d | 16 +++++--- source/ecs/attributes.d | 17 ++++++++- source/ecs/block_allocator.d | 71 +++++++++++++++++++++++------------- source/ecs/core.d | 47 ++++++++++++++++++++++++ source/ecs/entity.d | 26 +++++++------ source/ecs/events.d | 4 +- source/ecs/id_manager.d | 11 +++++- source/ecs/manager.d | 61 +++++++++++++++++++++++-------- source/ecs/std.d | 4 ++ source/ecs/system.d | 6 ++- 10 files changed, 199 insertions(+), 64 deletions(-) diff --git a/source/ecs/atomic.d b/source/ecs/atomic.d index 1ab78af..91cc3d4 100644 --- a/source/ecs/atomic.d +++ b/source/ecs/atomic.d @@ -1,3 +1,9 @@ +/************************************************************************************************************************ +*It's internal code. Can be used for atomics if emscripten backend will be used. +* +*This module contain atomic operations which include support for emscripten atomics functions. +*Emscripten functions are contained in API similar to druntime. +*/ module ecs.atomic; version(Emscripten)version = ECSEmscripten; @@ -14,7 +20,7 @@ version(ECSEmscripten) rel, seq } - + extern(C) ubyte emscripten_atomic_cas_u8(void *addr, ubyte oldVal, ubyte newVal) @nogc nothrow pure; extern(C) ushort emscripten_atomic_cas_u16(void *addr, ushort oldVal, ushort newVal) @nogc nothrow pure; extern(C) uint emscripten_atomic_cas_u32(void *addr, uint oldVal, uint newVal) @nogc nothrow pure; @@ -35,7 +41,7 @@ version(ECSEmscripten) extern(C) ushort emscripten_atomic_sub_u16(void *addr, ushort val) @nogc nothrow pure; extern(C) uint emscripten_atomic_sub_u32(void *addr, uint val) @nogc nothrow pure; - pure nothrow @nogc Unqual!T atomicOp(string op, T, V1)(ref shared T val, V1 mod) + public pure nothrow @nogc Unqual!T atomicOp(string op, T, V1)(ref shared T val, V1 mod) { static if(op == "+=") { @@ -53,7 +59,7 @@ version(ECSEmscripten) } } - pure nothrow @nogc @trusted void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) + public pure nothrow @nogc @trusted void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) { alias UT = Unqual!T; static if(is(UT == bool) || is(UT == byte) || is(UT == ubyte))emscripten_atomic_store_u8(cast(void*)&val, cast(UT)newval); @@ -62,7 +68,7 @@ version(ECSEmscripten) else static assert(0); } - pure nothrow @nogc @trusted T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) + public pure nothrow @nogc @trusted T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) { alias UT = Unqual!T; static if(is(UT == bool))return emscripten_atomic_load_u8(cast(const void*)&val) != 0; @@ -72,7 +78,7 @@ version(ECSEmscripten) else static assert(0); } - pure nothrow @nogc @trusted bool cas(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T, V1, V2)(T* here, V1 ifThis, V2 writeThis) + public pure nothrow @nogc @trusted bool cas(MemoryOrder succ = MemoryOrder.seq, MemoryOrder fail = MemoryOrder.seq, T, V1, V2)(T* here, V1 ifThis, V2 writeThis) { alias UT = Unqual!T; static if(is(UT == bool))return emscripten_atomic_cas_u8(cast(void*)here, cast(Unqual!T)ifThis, cast(Unqual!T)writeThis) == ifThis; diff --git a/source/ecs/attributes.d b/source/ecs/attributes.d index cb49933..2b24178 100644 --- a/source/ecs/attributes.d +++ b/source/ecs/attributes.d @@ -1,8 +1,21 @@ +/************************************************************************************************************************ +*This module contain attributes used to mark components. +*Currently only two attributes are supported: +* - optional: mark component as optional for system update +* - readonly: mark component access as read only (used for multithreading) +* +*By default components are required and mutable. "const" attribute can be used insteac od readonly mark. +*ex. +*Struct EntitiesData +*{ +* Comp1[] cmp; //mutable required component +* @readonly @optional Comp2[] cmp2; //optional read only component +* @optional const (Comp3)[] cmp3; //same as cmp2 +*} +*/ module ecs.attributes; ///Used to mark optional components for system. enum optional = "optional"; -//Used to mark components excluded from update for system. Enum 'ExcludedComponents' should be used instead of it. -//enum excluded = "excluded"; ///Used to mark readonly components for system. "const" can be used insted. enum readonly = "readonly"; \ No newline at end of file diff --git a/source/ecs/block_allocator.d b/source/ecs/block_allocator.d index edd2275..5db4dab 100644 --- a/source/ecs/block_allocator.d +++ b/source/ecs/block_allocator.d @@ -1,23 +1,23 @@ +/************************************************************************************************************************ +*It's internal code. +* +*Module contain memory allocator. +*/ module ecs.block_allocator; import ecs.manager; import ecs.std; +/************************************************************************************************************************ +*Allocator allocate large blocks and return smaller blocks. When there is no more blocks then next large block is allocated. +*By default freeing memory only returns it to allocator. To free large memory chunks freeMemory function is used. +*freeMemory function return to system memory even if chunk blocks wasn't freed. +*/ struct BlockAllocator { - private uint block_size; - private uint blocks_in_allocation; - - struct BlockPointers - { - void*[32] blocks; - uint numberof = 0; - BlockPointers* next_pointers = null; - } - - void* next_block = null; - BlockPointers* pointers = null; - + /************************************************************************************************************************ + *Get new block. Allocator automatically allocate next memory chunk if needed. + */ void* getBlock() nothrow @nogc { if (next_block is null) @@ -27,13 +27,35 @@ struct BlockAllocator return ret; } + /************************************************************************************************************************ + *Return block to allocator for further use. + */ void freeBlock(void* block) nothrow @nogc { *cast(void**)block = next_block; next_block = block; } - private void allocBlock() nothrow @nogc + /************************************************************************************************************************ + *Free whole used memory. This function return to system all memory chunks even if not every black was freed. + */ + void freeMemory() nothrow @nogc + { + while(pointers) + { + foreach(i;0..pointers.numberof) + { + Mallocator.alignDispose(pointers.blocks[i]); + } + BlockPointers* next_pointers = pointers.next_pointers; + Mallocator.dispose(pointers); + pointers = next_pointers; + } + } + +private: + + void allocBlock() nothrow @nogc { next_block = cast(void*) Mallocator.alignAlloc( block_size * blocks_in_allocation, block_size); @@ -57,17 +79,16 @@ struct BlockAllocator *pointer = null; } - void freeMemory() nothrow @nogc + struct BlockPointers { - while(pointers) - { - foreach(i;0..pointers.numberof) - { - Mallocator.alignDispose(pointers.blocks[i]); - } - BlockPointers* next_pointers = pointers.next_pointers; - Mallocator.dispose(pointers); - pointers = next_pointers; - } + void*[32] blocks; + uint numberof = 0; + BlockPointers* next_pointers = null; } + + uint block_size; + uint blocks_in_allocation; + + void* next_block = null; + BlockPointers* pointers = null; } diff --git a/source/ecs/core.d b/source/ecs/core.d index 956c324..dde39a5 100644 --- a/source/ecs/core.d +++ b/source/ecs/core.d @@ -1,27 +1,74 @@ +/************************************************************************************************************************ +*This module contain main templates for user. +*There are three structure templates (mixins) which should be added on top of structure: +* - System: make system structure +* - Component: make component structure +* - Event: make event structure +* +*ex. +*Struct System1 +*{ +* mixin!ECS.System; +*} +* +*Struct System2 +*{ +* mixin!ECS.System(16);//set number of jobs generated for system by multithreaded update +*} +* +*Struct Component1 +*{ +* mixin!ECS.Component; +*} +* +*Struct Event1 +*{ +* mixin!ECS.Event; +*} +* +*There is also template for generating list of excluded components "ExcludedComponets(T...)". +*This template takes component structure types and making list of excluded components used in "registerSystem" function. +* +*/ module ecs.core; public import ecs.manager; public import ecs.entity; +/************************************************************************************************************************ +*Main struct used as namespace for templates. +*/ static struct ECS { + /************************************************************************************************************************ + *Mark structure as System. Should be added on top of structure (before any data). + */ mixin template System(uint jobs_count = 32) { __gshared ushort system_id; uint __ecs_jobs_count = jobs_count; } + /************************************************************************************************************************ + *Mark structure as Component. Should be added on top of structure (before any data). + */ mixin template Component() { __gshared ushort component_id; } + /************************************************************************************************************************ + *Mark structure as Event. Should be added on top of structure (before any data). + */ mixin template Event() { __gshared ushort event_id; EntityID entity_id; } + /************************************************************************************************************************ + *Make list of excluded components. This template get structure types as argument. Should be added inside System structure. + */ mixin template ExcludedComponents(T...) { alias ExcludedComponents = T; diff --git a/source/ecs/entity.d b/source/ecs/entity.d index 8fbc6e4..699bf61 100644 --- a/source/ecs/entity.d +++ b/source/ecs/entity.d @@ -7,7 +7,7 @@ import ecs.system; import ecs.manager; /************************************************************************************************************************ -*Entity ID structure. +*Entity ID structure. Used as reference to Entity. Pointer to entity should be ever used to store entity reference! */ struct EntityID { @@ -26,15 +26,8 @@ struct Entity EntityID id; /************************************************************************************************************************ - *Update pointer to it in IDManager - */ - void updateID() nothrow @nogc - { - EntityManager.instance.id_manager.update(this); - } - - /************************************************************************************************************************ - *Get specified component. If component doesn't exist function retun null. + *Get specified component. If component doesn't exist function retun null. Pointer is valid only before next "commit()", "begin()" or "end()" + *function is called. Returned pointer shouldn't be used to store reference to entity data. */ T* getComponent(T)() const { @@ -49,10 +42,21 @@ struct Entity uint ind = cast(uint)((cast(void*)&this - block.dataBegin()) / EntityID.sizeof()); return cast(T*)(cast(void*)block + info.deltas[T.component_id] + ind * T.sizeof); } + + /*package void updateID() nothrow @nogc + { + EntityManager.instance.id_manager.update(this); + }*/ } /************************************************************************************************************************ *Entity template structure. +*Entity contain whole information needed to create new entity. Allocating EntityTemplate is considered as more expensive operation +*than adding entity. Whole components memory is stored in EntityTemplate and is copyied to newly added entity. +*If you want to place several entity with small difference in data then you should take pointer to component and change it before every +*entity addition. +*There is no restriction about number of allocated templates. Single template can be used from multiple threads, but if you +*want to changes some components data before add entity (entity position for example) it's better to use multiple templates. */ export struct EntityTemplate { @@ -62,7 +66,7 @@ export struct EntityTemplate EntityManager.EntityInfo* info; /************************************************************************************************************************ - *Get specified component. If component doesn't exist function return null. + *Get specified component. If component doesn't exist function return null. Returned pointer is valid during EntityTemplate lifetime. */ T* getComponent(T)() nothrow @nogc { diff --git a/source/ecs/events.d b/source/ecs/events.d index 6aa773f..7186d13 100644 --- a/source/ecs/events.d +++ b/source/ecs/events.d @@ -7,7 +7,7 @@ import ecs.std; import std.algorithm.comparison : max; -struct EventManager +package struct EventManager { void initialize(EntityManager* m) nothrow @nogc @@ -34,7 +34,7 @@ struct EventManager EventData* data = &events[Ev.event_id]; EventBlock* block = data.blocks[block_id]; - EntityManager.EventInfo* info = &gEM.events[Ev.event_id]; + //EntityManager.EventInfo* info = &gEM.events[Ev.event_id]; event.entity_id = id; if(block is null) diff --git a/source/ecs/id_manager.d b/source/ecs/id_manager.d index 04cc360..475eb94 100644 --- a/source/ecs/id_manager.d +++ b/source/ecs/id_manager.d @@ -19,7 +19,7 @@ struct IDManager { //uint current = m_next_id; //uint next;// = m_ids_array[m_next_id].next_id; -begin: +//begin: //if (current == uint.max)//> m_last_id) int current = m_stack_top.atomicOp!"-="(1) + 1; if(current < 0) @@ -142,6 +142,9 @@ begin: return data.counter == id.counter; } + /************************************************************************************************************************ + *Initialize manager. + */ void initialize() nothrow @nogc { m_ids_array = Mallocator.makeArray!Data(65536); @@ -158,6 +161,9 @@ begin: optimize(); } + /************************************************************************************************************************ + *Free manager memory. + */ void deinitialize() @trusted @nogc nothrow { if(m_ids_array)Mallocator.dispose(m_ids_array); @@ -178,6 +184,9 @@ begin: } } + /************************************************************************************************************************ + *Optimize memory. Must be called if any ID was added and some ID will be removed. + */ void optimize() nothrow @nogc { if(m_stack_top < -1)m_stack_top = -1; diff --git a/source/ecs/manager.d b/source/ecs/manager.d index 853cf14..747e1ed 100644 --- a/source/ecs/manager.d +++ b/source/ecs/manager.d @@ -1,5 +1,5 @@ /************************************************************************************************************************ -*Most important module. +*Most important module. Almost every function is called from EntityManager. */ module ecs.manager; @@ -25,11 +25,35 @@ import ecs.atomic; export alias gEM = EntityManager.instance; export alias gEntityManager = EntityManager.instance; -export alias gEventManager = 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 { @@ -353,7 +377,7 @@ export struct EntityManager static if (!(hasMember!(Sys, "system_id")) || !is(typeof(Sys.system_id) == ushort)) { - static assert(0); //, "Add \"mixin ECS.System;\" in top of system structure;"); //"System should have \"__gshared ushort system_id"); + static assert(0); //, "Add \"mixin ECS.System;\" in top of system structure;"); } static if (!(hasMember!(Sys, "EntitiesData"))) @@ -463,11 +487,11 @@ export struct EntityManager m_req[m_req_counter++] = info; } - CompInfo[32] m_readonly; - CompInfo[32] m_mutable; - CompInfo[32] m_excluded; - CompInfo[32] m_optional; - CompInfo[32] m_req; + CompInfo[64] m_readonly; + CompInfo[64] m_mutable; + CompInfo[64] m_excluded; + CompInfo[64] m_optional; + CompInfo[64] m_req; uint m_readonly_counter; uint m_mutable_counter; @@ -635,7 +659,12 @@ export struct EntityManager foreach (iii, comp_info; components_info.req) { ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); - assert(comp != ushort.max); //, "Can't register system \""~Sys.stringof~"\" due to non existing component \""~comp_info.type~"\"."); + version (betterC) + assert(comp != ushort.max, + "Can't register system 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) @@ -1319,7 +1348,7 @@ export struct EntityManager *Allocate EntityTemplate with all components from entity witch it's data and returns pointer to it. * *Params: - *id = ID of entity from which should be created template + *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) @@ -1756,7 +1785,7 @@ export struct EntityManager Entity* new_entity = cast(Entity*) start; new_entity.id = entity.id; - new_entity.updateID(); + id_manager.update(*new_entity); //new_entity.updateID(); uint ind = block.entityIndex(entity); @@ -1876,7 +1905,7 @@ export struct EntityManager Entity* new_entity = cast(Entity*) start; new_entity.id = entity.id; - new_entity.updateID(); + id_manager.update(*new_entity); //new_entity.updateID(); uint j = 0; uint k = 0; @@ -2001,7 +2030,7 @@ export struct EntityManager *instead of pointer. * *Params: - *tmpl = pointer entity template allocated by EntityManager. + *id = ID of entity to be copyied. */ export Entity* addEntityCopy(EntityID id) { @@ -2044,7 +2073,7 @@ export struct EntityManager //add_mutex.lock_nothrow(); new_entity.id = id_manager.getNewID(); //add_mutex.unlock_nothrow(); - new_entity.updateID(); + id_manager.update(*new_entity); //new_entity.updateID(); return new_entity; } @@ -2094,7 +2123,7 @@ export struct EntityManager //add_mutex.lock_nothrow(); entity.id = id_manager.getNewID(); //add_mutex.unlock_nothrow(); - entity.updateID(); + id_manager.update(*entity); //entity.updateID(); return entity; } @@ -2237,7 +2266,7 @@ export struct EntityManager block = info.last_block; entity.id = *cast(EntityID*)(block.dataBegin() + block.entities_count * EntityID.sizeof); - entity.updateID(); + id_manager.update(*entity); //entity.updateID(); } block = info.last_block; diff --git a/source/ecs/std.d b/source/ecs/std.d index 81eed0a..a3a4f5e 100644 --- a/source/ecs/std.d +++ b/source/ecs/std.d @@ -1,3 +1,7 @@ +/************************************************************************************************************************ +*It's internal code! +*This module contain implementation of standard functionality. +*/ module ecs.std; version(Emscripten)version = ECSEmscripten; diff --git a/source/ecs/system.d b/source/ecs/system.d index 54b3555..35eb996 100644 --- a/source/ecs/system.d +++ b/source/ecs/system.d @@ -18,6 +18,8 @@ import ecs.manager; *
-void onDestroy(); *
-void onAddEntity(EntitesData); *
-void onRemoveEntity(EntitiesData); +*
-void onChangeEntity(EntitiesData); +*
-void handleEvent(Entity*, Event); */ struct System { @@ -82,14 +84,14 @@ struct System return cast(const(char)[]) m_name; } +package: + struct EventCaller { ushort id; void* callback; } -package: - ///should system be executed in current update? bool m_execute = true; ///system id