Common update:

-added multiple new function to allocate template and add entity
-updated README.md (complete initial version)
-empty components now don't take memory
-fixedd small bug with TestRunner
-added many new tests (HashMap, Vector, EntityMeta, ...)
-added default hashing function to HashMap
-fixed critical bug with adding entities
-fixed small bug with adding entity with remplacement components
-added asserts into code to better bug detection
-small performance improvement for events
-added ComponentRef structure which contain data pointer and componentID
-remove EntityID from Event structure
-now events are handled before removing entiteis
-fixed GDC compilation
-fixed rendering of rotated sprites
-added weapons as separate entities to space ship and others
-added Tower enemy to SpaceInvaders demo
-added Boss to SpaceInvaders demo (boss has four tower attached to it)
-Boss towers shoot multiple bullets upon death
-fixed critical bug with demos switching
-fixed critical bug related to adding/removing entities form inside onAdd/onRemove entity callback
-added animation support
-added particles sypport and particles for firing and explostions, and more
-multithreaded rendering now has same rendering order as singlethreaded
-application automaticallu detect host CPU threads count
-added upgrades to SPaceInvaders demo
-fixed texture memory freeing
-improved documentation
-improved multithreaded performance
-improve shader code
-fixed registration issue
-some additional performance improvements
-added depth and colors to rendering parameters
-jobs now has names corresponding to their systems
-change execute() -> willExecute()
-added EntityMeta structure to speedup getting fetching components form entity
-improved multithreading rendering
-added possibility tio change number of threads runtime
-added bullets collision detection in SpaceInvaders demo
-some CI changes
-added VBO batch rendering (current default, no render mode switch yet)
-fixed camera positioning calculation
-fixed buffer issue with WebGL
-added viewport scalling (at least 300 pixels height). Pixels are scalled if screen is bigger.
-center demos gameplay area
-added fullpage html template for Emscripten build
-added many new sprites to atlas
-fixed critical bug with CPU usage in multithreaded mode
-snake render tile coresponding to body part
-snake is destroyed after collision and emit some particles
-added some functionality to vectors
-fixed documentation issue in Manager.d
-more minor code changes and cleanup
This commit is contained in:
Dawid Masiukiewicz 2020-05-28 16:48:42 +00:00
parent 2ddb97e9ce
commit 024356df9b
62 changed files with 5918 additions and 1673 deletions

132
source/bubel/ecs/atomic.d Normal file
View file

@ -0,0 +1,132 @@
/************************************************************************************************************************
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.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.atomic;
version (Emscripten) version = ECSEmscripten;
version (ECSEmscripten)
{
import std.traits;
enum MemoryOrder
{
acq,
acq_rel,
raw,
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;
extern (C) ubyte emscripten_atomic_load_u8(const void* addr) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_load_u16(const void* addr) @nogc nothrow pure;
extern (C) uint emscripten_atomic_load_u32(const void* addr) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_store_u8(void* addr, ubyte val) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_store_u16(void* addr, ushort val) @nogc nothrow pure;
extern (C) uint emscripten_atomic_store_u32(void* addr, uint val) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_add_u8(void* addr, ubyte val) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_add_u16(void* addr, ushort val) @nogc nothrow pure;
extern (C) uint emscripten_atomic_add_u32(void* addr, uint val) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_sub_u8(void* addr, ubyte val) @nogc nothrow pure;
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;
public pure nothrow @nogc Unqual!T atomicOp(string op, T, V1)(ref shared T val, V1 mod)
{
static if (op == "+=")
{
static if (is(T == byte) || is(T == ubyte))
return cast(Unqual!T)(emscripten_atomic_add_u8(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else static if (is(T == short) || is(T == ushort))
return cast(Unqual!T)(emscripten_atomic_add_u16(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else static if (is(T == int) || is(T == uint))
return cast(Unqual!T)(emscripten_atomic_add_u32(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else
static assert(0);
}
else static if (op == "-=")
{
static if (is(T == byte) || is(T == ubyte))
return cast(Unqual!T)(emscripten_atomic_sub_u8(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else static if (is(T == short) || is(T == ushort))
return cast(Unqual!T)(emscripten_atomic_sub_u16(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else static if (is(T == int) || is(T == uint))
return cast(Unqual!T)(emscripten_atomic_sub_u32(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else
static assert(0);
}
}
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);
else static if (is(UT == short) || is(UT == ushort))
emscripten_atomic_store_u16(cast(void*)&val, cast(UT) newval);
else static if (is(UT == int) || is(UT == uint))
emscripten_atomic_store_u32(cast(void*)&val, cast(UT) newval);
else
static assert(0);
}
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;
else static if (is(UT == byte) || is(UT == ubyte))
return emscripten_atomic_load_u8(cast(const void*)&val);
else static if (is(UT == short) || is(UT == ushort))
return emscripten_atomic_load_u16(cast(const void*)&val);
else static if (is(UT == int) || is(UT == uint))
return emscripten_atomic_load_u32(cast(const void*)&val);
else
static assert(0);
}
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;
else static if (is(UT == byte) || is(UT == ubyte))
return emscripten_atomic_cas_u8(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else static if (is(UT == short) || is(UT == ushort))
return emscripten_atomic_cas_u16(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else static if (is(UT == int) || is(UT == uint))
return emscripten_atomic_cas_u32(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else
static assert(0);
}
}
else
{
public import core.atomic;
}

View file

@ -0,0 +1,28 @@
/************************************************************************************************************************
This module contain attributes used to mark components.
Currently only two attributes are supported:
$(LIST
* 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.
---
Struct EntitiesData
{
Comp1[] cmp; //mutable required component
@readonly @optional Comp2[] cmp2; //optional read only component
@optional const (Comp3)[] cmp3; //same as cmp2
}
---
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.attributes;
///Used to mark optional components for system.
enum optional = "optional";
///Used to mark readonly components for system. "const" can be used insted.
enum readonly = "readonly";

View file

@ -0,0 +1,99 @@
/************************************************************************************************************************
It's internal code.
Module contain memory allocator.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.block_allocator;
import bubel.ecs.manager;
import bubel.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
{
/************************************************************************************************************************
Get new block. Allocator automatically allocate next memory chunk if needed.
*/
void* getBlock() nothrow @nogc
{
if (next_block is null)
allocBlock();
void* ret = next_block;
next_block = *cast(void**) next_block;
return ret;
}
/************************************************************************************************************************
Return block to allocator for further use.
*/
void freeBlock(void* block) nothrow @nogc
{
*cast(void**) block = next_block;
next_block = block;
}
/************************************************************************************************************************
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);
if (next_block is null)
assert(0);
if (pointers is null)
pointers = Mallocator.make!BlockPointers;
if (pointers.numberof >= 32)
{
BlockPointers* new_pointers = Mallocator.make!BlockPointers;
new_pointers.next_pointers = pointers;
pointers = new_pointers;
}
pointers.blocks[pointers.numberof++] = next_block;
foreach (i; 0 .. blocks_in_allocation - 1)
{
void** pointer = cast(void**)(next_block + i * block_size);
*pointer = next_block + (i + 1) * block_size;
}
void** pointer = cast(void**)(next_block + (blocks_in_allocation - 1) * block_size);
*pointer = null;
}
struct BlockPointers
{
void*[32] blocks;
uint numberof = 0;
BlockPointers* next_pointers = null;
}
uint block_size;
uint blocks_in_allocation;
void* next_block = null;
BlockPointers* pointers = null;
}

115
source/bubel/ecs/core.d Normal file
View file

@ -0,0 +1,115 @@
/************************************************************************************************************************
This module contain main templates for user.
There are three structure templates (mixins) which should be added on top of structure:
$(LIST
* System: make system structure
* Component: make component structure
* Event: make event structure
)
---
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.
---
Struct System1
{
mixin!ECS.System;
struct EntitiesData
{
... //used components
}
ExcludedComponets!(Comp1, Comp2);
}
---
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.core;
public import bubel.ecs.manager;
public import bubel.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 = ushort.max;
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 = ushort.max;
ComponentRef ref_() @nogc nothrow return
{
return ComponentRef(&this, component_id);
}
}
/************************************************************************************************************************
Mark structure as Event. Should be added on top of structure (before any data).
*/
mixin template Event()
{
__gshared ushort event_id = ushort.max;
}
/************************************************************************************************************************
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;
}
/************************************************************************************************************************
Make list of readonly ependencies. This template get strings as arguments. Should be added inside System structure.
*/
mixin template ReadOnlyDependencies(T...)
{
alias ReadOnlyDependencies = T;
}
/************************************************************************************************************************
Make list of writable ependencies. This template get strings as arguments. Should be added inside System structure.
*/
mixin template WritableDependencies(T...)
{
alias WritableDependencies = T;
}
}

120
source/bubel/ecs/entity.d Normal file
View file

@ -0,0 +1,120 @@
/************************************************************************************************************************
Entity module.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.entity;
import bubel.ecs.system;
import bubel.ecs.manager;
/************************************************************************************************************************
Entity ID structure. Used as reference to Entity. Pointer to entity should be ever used to store entity reference!
*/
struct EntityID
{
///Index to entity in IDManager.
uint id;
///Counter required for reusing ID.
uint counter;
}
/************************************************************************************************************************
Structure of Entity. It's have only ID, but have full konwlege to get to components memory (If pointer to it is proper).
*/
struct Entity
{
///Entity ID.
EntityID id;
/************************************************************************************************************************
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
{
EntityManager.EntitiesBlock* block = gEM.getMetaData(&this);
EntityManager.EntityInfo* info = block.type_info;
if (T.component_id >= info.deltas.length || info.deltas[T.component_id] == 0)
return null;
return cast(T*)(cast(void*)block + info.deltas[T.component_id] + block.entityIndex(&this) * T.sizeof);
}
bool hasComponent(ushort component_id)
{
EntityManager.EntitiesBlock* block = gEM.getMetaData(&this);
EntityManager.EntityInfo* info = block.type_info;
if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false;
return true;
}
EntityMeta getMeta()
{
EntityMeta meta;
meta.block = gEM.getMetaData(&this);
meta.index = meta.block.entityIndex(&this);
return meta;
}
}
struct EntityMeta
{
EntityManager.EntitiesBlock* block;
ushort index;
T* getComponent(T)() const
{
const (EntityManager.EntityInfo)* info = block.type_info;
if (T.component_id >= info.deltas.length || info.deltas[T.component_id] == 0)
return null;
return cast(T*)(cast(void*)block + block.type_info.deltas[T.component_id] + index * T.sizeof);
}
bool hasComponent(ushort component_id)
{
EntityManager.EntityInfo* info = block.type_info;
if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false;
return true;
}
}
/************************************************************************************************************************
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
{
///Entity components data
ubyte[] entity_data;
///Pointer to entity type info.
EntityManager.EntityInfo* info;
/************************************************************************************************************************
Get specified component. If component doesn't exist function return null. Returned pointer is valid during EntityTemplate lifetime.
*/
T* getComponent(T)() nothrow @nogc
{
if(T.component_id >= info.tmpl_deltas.length || info.tmpl_deltas[T.component_id] == ushort.max)return null;
return cast(T*)(entity_data.ptr + info.tmpl_deltas[T.component_id]);
}
}
/************************************************************************************************************************
ComponentRef contain component ID and pointer to it. It used to add component data to entity.
*/
export struct ComponentRef
{
///pointer to component
void* ptr;
///component index
ushort component_id;
}

188
source/bubel/ecs/events.d Normal file
View file

@ -0,0 +1,188 @@
module bubel.ecs.events;
import bubel.ecs.block_allocator;
import bubel.ecs.entity;
import bubel.ecs.manager;
import bubel.ecs.std;
import std.algorithm.comparison : max;
package struct EventManager
{
void initialize(EntityManager* m) nothrow @nogc
{
allocator = BlockAllocator(events_block_size, events_blocks_in_allocation);
event_block_alloc_mutex = Mallocator.make!Mutex;
event_block_alloc_mutex.initialize();
manager = m;
}
void destroy() nothrow @nogc
{
if (event_block_alloc_mutex)
{
event_block_alloc_mutex.destroy();
Mallocator.dispose(event_block_alloc_mutex);
event_block_alloc_mutex = null;
}
}
export void sendEvent(Ev)(EntityID id, Ev event, uint thread_id = 0) nothrow @nogc
{
uint block_id = current_index + thread_id;
EventData* data = &events[Ev.event_id];
EventBlock* block = data.blocks[block_id];
//EntityManager.EventInfo* info = &manager.events[Ev.event_id];
//event.entity_id = id;
if (block is null)
{
event_block_alloc_mutex.lock();
block = cast(EventBlock*) allocator.getBlock();
event_block_alloc_mutex.unlock();
*block = EventBlock();
data.first_blocks[block_id] = block;
data.blocks[block_id] = block;
}
if (block.count >= data.max_events)
{
event_block_alloc_mutex.lock();
EventBlock* new_block = cast(EventBlock*) allocator.getBlock();
event_block_alloc_mutex.unlock();
*new_block = EventBlock();
block.next = new_block;
block = new_block;
data.blocks[block_id] = block;
}
uint size = Ev.sizeof + EntityID.sizeof;
void* ptr = cast(void*) block + data.data_offset + block.count * size;
*cast(EntityID*)ptr = id;
*cast(Ev*)(ptr + EntityID.sizeof) = event;
//Ev* event_array = cast(Ev*)(cast(void*) block + data.data_offset);
//event_array[block.count] = event;
block.count++;
}
void swapCurrent() nothrow @nogc
{
uint threads_count = cast(uint) manager.threads.length;
if (current_index == 0)
current_index = threads_count;
else
current_index = 0;
foreach (ref event; events)
{
foreach (ref first_block; event.first_blocks[current_index
.. current_index + threads_count])
{
EventBlock* block = first_block;
while (block)
{
EventBlock* to_dispose = block;
block = block.next;
allocator.freeBlock(to_dispose);
}
first_block = null;
}
foreach (ref block; event.blocks[current_index .. current_index + threads_count])
{
block = null;
}
}
}
void clearEvents() nothrow @nogc
{
//uint threads_count = cast(uint)manager.threads.length;
foreach (ref event; events)
{
foreach (ref first_block; event.first_blocks)
{
EventBlock* block = first_block;
while (block)
{
EventBlock* to_dispose = block;
block = block.next;
allocator.freeBlock(to_dispose);
}
first_block = null;
}
foreach (ref block; event.blocks)
{
block = null;
}
}
}
void allocateData(uint threads_count) nothrow @nogc
{
disposeData();
events = Mallocator.makeArray!EventData(manager.events.length);
foreach (i, ref event; events)
{
event.blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2);
event.first_blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2);
event.data_offset = EventBlock.sizeof; //manager.events[i].
manager.alignNum(event.data_offset, manager.events[i].alignment);
uint size = manager.events[i].size + EntityID.sizeof;
event.max_events = cast(ushort)(
(events_block_size - event.data_offset) / size);
}
}
private void disposeData() nothrow @nogc
{
clearEvents();
if (events)
{
foreach (ref event; events)
{
Mallocator.dispose(event.blocks);
Mallocator.dispose(event.first_blocks);
}
Mallocator.dispose(events);
}
allocator.freeMemory();
}
~this() nothrow @nogc
{
disposeData();
}
///Single page size. Must be power of two.
enum events_block_size = 1 << 14;
///Number of pages in block.
enum events_blocks_in_allocation = 128;
struct EventBlock
{
EventBlock* next;
ushort count = 0;
}
struct EventData
{
ushort data_offset;
ushort max_events;
EventBlock*[] blocks;
EventBlock*[] first_blocks;
//EventBlock*[] current_blocks;
}
uint current_index = 0;
EventData[] events;
Mutex* event_block_alloc_mutex;
BlockAllocator allocator;
EntityManager* manager;
}

370
source/bubel/ecs/hash_map.d Executable file
View file

@ -0,0 +1,370 @@
module bubel.ecs.hash_map;
import std.traits;
import bubel.ecs.vector;
import bubel.ecs.traits;
enum doNotInline = "version(DigitalMars)pragma(inline,false);version(LDC)pragma(LDC_never_inline);";
private enum HASH_EMPTY = 0;
private enum HASH_DELETED = 0x1;
private enum HASH_FILLED_MARK = ulong(1) << 8 * ulong.sizeof - 1;
export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc
{
static if (isIntegral!(T))
{
return hashInt(t);
}
else
{
static if(isArray!T)return hashInt(hash((cast(byte*)t.ptr)[0 .. t.length * ForeachType!(T).sizeof]));
else return hashInt(hash((cast(byte*)&t)[0 .. T.sizeof])); //hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts
}
}
ulong hash(byte[] array) nothrow @nogc
{
ulong hash = 0;
foreach(c;array)
hash = c + (hash << 6) + (hash << 16) - hash;
return hash;
}
// Can turn bad hash function to good one
export ulong hashInt(ulong x) nothrow @nogc @safe
{
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
x = x ^ (x >> 31);
return x;
}
struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc)
{
alias Key = KeyPar;
alias Value = ValuePar;
nothrow:
enum rehashFactor = 0.75;
enum size_t getIndexEmptyValue = size_t.max;
static struct KeyVal
{
Key key;
Value value;
}
static struct Bucket
{
ulong hash;
KeyVal keyValue;
}
Vector!Bucket elements; // Length should be always power of 2
size_t length; // Used to compute loadFactor
size_t markerdDeleted;
export void clear()
{
elements.clear();
length = 0;
markerdDeleted = 0;
}
export void reset()
{
elements.reset();
length = 0;
markerdDeleted = 0;
}
export bool isIn(ref Key el)
{
return getIndex(el) != getIndexEmptyValue;
}
export bool isIn(Key el)
{
return getIndex(el) != getIndexEmptyValue;
}
export Value* getPtr()(auto ref Key k)
{
size_t index = getIndex(k);
if (index == getIndexEmptyValue)
{
return null;
}
else
{
return &elements[index].keyValue.value;
}
}
export ref Value get()(auto ref Key k)
{
size_t index = getIndex(k);
assert(index != getIndexEmptyValue);
return elements[index].keyValue.value;
}
deprecated("Use get with second parameter.") export auto ref Value getDefault()(
auto ref Key k, auto ref Value defaultValue)
{
return get(k, defaultValue);
}
export auto ref Value get()(auto ref Key k, auto ref Value defaultValue)
{
size_t index = getIndex(k);
if (index == getIndexEmptyValue)
{
return defaultValue;
}
else
{
return elements[index].keyValue.value;
}
}
export ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue)
{
size_t index = getIndex(k);
if (index == getIndexEmptyValue)
{
add(k, defaultValue);
}
index = getIndex(k);
assert(index != getIndexEmptyValue);
return elements[index].keyValue.value;
}
export bool tryRemove(Key el)
{
size_t index = getIndex(el);
if (index == getIndexEmptyValue)
{
return false;
}
length--;
elements[index].hash = HASH_DELETED;
markerdDeleted++;
return true;
}
export void remove(Key el)
{
bool ok = tryRemove(el);
assert(ok);
}
export ref Value opIndex()(auto ref Key key)
{
return get(key);
}
export void opIndexAssign()(auto ref Value value, auto ref Key key)
{
add(key, value);
}
export void add()(auto ref Key key, auto ref Value value)
{
size_t index = getIndex(key);
if (index != getIndexEmptyValue)
{
elements[index].keyValue.value = value;
return;
}
if (getLoadFactor(length + 1) > rehashFactor
|| getLoadFactor(length + markerdDeleted) > rehashFactor)
{
rehash();
}
length++;
immutable ulong hash = hashFunc(key) | HASH_FILLED_MARK;
immutable size_t rotateMask = elements.length - 1;
index = hash & rotateMask; // Starting point
while (true)
{
Bucket* gr = &elements[index];
if ((gr.hash & HASH_FILLED_MARK) == 0)
{
if (gr.hash == HASH_DELETED)
{
markerdDeleted--;
}
gr.hash = hash;
gr.keyValue.key = key;
gr.keyValue.value = value;
return;
}
index++;
index = index & rotateMask;
}
}
// For debug
//int numA;
//int numB;
export size_t getIndex(Key el)
{
return getIndex(el);
}
export size_t getIndex(ref Key el)
{
mixin(doNotInline);
immutable size_t groupsLength = elements.length;
if (groupsLength == 0)
{
return getIndexEmptyValue;
}
immutable ulong hash = hashFunc(el) | HASH_FILLED_MARK;
immutable size_t rotateMask = groupsLength - 1;
size_t index = hash & rotateMask; // Starting point
//numA++;
while (true)
{
//numB++;
Bucket* gr = &elements[index];
if (gr.hash == hash && gr.keyValue.key == el)
{
return index;
}
if (gr.hash == HASH_EMPTY)
{
return getIndexEmptyValue;
}
index++;
index = index & rotateMask;
}
}
export float getLoadFactor(size_t forElementsNum)
{
if (elements.length == 0)
{
return 1;
}
return cast(float) forElementsNum / (elements.length);
}
export void rehash()()
{
mixin(doNotInline);
// Get all elements
Vector!KeyVal allElements;
allElements.reserve(elements.length);
foreach (ref Bucket el; elements)
{
if ((el.hash & HASH_FILLED_MARK) == 0)
{
el.hash = HASH_EMPTY;
continue;
}
el.hash = HASH_EMPTY;
allElements ~= el.keyValue;
}
if (getLoadFactor(length + 1) > rehashFactor)
{ // Reallocate
elements.length = (elements.length ? elements.length : 4) << 1; // Power of two, initially 8 elements
}
// Insert elements
foreach (i, ref el; allElements)
{
add(el.key, el.value);
}
length = allElements.length;
markerdDeleted = 0;
}
// foreach support
export int opApply(DG)(scope DG dg)
{
int result;
foreach (ref Bucket gr; elements)
{
if ((gr.hash & HASH_FILLED_MARK) == 0)
{
continue;
}
static if (isForeachDelegateWithTypes!(DG, Key))
{
result = dg(gr.keyValue.key);
}
else static if (isForeachDelegateWithTypes!(DG, Value))
{
result = dg(gr.keyValue.value);
}
else static if (isForeachDelegateWithTypes!(DG, Key, Value))
{
result = dg(gr.keyValue.key, gr.keyValue.value);
}
else
{
static assert(0);
}
if (result)
break;
}
return result;
}
export int byKey(scope int delegate(Key k) nothrow dg)
{
int result;
foreach (ref Key k; this)
{
result = dg(k);
if (result)
break;
}
return result;
}
export int byValue(scope int delegate(ref Value k) nothrow dg)
{
int result;
foreach (ref Value v; this)
{
result = dg(v);
if (result)
break;
}
return result;
}
export int byKeyValue(scope int delegate(ref Key k, ref Value v) nothrow dg)
{
int result;
foreach (ref Key k, ref Value v; this)
{
result = dg(k, v);
if (result)
break;
}
return result;
}
}

View file

@ -0,0 +1,239 @@
module bubel.ecs.id_manager;
import bubel.ecs.entity;
import bubel.ecs.std;
import bubel.ecs.vector;
import bubel.ecs.atomic;
import core.stdc.string : memcpy;
/************************************************************************************************************************
IDManager is responsible for assignment and removing IDs. IDs are unique throughtout a whole duration of the program.
*/
struct IDManager
{
/************************************************************************************************************************
Get new ID.
*/
pragma(inline, false) EntityID getNewID() nothrow @nogc
{
int current = m_stack_top.atomicOp!"-="(1) + 1;
if (current < 0)
{
uint add_id = m_last_id.atomicOp!"+="(1) - 1;
if (add_id >= m_ids_array.length)
{
uint local_id = add_id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
if (block_id >= m_blocks_count)
{
add_mutex.lock();
if (block_id >= m_blocks_count)
{
m_blocks[m_blocks_count].alloc();
m_blocks_count++;
}
add_mutex.unlock();
}
}
EntityID id;
id.id = add_id;
id.counter = 0;
return id;
}
uint index = m_free_stack[current];
EntityID id;
id.id = index;
id.counter = m_ids_array[index].counter;
return id;
}
/************************************************************************************************************************
Release ID.
*/
void releaseID(EntityID id) nothrow @nogc
{
optimize();
Data* data = &m_ids_array[id.id];
if (data.counter != id.counter)
return;
data.counter++;
data.entity = null;
m_stack_top.atomicOp!"+="(1);
m_free_stack[m_stack_top] = id.id;
}
/************************************************************************************************************************
Update pointer to entity. The purpose of this function is to ensure that pointer to entity is always correct.
*/
void update(ref Entity entity) nothrow @nogc
{
if (entity.id.id >= cast(uint) m_ids_array.length)
{
uint local_id = entity.id.id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
local_id -= block_id << 16;
m_blocks[block_id].data[local_id].entity = &entity;
}
else //if (entity.id.counter == m_ids_array[entity.id.id].counter)
m_ids_array[entity.id.id].entity = &entity;
}
/************************************************************************************************************************
Returns pointer to entity.
*/
export Entity* getEntityPointer(EntityID id) nothrow @nogc
{
if (id.id >= m_ids_array.length)
{
uint local_id = id.id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
assert(block_id < m_blocks_count);
if (block_id >= m_blocks_count)
return null;
local_id -= block_id << 16;
if (m_blocks[block_id].data[local_id].counter != id.counter)
return null;
return m_blocks[block_id].data[local_id].entity;
}
Data* data = &m_ids_array[id.id];
if (data.counter != id.counter)
return null;
else
return data.entity;
}
/************************************************************************************************************************
Check if entity with specified ID exist.
*/
export bool isExist(EntityID id) nothrow @nogc
{
if (id.id >= m_ids_array.length)
return false;
Data* data = &m_ids_array[id.id];
if (data.entity is null)
return false;
return data.counter == id.counter;
}
/************************************************************************************************************************
Initialize manager.
*/
void initialize() nothrow @nogc
{
m_ids_array = Mallocator.makeArray!Data(65536);
m_free_stack = Mallocator.makeArray!uint(65536);
m_blocks = Mallocator.makeArray!Block(64);
foreach (ref block; m_blocks)
block = Block();
m_blocks_count = 1;
m_blocks[0].alloc();
add_mutex = Mallocator.make!Mutex();
add_mutex.initialize();
getNewID();
optimize();
}
/************************************************************************************************************************
Free manager memory.
*/
void deinitialize() @trusted @nogc nothrow
{
if (m_ids_array)
Mallocator.dispose(m_ids_array);
if (m_free_stack)
Mallocator.dispose(m_free_stack);
if (m_blocks)
{
foreach (ref block; m_blocks)
{
if (block.data)
block.free();
}
Mallocator.dispose(m_blocks);
}
if (add_mutex)
{
add_mutex.destroy();
Mallocator.dispose(add_mutex); //cast(void*)add_mutex); //workaround for compiler bug
add_mutex = null;
}
}
/************************************************************************************************************************
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;
if (m_last_id > m_ids_array.length)
{
uint begin = cast(uint) m_ids_array.length;
Data[] new_array = Mallocator.makeArray!Data(begin + (m_blocks_count << 16));
memcpy(new_array.ptr, m_ids_array.ptr, m_ids_array.length * Data.sizeof);
Mallocator.dispose(m_ids_array);
m_ids_array = new_array;
uint[] new_stack = Mallocator.makeArray!uint(m_ids_array.length);
memcpy(new_stack.ptr, m_free_stack.ptr, m_free_stack.length * uint.sizeof);
Mallocator.dispose(m_free_stack);
m_free_stack = new_stack;
foreach (block; m_blocks[0 .. m_blocks_count - 1])
{
memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof,
block.data.ptr, 65536 * Data.sizeof);
begin += 65536;
}
memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof,
m_blocks[m_blocks_count - 1].data.ptr, (m_last_id - begin) * Data.sizeof);
foreach (ref block; m_blocks[1 .. m_blocks_count])
block.free();
m_blocks_count = 1;
}
}
private static struct Block
{
void alloc() nothrow @nogc
{
assert(data is null);
data = Mallocator.makeArray!Data(65536);
}
void free() nothrow @nogc
{
assert(data !is null);
Mallocator.dispose(data);
data = null;
}
Data[] data = null; //65536
}
private static struct Data
{
uint counter = 0;
//uint next_id = uint.max;
Entity* entity = null;
}
private:
Mutex* add_mutex;
Data[] m_ids_array = null;
uint m_blocks_count = 0;
Block[] m_blocks;
uint[] m_free_stack = null;
align(64) shared uint m_last_id = 0;
align(64) shared int m_stack_top = -1;
}

3862
source/bubel/ecs/manager.d Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
module ecs;
public import bubel.ecs.core;
public import bubel.ecs.entity;
public import bubel.ecs.manager;
public import bubel.ecs.system;
import bubel.ecs.events;
import bubel.ecs.id_manager;
import bubel.ecs.std;

View file

@ -0,0 +1,77 @@
module bubel.ecs.simple_vector;
import bubel.ecs.std;
//import core.stdc.string;
/************************************************************************************************************************
Vector for byte data. Simpler than standard template-based implementation designed for better performance. \
Rellocates 1024 elements at once instead of doubling size.
*/
struct SimpleVector
{
@disable this(this);
///Add element to vector
void add(ubyte el) nothrow @nogc
{
while (used >= data.length)
{
if (data is null)
data = Mallocator.makeArray!ubyte(1024);
else
data = Mallocator.expandArray(data, data.length);
}
data[used++] = el;
}
///Add array of elements to vector
void add(ubyte[] el) nothrow @nogc
{
while (used + el.length >= data.length)
{
if (data is null)
data = Mallocator.makeArray!ubyte(1024);
else
data = Mallocator.expandArray(data, data.length);
}
memcpy(data.ptr + used, el.ptr, el.length);
used += el.length;
}
///Return vector length
size_t length() nothrow @nogc
{
return used;
}
export ref ubyte opIndex(size_t pos) nothrow @nogc
{
return data[pos];
}
export ubyte[] opSlice() nothrow @nogc
{
return data[0 .. used];
}
export ubyte[] opSlice(size_t x, size_t y) nothrow @nogc
{
return data[x .. y];
}
export size_t opDollar() nothrow @nogc
{
return used;
}
///set vector length to 0
void clear() nothrow @nogc
{
used = 0;
}
ubyte[] data = null;
size_t used = 0;
}

363
source/bubel/ecs/std.d Normal file
View file

@ -0,0 +1,363 @@
/************************************************************************************************************************
It's internal code!
This module contain implementation of standard functionality.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.std;
version (Emscripten) version = ECSEmscripten;
import std.traits;
version (ECSEmscripten)
{
extern (C) struct pthread_mutex_t
{
union
{
int[6] __i;
void[6]* __p;
}
}
extern (C) struct pthread_mutexattr_t
{
uint __attr;
}
extern (C) int memcmp(const void* s1, const void* s2, size_t size);
extern (C) void exit(int status) nothrow @nogc;
extern (C) void __assert(const(char)* msg, const(char)* file, uint line)
{
exit(-20);
}
extern (C) void free(void*) @nogc nothrow @system;
extern (C) void* malloc(size_t size) @nogc nothrow @system;
extern (C) void* realloc(void*, size_t size) @nogc nothrow @system;
extern (C) void* memcpy(return void*, scope const void*, size_t size) @nogc nothrow @system;
extern (C) void* memset(void*, int val, size_t size) @nogc nothrow @system;
extern (C) int posix_memalign(void**, size_t, size_t) @nogc nothrow @system;
extern (C) void qsort(void* base, size_t num, size_t size,
int function(const void*, const void*) compar) @nogc nothrow @system;
extern (C) int pthread_mutex_lock(pthread_mutex_t* mutex) @nogc nothrow;
extern (C) int pthread_mutex_trylock(pthread_mutex_t* mutex) @nogc nothrow;
extern (C) int pthread_mutex_unlock(pthread_mutex_t* mutex) @nogc nothrow;
extern (C) void pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type) @nogc nothrow;
extern (C) void pthread_mutexattr_destroy(pthread_mutexattr_t* attr) @nogc nothrow;
extern (C) int pthread_mutexattr_init(pthread_mutexattr_t* attr) @nogc nothrow;
extern (C) int pthread_mutex_destroy(pthread_mutex_t* mutex) @nogc nothrow;
extern (C) int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) @nogc nothrow;
}
else
{
public import core.stdc.stdlib : malloc, free, realloc;
public import core.stdc.string : memcpy, memset;
public import core.stdc.stdlib : qsort;
}
version (ECSEmscripten)
{
}
else version (Windows)
{
import core.sys.windows.windows;
extern (Windows) void* _aligned_malloc(size_t size, size_t alignment) @nogc nothrow @system;
extern (Windows) void _aligned_free(void* ptr) @nogc nothrow @system;
version (LDC)
{
/*extern(Windows) void* __alloca(size_t size) @nogc nothrow @system;
alias alloca = __alloca;*/
extern (Windows) void ___chkstk_ms() @nogc nothrow @system;
extern (Windows) void __chkstk()
{
___chkstk_ms();
}
}
}
else version (Posix)
{
import core.sys.posix.pthread;
import core.sys.posix.stdlib : posix_memalign;
}
version (ECSEmscripten)
{
private const uint max_alloca = 10000;
private __gshared byte[max_alloca] alloca_array;
private __gshared uint alloca_pos = 0;
export extern (C) void* alloca(size_t length) @nogc nothrow
{
if (alloca_pos + length > max_alloca)
alloca_pos = 0;
void* ret = &alloca_array[alloca_pos];
alloca_pos += length;
return ret;
}
//extern(C) void* alloca(size_t size) @nogc nothrow;
/*export extern(C) void* alloca(size_t length) @nogc nothrow
{
return null;
}*/
}
else version (D_BetterC)
{
private const uint max_alloca = 10000;
private __gshared byte[max_alloca] alloca_array;
private uint alloca_pos = 0;
export extern (C) void* __alloca(size_t length) @nogc nothrow
{
if (alloca_pos + length > max_alloca)
alloca_pos = 0;
void* ret = &alloca_array[alloca_pos];
alloca_pos += length;
return ret;
}
alias alloca = __alloca;
version (DigitalMars)
{
export extern (C) float* _memsetFloat(float* p, float value, size_t count) @nogc nothrow
{
float* pstart = p;
float* ptop;
for (ptop = &p[count]; p < ptop; p++)
*p = value;
return pstart;
}
}
version (GNU)
{
extern (C) void __gdc_personality_v0()
{
}
}
}
else
{
public import core.stdc.stdlib : alloca;
}
static struct Mallocator
{
static T[] makeArray(T)(size_t length) nothrow @nogc
{
T[] ret = (cast(T*) malloc(T.sizeof * length))[0 .. length];
static if (__traits(isPOD, T))
{
static immutable T init = T.init;
foreach (i; 0 .. ret.length)
{
memcpy(&ret[i], &init, T.sizeof);
}
}
else
{
static import std.conv;
foreach (i; 0 .. ret.length)
{
std.conv.emplace(&ret[i]);
}
}
return ret;
}
static T[] makeArray(T)(size_t length, T initializer) nothrow @nogc
{
T[] ret = (cast(T*) malloc(T.sizeof * length))[0 .. length];
foreach (ref v; ret)
v = initializer;
return ret;
}
static T[] expandArray(T)(T[] array, size_t length) nothrow @nogc
{
size_t new_length = array.length + length;
return (cast(T*) realloc(array.ptr, T.sizeof * new_length))[0 .. new_length];
}
static T[] makeArray(T)(T[] array) nothrow @nogc
{
T[] ret = (cast(T*) malloc(T.sizeof * array.length))[0 .. array.length]; //Mallocator.makeArray!(T)(array.length);
foreach (i, ref v; ret)
v = array[i];
return ret;
}
static T* make(T, Args...)(Args args)
{
T* ret = cast(T*) malloc(T.sizeof);
static import std.conv;
static if (__traits(isPOD, T))
{
static immutable T init = T.init;
memcpy(ret, &init, T.sizeof);
}
else static if (is(T == struct))
std.conv.emplace(ret, args);
return ret;
}
static void* alignAlloc(size_t length, size_t alignment) nothrow @nogc
{
void* ret;
version (Posix)
posix_memalign(&ret, alignment, length); //ret = aligned_alloc(alignment, length);
else version (Windows)
ret = _aligned_malloc(length, alignment);
else version (ECSEmscripten)
posix_memalign(&ret, alignment, length); //malloc(length);
else
static assert(0, "Unimplemented platform!");
return ret;
}
static void dispose(T)(T object) nothrow @nogc
{
static if (__traits(hasMember, T, "__xdtor"))
object.__xdtor();
else static if (__traits(hasMember, T, "__dtor"))
object.__dtor();
free(cast(void*) object);
}
static void alignDispose(T)(T object)
{
static if (__traits(hasMember, T, "__xdtor"))
object.__xdtor();
else static if (__traits(hasMember, T, "__dtor"))
object.__dtor();
version (Posix)
free(cast(void*) object);
else version (Windows)
_aligned_free(cast(void*) object);
else version (ECSEmscripten)
free(cast(void*) object);
else
static assert(0, "Unimplemented platform!");
}
}
struct Mutex
{
version (ECSEmscripten)
{
void initialize() nothrow @nogc
{
pthread_mutexattr_t attr = void;
//pthread_mutexattr_init(&attr);
//pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(cast(pthread_mutex_t*)&m_handle, &attr);
//pthread_mutexattr_destroy(&attr);
}
void destroy() nothrow @nogc
{
pthread_mutex_destroy(&m_handle);
}
void lock() nothrow @nogc
{
pthread_mutex_lock(&m_handle);
}
void unlock() nothrow @nogc
{
pthread_mutex_unlock(&m_handle);
}
int tryLock() nothrow @nogc
{
return pthread_mutex_trylock(&m_handle) == 0;
}
private pthread_mutex_t m_handle;
}
else version (Windows)
{
void initialize() nothrow @nogc
{
InitializeCriticalSection(cast(CRITICAL_SECTION*)&m_handle);
}
void destroy() nothrow @nogc
{
DeleteCriticalSection(&m_handle);
}
void lock() nothrow @nogc
{
EnterCriticalSection(&m_handle);
}
void unlock() nothrow @nogc
{
LeaveCriticalSection(&m_handle);
}
int tryLock() nothrow @nogc
{
return TryEnterCriticalSection(&m_handle) != 0;
}
CRITICAL_SECTION m_handle;
}
else version (Posix)
{
void initialize() nothrow @nogc
{
pthread_mutexattr_t attr = void;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(cast(pthread_mutex_t*)&m_handle, &attr);
pthread_mutexattr_destroy(&attr);
}
void destroy() nothrow @nogc
{
pthread_mutex_destroy(&m_handle);
}
void lock() nothrow @nogc
{
pthread_mutex_lock(&m_handle);
}
void unlock() nothrow @nogc
{
pthread_mutex_unlock(&m_handle);
}
int tryLock() nothrow @nogc
{
return pthread_mutex_trylock(&m_handle) == 0;
}
private pthread_mutex_t m_handle;
}
else
static assert(0, "unsupported platform!");
}

169
source/bubel/ecs/system.d Normal file
View file

@ -0,0 +1,169 @@
/************************************************************************************************************************
System module.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.system;
import bubel.ecs.entity;
import bubel.ecs.manager;
/************************************************************************************************************************
System contain data required to proper glue EntityManager with Systems.
System callbacks:
$(LIST
* void onUpdate(EntitesData);
* void onEnable() - called inside system.enable() function
* void onDisable() - called inside system.disable() function
* bool onBegin() - called inside manager.begin()
* void onEnd() - called inside manager.end()
* void onCreate() - called after registration inside registerSystem function
* void onDestroy() - called during re-registration and inside manager destructor
* void onAddEntity(EntitesData) - called for every entity which are assigned to system (by adding new entity or changing its components)
* void onRemoveEntity(EntitiesData) - called for every entity removed from system update process
* void onChangeEntity(EntitiesData) - called for every entity which components are changed but it was previously assigned to system
* void handleEvent(Entity*, Event) - called for every event supported by system
)
*/
struct System
{
/************************************************************************************************************************
Check if system is enabled.
*/
export bool enabled() nothrow @nogc
{
return m_enabled;
}
/************************************************************************************************************************
Enable system. If actually it is enabled function do nothing.
*/
export void enable() nothrow @nogc
{
if (!m_enabled && m_enable)
(cast(void function(void*) nothrow @nogc) m_enable)(m_system_pointer);
m_enabled = true;
}
/************************************************************************************************************************
Disable system. If actually it is disabled function do nothing.
*/
export void disable() nothrow @nogc
{
if (m_enabled && m_disable)
(cast(void function(void*) nothrow @nogc) m_disable)(m_system_pointer);
m_enabled = false;
}
/************************************************************************************************************************
Get system priority.
*/
export int priority() nothrow @nogc
{
return m_priority;
}
/************************************************************************************************************************
Get if system will be executed during current frame. Should be checked after manager.begin(). Its value is setted as result of manager.onBegin() callback.
*/
export bool willExecute() nothrow @nogc
{
return m_execute;
}
/************************************************************************************************************************
Get system id.
*/
export ushort id() nothrow @nogc
{
return m_id;
}
/************************************************************************************************************************
Get system name.
*/
export const(char)[] name() nothrow @nogc
{
return cast(const(char)[]) m_name;
}
package:
struct EventCaller
{
ushort id;
void* callback;
}
///should system be executed in current update?
bool m_execute = true;
///system id
ushort m_id;
///is system empty? Empty systems don't update entities, and is called once per update
bool m_empty = false;
///should system update and catch events?
bool m_enabled = false;
///system priority
int m_priority;
///pointer to system implementation
void* m_system_pointer;
///system pass index
int m_pass;
///system name
char[] m_name;
///required components
ushort[] m_components;
///excluded components
ushort[] m_excluded_components;
///optional components
ushort[] m_optional_components;
EntityManager.Job[] jobs;
//System*[] m_dependencies;
ushort[] m_read_only_components;
ushort[] m_writable_components;
ushort[] m_readonly_dependencies;
ushort[] m_writable_dependencies;
EntityManager.SystemCaller* m_any_system_caller;
EventCaller[] m_event_callers;
//void function(ref EntityManager.CallData data) m_update;
void* m_update; ///workaroud for DMD bug with upper line
void delegate() m_update_delegate;
//void function(void* system_pointer) m_enable;
//void function(void* system_pointer) m_disable;
//void function(void* system_pointer) m_create;
//void function(void* system_pointer) m_destroy;
//void function(void* system_pointer) m_begin;
//void function(void* system_pointer) m_end;
void* m_enable;
void* m_disable;
void* m_create;
void* m_destroy;
void* m_begin;
void* m_end;
void* m_add_entity;
void* m_remove_entity;
void* m_change_entity;
//void function(ref EntityManager.CallData data) m_initialize;
//void function(ref EntityManager.CallData data) m_deinitilize;
void* m_initialize;
void* m_deinitilize;
}

39
source/bubel/ecs/traits.d Normal file
View file

@ -0,0 +1,39 @@
module bubel.ecs.traits;
import std.traits;
bool isForeachDelegateWithTypes(DG, Types...)()
{
return is(DG == delegate) && is(ReturnType!DG == int) && is(Parameters!DG == Types);
}
unittest
{
assert(isForeachDelegateWithTypes!(int delegate(int, int), int, int));
assert(isForeachDelegateWithTypes!(int delegate(ref int, ref int), int, int));
assert(!isForeachDelegateWithTypes!(int delegate(double), int, int));
}
/************************************************************************************************************************
Returns index of Component/Entity array in System's EntitiesData struct
*/
static long getIndexOfTypeInEntitiesData(EntitiesData, Type)()
{
alias EntitiesDataFields = Fields!(EntitiesData);
long index = -1;
foreach (fieldNum, FieldType; Fields!(EntitiesData))
{
static if (!isBasicType!(FieldType)) // Not basic type
{
// FieldType should be something like: 'const(SomeComponent)[]'
enum bool entitiesMatches = is(Type == Unqual!(ForeachType!(FieldType)));
static if (entitiesMatches)
{
index = fieldNum;
break;
}
}
}
return index;
}

309
source/bubel/ecs/vector.d Normal file
View file

@ -0,0 +1,309 @@
module bubel.ecs.vector;
import core.bitop;
//import core.stdc.stdlib : free, malloc;
import bubel.ecs.std;
//import core.stdc.string : memcpy, memset;
//import std.algorithm : swap;
import std.conv : emplace;
import std.traits : hasMember, isCopyable, TemplateOf, Unqual;
export @nogc @safe nothrow pure size_t nextPow2(size_t num)
{
return 1 << bsr(num) + 1;
}
export __gshared size_t gVectorsCreated = 0;
export __gshared size_t gVectorsDestroyed = 0;
struct Vector(T)
{
T[] array;
size_t used;
public:
export this()(T t)
{
add(t);
}
export this(X)(X[] t) if (is(Unqual!X == Unqual!T))
{
add(t);
}
/*static if (isCopyable!T) {
export this(this) {
T[] tmp = array[0 .. used];
array = null;
used = 0;
add(tmp);
}
} else {
@disable this(this);
}*/
@disable this(this);
export ~this()
{
clear();
}
export void clear()
{
removeAll();
}
export void removeAll()
{
if (array !is null)
{
/*foreach (ref el; array[0 .. used]) {
destroy(el);
}*/
//freeData(cast(void[]) array);
freeData((cast(void*) array.ptr)[0 .. array.length * T.sizeof]);
gVectorsDestroyed++;
}
array = null;
used = 0;
}
export bool empty() const
{
return (used == 0);
}
export size_t length() const
{
return used;
}
export void length(size_t newLength)
{
if (newLength > used)
{
reserve(newLength);
foreach (ref el; array[used .. newLength])
{
emplace(&el);
}
}
else
{
foreach (ref el; array[newLength .. used])
{
//destroy(el);
static if (__traits(hasMember, T, "__xdtor"))
el.__xdtor();
else static if (__traits(hasMember, T, "__dtor"))
el.__dtor();
}
}
used = newLength;
}
export void reset()
{
used = 0;
}
export void reserve(size_t numElements)
{
if (numElements > array.length)
{
extend(numElements);
}
}
export size_t capacity()
{
return array.length - used;
}
export void extend(size_t newNumOfElements)
{
auto oldArray = manualExtend(array, newNumOfElements);
if (oldArray !is null)
{
freeData(oldArray);
}
}
export @nogc void freeData(void[] data)
{
// 0x0F probably invalid value for pointers and other types
memset(data.ptr, 0x0F, data.length); // Makes bugs show up xD
free(data.ptr);
}
export static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0)
{
if (newNumOfElements == 0)
newNumOfElements = 2;
if (array.length == 0)
gVectorsCreated++;
T[] oldArray = array;
size_t oldSize = oldArray.length * T.sizeof;
size_t newSize = newNumOfElements * T.sizeof;
T* memory = cast(T*) malloc(newSize);
memcpy(cast(void*) memory, cast(void*) oldArray.ptr, oldSize);
array = memory[0 .. newNumOfElements];
//return cast(void[]) oldArray;
return (cast(void*) oldArray.ptr)[0 .. oldArray.length * T.sizeof];
}
export Vector!T copy()()
{
Vector!T duplicate;
duplicate.reserve(used);
duplicate ~= array[0 .. used];
return duplicate;
}
/*export bool canAddWithoutRealloc(uint elemNum = 1)
{
return used + elemNum <= array.length;
}*/
export void add()(T t)
{
if (used >= array.length)
{
extend(nextPow2(used + 1));
}
emplace(&array[used], t);
used++;
}
/// Add element at given position moving others
export void add()(T t, size_t pos)
{
assert(pos <= used);
if (used >= array.length)
{
extend(array.length * 2);
}
foreach_reverse (size_t i; pos .. used)
{
//swap(array[i + 1], array[i]);
array[i + 1] = array[i];
}
emplace(&array[pos], t);
used++;
}
export void add(X)(X[] t) if (is(Unqual!X == Unqual!T))
{
if (used + t.length > array.length)
{
extend(nextPow2(used + t.length));
}
foreach (i; 0 .. t.length)
{
emplace(&array[used + i], t[i]);
}
used += t.length;
}
export void remove(size_t elemNum)
{
//destroy(array[elemNum]);
static if (__traits(hasMember, T, "__xdtor"))
array[elemNum].__xdtor();
else static if (__traits(hasMember, T, "__dtor"))
array[elemNum].__dtor();
//swap(array[elemNum], array[used - 1]);
array[elemNum] = array[used - 1];
used--;
}
export void removeStable()(size_t elemNum)
{
used--;
foreach (i; 0 .. used)
{
array[i] = array[i + 1];
}
}
export bool tryRemoveElement()(T elem)
{
foreach (i, ref el; array[0 .. used])
{
if (el == elem)
{
remove(i);
return true;
}
}
return false;
}
export void removeElement()(T elem)
{
bool ok = tryRemoveElement(elem);
assert(ok, "There is no such an element in vector");
}
export ref T opIndex(size_t elemNum) const
{
//debug assert(elemNum < used, "Range violation [index]");
return *cast(T*)&array.ptr[elemNum];
}
export auto opSlice()
{
return array.ptr[0 .. used];
}
export T[] opSlice(size_t x, size_t y)
{
assert(y <= used);
return array.ptr[x .. y];
}
export size_t opDollar()
{
return used;
}
export void opAssign(X)(X[] slice)
{
reset();
this ~= slice;
}
export void opOpAssign(string op)(T obj)
{
//static assert(op == "~");
add(obj);
}
export void opOpAssign(string op, X)(X[] obj)
{
//static assert(op == "~");
add(obj);
}
export void opIndexAssign()(T obj, size_t elemNum)
{
assert(elemNum < used, "Range viloation");
array[elemNum] = obj;
}
export void opSliceAssign()(T[] obj, size_t a, size_t b)
{
assert(b <= used && a <= b, "Range viloation");
array.ptr[a .. b] = obj;
}
export bool opEquals()(auto ref const Vector!(T) r) const
{
return used == r.used && array.ptr[0 .. used] == r.array.ptr[0 .. r.used];
}
}