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