Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

27 changed files with 415 additions and 572 deletions

5
.gitignore vendored
View file

@ -1,8 +1,9 @@
* *
!*/
!source/** !source/**
!tests/** !tests/**
!README.md !README.md
!dub.json !./dub.json
!.gitignore !.gitignore
!codecov.yml !codecov.yml
!skeleton.html !skeleton.html
@ -11,5 +12,3 @@
!meson_options.txt !meson_options.txt
!compile_wasm.py !compile_wasm.py
!compile_android.py !compile_android.py
!.gitlab-ci.yml
!LICENSE

View file

@ -23,7 +23,7 @@ build_code:
test_dmd_debug: test_dmd_debug:
stage: test stage: test
image: debian:buster-slim image: frolvlad/alpine-glibc
script: script:
- binaries/dmd_debug_unittest - binaries/dmd_debug_unittest
artifacts: artifacts:
@ -31,7 +31,7 @@ test_dmd_debug:
junit: test_report.xml junit: test_report.xml
test_dmd: test_dmd:
stage: test stage: test
image: debian:buster-slim image: frolvlad/alpine-glibc
script: script:
- binaries/dmd_release_unittest - binaries/dmd_release_unittest
artifacts: artifacts:
@ -39,7 +39,7 @@ test_dmd:
junit: test_report.xml junit: test_report.xml
test_dmd_betterC: test_dmd_betterC:
stage: test stage: test
image: debian:buster-slim image: frolvlad/alpine-glibc
script: script:
- binaries/dmd_debug_unittest_bc - binaries/dmd_debug_unittest_bc
artifacts: artifacts:
@ -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 df87b1d8-85f4-4584-96e3-1315d27ec2c5 - bash <(curl -s https://codecov.io/bash) -s reports -t 1a0c0169-a721-4085-8252-fed4755dcd8c
wasm: wasm:
stage: build_wasm stage: build_wasm

View file

@ -10,13 +10,6 @@ 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-emscripten -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' ' ldc_cmd = 'ldc2 ' + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-unknown-wasm -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 = '' ldc_flags = '--d-version=ECSEmscripten '
import_paths = ['source','tests'] import_paths = ['source','tests']
build_tests = 0 build_tests = 0

View file

@ -1,6 +1,7 @@
import os import os
import ntpath import ntpath
import sys import sys
import imp
def compile(sources, output): def compile(sources, output):
files = [] files = []
@ -13,7 +14,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-emscripten -betterC --output-bc --od=.bc --singleobj --checkaction=C --of=' + output + ' ' ldc_cmd = compiler + shared_flags + ldc_flags + '-oq -mtriple=wasm32-unknown-unknown-wasm -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 + ' '
@ -40,7 +41,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=SDL_209 --d-version=BindSDL_Static --d-version=BindSDL_Image --d-version=MM_USE_POSIX_THREADS ' ldc_flags = '--d-version=ECSEmscripten --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:]:
@ -120,9 +121,8 @@ 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

@ -19,7 +19,7 @@ sdl2_image_dep = dependency('SDL2_image')
subdir('utils') # Utils library subdir('utils') # Utils library
executable('BubelECSDemos', [demos_src, external_src], executable('decs-demos', [demos_src, external_src],
include_directories : [demos_inc, external_inc], include_directories : [demos_inc, external_inc],
d_module_versions : versions, d_module_versions : versions,
link_with : [ecs_lib, ecs_utils_lib], link_with : [ecs_lib, ecs_utils_lib],
@ -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,
bubel_ecs_dep, decs_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('ECSUtils', utils_src, ecs_utils_lib = library('ecs_utils', 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 : [
bubel_ecs_dep, decs_dep,
bindbc_loader_dep, bindbc_loader_dep,
bindbc_sdl_dep, bindbc_sdl_dep,
] ]

View file

@ -1,11 +1,11 @@
{ {
"name": "bubel-ecs", "name": "bubel_ecs",
"targetName" : "BubelECS", "targetName" : "bubel_ecs",
"authors": [ "authors": [
"Michał Masiukiewicz", "Dawid Masiukiewicz" "Michał Masiukiewicz", "Dawid Masiukiewicz"
], ],
"description": "Dynamic Entity Component System", "description": "Dynamic Entity Component System",
"copyright": "Copyright © 2018-2023, Michał Masiukiewicz, Dawid Masiukiewicz", "copyright": "Copyright © 2018-2019, 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=ctfe" "-cov"
] ]
}, },
{ {

View file

@ -1,4 +1,4 @@
project('bubel-ecs', 'd', version : '0.5.0') project('decs', 'd', version : '0.5.0')
# Options # Options
betterC_opt = get_option('betterC') betterC_opt = get_option('betterC')
@ -49,36 +49,23 @@ if betterC_opt
endif endif
endif endif
add_project_arguments(args, language : 'd') add_global_arguments(args, language : 'd')
add_project_link_arguments(link_args, language : 'd') add_global_link_arguments(link_args, language : 'd')
# Dependencies # Dependencies
threads_dep = dependency('threads') threads_dep = dependency('threads')
d_versions = [] ecs_lib = library('decs',
deps = []
if host_machine.cpu_family() == 'wasm32'
else
# meson incorectly adds "-s USE_PTHREADS=1" to ldc2 invocation whe pthreads is added as dependency
# add it for non wasm builds
deps += threads_dep
endif
ecs_lib = library('BubelECS',
src, src,
d_module_versions : d_versions,
include_directories : [inc], include_directories : [inc],
) )
bubel_ecs_dep = declare_dependency( decs_dep = declare_dependency(
include_directories : [inc], include_directories : [inc],
link_with : ecs_lib, link_with : ecs_lib,
dependencies : deps, dependencies : threads_dep,
) )
meson.override_dependency('bubel-ecs', bubel_ecs_dep)
# Tests # Tests
if BuildTests_opt if BuildTests_opt
subdir('tests') subdir('tests')

View file

@ -4,10 +4,129 @@ 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-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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;
public import core.atomic; version (Emscripten) version = ECSEmscripten;
version (ECSEmscripten)
{
import std.traits;
enum MemoryOrder
{
acq,
acq_rel,
raw,
rel,
seq
}
extern (C) ubyte emscripten_atomic_cas_u8(void* addr, ubyte oldVal, ubyte newVal) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_cas_u16(void* addr, ushort oldVal, ushort newVal) @nogc nothrow pure;
extern (C) uint emscripten_atomic_cas_u32(void* addr, uint oldVal, uint newVal) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_load_u8(const void* addr) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_load_u16(const void* addr) @nogc nothrow pure;
extern (C) uint emscripten_atomic_load_u32(const void* addr) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_store_u8(void* addr, ubyte val) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_store_u16(void* addr, ushort val) @nogc nothrow pure;
extern (C) uint emscripten_atomic_store_u32(void* addr, uint val) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_add_u8(void* addr, ubyte val) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_add_u16(void* addr, ushort val) @nogc nothrow pure;
extern (C) uint emscripten_atomic_add_u32(void* addr, uint val) @nogc nothrow pure;
extern (C) ubyte emscripten_atomic_sub_u8(void* addr, ubyte val) @nogc nothrow pure;
extern (C) ushort emscripten_atomic_sub_u16(void* addr, ushort val) @nogc nothrow pure;
extern (C) uint emscripten_atomic_sub_u32(void* addr, uint val) @nogc nothrow pure;
public pure nothrow @nogc Unqual!T atomicOp(string op, T, V1)(ref shared T val, V1 mod)
{
static if (op == "+=")
{
static if (is(T == byte) || is(T == ubyte))
return cast(Unqual!T)(emscripten_atomic_add_u8(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else static if (is(T == short) || is(T == ushort))
return cast(Unqual!T)(emscripten_atomic_add_u16(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else static if (is(T == int) || is(T == uint))
return cast(Unqual!T)(emscripten_atomic_add_u32(cast(void*)&val,
cast(Unqual!T) mod) + 1);
else
static assert(0);
}
else static if (op == "-=")
{
static if (is(T == byte) || is(T == ubyte))
return cast(Unqual!T)(emscripten_atomic_sub_u8(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else static if (is(T == short) || is(T == ushort))
return cast(Unqual!T)(emscripten_atomic_sub_u16(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else static if (is(T == int) || is(T == uint))
return cast(Unqual!T)(emscripten_atomic_sub_u32(cast(void*)&val,
cast(Unqual!T) mod) - 1);
else
static assert(0);
}
}
public pure nothrow @nogc @trusted void atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val,
V newval)
{
alias UT = Unqual!T;
static if (is(UT == bool) || is(UT == byte) || is(UT == ubyte))
emscripten_atomic_store_u8(cast(void*)&val, cast(UT) newval);
else static if (is(UT == short) || is(UT == ushort))
emscripten_atomic_store_u16(cast(void*)&val, cast(UT) newval);
else static if (is(UT == int) || is(UT == uint))
emscripten_atomic_store_u32(cast(void*)&val, cast(UT) newval);
else
static assert(0);
}
public pure nothrow @nogc @trusted T atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(
ref const T val)
{
alias UT = Unqual!T;
static if (is(UT == bool))
return emscripten_atomic_load_u8(cast(const void*)&val) != 0;
else static if (is(UT == byte) || is(UT == ubyte))
return emscripten_atomic_load_u8(cast(const void*)&val);
else static if (is(UT == short) || is(UT == ushort))
return emscripten_atomic_load_u16(cast(const void*)&val);
else static if (is(UT == int) || is(UT == uint))
return emscripten_atomic_load_u32(cast(const void*)&val);
else
static assert(0);
}
public pure nothrow @nogc @trusted bool cas(MemoryOrder succ = MemoryOrder.seq,
MemoryOrder fail = MemoryOrder.seq, T, V1, V2)(T* here, V1 ifThis, V2 writeThis)
{
alias UT = Unqual!T;
static if (is(UT == bool))
return emscripten_atomic_cas_u8(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else static if (is(UT == byte) || is(UT == ubyte))
return emscripten_atomic_cas_u8(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else static if (is(UT == short) || is(UT == ushort))
return emscripten_atomic_cas_u16(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else static if (is(UT == int) || is(UT == uint))
return emscripten_atomic_cas_u32(cast(void*) here,
cast(Unqual!T) ifThis, cast(Unqual!T) writeThis) == ifThis;
else
static assert(0);
}
}
else
{
public import core.atomic;
}

View file

@ -17,7 +17,7 @@ Struct EntitiesData
} }
--- ---
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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;
@ -94,7 +94,7 @@ static struct ECS
*/ */
mixin template Component() mixin template Component()
{ {
deprecated ComponentRef ref_() @nogc nothrow return ComponentRef ref_() @nogc nothrow return
{ {
return ComponentRef(&this, becsID!(typeof(this))); return ComponentRef(&this, becsID!(typeof(this)));
} }

View file

@ -1,7 +1,7 @@
/************************************************************************************************************************ /************************************************************************************************************************
Entity module. Entity module.
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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);
} }
export void* getComponent(ushort component_id) const 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);
} }
export bool hasComponent(ushort component_id) const 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;
} }
export EntityMeta getMeta() const 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);
} }
export void* getComponent(ushort component_id) const 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);
} }
export bool hasComponent(ushort component_id) const 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.
*/ */
export void* getComponent(ushort component_id) const nothrow @nogc 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,7 +1,3 @@
/************************************************************************************************************************
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.events; module bubel.ecs.events;
import bubel.ecs.block_allocator; import bubel.ecs.block_allocator;

View file

@ -1,8 +1,4 @@
/************************************************************************************************************************ module bubel.ecs.hash_map;
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.hash_map;
import std.traits; import std.traits;
@ -28,7 +24,7 @@ export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc
} }
} }
export ulong hash(byte[] array) nothrow @nogc ulong hash(byte[] array) nothrow @nogc
{ {
ulong hash = 0; ulong hash = 0;
@ -336,36 +332,36 @@ nothrow:
return result; return result;
} }
export int byKey(scope int delegate(ref Key k) dg) export int byKey(scope int delegate(Key k) nothrow dg)
{ {
int result; int result;
foreach (ref Key k; this) foreach (ref Key k; this)
{ {
result = (cast(int delegate(ref Key k) nothrow @nogc)dg)(k); result = dg(k);
if (result) if (result)
break; break;
} }
return result; return result;
} }
export int byValue(scope int delegate(ref Value v) dg) export int byValue(scope int delegate(ref Value k) nothrow dg)
{ {
int result; int result;
foreach (ref Value v; this) foreach (ref Value v; this)
{ {
result = (cast(int delegate(ref Value v) nothrow @nogc)dg)(v); result = dg(v);
if (result) if (result)
break; break;
} }
return result; return result;
} }
export int byKeyValue(scope int delegate(ref Key k, ref Value v) dg) export int byKeyValue(scope int delegate(ref Key k, ref Value v) nothrow 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 @nogc)dg)(k, v); result = dg(k, v);
if (result) if (result)
break; break;
} }

View file

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

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-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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,6 +10,10 @@ 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;
@ -27,8 +31,6 @@ 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.
@ -71,14 +73,17 @@ export struct EntityManager
{ {
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)
{ {
UpdatePass* pass = Mallocator.make!UpdatePass; UpdatePass* pass = Mallocator.make!UpdatePass;
pass.name = Mallocator.makeArray(cast(char[]) "update"); pass.name = Mallocator.makeArray(cast(char[]) "update");
//pass.name = Mallocator.makeArray!char("update".length);
//pass.name[0..$] = "update";
passes.add(pass); passes.add(pass);
passes_map.add(cast(const(char)[]) pass.name, cast(ushort)(passes.length - 1)); passes_map.add(cast(string) pass.name, cast(ushort)(passes.length - 1));
} }
} }
} }
@ -86,7 +91,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() nothrow @nogc export static void destroy()
{ {
if (gEntityManager is null) if (gEntityManager is null)
return; return;
@ -169,8 +174,6 @@ 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)
@ -235,8 +238,6 @@ 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]++;
@ -253,8 +254,6 @@ 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;
@ -298,6 +297,7 @@ export struct EntityManager
if (threads_count == 0) if (threads_count == 0)
threads_count = 1; threads_count = 1;
threads = Mallocator.makeArray!ThreadData(threads_count); threads = Mallocator.makeArray!ThreadData(threads_count);
//foreach(ref thread;threads)thread = ThreadData().init;
m_page_size = page_size; m_page_size = page_size;
m_pages_in_block = block_pages_count; m_pages_in_block = block_pages_count;
@ -331,37 +331,13 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -383,7 +359,7 @@ export struct EntityManager
assert(register_state, assert(register_state,
"registerSystem must be called between beginRegister() and endRegister()."); "registerSystem must be called between beginRegister() and endRegister().");
version (NoDRuntime) version (D_BetterC)
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.");
@ -392,7 +368,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))
@ -492,7 +468,7 @@ export struct EntityManager
static if (isArray!MemberType) static if (isArray!MemberType)
{ // Workaround. This code is never called with: not an array type, but compiler prints an error { // Workaround. This code is never called with: not an array type, but compiler prints an error
// name = fullyQualifiedName!(Unqual!(ForeachType!MemberType));//.stringof; // name = fullyQualifiedName!(Unqual!(ForeachType!MemberType));//.stringof;
name = fullName!(Unqual!(typeof(MemberType.init[0]))); name = fullName!(Unqual!(ForeachType!MemberType));
} }
bool is_optional; bool is_optional;
@ -718,7 +694,7 @@ export struct EntityManager
static if (isArray!MemberType) static if (isArray!MemberType)
{ // Workaround. This code is never called with: not an array type, but compiler prints an error { // Workaround. This code is never called with: not an array type, but compiler prints an error
// name = fullyQualifiedName!(Unqual!(ForeachType!MemberType)); // name = fullyQualifiedName!(Unqual!(ForeachType!MemberType));
name = fullName!(Unqual!(typeof(MemberType.init[0]))); name = fullName!(Unqual!(ForeachType!MemberType));
//name = Unqual!(ForeachType!MemberType).stringof; //name = Unqual!(ForeachType!MemberType).stringof;
} }
@ -842,7 +818,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -854,7 +830,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -866,7 +842,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -878,7 +854,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -890,7 +866,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -926,11 +902,10 @@ export struct EntityManager
static foreach (iii, comp_info; components_info.m_req[0 static foreach (iii, comp_info; components_info.m_req[0
.. components_info.m_req_counter]) .. components_info.m_req_counter])
{ {
__traits(getMember, input_data, comp_info.name) = ( __traits(getMember, input_data, comp_info.name) = (cast(ForeachType!(typeof(__traits(getMember,
cast(typeof( Sys.EntitiesData, comp_info.name)))*)(
(typeof(__traits(getMember, Sys.EntitiesData, comp_info.name))).init[0] cast(void*) block + info.deltas[system.m_components[iii]]))[offset
)*)(cast(void*) block + info.deltas[system.m_components[iii]]) .. entities_count];
)[offset .. entities_count];
} }
static foreach (iii, comp_info; components_info.m_optional[0 static foreach (iii, comp_info; components_info.m_optional[0
@ -1182,10 +1157,8 @@ 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")) static if(__traits(hasMember, Sys ,"__becs_jobs_count"))system.jobs = Mallocator.makeArray!(Job)(Sys.__becs_jobs_count);
system.jobs = Mallocator.makeArray!(Job)(Sys.__becs_jobs_count); else system.jobs = Mallocator.makeArray!(Job)(32);
else
system.jobs = Mallocator.makeArray!(Job)(32);
static if (OnUpdateOverloadNum != -1) static if (OnUpdateOverloadNum != -1)
{ {
@ -1200,7 +1173,7 @@ export struct EntityManager
{ {
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 (NoDRuntime) version (D_BetterC)
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.");
@ -1213,7 +1186,7 @@ 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 (NoDRuntime) version (D_BetterC)
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.");
@ -1257,8 +1230,7 @@ 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
{ {
@ -1281,6 +1253,8 @@ export struct EntityManager
{ {
UpdatePass* pass = Mallocator.make!UpdatePass; UpdatePass* pass = Mallocator.make!UpdatePass;
pass.name = Mallocator.makeArray(cast(char[]) name); pass.name = Mallocator.makeArray(cast(char[]) name);
/*pass.name = Mallocator.makeArray!char(name.length);
pass.name[0..$] = name[0..$];*/
passes.add(pass); passes.add(pass);
passes_map.add(name, cast(ushort)(passes.length - 1)); passes_map.add(name, cast(ushort)(passes.length - 1));
return cast(ushort)(passes.length - 1); return cast(ushort)(passes.length - 1);
@ -1336,8 +1310,7 @@ export struct EntityManager
info.size = Comp.sizeof; info.size = Comp.sizeof;
info.alignment = Comp.alignof; //8; info.alignment = Comp.alignof; //8;
info.init_data = Mallocator.makeArray!ubyte(Comp.sizeof); info.init_data = Mallocator.makeArray!ubyte(Comp.sizeof);
__gshared Comp init_memory; *cast(Comp*) info.init_data.ptr = Comp.init; // = Comp();
memcpy(info.init_data.ptr, &init_memory, Comp.sizeof);
ushort comp_id = components_map.get(cast(char[]) ComponentName, ushort.max); ushort comp_id = components_map.get(cast(char[]) ComponentName, ushort.max);
if (comp_id < components.length) if (comp_id < components.length)
@ -1412,8 +1385,6 @@ 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;
@ -1858,7 +1829,6 @@ 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);
@ -1873,29 +1843,21 @@ 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.components = Mallocator.makeArray!ushort(ids.length);
info.components[0 .. $] = ids[0 .. $];*/
info.deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1);
info.size = EntityID.sizeof; info.size = EntityID.sizeof;
info.alignment = EntityID.alignof; info.alignment = EntityID.alignof;
if(ids is null)
{
uint block_memory = cast(uint)(
m_page_size - EntitiesBlock.sizeof - info.size);
uint entites_in_block = block_memory / info.size;
info.max_entities = cast(ushort) entites_in_block;
}
else
{
uint components_size = EntityID.sizeof;
info.components = Mallocator.makeArray(ids);
info.deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1);
info.tmpl_deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1, ushort.max); info.tmpl_deltas = Mallocator.makeArray!ushort(ids[$ - 1] + 1, ushort.max);
uint components_size = EntityID.sizeof;
foreach (i, id; ids) foreach (i, id; ids)
{ {
@ -1925,22 +1887,10 @@ export struct EntityManager
current_delta += entites_in_block * components[id].size; 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)
@ -1953,6 +1903,16 @@ export struct EntityManager
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);
@ -2103,9 +2063,7 @@ 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 if(system.m_filter_entity && !(cast(bool function(void* system_pointer, EntityInfo* info) @nogc nothrow)system.m_filter_entity)(system, &entity))return;
.m_filter_entity)(system, &entity))
return;
entity.systems[system_id] = true; entity.systems[system_id] = true;
} }
@ -2150,8 +2108,7 @@ 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]) if(!info.systems[system_id])return;
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++)
@ -2467,11 +2424,29 @@ export struct EntityManager
void addComponents(Components...)(const EntityID entity_id, Components comps) nothrow @nogc void addComponents(Components...)(const EntityID entity_id, Components comps) nothrow @nogc
{ {
const uint num = Components.length; const uint num = Components.length;
/*ushort[num] new_ids;
static foreach (i, comp; Components)
{
new_ids[i] = comp.component_id;
}
ThreadData* data = &threads[threadID];
data.changeEntitiesList.add(cast(ubyte) 1u);
data.changeEntitiesList.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]);
data.changeEntitiesList.add((cast(ubyte*)&num)[0 .. uint.sizeof]);
data.changeEntitiesList.add(cast(ubyte[]) new_ids);
static foreach (i, comp; comps)
{
data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]);
}*/
//__addComponents(entity_id, new_ids, pointers);
ComponentRef[num] _comps; ComponentRef[num] _comps;
static foreach (i, comp; comps) static foreach (i, comp; comps)
{ {
_comps[i] = ComponentRef(&comp, becsID!(typeof(comp))); _comps[i] = comp.ref_;
} }
addComponents(entity_id, _comps); addComponents(entity_id, _comps);
@ -2494,6 +2469,11 @@ export struct EntityManager
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]);
} }
/*data.changeEntitiesList.add(cast(ubyte[]) new_ids);
static foreach (i, comp; comps)
{
data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]);
}*/
} }
/************************************************************************************************************************ /************************************************************************************************************************
@ -2566,7 +2546,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. Can be null in which case empty entity would be added (entity without components) tmpl = pointer entity template allocated by EntityManager.
*/ */
export Entity* addEntity(EntityTemplate* tmpl) export Entity* addEntity(EntityTemplate* tmpl)
{ {
@ -2578,14 +2558,12 @@ 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. Can be null in which case empty entity would be added (entity without components) tmpl = pointer entity template allocated by EntityManager.
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 = void; EntityInfo* info = tmpl.info;
if(tmpl)info = tmpl.info;
else info = getEntityInfo(null);
ushort index = 0; ushort index = 0;
EntitiesBlock* block; EntitiesBlock* block;
@ -3144,8 +3122,6 @@ 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);
@ -3160,9 +3136,6 @@ 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);
} }
@ -3276,6 +3249,8 @@ export struct EntityManager
if (index > 0) if (index > 0)
{ {
caller.exclusion = Mallocator.makeArray(exclusion[0 .. index]); caller.exclusion = Mallocator.makeArray(exclusion[0 .. index]);
/*caller.exclusion = Mallocator.makeArray!(SystemCaller*)(index);
caller.exclusion[0..$] = exclusion[0 .. index];*/
} }
else else
caller.exclusion = null; caller.exclusion = null;
@ -3323,6 +3298,8 @@ export struct EntityManager
if (index > 0) if (index > 0)
{ {
caller.dependencies = Mallocator.makeArray(exclusion[0 .. index]); caller.dependencies = Mallocator.makeArray(exclusion[0 .. index]);
/*caller.dependencies = Mallocator.makeArray!(SystemCaller*)(index);
caller.dependencies[0..$] = exclusion[0 .. index];*/
caller.job_group.dependencies = Mallocator.makeArray!(JobGroup*)(index); caller.job_group.dependencies = Mallocator.makeArray!(JobGroup*)(index);
foreach (j, dep; caller.dependencies) foreach (j, dep; caller.dependencies)
@ -3357,16 +3334,6 @@ 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
@ -3472,7 +3439,7 @@ export struct EntityManager
break; break;
} }
} }
if (components.length == 0 || id > components[$ - 1]) if (id > components[$ - 1])
ids[len++] = id; ids[len++] = id;
assert(len == components.length + 1); assert(len == components.length + 1);
@ -3526,8 +3493,7 @@ export struct EntityManager
export bool hasComponent(ushort component_id) export bool hasComponent(ushort component_id)
{ {
if (component_id >= deltas.length || !deltas[component_id]) if(component_id >= deltas.length || !deltas[component_id])return false;
return false;
return true; return true;
} }
@ -3679,6 +3645,7 @@ 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,7 +1,3 @@
/************************************************************************************************************************
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module ecs; module ecs;
public import bubel.ecs.core; public import bubel.ecs.core;

View file

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

View file

@ -2,14 +2,16 @@
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-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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 (Emscripten) version (ECSEmscripten)
{ {
extern (C) struct pthread_mutex_t extern (C) struct pthread_mutex_t
{ {
@ -27,6 +29,10 @@ version (Emscripten)
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;
@ -54,7 +60,7 @@ else
public import core.stdc.stdlib : qsort; public import core.stdc.stdlib : qsort;
} }
version (Emscripten) version (ECSEmscripten)
{ {
} }
else version (Windows) else version (Windows)
@ -83,7 +89,7 @@ else version (Posix)
import core.sys.posix.stdlib : posix_memalign; import core.sys.posix.stdlib : posix_memalign;
} }
version (Emscripten) version (ECSEmscripten)
{ {
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;
@ -288,7 +294,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 (Emscripten) else version (ECSEmscripten)
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!");
@ -335,7 +341,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 (Emscripten) else version (ECSEmscripten)
free(cast(void*) object); free(cast(void*) object);
else else
static assert(0, "Unimplemented platform!"); static assert(0, "Unimplemented platform!");
@ -345,7 +351,7 @@ static struct Mallocator
struct Mutex struct Mutex
{ {
version (Emscripten) version (ECSEmscripten)
{ {
void initialize() nothrow @nogc void initialize() nothrow @nogc
{ {

View file

@ -1,7 +1,7 @@
/************************************************************************************************************************ /************************************************************************************************************************
System module. System module.
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz Copyright: Copyright © 2018-2019, 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(EntitiesData); * void onUpdate(EntitesData);
* 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(EntitiesData) - called for every entity which are assigned to system (by adding new entity or changing its components) * void onAddEntity(EntitesData) - called for every entity which are assigned to system (by adding new entity or changing its components)
* void onRemoveEntity(EntitiesData) - called for every entity removed from system update process * void 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,100 +89,38 @@ 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:
///destory system. Dispose all data void destroy()
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,7 +1,3 @@
/************************************************************************************************************************
Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.traits; module bubel.ecs.traits;
import std.traits; import std.traits;
@ -11,11 +7,8 @@ import std.traits;
*/ */
ref ushort becsID(T)() ref ushort becsID(T)()
{ {
/// Embed id in struct so export can be added to variable definition __gshared ushort id = ushort.max;
static struct LocalStruct { return id;
export __gshared ushort id = ushort.max;
}
return LocalStruct.id;
} }
/************************************************************************************************************************ /************************************************************************************************************************
@ -73,7 +66,7 @@ static string attachParentName(alias T, string str)()
{ {
return attachParentName!(parent, parent_str[7 .. $] ~ '.' ~ str); return attachParentName!(parent, parent_str[7 .. $] ~ '.' ~ str);
} }
else return parent_str[7 .. $] ~ '.' ~ str; else return parent_str[8 .. $] ~ '.' ~ str;
} }
else static if(parent_str[0..8] == "package ") else static if(parent_str[0..8] == "package ")
{ {

View file

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

View file

@ -16,7 +16,7 @@ else import std.array : staticArray;
struct CInt struct CInt
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -25,7 +25,7 @@ struct CInt
struct CFloat struct CFloat
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -34,7 +34,7 @@ struct CFloat
struct CDouble struct CDouble
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -43,7 +43,7 @@ struct CDouble
struct CLong struct CLong
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -52,7 +52,7 @@ struct CLong
struct CShort struct CShort
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -61,7 +61,7 @@ struct CShort
struct CUnregistered struct CUnregistered
{ {
// mixin ECS.Component; mixin ECS.Component;
alias value this; alias value this;
@ -70,7 +70,7 @@ struct CUnregistered
struct CFlag struct CFlag
{ {
// mixin ECS.Component; mixin ECS.Component;
} }
struct LongAddSystem struct LongAddSystem
@ -113,31 +113,6 @@ 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;
@ -207,18 +182,17 @@ unittest
assert(*entity2.getComponent!CInt == 2); assert(*entity2.getComponent!CInt == 2);
assert(*entity2.getComponent!CFloat == 2.0); assert(*entity2.getComponent!CFloat == 2.0);
CInt int1 = CInt(10); //CInt cint = CInt(10);
CLong long1 = CLong(); //CLong clong;
CFlag flag1 = CFlag(); //Entity* entity3 = gEntityManager.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray);
Entity* entity3 = gEntityManager.addEntity(tmpl_, [ComponentRef(&int1, becsID(int1)), ComponentRef(&long1, becsID(long1)), ComponentRef(&flag1, becsID(flag1))].staticArray); Entity* entity3 = gEntityManager.addEntity(tmpl_, [CInt(10).ref_, CLong().ref_, CFlag().ref_].staticArray);
EntityID id = entity3.id; EntityID id = entity3.id;
assert(entity3.hasComponent(becsID!CInt)); assert(entity3.hasComponent(becsID!CInt));
assert(entity3.hasComponent(becsID!CFloat)); assert(entity3.hasComponent(becsID!CFloat));
assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0); assert(*entity3.getComponent!CFloat == 2.0);
CShort short1 = CShort(2); gEntityManager.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_].staticArray);
gEntityManager.addComponents(entity3.id, [ComponentRef(&flag1, becsID(flag1)),ComponentRef(&short1, becsID(short1))].staticArray);
gEntityManager.commit(); gEntityManager.commit();
entity3 = gEntityManager.getEntity(id); entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CInt); assert(entity3.getComponent!CInt);
@ -239,7 +213,7 @@ unittest
assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CInt == 10);
assert(*entity3.getComponent!CFloat == 2.0); assert(*entity3.getComponent!CFloat == 2.0);
gEntityManager.addComponents(entity3.id, [ComponentRef(&flag1, becsID(flag1)),ComponentRef(&short1, becsID(short1))].staticArray); gEntityManager.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_].staticArray);
gEntityManager.removeComponents(entity3.id, [becsID!CUnregistered].staticArray); gEntityManager.removeComponents(entity3.id, [becsID!CUnregistered].staticArray);
gEntityManager.commit(); gEntityManager.commit();
entity3 = gEntityManager.getEntity(id); entity3 = gEntityManager.getEntity(id);
@ -257,8 +231,7 @@ unittest
gEntityManager.endRegister(); gEntityManager.endRegister();
CUnregistered unregistered1 = CUnregistered(4); gEntityManager.addComponents(entity3.id, [CUnregistered(4).ref_].staticArray);
gEntityManager.addComponents(entity3.id, [ComponentRef(&unregistered1, becsID(unregistered1))].staticArray);
gEntityManager.commit(); gEntityManager.commit();
entity3 = gEntityManager.getEntity(id); entity3 = gEntityManager.getEntity(id);
assert(entity3.getComponent!CUnregistered); assert(entity3.getComponent!CUnregistered);
@ -268,82 +241,7 @@ 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

@ -119,12 +119,8 @@ unittest
gEntityManager.endRegister(); gEntityManager.endRegister();
EntityTemplate* tmpl = gEntityManager.allocateTemplate([becsID!CInt, becsID!CLong].staticArray); EntityTemplate* tmpl = gEntityManager.allocateTemplate([becsID!CInt, becsID!CLong].staticArray);
EntityID id = gEntityManager.addEntity(tmpl,[CLong(10).ref_, CInt(6).ref_].staticArray).id;
CLong clong = CLong(10); EntityID id2 = gEntityManager.addEntity(tmpl,[CInt(4).ref_].staticArray).id;
CInt cint = CInt(6);
CInt cint2 = CInt(4);
EntityID id = gEntityManager.addEntity(tmpl,[ComponentRef(&clong, becsID(clong)), ComponentRef(&cint, becsID(cint))].staticArray).id;
EntityID id2 = gEntityManager.addEntity(tmpl,[ComponentRef(&cint2, becsID(cint2))].staticArray).id;
gEntityManager.freeTemplate(tmpl); gEntityManager.freeTemplate(tmpl);
gEntityManager.commit(); gEntityManager.commit();
@ -144,32 +140,3 @@ 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();
}

View file

@ -10,7 +10,7 @@ tests_src = files(
'vector.d' 'vector.d'
) )
exe = executable('BubelECSTests', exe = executable('decs-tests',
tests_src, tests_src,
include_directories : [inc, include_directories('..')], include_directories : [inc, include_directories('..')],
d_args : args, d_args : args,