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:
parent
2ddb97e9ce
commit
024356df9b
62 changed files with 5918 additions and 1673 deletions
132
source/bubel/ecs/atomic.d
Normal file
132
source/bubel/ecs/atomic.d
Normal 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;
|
||||
}
|
||||
28
source/bubel/ecs/attributes.d
Normal file
28
source/bubel/ecs/attributes.d
Normal 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";
|
||||
99
source/bubel/ecs/block_allocator.d
Normal file
99
source/bubel/ecs/block_allocator.d
Normal 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
115
source/bubel/ecs/core.d
Normal 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
120
source/bubel/ecs/entity.d
Normal 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
188
source/bubel/ecs/events.d
Normal 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
370
source/bubel/ecs/hash_map.d
Executable 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;
|
||||
}
|
||||
}
|
||||
239
source/bubel/ecs/id_manager.d
Normal file
239
source/bubel/ecs/id_manager.d
Normal 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
3862
source/bubel/ecs/manager.d
Normal file
File diff suppressed because it is too large
Load diff
10
source/bubel/ecs/package.d
Normal file
10
source/bubel/ecs/package.d
Normal 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;
|
||||
77
source/bubel/ecs/simple_vector.d
Normal file
77
source/bubel/ecs/simple_vector.d
Normal 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
363
source/bubel/ecs/std.d
Normal 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
169
source/bubel/ecs/system.d
Normal 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
39
source/bubel/ecs/traits.d
Normal 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
309
source/bubel/ecs/vector.d
Normal 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];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue