diff --git a/demos/assets/textures/apple.png b/demos/assets/textures/apple.png new file mode 100644 index 0000000..63d8fb4 Binary files /dev/null and b/demos/assets/textures/apple.png differ diff --git a/demos/assets/textures/atlas.png b/demos/assets/textures/atlas.png new file mode 100644 index 0000000..498f34d Binary files /dev/null and b/demos/assets/textures/atlas.png differ diff --git a/demos/assets/textures/snake.png b/demos/assets/textures/snake.png new file mode 100644 index 0000000..7bc34f0 Binary files /dev/null and b/demos/assets/textures/snake.png differ diff --git a/demos/assets/textures/snake_head.png b/demos/assets/textures/snake_head.png new file mode 100644 index 0000000..9c8dbae Binary files /dev/null and b/demos/assets/textures/snake_head.png differ diff --git a/demos/assets/textures/snake_horizontal.png b/demos/assets/textures/snake_horizontal.png new file mode 100644 index 0000000..1859449 Binary files /dev/null and b/demos/assets/textures/snake_horizontal.png differ diff --git a/demos/compile_wasm.py b/demos/compile_wasm.py index c985693..30d0a0f 100644 --- a/demos/compile_wasm.py +++ b/demos/compile_wasm.py @@ -92,7 +92,7 @@ compile(['source'], 'demo.bc') if clean or os.path.exists('../ecs.bc') == 0 or os.path.isfile('../ecs.bc') == 0: compile(['../source'], '../ecs.bc') -emcc_cmd = 'emcc -v ' + shared_flags + emc_flags + '-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2048MB -s MALLOC=dlmalloc -s WASM=1 -o index.html ' +emcc_cmd = 'emcc -v ' + shared_flags + emc_flags + '-s ERROR_ON_UNDEFINED_SYMBOLS=0 -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -s ALLOW_MEMORY_GROWTH=1 -s MINIFY_HTML=0 -s WASM_MEM_MAX=2048MB -s MALLOC=dlmalloc -s WASM=1 -o ecs_demo.html ' #-s ALLOW_MEMORY_GROWTH=1 -s PROXY_TO_PTHREAD=1 -Wl,--no-check-features -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s TOTAL_MEMORY=512MB emcc_cmd += '../ecs.bc ' diff --git a/demos/source/app.d b/demos/source/app.d index ed4817a..6338035 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -138,6 +138,7 @@ struct CountSystem struct EntitiesData { uint length; + const (Entity)[] entity; } bool onBegin() @@ -749,7 +750,7 @@ void loadGFX() GfxConfig.materials = Mallocator.makeArray!Material(1); GfxConfig.meshes = Mallocator.makeArray!Mesh(1); - float[16] vertices = [-0.5,-0.5, 0,0, -0.5,0.5, 0,1, 0.5,-0.5, 1,0, 0.5,0.5, 1,1]; + float[16] vertices = [-0.5,-0.5, 0,1, -0.5,0.5, 0,0, 0.5,-0.5, 1,1, 0.5,0.5, 1,0]; GfxConfig.meshes[0].vertices = Mallocator.makeArray(vertices); ushort[6] indices = [0,1,2,1,2,3]; GfxConfig.meshes[0].indices = Mallocator.makeArray(indices); diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 2195aaa..f43c5a5 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -38,9 +38,7 @@ struct Snake EntityTemplate* apple_tmpl; EntityTemplate* snake_tmpl; - Texture snake_texture; - Texture wall_texture; - Texture apple_texture; + Texture texture; bool move_system = true; bool draw_system = true; @@ -85,15 +83,16 @@ struct Snake void drawMap() { + const float px = 1.0/512.0; foreach(x; 0 .. map_size) { foreach(y; 0 .. map_size) { switch(element(ivec2(x,y)).type) { - case MapElement.Type.apple:launcher.renderer.draw(apple_texture, vec2(x*32,y*32), vec2(32,32), vec4(0,0,1,1), 0, 0 , 0);break; - case MapElement.Type.snake:launcher.renderer.draw(snake_texture, vec2(x*32,y*32), vec2(32,32), vec4(0,0,1,1), 0, 0 , 0);break; - case MapElement.Type.wall:launcher.renderer.draw(wall_texture, vec2(x*32,y*32), vec2(32,32), vec4(0,0,1,1), 0, 0 , 0);break; + case MapElement.Type.apple:launcher.renderer.draw(texture, vec2(x*32,y*32), vec2(32,32), vec4(0,32*px,16*px,16*px), 0, 0 , 0);break; + case MapElement.Type.snake:launcher.renderer.draw(texture, vec2(x*32,y*32), vec2(32,32), vec4(0,48*px,16*px,16*px), 0, 0 , 0);break; + case MapElement.Type.wall:launcher.renderer.draw(texture, vec2(x*32,y*32), vec2(32,32), vec4(0,0,1,1), 0, 0 , 0);break; default:break; } } @@ -444,14 +443,8 @@ void snakeStart() { snake = Mallocator.make!Snake; - snake.snake_texture.create(); - snake.snake_texture.load("assets/textures/buckler.png"); - - snake.apple_texture.create(); - snake.apple_texture.load("assets/textures/buckler.png"); - - snake.wall_texture.create(); - snake.wall_texture.load("assets/textures/buckler.png"); + snake.texture.create(); + snake.texture.load("assets/textures/atlas.png"); launcher.manager.beginRegister(); @@ -503,10 +496,6 @@ void snakeStart() void snakeEnd() { - snake.wall_texture.destroy(); - snake.apple_texture.destroy(); - snake.snake_texture.destroy(); - //launcher.manager.freeTemplate(simple.tmpl); Mallocator.dispose(snake); } diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 2ded925..5473aed 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -29,9 +29,7 @@ struct SpaceInvaders EntityTemplate* enemy_tmpl; EntityTemplate* ship_tmpl; EntityTemplate* laser_tmpl; - Texture enemy_tex; - Texture ship_tex; - Texture laser_tex; + Texture texture; bool move_system = true; bool draw_system = true; @@ -111,6 +109,7 @@ struct CTexture mixin ECS.Component; Texture tex; + vec4 coords = vec4(0,0,0,1); } struct CVelocity @@ -214,7 +213,7 @@ struct DrawSystem { foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], vec4(0,0,1,1), 0, 0 , 0); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, 0, 0 , 0); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } } @@ -645,13 +644,12 @@ __gshared SpaceInvaders* space_invaders; void spaceInvadersStart() { + const float px = 1.0/512.0; + space_invaders = Mallocator.make!SpaceInvaders; - space_invaders.ship_tex.create(); - space_invaders.ship_tex.load("assets/textures/buckler.png"); - - space_invaders.laser_tex.create(); - space_invaders.laser_tex.load("assets/textures/buckler.png"); + space_invaders.texture.create(); + space_invaders.texture.load("assets/textures/atlas.png"); launcher.manager.beginRegister(); @@ -693,7 +691,8 @@ void spaceInvadersStart() space_invaders.ship_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.ship_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.ship_tex; + tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(0*px,48*px,16*px,16*px); CLocation* loc_comp = space_invaders.ship_tmpl.getComponent!CLocation; loc_comp.value = vec2(64,64); CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; @@ -707,7 +706,8 @@ void spaceInvadersStart() space_invaders.laser_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.laser_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.laser_tex; + tex_comp.tex = space_invaders.texture;//laser_tex; + tex_comp.coords = vec4(0*px,48*px,16*px,16*px); CScale* scale_comp = space_invaders.laser_tmpl.getComponent!CScale; scale_comp.value = vec2(4,16); CVelocity* vel_comp = space_invaders.laser_tmpl.getComponent!CVelocity; @@ -724,7 +724,8 @@ void spaceInvadersStart() space_invaders.enemy_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.enemy_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.ship_tex; + tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(32*px,32*px,16*px,16*px); CLocation* loc_comp = space_invaders.enemy_tmpl.getComponent!CLocation; loc_comp.value = vec2(64,space_invaders.map_size.y - 64); CShootDirection* shoot_dir_comp = space_invaders.enemy_tmpl.getComponent!CShootDirection; @@ -775,8 +776,6 @@ void spaceInvadersEnd() launcher.manager.getSystem(MovementSystem.system_id).disable(); launcher.manager.getSystem(ClampPositionSystem.system_id).disable(); - space_invaders.ship_tex.destroy(); - launcher.manager.freeTemplate(space_invaders.enemy_tmpl); Mallocator.dispose(space_invaders); } diff --git a/dub.json b/dub.json index dd0933c..1709ae0 100755 --- a/dub.json +++ b/dub.json @@ -125,7 +125,5 @@ "tests/tests.d" ] } - - ] } \ No newline at end of file diff --git a/source/ecs/events.d b/source/ecs/events.d index 7186d13..317b2f0 100644 --- a/source/ecs/events.d +++ b/source/ecs/events.d @@ -95,7 +95,7 @@ package struct EventManager void clearEvents() nothrow @nogc { - uint threads_count = cast(uint)manager.threads.length; + //uint threads_count = cast(uint)manager.threads.length; foreach(ref event;events) { foreach(ref first_block; event.first_blocks) @@ -133,23 +133,11 @@ package struct EventManager private void disposeData() nothrow @nogc { + clearEvents(); if(events) { foreach(ref event;events) { - foreach(first_block; event.first_blocks) - { - EventBlock* block = first_block; - EventBlock* next_block; - if(block)next_block = first_block.next; - while(block) - { - Mallocator.dispose(block); - block = next_block; - if(block)next_block = block.next; - } - } - Mallocator.dispose(event.blocks); Mallocator.dispose(event.first_blocks); } diff --git a/source/ecs/manager.d b/source/ecs/manager.d index f6f345a..1b9b959 100644 --- a/source/ecs/manager.d +++ b/source/ecs/manager.d @@ -1187,7 +1187,7 @@ export struct EntityManager (cast(Ev*) pointer).onDestroy(); } - info.destroy_callback = &callDestroy; + info.destroy_callback = cast(void function(void*) nothrow @nogc)&callDestroy; } info.size = Ev.sizeof; @@ -1214,7 +1214,7 @@ export struct EntityManager "Can't call function with system which hasn't EntitesData structure."); static assert(__traits(hasMember, Sys, "onUpdate"), "Can't call function with system which hasn't onUpdate function callback."); - static assert(is(T == typeof(&s.onUpdate)), "Function must match system update function."); + static assert(is(SetFunctionAttributes!(T,functionLinkage!(s.onUpdate), functionAttributes!(s.onUpdate)) == typeof(&s.onUpdate)), "Function must match system update function."); static assert(__traits(hasMember, Sys, "system_id"), "Sys must be system type."); System* system = getSystem(Sys.system_id); @@ -1527,8 +1527,8 @@ export struct EntityManager ids[j] = ids[i]; j++; } - else - debug assert(0, "Duplicated components in template!!!"); + //else + // debug assert(0, "Duplicated components in template!!!"); } ids = ids[0 .. j]; } @@ -1544,8 +1544,6 @@ export struct EntityManager { memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); - /*temp.entity_data[info.tmpl_deltas[comp] .. info.tmpl_deltas[comp] + components[comp].size] - = components[comp].init_data;*/ } return temp; @@ -1593,8 +1591,8 @@ export struct EntityManager ids[j] = ids[i]; j++; } - else - debug assert(0, "Duplicated components in template!!!"); + //else + // debug assert(0, "Duplicated components in template!!!"); } ids = ids[0 .. j]; } @@ -1747,11 +1745,12 @@ export struct EntityManager int j; for (j = 0; j < add_len; j++) { - if (systems[i].priority > systems[tmp_add[j]].priority) + if (systems[i].priority < systems[tmp_add[j]].priority) break; } add_len++; //move elements after new listener + if(add_len < tmp_add.length) for (int k = add_len; k > j; k--) { tmp_add[k] = tmp_add[k - 1]; @@ -1766,11 +1765,12 @@ export struct EntityManager int j; for (j = 0; j < rem_len; j++) { - if (systems[i].priority > systems[tmp_rem[j]].priority) + if (systems[i].priority < systems[tmp_rem[j]].priority) break; } rem_len++; //move elements after new listener + if(rem_len < tmp_add.length) for (int k = rem_len; k > j; k--) { tmp_rem[k] = tmp_rem[k - 1]; @@ -1785,11 +1785,12 @@ export struct EntityManager int j; for (j = 0; j < ch_len; j++) { - if (systems[i].priority > systems[tmp_ch[j]].priority) + if (systems[i].priority < systems[tmp_ch[j]].priority) break; } ch_len++; //move elements after new listener + if(ch_len < tmp_add.length) for (int k = ch_len; k > j; k--) { tmp_ch[k] = tmp_ch[k - 1]; @@ -2082,6 +2083,7 @@ export struct EntityManager { num--; new_ids[i] = new_ids[num]; + data_pointers[i] = data_pointers[num]; } } @@ -2555,6 +2557,7 @@ export struct EntityManager pointers[i] = &thread.change_entities_list[index]; index += components[ids[i]].size; } + __addComponents(id, ids, pointers[0 .. num]); } } @@ -2722,6 +2725,7 @@ export struct EntityManager call_data); } } + if(events[i].destroy_callback)events[i].destroy_callback(event_pointer); event_pointer += events[i].size; } block = block.next; @@ -2952,7 +2956,7 @@ export struct EntityManager ushort size; ushort alignment; EventCaller[] callers; - void function(void* pointer) destroy_callback; + void function(void* pointer) nothrow @nogc destroy_callback; } /************************************************************************************************************************ @@ -3133,12 +3137,13 @@ export struct EntityManager struct EntitiesBlock { ///return distance (in bytes) from begin of block to data - export uint dataDelta() nothrow @nogc pure + ///TODO: probably to remove. It's used by old code if I remeber correctly. + /*export uint dataDelta() nothrow @nogc pure { ushort dif = EntitiesBlock.sizeof; alignNum(dif, type_info.alignment); return dif; - } + }*/ ///return pointer to first element in block export void* dataBegin() nothrow @nogc pure diff --git a/source/ecs/std.d b/source/ecs/std.d index a3a4f5e..6077ede 100644 --- a/source/ecs/std.d +++ b/source/ecs/std.d @@ -187,13 +187,15 @@ static struct Mallocator static void dispose(T)(T object) nothrow @nogc { - static if(__traits(hasMember, T, "__dtor"))object.__dtor(); + static if(__traits(hasMember, T, "__xdtor"))object.__xdtor(); + else static if(__traits(hasMember, T, "__dtor"))object.__dtor(); free(cast(void*)object); } static void alignDispose(T)(T object) { - static if(__traits(hasMember, T, "__dtor"))object.__dtor(); + static if(__traits(hasMember, T, "__xdtor"))object.__xdtor(); + else static if(__traits(hasMember, T, "__dtor"))object.__dtor(); version(Posix)free(cast(void*)object); else version(Windows)_aligned_free(cast(void*)object); else version(ECSEmscripten)free(cast(void*)object); diff --git a/tests/basic.d b/tests/basic.d index c3dfcc5..f3f2832 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -1,7 +1,11 @@ module tests.basic; -import ecs.manager; import ecs.core; +import ecs.manager; +import ecs.system; +import ecs.attributes; + +import std.array : staticArray; struct CInt { @@ -57,15 +61,59 @@ struct CUnregistered short value = 12; } +struct LongAddSystem +{ + mixin ECS.System; + + struct EntitiesData + { + int length; + + CLong[] long_; + } + + void onUpdate(EntitiesData data) + { + updates_count++; + foreach(i;0..data.length) + { + data.long_[i] += 1; + } + } + + int updates_count = 0; +} + +struct EmptySystem +{ + mixin ECS.System!16; + + struct EntitiesData + { + int thread_id; + } + + void onUpdate(EntitiesData data) + { + count++; + } + + int count = 0; +} + void beforeEveryTest() { - gEM.initialize(1); + gEM.initialize(0); + + gEM.beginRegister(); gEM.registerComponent!CInt; gEM.registerComponent!CFloat; gEM.registerComponent!CDouble; gEM.registerComponent!CLong; gEM.registerComponent!CShort; + + gEM.endRegister(); } void afterEveryTest() @@ -91,6 +139,13 @@ unittest assert(entity.getComponent!CFloat); assert(*entity.getComponent!CInt == 1); assert(*entity.getComponent!CFloat == 2.0); + *entity.getComponent!CInt = 2; + + Entity* entity2 = gEM.addEntityCopy(entity.id); + assert(entity2.getComponent!CInt); + assert(entity2.getComponent!CFloat); + assert(*entity2.getComponent!CInt == 2); + assert(*entity2.getComponent!CFloat == 2.0); } //allocate templates @@ -100,6 +155,8 @@ unittest //basic template allocation ushort[2] ids = [CInt.component_id, CFloat.component_id]; EntityTemplate* tmpl_ = gEM.allocateTemplate(ids); + EntityTemplate* tmpl_d = gEM.allocateTemplate([CFloat.component_id, CInt.component_id, CFloat.component_id].staticArray); + assert(tmpl_d.info == tmpl_.info); assert(tmpl_.info.components.length == 2); assert(tmpl_.getComponent!CInt); assert(tmpl_.getComponent!CFloat); @@ -175,6 +232,7 @@ unittest assert(*tmpl_7.getComponent!CDouble == 3.0); assert(*tmpl_7.getComponent!CLong == 10); + gEM.freeTemplate(tmpl_d); gEM.freeTemplate(tmpl_); gEM.freeTemplate(tmpl_2); gEM.freeTemplate(tmpl_3); @@ -183,3 +241,983 @@ unittest gEM.freeTemplate(tmpl_6); gEM.freeTemplate(tmpl_7); } + +@("UnsortedComponentIDs") +unittest +{ + //basic template allocation + ushort[2] ids = [CFloat.component_id, CInt.component_id]; + ushort[2] ids2 = [CInt.component_id, CFloat.component_id]; + EntityTemplate* tmpl_ = gEM.allocateTemplate(ids); + EntityTemplate* tmpl_2 = gEM.allocateTemplate(ids2); + assert(tmpl_.info.components.length == 2); + assert(tmpl_.getComponent!CInt); + assert(tmpl_.getComponent!CFloat); + assert(*tmpl_.getComponent!CInt == 1); + assert(*tmpl_.getComponent!CFloat == 2.0); + assert(tmpl_.info == tmpl_2.info); +} + +@("MultiRegister") +unittest +{ + gEM.beginRegister(); + + gEM.endRegister(); + + gEM.beginRegister(); + + gEM.registerComponent!CLong; + gEM.registerComponent!CShort; + + gEM.endRegister(); +} + +@("EmptySystem") +unittest +{ + gEM.beginRegister(); + + gEM.registerSystem!EmptySystem(0); + + gEM.endRegister(); + + EmptySystem* system = gEM.getSystem!EmptySystem; + assert(system !is null); + assert(system.count == 0); + + System* ecs_system = gEM.getSystem(EmptySystem.system_id); + assert(ecs_system !is null); + assert(ecs_system.id == EmptySystem.system_id); + assert(ecs_system.name == "EmptySystem"); + + gEM.begin(); + + gEM.update(); + + gEM.end(); + + assert(system.count == 1); +} + +@("SystemCallbacks") +unittest +{ + struct TestSystem + { + mixin ECS.System!16; + + mixin ECS.ExcludedComponents!(CShort); + + struct EntitiesData + { + int length; + CLong[] long_; + @optional CInt[] int_; + } + + void onCreate() + { + create++; + } + + void onDestroy() + { + if(destroy)(*destroy)++; + } + + void onEnable() + { + enable++; + } + + void onDisable() + { + disable++; + } + + bool onBegin() + { + begin++; + update = 0; + return pass; + } + + void onEnd() + { + end++; + } + + void onUpdate(EntitiesData data) + { + update++; + } + + int create = 0; + int* destroy; + int update = 0; + int begin = 0; + int end = 0; + int enable = 0; + int disable = 0; + bool pass = true; + } + + gEM.beginRegister(); + + gEM.registerSystem!TestSystem(0); + + gEM.endRegister(); + + TestSystem* system = gEM.getSystem!TestSystem; + int destroy = 0; + system.destroy = &destroy; + + gEM.beginRegister(); + + gEM.registerSystem!TestSystem(0); + + gEM.endRegister(); + + system = gEM.getSystem!TestSystem; + system.destroy = &destroy; + assert(system !is null); + assert(system.create == 1); + assert(system.begin == 0); + assert(system.end == 0); + assert(system.enable == 1); + assert(system.disable == 0); + //FIXME: currently destroy is only called with Manager.destory which is bug, but there is no workaround for this by now + //assert(destroy == 1); + + System* ecs_system = gEM.getSystem(system.system_id); + + ecs_system.enable(); + assert(system.enable == 1); + ecs_system.disable(); + ecs_system.disable(); + ecs_system.enable(); + assert(system.enable == 2); + assert(system.disable == 1); + + + ushort[2] ids = [CLong.component_id,CFloat.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + gEM.addEntity(tmpl); + + gEM.begin(); + assert(system.begin == 1); + + gEM.update(); + assert(system.update == 1); + + gEM.end(); + assert(system.end == 1); + + ushort[2] ids2 = [CLong.component_id, CInt.component_id]; + EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2); + scope (exit) gEM.freeTemplate(tmpl2); + gEM.addEntity(tmpl2); + gEM.addEntity(tmpl2); + + gEM.begin(); + assert(system.begin == 2); + + gEM.update(); + assert(system.update == 2);//system is updated number of entity blocks times + + gEM.end(); + assert(system.end == 2); + + ushort[2] ids3 = [CLong.component_id, CShort.component_id]; + EntityTemplate* tmpl3 = gEM.allocateTemplate(ids3); + scope (exit) gEM.freeTemplate(tmpl3); + gEM.addEntity(tmpl3); + + //entity with excluded component shouldn't be updated + gEM.begin(); + assert(system.begin == 3); + + gEM.update(); + assert(system.update == 2); + + gEM.end(); + assert(system.end == 3); + + //system can be disable form update in onBegin() callback, onEnd() callback is called + system.pass = false; + gEM.begin(); + assert(system.begin == 4); + + gEM.update(); + assert(system.update == 0); + + gEM.end(); + assert(system.end == 4); + system.pass = true; + + //disabled system is't called + ecs_system.disable(); + gEM.begin(); + assert(system.begin == 4); + + gEM.update(); + assert(system.update == 0); + + gEM.end(); + assert(system.end == 4); + ecs_system.enable(); + system.destroy = null; +} + +@("CustomPass") +unittest +{ + gEM.beginRegister(); + + gEM.registerPass("custom"); + gEM.registerSystem!LongAddSystem(-1,"custom"); + + gEM.endRegister(); + + LongAddSystem* system = gEM.getSystem!LongAddSystem; + assert(system !is null); + assert(system.updates_count == 0); + + System* ecs_system = gEM.getSystem(LongAddSystem.system_id); + assert(ecs_system !is null); + assert(ecs_system.id == LongAddSystem.system_id); + assert(ecs_system.priority == -1); + assert(ecs_system.name == "LongAddSystem"); + + ushort[1] ids = [CLong.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + gEM.addEntity(tmpl); + + gEM.begin(); + + gEM.update(); + assert(system.updates_count == 0); + gEM.update("custom"); + assert(system.updates_count == 1); + + gEM.end(); +} + +@("SystemEntityCallbacks") +unittest +{ + static int add_order = 0; + static int rem_order = 0; + static int change_order = 0; + struct TestSystem + { + mixin ECS.System!16; + + mixin ECS.ExcludedComponents!(CShort); + + struct EntitiesData + { + int length; + CLong[] long_; + @optional CInt[] int_; + } + + void onAddEntity(EntitiesData data) + { + add++; + assert(add_order == 1); + add_order++; + } + + void onRemoveEntity(EntitiesData data) + { + remove++; + assert(rem_order == 1); + rem_order++; + } + + void onChangeEntity(EntitiesData data) + { + change++; + assert(change_order == 1); + change_order++; + } + + void onUpdate(EntitiesData data) + { + } + + int add = 0; + int remove = 0; + int change = 0; + } + + struct TestSystem2 + { + mixin ECS.System!16; + + mixin ECS.ExcludedComponents!(CShort); + + struct EntitiesData + { + int length; + CLong[] long_; + @optional CInt[] int_; + } + + void onAddEntity(EntitiesData data) + { + assert(add_order == 2); + add_order = 0; + } + + void onRemoveEntity(EntitiesData data) + { + assert(rem_order == 2); + rem_order = 0 ; + } + + void onChangeEntity(EntitiesData data) + { + assert(change_order == 2); + change_order = 0; + } + + void onUpdate(EntitiesData data) + { + } + } + + struct TestSystem3 + { + mixin ECS.System!16; + + mixin ECS.ExcludedComponents!(CShort); + + struct EntitiesData + { + int length; + CLong[] long_; + @optional CInt[] int_; + } + + void onAddEntity(EntitiesData data) + { + assert(add_order == 0); + add_order++; + } + + void onRemoveEntity(EntitiesData data) + { + assert(rem_order == 0); + rem_order++; + } + + void onChangeEntity(EntitiesData data) + { + assert(change_order == 0); + change_order++; + } + + void onUpdate(EntitiesData data) + { + } + } + + gEM.beginRegister(); + + gEM.registerSystem!TestSystem3(-1); + gEM.registerSystem!TestSystem(0); + gEM.registerSystem!TestSystem2(1); + + gEM.endRegister(); + + TestSystem* system = gEM.getSystem!TestSystem; + assert(system !is null); + assert(system.add == 0); + assert(system.remove == 0); + assert(system.change == 0); + + EntityTemplate* tmpl = gEM.allocateTemplate([CLong.component_id,CFloat.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl); + EntityID id0 = gEM.addEntity(tmpl).id; + gEM.commit(); + assert(system.add == 1); + + EntityTemplate* tmpl2 = gEM.allocateTemplate([CLong.component_id, CInt.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl2); + EntityID id1 = gEM.addEntity(tmpl2).id; + gEM.commit(); + assert(system.add == 2); + + EntityTemplate* tmpl3 = gEM.allocateTemplate([CLong.component_id, CShort.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl3); + EntityID id2 = gEM.addEntity(tmpl3).id; + gEM.commit(); + assert(system.add == 2); + + gEM.beginRegister(); + gEM.endRegister(); + + gEM.removeComponents(id0, [CFloat.component_id].staticArray); + gEM.commit(); + assert(system.add == 2); + assert(system.remove == 0); + assert(system.change == 0); + + gEM.removeComponents(id1, [CInt.component_id].staticArray); + gEM.commit(); + assert(system.add == 2); + assert(system.remove == 0); + assert(system.change == 1); + + gEM.removeComponents(id2, [CShort.component_id].staticArray); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 0); + assert(system.change == 1); + + gEM.addComponents(id2, CShort(1)); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 1); + assert(system.change == 1); + + gEM.removeEntity(id0); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 2); + assert(system.change == 1); + + gEM.addComponents(id1, CInt(1)); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 2); + assert(system.change == 2); + + gEM.addComponents(id0, CFloat(1)); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 2); + assert(system.change == 2); + + gEM.removeEntity(id1); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 3); + assert(system.change == 2); + + gEM.removeEntity(id2); + gEM.commit(); + assert(system.add == 3); + assert(system.remove == 3); + assert(system.change == 2); +} + +@("TemplateCoverage") +unittest +{ + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + int length; + Entity[] entity; + @readonly CLong[] long_; + @optional CInt[] int_; + @readonly CFloat[] float_; + } + + mixin ECS.ExcludedComponents!(CUnregistered); + + void onUpdate(EntitiesData data) + { + + } + } + + gEM.beginRegister(); + + gEM.registerComponent!CUnregistered; + + gEM.registerSystem!TestSystem(0); + + gEM.endRegister(); +} + +@("UnregisteredSystem") +unittest +{ + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + int length; + Entity[] entity; + @readonly CLong[] long_; + @optional CInt[] int_; + @readonly CFloat[] float_; + } + + void onUpdate(EntitiesData data) + { + + } + } + + assert(gEM.getSystem!TestSystem is null); + assert(gEM.getSystem(TestSystem.system_id) is null); +} + +@("MultithreadedUpdate") +unittest +{ + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + int length; + Entity[] entity; + @readonly CLong[] long_; + @optional CInt[] int_; + @readonly CFloat[] float_; + } + + void onUpdate(EntitiesData data) + { + update++; + entities += data.length; + } + + int update = 0; + int entities = 0; + } + + struct TestEmptySystem + { + mixin ECS.System; + + struct EntitiesData + { + int length; + } + + void onUpdate(EntitiesData data) + { + update++; + } + + int update = 0; + } + + void dispatch(EntityManager.JobGroup grp) + { + foreach(job; grp.jobs) + { + job.execute(); + } + } + + uint getID() + { + return 0; + } + + gEM.setMultithreadingCallbacks(&dispatch, &getID); + + gEM.beginRegister(); + + gEM.registerPass("custom"); + gEM.registerSystem!TestSystem(-1,"custom"); + gEM.registerSystem!TestEmptySystem(1,"custom"); + + gEM.endRegister(); + + TestSystem* system = gEM.getSystem!TestSystem; + TestEmptySystem* empty_system = gEM.getSystem!TestEmptySystem; + + ushort[2] ids = [CLong.component_id,CFloat.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + EntityTemplate* tmpl2 = gEM.allocateTemplate([CLong.component_id,CInt.component_id,CShort.component_id,CFloat.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl2); + + gEM.begin(); + + gEM.updateMT("custom"); + + gEM.end(); + + assert(system.update == 0); + assert(system.entities == 0); + assert(empty_system.update == 1); + + gEM.addEntity(tmpl); + + gEM.begin(); + + gEM.updateMT("custom"); + + gEM.end(); + + assert(system.update == 1); + assert(system.entities == 1); + assert(empty_system.update == 2); + system.entities = 0; + + foreach(i;0..2000)gEM.addEntity(tmpl); + + gEM.begin(); + + gEM.updateMT("custom"); + + gEM.end(); + + assert(system.update > 2); + assert(system.entities == 2001); + assert(empty_system.update == 3); + system.entities = 0; + + foreach(i;0..10000)gEM.addEntity(tmpl); + + gEM.begin(); + + gEM.updateMT("custom"); + + gEM.end(); + + assert(system.entities == 12001); +} + +unittest +{ + assert(gEM.pageSize == 32768); + assert(gEM.pagesInBlock == 128); +} + +@("AddRemoveEntities") +unittest +{ + ushort[3] ids = [CLong.component_id,CFloat.component_id,CShort.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + + EntityID[5000] entities; + + foreach(i;0..4) + { + foreach(j;0..5000) + { + entities[j] = gEM.addEntity(tmpl).id; + } + gEM.commit(); + foreach(j;0..5000) + { + gEM.removeEntity(entities[j]); + } + gEM.commit(); + } +} + +@("ChangeEntityComponents") +unittest +{ + gEM.beginRegister(); + + gEM.registerComponent!CUnregistered; + + gEM.endRegister(); + + ushort[1] ids = [CLong.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + + EntityID id = gEM.addEntity(tmpl).id; + gEM.commit(); + Entity* entity = gEM.getEntity(id); + assert(entity.id == id); + assert(entity.getComponent!CLong !is null); + assert(entity.getComponent!CFloat is null); + assert(entity.getComponent!CUnregistered is null); + assert(entity.getComponent!CShort is null); + assert(entity.getComponent!CInt is null); + assert(*entity.getComponent!CLong == 10); + + gEM.addComponents(id, CShort(15), CFloat(13)); + gEM.commit(); + + entity = gEM.getEntity(id); + assert(entity.id == id); + assert(entity.getComponent!CLong !is null); + assert(entity.getComponent!CFloat !is null); + assert(entity.getComponent!CUnregistered is null); + assert(entity.getComponent!CShort !is null); + assert(entity.getComponent!CInt is null); + assert(*entity.getComponent!CLong == 10); + assert(*entity.getComponent!CShort == 15); + assert(*entity.getComponent!CFloat == 13); + + ushort[3] ids2 = [CFloat.component_id, CLong.component_id, CUnregistered.component_id]; + gEM.removeComponents(id, ids2); + gEM.commit(); + + entity = gEM.getEntity(id); + assert(entity.id == id); + assert(entity.getComponent!CLong is null); + assert(entity.getComponent!CFloat is null); + assert(entity.getComponent!CUnregistered is null); + assert(entity.getComponent!CShort !is null); + assert(entity.getComponent!CInt is null); + assert(*entity.getComponent!CShort == 15); + + gEM.removeComponents(id, ids2); + gEM.addComponents(id, CShort(11), CLong(2)); //wrong order of components + gEM.commit(); + + entity = gEM.getEntity(id); + assert(entity.id == id); + assert(entity.getComponent!CLong !is null); + assert(entity.getComponent!CFloat is null); + assert(entity.getComponent!CUnregistered is null); + assert(entity.getComponent!CShort !is null); + assert(entity.getComponent!CInt is null); + assert(*entity.getComponent!CLong == 2); + assert(*entity.getComponent!CShort == 15); + + gEM.removeEntity(id); + + entity = gEM.getEntity(id); + assert(entity !is null); + assert(entity.id == id); + + gEM.commit(); + entity = gEM.getEntity(id); + assert(entity is null); +} + +@("EventCallbacks") +unittest +{ + struct ETest + { + mixin ECS.Event; + } + + struct ETest2 + { + mixin ECS.Event; + + void onDestroy() + { + destory++; + } + + int super_liczba = 0; + static int destory = 0; + } + + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + int length; + Entity[] entity; + @readonly CLong[] long_; + @optional CInt[] int_; + } + + void onUpdate(EntitiesData data) + { + + } + + void handleEvent(Entity* entity, ETest event) + { + CLong* long_ = entity.getComponent!CLong; + CInt* int_ = entity.getComponent!CInt; + *long_ += 16; + if(int_)*int_ += 6; + } + + void handleEvent(Entity* entity, ETest2 event) + { + CLong* long_ = entity.getComponent!CLong; + CInt* int_ = entity.getComponent!CInt; + *long_ += event.super_liczba * 2; + if(int_)*int_ += event.super_liczba * 4; + } + } + + struct TestSystem2 + { + mixin ECS.System; + + struct EntitiesData + { + int length; + Entity[] entity; + CShort[] short_; + @optional CInt[] int_; + } + + void handleEvent(Entity* entity, ETest event) + { + CShort* short_ = entity.getComponent!CShort; + CInt* int_ = entity.getComponent!CInt; + *short_ += 8; + if(int_)*int_ += 2; + } + + void handleEvent(Entity* entity, ETest2 event) + { + CShort* short_ = entity.getComponent!CShort; + CInt* int_ = entity.getComponent!CInt; + *short_ += event.super_liczba; + if(int_)*int_ *= event.super_liczba; + } + } + + gEM.beginRegister(); + + gEM.registerEvent!ETest; + gEM.registerEvent!ETest2; + + gEM.registerEvent!ETest; + gEM.registerEvent!ETest2; + + gEM.registerSystem!TestSystem2(1); + gEM.registerSystem!TestSystem(0); + + gEM.endRegister(); + + ushort[1] ids = [CLong.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + ushort[1] ids2 = [CShort.component_id]; + EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2); + scope (exit) gEM.freeTemplate(tmpl2); + + Entity* entity = gEM.addEntity(tmpl); + EntityID id = entity.id; + assert(*entity.getComponent!CLong == 10); + Entity* entity2 = gEM.addEntity(tmpl2); + EntityID id2 = entity2.id; + assert(*entity2.getComponent!CShort == 12); + + gEM.sendEvent(id,ETest()); + gEM.sendEvent(id,ETest2(id,10)); + gEM.sendEvent(id2,ETest()); + gEM.sendEvent(id2,ETest2(id2,12)); + gEM.commit(); + assert(ETest2.destory == 2); + + entity = gEM.getEntity(id); + entity2 = gEM.getEntity(id2); + assert(*entity.getComponent!CLong == 46); + assert(*entity2.getComponent!CShort == 32); + + gEM.addComponents(id, CInt(2), CShort(1)); + gEM.sendEvent(id,ETest()); + gEM.sendEvent(id,ETest2(id,2)); + gEM.commit(); + assert(ETest2.destory == 3); + + entity = gEM.getEntity(id); + assert(*entity.getComponent!CLong == 66); + assert(*entity.getComponent!CInt == 36); + + //test for multiple event blocks + long result = *entity.getComponent!CLong; + foreach(i;0..10000) + { + gEM.sendEvent(id,ETest()); + gEM.sendEvent(id,ETest2(id,4)); + result += 16; + result += 8; + } + gEM.commit(); + assert(ETest2.destory == 10003); + entity = gEM.getEntity(id); + assert(*entity.getComponent!CLong == result); + + //cover funcion to clearEvents before destroying manager + gEM.sendEvent(id,ETest()); +} + +@("EntitiesFunction") +unittest +{ + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + void func1(TestSystem.EntitiesData entities) + { + foreach(i;0 .. entities.length) + { + entities.int_[i] += entities.int_[i] / 2; + } + } + + void func2(TestSystem.EntitiesData entities) + { + foreach(i;0 .. entities.length) + { + entities.int_[i] += 8; + } + } + + gEM.beginRegister(); + + gEM.registerSystem!TestSystem(1); + + gEM.endRegister(); + + EntityTemplate* tmpl = gEM.allocateTemplate([CInt.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl); + EntityID id1 = gEM.addEntity(tmpl).id; + + EntityTemplate* tmpl2 = gEM.allocateTemplate([CInt.component_id, CLong.component_id].staticArray); + scope (exit) gEM.freeTemplate(tmpl2); + EntityID id2 = gEM.addEntity(tmpl2).id; + + gEM.begin(); + + Entity* entity1 = gEM.getEntity(id1); + Entity* entity2 = gEM.getEntity(id2); + assert(*entity1.getComponent!CInt == 1); + assert(*entity2.getComponent!CInt == 1); + + gEM.callEntitiesFunction!TestSystem(&func2); + assert(*entity1.getComponent!CInt == 9); + assert(*entity2.getComponent!CInt == 9); + + gEM.callEntitiesFunction!TestSystem(&func1); + assert(*entity1.getComponent!CInt == 13); + assert(*entity2.getComponent!CInt == 13); + + gEM.end(); +} \ No newline at end of file diff --git a/tests/runner.d b/tests/runner.d index 94b5ed2..5eaf99b 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -60,6 +60,11 @@ string copyString(const char* str) return cast(string) Mallocator.makeArray(arr); } +string copyString(string str) +{ + return cast(string) Mallocator.makeArray((cast(char*)str)[0 .. str.length + 1]); +} + struct TestRunner(Args...) { void generateJUnit() @@ -90,10 +95,10 @@ struct TestRunner(Args...) write(test.name); write("\" classname=\""); write(suite.name); - write("\">\n"); + write("\">"); if (test.msg) { - write("\t\t\t"); write("Assert! File: "); @@ -103,8 +108,10 @@ struct TestRunner(Args...) write(" Message: "); write(test.msg[0 .. $ - 1]); write("\n"); + write("\t\t\n"); } - write("\t\t\n"); + else write("\n"); + } write("\t\n"); @@ -168,21 +175,39 @@ struct TestRunner(Args...) static if (__traits(hasMember, module_, "beforeEveryTest")) module_.beforeEveryTest(); - // Save calling environment for longjmp - int jmp_ret = setjmp(gEnvBuffer); - - if (jmp_ret == ASSERTED) + version(D_BetterC) { - passed = false; - test.passed = false; - test.file = copyString(gAssertInfo.file); - test.file_line = gAssertInfo.line; - test.msg = copyString(gAssertInfo.msg); + // Save calling environment for longjmp + int jmp_ret = setjmp(gEnvBuffer); + + if (jmp_ret == ASSERTED) + { + test.passed = false; + test.file = copyString(gAssertInfo.file); + test.file_line = gAssertInfo.line; + test.msg = copyString(gAssertInfo.msg); + } + else + { + unittest_(); + test.passed = true; + } } else { - unittest_(); - test.passed = true; + import core.exception : AssertError; + try + { + unittest_(); + test.passed = true; + } + catch(AssertError error) + { + test.passed = false; + test.file = copyString(error.file); + test.file_line = cast(int)error.line; + test.msg = copyString(error.msg); + } } if (test.passed) @@ -360,3 +385,14 @@ extern (C) int main(int argc, char** args) else return 1; } + +version (D_BetterC) +{ + version(LDC) + { + extern (C) __gshared int _d_eh_personality(int, int, size_t, void*, void*) + { + return 0; + } + } +} \ No newline at end of file diff --git a/tests/vector.d b/tests/vector.d index 109ff37..4b2ad5b 100644 --- a/tests/vector.d +++ b/tests/vector.d @@ -16,4 +16,20 @@ unittest vector.clear(); assert(vector.length == 0); + + ubyte[1025] array; + foreach(i;0..cast(uint)array.length)array[i] = cast(ubyte)i; + vector.add(array); + assert(vector.length == 1025); + assert(vector[] == array[]); + + SimpleVector vector2; + vector2.clear(); + vector2.add(array[0..1023]); + vector2.add('a'); + vector2.add('b'); + assert(vector2.length == 1025); + assert(vector2[0..1023] == array[0..1023]); + assert(vector2[1023] == 'a'); + assert(vector2[1024] == 'b'); }