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/manager.d b/source/ecs/manager.d index f6f345a..04b1c31 100644 --- a/source/ecs/manager.d +++ b/source/ecs/manager.d @@ -1752,6 +1752,7 @@ export struct EntityManager } 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]; @@ -1771,6 +1772,7 @@ export struct EntityManager } 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]; @@ -1790,6 +1792,7 @@ export struct EntityManager } 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]; diff --git a/tests/basic.d b/tests/basic.d index c3dfcc5..451f230 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -1,7 +1,9 @@ module tests.basic; -import ecs.manager; import ecs.core; +import ecs.manager; +import ecs.system; +import ecs.attributes; struct CInt { @@ -57,15 +59,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.beginRegister(); + gEM.registerComponent!CInt; gEM.registerComponent!CFloat; gEM.registerComponent!CDouble; gEM.registerComponent!CLong; gEM.registerComponent!CShort; + + gEM.endRegister(); } void afterEveryTest() @@ -183,3 +229,344 @@ 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() + { + (*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(); +} + +@("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 +{ + struct TestSystem + { + mixin ECS.System!16; + + mixin ECS.ExcludedComponents!(CShort); + + struct EntitiesData + { + int length; + CLong[] long_; + @optional CInt[] int_; + } + + void onAddEntity(EntitiesData data) + { + add++; + } + + void onRemoveEntity(EntitiesData data) + { + remove++; + } + + void onChangeEntity(EntitiesData data) + { + change++; + } + + void onUpdate(EntitiesData data) + { + } + + int add = 0; + int remove = 0; + int change = 0; + } + + gEM.beginRegister(); + + gEM.registerSystem!TestSystem(0); + + gEM.endRegister(); + + TestSystem* system = gEM.getSystem!TestSystem; + assert(system !is null); + assert(system.add == 0); + assert(system.remove == 0); + assert(system.change == 0); + + ushort[2] ids = [CLong.component_id,CFloat.component_id]; + EntityTemplate* tmpl = gEM.allocateTemplate(ids); + scope (exit) gEM.freeTemplate(tmpl); + EntityID id0 = gEM.addEntity(tmpl).id; + gEM.commit(); + assert(system.add == 1); + + ushort[2] ids2 = [CLong.component_id, CInt.component_id]; + EntityTemplate* tmpl2 = gEM.allocateTemplate(ids2); + scope (exit) gEM.freeTemplate(tmpl2); + EntityID id1 = gEM.addEntity(tmpl2).id; + gEM.commit(); + assert(system.add == 2); + + ushort[2] ids3 = [CLong.component_id, CShort.component_id]; + EntityTemplate* tmpl3 = gEM.allocateTemplate(ids3); + scope (exit) gEM.freeTemplate(tmpl3); + EntityID id2 = gEM.addEntity(tmpl3).id; + gEM.commit(); + assert(system.add == 2); + + gEM.commit(); +} \ No newline at end of file diff --git a/tests/runner.d b/tests/runner.d index 94b5ed2..9f219bd 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)