Compare commits

..

18 commits

Author SHA1 Message Date
f19bce1a57 Remove ECSEmscripten as Emscripten version is now supported in LDC (+fix compile scripts) 2025-05-14 13:26:10 +02:00
d77317c816 Make it possible to use custom runtime (such as numem) 2025-04-10 14:30:07 +02:00
76a23aa4ed Fix typo 2025-04-10 14:00:20 +02:00
9b75772039 Add missing export visibility attributes 2025-04-10 13:59:21 +02:00
50fa2ce19c Merge branch '2-empty-entity-crash' into 'master'
Fix crash in commit() when all components were removed from entity

Closes #2

See merge request Mergul/bubel-ecs!27
2023-04-27 10:30:22 +00:00
8ac9fa5dbd Fix crash in commit() when all components were removed from entity
Fix crash from issue #2 and add unittest case for it. Also implements general "empty" entity support
2023-04-24 23:46:16 +02:00
beb1837c43 Merge branch 'fixes' into 'master'
Fix unregisterSystem function

See merge request Mergul/bubel-ecs!26
2023-03-09 11:02:53 +00:00
0702b007d1 Fix unregisterSystem function
and add function to get m_system_pointer from System
2023-03-09 11:04:12 +01:00
9bf6f84d45 Update README
Add information about how to use library with DUB package manager
2023-01-18 15:09:25 +01:00
65c6ea4489 Fix demos compilation (using Meson) 2023-01-18 14:53:26 +01:00
ed0603a675 Update copyright notice 2023-01-18 14:40:59 +01:00
6bf8837e8f Update codecov token 2022-11-12 11:41:08 +00:00
6af3028070 Merge branch 'unregister_system' into 'master'
Add unregisterSystem functionality

See merge request Mergul/bubel-ecs!25
2022-11-12 11:15:31 +00:00
c2ba4c632a Fixes
-Assert if callEntitiesFunction is called for system which is not alive
-cleanup and formatting
2022-11-12 12:10:09 +01:00
5f4ba90b3e Add unregisterSystem functionality 2022-11-10 10:54:23 +01:00
881d6d113b Merge branch 'export_id' into 'master'
Export id generated by becsID

See merge request Mergul/bubel-ecs!24
2022-11-10 09:28:34 +00:00
7a614686c8 Export id generated by becsID 2022-11-08 22:05:57 +01:00
6c4109d86c Merge branch 'improve_component_init' into 'master'
Syntax '*ptr = Structure.init' requires opAssign operator, get rid of this...

See merge request Mergul/bubel-ecs!23
2022-10-31 18:35:26 +00:00
25 changed files with 546 additions and 406 deletions

View file

@ -56,7 +56,7 @@ coverage_test_dmd:
- mkdir reports - mkdir reports
- binaries/dmd_unittest_cov - binaries/dmd_unittest_cov
after_script: after_script:
- bash <(curl -s https://codecov.io/bash) -s reports -t 1a0c0169-a721-4085-8252-fed4755dcd8c - bash <(curl -s https://codecov.io/bash) -s reports -t df87b1d8-85f4-4584-96e3-1315d27ec2c5
wasm: wasm:
stage: build_wasm stage: build_wasm

View file

@ -10,6 +10,13 @@ Bubel ECS was tested on Linux, Windows, Android and WASM.
**Currently library is in beta stage so some significant API changes can appear.** **Currently library is in beta stage so some significant API changes can appear.**
Package is available on [DUB package repository](https://code.dlang.org/packages/bubel-ecs). Usage:
```
"dependencies": {
"bubel-ecs": "~>0.1.1"
}
```
## Design ## Design
For core information about Entity-Component-System architectural pattern please read definition described at [Wikipedia](https://en.wikipedia.org/wiki/Entity_component_system). For core information about Entity-Component-System architectural pattern please read definition described at [Wikipedia](https://en.wikipedia.org/wiki/Entity_component_system).

View file

@ -13,7 +13,7 @@ def compile(sources, output):
if file_extension == '.d' and filename != 'package': if file_extension == '.d' and filename != 'package':
files.append(os.path.join(r, file)) files.append(os.path.join(r, file))
ldc_cmd = 'ldc2 ' + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-unknown-wasm -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' ' ldc_cmd = 'ldc2 ' + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-emscripten -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' '
for path in sources: for path in sources:
ldc_cmd += '-I' + path + ' ' ldc_cmd += '-I' + path + ' '
@ -33,7 +33,7 @@ def compile(sources, output):
shared_flags = '' shared_flags = ''
clean = 0 clean = 0
emc_flags = '' emc_flags = ''
ldc_flags = '--d-version=ECSEmscripten ' ldc_flags = ''
import_paths = ['source','tests'] import_paths = ['source','tests']
build_tests = 0 build_tests = 0

View file

@ -1,7 +1,6 @@
import os import os
import ntpath import ntpath
import sys import sys
import imp
def compile(sources, output): def compile(sources, output):
files = [] files = []
@ -14,7 +13,7 @@ def compile(sources, output):
if file_extension == '.d' and filename != 'package': if file_extension == '.d' and filename != 'package':
files.append(os.path.join(r, file)) files.append(os.path.join(r, file))
ldc_cmd = compiler + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-unknown-wasm -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' ' ldc_cmd = compiler + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-emscripten -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' '
for path in sources: for path in sources:
ldc_cmd += '-I' + path + ' ' ldc_cmd += '-I' + path + ' '
@ -41,7 +40,7 @@ only_bc = 0
multi = 0 multi = 0
sources = ['tests', 'source'] sources = ['tests', 'source']
emc_flags = '-s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="[\'png\']" ' emc_flags = '-s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="[\'png\']" '
ldc_flags = '--d-version=ECSEmscripten --d-version=SDL_209 --d-version=BindSDL_Static --d-version=BindSDL_Image --d-version=MM_USE_POSIX_THREADS ' ldc_flags = '--d-version=SDL_209 --d-version=BindSDL_Static --d-version=BindSDL_Image --d-version=MM_USE_POSIX_THREADS '
import_paths = ['external/sources', 'external/imports', 'external/wasm_imports', '../source', 'utils/source', 'simple/source'] import_paths = ['external/sources', 'external/imports', 'external/wasm_imports', '../source', 'utils/source', 'simple/source']
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
@ -121,8 +120,9 @@ emcc_cmd += 'demo.bc '
os.system("mkdir build") os.system("mkdir build")
emscripten = imp.load_source('', os.path.expanduser("~") + '/.emscripten') # emscripten = imp.load_source('', os.path.expanduser("~") + '/.emscripten')
pack_cmd = emscripten.EMSCRIPTEN_ROOT + '/tools/file_packager.py build/assets.data --preload assets --js-output=build/assets.js' # pack_cmd = emscripten.EMSCRIPTEN_ROOT + '/tools/file_packager.py build/assets.data --preload assets --js-output=build/assets.js'
pack_cmd = os.path.expandvars('$EMSDK/upstream/emscripten') + '/tools/file_packager.py build/assets.data --preload assets --js-output=build/assets.js'
print('Packafing files: ' + pack_cmd) print('Packafing files: ' + pack_cmd)
os.system(pack_cmd) os.system(pack_cmd)

View file

@ -27,7 +27,7 @@ executable('BubelECSDemos', [demos_src, external_src],
bindbc_loader_dep, bindbc_loader_dep,
bindbc_sdl_dep, bindbc_sdl_dep,
cimgui_dep, cimgui_dep,
decs_dep, bubel_ecs_dep,
ecs_utils_dep, ecs_utils_dep,
sdl2_dep, sdl2_dep,
sdl2_image_dep, sdl2_image_dep,

View file

@ -5,12 +5,12 @@ subdir('source/ecs_utils')
utils_inc = include_directories('source/') utils_inc = include_directories('source/')
# Dependencies # Dependencies
ecs_utils_lib = library('ecs_utils', utils_src, ecs_utils_lib = library('ECSUtils', utils_src,
include_directories : [demos_inc, external_inc, utils_inc], include_directories : [demos_inc, external_inc, utils_inc],
link_args : link_args, link_args : link_args,
d_module_versions : versions, d_module_versions : versions,
dependencies : [ dependencies : [
decs_dep, bubel_ecs_dep,
bindbc_loader_dep, bindbc_loader_dep,
bindbc_sdl_dep, bindbc_sdl_dep,
] ]

View file

@ -5,7 +5,7 @@
"Michał Masiukiewicz", "Dawid Masiukiewicz" "Michał Masiukiewicz", "Dawid Masiukiewicz"
], ],
"description": "Dynamic Entity Component System", "description": "Dynamic Entity Component System",
"copyright": "Copyright © 2018-2019, Michał Masiukiewicz, Dawid Masiukiewicz", "copyright": "Copyright © 2018-2023, Michał Masiukiewicz, Dawid Masiukiewicz",
"license": "BSD 3-clause", "license": "BSD 3-clause",
"sourcePaths" : ["source\/"], "sourcePaths" : ["source\/"],
"excludedSourceFiles":[ "excludedSourceFiles":[
@ -63,7 +63,7 @@
], ],
"dflags": [ "dflags": [
"-unittest", "-unittest",
"-cov" "-cov=ctfe"
] ]
}, },
{ {

View file

@ -58,7 +58,7 @@ threads_dep = dependency('threads')
d_versions = [] d_versions = []
deps = [] deps = []
if host_machine.cpu_family() == 'wasm32' if host_machine.cpu_family() == 'wasm32'
d_versions += 'ECSEmscripten'
else else
# meson incorectly adds "-s USE_PTHREADS=1" to ldc2 invocation whe pthreads is added as dependency # meson incorectly adds "-s USE_PTHREADS=1" to ldc2 invocation whe pthreads is added as dependency
# add it for non wasm builds # add it for non wasm builds

View file

@ -4,129 +4,10 @@ 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. This module contain atomic operations which include support for emscripten atomics functions.
Emscripten functions are contained in API similar to druntime. Emscripten functions are contained in API similar to druntime.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.atomic; module bubel.ecs.atomic;
version (Emscripten) version = ECSEmscripten; public import core.atomic;
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

@ -17,7 +17,7 @@ Struct EntitiesData
} }
--- ---
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.attributes; module bubel.ecs.attributes;

View file

@ -3,7 +3,7 @@ It's internal code.
Module contain memory allocator. Module contain memory allocator.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.block_allocator; module bubel.ecs.block_allocator;

View file

@ -67,7 +67,7 @@ Struct System1
} }
--- ---
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.core; module bubel.ecs.core;

View file

@ -1,7 +1,7 @@
/************************************************************************************************************************ /************************************************************************************************************************
Entity module. Entity module.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.entity; module bubel.ecs.entity;
@ -44,7 +44,7 @@ struct Entity
return cast(T*)getComponent(becsID!T); return cast(T*)getComponent(becsID!T);
} }
void* getComponent(ushort component_id) const export void* getComponent(ushort component_id) const
{ {
EntityManager.EntitiesBlock* block = gEntityManager.getMetaData(&this); EntityManager.EntitiesBlock* block = gEntityManager.getMetaData(&this);
EntityManager.EntityInfo* info = block.type_info; EntityManager.EntityInfo* info = block.type_info;
@ -54,7 +54,7 @@ struct Entity
return (cast(void*)block + info.deltas[component_id] + block.entityIndex(&this) * gEntityManager.components[component_id].size); return (cast(void*)block + info.deltas[component_id] + block.entityIndex(&this) * gEntityManager.components[component_id].size);
} }
bool hasComponent(ushort component_id) const export bool hasComponent(ushort component_id) const
{ {
EntityManager.EntitiesBlock* block = gEntityManager.getMetaData(&this); EntityManager.EntitiesBlock* block = gEntityManager.getMetaData(&this);
EntityManager.EntityInfo* info = block.type_info; EntityManager.EntityInfo* info = block.type_info;
@ -62,7 +62,7 @@ struct Entity
return true; return true;
} }
EntityMeta getMeta() const export EntityMeta getMeta() const
{ {
EntityMeta meta; EntityMeta meta;
meta.block = gEntityManager.getMetaData(&this); meta.block = gEntityManager.getMetaData(&this);
@ -85,7 +85,7 @@ struct EntityMeta
return cast(T*)getComponent(becsID!T); return cast(T*)getComponent(becsID!T);
} }
void* getComponent(ushort component_id) const export void* getComponent(ushort component_id) const
{ {
const (EntityManager.EntityInfo)* info = block.type_info; const (EntityManager.EntityInfo)* info = block.type_info;
@ -95,7 +95,7 @@ struct EntityMeta
return (cast(void*)block + info.deltas[component_id] + index * gEntityManager.components[component_id].size); return (cast(void*)block + info.deltas[component_id] + index * gEntityManager.components[component_id].size);
} }
bool hasComponent(ushort component_id) const export bool hasComponent(ushort component_id) const
{ {
const EntityManager.EntityInfo* info = block.type_info; const EntityManager.EntityInfo* info = block.type_info;
if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false; if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false;
@ -133,7 +133,7 @@ export struct EntityTemplate
/************************************************************************************************************************ /************************************************************************************************************************
Get specified component. If component doesn't exist function return null. Returned pointer is valid during EntityTemplate lifetime. Get specified component. If component doesn't exist function return null. Returned pointer is valid during EntityTemplate lifetime.
*/ */
void* getComponent(ushort component_id) const nothrow @nogc export void* getComponent(ushort component_id) const nothrow @nogc
{ {
if(component_id >= info.tmpl_deltas.length || info.tmpl_deltas[component_id] == ushort.max)return null; if(component_id >= info.tmpl_deltas.length || info.tmpl_deltas[component_id] == ushort.max)return null;
return cast(void*)(entity_data.ptr + info.tmpl_deltas[component_id]); return cast(void*)(entity_data.ptr + info.tmpl_deltas[component_id]);

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.events; module bubel.ecs.events;

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.hash_map; module bubel.ecs.hash_map;
@ -28,7 +28,7 @@ export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc
} }
} }
ulong hash(byte[] array) nothrow @nogc export ulong hash(byte[] array) nothrow @nogc
{ {
ulong hash = 0; ulong hash = 0;
@ -72,31 +72,31 @@ nothrow:
size_t length; // Used to compute loadFactor size_t length; // Used to compute loadFactor
size_t markerdDeleted; size_t markerdDeleted;
void clear() export void clear()
{ {
elements.clear(); elements.clear();
length = 0; length = 0;
markerdDeleted = 0; markerdDeleted = 0;
} }
void reset() export void reset()
{ {
elements.reset(); elements.reset();
length = 0; length = 0;
markerdDeleted = 0; markerdDeleted = 0;
} }
bool isIn(ref Key el) export bool isIn(ref Key el)
{ {
return getIndex(el) != getIndexEmptyValue; return getIndex(el) != getIndexEmptyValue;
} }
bool isIn(Key el) export bool isIn(Key el)
{ {
return getIndex(el) != getIndexEmptyValue; return getIndex(el) != getIndexEmptyValue;
} }
Value* getPtr()(auto ref Key k) export Value* getPtr()(auto ref Key k)
{ {
size_t index = getIndex(k); size_t index = getIndex(k);
if (index == getIndexEmptyValue) if (index == getIndexEmptyValue)
@ -109,20 +109,20 @@ nothrow:
} }
} }
ref Value get()(auto ref Key k) export ref Value get()(auto ref Key k)
{ {
size_t index = getIndex(k); size_t index = getIndex(k);
assert(index != getIndexEmptyValue); assert(index != getIndexEmptyValue);
return elements[index].keyValue.value; return elements[index].keyValue.value;
} }
deprecated("Use get with second parameter.") auto ref Value getDefault()( deprecated("Use get with second parameter.") export auto ref Value getDefault()(
auto ref Key k, auto ref Value defaultValue) auto ref Key k, auto ref Value defaultValue)
{ {
return get(k, defaultValue); return get(k, defaultValue);
} }
auto ref Value get()(auto ref Key k, auto ref Value defaultValue) export auto ref Value get()(auto ref Key k, auto ref Value defaultValue)
{ {
size_t index = getIndex(k); size_t index = getIndex(k);
if (index == getIndexEmptyValue) if (index == getIndexEmptyValue)
@ -135,7 +135,7 @@ nothrow:
} }
} }
ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue) export ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue)
{ {
size_t index = getIndex(k); size_t index = getIndex(k);
if (index == getIndexEmptyValue) if (index == getIndexEmptyValue)
@ -148,7 +148,7 @@ nothrow:
} }
bool tryRemove(Key el) export bool tryRemove(Key el)
{ {
size_t index = getIndex(el); size_t index = getIndex(el);
if (index == getIndexEmptyValue) if (index == getIndexEmptyValue)
@ -161,23 +161,23 @@ nothrow:
return true; return true;
} }
void remove(Key el) export void remove(Key el)
{ {
bool ok = tryRemove(el); bool ok = tryRemove(el);
assert(ok); assert(ok);
} }
ref Value opIndex()(auto ref Key key) export ref Value opIndex()(auto ref Key key)
{ {
return get(key); return get(key);
} }
void opIndexAssign()(auto ref Value value, auto ref Key key) export void opIndexAssign()(auto ref Value value, auto ref Key key)
{ {
add(key, value); add(key, value);
} }
void add()(auto ref Key key, auto ref Value value) export void add()(auto ref Key key, auto ref Value value)
{ {
size_t index = getIndex(key); size_t index = getIndex(key);
if (index != getIndexEmptyValue) if (index != getIndexEmptyValue)
@ -221,12 +221,12 @@ nothrow:
//int numA; //int numA;
//int numB; //int numB;
size_t getIndex(Key el) export size_t getIndex(Key el)
{ {
return getIndex(el); return getIndex(el);
} }
size_t getIndex(ref Key el) export size_t getIndex(ref Key el)
{ {
mixin(doNotInline); mixin(doNotInline);
@ -260,7 +260,7 @@ nothrow:
} }
float getLoadFactor(size_t forElementsNum) export float getLoadFactor(size_t forElementsNum)
{ {
if (elements.length == 0) if (elements.length == 0)
{ {
@ -269,7 +269,7 @@ nothrow:
return cast(float) forElementsNum / (elements.length); return cast(float) forElementsNum / (elements.length);
} }
void rehash()() export void rehash()()
{ {
mixin(doNotInline); mixin(doNotInline);
// Get all elements // Get all elements
@ -303,7 +303,7 @@ nothrow:
} }
// foreach support // foreach support
int opApply(DG)(scope DG dg) export int opApply(DG)(scope DG dg)
{ {
int result; int result;
foreach (ref Bucket gr; elements) foreach (ref Bucket gr; elements)
@ -336,36 +336,36 @@ nothrow:
return result; return result;
} }
int byKey(scope int delegate(ref Key k) dg) export int byKey(scope int delegate(ref Key k) dg)
{ {
int result; int result;
foreach (ref Key k; this) foreach (ref Key k; this)
{ {
result = (cast(int delegate(ref Key k) nothrow)dg)(k); result = (cast(int delegate(ref Key k) nothrow @nogc)dg)(k);
if (result) if (result)
break; break;
} }
return result; return result;
} }
int byValue(scope int delegate(ref Value v) dg) export int byValue(scope int delegate(ref Value v) dg)
{ {
int result; int result;
foreach (ref Value v; this) foreach (ref Value v; this)
{ {
result = (cast(int delegate(ref Value v) nothrow)dg)(v); result = (cast(int delegate(ref Value v) nothrow @nogc)dg)(v);
if (result) if (result)
break; break;
} }
return result; return result;
} }
int byKeyValue(scope int delegate(ref Key k, ref Value v) dg) export int byKeyValue(scope int delegate(ref Key k, ref Value v) dg)
{ {
int result; int result;
foreach (ref Key k, ref Value v; this) foreach (ref Key k, ref Value v; this)
{ {
result = (cast(int delegate(ref Key k, ref Value v) nothrow)dg)(k, v); result = (cast(int delegate(ref Key k, ref Value v) nothrow @nogc)dg)(k, v);
if (result) if (result)
break; break;
} }

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.id_manager; module bubel.ecs.id_manager;

View file

@ -1,7 +1,7 @@
/************************************************************************************************************************ /************************************************************************************************************************
Most important module. Almost every function is called from EntityManager. Most important module. Almost every function is called from EntityManager.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.manager; module bubel.ecs.manager;
@ -10,10 +10,6 @@ import std.algorithm : max;
import std.conv : to; import std.conv : to;
import std.traits; import std.traits;
//import core.atomic;
//import core.stdc.stdlib : qsort;
//import core.stdc.string;
import bubel.ecs.system; //not ordered as forward reference bug workaround import bubel.ecs.system; //not ordered as forward reference bug workaround
import bubel.ecs.block_allocator; import bubel.ecs.block_allocator;
import bubel.ecs.entity; import bubel.ecs.entity;
@ -31,6 +27,8 @@ alias SerializeVector = bubel.ecs.vector.Vector!ubyte;
///Global EntityManager used for everything. ///Global EntityManager used for everything.
export __gshared EntityManager* gEntityManager = null; export __gshared EntityManager* gEntityManager = null;
version(D_BetterC) version = NoDRuntime;
/************************************************************************************************************************ /************************************************************************************************************************
Entity manager is responsible for everything. Entity manager is responsible for everything.
@ -69,11 +67,10 @@ export struct EntityManager
Initialize ECS. Initialize ECS.
*/ */
export static void initialize(uint threads_count = 1, uint page_size = 32768, export static void initialize(uint threads_count = 1, uint page_size = 32768,
uint block_pages_count = 128) uint block_pages_count = 128)
{ {
if (gEntityManager is null) if (gEntityManager is null)
{ {
//gEntityManager = Mallocator.make!EntityManager(threads_count);
gEntityManager = Mallocator.make!EntityManager(threads_count, page_size, block_pages_count); gEntityManager = Mallocator.make!EntityManager(threads_count, page_size, block_pages_count);
with (gEntityManager) with (gEntityManager)
@ -81,7 +78,7 @@ export struct EntityManager
UpdatePass* pass = Mallocator.make!UpdatePass; UpdatePass* pass = Mallocator.make!UpdatePass;
pass.name = Mallocator.makeArray(cast(char[]) "update"); pass.name = Mallocator.makeArray(cast(char[]) "update");
passes.add(pass); passes.add(pass);
passes_map.add(cast(string) pass.name, cast(ushort)(passes.length - 1)); passes_map.add(cast(const(char)[]) pass.name, cast(ushort)(passes.length - 1));
} }
} }
} }
@ -89,7 +86,7 @@ export struct EntityManager
/************************************************************************************************************************ /************************************************************************************************************************
Deinitialize and destroy ECS. This function release whole memory. Deinitialize and destroy ECS. This function release whole memory.
*/ */
export static void destroy() export static void destroy() nothrow @nogc
{ {
if (gEntityManager is null) if (gEntityManager is null)
return; return;
@ -172,6 +169,8 @@ export struct EntityManager
foreach (ref system; systems) foreach (ref system; systems)
{ {
if (system.isAlive() == false)
continue;
if (system.m_empty) if (system.m_empty)
{ {
if (system.m_update) if (system.m_update)
@ -181,7 +180,7 @@ export struct EntityManager
if (system.m_update is null) if (system.m_update is null)
{ {
if (system.m_add_entity || system.m_remove_entity if (system.m_add_entity || system.m_remove_entity
|| system.m_change_entity || system.m_event_callers.length) || system.m_change_entity || system.m_event_callers.length)
{ {
foreach (info; &entities_infos.byValue) foreach (info; &entities_infos.byValue)
{ {
@ -236,6 +235,8 @@ export struct EntityManager
foreach (ref system; systems) foreach (ref system; systems)
{ {
if (system.isAlive() == false)
continue;
foreach (caller; system.m_event_callers) foreach (caller; system.m_event_callers)
{ {
event_callers[caller.id]++; event_callers[caller.id]++;
@ -252,6 +253,8 @@ export struct EntityManager
foreach (ref system; systems) foreach (ref system; systems)
{ {
if (system.isAlive() == false)
continue;
foreach (caller; system.m_event_callers) foreach (caller; system.m_event_callers)
{ {
events[caller.id].callers[event_callers[caller.id]].callback = caller.callback; events[caller.id].callers[event_callers[caller.id]].callback = caller.callback;
@ -275,7 +278,7 @@ export struct EntityManager
foreach (ref event; events) foreach (ref event; events)
{ {
qsort(event.callers.ptr, event.callers.length, qsort(event.callers.ptr, event.callers.length,
EventCaller.sizeof, &comapreEventCaller); EventCaller.sizeof, &comapreEventCaller);
} }
//qsort(event_callers.ptr, event_callers.length, EventInfo.sizeof, &compareUShorts); //qsort(event_callers.ptr, event_callers.length, EventInfo.sizeof, &compareUShorts);
@ -328,13 +331,37 @@ export struct EntityManager
allocator.freeMemory(); allocator.freeMemory();
} }
/************************************************************************************************************************
Unregister given system form EntityManager.
*/
void unregisterSystem(Sys)()
{
ushort system_id = becsID!Sys;
System* system = getSystem(system_id);
unregisterSystem(system);
}
/************************************************************************************************************************
Unregister given system form EntityManager.
*/
export void unregisterSystem(System* system) nothrow @nogc
{
assert(register_state, "unregisterSystem must be called between beginRegister() and endRegister().");
assert(system !is null, "System was not registered");
assert(system.isAlive, "System already unregistered");
//disable, destroy and dispose user created system but keep name and other data
system.destroySystemData();
}
/************************************************************************************************************************ /************************************************************************************************************************
Same as "void registerSystem(Sys)(int priority, int pass = 0)" but use pass name instead of id. Same as "void registerSystem(Sys)(int priority, int pass = 0)" but use pass name instead of id.
*/ */
void registerSystem(Sys)(int priority, const(char)[] pass_name) void registerSystem(Sys)(int priority, const(char)[] pass_name)
{ {
ushort pass = passes_map.get(pass_name, ushort.max); ushort pass = passes_map.get(pass_name, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(pass != ushort.max, "Update pass doesn't exist."); assert(pass != ushort.max, "Update pass doesn't exist.");
else else
assert(pass != ushort.max, "Update pass (Name " ~ pass_name ~ ") doesn't exist."); assert(pass != ushort.max, "Update pass (Name " ~ pass_name ~ ") doesn't exist.");
@ -355,8 +382,8 @@ export struct EntityManager
//alias STC = ParameterStorageClass; //alias STC = ParameterStorageClass;
assert(register_state, assert(register_state,
"registerSystem must be called between beginRegister() and endRegister()."); "registerSystem must be called between beginRegister() and endRegister().");
version (D_BetterC) version (NoDRuntime)
assert(pass < passes.length, "Update pass doesn't exist."); assert(pass < passes.length, "Update pass doesn't exist.");
else else
assert(pass < passes.length, "Update pass (ID " ~ pass.to!string ~ ") doesn't exist."); assert(pass < passes.length, "Update pass (ID " ~ pass.to!string ~ ") doesn't exist.");
@ -365,7 +392,7 @@ export struct EntityManager
enum SystemName = fullName!Sys; enum SystemName = fullName!Sys;
//enum SystemName = Sys.stringof; //enum SystemName = Sys.stringof;
System system; System system = System();
system.m_pass = pass; system.m_pass = pass;
// static if (!(hasMember!(Sys, "system_id")) || !is(typeof(Sys.system_id) == ushort)) // static if (!(hasMember!(Sys, "system_id")) || !is(typeof(Sys.system_id) == ushort))
@ -405,7 +432,7 @@ export struct EntityManager
// enum EventName = fullyQualifiedName!(Unqual!(EventParamType));//.stringof; // enum EventName = fullyQualifiedName!(Unqual!(EventParamType));//.stringof;
ushort evt = events_map.get(cast(char[]) EventName, ushort.max); ushort evt = events_map.get(cast(char[]) EventName, ushort.max);
assert(evt != ushort.max, assert(evt != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing event \"" ~ EventName ~ "\"."); ~ "\" due to non existing event \"" ~ EventName ~ "\".");
callers[i].callback = cast(void*)&callEventHandler!(EventParamType); callers[i].callback = cast(void*)&callEventHandler!(EventParamType);
@ -448,14 +475,14 @@ export struct EntityManager
bool checkExcludedComponentsSomething(Sys)() bool checkExcludedComponentsSomething(Sys)()
{ {
return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string, return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string,
typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents); typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents);
} }
foreach (member; __traits(allMembers, Sys.EntitiesData)) foreach (member; __traits(allMembers, Sys.EntitiesData))
{ {
alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member));
if (member == "length" || member == "thread_id" || member == "job_id" if (member == "length" || member == "thread_id" || member == "job_id"
|| is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) || is(MemberType == Entity[]) || is(MemberType == const(Entity)[]))
{ {
//continue; //continue;
} }
@ -638,7 +665,7 @@ export struct EntityManager
} }
static void allocateSystemComponents(ComponentsIndices!component_counts components_info)( static void allocateSystemComponents(ComponentsIndices!component_counts components_info)(
ref System system) ref System system)
{ {
size_t req = components_info.req.length; size_t req = components_info.req.length;
size_t opt = components_info.optional.length; size_t opt = components_info.optional.length;
@ -672,14 +699,14 @@ export struct EntityManager
bool checkExcludedComponentsSomething(Sys)() bool checkExcludedComponentsSomething(Sys)()
{ {
return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string, return __traits(compiles, allSameType!(string, typeof(Sys.ExcludedComponents))) && allSameType!(string,
typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents); typeof(Sys.ExcludedComponents)) && isExpressions!(Sys.ExcludedComponents);
} }
foreach (member; __traits(allMembers, Sys.EntitiesData)) foreach (member; __traits(allMembers, Sys.EntitiesData))
{ {
alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member));
if (member == "length" || member == "thread_id" || member == "job_id" if (member == "length" || member == "thread_id" || member == "job_id"
|| is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) || is(MemberType == Entity[]) || is(MemberType == const(Entity)[]))
{ {
if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[]))
components_info.entites_array = member; components_info.entites_array = member;
@ -787,23 +814,23 @@ export struct EntityManager
else static if (member == "length") else static if (member == "length")
{ {
static assert(isIntegral!(MemberType), static assert(isIntegral!(MemberType),
"EntitiesData 'length' member must be integral type."); "EntitiesData 'length' member must be integral type.");
static assert(MemberType.sizeof > 1, static assert(MemberType.sizeof > 1,
"EntitiesData 'length' member can't be byte or ubyte."); "EntitiesData 'length' member can't be byte or ubyte.");
} }
else static if (member == "thread_id") else static if (member == "thread_id")
{ {
static assert(isIntegral!(MemberType), static assert(isIntegral!(MemberType),
"EntitiesData 'thread_id' member must be integral type."); "EntitiesData 'thread_id' member must be integral type.");
static assert(MemberType.sizeof > 1, static assert(MemberType.sizeof > 1,
"EntitiesData 'thread_id' member can't be byte or ubyte."); "EntitiesData 'thread_id' member can't be byte or ubyte.");
} }
else static if (member == "job_id") else static if (member == "job_id")
{ {
static assert(isIntegral!(MemberType), static assert(isIntegral!(MemberType),
"EntitiesData 'job_id' member must be integral type."); "EntitiesData 'job_id' member must be integral type.");
static assert(MemberType.sizeof > 1, static assert(MemberType.sizeof > 1,
"EntitiesData 'job_id' member can't be byte or ubyte."); "EntitiesData 'job_id' member can't be byte or ubyte.");
} }
else static if (!(isArray!(MemberType))) else static if (!(isArray!(MemberType)))
static assert(0, "EntitiesData members should be arrays of elements!"); static assert(0, "EntitiesData members should be arrays of elements!");
@ -815,9 +842,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.req) foreach (iii, comp_info; components_info.req)
{ {
ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing component."); ~ "\" due to non existing component.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -827,9 +854,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.excluded) foreach (iii, comp_info; components_info.excluded)
{ {
ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing component."); ~ "\" due to non existing component.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -839,9 +866,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.optional) foreach (iii, comp_info; components_info.optional)
{ {
ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing component."); ~ "\" due to non existing component.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -851,9 +878,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.readonly) foreach (iii, comp_info; components_info.readonly)
{ {
ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing component."); ~ "\" due to non existing component.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -863,9 +890,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.mutable) foreach (iii, comp_info; components_info.mutable)
{ {
ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = components_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing component."); ~ "\" due to non existing component.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -875,14 +902,14 @@ export struct EntityManager
} }
static void fillInputData(ref Sys.EntitiesData input_data, EntityInfo* info, static void fillInputData(ref Sys.EntitiesData input_data, EntityInfo* info,
EntitiesBlock* block, uint offset, uint entities_count, System* system) EntitiesBlock* block, uint offset, uint entities_count, System* system)
{ {
//enum ComponentsIndices components_info = getComponentsInfo(); //enum ComponentsIndices components_info = getComponentsInfo();
static if (components_info.entites_array) static if (components_info.entites_array)
{ {
__traits(getMember, input_data, components_info.entites_array) = ( __traits(getMember, input_data, components_info.entites_array) = (
cast(Entity*) block.dataBegin())[offset .. entities_count]; cast(Entity*) block.dataBegin())[offset .. entities_count];
} }
static if (hasMember!(Sys.EntitiesData, "length")) static if (hasMember!(Sys.EntitiesData, "length"))
@ -902,8 +929,7 @@ export struct EntityManager
__traits(getMember, input_data, comp_info.name) = ( __traits(getMember, input_data, comp_info.name) = (
cast(typeof( cast(typeof(
(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))).init[0] (typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))).init[0]
)*) )*)(cast(void*) block + info.deltas[system.m_components[iii]])
(cast(void*) block + info.deltas[system.m_components[iii]])
)[offset .. entities_count]; )[offset .. entities_count];
} }
@ -911,7 +937,7 @@ export struct EntityManager
.. components_info.m_optional_counter]) .. components_info.m_optional_counter])
{ {
if (system.m_optional_components[iii] < info.deltas.length if (system.m_optional_components[iii] < info.deltas.length
&& info.deltas[system.m_optional_components[iii]] != 0) && info.deltas[system.m_optional_components[iii]] != 0)
{ {
__traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember, __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember,
Sys.EntitiesData, comp_info.name)))*)(cast( Sys.EntitiesData, comp_info.name)))*)(cast(
@ -1010,7 +1036,7 @@ export struct EntityManager
static if (hasMember!(Sys.EntitiesData, "thread_id")) static if (hasMember!(Sys.EntitiesData, "thread_id"))
{ {
input_data.thread_id = cast( input_data.thread_id = cast(
typeof(input_data.thread_id)) data.thread_id; typeof(input_data.thread_id)) data.thread_id;
} }
static if (hasMember!(Sys.EntitiesData, "job_id")) static if (hasMember!(Sys.EntitiesData, "job_id"))
@ -1021,7 +1047,7 @@ export struct EntityManager
//s.onUpdate(input_data); //s.onUpdate(input_data);
(cast(typeof(&__traits(getOverloads, s, (cast(typeof(&__traits(getOverloads, s,
"onUpdate")[OnUpdateOverloadNum])) data.update_delegate)( "onUpdate")[OnUpdateOverloadNum])) data.update_delegate)(
input_data); input_data);
} }
block = block.next_block; block = block.next_block;
offset = 0; offset = 0;
@ -1091,15 +1117,15 @@ export struct EntityManager
foreach (func; __traits(getOverloads, Sys, func_name)) foreach (func; __traits(getOverloads, Sys, func_name))
{ {
static if ((Parameters!(func)).length == 1 static if ((Parameters!(func)).length == 1
&& is(Parameters!(func)[0] == Sys.EntitiesData) && is(Parameters!(func)[0] == Sys.EntitiesData)
&& is(ReturnType!(func) == RetType)) && is(ReturnType!(func) == RetType))
{ {
static RetType callFunc(ref ListenerCallData data) static RetType callFunc(ref ListenerCallData data)
{ {
Sys* s = cast(Sys*) data.system.m_system_pointer; Sys* s = cast(Sys*) data.system.m_system_pointer;
Sys.EntitiesData input_data; Sys.EntitiesData input_data;
fillInputData(input_data, data.block.type_info, fillInputData(input_data, data.block.type_info,
data.block, data.begin, data.end, data.system); data.block, data.begin, data.end, data.system);
static if (is(RetTyp == void)) static if (is(RetTyp == void))
mixin("s." ~ func_name ~ "(input_data)"); mixin("s." ~ func_name ~ "(input_data)");
else else
@ -1120,8 +1146,8 @@ export struct EntityManager
foreach (func; __traits(getOverloads, Sys, func_name)) foreach (func; __traits(getOverloads, Sys, func_name))
{ {
static if ((Parameters!(func)).length == 1 static if ((Parameters!(func)).length == 1
&& is(Parameters!(func)[0] == EntityInfo*) && is(Parameters!(func)[0] == EntityInfo*)
&& is(ReturnType!(func) == RetType)) && is(ReturnType!(func) == RetType))
{ {
static RetType callFunc(void* system_pointer, EntityInfo* info) static RetType callFunc(void* system_pointer, EntityInfo* info)
{ {
@ -1156,14 +1182,16 @@ export struct EntityManager
system.m_priority = priority; system.m_priority = priority;
//(cast(Sys*) system.m_system_pointer).__ecsInitialize(); //(cast(Sys*) system.m_system_pointer).__ecsInitialize();
//system.jobs = (cast(Sys*) system.m_system_pointer)._ecs_jobs; //system.jobs = (cast(Sys*) system.m_system_pointer)._ecs_jobs;
static if(__traits(hasMember, Sys ,"__becs_jobs_count"))system.jobs = Mallocator.makeArray!(Job)(Sys.__becs_jobs_count); static if (__traits(hasMember, Sys, "__becs_jobs_count"))
else system.jobs = Mallocator.makeArray!(Job)(32); system.jobs = Mallocator.makeArray!(Job)(Sys.__becs_jobs_count);
else
system.jobs = Mallocator.makeArray!(Job)(32);
static if (OnUpdateOverloadNum != -1) static if (OnUpdateOverloadNum != -1)
{ {
Sys* s = cast(Sys*) system.m_system_pointer; Sys* s = cast(Sys*) system.m_system_pointer;
system.m_update_delegate = cast(void delegate())&__traits(getOverloads, system.m_update_delegate = cast(void delegate())&__traits(getOverloads,
s, "onUpdate")[OnUpdateOverloadNum]; s, "onUpdate")[OnUpdateOverloadNum];
} }
genCompList(system, components_map); genCompList(system, components_map);
@ -1171,10 +1199,10 @@ export struct EntityManager
foreach (iii, comp_info; components_info.readonlyDeps) foreach (iii, comp_info; components_info.readonlyDeps)
{ {
ushort comp = external_dependencies_map.get(cast(const(char)[]) comp_info.type, ushort comp = external_dependencies_map.get(cast(const(char)[]) comp_info.type,
ushort.max); ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing dependency."); ~ "\" due to non existing dependency.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -1185,9 +1213,9 @@ export struct EntityManager
foreach (iii, comp_info; components_info.writableDeps) foreach (iii, comp_info; components_info.writableDeps)
{ {
ushort comp = external_dependencies_map.get(cast(char[]) comp_info.type, ushort.max); ushort comp = external_dependencies_map.get(cast(char[]) comp_info.type, ushort.max);
version (D_BetterC) version (NoDRuntime)
assert(comp != ushort.max, assert(comp != ushort.max,
"Can't register system \"" ~ SystemName "Can't register system \"" ~ SystemName
~ "\" due to non existing dependency."); ~ "\" due to non existing dependency.");
else else
assert(comp != ushort.max, "Can't register system \"" ~ SystemName assert(comp != ushort.max, "Can't register system \"" ~ SystemName
@ -1229,7 +1257,8 @@ export struct EntityManager
} }
/************************************************************************************************************************ /************************************************************************************************************************
Return system ECS api by id Return system ECS api by id.
System pointer might become invalid after registration process. It should be get once again after new systems are registered.
*/ */
export System* getSystem(ushort id) nothrow @nogc export System* getSystem(ushort id) nothrow @nogc
{ {
@ -1279,8 +1308,8 @@ export struct EntityManager
// } // }
static if (hasMember!(Comp, "onDestroy") && isFunction!(Comp.onDestroy) static if (hasMember!(Comp, "onDestroy") && isFunction!(Comp.onDestroy)
&& is(ReturnType!(Comp.onDestroy) == void) && is(ReturnType!(Comp.onDestroy) == void)
&& Parameters!(Comp.onDestroy).length == 0) && Parameters!(Comp.onDestroy).length == 0)
{ {
static void callDestroy(void* pointer) nothrow @nogc static void callDestroy(void* pointer) nothrow @nogc
{ {
@ -1291,7 +1320,7 @@ export struct EntityManager
} }
static if (hasMember!(Comp, "onCreate") && isFunction!(Comp.onCreate) static if (hasMember!(Comp, "onCreate") && isFunction!(Comp.onCreate)
&& is(ReturnType!(Comp.onCreate) == void) && Parameters!(Comp.onCreate).length == 0) && is(ReturnType!(Comp.onCreate) == void) && Parameters!(Comp.onCreate).length == 0)
{ {
static void callCreate(void* pointer) nothrow @nogc static void callCreate(void* pointer) nothrow @nogc
{ {
@ -1337,7 +1366,7 @@ export struct EntityManager
// } // }
static if (hasMember!(Ev, "onDestroy") && isFunction!(Ev.onDestroy) static if (hasMember!(Ev, "onDestroy") && isFunction!(Ev.onDestroy)
&& is(ReturnType!(Ev.onDestroy) == void) && Parameters!(Ev.onDestroy).length == 0) && is(ReturnType!(Ev.onDestroy) == void) && Parameters!(Ev.onDestroy).length == 0)
{ {
static void callDestroy(void* pointer) static void callDestroy(void* pointer)
{ {
@ -1371,10 +1400,10 @@ export struct EntityManager
Sys* s; Sys* s;
static assert(isDelegate!func, "Function must be delegate."); static assert(isDelegate!func, "Function must be delegate.");
static assert(__traits(hasMember, Sys, "EntitiesData"), static assert(__traits(hasMember, Sys, "EntitiesData"),
"Can't call function with system which hasn't EntitesData structure."); "Can't call function with system which hasn't EntitesData structure.");
///TODO: make possibly to call function to group without system with onUpdate function ///TODO: make possibly to call function to group without system with onUpdate function
static assert(__traits(hasMember, Sys, "onUpdate"), static assert(__traits(hasMember, Sys, "onUpdate"),
"Can't call function with system which hasn't onUpdate function callback."); "Can't call function with system which hasn't onUpdate function callback.");
// static assert(is(SetFunctionAttributes!(T, functionLinkage!(s.onUpdate), // static assert(is(SetFunctionAttributes!(T, functionLinkage!(s.onUpdate),
// functionAttributes!(s.onUpdate)) == typeof(&s.onUpdate)), // functionAttributes!(s.onUpdate)) == typeof(&s.onUpdate)),
// "Function must match system update function."); FIXME: It's lead to crash on android build // "Function must match system update function."); FIXME: It's lead to crash on android build
@ -1382,7 +1411,9 @@ export struct EntityManager
System* system = getSystem(becsID!Sys); System* system = getSystem(becsID!Sys);
assert(system != null, assert(system != null,
"System must be registered in EntityManager before any funcion can be called."); "System must be registered in EntityManager before any funcion can be called.");
assert(system.isAlive(), "System must be alive (registered) in order to call entities function on its entities");
if (!system.m_any_system_caller) if (!system.m_any_system_caller)
return; return;
@ -1424,7 +1455,7 @@ export struct EntityManager
foreach (info; caller.infos) foreach (info; caller.infos)
{ {
CallData data = CallData(caller.system_id, sys, info, CallData data = CallData(caller.system_id, sys, info,
sys.m_update_delegate); sys.m_update_delegate);
data.update(); data.update();
} }
} }
@ -1446,7 +1477,7 @@ export struct EntityManager
assert(!register_state); assert(!register_state);
assert(pass < passes.length); assert(pass < passes.length);
assert(m_dispatch_jobs, assert(m_dispatch_jobs,
"Can't update with multithreading without JobDispatch function. Please use setJobDispatchFunc()."); "Can't update with multithreading without JobDispatch function. Please use setJobDispatchFunc().");
Vector!CallData tmp_datas; Vector!CallData tmp_datas;
tmp_datas.reserve(8); tmp_datas.reserve(8);
@ -1459,7 +1490,7 @@ export struct EntityManager
void nextJob() void nextJob()
{ {
CallData[] callers = m_call_data_allocator.getCallData( CallData[] callers = m_call_data_allocator.getCallData(
cast(uint) tmp_datas.length); cast(uint) tmp_datas.length);
//callers[0 .. $] = tmp_datas[0 .. $]; //callers[0 .. $] = tmp_datas[0 .. $];
memcpy(callers.ptr, &tmp_datas[0], CallData.sizeof * tmp_datas.length); memcpy(callers.ptr, &tmp_datas[0], CallData.sizeof * tmp_datas.length);
tmp_datas.clear(); tmp_datas.clear();
@ -1514,7 +1545,7 @@ export struct EntityManager
//if this info will fill job //if this info will fill job
if ((blocks_count - 1) * info.max_entities + entities_count if ((blocks_count - 1) * info.max_entities + entities_count
+ info.last_block.entities_count - first_elem >= entities_per_job) + info.last_block.entities_count - first_elem >= entities_per_job)
{ {
int reamaining_entities = (entities_per_job - entities_count - ( int reamaining_entities = (entities_per_job - entities_count - (
first_block.entities_count - first_elem)); first_block.entities_count - first_elem));
@ -1535,9 +1566,9 @@ export struct EntityManager
assert(entities_per_job == full_blocks_count * info.max_entities + entities_count + ( assert(entities_per_job == full_blocks_count * info.max_entities + entities_count + (
first_block.entities_count - first_elem)); first_block.entities_count - first_elem));
CallData data = CallData(caller.system_id, sys, CallData data = CallData(caller.system_id, sys,
info, sys.m_update_delegate, first_block, info, sys.m_update_delegate, first_block,
cast(ushort)(full_blocks_count + 1), cast(ushort)(full_blocks_count + 1),
cast(ushort) first_elem, 0); cast(ushort) first_elem, 0);
tmp_datas.add(data); tmp_datas.add(data);
first_elem = 0; first_elem = 0;
blocks_count -= full_blocks_count + 1; blocks_count -= full_blocks_count + 1;
@ -1546,14 +1577,14 @@ export struct EntityManager
else else
{ {
entities_count += full_blocks_count * info.max_entities + ( entities_count += full_blocks_count * info.max_entities + (
first_block.entities_count - first_elem); // - first_elem; first_block.entities_count - first_elem); // - first_elem;
uint last_elem = entities_per_job - entities_count; // + first_elem - 1; uint last_elem = entities_per_job - entities_count; // + first_elem - 1;
assert(last_elem > 0); assert(last_elem > 0);
assert(last_elem <= block.entities_count); assert(last_elem <= block.entities_count);
CallData data = CallData(caller.system_id, sys, CallData data = CallData(caller.system_id, sys,
info, sys.m_update_delegate, first_block, info, sys.m_update_delegate, first_block,
cast(ushort)(full_blocks_count + 2), cast(ushort)(full_blocks_count + 2),
cast(ushort) first_elem, cast(ushort) last_elem); cast(ushort) first_elem, cast(ushort) last_elem);
tmp_datas.add(data); tmp_datas.add(data);
first_elem = last_elem; first_elem = last_elem;
blocks_count -= full_blocks_count + 1; blocks_count -= full_blocks_count + 1;
@ -1570,8 +1601,8 @@ export struct EntityManager
uint last_elem = entities_per_job - entities_count; uint last_elem = entities_per_job - entities_count;
assert(last_elem > 0); assert(last_elem > 0);
CallData data = CallData(caller.system_id, sys, CallData data = CallData(caller.system_id, sys,
info, sys.m_update_delegate, first_block, 1, info, sys.m_update_delegate, first_block, 1,
cast(ushort) first_elem, cast(ushort)(first_elem + last_elem)); cast(ushort) first_elem, cast(ushort)(first_elem + last_elem));
tmp_datas.add(data); tmp_datas.add(data);
first_elem += last_elem; first_elem += last_elem;
assert(first_elem <= first_block.entities_count); assert(first_elem <= first_block.entities_count);
@ -1591,7 +1622,7 @@ export struct EntityManager
{ {
//take whole info blocks //take whole info blocks
CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate, CallData data = CallData(caller.system_id, sys, info, sys.m_update_delegate,
first_block, cast(ushort) blocks_count, cast(ushort) first_elem); first_block, cast(ushort) blocks_count, cast(ushort) first_elem);
tmp_datas.add(data); tmp_datas.add(data);
entities_count += (blocks_count - 1) * info.max_entities entities_count += (blocks_count - 1) * info.max_entities
+ info.last_block.entities_count - first_elem; + info.last_block.entities_count - first_elem;
@ -1606,7 +1637,7 @@ export struct EntityManager
} }
export void setMultithreadingCallbacks(void delegate(JobGroup) dispatch_callback, export void setMultithreadingCallbacks(void delegate(JobGroup) dispatch_callback,
uint delegate() get_id_callback) uint delegate() get_id_callback)
{ {
m_dispatch_jobs = cast(void delegate(JobGroup jobs) nothrow @nogc) dispatch_callback; m_dispatch_jobs = cast(void delegate(JobGroup jobs) nothrow @nogc) dispatch_callback;
m_thread_id_func = cast(uint delegate() nothrow @nogc) get_id_callback; m_thread_id_func = cast(uint delegate() nothrow @nogc) get_id_callback;
@ -1671,7 +1702,7 @@ export struct EntityManager
if (components[comp].size == 0) if (components[comp].size == 0)
continue; continue;
memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp],
components[comp].init_data.ptr, components[comp].size); components[comp].init_data.ptr, components[comp].size);
} }
} }
else else
@ -1682,8 +1713,8 @@ export struct EntityManager
if (components[comp].size == 0) if (components[comp].size == 0)
continue; continue;
memcpy(cast(void*) temp.entity_data.ptr + info.tmpl_deltas[comp], memcpy(cast(void*) temp.entity_data.ptr + info.tmpl_deltas[comp],
cast(void*) block + info.deltas[comp] + components[comp].size * index, cast(void*) block + info.deltas[comp] + components[comp].size * index,
components[comp].size); components[comp].size);
} }
} }
@ -1732,7 +1763,7 @@ export struct EntityManager
if (components[comp].size == 0) if (components[comp].size == 0)
continue; continue;
memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp],
components[comp].init_data.ptr, components[comp].size); components[comp].init_data.ptr, components[comp].size);
} }
return temp; return temp;
@ -1748,18 +1779,18 @@ export struct EntityManager
remove_components_ids = array of components to remove from base template remove_components_ids = array of components to remove from base template
*/ */
export EntityTemplate* allocateTemplate(EntityTemplate* base_tmpl, export EntityTemplate* allocateTemplate(EntityTemplate* base_tmpl,
ushort[] components_ids, ushort[] remove_components_ids = null) ushort[] components_ids, ushort[] remove_components_ids = null)
{ {
size_t len = base_tmpl.info.components.length + components_ids.length; size_t len = base_tmpl.info.components.length + components_ids.length;
ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * len))[0 .. len]; ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * len))[0 .. len];
memcpy(ids.ptr, base_tmpl.info.components.ptr, memcpy(ids.ptr, base_tmpl.info.components.ptr,
ushort.sizeof * base_tmpl.info.components.length); ushort.sizeof * base_tmpl.info.components.length);
memcpy(ids.ptr + base_tmpl.info.components.length, components_ids.ptr, memcpy(ids.ptr + base_tmpl.info.components.length, components_ids.ptr,
ushort.sizeof * components_ids.length); ushort.sizeof * components_ids.length);
qsort(ids.ptr, ids.length, ushort.sizeof, &compareUShorts); qsort(ids.ptr, ids.length, ushort.sizeof, &compareUShorts);
qsort(remove_components_ids.ptr, remove_components_ids.length, qsort(remove_components_ids.ptr, remove_components_ids.length,
ushort.sizeof, &compareUShorts); ushort.sizeof, &compareUShorts);
{ {
uint k = 0; uint k = 0;
uint j = 1; uint j = 1;
@ -1799,20 +1830,20 @@ export struct EntityManager
foreach (comp; info.components) foreach (comp; info.components)
{ {
if (comp < base_tmpl.info.tmpl_deltas.length if (comp < base_tmpl.info.tmpl_deltas.length
&& base_tmpl.info.tmpl_deltas[comp] != ushort.max) //copy data from base component && base_tmpl.info.tmpl_deltas[comp] != ushort.max) //copy data from base component
{ {
if (components[comp].size == 0) if (components[comp].size == 0)
continue; continue;
memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp],
base_tmpl.entity_data.ptr + base_tmpl.info.tmpl_deltas[comp], base_tmpl.entity_data.ptr + base_tmpl.info.tmpl_deltas[comp],
components[comp].size); components[comp].size);
} }
else //fill with default data else //fill with default data
{ {
if (components[comp].size == 0) if (components[comp].size == 0)
continue; continue;
memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp],
components[comp].init_data.ptr, components[comp].size); components[comp].init_data.ptr, components[comp].size);
} }
} }
@ -1827,6 +1858,7 @@ export struct EntityManager
*/ */
export EntityTemplate* allocateTemplate(EntityTemplate* copy_tmpl) export EntityTemplate* allocateTemplate(EntityTemplate* copy_tmpl)
{ {
assert(copy_tmpl, "copy_tmpl can't be null");
EntityTemplate* tmpl = Mallocator.make!EntityTemplate; EntityTemplate* tmpl = Mallocator.make!EntityTemplate;
tmpl.info = copy_tmpl.info; tmpl.info = copy_tmpl.info;
tmpl.entity_data = Mallocator.makeArray(copy_tmpl.entity_data); tmpl.entity_data = Mallocator.makeArray(copy_tmpl.entity_data);
@ -1841,74 +1873,86 @@ export struct EntityManager
*/ */
export EntityInfo* getEntityInfo(ushort[] ids) export EntityInfo* getEntityInfo(ushort[] ids)
{ {
if(ids.length == 0)ids = null;
EntityInfo* info = entities_infos.get(ids, null); EntityInfo* info = entities_infos.get(ids, null);
if (info is null) if (info is null)
{ {
info = Mallocator.make!EntityInfo; info = Mallocator.make!EntityInfo;
info.components = Mallocator.makeArray(ids);
info.deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1);
info.size = EntityID.sizeof; info.size = EntityID.sizeof;
info.alignment = EntityID.alignof; info.alignment = EntityID.alignof;
info.tmpl_deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1, ushort.max); if(ids is null)
uint components_size = EntityID.sizeof;
foreach (i, id; ids)
{ {
info.alignment = max(info.alignment, components[id].alignment); uint block_memory = cast(uint)(
alignNum(info.size, components[id].alignment); m_page_size - EntitiesBlock.sizeof - info.size);
info.tmpl_deltas[id] = info.size; uint entites_in_block = block_memory / info.size;
info.size += components[id].size; info.max_entities = cast(ushort) entites_in_block;
components_size += components[id].size;
} }
alignNum(info.size, info.alignment); else
uint block_memory = cast(uint)(
m_page_size - EntitiesBlock.sizeof - (info.size - components_size));
//uint entity_comps_size = EntityID.sizeof;
uint mem_begin = EntitiesBlock.sizeof;
uint entites_in_block = block_memory / info.size; //entity_comps_size;
info.max_entities = cast(ushort) entites_in_block;
ushort current_delta = cast(ushort)(mem_begin + entites_in_block * EntityID.sizeof);
foreach (i, id; ids)
{ {
if (current_delta == 0) uint components_size = EntityID.sizeof;
current_delta = ushort.max;
alignNum(current_delta, components[id].alignment); info.components = Mallocator.makeArray(ids);
info.deltas[id] = cast(ushort) current_delta; info.deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1);
current_delta += entites_in_block * components[id].size; info.tmpl_deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1, ushort.max);
foreach (i, id; ids)
{
info.alignment = max(info.alignment, components[id].alignment);
alignNum(info.size, components[id].alignment);
info.tmpl_deltas[id] = info.size;
info.size += components[id].size;
components_size += components[id].size;
}
alignNum(info.size, info.alignment);
uint block_memory = cast(uint)(
m_page_size - EntitiesBlock.sizeof - (info.size - components_size));
//uint entity_comps_size = EntityID.sizeof;
uint mem_begin = EntitiesBlock.sizeof;
uint entites_in_block = block_memory / info.size; //entity_comps_size;
info.max_entities = cast(ushort) entites_in_block;
ushort current_delta = cast(ushort)(mem_begin + entites_in_block * EntityID.sizeof);
foreach (i, id; ids)
{
if (current_delta == 0)
current_delta = ushort.max;
alignNum(current_delta, components[id].alignment);
info.deltas[id] = cast(ushort) current_delta;
current_delta += entites_in_block * components[id].size;
}
info.comp_add_info = Mallocator.makeArray!(EntityInfo*)(info.deltas.length);
info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(info.deltas.length);
foreach (comp; info.components)
{
info.comp_add_info[comp] = info;
info.comp_rem_info[comp] = null;
}
} }
info.systems = Mallocator.makeArray!bool(systems.length); info.systems = Mallocator.makeArray!bool(systems.length);
foreach (i, ref system; systems) foreach (i, ref system; systems)
{ {
if (system.isAlive() == false)
continue;
if (system.m_empty) if (system.m_empty)
continue; continue;
if (system.m_update is null) if (system.m_update is null)
{ {
if (system.m_add_entity || system.m_remove_entity if (system.m_add_entity || system.m_remove_entity
|| system.m_change_entity || system.m_event_callers.length) || system.m_change_entity || system.m_event_callers.length)
connectListenerToEntityInfo(*info, cast(uint) i); connectListenerToEntityInfo(*info, cast(uint) i);
continue; continue;
} }
addSystemCaller(*info, cast(uint) i); addSystemCaller(*info, cast(uint) i);
} }
info.comp_add_info = Mallocator.makeArray!(EntityInfo*)(gEntityManager.components.length);
//info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(gEntityManager.components.length);
info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(info.deltas.length);
foreach (comp; info.components)
{
info.comp_add_info[comp] = info;
info.comp_rem_info[comp] = null;
}
entities_infos.add(info.components, info); entities_infos.add(info.components, info);
generateListeners(info); generateListeners(info);
@ -2059,7 +2103,9 @@ export struct EntityManager
} }
///call Custom Entity Filter test if function exists ///call Custom Entity Filter test if function exists
if(system.m_filter_entity && !(cast(bool function(void* system_pointer, EntityInfo* info) @nogc nothrow)system.m_filter_entity)(system, &entity))return; if (system.m_filter_entity && !(cast(bool function(void* system_pointer, EntityInfo* info) @nogc nothrow) system
.m_filter_entity)(system, &entity))
return;
entity.systems[system_id] = true; entity.systems[system_id] = true;
} }
@ -2104,7 +2150,8 @@ export struct EntityManager
System* system = &systems[system_id]; System* system = &systems[system_id];
connectListenerToEntityInfo(info, system_id); connectListenerToEntityInfo(info, system_id);
if(!info.systems[system_id])return; if (!info.systems[system_id])
return;
uint index = 0; uint index = 0;
for (; index < passes[system.m_pass].system_callers.length; index++) for (; index < passes[system.m_pass].system_callers.length; index++)
@ -2213,7 +2260,7 @@ export struct EntityManager
if (comp_size == 0) if (comp_size == 0)
continue; continue;
memcpy(cast(void*) new_block + new_info.deltas[comp] + new_block.entities_count * comp_size, memcpy(cast(void*) new_block + new_info.deltas[comp] + new_block.entities_count * comp_size,
cast(void*) block + info.deltas[comp] + ind * comp_size, comp_size); cast(void*) block + info.deltas[comp] + ind * comp_size, comp_size);
} }
new_block.entities_count++; new_block.entities_count++;
@ -2227,7 +2274,7 @@ export struct EntityManager
if (!info.systems[listener]) if (!info.systems[listener])
{ {
callAddEntityListener(&systems[listener], new_info, new_block, callAddEntityListener(&systems[listener], new_info, new_block,
new_block.entities_count - 1, new_block.entities_count); new_block.entities_count - 1, new_block.entities_count);
} }
} }
} }
@ -2239,7 +2286,7 @@ export struct EntityManager
if (info.systems[listener]) if (info.systems[listener])
{ {
callChangeEntityListener(&systems[listener], new_info, new_block, callChangeEntityListener(&systems[listener], new_info, new_block,
new_block.entities_count - 1, new_block.entities_count, del_ids); new_block.entities_count - 1, new_block.entities_count, del_ids);
} }
} }
} }
@ -2390,7 +2437,7 @@ export struct EntityManager
if (!info.systems[listener]) if (!info.systems[listener])
{ {
callAddEntityListener(&systems[listener], new_info, new_block, callAddEntityListener(&systems[listener], new_info, new_block,
new_block.entities_count - 1, new_block.entities_count); new_block.entities_count - 1, new_block.entities_count);
} }
} }
} }
@ -2402,7 +2449,7 @@ export struct EntityManager
if (info.systems[listener]) if (info.systems[listener])
{ {
callChangeEntityListener(&systems[listener], new_info, new_block, callChangeEntityListener(&systems[listener], new_info, new_block,
new_block.entities_count - 1, new_block.entities_count, new_ids); new_block.entities_count - 1, new_block.entities_count, new_ids);
} }
} }
} }
@ -2445,7 +2492,7 @@ export struct EntityManager
{ {
if (components[ref_.component_id].size != 0) if (components[ref_.component_id].size != 0)
data.changeEntitiesList.add( data.changeEntitiesList.add(
(cast(ubyte*) ref_.ptr)[0 .. components[ref_.component_id].size]); (cast(ubyte*) ref_.ptr)[0 .. components[ref_.component_id].size]);
} }
} }
@ -2495,12 +2542,12 @@ export struct EntityManager
ushort size = components[comp].size; ushort size = components[comp].size;
if (size != 0) if (size != 0)
memcpy(cast(void*) new_block + info.deltas[comp] + new_id * size, memcpy(cast(void*) new_block + info.deltas[comp] + new_id * size,
cast(void*) block + info.deltas[comp] + size * index, size); cast(void*) block + info.deltas[comp] + size * index, size);
if (components[comp].create_callback) if (components[comp].create_callback)
{ {
components[comp].create_callback( components[comp].create_callback(
cast(void*) new_block + info.deltas[comp] + new_id * size); cast(void*) new_block + info.deltas[comp] + new_id * size);
} }
} }
@ -2519,7 +2566,7 @@ export struct EntityManager
use you should save ID instead of pointer. use you should save ID instead of pointer.
Params: Params:
tmpl = pointer entity template allocated by EntityManager. tmpl = pointer entity template allocated by EntityManager. Can be null in which case empty entity would be added (entity without components)
*/ */
export Entity* addEntity(EntityTemplate* tmpl) export Entity* addEntity(EntityTemplate* tmpl)
{ {
@ -2531,12 +2578,14 @@ export struct EntityManager
use you should save ID instead of pointer. use you should save ID instead of pointer.
Params: Params:
tmpl = pointer entity template allocated by EntityManager. tmpl = pointer entity template allocated by EntityManager. Can be null in which case empty entity would be added (entity without components)
replacement = list of components references to used. Memory form list replace data from template inside new entity. Should be used only for data which vary between most entities (like 3D position etc.) replacement = list of components references to used. Memory form list replace data from template inside new entity. Should be used only for data which vary between most entities (like 3D position etc.)
*/ */
export Entity* addEntity(EntityTemplate* tmpl, ComponentRef[] replacement) export Entity* addEntity(EntityTemplate* tmpl, ComponentRef[] replacement)
{ {
EntityInfo* info = tmpl.info; EntityInfo* info = void;
if(tmpl)info = tmpl.info;
else info = getEntityInfo(null);
ushort index = 0; ushort index = 0;
EntitiesBlock* block; EntitiesBlock* block;
@ -2557,7 +2606,7 @@ export struct EntityManager
uint size = components[comp].size; uint size = components[comp].size;
if (size != 0) if (size != 0)
memcpy(cast(void*) block + info.deltas[comp] + size * id, memcpy(cast(void*) block + info.deltas[comp] + size * id,
tmpl.entity_data.ptr + info.tmpl_deltas[comp], size); tmpl.entity_data.ptr + info.tmpl_deltas[comp], size);
} }
foreach (comp; replacement) foreach (comp; replacement)
@ -2579,7 +2628,7 @@ export struct EntityManager
if (components[comp].create_callback) if (components[comp].create_callback)
{ {
components[comp].create_callback( components[comp].create_callback(
cast(void*) block + info.deltas[comp] + id * components[comp].size); cast(void*) block + info.deltas[comp] + id * components[comp].size);
} }
} }
@ -2714,7 +2763,7 @@ export struct EntityManager
} }
private void removeEntityNoID(Entity* entity, EntitiesBlock* block, private void removeEntityNoID(Entity* entity, EntitiesBlock* block,
bool call_destructors = false) nothrow @nogc bool call_destructors = false) nothrow @nogc
{ {
EntityInfo* info = block.type_info; EntityInfo* info = block.type_info;
@ -2846,7 +2895,7 @@ export struct EntityManager
} }
private static void callAddEntityListener(System* system, EntityInfo* info, private static void callAddEntityListener(System* system, EntityInfo* info,
EntitiesBlock* block, int begin, int end) @nogc nothrow EntitiesBlock* block, int begin, int end) @nogc nothrow
{ {
ListenerCallData data; ListenerCallData data;
data.system = system; data.system = system;
@ -2857,7 +2906,7 @@ export struct EntityManager
} }
private void callRemoveEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, private void callRemoveEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin,
int end) @nogc nothrow int end) @nogc nothrow
{ {
foreach (listener; info.remove_listeners) foreach (listener; info.remove_listeners)
{ {
@ -2867,7 +2916,7 @@ export struct EntityManager
} }
private static void callRemoveEntityListener(System* system, private static void callRemoveEntityListener(System* system,
EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow
{ {
ListenerCallData data; ListenerCallData data;
data.system = system; data.system = system;
@ -2878,7 +2927,7 @@ export struct EntityManager
} }
private void callChangeEntityListener(System* system, EntityInfo* info, private void callChangeEntityListener(System* system, EntityInfo* info,
EntitiesBlock* block, int begin, int end, ushort[] ch_ids) @nogc nothrow EntitiesBlock* block, int begin, int end, ushort[] ch_ids) @nogc nothrow
{ {
int i = 0; int i = 0;
int j = 0; int j = 0;
@ -3032,7 +3081,7 @@ export struct EntityManager
foreach (caller; events[i].callers) foreach (caller; events[i].callers)
{ {
if (call_data.block.type_info.systems[caller.system.m_id] == false if (call_data.block.type_info.systems[caller.system.m_id] == false
|| !caller.system.enabled || !caller.system.willExecute) || !caller.system.enabled || !caller.system.willExecute)
continue; continue;
call_data.system_pointer = caller.system.m_system_pointer; call_data.system_pointer = caller.system.m_system_pointer;
(cast(void function(ref EventCallData) nothrow @nogc) caller (cast(void function(ref EventCallData) nothrow @nogc) caller
@ -3095,9 +3144,11 @@ export struct EntityManager
foreach (ref system; systems) foreach (ref system; systems)
{ {
if (system.isAlive() == false)
continue;
if (system.enabled && system.m_begin) if (system.enabled && system.m_begin)
system.m_execute = (cast(bool function(void*)) system.m_begin)( system.m_execute = (cast(bool function(void*)) system.m_begin)(
system.m_system_pointer); system.m_system_pointer);
} }
} }
@ -3109,6 +3160,9 @@ export struct EntityManager
foreach (ref system; systems) foreach (ref system; systems)
{ {
if (system.isAlive() == false)
continue;
if (system.enabled && system.m_end) if (system.enabled && system.m_end)
(cast(void function(void*)) system.m_end)(system.m_system_pointer); (cast(void function(void*)) system.m_end)(system.m_system_pointer);
} }
@ -3247,7 +3301,7 @@ export struct EntityManager
} }
qsort(pass.system_callers.array.ptr, pass.system_callers.length, qsort(pass.system_callers.array.ptr, pass.system_callers.length,
(SystemCaller*).sizeof, &compareSystems); (SystemCaller*).sizeof, &compareSystems);
foreach (i, caller; pass.system_callers) foreach (i, caller; pass.system_callers)
caller.job_group.id = cast(uint) i; caller.job_group.id = cast(uint) i;
@ -3303,6 +3357,16 @@ export struct EntityManager
export ~this() nothrow @nogc export ~this() nothrow @nogc
{ {
} }
export void opAssign(ComponentInfo c)
{
size = c.size;
alignment = c.alignment;
init_data = c.init_data;
destroy_callback = c.destroy_callback;
create_callback = c.create_callback;
}
///Component size ///Component size
ushort size; ushort size;
///Component data alignment ///Component data alignment
@ -3374,7 +3438,7 @@ export struct EntityManager
if (comp_add_info.length <= id) if (comp_add_info.length <= id)
{ {
EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)( EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)(
gEntityManager.components.length); gEntityManager.components.length);
if (comp_add_info !is null) if (comp_add_info !is null)
{ {
//new_infos[0 .. comp_add_info.length] = comp_add_info[0 .. $]; //new_infos[0 .. comp_add_info.length] = comp_add_info[0 .. $];
@ -3408,7 +3472,7 @@ export struct EntityManager
break; break;
} }
} }
if (id > components[$ - 1]) if (components.length == 0 || id > components[$ - 1])
ids[len++] = id; ids[len++] = id;
assert(len == components.length + 1); assert(len == components.length + 1);
@ -3462,7 +3526,8 @@ export struct EntityManager
export bool hasComponent(ushort component_id) export bool hasComponent(ushort component_id)
{ {
if(component_id >= deltas.length || !deltas[component_id])return false; if (component_id >= deltas.length || !deltas[component_id])
return false;
return true; return true;
} }
@ -3614,7 +3679,6 @@ export struct EntityManager
export void execute() nothrow @nogc export void execute() nothrow @nogc
{ {
//gEntityManager.getThreadID();
foreach (ref caller; callers) foreach (ref caller; callers)
{ {
caller.thread_id = gEntityManager.threadID(); caller.thread_id = gEntityManager.threadID();

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module ecs; module ecs;

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.simple_vector; module bubel.ecs.simple_vector;

View file

@ -2,16 +2,14 @@
It's internal code! It's internal code!
This module contain implementation of standard functionality. This module contain implementation of standard functionality.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.std; module bubel.ecs.std;
version (Emscripten) version = ECSEmscripten;
import std.traits; import std.traits;
version (ECSEmscripten) version (Emscripten)
{ {
extern (C) struct pthread_mutex_t extern (C) struct pthread_mutex_t
{ {
@ -29,10 +27,6 @@ version (ECSEmscripten)
extern (C) int memcmp(const void* s1, const void* s2, size_t size); extern (C) int memcmp(const void* s1, const void* s2, size_t size);
extern (C) void exit(int status) nothrow @nogc; 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 free(void*) @nogc nothrow @system;
extern (C) void* malloc(size_t size) @nogc nothrow @system; extern (C) void* malloc(size_t size) @nogc nothrow @system;
@ -60,7 +54,7 @@ else
public import core.stdc.stdlib : qsort; public import core.stdc.stdlib : qsort;
} }
version (ECSEmscripten) version (Emscripten)
{ {
} }
else version (Windows) else version (Windows)
@ -89,7 +83,7 @@ else version (Posix)
import core.sys.posix.stdlib : posix_memalign; import core.sys.posix.stdlib : posix_memalign;
} }
version (ECSEmscripten) version (Emscripten)
{ {
private const uint max_alloca = 10000; private const uint max_alloca = 10000;
private __gshared byte[max_alloca] alloca_array; private __gshared byte[max_alloca] alloca_array;
@ -294,7 +288,7 @@ static struct Mallocator
posix_memalign(&ret, alignment, length); //ret = aligned_alloc(alignment, length); posix_memalign(&ret, alignment, length); //ret = aligned_alloc(alignment, length);
else version (Windows) else version (Windows)
ret = _aligned_malloc(length, alignment); ret = _aligned_malloc(length, alignment);
else version (ECSEmscripten) else version (Emscripten)
posix_memalign(&ret, alignment, length); //malloc(length); posix_memalign(&ret, alignment, length); //malloc(length);
else else
static assert(0, "Unimplemented platform!"); static assert(0, "Unimplemented platform!");
@ -341,7 +335,7 @@ static struct Mallocator
free(cast(void*) object); free(cast(void*) object);
else version (Windows) else version (Windows)
_aligned_free(cast(void*) object); _aligned_free(cast(void*) object);
else version (ECSEmscripten) else version (Emscripten)
free(cast(void*) object); free(cast(void*) object);
else else
static assert(0, "Unimplemented platform!"); static assert(0, "Unimplemented platform!");
@ -351,7 +345,7 @@ static struct Mallocator
struct Mutex struct Mutex
{ {
version (ECSEmscripten) version (Emscripten)
{ {
void initialize() nothrow @nogc void initialize() nothrow @nogc
{ {

View file

@ -1,7 +1,7 @@
/************************************************************************************************************************ /************************************************************************************************************************
System module. System module.
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.system; module bubel.ecs.system;
@ -13,14 +13,14 @@ import bubel.ecs.manager;
System contain data required to proper glue EntityManager with Systems. System contain data required to proper glue EntityManager with Systems.
System callbacks: System callbacks:
$(LIST $(LIST
* void onUpdate(EntitesData); * void onUpdate(EntitiesData);
* void onEnable() - called inside system.enable() function * void onEnable() - called inside system.enable() function
* void onDisable() - called inside system.disable() function * void onDisable() - called inside system.disable() function
* bool onBegin() - called inside manager.begin() * bool onBegin() - called inside manager.begin()
* void onEnd() - called inside manager.end() * void onEnd() - called inside manager.end()
* void onCreate() - called after registration inside registerSystem function * void onCreate() - called after registration inside registerSystem function
* void onDestroy() - called during re-registration and inside manager destructor * 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 onAddEntity(EntitiesData) - 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 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 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 * void handleEvent(Entity*, Event) - called for every event supported by system
@ -89,38 +89,100 @@ struct System
return cast(const(char)[]) m_name; return cast(const(char)[]) m_name;
} }
/************************************************************************************************************************
Return false if system was unregistered, true otherwise.
*/
export bool isAlive() nothrow @nogc
{
return m_system_pointer != null;
}
/************************************************************************************************************************
Return pointer to user side system object
*/
export void* ptr() nothrow @nogc
{
return m_system_pointer;
}
package: package:
void destroy() ///destory system. Dispose all data
export void destroy() nothrow @nogc
{
import bubel.ecs.std : Mallocator;
destroySystemData();
if (m_name)
{
Mallocator.dispose(m_name);
m_name = null;
}
}
///destroy all system data but keeps name which is used for case of system re-registration
void destroySystemData() nothrow @nogc
{ {
import bubel.ecs.std : Mallocator; import bubel.ecs.std : Mallocator;
disable(); disable();
if (m_destroy) if (m_destroy)
(cast(void function(void*)) m_destroy)(m_system_pointer); {
(cast(void function(void*) nothrow @nogc) m_destroy)(m_system_pointer);
m_destroy = null;
}
if (m_name)
Mallocator.dispose(m_name);
if (m_components) if (m_components)
{
Mallocator.dispose(m_components); Mallocator.dispose(m_components);
m_components = null;
}
if (m_excluded_components) if (m_excluded_components)
{
Mallocator.dispose(m_excluded_components); Mallocator.dispose(m_excluded_components);
m_excluded_components = null;
}
if (m_optional_components) if (m_optional_components)
{
Mallocator.dispose(m_optional_components); Mallocator.dispose(m_optional_components);
m_optional_components = null;
}
if (jobs) if (jobs)
{
Mallocator.dispose(jobs); Mallocator.dispose(jobs);
jobs = null;
}
if (m_read_only_components) if (m_read_only_components)
{
Mallocator.dispose(m_read_only_components); Mallocator.dispose(m_read_only_components);
m_read_only_components = null;
}
if (m_writable_components) if (m_writable_components)
{
Mallocator.dispose(m_writable_components); Mallocator.dispose(m_writable_components);
m_writable_components = null;
}
if (m_readonly_dependencies) if (m_readonly_dependencies)
{
Mallocator.dispose(m_readonly_dependencies); Mallocator.dispose(m_readonly_dependencies);
m_readonly_dependencies = null;
}
if (m_writable_dependencies) if (m_writable_dependencies)
{
Mallocator.dispose(m_writable_dependencies); Mallocator.dispose(m_writable_dependencies);
m_writable_dependencies = null;
}
if (m_event_callers) if (m_event_callers)
{
Mallocator.dispose(m_event_callers); Mallocator.dispose(m_event_callers);
m_event_callers = null;
}
if (m_system_pointer) if (m_system_pointer)
{
Mallocator.dispose(m_system_pointer); Mallocator.dispose(m_system_pointer);
m_system_pointer = null;
}
} }
struct EventCaller struct EventCaller

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.traits; module bubel.ecs.traits;
@ -11,8 +11,11 @@ import std.traits;
*/ */
ref ushort becsID(T)() ref ushort becsID(T)()
{ {
__gshared ushort id = ushort.max; /// Embed id in struct so export can be added to variable definition
return id; static struct LocalStruct {
export __gshared ushort id = ushort.max;
}
return LocalStruct.id;
} }
/************************************************************************************************************************ /************************************************************************************************************************

View file

@ -1,5 +1,5 @@
/************************************************************************************************************************ /************************************************************************************************************************
Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder. License: BSD 3-clause, see LICENSE file in project root folder.
*/ */
module bubel.ecs.vector; module bubel.ecs.vector;
@ -14,13 +14,13 @@ import bubel.ecs.std;
import std.conv : emplace; import std.conv : emplace;
import std.traits : hasMember, isCopyable, TemplateOf, Unqual; import std.traits : hasMember, isCopyable, TemplateOf, Unqual;
@nogc @safe nothrow pure size_t nextPow2(size_t num) export @nogc @safe nothrow pure size_t nextPow2(size_t num)
{ {
return 1 << bsr(num) + 1; return 1 << bsr(num) + 1;
} }
__gshared size_t gVectorsCreated = 0; export __gshared size_t gVectorsCreated = 0;
__gshared size_t gVectorsDestroyed = 0; export __gshared size_t gVectorsDestroyed = 0;
struct Vector(T) struct Vector(T)
{ {
@ -28,19 +28,19 @@ struct Vector(T)
size_t used; size_t used;
public: public:
this()(T t) export this()(T t)
{ {
add(t); add(t);
} }
this(X)(X[] t) if (is(Unqual!X == Unqual!T)) export this(X)(X[] t) if (is(Unqual!X == Unqual!T))
{ {
add(t); add(t);
} }
/*static if (isCopyable!T) { /*static if (isCopyable!T) {
this(this) { export this(this) {
T[] tmp = array[0 .. used]; T[] tmp = array[0 .. used];
array = null; array = null;
used = 0; used = 0;
@ -52,17 +52,17 @@ public:
@disable this(this); @disable this(this);
~this() export ~this()
{ {
clear(); clear();
} }
void clear() export void clear()
{ {
removeAll(); removeAll();
} }
void removeAll() export void removeAll()
{ {
if (array !is null) if (array !is null)
{ {
@ -77,17 +77,17 @@ public:
used = 0; used = 0;
} }
bool empty() const export bool empty() const
{ {
return (used == 0); return (used == 0);
} }
size_t length() const export size_t length() const
{ {
return used; return used;
} }
void length(size_t newLength) export void length(size_t newLength)
{ {
if (newLength > used) if (newLength > used)
{ {
@ -111,12 +111,12 @@ public:
used = newLength; used = newLength;
} }
void reset() export void reset()
{ {
used = 0; used = 0;
} }
void reserve(size_t numElements) export void reserve(size_t numElements)
{ {
if (numElements > array.length) if (numElements > array.length)
{ {
@ -124,12 +124,12 @@ public:
} }
} }
size_t capacity() export size_t capacity()
{ {
return array.length - used; return array.length - used;
} }
void extend(size_t newNumOfElements) export void extend(size_t newNumOfElements)
{ {
auto oldArray = manualExtend(array, newNumOfElements); auto oldArray = manualExtend(array, newNumOfElements);
if (oldArray !is null) if (oldArray !is null)
@ -138,14 +138,14 @@ public:
} }
} }
@nogc void freeData(void[] data) export @nogc void freeData(void[] data)
{ {
// 0x0F probably invalid value for pointers and other types // 0x0F probably invalid value for pointers and other types
memset(data.ptr, 0x0F, data.length); // Makes bugs show up xD memset(data.ptr, 0x0F, data.length); // Makes bugs show up xD
free(data.ptr); free(data.ptr);
} }
static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0) export static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0)
{ {
if (newNumOfElements == 0) if (newNumOfElements == 0)
newNumOfElements = 2; newNumOfElements = 2;
@ -161,7 +161,7 @@ public:
return (cast(void*) oldArray.ptr)[0 .. oldArray.length * T.sizeof]; return (cast(void*) oldArray.ptr)[0 .. oldArray.length * T.sizeof];
} }
Vector!T copy()() export Vector!T copy()()
{ {
Vector!T duplicate; Vector!T duplicate;
duplicate.reserve(used); duplicate.reserve(used);
@ -169,12 +169,12 @@ public:
return duplicate; return duplicate;
} }
/*bool canAddWithoutRealloc(uint elemNum = 1) /*export bool canAddWithoutRealloc(uint elemNum = 1)
{ {
return used + elemNum <= array.length; return used + elemNum <= array.length;
}*/ }*/
void add()(T t) export void add()(T t)
{ {
if (used >= array.length) if (used >= array.length)
{ {
@ -185,7 +185,7 @@ public:
} }
/// Add element at given position moving others /// Add element at given position moving others
void add()(T t, size_t pos) export void add()(T t, size_t pos)
{ {
assert(pos <= used); assert(pos <= used);
if (used >= array.length) if (used >= array.length)
@ -201,7 +201,7 @@ public:
used++; used++;
} }
void add(X)(X[] t) if (is(Unqual!X == Unqual!T)) export void add(X)(X[] t) if (is(Unqual!X == Unqual!T))
{ {
if (used + t.length > array.length) if (used + t.length > array.length)
{ {
@ -214,7 +214,7 @@ public:
used += t.length; used += t.length;
} }
void remove(size_t elemNum) export void remove(size_t elemNum)
{ {
//destroy(array[elemNum]); //destroy(array[elemNum]);
static if (__traits(hasMember, T, "__xdtor")) static if (__traits(hasMember, T, "__xdtor"))
@ -226,7 +226,7 @@ public:
used--; used--;
} }
void removeStable()(size_t elemNum) export void removeStable()(size_t elemNum)
{ {
used--; used--;
foreach (i; 0 .. used) foreach (i; 0 .. used)
@ -235,7 +235,7 @@ public:
} }
} }
bool tryRemoveElement()(T elem) export bool tryRemoveElement()(T elem)
{ {
foreach (i, ref el; array[0 .. used]) foreach (i, ref el; array[0 .. used])
{ {
@ -248,65 +248,65 @@ public:
return false; return false;
} }
void removeElement()(T elem) export void removeElement()(T elem)
{ {
bool ok = tryRemoveElement(elem); bool ok = tryRemoveElement(elem);
assert(ok, "There is no such an element in vector"); assert(ok, "There is no such an element in vector");
} }
ref T opIndex(size_t elemNum) const export ref T opIndex(size_t elemNum) const
{ {
//debug assert(elemNum < used, "Range violation [index]"); //debug assert(elemNum < used, "Range violation [index]");
return *cast(T*)&array.ptr[elemNum]; return *cast(T*)&array.ptr[elemNum];
} }
auto opSlice() export auto opSlice()
{ {
return array.ptr[0 .. used]; return array.ptr[0 .. used];
} }
T[] opSlice(size_t x, size_t y) export T[] opSlice(size_t x, size_t y)
{ {
assert(y <= used); assert(y <= used);
return array.ptr[x .. y]; return array.ptr[x .. y];
} }
size_t opDollar() export size_t opDollar()
{ {
return used; return used;
} }
void opAssign(X)(X[] slice) export void opAssign(X)(X[] slice)
{ {
reset(); reset();
this ~= slice; this ~= slice;
} }
void opOpAssign(string op)(T obj) export void opOpAssign(string op)(T obj)
{ {
//static assert(op == "~"); //static assert(op == "~");
add(obj); add(obj);
} }
void opOpAssign(string op, X)(X[] obj) export void opOpAssign(string op, X)(X[] obj)
{ {
//static assert(op == "~"); //static assert(op == "~");
add(obj); add(obj);
} }
void opIndexAssign()(T obj, size_t elemNum) export void opIndexAssign()(T obj, size_t elemNum)
{ {
assert(elemNum < used, "Range viloation"); assert(elemNum < used, "Range viloation");
array[elemNum] = obj; array[elemNum] = obj;
} }
void opSliceAssign()(T[] obj, size_t a, size_t b) export void opSliceAssign()(T[] obj, size_t a, size_t b)
{ {
assert(b <= used && a <= b, "Range viloation"); assert(b <= used && a <= b, "Range viloation");
array.ptr[a .. b] = obj; array.ptr[a .. b] = obj;
} }
bool opEquals()(auto ref const Vector!(T) r) const export bool opEquals()(auto ref const Vector!(T) r) const
{ {
return used == r.used && array.ptr[0 .. used] == r.array.ptr[0 .. r.used]; return used == r.used && array.ptr[0 .. used] == r.array.ptr[0 .. r.used];
} }

View file

@ -113,6 +113,31 @@ struct EmptySystem
int count = 0; int count = 0;
} }
struct EntityCounterSystem
{
mixin ECS.System!1;
struct EntitiesData
{
int thread_id;
uint length;
Entity[] entity;
}
bool onBegin()
{
count = 0;
return true;
}
void onUpdate(EntitiesData data)
{
count += data.length;
}
int count = 0;
}
void beforeEveryTest() void beforeEveryTest()
{ {
becsID!CUnregistered = ushort.max; becsID!CUnregistered = ushort.max;
@ -243,7 +268,82 @@ unittest
gEntityManager.commit(); gEntityManager.commit();
entity3 = gEntityManager.getEntity(id); entity3 = gEntityManager.getEntity(id);
assert(!entity3.getComponent!CUnregistered); assert(!entity3.getComponent!CUnregistered);
}
@("AddEmptyEntity")
unittest
{
struct OnAddRemoveChangeCounter
{
mixin ECS.System!1;
struct EntitiesData
{
int thread_id;
uint length;
Entity[] entity;
}
void onAddEntity(EntitiesData data)
{
add += data.length;
}
void onRemoveEntity(EntitiesData data)
{
assert(0, "It's impossible to remove entity from being updated by system which accept empty entity");
}
int add = 0;
}
gEntityManager.beginRegister();
gEntityManager.registerSystem!EntityCounterSystem(0);
gEntityManager.registerSystem!OnAddRemoveChangeCounter(1);
gEntityManager.endRegister();
CLong long_component = CLong(3);
Entity* entity = null;
EntityID entity_id = gEntityManager.addEntity(null).id;
EntityCounterSystem* system = gEntityManager.getSystem!EntityCounterSystem;
assert(system !is null);
assert(system.count == 0);
OnAddRemoveChangeCounter* add_remove_change_system = gEntityManager.getSystem!OnAddRemoveChangeCounter;
assert(add_remove_change_system !is null);
assert(add_remove_change_system.add == 0);
gEntityManager.commit();
assert(add_remove_change_system.add == 1);
entity = gEntityManager.getEntity(entity_id);
assert(!entity.hasComponent(becsID!CLong));
assert(entity.getComponent(becsID!CLong) is null);
gEntityManager.begin();
gEntityManager.update();
assert(system.count == 1);
gEntityManager.end();
gEntityManager.addEntityCopy(entity_id);
gEntityManager.addEntityCopy(entity_id);
gEntityManager.addComponents(entity_id, [ComponentRef(&long_component, becsID(long_component))].staticArray);
gEntityManager.commit();
assert(add_remove_change_system.add == 3, "onAddEntity missed");
entity = gEntityManager.getEntity(entity_id);
assert(entity.hasComponent(becsID!CLong));
assert(*entity.getComponent!CLong == 3);
gEntityManager.begin();
gEntityManager.update();
assert(system.count == 3);
gEntityManager.end();
} }
//allocate templates //allocate templates

View file

@ -144,3 +144,32 @@ unittest
gEntityManager.destroy(); gEntityManager.destroy();
} }
@("2-empty-entity-crash")
unittest
{
gEntityManager.initialize(0);
gEntityManager.beginRegister();
gEntityManager.registerComponent!CInt;
gEntityManager.registerComponent!CFloat;
gEntityManager.endRegister();
EntityTemplate* tmpl = gEntityManager.allocateTemplate([becsID!CInt, becsID!CFloat].staticArray);
scope(exit) gEntityManager.freeTemplate(tmpl);
EntityID id = gEntityManager.addEntity(tmpl).id;
// EntityID id2 = gEntityManager.addEntity().id;
gEntityManager.commit();
gEntityManager.removeComponents(id, [becsID!CInt, becsID!CFloat].staticArray);
gEntityManager.commit();
gEntityManager.destroy();
}