From 8381ac166bb672b512279ac0d5bc2fda0ded385e Mon Sep 17 00:00:00 2001 From: Mergul Date: Fri, 24 Apr 2020 20:55:25 +0200 Subject: [PATCH 01/37] Common update: -added VBo batch rendering (current default, no render mode switch yet) -fixed camera positioning calculation -fixed buffer issue with WebGL -added viewport scalling (at least 300 pixels height). Pixels are scalled if screen is bigger. -center demos gameplay area -added fullpage html template for Emscripten build --- demos/compile_wasm.py | 2 +- demos/source/app.d | 15 ++++++++--- demos/source/demos/simple.d | 15 +++++++---- demos/source/demos/snake.d | 12 +++++---- demos/source/demos/space_invaders.d | 23 +++++++++-------- demos/utils/source/ecs_utils/gfx/buffer.d | 6 ++--- demos/utils/source/ecs_utils/gfx/renderer.d | 28 ++++++++++++--------- 7 files changed, 61 insertions(+), 40 deletions(-) diff --git a/demos/compile_wasm.py b/demos/compile_wasm.py index 30d0a0f..b1f3a7e 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 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 ' +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 --shell-file emscripten_shell.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 6338035..150d653 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -51,6 +51,7 @@ struct Launcher void function() end; void function(SDL_Event*) event; void function(vec2, Tool, int) tool; + float scalling; ivec2 window_size = ivec2(1024,768); Renderer renderer; ubyte[] keys; @@ -60,6 +61,7 @@ struct Launcher ulong timer_freq; double delta_time; uint fps; + vec2 render_position; Tool used_tool; int tool_size = 0; @@ -229,7 +231,7 @@ void mainLoop(void* arg) } if(launcher.tool && event.button.button == SDL_BUTTON_LEFT && launcher.tool_repeat == 0 && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { - launcher.tool(vec2(event.button.x, launcher.window_size.y - event.button.y), launcher.used_tool, launcher.tool_size); + launcher.tool(vec2(event.button.x, launcher.window_size.y - event.button.y) * launcher.scalling - launcher.render_position, launcher.used_tool, launcher.tool_size); } } else if(event.type == SDL_MOUSEBUTTONUP) @@ -255,7 +257,7 @@ void mainLoop(void* arg) while(launcher.repeat_time > range) { launcher.repeat_time -= range; - launcher.tool(launcher.mouse.position, launcher.used_tool, launcher.tool_size); + launcher.tool((launcher.mouse.position*launcher.scalling)-launcher.render_position, launcher.used_tool, launcher.tool_size); } } @@ -525,7 +527,14 @@ void mainLoop(void* arg) } launcher.renderer.resize(launcher.window_size); - launcher.renderer.view(vec2(0,0),vec2(launcher.window_size.x,launcher.window_size.y)); + //launcher.renderer.view(vec2(0,0),vec2(launcher.window_size.x,launcher.window_size.y)); + //if(384, 768, 1152, 1536) + //576 960 1344 1728 + //float scalling; + if(launcher.window_size.y < 360)launcher.scalling = 1; + else launcher.scalling = 1.0 / ((launcher.window_size.y+120)/360); + launcher.renderer.view(launcher.render_position,vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling); + //launcher.renderer.view(vec2(0,0),vec2(1024*launcher.window_size.x/launcher.window_size.y,768)); //glClear(GL_COLOR_BUFFER_BIT); launcher.renderer.clear(); diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 36187c8..edd20c6 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -60,7 +60,7 @@ struct DrawSystem { foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(32,32), vec4(0,0,1,1), 0, 0 , 0); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), 0, 0 , 0); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } } @@ -81,7 +81,7 @@ struct MoveSystem foreach(i; 0..data.length) { data.locations[i].location.y = data.locations[i].location.y + 1; - if(data.locations[i].location.y > 400)data.locations[i].location.y = 0; + if(data.locations[i].location.y > 300)data.locations[i].location.y = 0; } } } @@ -119,7 +119,7 @@ void simpleStart() foreach(i; 0..10) foreach(j; 0..10) { - loc_comp.location = vec2(i*32+64,j*32+64); + loc_comp.location = vec2(i*16+64,j*16+64); launcher.manager.addEntity(simple.tmpl); } } @@ -147,6 +147,10 @@ void simpleTool(vec2 position, Tool tool, int size) { position.x += (randomf - 0.5) * size; position.y += (randomf - 0.5) * size; + if(position.x > 400)position.x -= 400; + else if(position.x < 0)position.x += 400; + if(position.y > 300)position.y -= 300; + else if(position.y < 0)position.y += 300; *location = position; } launcher.manager.addEntity(tmpl); @@ -169,17 +173,18 @@ void simpleEvent(SDL_Event* event) void spawnEntity() { CLocation* loc_comp = simple.tmpl.getComponent!CLocation; - loc_comp.location = vec2(randomf() * 600,0); + loc_comp.location = vec2(randomf() * 400,0); launcher.manager.addEntity(simple.tmpl); } bool simpleLoop() { + launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5; + if(launcher.getKeyState(SDL_SCANCODE_SPACE)) { foreach(i;0..1)spawnEntity(); } - launcher.manager.begin(); if(launcher.multithreading) diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index f43c5a5..19d0e31 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -90,9 +90,9 @@ struct Snake { switch(element(ivec2(x,y)).type) { - 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; + case MapElement.Type.apple:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, 0 , 0);break; + case MapElement.Type.snake:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,48*px,16*px,16*px), 0, 0 , 0);break; + case MapElement.Type.wall:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,0,1,1), 0, 0 , 0);break; default:break; } } @@ -520,8 +520,8 @@ void snakeTool(vec2 position, Tool tool, int size) position.x += (randomf - 0.5) * size; position.y += (randomf - 0.5) * size; ivec2 ipos; - ipos.x = cast(int)(position.x / 32); - ipos.y = cast(int)(position.y / 32); + ipos.x = cast(int)(position.x / 16); + ipos.y = cast(int)(position.y / 16); *ilocation = ipos; if(snake.element(ipos).type != MapElement.Type.empty)return; } @@ -540,6 +540,8 @@ void snakeEvent(SDL_Event* event) bool snakeLoop() { + launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(288,288)) * 0.5; + /*if(launcher.show_demo_wnd) { igSetNextWindowPos(ImVec2(800 - 260, 30), ImGuiCond_Once, ImVec2(0,0)); diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 5473aed..7058b59 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -34,7 +34,7 @@ struct SpaceInvaders bool move_system = true; bool draw_system = true; - const vec2 map_size = vec2(600,600); + const vec2 map_size = vec2(400,300); const float cell_size = 60; } @@ -101,7 +101,7 @@ struct CScale ///use component as it value alias value this; - vec2 value = vec2(32,32); + vec2 value = vec2(16,16); } struct CTexture @@ -564,8 +564,8 @@ struct MovementSystem { foreach(i;0..data.length) { - data.locations[i].x += data.velocity[i].x * launcher.delta_time; - data.locations[i].y += data.velocity[i].y * launcher.delta_time; + data.locations[i].x += data.velocity[i].x * launcher.delta_time * 0.5; + data.locations[i].y += data.velocity[i].y * launcher.delta_time * 0.5; } } } @@ -630,8 +630,8 @@ struct InputMovementSystem //move every entity using movement vector foreach(i; 0..data.length) { - data.locations[i].x += move_vector.x * launcher.delta_time * 0.5; - data.locations[i].y += move_vector.y * launcher.delta_time * 0.5; + data.locations[i].x += move_vector.x * launcher.delta_time * 0.25; + data.locations[i].y += move_vector.y * launcher.delta_time * 0.25; } } } @@ -709,7 +709,7 @@ void spaceInvadersStart() 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); + scale_comp.value = vec2(2,8); CVelocity* vel_comp = space_invaders.laser_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0,1); } @@ -727,7 +727,7 @@ void spaceInvadersStart() 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); + loc_comp.value = vec2(64,space_invaders.map_size.y - 16); CShootDirection* shoot_dir_comp = space_invaders.enemy_tmpl.getComponent!CShootDirection; shoot_dir_comp.direction = Direction.down; CVelocity* vel_comp = space_invaders.enemy_tmpl.getComponent!CVelocity; @@ -738,17 +738,17 @@ void spaceInvadersStart() current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); launcher.manager.addComponents(current_entity.id,CSideMove(0)); - loc_comp.value = vec2(128,space_invaders.map_size.y - 64); + loc_comp.value = vec2(128,space_invaders.map_size.y - 16); current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); launcher.manager.addComponents(current_entity.id,CSideMove(-1)); enemy_id = current_entity.id; //enemy_tmpl = launcher.manager.allocateTemplate(current_entity.id); - loc_comp.value = vec2(256,space_invaders.map_size.y - 64); + loc_comp.value = vec2(256,space_invaders.map_size.y - 16); launcher.manager.addEntity(space_invaders.enemy_tmpl); - loc_comp.value = vec2(0,space_invaders.map_size.y - 64); + loc_comp.value = vec2(0,space_invaders.map_size.y - 16); current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); launcher.manager.addComponents(current_entity.id,CSideMove(0)); @@ -809,6 +809,7 @@ void spaceInvadersEvent(SDL_Event* event) bool spaceInvadersLoop() { + launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5; /*if(launcher.show_demo_wnd) { diff --git a/demos/utils/source/ecs_utils/gfx/buffer.d b/demos/utils/source/ecs_utils/gfx/buffer.d index 1f38624..2c25324 100644 --- a/demos/utils/source/ecs_utils/gfx/buffer.d +++ b/demos/utils/source/ecs_utils/gfx/buffer.d @@ -55,10 +55,10 @@ struct Buffer glBufferStorage(GL_ARRAY_BUFFER,size*count,data, flags); }*/ - void bufferSubData(uint size, uint offset, void* data) nothrow + void bufferSubData(BindTarget target, uint size, uint offset, void* data) nothrow { - bind(BindTarget.array); - glBufferSubData(GL_ARRAY_BUFFER,offset,size,data); + bind(target); + glBufferSubData(target,offset,size,data); } void map(BindTarget target) nothrow diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index e064c9e..2a04c57 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -98,7 +98,7 @@ struct Renderer alias Technique = RenderTechnique; - __gshared Technique technique = Technique.simple; + __gshared Technique technique = Technique.vbo_batch; void* data_ptr; //import ecs_utils.core : RenderTechnique; @@ -339,6 +339,7 @@ struct Renderer //import core.stdc.string; with(this_) { + if(item_id >= MaxObjects)return; //pos += view_pos; size.x *= view_size.x; size.y *= view_size.y; @@ -470,8 +471,8 @@ struct Renderer break; case Technique.vbo_batch: //if(data_index){ - batch_vbo[0].bufferSubData(item_id*4*16,0,batch_vertices.ptr); - batch_ibo[0].bufferSubData(item_id*6*2,0,batch_indices.ptr); + batch_vbo[0].bufferSubData(Buffer.BindTarget.array,item_id*4*16,0,batch_vertices.ptr); + batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,item_id*6*2,0,batch_indices.ptr); batch_vbo[0].bind(Buffer.BindTarget.array); batch_ibo[0].bind(Buffer.BindTarget.element_array); @@ -480,8 +481,8 @@ struct Renderer glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)8);//} break; case Technique.instanced_attrib_divisor: - ubos[0].bufferSubData(data_index,0,uniform_block.ptr); - ubos[0].bind(Buffer.BindTarget.array); + ubos[0].bufferSubData(Buffer.BindTarget.uniform,data_index,0,uniform_block.ptr); + ubos[0].bind(Buffer.BindTarget.uniform); glEnableVertexAttribArray(2); glEnableVertexAttribArray(3); glEnableVertexAttribArray(4); @@ -496,7 +497,7 @@ struct Renderer break; case Technique.uniform_buffer: //ubos[0].bufferData(1,64*MaxObjects,BufferUsage,null); - /*if(data_index)*/ubos[0].bufferSubData(data_index,0,uniform_block.ptr); + /*if(data_index)*/ubos[0].bufferSubData(Buffer.BindTarget.uniform,data_index,0,uniform_block.ptr); break; case Technique.uniform_buffer_indexed: ubos[0].bindRange(Buffer.BindTarget.uniform,0,0,block_max_size); @@ -581,6 +582,8 @@ struct Renderer { material_id = render_list[i].material_id; GfxConfig.materials[material_id].bind(); + float[3*4] data = [1,0,0,1,0,0,0,0,0,0,1,1]; + GfxConfig.materials[material_id].pushUniforms(data.ptr); } if(texture.data != render_list[i].texture.data) { @@ -589,17 +592,17 @@ struct Renderer } uint instance_count = 16_384; - if(i*16_384 > item_id) + if((i+1)*16_384 > item_id) { - instance_count = i*16_384 - item_id; + instance_count = item_id%16_384; } - /*glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*16_384*4*16)); + glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*16_384*4*16)); glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)(i*16_384*4*16+8)); - glDrawElements(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2));*/ + glDrawElements(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2)); - glDrawElementsBaseVertex(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2),i*16_384*4); + //glDrawElementsBaseVertex(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2),i*16_384*4); } } else if(technique == Technique.ssbo_instanced || technique == Technique.instanced_attrib_divisor) @@ -794,9 +797,10 @@ struct Renderer void view(vec2 pos, vec2 size) { - view_pos = pos * size - 1; + //view_pos = pos * size - 1; view_size = vec2(2/size.x,2/size.y); sdl_transform = vec4(0,0,1.0/size.x,1.0/size.y); + view_pos = (pos - size * 0.5) * view_size; } __gshared void function(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, float angle, uint material_id, uint mesh_id) __draw; -- 2.47.2 From 50715fbc40a8ea4821ab840e42e60e8600b938ef Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 28 Apr 2020 14:13:55 +0200 Subject: [PATCH 02/37] Demos update: -modified texture atlas -fixed bug with high CPU usage (use pthread instead of builtin D multithreading) -added new graphics -snake now render tile coresponding to body part -snake is destroyed after collision and emit some particles -added some functionality to vectors -fixed documentation issue in Manager.d --- demos/assets/textures/atlas.png | Bin 17825 -> 24668 bytes demos/external/sources/mmutils/thread_pool.d | 2 + demos/source/demos/snake.d | 337 ++++++++++++++++++- demos/source/demos/space_invaders.d | 24 +- demos/utils/source/ecs_utils/math/vector.d | 19 ++ source/ecs/manager.d | 7 +- 6 files changed, 366 insertions(+), 23 deletions(-) diff --git a/demos/assets/textures/atlas.png b/demos/assets/textures/atlas.png index 498f34dd82959385bc9a96199c88707d782b46a0..c628969c0e7149419eb62313fb5c3274457838f3 100644 GIT binary patch literal 24668 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 literal 17825 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 data.animation[i].frames.length)data.animation[i].time -= cast(float)data.animation[i].frames.length; + } + } +} + + +struct AnimationRenderSystem +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + @readonly CAnimation[] animation; + @readonly CLocation[] location; + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + launcher.renderer.draw(snake.texture, cast(vec2)cast(ivec2)data.location[i].location, vec2(16,16), data.animation[i].frames[cast(int)(data.animation[i].time)], 0, 0 , 0); + } + } +} + struct MoveSystem { mixin ECS.System!64; + EntityTemplate* destroy_template; + CLocation* destroy_location; + CParticleVector* destroy_vector; + struct EntitiesData { uint length; @@ -239,6 +380,13 @@ struct MoveSystem CILocation[] location; } + void setTemplates() + { + destroy_template = snake.snake_destroy_particle; + destroy_location = destroy_template.getComponent!CLocation; + destroy_vector = destroy_template.getComponent!CParticleVector; + } + void moveLocation(ref CILocation location, CMovement.Direction direction) { final switch(direction) @@ -276,6 +424,56 @@ struct MoveSystem else .snake.element(MapElement(),location); } + static CMovement.Direction getDirection(ivec2 p1, ivec2 p2) + { + if(p1.x - p2.x == -1)return CMovement.direction.right; + else if(p1.x - p2.x == 1)return CMovement.direction.left; + else if(p1.y - p2.y == -1)return CMovement.direction.up; + else if(p1.y - p2.y == 1)return CMovement.direction.down; + else if(p1.x - p2.x > 1)return CMovement.direction.right; + else if(p1.x - p2.x < -1)return CMovement.direction.left; + else if(p1.y - p2.y > 1)return CMovement.direction.up; + else return CMovement.direction.down; + } + + static MapElement.Type snakePart(ivec2 p1, ivec2 p2, ivec2 p3) + { + CMovement.Direction direction = getDirection(p1, p2); + CMovement.Direction direction2 = getDirection(p1, p3); + uint case_ = direction*4 + direction2; + final switch(case_) + { + case 0:return MapElement.Type.snake_horizontal; + case 1:return MapElement.Type.snake_horizontal; + case 2:return MapElement.Type.snake_turn_lu; + case 3:return MapElement.Type.snake_turn_ru; + case 4:return MapElement.Type.snake_horizontal; + case 5:return MapElement.Type.snake_horizontal; + case 6:return MapElement.Type.snake_turn_ld; + case 7:return MapElement.Type.snake_turn_rd; + case 8:return MapElement.Type.snake_turn_lu; + case 9:return MapElement.Type.snake_turn_ld; + case 10:return MapElement.Type.snake_vertical; + case 11:return MapElement.Type.snake_vertical; + case 12:return MapElement.Type.snake_turn_ru; + case 13:return MapElement.Type.snake_turn_rd; + case 14:return MapElement.Type.snake_vertical; + case 15:return MapElement.Type.snake_vertical; + } + } + + static MapElement.Type snakeTail(ivec2 p1, ivec2 p2) + { + CMovement.Direction direction = getDirection(p1, p2); + final switch(direction) + { + case CMovement.Direction.up:return MapElement.Type.snake_tail_up; + case CMovement.Direction.down:return MapElement.Type.snake_tail_down; + case CMovement.Direction.left:return MapElement.Type.snake_tail_left; + case CMovement.Direction.right:return MapElement.Type.snake_tail_right; + } + } + void onUpdate(EntitiesData data) { if(data.snakes) @@ -286,21 +484,104 @@ struct MoveSystem moveLocation(data.location[i], data.movement[i].direction); final switch(snake.element(data.location[i].location).type) { - case MapElement.Type.snake: - launcher.manager.removeEntity(data.entities[i].id); - break; - case MapElement.Type.wall: + case MapElement.Type.snake_head_up:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_head_down:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_head_left:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_head_right:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_tail_up:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_tail_down:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_tail_left:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_tail_right:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_turn_ld:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_turn_lu:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_turn_rd:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_turn_ru:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_vertical:goto case(MapElement.Type.snake_horizontal); + case MapElement.Type.snake_horizontal: + foreach(ivec2 loc; data.snakes[i].parts) + { + destroy_location.x = loc.x * 16; + destroy_location.y = loc.y * 16; + snake.element(MapElement(MapElement.Type.empty, EntityID()),loc); + launcher.manager.addEntity(snake.snake_destroy_particle); + foreach(j;0..10) + { + destroy_location.x = loc.x * 16 + randomf() * 8 - 4; + destroy_location.y = loc.y * 16 + randomf() * 8 - 4; + destroy_vector.velocity = vec2(randomf(),randomf())*0.4-0.2; + snake.element(MapElement(MapElement.Type.empty, EntityID()),loc); + launcher.manager.addEntity(snake.snake_destroy_particle); + } + + } + destroy_location.x = new_location.x * 16; + destroy_location.y = new_location.y * 16; + snake.element(MapElement(MapElement.Type.empty, EntityID()),new_location); + launcher.manager.addEntity(snake.snake_destroy_particle); launcher.manager.removeEntity(data.entities[i].id); break; + case MapElement.Type.wall:break; + //launcher.manager.removeEntity(data.entities[i].id); + //break; case MapElement.Type.empty: moveSnake(data.snakes[i], new_location); - snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.location[i].location); + final switch(data.movement[i].direction) + { + case CMovement.Direction.up: + snake.element(MapElement(MapElement.Type.snake_head_up, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.right: + snake.element(MapElement(MapElement.Type.snake_head_right, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.down: + snake.element(MapElement(MapElement.Type.snake_head_down, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.left: + snake.element(MapElement(MapElement.Type.snake_head_left, data.entities[i].id),data.location[i].location); + break; + } + if(data.snakes[i].parts.length > 1) + { + MapElement.Type elem_type = snakePart(data.snakes[i].parts[$-1],data.location[i],data.snakes[i].parts[$-2]); + snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[$-1]); + elem_type = snakeTail(data.snakes[i].parts[1], data.snakes[i].parts[0]); + snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[0]); + } + else if(data.snakes[i].parts.length == 1) + { + MapElement.Type elem_type = snakeTail(data.location[i], data.snakes[i].parts[0]); + snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[0]); + } break; case MapElement.Type.apple: launcher.manager.removeEntity(snake.element(data.location[i].location).id); - data.snakes[i].parts.add(new_location); - snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),new_location); - snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.location[i].location); + if(data.snakes[i].parts.length < 100)data.snakes[i].parts.add(new_location); + + if(data.snakes[i].parts.length > 1) + { + MapElement.Type elem_type = snakePart(data.snakes[i].parts[$-1],data.location[i],data.snakes[i].parts[$-2]); + snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[$-1]); + } + else if(data.snakes[i].parts.length == 1) + { + MapElement.Type elem_type = snakeTail(data.location[i], new_location); + snake.element(MapElement(elem_type, data.entities[i].id),new_location); + } + final switch(data.movement[i].direction) + { + case CMovement.Direction.up: + snake.element(MapElement(MapElement.Type.snake_head_up, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.right: + snake.element(MapElement(MapElement.Type.snake_head_right, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.down: + snake.element(MapElement(MapElement.Type.snake_head_down, data.entities[i].id),data.location[i].location); + break; + case CMovement.Direction.left: + snake.element(MapElement(MapElement.Type.snake_head_left, data.entities[i].id),data.location[i].location); + break; + } snake.addApple(); break; } @@ -455,19 +736,31 @@ void snakeStart() launcher.manager.registerComponent!CSnake; launcher.manager.registerComponent!CApple; launcher.manager.registerComponent!CParticle; + launcher.manager.registerComponent!CParticleVector; launcher.manager.registerComponent!CMovement; launcher.manager.registerComponent!CInput; + launcher.manager.registerComponent!CAnimation; launcher.manager.registerSystem!MoveSystem(0,"fixed"); launcher.manager.registerSystem!InputSystem(-100); launcher.manager.registerSystem!FixSnakeDirectionSystem(-1,"fixed"); launcher.manager.registerSystem!AppleSystem(-1,"fixed"); + launcher.manager.registerSystem!AnimationRenderSystem(100); + launcher.manager.registerSystem!AnimationSystem(-1); + launcher.manager.registerSystem!ParticleSystem(-1); + launcher.manager.registerSystem!ParticleMovementSystem(-1); launcher.manager.endRegister(); launcher.gui_manager.addSystem(MoveSystem.system_id,"Move System"); launcher.gui_manager.addSystem(InputSystem.system_id,"Input System"); launcher.gui_manager.addSystem(FixSnakeDirectionSystem.system_id,"Fix Direction System"); + launcher.gui_manager.addSystem(AnimationRenderSystem.system_id,"Animation Render System"); + launcher.gui_manager.addSystem(AnimationSystem.system_id,"Animation System"); + launcher.gui_manager.addSystem(ParticleSystem.system_id,"Particle Life System"); + launcher.gui_manager.addSystem(ParticleMovementSystem.system_id,"Particle Movement System"); + + snake.snake_destroy_particle_frames = Mallocator.makeArray([vec4(64,144,16,16)*px,vec4(80,144,16,16)*px,vec4(96,144,16,16)*px,vec4(112,144,16,16)*px].staticArray); { ushort[4] components = [CILocation.component_id, CSnake.component_id, CMovement.component_id, CInput.component_id]; @@ -477,14 +770,26 @@ void snakeStart() launcher.manager.addEntity(snake.snake_tmpl); } + { + snake.snake_destroy_particle = launcher.manager.allocateTemplate([CLocation.component_id, CParticle.component_id, CParticleVector.component_id, CAnimation.component_id].staticArray); + CAnimation* canim = snake.snake_destroy_particle.getComponent!CAnimation; + canim.frames = snake.snake_destroy_particle_frames; + CParticle* particle = snake.snake_destroy_particle.getComponent!CParticle; + particle.life = 400; + } + { ushort[2] components = [CILocation.component_id, CApple.component_id]; snake.apple_tmpl = launcher.manager.allocateTemplate(components); snake.addApple(); } - + launcher.gui_manager.addTemplate(snake.snake_tmpl, "Snake"); launcher.gui_manager.addTemplate(snake.apple_tmpl, "Apple"); + launcher.gui_manager.addTemplate(snake.snake_destroy_particle, "Particle"); + + MoveSystem* move_system = launcher.manager.getSystem!MoveSystem(); + move_system.setTemplates(); /*foreach(i; 0..10) foreach(j; 0..10) diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 7058b59..bddf53c 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -16,6 +16,8 @@ import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +enum float px = 1.0/512.0; + extern(C): /*####################################################################################################################### @@ -587,6 +589,7 @@ struct InputMovementSystem const (CInput)[] input; //components are treated as required by default CLocation[] locations; + CTexture[] textures; } /** @@ -595,6 +598,7 @@ struct InputMovementSystem */ bool onBegin() { + move_vector = vec2(0,0); if(launcher.getKeyState(SDL_SCANCODE_W)) { move_vector = vec2(0,1); @@ -616,7 +620,7 @@ struct InputMovementSystem return true; } //don't call system update because no key pressed - return false; + return true; } /** @@ -627,11 +631,21 @@ struct InputMovementSystem */ void onUpdate(EntitiesData data) { + if(move_vector.x == 0) + { + foreach(i; 0..data.length) + { + data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); + } + return; + } //move every entity using movement vector foreach(i; 0..data.length) { data.locations[i].x += move_vector.x * launcher.delta_time * 0.25; data.locations[i].y += move_vector.y * launcher.delta_time * 0.25; + if(move_vector.x > 0)data.textures[i].coords = vec4(48*px,80*px,48*px,32*px); + else data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); } } } @@ -644,8 +658,6 @@ __gshared SpaceInvaders* space_invaders; void spaceInvadersStart() { - const float px = 1.0/512.0; - space_invaders = Mallocator.make!SpaceInvaders; space_invaders.texture.create(); @@ -690,9 +702,11 @@ void spaceInvadersStart() ushort[7] components = [CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id]; space_invaders.ship_tmpl = launcher.manager.allocateTemplate(components); + CScale* scale_comp = space_invaders.ship_tmpl.getComponent!CScale; + scale_comp.value = vec2(48,32); CTexture* tex_comp = space_invaders.ship_tmpl.getComponent!CTexture; tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(0*px,48*px,16*px,16*px); + tex_comp.coords = vec4(0*px,80*px,48*px,32*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 +721,7 @@ void spaceInvadersStart() CTexture* tex_comp = space_invaders.laser_tmpl.getComponent!CTexture; tex_comp.tex = space_invaders.texture;//laser_tex; - tex_comp.coords = vec4(0*px,48*px,16*px,16*px); + tex_comp.coords = vec4(0*px,24*px,2*px,8*px); CScale* scale_comp = space_invaders.laser_tmpl.getComponent!CScale; scale_comp.value = vec2(2,8); CVelocity* vel_comp = space_invaders.laser_tmpl.getComponent!CVelocity; diff --git a/demos/utils/source/ecs_utils/math/vector.d b/demos/utils/source/ecs_utils/math/vector.d index 10b4de5..4b9f3af 100644 --- a/demos/utils/source/ecs_utils/math/vector.d +++ b/demos/utils/source/ecs_utils/math/vector.d @@ -30,6 +30,11 @@ struct vec2 else static assert(0, "Operator "~op~" not implemented"); } + ivec2 opCast() + { + return ivec2(cast(int)x,cast(int)y); + } + void opOpAssign(string op)(vec2 v) { static if (op == "+") @@ -69,6 +74,15 @@ struct vec4 } float[4] data; } + + vec4 opBinary(string op)(float v) + { + static if (op == "+") return vec4(x + v, y + v, z + v, w + v); + else static if (op == "-") return vec4(x - v, y - v, z - v, w - v); + else static if (op == "*") return vec4(x * v, y * v, z * v, w * v); + else static if (op == "/") return vec4(x / v, y / v, z / v, w / v); + else static assert(0, "Operator "~op~" not implemented"); + } } struct ivec2 @@ -82,6 +96,11 @@ struct ivec2 } int[2] data; } + + vec2 opCast() + { + return vec2(x,y); + } } struct ivec4 diff --git a/source/ecs/manager.d b/source/ecs/manager.d index 1b9b959..9f5dceb 100644 --- a/source/ecs/manager.d +++ b/source/ecs/manager.d @@ -1550,10 +1550,13 @@ export struct EntityManager } /************************************************************************************************************************ - *Allocate EntityTemplate with specifed components and returns pointer to it. + *Allocate EntityTemplate from basic Template with modifications by adding and removing some components and returns pointer to it. + *Arrays of components needen't to be checked for repeated components, as function itself check if components exist in base template. * *Params: - *components_ids = array of components allocated with template + *base_tmpl = template from which components sould be copied + *components_ids = array of new components to add + *remove_components_ids = array of components to remove from base template */ export EntityTemplate* allocateTemplate(EntityTemplate* base_tmpl, ushort[] components_ids, ushort[] remove_components_ids = null) -- 2.47.2 From 7bc07666d010ef9cfe7d66a8ca10fbf7362f3a28 Mon Sep 17 00:00:00 2001 From: Dawid Masiukiewicz Date: Sat, 2 May 2020 21:11:22 +0000 Subject: [PATCH 03/37] Initial release README.md --- README.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3f6816..abc9709 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,180 @@ -# Dynamic Entity Component System +# Bubel Entity Component System [![pipeline status](https://gitlab.com/Mergul/bubel-ecs/badges/master/pipeline.svg)](https://gitlab.com/Mergul/bubel-ecs/-/commits/master) [![codecov](https://codecov.io/gl/Mergul/bubel-ecs/branch/master/graph/badge.svg?token=Unm0TJhFoW)](https://codecov.io/gl/Mergul/bubel-ecs) -Entity-Component-System implementation in D language. +BubelECS is Entity-Component-System architecture implementation in D language. +Library aims to delivery fast and flexible architecture for developing games. Library is @nogc and betterC compatible. Even Emscripten build works very well. +Important goal is to keep code without any external dependencies, i. eg. multithreading support don't contain any parallel execution +but emit jobs with delegate and array of dependencies. + +BubelECS was tested on Linux, Windows, Android and WASM. + +**Currently library is in beta stage so some significant API changes can appear.** + +## Design + +For core information about Entity-Component-System architectural pattern I recommend to read definition described at [Wikipedia](https://en.wikipedia.org/wiki/Entity_component_system). + +Main design principles are: + + * **Data oriented design** - components memory is packed into tight block so iterating over entity components is cache friendly + * **Fast and safe EntityID** - every entity is referenced by its unique ID. If entity was destroyed EntityManager will return null pointer for given EntityID. Access to entity from ID is linear operation. + * **Multithreading support** - whole library was developed with multithreading in mind. Adding components is thread-friendly so you get usable EntityID as early as possible. Operations like adding or removing components are deferred to end of frame. Dependencies between systems update are generated automatically. + * **Good logic separation** - system needs information only about components which it's use, there is no relation between systems. Systems can be compiled as multiple separate libraries and used together. + * **Easy systems ordering** - systems are ordered by single priority value. Order of execution of systems with same priority is undefined but order for different priorites are always preserved. + * **Flexible entity to system assignment** - system iterate over any entity that has chosen components. Components can be marked as optional so isn't required but can be used by system. Additionaly system can contain list of components which makes entity excluded from calculation. + * **Builtin events handling** - along with EntityManager library contain EventsManager which makes easier to communicate between multiple entities. + * **Hot-reload** - hot-reloading for systems should be as easy as possible. **Currently not achived yet (WIP!)**. + +Currently library is incredibly fast to iterate over entities and fast enought in direct access to entity component if it's needed. \ +There are some assumptions that should be considered when developing application: + + * Iterating over same components is incredibly fast so it's should be main way of making calculations. + * Using of direct access and events should be used very wisely and not too much. + * Direct component access is faster than events, because events must buffer memory and call multiple system handler callbacks. + * Components can be used to marking entities, assingment to systems and changing logic of entity in runtime. But due to memory fragmentation there sould by many of entities with same type. In other words, sometimes making to many components can lead to performence drop even if adding or removing components is fast enough. + * Systems give great opporunity to separate game logic. Systems can be enabled and disabled easly in runtime. It's can be used to enabling some debug systems if needed. Additionaly this concept makes game logic changes easier to deal with. If you develop your appliactiona wisely it should be trivial to change some core logic by changing only one system, or adding new system. Every entity can easily takes some behaviour from different entity type by adding several components. + +### Features + + * ECS architectural pattern + * Data oriented design + * Safe identifiers (EntityID) + * EntityTemplates - used to add entities, contain list of components and it's data + * Basic events handling + * Easy systems ordering + * Automatic multithreaded jobs generating + * Runtime and fast components adding and removing + * Listeners for adding and removing entity components inside systems + * Passes - systems are grouped to passes. Pass update call update callback of assigned systems. + * Calling custom callbacks for system entity groups + * betterC compatibility + * Emscripten compatibility + +### Planned + + * Worlds - every world works as separate environment. Entities don't with entities from different world. Systems and components should be registered for every world separately. + * Hot-reload support - currently only reloading systems (their functions) works and only if code changes apperas only inside functions. There is possibility to serialize every entity and system, but it's should be provided by library itself with special callbacks and helpers. Additionaly planned is system which helps with taking new EntityID from serialized identifiers. + * External dependencies - ability to provide dependencies between system which isn't related to components. + * Static components - this components will have different memory model and can be accessed only directly. It will be slower with access but don't trigger memory copy when component is added or some different entity was destroyed. It should fix problem with big components which isn't needed by systems iteration callbacks or is required rarely. + * Better EventManager - currently implementy event handling system isn't it what I wanted. I'm not sure exacly what I can improve but multithreaded event execution is one of things which I considered and it probably will work. + * More demos and examples - demo appliaction is very basic now, but in future I planned more minigames and sanbox mode (opportunity to mix many components and systems). I planed some examples to show how to use basic functionality. + * C API - this is currenly in consideration. Everything is dependent on my free time and amount of work required to create good C API. + * More smaller improvement in draft stage... + +For more information about design and usage I recommend to read [documentation](https://mergul.gitlab.io/bubel-ecs/ecs.html)**(WIP)**. + +## Build Instructions + +To build library you needs recent D compiler and optionally Emscripten (with python) to build web version. Currenlty tested are: LDC, DMD and GDC. \ +Supported build systems are DUB and Meson. + +##### DUB +```shell +#available configurations: library, dynlib, library-betterC, dynlib-betterC +dub build -c library -b release +``` + +##### Meson +```shell +#use '-DbetterC=true ' to build betterC code +meson build . #add '--buildtype=release' to build release code +cd build +ninja +``` + +##### Emscripten +```shell +python compile_wasm.py -opt +``` + +For more detailed build options please check documentation for used build system. + +## Demos + +Repository contain demo application. You can check demo [online](https://mergul.gitlab.io/bubel-ecs/ecs_demo.html) or build it form source code using DUB. \ +Online demo support multithreading and page tries to check if client support WASM multithreading or not and loads properly JS and WASM code. It was tested on Chrome, Firefox, Opera, Brave on Linux and Android. On firefox there is problem with multithreaded version so if demo don't works please try to disable shared_memory in browser flags. + +## Code example + +```d + +struct Position +{ + mixin ECS.Components; //makes struct component + float x; + float y; +} + +struct Velocity +{ + mixin ECS.Components; + //default values works + float x = 0.1; + float y = 1; +} + +struct StaticFlag +{ + mixin ECS.Components; +} + +struct UpdateSystem +{ + mixin ECS.System; //makes struct system + + ECS.ExcludedComponents!(StaticFlag); //prevents static entities from update + + struct EntitiesData + { + int length; //entities count + @readonly Entity[] entities; //entities arrays, entity contain ID only + Position[] positions; //positions array + @readonly Velocity[] velocities; //veocities array, readonly (Multithreading tag) + } + + void onUpdate(EntitiesData data) //update callback, called multiple times per frame for every entities types + { + foreach(i; 0..data.length) //iterate over entities + { + data.positions[i].x += data.velocities[i].x * dt; + data.positions[i].y += data.velocities[i].y * dt; + } + } +} + +void main() +{ + manager.beginRegister(); + //register components + manager.registerComponent!Position; + manager.registerComponent!Velocity; + manager.registerComponent!StaticFlag; + //register system with priority 0 + manager.registerSystem!UpdateSystem(0); + manager.endRegister(); + + //allocate template + EntityTemplate* tmpl = manager.allocateEmplate([Velocity.component_id, Position.component_id].staticArray); + scope (exit) manager.freeTemplate(tmpl); + + //gets pointer to template component data + Position* position = tmpl.getComponent!Position; + foreach(i; 0 .. 100) + { + position.x = i % 10; + position.y = i / 10; + manager.addEntity(tmpl); + } + + manager.begin(); //start frame + manager.update(); //update all systems, there onUpdate callbacks are called + manager.end(); //end frame +} + +``` + +## Links + +Documentation: https://mergul.gitlab.io/bubel-ecs/ecs.html \ +Online demo: https://mergul.gitlab.io/bubel-ecs/ecs_demo.html -- 2.47.2 From 109775af871e05f8f7c814e380a900d341c44f20 Mon Sep 17 00:00:00 2001 From: Dawid Masiukiewicz Date: Mon, 4 May 2020 10:33:54 +0000 Subject: [PATCH 04/37] Update README.md --- README.md | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index abc9709..400b0a1 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ [![pipeline status](https://gitlab.com/Mergul/bubel-ecs/badges/master/pipeline.svg)](https://gitlab.com/Mergul/bubel-ecs/-/commits/master) [![codecov](https://codecov.io/gl/Mergul/bubel-ecs/branch/master/graph/badge.svg?token=Unm0TJhFoW)](https://codecov.io/gl/Mergul/bubel-ecs) -BubelECS is Entity-Component-System architecture implementation in D language. -Library aims to delivery fast and flexible architecture for developing games. Library is @nogc and betterC compatible. Even Emscripten build works very well. -Important goal is to keep code without any external dependencies, i. eg. multithreading support don't contain any parallel execution -but emit jobs with delegate and array of dependencies. +BubelECS is Entity-Component-System architectural pattern implementation in D language. +Library aims to delivery fast and flexible architecture for developing games. Library is **@nogc** and **betterC** compatible. WASM is supported thorugh Emscripten. +Project haven't any external dependencies. BubelECS was tested on Linux, Windows, Android and WASM. @@ -13,40 +12,38 @@ BubelECS was tested on Linux, Windows, Android and WASM. ## Design -For core information about Entity-Component-System architectural pattern I recommend to 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). Main design principles are: * **Data oriented design** - components memory is packed into tight block so iterating over entity components is cache friendly - * **Fast and safe EntityID** - every entity is referenced by its unique ID. If entity was destroyed EntityManager will return null pointer for given EntityID. Access to entity from ID is linear operation. - * **Multithreading support** - whole library was developed with multithreading in mind. Adding components is thread-friendly so you get usable EntityID as early as possible. Operations like adding or removing components are deferred to end of frame. Dependencies between systems update are generated automatically. + * **Fast and safe EntityID** - every entity is referenced by its unique ID. Accessing by ID is safe even if entity don'y exist. Access by ID is constant time operation. + * **Multithreading support** - whole library was developed with multithreading in mind. Adding components is thread-friendly so you get usable EntityID as early as possible. Operations like adding or removing components are deferred to end of frame. Dependencies between systems parallel execution are generated automatically. * **Good logic separation** - system needs information only about components which it's use, there is no relation between systems. Systems can be compiled as multiple separate libraries and used together. - * **Easy systems ordering** - systems are ordered by single priority value. Order of execution of systems with same priority is undefined but order for different priorites are always preserved. - * **Flexible entity to system assignment** - system iterate over any entity that has chosen components. Components can be marked as optional so isn't required but can be used by system. Additionaly system can contain list of components which makes entity excluded from calculation. - * **Builtin events handling** - along with EntityManager library contain EventsManager which makes easier to communicate between multiple entities. - * **Hot-reload** - hot-reloading for systems should be as easy as possible. **Currently not achived yet (WIP!)**. + * **Flexible execution model** - system iterate over entities which meet specified conditions. Components can be marked as required, optional or excluded. Systems are exectued in specific order determined by system priority. + * **Builtin events handling** - library has builtin support for event handlig to makes easier communication between different entities. + * **Hot-reload** - hot-reloading for systems should be as easy as possible. In other words library should give functionality to support hot-reload of systems and components with minimal work. **(WIP!)**. -Currently library is incredibly fast to iterate over entities and fast enought in direct access to entity component if it's needed. \ There are some assumptions that should be considered when developing application: - * Iterating over same components is incredibly fast so it's should be main way of making calculations. - * Using of direct access and events should be used very wisely and not too much. + * Iterating over components is fastest way of access data so it's should be main way of making calculations. + * Using of direct access and events should be used very wisely and minimized. * Direct component access is faster than events, because events must buffer memory and call multiple system handler callbacks. - * Components can be used to marking entities, assingment to systems and changing logic of entity in runtime. But due to memory fragmentation there sould by many of entities with same type. In other words, sometimes making to many components can lead to performence drop even if adding or removing components is fast enough. - * Systems give great opporunity to separate game logic. Systems can be enabled and disabled easly in runtime. It's can be used to enabling some debug systems if needed. Additionaly this concept makes game logic changes easier to deal with. If you develop your appliactiona wisely it should be trivial to change some core logic by changing only one system, or adding new system. Every entity can easily takes some behaviour from different entity type by adding several components. + * Components can be used to marking entities, assingment to systems and changing logic of entity in runtime. Using too much component based marking can lead to memory fragmentation and performence drop. + * Systems give great opporunity to separate game logic. Systems can be enabled and disabled easly in runtime. It's can be used to enabling some debug systems if needed. Additionaly this concept makes game logic changes easier to deal with. If you develop your application wisely it should be trivial to change some core logic by changing only several systems or adding some new systems. Every entity can easily takes some behaviour from different entity type by adding several components. ### Features * ECS architectural pattern * Data oriented design * Safe identifiers (EntityID) - * EntityTemplates - used to add entities, contain list of components and it's data + * EntityTemplates * Basic events handling * Easy systems ordering * Automatic multithreaded jobs generating * Runtime and fast components adding and removing * Listeners for adding and removing entity components inside systems - * Passes - systems are grouped to passes. Pass update call update callback of assigned systems. + * Update passes * Calling custom callbacks for system entity groups * betterC compatibility * Emscripten compatibility @@ -54,15 +51,16 @@ There are some assumptions that should be considered when developing application ### Planned * Worlds - every world works as separate environment. Entities don't with entities from different world. Systems and components should be registered for every world separately. - * Hot-reload support - currently only reloading systems (their functions) works and only if code changes apperas only inside functions. There is possibility to serialize every entity and system, but it's should be provided by library itself with special callbacks and helpers. Additionaly planned is system which helps with taking new EntityID from serialized identifiers. + * Hot-reload support - currently only reloading systems (their functions) works. There is possibility to serialize every entity and system, but it's should be provided by library itself with special callbacks and helpers. Additionaly planned is system which helps with taking new EntityID from serialized identifiers. * External dependencies - ability to provide dependencies between system which isn't related to components. - * Static components - this components will have different memory model and can be accessed only directly. It will be slower with access but don't trigger memory copy when component is added or some different entity was destroyed. It should fix problem with big components which isn't needed by systems iteration callbacks or is required rarely. - * Better EventManager - currently implementy event handling system isn't it what I wanted. I'm not sure exacly what I can improve but multithreaded event execution is one of things which I considered and it probably will work. - * More demos and examples - demo appliaction is very basic now, but in future I planned more minigames and sanbox mode (opportunity to mix many components and systems). I planed some examples to show how to use basic functionality. - * C API - this is currenly in consideration. Everything is dependent on my free time and amount of work required to create good C API. - * More smaller improvement in draft stage... + * Static components - this components will have different memory model and can be accessed only directly. It will be slower to access but won't trigger memory copy when component is added. It should fix problem with big components which isn't needed by systems iteration callbacks or is required rarely. + * Better EventManager - there are several optimization and improvements that can be added in the future. + * More demos and examples - demo appliaction is very basic now, but in future more minigames and sanbox mode (opportunity to mix many components and systems) are planned. + * C API - in highly depends on amount of work required. Makes possible to use library from different languages. + * GPU compute - idea in draft stage. Special components and systems whose data wolud be on GPU memory. + * More smaller improvements... -For more information about design and usage I recommend to read [documentation](https://mergul.gitlab.io/bubel-ecs/ecs.html)**(WIP)**. +For more information about design and usage feel free to read [documentation](https://mergul.gitlab.io/bubel-ecs/ecs.html)**(WIP)** and [WIKI](https://gitlab.com/Mergul/bubel-ecs/-/wikis/home). ## Build Instructions -- 2.47.2 From 167ad5437ab6b5e49ab14ea33ef8ab3a064a05d3 Mon Sep 17 00:00:00 2001 From: Mergul Date: Mon, 4 May 2020 16:17:40 +0200 Subject: [PATCH 05/37] CI changes --- .gitlab-ci.yml | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 14ba23c..1710e4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,25 +9,18 @@ build_code: stage: build image: "registry.gitlab.com/mergul/bubel-ecs:latest" script: - - mkdir build - /bin/bash /compile_ecs.sh - - cp artifacts/* build/ - - cp -r public build/ artifacts: expire_in: 1h paths: - - build - rules: - - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' - when: always - - when: always + - binaries allow_failure: true test_dmd_debug: stage: test image: frolvlad/alpine-glibc script: - - build/dmd_debug_unittest + - binaries/dmd_debug_unittest artifacts: reports: junit: test_report.xml @@ -35,7 +28,7 @@ test_dmd: stage: test image: frolvlad/alpine-glibc script: - - build/dmd_release_unittest + - binaries/dmd_release_unittest artifacts: reports: junit: test_report.xml @@ -43,7 +36,7 @@ test_dmd_betterC: stage: test image: frolvlad/alpine-glibc script: - - build/dmd_release_unittest_bc + - binaries/dmd_release_unittest_bc artifacts: reports: junit: test_report.xml @@ -56,10 +49,25 @@ coverage_test_dmd: - build_code script: - mkdir reports - - build/dmd_unittest_cov + - binaries/dmd_unittest_cov after_script: - bash <(curl -s https://codecov.io/bash) -s reports -t 1a0c0169-a721-4085-8252-fed4755dcd8c +build_wasm: + stage: build + image: "registry.gitlab.com/mergul/bubel-ecs:latest" + script: + - /bin/bash /compile_wasm.sh + - /bin/bash /gen_doc.sh + artifacts: + expire_in: 1h + paths: + - build + rules: + - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' + when: always + allow_failure: true + emscripten: stage: build_emscripten image: "registry.gitlab.com/mergul/bubel-ecs/emscripten:latest" -- 2.47.2 From 5411e97cb1b65a0f4dac8544b83ec5e1147f8c5a Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 5 May 2020 16:56:51 +0200 Subject: [PATCH 06/37] Move ECS to Bubel module --- .gitlab-ci.yml | 4 +++ README.md | 5 ++++ demos/external/sources/mmutils/thread_pool.d | 4 +-- demos/source/app.d | 6 ++-- demos/source/demos/bullet_madnes.d | 10 +++---- demos/source/demos/chipmunk2d.d | 10 +++---- demos/source/demos/events.d | 10 +++---- demos/source/demos/flag_component.d | 10 +++---- demos/source/demos/physics.d | 10 +++---- demos/source/demos/simple.d | 10 +++---- demos/source/demos/snake.d | 12 ++++---- demos/source/demos/space_invaders.d | 10 +++---- demos/source/game_core/job_updater.d | 8 +++--- demos/source/gui/manager.d | 8 +++--- demos/source/gui/system.d | 2 +- demos/source/gui/template_.d | 2 +- demos/utils/source/ecs_utils/gfx/buffer.d | 2 +- demos/utils/source/ecs_utils/gfx/config.d | 4 +-- demos/utils/source/ecs_utils/gfx/material.d | 2 +- demos/utils/source/ecs_utils/gfx/mesh.d | 2 +- demos/utils/source/ecs_utils/gfx/renderer.d | 2 +- demos/utils/source/ecs_utils/gfx/shader.d | 2 +- demos/utils/source/ecs_utils/gfx/texture.d | 2 +- demos/utils/source/ecs_utils/gfx/vertex.d | 2 +- meson.build | 30 ++++++++++---------- source/{ => bubel}/ecs/atomic.d | 2 +- source/{ => bubel}/ecs/attributes.d | 2 +- source/{ => bubel}/ecs/block_allocator.d | 6 ++-- source/{ => bubel}/ecs/core.d | 6 ++-- source/{ => bubel}/ecs/entity.d | 6 ++-- source/{ => bubel}/ecs/events.d | 10 +++---- source/{ => bubel}/ecs/hash_map.d | 6 ++-- source/{ => bubel}/ecs/id_manager.d | 10 +++---- source/{ => bubel}/ecs/manager.d | 26 ++++++++--------- source/bubel/ecs/package.d | 10 +++++++ source/{ => bubel}/ecs/simple_vector.d | 4 +-- source/{ => bubel}/ecs/std.d | 2 +- source/{ => bubel}/ecs/system.d | 6 ++-- source/{ => bubel}/ecs/traits.d | 2 +- source/{ => bubel}/ecs/vector.d | 4 +-- source/ecs/package.d | 10 ------- tests/basic.d | 8 +++--- tests/id_manager.d | 4 +-- tests/runner.d | 6 ++-- tests/tests.d | 14 ++++----- tests/vector.d | 4 +-- 46 files changed, 163 insertions(+), 154 deletions(-) rename source/{ => bubel}/ecs/atomic.d (99%) rename source/{ => bubel}/ecs/attributes.d (97%) rename source/{ => bubel}/ecs/block_allocator.d (97%) rename source/{ => bubel}/ecs/core.d (96%) rename source/{ => bubel}/ecs/entity.d (97%) rename source/{ => bubel}/ecs/events.d (97%) rename source/{ => bubel}/ecs/hash_map.d (98%) rename source/{ => bubel}/ecs/id_manager.d (98%) rename source/{ => bubel}/ecs/manager.d (99%) create mode 100644 source/bubel/ecs/package.d rename source/{ => bubel}/ecs/simple_vector.d (95%) rename source/{ => bubel}/ecs/std.d (99%) rename source/{ => bubel}/ecs/system.d (98%) rename source/{ => bubel}/ecs/traits.d (97%) rename source/{ => bubel}/ecs/vector.d (99%) delete mode 100644 source/ecs/package.d diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1710e4a..a2d26ff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,7 @@ +default: + artifacts: + expire_in: 1 day + stages: - build - test diff --git a/README.md b/README.md index 400b0a1..b223f72 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,11 @@ ninja python compile_wasm.py -opt ``` +##### Documentation +```shell +adrdox -i source/bubel/ecs/ -o docs/ -s skeleton.html +``` + For more detailed build options please check documentation for used build system. ## Demos diff --git a/demos/external/sources/mmutils/thread_pool.d b/demos/external/sources/mmutils/thread_pool.d index 374fbc1..161eb22 100644 --- a/demos/external/sources/mmutils/thread_pool.d +++ b/demos/external/sources/mmutils/thread_pool.d @@ -1,6 +1,6 @@ module mmutils.thread_pool; -import ecs.atomic; +import bubel.ecs.atomic; //import core.stdc.stdio; //import core.stdc.stdlib : free, malloc, realloc; @@ -32,7 +32,7 @@ else version (D_BetterC) { - import ecs.std; + import bubel.ecs.std; extern (C) __gshared int _d_eh_personality(int, int, size_t, void*, void*) { return 0; diff --git a/demos/source/app.d b/demos/source/app.d index 150d653..acdc385 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -6,9 +6,9 @@ import cimgui.cimgui; import game_core.job_updater; -import ecs.manager; -import ecs.core; -import ecs.std; +import bubel.ecs.manager; +import bubel.ecs.core; +import bubel.ecs.std; import ecs_utils.gfx.renderer; import ecs_utils.imgui_bind; diff --git a/demos/source/demos/bullet_madnes.d b/demos/source/demos/bullet_madnes.d index 0996e2e..420d6a3 100644 --- a/demos/source/demos/bullet_madnes.d +++ b/demos/source/demos/bullet_madnes.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/chipmunk2d.d b/demos/source/demos/chipmunk2d.d index 25b9c66..96e5fa6 100644 --- a/demos/source/demos/chipmunk2d.d +++ b/demos/source/demos/chipmunk2d.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/events.d b/demos/source/demos/events.d index 620ff4d..6215160 100644 --- a/demos/source/demos/events.d +++ b/demos/source/demos/events.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/flag_component.d b/demos/source/demos/flag_component.d index a1863b9..392164e 100644 --- a/demos/source/demos/flag_component.d +++ b/demos/source/demos/flag_component.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/physics.d b/demos/source/demos/physics.d index 6e9ba4a..5fccc0b 100644 --- a/demos/source/demos/physics.d +++ b/demos/source/demos/physics.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index edd20c6..89fe801 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 6bb8cef..c4123ba 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -6,12 +6,12 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; -import ecs.vector; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; +import bubel.ecs.vector; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index bddf53c..92c4e15 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -6,11 +6,11 @@ import bindbc.sdl; import cimgui.cimgui; -import ecs.attributes; -import ecs.core; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import ecs_utils.gfx.texture; import ecs_utils.math.vector; diff --git a/demos/source/game_core/job_updater.d b/demos/source/game_core/job_updater.d index 46cd609..6c20a83 100644 --- a/demos/source/game_core/job_updater.d +++ b/demos/source/game_core/job_updater.d @@ -1,13 +1,13 @@ module game_core.job_updater; -import ecs.std; -import ecs.vector; -import ecs.atomic; +import bubel.ecs.std; +import bubel.ecs.vector; +import bubel.ecs.atomic; import ecs_utils.utils; //import core.time; -import ecs.manager; +import bubel.ecs.manager; import mmutils.thread_pool; version(LDC) diff --git a/demos/source/gui/manager.d b/demos/source/gui/manager.d index 8a46a92..185fcd1 100644 --- a/demos/source/gui/manager.d +++ b/demos/source/gui/manager.d @@ -4,10 +4,10 @@ import app; import cimgui.cimgui; -import ecs.std; -import ecs.system; -import ecs.vector; -import ecs.entity; +import bubel.ecs.std; +import bubel.ecs.system; +import bubel.ecs.vector; +import bubel.ecs.entity; import gui.system; import gui.template_; diff --git a/demos/source/gui/system.d b/demos/source/gui/system.d index bbee409..112bed3 100644 --- a/demos/source/gui/system.d +++ b/demos/source/gui/system.d @@ -1,6 +1,6 @@ module gui.system; -import ecs.system; +import bubel.ecs.system; struct SystemGUI { diff --git a/demos/source/gui/template_.d b/demos/source/gui/template_.d index 2aa1833..4d3de11 100644 --- a/demos/source/gui/template_.d +++ b/demos/source/gui/template_.d @@ -1,6 +1,6 @@ module gui.template_; -import ecs.entity; +import bubel.ecs.entity; struct TemplateGUI { diff --git a/demos/utils/source/ecs_utils/gfx/buffer.d b/demos/utils/source/ecs_utils/gfx/buffer.d index 2c25324..b11cb85 100644 --- a/demos/utils/source/ecs_utils/gfx/buffer.d +++ b/demos/utils/source/ecs_utils/gfx/buffer.d @@ -1,6 +1,6 @@ module ecs_utils.gfx.buffer; -import ecs.std; +import bubel.ecs.std; import glad.gl.gl; import glad.gl.gles2; diff --git a/demos/utils/source/ecs_utils/gfx/config.d b/demos/utils/source/ecs_utils/gfx/config.d index de5528a..0683647 100644 --- a/demos/utils/source/ecs_utils/gfx/config.d +++ b/demos/utils/source/ecs_utils/gfx/config.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.config; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; import ecs_utils.gfx.material; import ecs_utils.gfx.mesh; @@ -18,7 +18,7 @@ enum LayerType sorted } -import ecs.vector; +import bubel.ecs.vector; static struct GfxConfig { diff --git a/demos/utils/source/ecs_utils/gfx/material.d b/demos/utils/source/ecs_utils/gfx/material.d index b697992..fbd88b9 100644 --- a/demos/utils/source/ecs_utils/gfx/material.d +++ b/demos/utils/source/ecs_utils/gfx/material.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.material; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; import ecs_utils.gfx.shader; diff --git a/demos/utils/source/ecs_utils/gfx/mesh.d b/demos/utils/source/ecs_utils/gfx/mesh.d index 346226f..20d5855 100644 --- a/demos/utils/source/ecs_utils/gfx/mesh.d +++ b/demos/utils/source/ecs_utils/gfx/mesh.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.mesh; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; import ecs_utils.gfx.buffer; diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 2a04c57..d56b5c8 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.renderer; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; //import ecs_utils.core : Backend; import ecs_utils.gfx.buffer; diff --git a/demos/utils/source/ecs_utils/gfx/shader.d b/demos/utils/source/ecs_utils/gfx/shader.d index 7749013..5e24d9a 100644 --- a/demos/utils/source/ecs_utils/gfx/shader.d +++ b/demos/utils/source/ecs_utils/gfx/shader.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.shader; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; import glad.gl.gl; diff --git a/demos/utils/source/ecs_utils/gfx/texture.d b/demos/utils/source/ecs_utils/gfx/texture.d index bb4c62a..05e2fd9 100644 --- a/demos/utils/source/ecs_utils/gfx/texture.d +++ b/demos/utils/source/ecs_utils/gfx/texture.d @@ -2,7 +2,7 @@ module ecs_utils.gfx.texture; import bindbc.sdl; -import ecs.std; +import bubel.ecs.std; import ecs_utils.math.vector; diff --git a/demos/utils/source/ecs_utils/gfx/vertex.d b/demos/utils/source/ecs_utils/gfx/vertex.d index 1d11fdd..b63803c 100644 --- a/demos/utils/source/ecs_utils/gfx/vertex.d +++ b/demos/utils/source/ecs_utils/gfx/vertex.d @@ -1,6 +1,6 @@ module ecs_utils.gfx.vertex; -import ecs.std; +import bubel.ecs.std; struct Vertex { diff --git a/meson.build b/meson.build index 3317d5c..c31b93a 100644 --- a/meson.build +++ b/meson.build @@ -1,21 +1,21 @@ project('DECS', 'd') src = [ - 'source/ecs/atomic.d', - 'source/ecs/attributes.d', - 'source/ecs/block_allocator.d', - 'source/ecs/core.d', - 'source/ecs/entity.d', - 'source/ecs/events.d', - 'source/ecs/hash_map.d', - 'source/ecs/id_manager.d', - 'source/ecs/manager.d', - 'source/ecs/package.d', - 'source/ecs/simple_vector.d', - 'source/ecs/std.d', - 'source/ecs/system.d', - 'source/ecs/traits.d', - 'source/ecs/vector.d' + 'source/bubel/ecs/atomic.d', + 'source/bubel/ecs/attributes.d', + 'source/bubel/ecs/block_allocator.d', + 'source/bubel/ecs/core.d', + 'source/bubel/ecs/entity.d', + 'source/bubel/ecs/events.d', + 'source/bubel/ecs/hash_map.d', + 'source/bubel/ecs/id_manager.d', + 'source/bubel/ecs/manager.d', + 'source/bubel/ecs/package.d', + 'source/bubel/ecs/simple_vector.d', + 'source/bubel/ecs/std.d', + 'source/bubel/ecs/system.d', + 'source/bubel/ecs/traits.d', + 'source/bubel/ecs/vector.d' ] tests_src = [ diff --git a/source/ecs/atomic.d b/source/bubel/ecs/atomic.d similarity index 99% rename from source/ecs/atomic.d rename to source/bubel/ecs/atomic.d index 5e5f447..9155c8f 100644 --- a/source/ecs/atomic.d +++ b/source/bubel/ecs/atomic.d @@ -7,7 +7,7 @@ Emscripten functions are contained in API similar to druntime. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.atomic; +module bubel.ecs.atomic; version(Emscripten)version = ECSEmscripten; diff --git a/source/ecs/attributes.d b/source/bubel/ecs/attributes.d similarity index 97% rename from source/ecs/attributes.d rename to source/bubel/ecs/attributes.d index 20d0f60..2bc0aec 100644 --- a/source/ecs/attributes.d +++ b/source/bubel/ecs/attributes.d @@ -20,7 +20,7 @@ Struct EntitiesData Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.attributes; +module bubel.ecs.attributes; ///Used to mark optional components for system. enum optional = "optional"; diff --git a/source/ecs/block_allocator.d b/source/bubel/ecs/block_allocator.d similarity index 97% rename from source/ecs/block_allocator.d rename to source/bubel/ecs/block_allocator.d index d9f08ca..740b762 100644 --- a/source/ecs/block_allocator.d +++ b/source/bubel/ecs/block_allocator.d @@ -6,10 +6,10 @@ Module contain memory allocator. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.block_allocator; +module bubel.ecs.block_allocator; -import ecs.manager; -import ecs.std; +import bubel.ecs.manager; +import bubel.ecs.std; /************************************************************************************************************************ Allocator allocate large blocks and return smaller blocks. When there is no more blocks then next large block is allocated. diff --git a/source/ecs/core.d b/source/bubel/ecs/core.d similarity index 96% rename from source/ecs/core.d rename to source/bubel/ecs/core.d index c346c9f..ccdf3e2 100644 --- a/source/ecs/core.d +++ b/source/bubel/ecs/core.d @@ -49,10 +49,10 @@ Struct System1 Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.core; +module bubel.ecs.core; -public import ecs.manager; -public import ecs.entity; +public import bubel.ecs.manager; +public import bubel.ecs.entity; /************************************************************************************************************************ Main struct used as namespace for templates. diff --git a/source/ecs/entity.d b/source/bubel/ecs/entity.d similarity index 97% rename from source/ecs/entity.d rename to source/bubel/ecs/entity.d index 0bd2dea..441f412 100644 --- a/source/ecs/entity.d +++ b/source/bubel/ecs/entity.d @@ -4,10 +4,10 @@ Entity module. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.entity; +module bubel.ecs.entity; -import ecs.system; -import ecs.manager; +import bubel.ecs.system; +import bubel.ecs.manager; /************************************************************************************************************************ Entity ID structure. Used as reference to Entity. Pointer to entity should be ever used to store entity reference! diff --git a/source/ecs/events.d b/source/bubel/ecs/events.d similarity index 97% rename from source/ecs/events.d rename to source/bubel/ecs/events.d index ac7e185..772d5b5 100644 --- a/source/ecs/events.d +++ b/source/bubel/ecs/events.d @@ -1,9 +1,9 @@ -module ecs.events; +module bubel.ecs.events; -import ecs.block_allocator; -import ecs.entity; -import ecs.manager; -import ecs.std; +import bubel.ecs.block_allocator; +import bubel.ecs.entity; +import bubel.ecs.manager; +import bubel.ecs.std; import std.algorithm.comparison : max; diff --git a/source/ecs/hash_map.d b/source/bubel/ecs/hash_map.d similarity index 98% rename from source/ecs/hash_map.d rename to source/bubel/ecs/hash_map.d index 5a8b253..1f7a1f8 100755 --- a/source/ecs/hash_map.d +++ b/source/bubel/ecs/hash_map.d @@ -1,9 +1,9 @@ -module ecs.hash_map; +module bubel.ecs.hash_map; import std.traits; -import ecs.vector; -import ecs.traits; +import bubel.ecs.vector; +import bubel.ecs.traits; enum doNotInline = "version(DigitalMars)pragma(inline,false);version(LDC)pragma(LDC_never_inline);"; diff --git a/source/ecs/id_manager.d b/source/bubel/ecs/id_manager.d similarity index 98% rename from source/ecs/id_manager.d rename to source/bubel/ecs/id_manager.d index 1ab8465..c8eadec 100644 --- a/source/ecs/id_manager.d +++ b/source/bubel/ecs/id_manager.d @@ -1,10 +1,10 @@ -module ecs.id_manager; +module bubel.ecs.id_manager; -import ecs.entity; -import ecs.std; -import ecs.vector; +import bubel.ecs.entity; +import bubel.ecs.std; +import bubel.ecs.vector; -import ecs.atomic; +import bubel.ecs.atomic; import core.stdc.string : memcpy; /************************************************************************************************************************ diff --git a/source/ecs/manager.d b/source/bubel/ecs/manager.d similarity index 99% rename from source/ecs/manager.d rename to source/bubel/ecs/manager.d index aca7f53..be4f37a 100644 --- a/source/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -4,7 +4,7 @@ Most important module. Almost every function is called from EntityManager. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.manager; +module bubel.ecs.manager; import std.algorithm : max; import std.conv : to; @@ -14,21 +14,21 @@ import std.traits; //import core.stdc.stdlib : qsort; //import core.stdc.string; -import ecs.system; //not ordered as forward reference bug workaround -import ecs.block_allocator; -import ecs.entity; -import ecs.events; -import ecs.hash_map; -import ecs.id_manager; -import ecs.simple_vector; -import ecs.std; -import ecs.traits; -import ecs.vector; -import ecs.atomic; +import bubel.ecs.system; //not ordered as forward reference bug workaround +import bubel.ecs.block_allocator; +import bubel.ecs.entity; +import bubel.ecs.events; +import bubel.ecs.hash_map; +import bubel.ecs.id_manager; +import bubel.ecs.simple_vector; +import bubel.ecs.std; +import bubel.ecs.traits; +import bubel.ecs.vector; +import bubel.ecs.atomic; export alias gEM = EntityManager.instance; export alias gEntityManager = EntityManager.instance; -alias SerializeVector = ecs.vector.Vector!ubyte; +alias SerializeVector = bubel.ecs.vector.Vector!ubyte; /************************************************************************************************************************ Entity manager is responsible for everything. diff --git a/source/bubel/ecs/package.d b/source/bubel/ecs/package.d new file mode 100644 index 0000000..ee3f62b --- /dev/null +++ b/source/bubel/ecs/package.d @@ -0,0 +1,10 @@ +module ecs; + +public import bubel.ecs.core; +public import bubel.ecs.entity; +public import bubel.ecs.manager; +public import bubel.ecs.system; + +import bubel.ecs.events; +import bubel.ecs.id_manager; +import bubel.ecs.std; \ No newline at end of file diff --git a/source/ecs/simple_vector.d b/source/bubel/ecs/simple_vector.d similarity index 95% rename from source/ecs/simple_vector.d rename to source/bubel/ecs/simple_vector.d index 175c015..28d4707 100644 --- a/source/ecs/simple_vector.d +++ b/source/bubel/ecs/simple_vector.d @@ -1,6 +1,6 @@ -module ecs.simple_vector; +module bubel.ecs.simple_vector; -import ecs.std; +import bubel.ecs.std; //import core.stdc.string; diff --git a/source/ecs/std.d b/source/bubel/ecs/std.d similarity index 99% rename from source/ecs/std.d rename to source/bubel/ecs/std.d index c027fd5..4cec197 100644 --- a/source/ecs/std.d +++ b/source/bubel/ecs/std.d @@ -5,7 +5,7 @@ This module contain implementation of standard functionality. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.std; +module bubel.ecs.std; version(Emscripten)version = ECSEmscripten; diff --git a/source/ecs/system.d b/source/bubel/ecs/system.d similarity index 98% rename from source/ecs/system.d rename to source/bubel/ecs/system.d index 22a3955..6452e6c 100644 --- a/source/ecs/system.d +++ b/source/bubel/ecs/system.d @@ -4,10 +4,10 @@ System module. Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz License: BSD 3-clause, see LICENSE file in project root folder. */ -module ecs.system; +module bubel.ecs.system; -import ecs.entity; -import ecs.manager; +import bubel.ecs.entity; +import bubel.ecs.manager; /************************************************************************************************************************ System contain data required to proper glue EntityManager with Systems. diff --git a/source/ecs/traits.d b/source/bubel/ecs/traits.d similarity index 97% rename from source/ecs/traits.d rename to source/bubel/ecs/traits.d index d19259f..7043b2e 100644 --- a/source/ecs/traits.d +++ b/source/bubel/ecs/traits.d @@ -1,4 +1,4 @@ -module ecs.traits; +module bubel.ecs.traits; import std.traits; diff --git a/source/ecs/vector.d b/source/bubel/ecs/vector.d similarity index 99% rename from source/ecs/vector.d rename to source/bubel/ecs/vector.d index 84ecc51..3334be2 100644 --- a/source/ecs/vector.d +++ b/source/bubel/ecs/vector.d @@ -1,8 +1,8 @@ -module ecs.vector; +module bubel.ecs.vector; import core.bitop; //import core.stdc.stdlib : free, malloc; -import ecs.std; +import bubel.ecs.std; //import core.stdc.string : memcpy, memset; //import std.algorithm : swap; import std.conv : emplace; diff --git a/source/ecs/package.d b/source/ecs/package.d deleted file mode 100644 index eda440d..0000000 --- a/source/ecs/package.d +++ /dev/null @@ -1,10 +0,0 @@ -module ecs; - -public import ecs.core; -public import ecs.entity; -public import ecs.manager; -public import ecs.system; - -import ecs.events; -import ecs.id_manager; -import ecs.std; \ No newline at end of file diff --git a/tests/basic.d b/tests/basic.d index f3f2832..653575f 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -1,9 +1,9 @@ module tests.basic; -import ecs.core; -import ecs.manager; -import ecs.system; -import ecs.attributes; +import bubel.ecs.core; +import bubel.ecs.manager; +import bubel.ecs.system; +import bubel.ecs.attributes; import std.array : staticArray; diff --git a/tests/id_manager.d b/tests/id_manager.d index afaafe5..b0f97d5 100644 --- a/tests/id_manager.d +++ b/tests/id_manager.d @@ -1,7 +1,7 @@ module tests.id_manager; -import ecs.id_manager; -import ecs.entity; +import bubel.ecs.id_manager; +import bubel.ecs.entity; unittest { diff --git a/tests/runner.d b/tests/runner.d index 5eaf99b..7b8f6b5 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -5,9 +5,9 @@ import core.stdc.stdio; import core.stdc.string; import core.sys.posix.setjmp; -import ecs.vector; -import ecs.simple_vector; -import ecs.std; +import bubel.ecs.vector; +import bubel.ecs.simple_vector; +import bubel.ecs.std; enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; diff --git a/tests/tests.d b/tests/tests.d index ea554d1..d923116 100644 --- a/tests/tests.d +++ b/tests/tests.d @@ -3,12 +3,12 @@ module tests.tests; import std.experimental.allocator; import std.experimental.allocator.mallocator;*/ -import ecs.entity; -import ecs.events; -import ecs.manager; -import ecs.system; -import ecs.attributes; -import ecs.core; +import bubel.ecs.entity; +import bubel.ecs.events; +import bubel.ecs.manager; +import bubel.ecs.system; +import bubel.ecs.attributes; +import bubel.ecs.core; version (WebAssembly) { @@ -714,7 +714,7 @@ else: //foreach(i; 0..1_000_000)gEM.removeEntity(gEM.addEntity(tmpl).id); - import ecs.std; + import bubel.ecs.std; EntityID[] idss = Mallocator.makeArray!EntityID(5000); //[5000] //scope(exit)Mallocator.dispose(idss); diff --git a/tests/vector.d b/tests/vector.d index 4b2ad5b..d733f0f 100644 --- a/tests/vector.d +++ b/tests/vector.d @@ -1,7 +1,7 @@ module tests.vector; -import ecs.simple_vector; -//import ecs.vector; +import bubel.ecs.simple_vector; +//import bubel.ecs.vector; @("simple-vector") unittest -- 2.47.2 From 46aba822d0a3024d7177261b18844e130a2ff65a Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 6 May 2020 10:55:40 +0200 Subject: [PATCH 07/37] Improved documentation and removed some old code --- source/bubel/ecs/id_manager.d | 27 --------------------------- source/bubel/ecs/simple_vector.d | 8 ++++++++ source/bubel/ecs/system.d | 26 +++++++++++++------------- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/source/bubel/ecs/id_manager.d b/source/bubel/ecs/id_manager.d index c8eadec..4c3a2c3 100644 --- a/source/bubel/ecs/id_manager.d +++ b/source/bubel/ecs/id_manager.d @@ -17,10 +17,6 @@ struct IDManager */ pragma(inline, false) EntityID getNewID() nothrow @nogc { - //uint current = m_next_id; - //uint next;// = m_ids_array[m_next_id].next_id; -//begin: - //if (current == uint.max)//> m_last_id) int current = m_stack_top.atomicOp!"-="(1) + 1; if(current < 0) { @@ -48,29 +44,11 @@ struct IDManager return id; } - - //current += 1; uint index = m_free_stack[current]; EntityID id; id.id = index; id.counter = m_ids_array[index].counter; - //current = m_ids_array[index].next_id; return id; - - /*next = m_ids_array[current].next_id; - if(cas(&m_next_id,current,next)) - { - EntityID id; - id.id = current; - id.counter = m_ids_array[current].counter; - m_next_id = m_ids_array[current].next_id; - return id; - } - else - { - current = next; - goto begin; - }*/ } /************************************************************************************************************************ @@ -82,9 +60,7 @@ struct IDManager if (data.counter != id.counter) return; data.counter++; - //data.next_id = m_next_id; data.entity = null; - ///m_next_id = id.id; m_stack_top.atomicOp!"+="(1); m_free_stack[m_stack_top] = id.id; @@ -241,13 +217,10 @@ struct IDManager private: Mutex* add_mutex; - //shared uint m_next_id = 0; - //shared uint m_last_id = 0; Data[] m_ids_array = null; uint m_blocks_count = 0; Block[] m_blocks; - //shared int m_stack_top = -1; uint[] m_free_stack = null; align(64) shared uint m_last_id = 0; diff --git a/source/bubel/ecs/simple_vector.d b/source/bubel/ecs/simple_vector.d index 28d4707..1de026e 100644 --- a/source/bubel/ecs/simple_vector.d +++ b/source/bubel/ecs/simple_vector.d @@ -4,11 +4,16 @@ import bubel.ecs.std; //import core.stdc.string; +/************************************************************************************************************************ +Vector for byte data. Simpler than standard template-based implementation designed for better performance. \ +Rellocates 1024 elements at once instead of doubling size. +*/ struct SimpleVector { @disable this(this); + ///Add element to vector void add(ubyte el) nothrow @nogc { while(used >= data.length) @@ -19,6 +24,7 @@ struct SimpleVector data[used++] = el; } + ///Add array of elements to vector void add(ubyte[] el) nothrow @nogc { while(used + el.length >= data.length) @@ -30,6 +36,7 @@ struct SimpleVector used += el.length; } + ///Return vector length size_t length() nothrow @nogc { return used; @@ -55,6 +62,7 @@ struct SimpleVector return used; } + ///set vector length to 0 void clear() nothrow @nogc { used = 0; diff --git a/source/bubel/ecs/system.d b/source/bubel/ecs/system.d index 6452e6c..7343831 100644 --- a/source/bubel/ecs/system.d +++ b/source/bubel/ecs/system.d @@ -14,16 +14,16 @@ System contain data required to proper glue EntityManager with Systems. System callbacks: $(LIST * void onUpdate(EntitesData); - * void onEnable() - * void onDisable(); - * bool onBegin(); - * void onEnd(); - * void onCreate() - * void onDestroy(); - * void onAddEntity(EntitesData); - * void onRemoveEntity(EntitiesData); - * void onChangeEntity(EntitiesData); - * void handleEvent(Entity*, Event); + * void onEnable() - called inside system.enable() function + * void onDisable() - called inside system.disable() function + * bool onBegin() - called inside manager.begin() + * void onEnd() - called inside manager.end() + * void onCreate() - called after registration inside registerSystem function + * void onDestroy() - called during re-registration and inside manager destructor + * void onAddEntity(EntitesData) - called for every entity which are assigned to system (by adding new entity or changing its components) + * void onRemoveEntity(EntitiesData) - called for every entity removed from system update process + * void onChangeEntity(EntitiesData) - called for every entity which components are changed but it was previously assigned to system + * void handleEvent(Entity*, Event) - called for every event supported by system ) */ struct System @@ -66,15 +66,15 @@ struct System } /************************************************************************************************************************ - Get system priority. + Get if system will be executed during current frame. Should be checked after manager.begin(). Its value is setted as result of manager.onBegin() callback. */ - export bool execute() nothrow @nogc + export bool willExecute() nothrow @nogc { return m_execute; } /************************************************************************************************************************ - Get system priority. + Get system id. */ export ushort id() nothrow @nogc { -- 2.47.2 From 4bd5a37b5d51be1d9ed088c6461d0f2ab369c3e9 Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 7 May 2020 14:07:07 +0200 Subject: [PATCH 08/37] Demo update and start counting tests times -Fixed performance issue with multithreading and rendering -start making better shaders (by using many macros) -speed up rendeing when maximum objects count was reached -remove map rendering form Snake demo, and render entities by themself -start adding depth and color rendering parameters -added properly names to jobs (for debugging purpses) -starts adding multithreaded rendering -added some math to vectors -changes execute() to willExecute(). Probably should have different name. --- demos/assets/shaders/base.fp | 45 ++- demos/assets/shaders/base.vp | 57 +++- demos/external/sources/mmutils/thread_pool.d | 26 +- demos/source/app.d | 2 +- demos/source/demos/simple.d | 1 + demos/source/demos/snake.d | 319 ++++++++++--------- demos/source/demos/space_invaders.d | 11 +- demos/source/game_core/job_updater.d | 7 +- demos/utils/source/ecs_utils/gfx/renderer.d | 66 +++- demos/utils/source/ecs_utils/math/vector.d | 9 + source/bubel/ecs/manager.d | 4 +- tests/runner.d | 14 + tests/time.d | 66 ++++ 13 files changed, 429 insertions(+), 198 deletions(-) create mode 100644 tests/time.d diff --git a/demos/assets/shaders/base.fp b/demos/assets/shaders/base.fp index 043fa58..ca38c30 100644 --- a/demos/assets/shaders/base.fp +++ b/demos/assets/shaders/base.fp @@ -3,6 +3,31 @@ precision mediump float; precision lowp sampler2D; precision lowp samplerCube; + +#ifdef GLES + #define TEX(x,y) texture2D(x,y) + #if __VERSION__ >290 + #define M_IN in mediump + #define L_IN in lowp + #else + #define M_IN varying mediump + #define L_IN varying lowp + #endif +#else + #define TEX(x,y) texture(x,y) + #if __VERSION__ > 320 + #define M_IN in + #define L_IN in + #else + #define M_IN varying + #define L_IN varying + #endif +#endif + + +M_IN vec2 uv; +M_IN vec4 color; +/* #ifdef GLES #if __VERSION__ >290 in mediump vec2 uv; @@ -15,7 +40,7 @@ precision lowp samplerCube; #else varying vec2 uv; #endif -#endif +#endif*/ //layout(binding = 0)uniform sampler2D tex; @@ -23,20 +48,8 @@ uniform sampler2D tex; //layout(location = 0) out vec4 outColor; -void main() { - - #ifdef GLES - #if __VERSION__ >290 - gl_FragColor = texture(tex,uv); - #else - gl_FragColor = texture2D(tex,uv); - #endif - #else - #if __VERSION__ > 320 - gl_FragColor = texture(tex,uv); - #else - gl_FragColor = texture2D(tex,uv); - #endif - #endif +void main() +{ + gl_FragColor = TEX(tex,uv);// * color; if(gl_FragColor.a < 0.01)discard; } diff --git a/demos/assets/shaders/base.vp b/demos/assets/shaders/base.vp index 42ed4a7..d2d1af8 100644 --- a/demos/assets/shaders/base.vp +++ b/demos/assets/shaders/base.vp @@ -2,12 +2,37 @@ precision highp float; precision highp int; precision lowp sampler2D; precision lowp samplerCube; - #ifdef GLES #if __VERSION__ >290 - layout(location = 0) uniform vec4 matrix_1; - layout(location = 1) uniform vec4 matrix_2; - layout(location = 2) uniform vec4 uv_transform; + #define LOC(x) layout(location = x) + #define ATT in + #define M_OUT out mediump + #define L_OUT out lowp + #else + #define LOC(x) + #define ATT attribute + #define M_OUT varying mediump + #define L_OUT varying lowp + #endif +#else + #if __VERSION__ > 320 + #define LOC(x) layout(location = x) + #define ATT in + #define M_OUT out + #define L_OUT out + #else + #define LOC(x) + #define ATT attribute + #define M_OUT varying + #define L_OUT varying + #endif +#endif +/* +#ifdef GLES + #if __VERSION__ >290 + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; layout(location = 0) in vec2 positions; layout(location = 1) in vec2 tex_coords; @@ -43,13 +68,35 @@ precision lowp samplerCube; varying vec2 uv; #endif +#endif*/ + + + +M_OUT vec2 uv; +L_OUT vec4 color; + +LOC(0) ATT vec2 positions; +LOC(1) ATT vec2 tex_coords; + +#ifdef VBO_BATCH + LOC(2) ATT float depth; + LOC(3) ATT vec4 vcolor; +#else + uniform vec4 matrix_1; + uniform vec4 matrix_2; + uniform vec4 uv_transform; + uniform vec4 vcolor; + + float depth = matrix_2.z; #endif void main() { vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1) * vec3(positions,1.0); + position.z = depth; uv = tex_coords * uv_transform.zw + uv_transform.xy; - + color = vcolor; + gl_Position = vec4(position.xy,0,1.0); } diff --git a/demos/external/sources/mmutils/thread_pool.d b/demos/external/sources/mmutils/thread_pool.d index 161eb22..20491ef 100644 --- a/demos/external/sources/mmutils/thread_pool.d +++ b/demos/external/sources/mmutils/thread_pool.d @@ -9,7 +9,7 @@ import bubel.ecs.atomic; //import std.stdio; import std.algorithm : map; -version = MM_NO_LOGS; // Disable log creation +//version = MM_NO_LOGS; // Disable log creation //version = MM_USE_POSIX_THREADS; // Use posix threads insted of standard library, required for betterC version (Posix)version = MM_USE_POSIX_THREADS; @@ -1141,17 +1141,19 @@ public: foreach (ref log; logs) { - size += log.name.length; // size of name + size += log.name.length + 1; // size of name } char* buffer = cast(char*) malloc(size); foreach (ref log; logs) { - + char[100] name_buffer; + name_buffer[0 .. log.name.length] = log.name; + name_buffer[log.name.length] = 0; size_t charWritten = snprintf(buffer + used, size - used, `{"name":"%s", "pid":1, "tid":%lld, "ph":"X", "ts":%lld, "dur":%lld }, %s`, - log.name.ptr, threadData.threadId + 1, log.time, log.duration, "\n".ptr); + name_buffer.ptr, threadData.threadId + 1, log.time, log.duration, "\n".ptr); used += charWritten; } @@ -1447,7 +1449,21 @@ private void threadFunc(ThreadData* threadData) if (data is null) { // Thread does not have own job and can not steal it, so wait for a job - bool ok = threadData.semaphore.timedWait(1_000 + !acceptJobs * 10_000); + int tryWait = 0; + //bool ok = threadData.semaphore.timedWait(1_000 + !acceptJobs * 10_000); + bool ok = true; + while(!threadData.semaphore.tryWait()) + { + tryWait++; + if(tryWait>5000) + { + ok = false; + break; + } + static foreach(i;0..10)instructionPause(); + } + if(!ok)ok = threadData.semaphore.timedWait(1_000 + !acceptJobs * 10_000); + if (ok) { diff --git a/demos/source/app.d b/demos/source/app.d index acdc385..27c706d 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -253,7 +253,7 @@ void mainLoop(void* arg) if(launcher.tool && launcher.tool_repeat != 0 && launcher.mouse.left && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { float range = 500.0 / cast(float)launcher.tool_repeat; - launcher.repeat_time += launcher.delta_time; + launcher.repeat_time += launcher.delta_time*100; while(launcher.repeat_time > range) { launcher.repeat_time -= range; diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 89fe801..8d6b0f2 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -58,6 +58,7 @@ struct DrawSystem void onUpdate(EntitiesData data) { + if(launcher.renderer.item_id >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached foreach(i; 0..data.length) { launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), 0, 0 , 0); diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index c4123ba..2606fd9 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -30,8 +30,9 @@ struct MapElement empty = 0, apple = 1, wall = 2, + snake = 3, - snake_head_up = 5, + /* snake_head_up = 5, snake_head_down = 6, snake_head_left = 7, snake_head_right = 8, @@ -44,13 +45,31 @@ struct MapElement snake_turn_rd = 15, snake_turn_ru = 16, snake_vertical = 17, - snake_horizontal = 18 + snake_horizontal = 18*/ } Type type; EntityID id; } +enum SnakePart : ubyte +{ + head_up = 0, + head_down = 1, + head_left = 2, + head_right = 3, + tail_up = 4, + tail_down = 5, + tail_left = 6, + tail_right = 7, + turn_ld = 8, + turn_lu = 9, + turn_rd = 10, + turn_ru = 11, + vertical = 12, + horizontal = 13 +} + struct Snake { __gshared const (char)* tips = "Use \"WASD\" keys to move."; @@ -104,39 +123,6 @@ struct Snake *location = random_pos; Entity* apple = launcher.manager.addEntity(apple_tmpl); } - - void drawMap() - { - foreach(x; 0 .. map_size) - { - foreach(y; 0 .. map_size) - { - switch(element(ivec2(x,y)).type) - { - case MapElement.Type.apple:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, 0 , 0);break; - - case MapElement.Type.snake_head_up:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(48*px,112*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_head_down:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(48*px,144*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_head_left:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,128*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_head_right:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(32*px,128*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_tail_up:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(16*px,112*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_tail_down:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,112*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_tail_left:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(32*px,112*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_tail_right:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,144*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_turn_ld:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(64*px,128*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_turn_lu:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(32*px,144*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_turn_rd:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(16*px,144*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_turn_ru:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(64*px,112*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_vertical:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(16*px,128*px,16*px,16*px), 0, 0 , 0);break; - case MapElement.Type.snake_horizontal:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(48*px,128*px,16*px,16*px), 0, 0 , 0);break; - - case MapElement.Type.wall:launcher.renderer.draw(texture, vec2(x*16,y*16), vec2(16,16), vec4(0,0,1,1), 0, 0 , 0);break; - default:break; - } - } - } - } - } struct Animation @@ -184,6 +170,7 @@ struct CSnake mixin ECS.Component; + struct Parts { uint length = 0; @@ -217,6 +204,7 @@ struct CSnake } Parts parts; + CMovement.Direction direction; } struct CApple @@ -424,81 +412,19 @@ struct MoveSystem else .snake.element(MapElement(),location); } - static CMovement.Direction getDirection(ivec2 p1, ivec2 p2) - { - if(p1.x - p2.x == -1)return CMovement.direction.right; - else if(p1.x - p2.x == 1)return CMovement.direction.left; - else if(p1.y - p2.y == -1)return CMovement.direction.up; - else if(p1.y - p2.y == 1)return CMovement.direction.down; - else if(p1.x - p2.x > 1)return CMovement.direction.right; - else if(p1.x - p2.x < -1)return CMovement.direction.left; - else if(p1.y - p2.y > 1)return CMovement.direction.up; - else return CMovement.direction.down; - } - - static MapElement.Type snakePart(ivec2 p1, ivec2 p2, ivec2 p3) - { - CMovement.Direction direction = getDirection(p1, p2); - CMovement.Direction direction2 = getDirection(p1, p3); - uint case_ = direction*4 + direction2; - final switch(case_) - { - case 0:return MapElement.Type.snake_horizontal; - case 1:return MapElement.Type.snake_horizontal; - case 2:return MapElement.Type.snake_turn_lu; - case 3:return MapElement.Type.snake_turn_ru; - case 4:return MapElement.Type.snake_horizontal; - case 5:return MapElement.Type.snake_horizontal; - case 6:return MapElement.Type.snake_turn_ld; - case 7:return MapElement.Type.snake_turn_rd; - case 8:return MapElement.Type.snake_turn_lu; - case 9:return MapElement.Type.snake_turn_ld; - case 10:return MapElement.Type.snake_vertical; - case 11:return MapElement.Type.snake_vertical; - case 12:return MapElement.Type.snake_turn_ru; - case 13:return MapElement.Type.snake_turn_rd; - case 14:return MapElement.Type.snake_vertical; - case 15:return MapElement.Type.snake_vertical; - } - } - - static MapElement.Type snakeTail(ivec2 p1, ivec2 p2) - { - CMovement.Direction direction = getDirection(p1, p2); - final switch(direction) - { - case CMovement.Direction.up:return MapElement.Type.snake_tail_up; - case CMovement.Direction.down:return MapElement.Type.snake_tail_down; - case CMovement.Direction.left:return MapElement.Type.snake_tail_left; - case CMovement.Direction.right:return MapElement.Type.snake_tail_right; - } - } - void onUpdate(EntitiesData data) { if(data.snakes) { foreach(i; 0..data.length) { + data.snakes[i].direction = data.movement[i].direction; ivec2 new_location = data.location[i]; moveLocation(data.location[i], data.movement[i].direction); final switch(snake.element(data.location[i].location).type) { - case MapElement.Type.snake_head_up:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_head_down:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_head_left:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_head_right:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_tail_up:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_tail_down:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_tail_left:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_tail_right:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_turn_ld:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_turn_lu:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_turn_rd:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_turn_ru:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_vertical:goto case(MapElement.Type.snake_horizontal); - case MapElement.Type.snake_horizontal: - foreach(ivec2 loc; data.snakes[i].parts) + case MapElement.Type.snake: + foreach(loc; data.snakes[i].parts) { destroy_location.x = loc.x * 16; destroy_location.y = loc.y * 16; @@ -520,37 +446,20 @@ struct MoveSystem launcher.manager.addEntity(snake.snake_destroy_particle); launcher.manager.removeEntity(data.entities[i].id); break; + case MapElement.Type.wall:break; - //launcher.manager.removeEntity(data.entities[i].id); - //break; + case MapElement.Type.empty: moveSnake(data.snakes[i], new_location); - final switch(data.movement[i].direction) - { - case CMovement.Direction.up: - snake.element(MapElement(MapElement.Type.snake_head_up, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.right: - snake.element(MapElement(MapElement.Type.snake_head_right, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.down: - snake.element(MapElement(MapElement.Type.snake_head_down, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.left: - snake.element(MapElement(MapElement.Type.snake_head_left, data.entities[i].id),data.location[i].location); - break; - } + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.location[i].location); if(data.snakes[i].parts.length > 1) { - MapElement.Type elem_type = snakePart(data.snakes[i].parts[$-1],data.location[i],data.snakes[i].parts[$-2]); - snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[$-1]); - elem_type = snakeTail(data.snakes[i].parts[1], data.snakes[i].parts[0]); - snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[0]); + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.snakes[i].parts[$-1]); + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.snakes[i].parts[0]); } else if(data.snakes[i].parts.length == 1) { - MapElement.Type elem_type = snakeTail(data.location[i], data.snakes[i].parts[0]); - snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[0]); + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.snakes[i].parts[0]); } break; case MapElement.Type.apple: @@ -559,29 +468,13 @@ struct MoveSystem if(data.snakes[i].parts.length > 1) { - MapElement.Type elem_type = snakePart(data.snakes[i].parts[$-1],data.location[i],data.snakes[i].parts[$-2]); - snake.element(MapElement(elem_type, data.entities[i].id),data.snakes[i].parts[$-1]); + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.snakes[i].parts[$-1]); } else if(data.snakes[i].parts.length == 1) { - MapElement.Type elem_type = snakeTail(data.location[i], new_location); - snake.element(MapElement(elem_type, data.entities[i].id),new_location); - } - final switch(data.movement[i].direction) - { - case CMovement.Direction.up: - snake.element(MapElement(MapElement.Type.snake_head_up, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.right: - snake.element(MapElement(MapElement.Type.snake_head_right, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.down: - snake.element(MapElement(MapElement.Type.snake_head_down, data.entities[i].id),data.location[i].location); - break; - case CMovement.Direction.left: - snake.element(MapElement(MapElement.Type.snake_head_left, data.entities[i].id),data.location[i].location); - break; + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),new_location); } + snake.element(MapElement(MapElement.Type.snake, data.entities[i].id),data.location[i].location); snake.addApple(); break; } @@ -593,10 +486,10 @@ struct MoveSystem { final switch(data.movement[i].direction) { - case CMovement.Direction.down:data.location[i].location.y -= 1;break; - case CMovement.Direction.up:data.location[i].location.y += 1;break; - case CMovement.Direction.left:data.location[i].location.x -= 1;break; - case CMovement.Direction.right:data.location[i].location.x += 1;break; + case CMovement.Direction.down:data.location[i].y -= 1;break; + case CMovement.Direction.up:data.location[i].y += 1;break; + case CMovement.Direction.left:data.location[i].x -= 1;break; + case CMovement.Direction.right:data.location[i].x += 1;break; } } } @@ -699,6 +592,132 @@ struct FixSnakeDirectionSystem } } +struct DrawAppleSystem +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + @readonly CILocation[] location; + const (CApple)[] apple; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.location.length) + { + launcher.renderer.draw(snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, 0 , 0); + } + } +} + +struct DrawSnakeSystem +{ + mixin ECS.System!1; + + struct EntitiesData + { + uint length; + @readonly CILocation[] location; + const (CSnake)[] snake; + } + + static CMovement.Direction getDirection(ivec2 p1, ivec2 p2) + { + if(p1.x - p2.x == -1)return CMovement.direction.right; + else if(p1.x - p2.x == 1)return CMovement.direction.left; + else if(p1.y - p2.y == -1)return CMovement.direction.up; + else if(p1.y - p2.y == 1)return CMovement.direction.down; + else if(p1.x - p2.x > 1)return CMovement.direction.right; + else if(p1.x - p2.x < -1)return CMovement.direction.left; + else if(p1.y - p2.y > 1)return CMovement.direction.up; + else return CMovement.direction.down; + } + + static SnakePart snakePart(ivec2 p1, ivec2 p2, ivec2 p3) + { + CMovement.Direction direction = getDirection(p1, p2); + CMovement.Direction direction2 = getDirection(p1, p3); + uint case_ = direction*4 + direction2; + final switch(case_) + { + case 0:return SnakePart.horizontal; + case 1:return SnakePart.horizontal; + case 2:return SnakePart.turn_lu; + case 3:return SnakePart.turn_ru; + case 4:return SnakePart.horizontal; + case 5:return SnakePart.horizontal; + case 6:return SnakePart.turn_ld; + case 7:return SnakePart.turn_rd; + case 8:return SnakePart.turn_lu; + case 9:return SnakePart.turn_ld; + case 10:return SnakePart.vertical; + case 11:return SnakePart.vertical; + case 12:return SnakePart.turn_ru; + case 13:return SnakePart.turn_rd; + case 14:return SnakePart.vertical; + case 15:return SnakePart.vertical; + } + } + + static SnakePart snakeTail(ivec2 p1, ivec2 p2) + { + CMovement.Direction direction = getDirection(p1, p2); + final switch(direction) + { + case CMovement.Direction.up:return SnakePart.tail_up; + case CMovement.Direction.down:return SnakePart.tail_down; + case CMovement.Direction.left:return SnakePart.tail_left; + case CMovement.Direction.right:return SnakePart.tail_right; + } + } + + static void drawElement(ivec2 loc, SnakePart part) + { + final switch(cast(ubyte)part) + { + case SnakePart.tail_up:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,112,16,16)*px, 0, 0, 0);break; + case SnakePart.tail_down:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,112,16,16)*px, 0, 0, 0);break; + case SnakePart.tail_left:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,112,16,16)*px, 0, 0, 0);break; + case SnakePart.tail_right:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,144,16,16)*px, 0, 0, 0);break; + case SnakePart.turn_ld:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,128,16,16)*px, 0, 0, 0);break; + case SnakePart.turn_lu:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,144,16,16)*px, 0, 0, 0);break; + case SnakePart.turn_rd:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,144,16,16)*px, 0, 0, 0);break; + case SnakePart.turn_ru:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,112,16,16)*px, 0, 0, 0);break; + case SnakePart.vertical:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,128,16,16)*px, 0, 0, 0);break; + case SnakePart.horizontal:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(48,128,16,16)*px, 0, 0, 0);break; + } + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + const (CSnake)* snake = &data.snake[i]; + scope vec2 loc = cast(vec2)(data.location[i].location * 16); + final switch(snake.direction) + { + case CMovement.Direction.up:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,112,16,16)*px, 0, 0 , 0);break; + case CMovement.Direction.down:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,144,16,16)*px, 0, 0 , 0);break; + case CMovement.Direction.left:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,128,16,16)*px, 0, 0 , 0);break; + case CMovement.Direction.right:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(32,128,16,16)*px, 0, 0 , 0);break; + } + if(snake.parts.length >1) + { + foreach(j;1..snake.parts.length - 1)drawElement(snake.parts[j]*16, snakePart(snake.parts[j], snake.parts[j+1], snake.parts[j-1])); + drawElement(snake.parts[$-1]*16, snakePart(snake.parts[$-1], data.location[i], snake.parts[$-2])); + drawElement(snake.parts[0]*16, snakeTail(snake.parts[1], snake.parts[0])); + } + else if(snake.parts.length == 1) + { + drawElement(snake.parts[0]*16, snakeTail(data.location[i], snake.parts[0])); + } + + } + } +} + struct CleanSystem { mixin ECS.System!64; @@ -749,6 +768,8 @@ void snakeStart() launcher.manager.registerSystem!AnimationSystem(-1); launcher.manager.registerSystem!ParticleSystem(-1); launcher.manager.registerSystem!ParticleMovementSystem(-1); + launcher.manager.registerSystem!DrawAppleSystem(99); + launcher.manager.registerSystem!DrawSnakeSystem(101); launcher.manager.endRegister(); @@ -766,7 +787,7 @@ void snakeStart() ushort[4] components = [CILocation.component_id, CSnake.component_id, CMovement.component_id, CInput.component_id]; snake.snake_tmpl = launcher.manager.allocateTemplate(components); CILocation* loc_comp = snake.snake_tmpl.getComponent!CILocation; - loc_comp.location = ivec2(2,2); + *loc_comp = ivec2(2,2); launcher.manager.addEntity(snake.snake_tmpl); } @@ -794,7 +815,7 @@ void snakeStart() /*foreach(i; 0..10) foreach(j; 0..10) { - loc_comp.location = vec2(i*32+64,j*32+64); + loc_compation = vec2(i*32+64,j*32+64); launcher.manager.addEntity(simple.tmpl); }*/ } @@ -878,7 +899,7 @@ bool snakeLoop() launcher.manager.end(); - snake.drawMap(); + //snake.drawMap(); return true; } \ No newline at end of file diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 92c4e15..6d86113 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -179,6 +179,13 @@ struct CSideMove byte group = -1; } +struct CDepth +{ + mixin ECS.Component; + + short depth; +} + /*####################################################################################################################### ------------------------------------------------ Events ------------------------------------------------------------------ #######################################################################################################################*/ @@ -620,7 +627,7 @@ struct InputMovementSystem return true; } //don't call system update because no key pressed - return true; + return false; } /** @@ -637,7 +644,7 @@ struct InputMovementSystem { data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); } - return; + //return; } //move every entity using movement vector foreach(i; 0..data.length) diff --git a/demos/source/game_core/job_updater.d b/demos/source/game_core/job_updater.d index 6c20a83..47d2423 100644 --- a/demos/source/game_core/job_updater.d +++ b/demos/source/game_core/job_updater.d @@ -63,6 +63,7 @@ struct ECSJobUpdater JobData[1024] jobs; JobCaller[1024] callers; uint count = 0; + string name; void dependantOn(Group* dependency) { @@ -89,7 +90,7 @@ struct ECSJobUpdater void add(JobCaller caller) { callers[count] = caller; - jobs[count] = JobData(&callers[count].callJob,"hmm"); + jobs[count] = JobData(&callers[count].callJob,name); count++; } } @@ -216,6 +217,8 @@ struct ECSJobUpdater return; } + jobs[group.id].name = cast(string)group.caller.system.name; + foreach(ref job;group.jobs) { uint index = 0; @@ -233,7 +236,7 @@ struct ECSJobUpdater foreach(dep;group.dependencies) { - if(jobs[dep.id].count && dep.caller.system.execute && dep.caller.system.enabled)jobs[group.id].dependantOn(&jobs[dep.id]); + if(jobs[dep.id].count && dep.caller.system.willExecute && dep.caller.system.enabled)jobs[group.id].dependantOn(&jobs[dep.id]); else deps--; } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index d56b5c8..8735d98 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -9,6 +9,9 @@ import ecs_utils.gfx.buffer; import ecs_utils.gfx.texture; import ecs_utils.math.vector; +import bubel.ecs.block_allocator; +import bubel.ecs.vector; + import glad.gl.gl; version = ver1; @@ -41,9 +44,10 @@ enum RenderTechnique struct Renderer { //static SDL_Renderer* main_sdl_renderer; + BlockAllocator allocator; enum MaxObjects = 1024 * 64 * 4; - enum BufferUsage = GL_STATIC_DRAW; + enum BufferUsage = GL_DYNAMIC_DRAW; //SDL_Window* sdl_window; //SDL_Renderer* sdl_renderer; @@ -53,8 +57,35 @@ struct Renderer vec2 view_pos = vec2(-1,-1); vec2 view_size = vec2(1,1); + const uint batch_size = 16_384; //uint[2] time_queries; + struct VertexBlock + { + float[] batch_vertices; + ushort[] batch_indices; + void* memory; + uint itmes = 0; + } + + VertexBlock getBlock() + { + VertexBlock block; + block.memory = allocator.getBlock(); + block.batch_vertices = (cast(float*)block.memory)[0 .. 1]; + return block; + } + + + struct Thread + { + + Vector!VertexBlock block; + RenderData[] render_list; + } + Thread[] threads; + + Buffer[2] ubos; int block_alignment = 1; int block_max_size = 16384; @@ -253,6 +284,8 @@ struct Renderer SDL_Log("Uniform block alignment: %u",block_alignment); SDL_Log("Uniform block max size: %u",block_max_size); SDL_Log("Data offset: %u",data_offset); + + allocator = BlockAllocator(1245184, 32); } } @@ -261,12 +294,13 @@ struct Renderer } - void draw(Texture tex, vec2 pos, vec2 size, vec4 coords, float angle = 0, uint material_id = 0, uint mesh_id = 0) + void draw(Texture tex, vec2 pos, vec2 size, vec4 coords, short depth = 0, uint color = uint.max, float angle = 0, uint material_id = 0, uint mesh_id = 0, uint thread_id = 0) { - __draw(this,tex,pos,size,coords,angle,material_id,mesh_id); + if(item_id >= MaxObjects)return; + __draw(this,tex,pos,size,coords,depth,color,angle,material_id,mesh_id,thread_id); } - private static void __draw_sdl(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, float angle, uint material_id, uint mesh_id) + private static void __draw_sdl(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id) { /*with(this_) { @@ -286,7 +320,7 @@ struct Renderer }*/ } - private static void __draw_gl(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, float angle, uint material_id, uint mesh_id) + private static void __draw_gl(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id) { //import core.stdc.string; with(this_) @@ -333,13 +367,13 @@ struct Renderer } } - private static void __draw_gl_vbo_batch(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, float angle, uint material_id, uint mesh_id) + private static void __draw_gl_vbo_batch(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id = 0) { import ecs_utils.gfx.config; //import core.stdc.string; with(this_) { - if(item_id >= MaxObjects)return; + //if(item_id >= MaxObjects)return; //pos += view_pos; size.x *= view_size.x; size.y *= view_size.y; @@ -400,7 +434,7 @@ struct Renderer batch_vertices[item_id*16+14] = GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x; batch_vertices[item_id*16+15] = GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y; - uint ind_id = item_id % 16_384; + uint ind_id = item_id % batch_size; batch_indices[item_id*6] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[0] + ind_id*4); batch_indices[item_id*6+1] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[1] + ind_id*4); @@ -575,7 +609,7 @@ struct Renderer //glBeginQuery(GL_TIME_ELAPSED, time_queries[0]); if(technique == Technique.vbo_batch) { - uint items = item_id/16_384+1; + uint items = item_id/batch_size+1; foreach(i; 0..items) { if(material_id != render_list[i].material_id) @@ -591,16 +625,16 @@ struct Renderer render_list[i].texture.bind(); } - uint instance_count = 16_384; - if((i+1)*16_384 > item_id) + uint instance_count = batch_size; + if((i+1)*batch_size > item_id) { - instance_count = item_id%16_384; + instance_count = item_id%batch_size; } - glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*16_384*4*16)); - glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)(i*16_384*4*16+8)); + glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16)); + glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16+8)); - glDrawElements(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2)); + glDrawElements(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*batch_size*6*2)); //glDrawElementsBaseVertex(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2),i*16_384*4); } @@ -803,7 +837,7 @@ struct Renderer view_pos = (pos - size * 0.5) * view_size; } - __gshared void function(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, float angle, uint material_id, uint mesh_id) __draw; + __gshared void function(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id) __draw; __gshared void function(ref Renderer this_) __present; __gshared void function(ref Renderer this_) __clear; __gshared void function(ref Renderer this_) __initialize; diff --git a/demos/utils/source/ecs_utils/math/vector.d b/demos/utils/source/ecs_utils/math/vector.d index 4b9f3af..a74b6db 100644 --- a/demos/utils/source/ecs_utils/math/vector.d +++ b/demos/utils/source/ecs_utils/math/vector.d @@ -97,6 +97,15 @@ struct ivec2 int[2] data; } + ivec2 opBinary(string op, T)(T v) + { + static if (op == "+") return ivec2(x + v, y + v); + else static if (op == "-") return ivec2(x - v, y - v); + else static if (op == "*") return ivec2(x * v, y * v); + else static if (op == "/") return ivec2(x / v, y / v); + else static assert(0, "Operator "~op~" not implemented"); + } + vec2 opCast() { return vec2(x,y); diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index be4f37a..1b896db 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -1253,7 +1253,7 @@ export struct EntityManager foreach (caller; passes[pass].system_callers) { System* sys = &systems[caller.system_id]; - if (sys.enabled && sys.execute) + if (sys.enabled && sys.willExecute) { if (sys.m_empty) { @@ -1293,7 +1293,7 @@ export struct EntityManager foreach (caller; passes[pass].system_callers) { System* sys = &systems[caller.system_id]; - if (sys.enabled && sys.execute) + if (sys.enabled && sys.willExecute) { uint job_id = 0; void nextJob() diff --git a/tests/runner.d b/tests/runner.d index 7b8f6b5..4aa9bcb 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -9,6 +9,8 @@ import bubel.ecs.vector; import bubel.ecs.simple_vector; import bubel.ecs.std; +import tests.time; + enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; @@ -95,6 +97,8 @@ struct TestRunner(Args...) write(test.name); write("\" classname=\""); write(suite.name); + write("\" time=\""); + write(cast(double)test.time*0.000001); write("\">"); if (test.msg) { @@ -189,8 +193,10 @@ struct TestRunner(Args...) } else { + long time = Time.getUSecTime(); unittest_(); test.passed = true; + test.time = cast(int)(Time.getUSecTime() - time); } } else @@ -281,6 +287,14 @@ struct TestRunner(Args...) junit.add(cast(ubyte[]) txt); } + void write(double num) + { + ubyte[40] buffer; + int len; + len = sprintf(cast(char*) buffer.ptr, "%2.8lf", num); + junit.add(buffer[0 .. len]); + } + void write(uint num) { ubyte[20] buffer; diff --git a/tests/time.d b/tests/time.d new file mode 100644 index 0000000..e91b76e --- /dev/null +++ b/tests/time.d @@ -0,0 +1,66 @@ +module tests.time; + + +version (WebAssembly) +{ + alias int time_t; + alias int clockid_t; + enum CLOCK_REALTIME = 0; + + struct timespec + { + time_t tv_sec; + int tv_nsec; + } + + extern (C) int clock_gettime(clockid_t, timespec*) @nogc nothrow @system; + + struct Time + { + static long getUSecTime() + { + time_t time; + timespec spec; + + clock_gettime(CLOCK_REALTIME, &spec); + + //time = spec.tv_sec; + return spec.tv_sec * 1000_000 + spec.tv_nsec / 1000; //time / 1000_000; + } + } +} +else version (Windows) +{ + import core.stdc.stdio : printf; + import core.sys.windows.windows; + + struct Time + { + static long getUSecTime() + { + LARGE_INTEGER time, freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&time); + return time.QuadPart / (freq.QuadPart / 1000_000); + } + } +} +else version (Posix) +{ + import core.stdc.stdio : printf; + import core.sys.posix.time; + + struct Time + { + static long getUSecTime() + { + time_t time; + timespec spec; + + clock_gettime(CLOCK_REALTIME, &spec); + + //time = spec.tv_sec; + return spec.tv_sec * 1000_000 + spec.tv_nsec / 1000; //time / 1000_000; + } + } +} \ No newline at end of file -- 2.47.2 From f6e7af10148d7fb47ac7b58f44f6551c6b46640b Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 7 May 2020 21:58:34 +0200 Subject: [PATCH 09/37] Some preformance tests and added EntityMeta structure --- source/bubel/ecs/entity.d | 22 +++++ tests/access_perf.d | 128 +++++++++++++++++++++++++++ tests/perf.d | 178 ++++++++++++++++++++++++++++++++++++++ tests/runner.d | 77 ++++++++++++----- 4 files changed, 385 insertions(+), 20 deletions(-) create mode 100644 tests/access_perf.d create mode 100644 tests/perf.d diff --git a/source/bubel/ecs/entity.d b/source/bubel/ecs/entity.d index 441f412..10720a2 100644 --- a/source/bubel/ecs/entity.d +++ b/source/bubel/ecs/entity.d @@ -45,6 +45,28 @@ struct Entity uint ind = cast(uint)((cast(void*)&this - block.dataBegin()) / EntityID.sizeof()); return cast(T*)(cast(void*)block + info.deltas[T.component_id] + ind * T.sizeof); } + + EntityMeta getMeta() + { + EntityMeta meta; + meta.block = gEM.getMetaData(&this); + static if (EntityID.sizeof == 8) + meta.index = cast(ushort)((cast(void*)&this - meta.block.dataBegin()) >> 3); + else + meta.index = cast(ushort)((cast(void*)&this - meta.block.dataBegin()) / EntityID.sizeof()); + return meta; + } +} + +struct EntityMeta +{ + EntityManager.EntitiesBlock* block; + ushort index; + + T* getComponent(T)() const + { + return cast(T*)(cast(void*)block + block.type_info.deltas[T.component_id] + index * T.sizeof); + } } /************************************************************************************************************************ diff --git a/tests/access_perf.d b/tests/access_perf.d new file mode 100644 index 0000000..141a2e6 --- /dev/null +++ b/tests/access_perf.d @@ -0,0 +1,128 @@ +module tests.access_perf; + +import tests.runner; + +import bubel.ecs.core; +import bubel.ecs.manager; +import bubel.ecs.entity; + +import std.array : staticArray; + +import core.stdc.stdio; + +struct CLong +{ + mixin ECS.Component; + + alias value this; + + long value = 10; +} + +struct CInt +{ + mixin ECS.Component; + + alias value this; + + int value = 10; +} + +struct CUInt +{ + mixin ECS.Component; + + alias value this; + + uint value = 12; +} + +struct CBig +{ + mixin ECS.Component; + uint[32] data; +} + +EntityTemplate* tmpl; + +void beforeEveryTest() +{ + gEM.initialize(0); + + gEM.beginRegister(); + + gEM.registerComponent!CLong; + gEM.registerComponent!CInt; + gEM.registerComponent!CUInt; + gEM.registerComponent!CBig; + + gEM.endRegister(); + + tmpl = gEM.allocateTemplate([CLong.component_id, CInt.component_id, CUInt.component_id, CBig.component_id].staticArray); + foreach(i; 0 .. 100_000)gEM.addEntity(tmpl); +} + +void afterEveryTest() +{ + if(tmpl)gEM.freeTemplate(tmpl); + tmpl = null; + gEM.destroy(); +} + +@("DirectAccess100k1comp") +unittest +{ + foreach(i;0..25000) + { + Entity* entity = gEM.getEntity(EntityID(i*4+1,0)); + CUInt* comp1 = entity.getComponent!CUInt; + comp1.value = 4; + } +} + +@("DirectAccess100k4comp") +unittest +{ + foreach(i;0..25000) + { + Entity* entity = gEM.getEntity(EntityID(i*4+1,0)); + CUInt* comp1 = entity.getComponent!CUInt; + comp1.value = 4; + CInt* comp2 = entity.getComponent!CInt; + comp2.value = 3; + CLong* comp3 = entity.getComponent!CLong; + comp3.value = 1; + CBig* comp4 = entity.getComponent!CBig; + comp4.data[0] = 2; + } +} + +@("DirectAccess100k1compWithMeta") +unittest +{ + foreach(i;0..25000) + { + Entity* entity = gEM.getEntity(EntityID(i*4+1,0)); + EntityMeta meta = entity.getMeta(); + CUInt* comp1 = meta.getComponent!CUInt; + comp1.value = 4; + } +} + +@("DirectAccess100k4compWithMeta") +unittest +{ + foreach(i;0..25000) + { + Entity* entity = gEM.getEntity(EntityID(i*4+1,0)); + EntityMeta meta = entity.getMeta(); + CUInt* comp1 = meta.getComponent!CUInt; + comp1.value = 4; + CInt* comp2 = meta.getComponent!CInt; + comp2.value = 3; + CLong* comp3 = meta.getComponent!CLong; + comp3.value = 1; + CBig* comp4 = meta.getComponent!CBig; + comp4.data[0] = 2; + } +} \ No newline at end of file diff --git a/tests/perf.d b/tests/perf.d new file mode 100644 index 0000000..135dc54 --- /dev/null +++ b/tests/perf.d @@ -0,0 +1,178 @@ +module tests.perf; + +import tests.runner; + +import bubel.ecs.core; +import bubel.ecs.manager; +import bubel.ecs.entity; + +import std.array : staticArray; + +import core.stdc.stdio; + +struct CLong +{ + mixin ECS.Component; + + alias value this; + + long value = 10; +} + +struct CShort +{ + mixin ECS.Component; + + alias value this; + + short value = 12; +} + +struct CInt +{ + mixin ECS.Component; + + alias value this; + + int value = 10; +} + +struct CUInt +{ + mixin ECS.Component; + + alias value this; + + uint value = 12; +} + +struct CBig +{ + mixin ECS.Component; + uint[32] data; +} + +EntityTemplate* tmpl; + +void beforeEveryTest() +{ + gEM.initialize(0); + + gEM.beginRegister(); + + gEM.registerComponent!CLong; + gEM.registerComponent!CShort; + gEM.registerComponent!CInt; + gEM.registerComponent!CUInt; + gEM.registerComponent!CBig; + + gEM.endRegister(); + tmpl = null; +} + +void afterEveryTest() +{ + if(tmpl)gEM.freeTemplate(tmpl); + tmpl = null; + gEM.destroy(); +} + +void smallTmpl() +{ + tmpl = gEM.allocateTemplate([CShort.component_id].staticArray); +} + +void bigTmpl() +{ + tmpl = gEM.allocateTemplate([CBig.component_id].staticArray); +} + +void multiSmallTmpl() +{ + tmpl = gEM.allocateTemplate([CShort.component_id, CLong.component_id, CInt.component_id, CUInt.component_id].staticArray); +} + +void multiBigTmpl() +{ + tmpl = gEM.allocateTemplate([CLong.component_id, CInt.component_id, CUInt.component_id, CBig.component_id].staticArray); +} + +@("AddEntities100k1comp2b") @(before, &smallTmpl) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k1comp128b") @(before, &bigTmpl) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k4comp18b") @(before, &multiSmallTmpl) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k4comp144b") @(before, &multiBigTmpl) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +void allocDealloc100k() +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); + gEM.commit(); + foreach(i; 0..100_000)gEM.removeEntity(EntityID(i + 1,0)); + gEM.commit(); +} + +void smallTmplPreAlloc() +{ + tmpl = gEM.allocateTemplate([CShort.component_id].staticArray); + allocDealloc100k(); +} + +void bigTmplPreAlloc() +{ + tmpl = gEM.allocateTemplate([CBig.component_id].staticArray); + allocDealloc100k(); +} + +void multiSmallTmplPreAlloc() +{ + tmpl = gEM.allocateTemplate([CShort.component_id, CLong.component_id, CInt.component_id, CUInt.component_id].staticArray); + allocDealloc100k(); +} + +void multiBigTmplPreAlloc() +{ + tmpl = gEM.allocateTemplate([CLong.component_id, CInt.component_id, CUInt.component_id, CBig.component_id].staticArray); + allocDealloc100k(); +} + +@("AddEntities100k1comp2bPreAlloc") @(before, &smallTmplPreAlloc) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k1comp128bPreAlloc") @(before, &bigTmplPreAlloc) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k4comp18bPreAlloc") @(before, &multiSmallTmplPreAlloc) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} + +@("AddEntities100k4comp144bPreAlloc") @(before, &multiBigTmplPreAlloc) +unittest +{ + foreach(i; 0..100_000)gEM.addEntity(tmpl); +} \ No newline at end of file diff --git a/tests/runner.d b/tests/runner.d index 4aa9bcb..f299664 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -9,8 +9,19 @@ import bubel.ecs.vector; import bubel.ecs.simple_vector; import bubel.ecs.std; +import std.traits; + import tests.time; +version (LDC) +{ + import ldc.attributes; +} +else +{ + enum optStrategy = 0; +} + enum int ASSERTED = 123; enum string OUT_FILE = "test_report.xml"; @@ -124,42 +135,65 @@ struct TestRunner(Args...) write(""); } + @(optStrategy,"none") void runTests(string[] include = null, string[] exclude = null) { foreach (i, module_; Args) { TestSuite* suite = &suites[i]; suite.name = module_.stringof; + + void function() before; + void function() after; + foreach (index, unittest_; __traits(getUnitTests, module_)) { enum attributes = __traits(getAttributes, unittest_); + static if (attributes.length != 0) { - if (include.length > 0) + foreach(attr_id, attr; attributes) { - bool matched = false; - foreach (str; include) + static if(isFunctionPointer!(attr)){} + else static if(attr == "_tr_before") { - if (match(str, attributes[0])) - { - matched = true; - break; - } + static assert(attr_id+1 < attributes.length); + enum attr2 = attributes[attr_id + 1]; + //static assert(__traits(hasMember, module_, attr2)); + //alias func = __traits(getMember, module_, attr2); + //attr2(); + before = attr2; + //static assert(is(typeof(__traits(getMember, module_, attr2)) == void function())); } - - foreach (str; exclude) + else { - if (match(str, attributes[0])) + if (include.length > 0) { - matched = false; - break; - } - } + bool matched = false; + foreach (str; include) + { + if (match(str, attr)) + { + matched = true; + break; + } + } - if (!matched) - { - suite.skipped++; - continue; + foreach (str; exclude) + { + if (match(str, attr)) + { + matched = false; + break; + } + } + + if (!matched) + { + suite.skipped++; + continue; + } + } } } } @@ -178,6 +212,7 @@ struct TestRunner(Args...) static if (__traits(hasMember, module_, "beforeEveryTest")) module_.beforeEveryTest(); + if(before)before(); version(D_BetterC) { @@ -318,6 +353,8 @@ version (notBetterC) version (D_Coverage) extern (C) void dmd_coverDestPath(string path); } +enum before = "_tr_before"; + void extractStrings(ref Vector!string container, string str) { uint s = 0; @@ -383,7 +420,7 @@ extern (C) int main(int argc, char** args) } } - TestRunner!(tests.runner, tests.id_manager, tests.vector, tests.basic) runner; + TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf) runner; runner.runTests(include[], exclude[]); -- 2.47.2 From c94510a487f3c85ce216b28a88602158aa4f3381 Mon Sep 17 00:00:00 2001 From: Mergul Date: Sat, 9 May 2020 19:41:00 +0200 Subject: [PATCH 10/37] Improved Demo and multithreading rendering: -added support for multithreaded rendering (fast) -improved shaders -added support for rendering depth -added rendering color support -improved DeptThreadPool (dynamics setting number of tryWait counts before TryWait. Low cpu usage with high responivity) -added possibility to change number of threads --- demos/assets/shaders/base.fp | 2 +- demos/assets/shaders/base.vp | 14 +- demos/external/sources/mmutils/thread_pool.d | 6 +- demos/source/app.d | 23 +- demos/source/demos/simple.d | 8 +- demos/source/demos/snake.d | 30 +- demos/source/demos/space_invaders.d | 24 +- demos/utils/source/ecs_utils/gfx/renderer.d | 309 ++++++++++++++----- 8 files changed, 311 insertions(+), 105 deletions(-) diff --git a/demos/assets/shaders/base.fp b/demos/assets/shaders/base.fp index ca38c30..d86e92e 100644 --- a/demos/assets/shaders/base.fp +++ b/demos/assets/shaders/base.fp @@ -50,6 +50,6 @@ uniform sampler2D tex; void main() { - gl_FragColor = TEX(tex,uv);// * color; + gl_FragColor = TEX(tex,uv) * color; if(gl_FragColor.a < 0.01)discard; } diff --git a/demos/assets/shaders/base.vp b/demos/assets/shaders/base.vp index d2d1af8..7be086b 100644 --- a/demos/assets/shaders/base.vp +++ b/demos/assets/shaders/base.vp @@ -70,7 +70,7 @@ precision lowp samplerCube; #endif #endif*/ - +#define VBO_BATCH 1 M_OUT vec2 uv; L_OUT vec4 color; @@ -91,12 +91,16 @@ LOC(1) ATT vec2 tex_coords; #endif void main() { + #ifdef VBO_BATCH + vec3 position = vec3(positions*4,1.0); + uv = tex_coords; + #else + vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1) * vec3(positions,1.0); + uv = tex_coords * uv_transform.zw + uv_transform.xy; + #endif - vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1) * vec3(positions,1.0); - position.z = depth; - uv = tex_coords * uv_transform.zw + uv_transform.xy; color = vcolor; - gl_Position = vec4(position.xy,0,1.0); + gl_Position = vec4(position.xy,depth,1.0); } diff --git a/demos/external/sources/mmutils/thread_pool.d b/demos/external/sources/mmutils/thread_pool.d index 20491ef..143960c 100644 --- a/demos/external/sources/mmutils/thread_pool.d +++ b/demos/external/sources/mmutils/thread_pool.d @@ -9,13 +9,14 @@ import bubel.ecs.atomic; //import std.stdio; import std.algorithm : map; -//version = MM_NO_LOGS; // Disable log creation +version = MM_NO_LOGS; // Disable log creation //version = MM_USE_POSIX_THREADS; // Use posix threads insted of standard library, required for betterC version (Posix)version = MM_USE_POSIX_THREADS; version (WebAssembly) { + version = MM_NO_LOGS; extern(C) struct FILE { @@ -799,6 +800,7 @@ struct ThreadPool alias FlushLogsDelegaste = void delegate(ThreadData* threadData, JobLog[] logs); /// Type of delegate to flush logs FlushLogsDelegaste onFlushLogs; /// User custom delegate to flush logs, if overriden defaultFlushLogs will be used. Can be sset after initialize() call int logsCacheNum; /// Number of log cache entries. Should be set before setThreadsNum is called + int tryWaitCount = 2000; ///Number of times which tryWait are called before timedWait call. Higher value sets better response but takes CPU time even if there are no jobs. private: ThreadData*[gMaxThreadsNum] threadsData; /// Data for threads align(64) shared int threadsNum; /// Number of threads currentlu accepting jobs @@ -1455,7 +1457,7 @@ private void threadFunc(ThreadData* threadData) while(!threadData.semaphore.tryWait()) { tryWait++; - if(tryWait>5000) + if(tryWait>threadPool.tryWaitCount) { ok = false; break; diff --git a/demos/source/app.d b/demos/source/app.d index 27c706d..af4b331 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -58,6 +58,7 @@ struct Launcher uint style = 3; uint entities_count; bool multithreading; + int threads; ulong timer_freq; double delta_time; uint fps; @@ -253,13 +254,12 @@ void mainLoop(void* arg) if(launcher.tool && launcher.tool_repeat != 0 && launcher.mouse.left && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { float range = 500.0 / cast(float)launcher.tool_repeat; - launcher.repeat_time += launcher.delta_time*100; + launcher.repeat_time += launcher.delta_time; while(launcher.repeat_time > range) { launcher.repeat_time -= range; launcher.tool((launcher.mouse.position*launcher.scalling)-launcher.render_position, launcher.used_tool, launcher.tool_size); } - } version(WebAssembly) @@ -317,6 +317,14 @@ void mainLoop(void* arg) { launcher.multithreading = !launcher.multithreading; } + igSetNextItemWidth(0); + igLabelText("Threads:",null); + igSameLine(0,4); + if(igSliderInt("##Threads",&launcher.threads, 1, 12, null))//"Multithreading", null, launcher.multithreading, true)) + { + launcher.job_updater.pool.setThreadsNum(launcher.threads); + //launcher.threads = !launcher.multithreading; + } if(igBeginMenu("Show",true)) { if(igMenuItemBool("Statistics",null,launcher.show_stat_wnd,true)) @@ -539,11 +547,14 @@ void mainLoop(void* arg) launcher.renderer.clear(); double loop_time = launcher.getTime(); + launcher.job_updater.pool.tryWaitCount = 5000; if(launcher.loop && !launcher.loop()) { quit(); *cast(bool*)arg = false; } + launcher.job_updater.pool.tryWaitCount = 10; + loop_time = launcher.getTime() - loop_time; double draw_time = launcher.getTime(); @@ -785,7 +796,15 @@ void loadGFX() GfxConfig.materials[0].compile(); GfxConfig.materials[0].bindAttribLocation("positions",0); GfxConfig.materials[0].bindAttribLocation("tex_coords",1); + GfxConfig.materials[0].bindAttribLocation("depth",2); + GfxConfig.materials[0].bindAttribLocation("vcolor",3); GfxConfig.materials[0].link(); + + /* import std.stdio; + writeln("positions ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"positions")); + writeln("tex_coords ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"tex_coords")); + writeln("depth ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"depth")); + writeln("vcolor ",glGetAttribLocation(GfxConfig.materials[0].data.modules[0].gl_handle,"vcolor"));*/ GfxConfig.materials[0].data.uniforms = Mallocator.makeArray!(Material.Uniform)(3); GfxConfig.materials[0].data.uniforms[0] = Material.Uniform(Material.Type.float4, GfxConfig.materials[0].getLocation("matrix_1"), 0); diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 8d6b0f2..686f9be 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -47,23 +47,25 @@ struct CTexture struct DrawSystem { - mixin ECS.System!1; + mixin ECS.System!32; struct EntitiesData { uint length; + uint thread_id; @readonly CTexture[] textures; @readonly CLocation[] locations; } void onUpdate(EntitiesData data) { - if(launcher.renderer.item_id >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached + if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), 0, 0 , 0); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y*64+data.locations[i].x), uint.max, 0, 0, 0, data.thread_id); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } + if(data.thread_id == 0)launcher.renderer.pushData(); } } diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 2606fd9..94f2654 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -607,7 +607,7 @@ struct DrawAppleSystem { foreach(i; 0..data.location.length) { - launcher.renderer.draw(snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, 0 , 0); + launcher.renderer.draw(snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, uint.max, 0); } } } @@ -677,16 +677,16 @@ struct DrawSnakeSystem { final switch(cast(ubyte)part) { - case SnakePart.tail_up:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,112,16,16)*px, 0, 0, 0);break; - case SnakePart.tail_down:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,112,16,16)*px, 0, 0, 0);break; - case SnakePart.tail_left:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,112,16,16)*px, 0, 0, 0);break; - case SnakePart.tail_right:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,144,16,16)*px, 0, 0, 0);break; - case SnakePart.turn_ld:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,128,16,16)*px, 0, 0, 0);break; - case SnakePart.turn_lu:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,144,16,16)*px, 0, 0, 0);break; - case SnakePart.turn_rd:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,144,16,16)*px, 0, 0, 0);break; - case SnakePart.turn_ru:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,112,16,16)*px, 0, 0, 0);break; - case SnakePart.vertical:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,128,16,16)*px, 0, 0, 0);break; - case SnakePart.horizontal:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(48,128,16,16)*px, 0, 0, 0);break; + case SnakePart.tail_up:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,112,16,16)*px, 0, uint.max, 0);break; + case SnakePart.tail_down:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,112,16,16)*px, 0, uint.max, 0);break; + case SnakePart.tail_left:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,112,16,16)*px, 0, uint.max, 0);break; + case SnakePart.tail_right:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,144,16,16)*px, 0, uint.max, 0);break; + case SnakePart.turn_ld:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,128,16,16)*px, 0, uint.max, 0);break; + case SnakePart.turn_lu:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,144,16,16)*px, 0, uint.max, 0);break; + case SnakePart.turn_rd:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,144,16,16)*px, 0, uint.max, 0);break; + case SnakePart.turn_ru:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,112,16,16)*px, 0, uint.max, 0);break; + case SnakePart.vertical:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,128,16,16)*px, 0, uint.max, 0);break; + case SnakePart.horizontal:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(48,128,16,16)*px, 0, uint.max, 0);break; } } @@ -698,10 +698,10 @@ struct DrawSnakeSystem scope vec2 loc = cast(vec2)(data.location[i].location * 16); final switch(snake.direction) { - case CMovement.Direction.up:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,112,16,16)*px, 0, 0 , 0);break; - case CMovement.Direction.down:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,144,16,16)*px, 0, 0 , 0);break; - case CMovement.Direction.left:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,128,16,16)*px, 0, 0 , 0);break; - case CMovement.Direction.right:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(32,128,16,16)*px, 0, 0 , 0);break; + case CMovement.Direction.up:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,112,16,16)*px, 0, uint.max, 0);break; + case CMovement.Direction.down:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,144,16,16)*px, 0, uint.max, 0);break; + case CMovement.Direction.left:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,128,16,16)*px, 0, uint.max, 0);break; + case CMovement.Direction.right:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(32,128,16,16)*px, 0, uint.max, 0);break; } if(snake.parts.length >1) { diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 6d86113..30891c8 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -183,6 +183,8 @@ struct CDepth { mixin ECS.Component; + alias depth this; + short depth; } @@ -208,23 +210,33 @@ struct EChangeDirection struct DrawSystem { - mixin ECS.System!1; + mixin ECS.System!32; struct EntitiesData { uint length; + uint thread_id; @readonly CTexture[] textures; @readonly CLocation[] locations; @readonly CScale[] scale; + @readonly @optional CDepth[] depth; } void onUpdate(EntitiesData data) { - foreach(i; 0..data.length) - { - 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)); - } + if(!data.depth) + foreach(i; 0..data.length) + { + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, uint.max, 0, 0, 0, data.thread_id); + //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); + } + else + foreach(i; 0..data.length) + { + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), uint.max, 0, 0, 0, data.thread_id); + //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); + } + if(data.thread_id == 0)launcher.renderer.pushData(); } } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 8735d98..ba42cf6 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -11,8 +11,8 @@ import ecs_utils.math.vector; import bubel.ecs.block_allocator; import bubel.ecs.vector; - -import glad.gl.gl; +version(WebAssembly)import glad.gl.gles2; +else import glad.gl.gl; version = ver1; /*version(ver5)version = vv2; @@ -57,31 +57,105 @@ struct Renderer vec2 view_pos = vec2(-1,-1); vec2 view_size = vec2(1,1); - const uint batch_size = 16_384; + enum block_size = 2^^16; + enum batch_size = block_size/68;//963;//16_384; //uint[2] time_queries; struct VertexBlock { - float[] batch_vertices; + enum max_items = batch_size;//963; + byte[] batch_vertices; ushort[] batch_indices; void* memory; - uint itmes = 0; + uint items = 0; } + Mutex* get_block_mutex; + Mutex* block_stack_mutex; + VertexBlock getBlock() { VertexBlock block; + get_block_mutex.lock(); block.memory = allocator.getBlock(); - block.batch_vertices = (cast(float*)block.memory)[0 .. 1]; + get_block_mutex.unlock(); + block.batch_vertices = (cast(byte*)block.memory)[0 .. VertexBlock.max_items * 4 * 14]; + block.batch_indices = (cast(ushort*)block.memory)[VertexBlock.max_items * 4 * 7 .. VertexBlock.max_items * (4 * 7 + 6)]; return block; } + Vector!VertexBlock blocks; + uint current_block = 0; + uint render_blocks = 0; + + void pushBlock(VertexBlock block) + { + block_stack_mutex.lock(); + prepared_items += block.items; + blocks.add(block); + render_blocks++; + block_stack_mutex.unlock(); + } + + bool isRemainingBlocks() + { + if(render_blocks <= current_block)return false; + return true; + } + + VertexBlock fetchBlock() + { + block_stack_mutex.lock(); + VertexBlock block = blocks[current_block]; + current_block++; + block_stack_mutex.unlock(); + return block; + } + + void freeBlocks() + { + block_stack_mutex.lock(); + render_blocks = 0; + current_block = 0; + foreach(VertexBlock block; blocks) + { + allocator.freeBlock(block.memory); + } + blocks.clear; + prepared_items=0; + draw_list.clear(); + block_stack_mutex.unlock(); + } + + void pushData() + { + //if(!isRemainingBlocks())return; + while(isRemainingBlocks()) + { + VertexBlock block = fetchBlock(); + uint items = block.items; + if(items + item_id >= MaxObjects)items = MaxObjects - item_id; + batch_vbo[0].bufferSubData(Buffer.BindTarget.array,items*4*14,item_id*4*14,block.batch_vertices.ptr); + batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,items*2*6,item_id*2*6,block.batch_indices.ptr); + draw_list.add(DrawCall(item_id,items)); + item_id += items; + } + } + + void pushThreadsBlocks() + { + foreach(i, ref Thread thread; threads) + { + pushBlock(thread.block); + thread.block = getBlock(); + } + } struct Thread { - - Vector!VertexBlock block; + //Vector!VertexBlock block; RenderData[] render_list; + VertexBlock block; } Thread[] threads; @@ -102,7 +176,7 @@ struct Renderer Buffer[2] batch_vbo; Buffer[2] batch_ibo; - float[] batch_vertices; + ubyte[] batch_vertices; ushort[] batch_indices; Buffer indirect_buffer; @@ -121,8 +195,17 @@ struct Renderer uint mesh_id; } + struct DrawCall + { + uint start; + uint count; + } + + Vector!DrawCall draw_list; + RenderData[] render_list; uint item_id; + uint prepared_items; uint[] multi_count; uint[] multi_offset; @@ -140,6 +223,18 @@ struct Renderer { //this.technique = __ecs_used_technique; __initialize(this); + + get_block_mutex = Mallocator.make!Mutex(); + block_stack_mutex = Mallocator.make!Mutex(); + get_block_mutex.initialize(); + block_stack_mutex.initialize(); + + + threads = Mallocator.makeArray!Thread(12); + foreach(ref Thread thread;threads) + { + thread.block = getBlock(); + } } private static void __initialize_gl(ref Renderer this_) @@ -172,16 +267,16 @@ struct Renderer case Technique.vbo_batch: batch_vbo[0].create(); batch_ibo[0].create(); - batch_vbo[0].bufferData(Buffer.BindTarget.array,16,4*MaxObjects,BufferUsage,null); + batch_vbo[0].bufferData(Buffer.BindTarget.array,14,4*MaxObjects,BufferUsage,null); batch_ibo[0].bufferData(Buffer.BindTarget.element_array,2,6*MaxObjects,BufferUsage,null); batch_vbo[1].create(); batch_ibo[1].create(); - batch_vbo[1].bufferData(Buffer.BindTarget.array,16,4*MaxObjects,BufferUsage,null); + batch_vbo[1].bufferData(Buffer.BindTarget.array,14,4*MaxObjects,BufferUsage,null); batch_ibo[1].bufferData(Buffer.BindTarget.element_array,2,6*MaxObjects,BufferUsage,null); - batch_vertices = Mallocator.makeArray!float(16*MaxObjects); - batch_indices = Mallocator.makeArray!ushort(6*MaxObjects); + //batch_vertices = Mallocator.makeArray!ubyte(14*4*MaxObjects); + //batch_indices = Mallocator.makeArray!ushort(6*MaxObjects); break; case Technique.instanced_attrib_divisor: goto case(Technique.uniform_buffer_indexed); @@ -285,7 +380,7 @@ struct Renderer SDL_Log("Uniform block max size: %u",block_max_size); SDL_Log("Data offset: %u",data_offset); - allocator = BlockAllocator(1245184, 32); + allocator = BlockAllocator(block_size, 32); } } @@ -296,7 +391,7 @@ struct Renderer void draw(Texture tex, vec2 pos, vec2 size, vec4 coords, short depth = 0, uint color = uint.max, float angle = 0, uint material_id = 0, uint mesh_id = 0, uint thread_id = 0) { - if(item_id >= MaxObjects)return; + if(prepared_items >= MaxObjects)return; __draw(this,tex,pos,size,coords,depth,color,angle,material_id,mesh_id,thread_id); } @@ -364,12 +459,14 @@ struct Renderer data_index += data_offset; item_id++; + prepared_items++; } } private static void __draw_gl_vbo_batch(ref Renderer this_, Texture tex, vec2 pos, vec2 size, vec4 coords, short depth, uint color, float angle, uint material_id, uint mesh_id, uint thread_id = 0) { import ecs_utils.gfx.config; + short[3] mem = [depth, *cast(short*)&color, *(cast(short*)&color + 1)]; //import core.stdc.string; with(this_) { @@ -389,16 +486,37 @@ struct Renderer memcpy(ptr+16,pos.data.ptr,8); memcpy(ptr+32,coords.data.ptr,16);*/ + short[] verts = cast(short[])threads[thread_id].block.batch_vertices; + uint item_id = threads[thread_id].block.items; + if(angle == 0) { - batch_vertices[item_id*16] = GfxConfig.meshes[mesh_id].vertices[0] * size.x + pos.x; - batch_vertices[item_id*16+1] = GfxConfig.meshes[mesh_id].vertices[1] * size.y + pos.y; - batch_vertices[item_id*16+4] = GfxConfig.meshes[mesh_id].vertices[4] * size.x + pos.x; - batch_vertices[item_id*16+5] = GfxConfig.meshes[mesh_id].vertices[5] * size.y + pos.y; - batch_vertices[item_id*16+8] = GfxConfig.meshes[mesh_id].vertices[8] * size.x + pos.x; - batch_vertices[item_id*16+9] = GfxConfig.meshes[mesh_id].vertices[9] * size.y + pos.y; - batch_vertices[item_id*16+12] = GfxConfig.meshes[mesh_id].vertices[12] * size.x + pos.x; - batch_vertices[item_id*16+13] = GfxConfig.meshes[mesh_id].vertices[13] * size.y + pos.y; + verts[item_id*28] = cast(short)((GfxConfig.meshes[mesh_id].vertices[0] * size.x + pos.x) * 8191); + verts[item_id*28+1] = cast(short)((GfxConfig.meshes[mesh_id].vertices[1] * size.y + pos.y) * 8191); + verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); + verts[item_id*28+3] = cast(short)((GfxConfig.meshes[mesh_id].vertices[3] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+4,mem.ptr,6); + + + verts[item_id*28+7] = cast(short)((GfxConfig.meshes[mesh_id].vertices[4] * size.x + pos.x) * 8191); + verts[item_id*28+8] = cast(short)((GfxConfig.meshes[mesh_id].vertices[5] * size.y + pos.y) * 8191); + verts[item_id*28+9] = cast(short)((GfxConfig.meshes[mesh_id].vertices[6] * coords.z + coords.x)*32767); + verts[item_id*28+10] = cast(short)((GfxConfig.meshes[mesh_id].vertices[7] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+11,mem.ptr,6); + + + verts[item_id*28+14] = cast(short)((GfxConfig.meshes[mesh_id].vertices[8] * size.x + pos.x) * 8191); + verts[item_id*28+15] = cast(short)((GfxConfig.meshes[mesh_id].vertices[9] * size.y + pos.y) * 8191); + verts[item_id*28+16] = cast(short)((GfxConfig.meshes[mesh_id].vertices[10] * coords.z + coords.x)*32767); + verts[item_id*28+17] = cast(short)((GfxConfig.meshes[mesh_id].vertices[11] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+18,mem.ptr,6); + + + verts[item_id*28+21] = cast(short)((GfxConfig.meshes[mesh_id].vertices[12] * size.x + pos.x) * 8191); + verts[item_id*28+22] = cast(short)((GfxConfig.meshes[mesh_id].vertices[13] * size.y + pos.y) * 8191); + verts[item_id*28+23] = cast(short)((GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x)*32767); + verts[item_id*28+24] = cast(short)((GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+25,mem.ptr,6); } else { @@ -406,50 +524,72 @@ struct Renderer float sinn = sinf(angle); float coss = cosf(angle); - /*batch_vertices[item_id*16] = GfxConfig.meshes[mesh_id].vertices[0] * size.x; - batch_vertices[item_id*16+1] = GfxConfig.meshes[mesh_id].vertices[1] * size.y; - batch_vertices[item_id*16+4] = GfxConfig.meshes[mesh_id].vertices[4] * size.x; - batch_vertices[item_id*16+5] = GfxConfig.meshes[mesh_id].vertices[5] * size.y; - batch_vertices[item_id*16+8] = GfxConfig.meshes[mesh_id].vertices[8] * size.x; - batch_vertices[item_id*16+9] = GfxConfig.meshes[mesh_id].vertices[9] * size.y; - batch_vertices[item_id*16+12] = GfxConfig.meshes[mesh_id].vertices[12] * size.x; - batch_vertices[item_id*16+13] = GfxConfig.meshes[mesh_id].vertices[13] * size.y;*/ + /*batch_vertices[item_id*28] = GfxConfig.meshes[mesh_id].vertices[0] * size.x; + batch_vertices[item_id*28+1] = GfxConfig.meshes[mesh_id].vertices[1] * size.y; + batch_vertices[item_id*28+4] = GfxConfig.meshes[mesh_id].vertices[4] * size.x; + batch_vertices[item_id*28+5] = GfxConfig.meshes[mesh_id].vertices[5] * size.y; + batch_vertices[item_id*28+8] = GfxConfig.meshes[mesh_id].vertices[8] * size.x; + batch_vertices[item_id*28+9] = GfxConfig.meshes[mesh_id].vertices[9] * size.y; + batch_vertices[item_id*28+12] = GfxConfig.meshes[mesh_id].vertices[12] * size.x; + batch_vertices[item_id*28+13] = GfxConfig.meshes[mesh_id].vertices[13] * size.y;*/ - batch_vertices[item_id*16] = (GfxConfig.meshes[mesh_id].vertices[0] * coss + GfxConfig.meshes[mesh_id].vertices[1] * sinn) * size.x + pos.x; - batch_vertices[item_id*16+1] = (GfxConfig.meshes[mesh_id].vertices[1] * coss - GfxConfig.meshes[mesh_id].vertices[0] * sinn) * size.y + pos.y; - batch_vertices[item_id*16+4] = (GfxConfig.meshes[mesh_id].vertices[4] * coss + GfxConfig.meshes[mesh_id].vertices[5] * sinn) * size.x + pos.x; - batch_vertices[item_id*16+5] = (GfxConfig.meshes[mesh_id].vertices[5] * coss - GfxConfig.meshes[mesh_id].vertices[4] * sinn) * size.y + pos.y; - batch_vertices[item_id*16+8] = (GfxConfig.meshes[mesh_id].vertices[8] * coss + GfxConfig.meshes[mesh_id].vertices[9] * sinn) * size.x + pos.x; - batch_vertices[item_id*16+9] = (GfxConfig.meshes[mesh_id].vertices[9] * coss - GfxConfig.meshes[mesh_id].vertices[8] * sinn) * size.y + pos.y; - batch_vertices[item_id*16+12] = (GfxConfig.meshes[mesh_id].vertices[12] * coss + GfxConfig.meshes[mesh_id].vertices[13] * sinn) * size.x + pos.x; - batch_vertices[item_id*16+13] = (GfxConfig.meshes[mesh_id].vertices[13] * coss - GfxConfig.meshes[mesh_id].vertices[12] * sinn) * size.y + pos.y; + verts[item_id*28] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[0] * coss + GfxConfig.meshes[mesh_id].vertices[1] * sinn) * size.x + pos.x) * 8191); + verts[item_id*28+1] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[1] * coss - GfxConfig.meshes[mesh_id].vertices[0] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+7] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[4] * coss + GfxConfig.meshes[mesh_id].vertices[5] * sinn) * size.x + pos.x) * 8191); + verts[item_id*28+8] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[5] * coss - GfxConfig.meshes[mesh_id].vertices[4] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+14] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[8] * coss + GfxConfig.meshes[mesh_id].vertices[9] * sinn) * size.x + pos.x) * 8191); + verts[item_id*28+15] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[9] * coss - GfxConfig.meshes[mesh_id].vertices[8] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+21] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[12] * coss + GfxConfig.meshes[mesh_id].vertices[13] * sinn) * size.x + pos.x) * 8191); + verts[item_id*28+22] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[13] * coss - GfxConfig.meshes[mesh_id].vertices[12] * sinn) * size.y + pos.y) * 8191); } - batch_vertices[item_id*16+2] = GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x; - batch_vertices[item_id*16+3] = GfxConfig.meshes[mesh_id].vertices[3] * coords.w + coords.y; - batch_vertices[item_id*16+6] = GfxConfig.meshes[mesh_id].vertices[6] * coords.z + coords.x; - batch_vertices[item_id*16+7] = GfxConfig.meshes[mesh_id].vertices[7] * coords.w + coords.y; - batch_vertices[item_id*16+10] = GfxConfig.meshes[mesh_id].vertices[10] * coords.z + coords.x; - batch_vertices[item_id*16+11] = GfxConfig.meshes[mesh_id].vertices[11] * coords.w + coords.y; - batch_vertices[item_id*16+14] = GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x; - batch_vertices[item_id*16+15] = GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y; + /*verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); + verts[item_id*28+3] = cast(short)((GfxConfig.meshes[mesh_id].vertices[3] * coords.w + coords.y)*32767); + verts[item_id*28+9] = cast(short)((GfxConfig.meshes[mesh_id].vertices[6] * coords.z + coords.x)*32767); + verts[item_id*28+10] = cast(short)((GfxConfig.meshes[mesh_id].vertices[7] * coords.w + coords.y)*32767); + verts[item_id*28+16] = cast(short)((GfxConfig.meshes[mesh_id].vertices[10] * coords.z + coords.x)*32767); + verts[item_id*28+17] = cast(short)((GfxConfig.meshes[mesh_id].vertices[11] * coords.w + coords.y)*32767); + verts[item_id*28+23] = cast(short)((GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x)*32767); + verts[item_id*28+24] = cast(short)((GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y)*32767);*/ - uint ind_id = item_id % batch_size; + /*verts[item_id*28+4] = depth; + verts[item_id*28+11] = depth; + verts[item_id*28+18] = depth; + verts[item_id*28+25] = depth; - batch_indices[item_id*6] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[0] + ind_id*4); - batch_indices[item_id*6+1] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[1] + ind_id*4); - batch_indices[item_id*6+2] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[2] + ind_id*4); - batch_indices[item_id*6+3] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[3] + ind_id*4); - batch_indices[item_id*6+4] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[4] + ind_id*4); - batch_indices[item_id*6+5] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[5] + ind_id*4); + *cast(uint*)&verts[item_id*28+5] = color; + *cast(uint*)&verts[item_id*28+12] = color; + *cast(uint*)&verts[item_id*28+19] = color; + *cast(uint*)&verts[item_id*28+26] = color; + + memcpy(verts.ptr+item_id*28+4,mem.ptr,6); + memcpy(verts.ptr+item_id*28+11,mem.ptr,6); + memcpy(verts.ptr+item_id*28+18,mem.ptr,6); + memcpy(verts.ptr+item_id*28+25,mem.ptr,6);*/ + + uint ind_id = (item_id % batch_size)*4; + + ushort[] indices = threads[thread_id].block.batch_indices; + + indices[item_id*6] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[0] + ind_id); + indices[item_id*6+1] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[1] + ind_id); + indices[item_id*6+2] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[2] + ind_id); + indices[item_id*6+3] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[3] + ind_id); + indices[item_id*6+4] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[4] + ind_id); + indices[item_id*6+5] = cast(ushort)(GfxConfig.meshes[mesh_id].indices[5] + ind_id); //render_list[item_id] = RenderData(tex,material_id,mesh_id); - render_list[item_id].texture = tex; - render_list[item_id].material_id = material_id; - render_list[item_id].mesh_id = mesh_id; + //render_list[item_id].texture = tex; + //render_list[item_id].material_id = material_id; + //render_list[item_id].mesh_id = mesh_id; //data_index += 1;//data_offset; - item_id++; + threads[thread_id].block.items++; + if(threads[thread_id].block.items >= VertexBlock.max_items) + { + pushBlock(threads[thread_id].block); + threads[thread_id].block = getBlock(); + } } } @@ -467,9 +607,22 @@ struct Renderer { glClearColor(0,0,0,0); glViewport(0,0,this_.resolution.x,this_.resolution.y); - glClear(GL_COLOR_BUFFER_BIT);// | GL_DEPTH_BUFFER_BIT); - glDisable(GL_DEPTH_TEST); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + //glDisable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); + glDepthFunc(GL_LESS); + + version(WebAssembly) + { + glDepthRangef(0,1); + } + else + { + glDepthRange(0,1); + } + //glDepthRange(0,1); + //glClearDepth(1); } void present() @@ -484,6 +637,10 @@ struct Renderer private static void __present_gl(ref Renderer this_) { + + this_.pushThreadsBlocks(); + this_.pushData(); + glViewport(0,0,this_.resolution.x,this_.resolution.y); //glEnable(GL_ALPHA_TEST); //glAlphaFunc(GL_GREATER, 0.01); @@ -505,14 +662,17 @@ struct Renderer break; case Technique.vbo_batch: //if(data_index){ - batch_vbo[0].bufferSubData(Buffer.BindTarget.array,item_id*4*16,0,batch_vertices.ptr); - batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,item_id*6*2,0,batch_indices.ptr); + //batch_vbo[0].bufferSubData(Buffer.BindTarget.array,item_id*4*14,0,batch_vertices.ptr); + //batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,item_id*6*2,0,batch_indices.ptr); batch_vbo[0].bind(Buffer.BindTarget.array); batch_ibo[0].bind(Buffer.BindTarget.element_array); - glVertexAttribPointer(0,2,GL_FLOAT,false,16,null); - glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)8);//} + //glVertexAttribPointer(0,2,GL_SHORT,true,14,null); + //glVertexAttribPointer(1,2,GL_SHORT,true,14,cast(void*)4);//} + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + //glVertexAttribPointer(2,1,GL_SHORT,true,14,cast(void*)6);//} break; case Technique.instanced_attrib_divisor: ubos[0].bufferSubData(Buffer.BindTarget.uniform,data_index,0,uniform_block.ptr); @@ -609,8 +769,8 @@ struct Renderer //glBeginQuery(GL_TIME_ELAPSED, time_queries[0]); if(technique == Technique.vbo_batch) { - uint items = item_id/batch_size+1; - foreach(i; 0..items) + //uint items = item_id/batch_size+1; + foreach(i; 0..draw_list.length) { if(material_id != render_list[i].material_id) { @@ -625,16 +785,20 @@ struct Renderer render_list[i].texture.bind(); } - uint instance_count = batch_size; + /*uint instance_count = batch_size; if((i+1)*batch_size > item_id) { instance_count = item_id%batch_size; - } + }*/ - glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16)); - glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16+8)); + // glVertexAttribPointer(0,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16)); + // glVertexAttribPointer(1,2,GL_FLOAT,false,16,cast(void*)(i*batch_size*4*16+8)); + glVertexAttribPointer(0,2,GL_SHORT,true,14,cast(void*)(draw_list[i].start*4*14)); + glVertexAttribPointer(1,2,GL_SHORT,true,14,cast(void*)(draw_list[i].start*4*14+4)); + glVertexAttribPointer(2,1,GL_SHORT,true,14,cast(void*)(draw_list[i].start*4*14+8)); + glVertexAttribPointer(3,4,GL_UNSIGNED_BYTE,true,14,cast(void*)(draw_list[i].start*4*14+10)); - glDrawElements(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*batch_size*6*2)); + glDrawElements(GL_TRIANGLES,draw_list[i].count*6,GL_UNSIGNED_SHORT,cast(void*)(draw_list[i].start*6*2)); //glDrawElementsBaseVertex(GL_TRIANGLES,instance_count*6,GL_UNSIGNED_SHORT,cast(void*)(i*16_384*6*2),i*16_384*4); } @@ -817,6 +981,9 @@ struct Renderer } glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + this_.freeBlocks(); /*glUseProgram(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);*/ -- 2.47.2 From b19fbb15288555ea94db39ea4e9504e933a8011f Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 12 May 2020 12:30:21 +0200 Subject: [PATCH 11/37] Fixed shader compilation on WebAssembly --- demos/assets/shaders/base.vp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/assets/shaders/base.vp b/demos/assets/shaders/base.vp index 7be086b..2777a24 100644 --- a/demos/assets/shaders/base.vp +++ b/demos/assets/shaders/base.vp @@ -92,10 +92,10 @@ LOC(1) ATT vec2 tex_coords; void main() { #ifdef VBO_BATCH - vec3 position = vec3(positions*4,1.0); + vec3 position = vec3(positions*4.0,1.0); uv = tex_coords; #else - vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1) * vec3(positions,1.0); + vec3 position = mat3(matrix_1.x,matrix_1.y,0,matrix_1.z,matrix_1.w,0,matrix_2.xy,1.0) * vec3(positions,1.0); uv = tex_coords * uv_transform.zw + uv_transform.xy; #endif -- 2.47.2 From 5e884352ba05c0bcbe2bf1db8429f5e9c11d247d Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 12 May 2020 17:28:31 +0200 Subject: [PATCH 12/37] Add support for external depencencies --- source/bubel/ecs/core.d | 16 +++ source/bubel/ecs/manager.d | 143 ++++++++++++++++++- source/bubel/ecs/system.d | 3 + source/bubel/ecs/vector.d | 8 +- tests/basic.d | 275 +++++++++++++++++++++++++++++++++++++ 5 files changed, 440 insertions(+), 5 deletions(-) diff --git a/source/bubel/ecs/core.d b/source/bubel/ecs/core.d index ccdf3e2..0487665 100644 --- a/source/bubel/ecs/core.d +++ b/source/bubel/ecs/core.d @@ -92,4 +92,20 @@ static struct ECS { alias ExcludedComponents = T; } + + /************************************************************************************************************************ + Make list of readonly ependencies. This template get strings as arguments. Should be added inside System structure. + */ + mixin template ReadOnlyDependencies(T...) + { + alias ReadOnlyDependencies = T; + } + + /************************************************************************************************************************ + Make list of writable ependencies. This template get strings as arguments. Should be added inside System structure. + */ + mixin template WritableDependencies(T...) + { + alias WritableDependencies = T; + } } \ No newline at end of file diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index 1b896db..c1eb62a 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -452,6 +452,8 @@ export struct EntityManager uint excluded = 1; uint optional = 1; uint req = 1; + uint readonly_dep = 1; + uint writable_dep = 1; } static ComponentsCounts getComponentsCounts()() @@ -532,7 +534,22 @@ export struct EntityManager { components_counts.excluded++; } + } + } + static if (__traits(hasMember, Sys, "ReadOnlyDependencies")) + { + foreach (str; Sys.ReadOnlyDependencies) + { + components_counts.readonly_dep++; + } + } + + static if (__traits(hasMember, Sys, "WritableDependencies")) + { + foreach (str; Sys.WritableDependencies) + { + components_counts.writable_dep++; } } @@ -568,6 +585,16 @@ export struct EntityManager return m_req[0 .. m_req_counter]; } + CompInfo[] readonlyDeps() + { + return m_readonly_dep[0 .. m_readonly_dep_counter]; + } + + CompInfo[] writableDeps() + { + return m_writable_dep[0 .. m_writable_dep_counter]; + } + void addReadonly(CompInfo info) { m_readonly[m_readonly_counter++] = info; @@ -593,17 +620,31 @@ export struct EntityManager m_req[m_req_counter++] = info; } + void addReadonlyDep(CompInfo info) + { + m_readonly_dep[m_readonly_dep_counter++] = info; + } + + void addWritableDep(CompInfo info) + { + m_writable_dep[m_writable_dep_counter++] = info; + } + CompInfo[counts.readonly] m_readonly; CompInfo[counts.mutable] m_mutable; CompInfo[counts.excluded] m_excluded; CompInfo[counts.optional] m_optional; CompInfo[counts.req] m_req; + CompInfo[counts.readonly_dep] m_readonly_dep; + CompInfo[counts.writable_dep] m_writable_dep; uint m_readonly_counter; uint m_mutable_counter; uint m_excluded_counter; uint m_optional_counter; uint m_req_counter; + uint m_readonly_dep_counter; + uint m_writable_dep_counter; string entites_array; } @@ -616,6 +657,8 @@ export struct EntityManager size_t excluded = components_info.excluded.length; size_t read_only = components_info.readonly.length; size_t modified = components_info.mutable.length; + size_t read_only_deps = components_info.readonlyDeps.length; + size_t writable_deps = components_info.writableDeps.length; if (req > 0) system.m_components = Mallocator.makeArray!ushort(req); @@ -627,6 +670,10 @@ export struct EntityManager system.m_read_only_components = Mallocator.makeArray!ushort(read_only); if (modified > 0) system.m_modified_components = Mallocator.makeArray!ushort(modified); + if (read_only_deps > 0) + system.m_readonly_dependencies = Mallocator.makeArray!ushort(read_only_deps); + if (writable_deps > 0) + system.m_writable_dependencies = Mallocator.makeArray!ushort(writable_deps); } @@ -714,6 +761,22 @@ export struct EntityManager } } + static if (__traits(hasMember, Sys, "ReadOnlyDependencies")) + { + foreach (str; Sys.ReadOnlyDependencies) + { + components_info.addReadonlyDep(CompInfo(str, str)); + } + } + + static if (__traits(hasMember, Sys, "WritableDependencies")) + { + foreach (str; Sys.WritableDependencies) + { + components_info.addWritableDep(CompInfo(str, str)); + } + } + return components_info; } @@ -809,7 +872,6 @@ export struct EntityManager ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); system.m_modified_components[iii] = comp; } - } static void fillInputData(ref Sys.EntitiesData input_data, EntityInfo* info, @@ -1059,6 +1121,32 @@ export struct EntityManager genCompList(system, components_map); + foreach (iii, comp_info; components_info.readonlyDeps) + { + ushort comp = external_dependencies_map.get(cast(const (char)[]) comp_info.type, ushort.max); + version (D_BetterC) + assert(comp != ushort.max, + "Can't register system \"" ~ Sys.stringof + ~ "\" due to non existing dependency."); + else + assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof + ~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\"."); + system.m_readonly_dependencies[iii] = comp; + } + + foreach (iii, comp_info; components_info.writableDeps) + { + ushort comp = external_dependencies_map.get(cast(char[]) comp_info.type, ushort.max); + version (D_BetterC) + assert(comp != ushort.max, + "Can't register system \"" ~ Sys.stringof + ~ "\" due to non existing dependency."); + else + assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof + ~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\"."); + system.m_writable_dependencies[iii] = comp; + } + ushort sys_id = systems_map.get(cast(char[]) Sys.stringof, ushort.max); if (sys_id < systems.length) { @@ -1118,6 +1206,11 @@ export struct EntityManager return cast(ushort)(passes.length - 1); } + export void registerDependency(const(char)[] name) + { + return external_dependencies_map.add(name, cast(ushort)external_dependencies_map.length); + } + /************************************************************************************************************************ Register component into EntityManager. */ @@ -2820,10 +2913,45 @@ export struct EntityManager foreach (caller; pass.system_callers) { index = 0; + ///gets systems which are excluding each other out_for: foreach (caller2; pass.system_callers) { if (caller is caller2) continue; + + ///check for external dependencies + foreach (cmp; caller.system.m_readonly_dependencies) + { + foreach (cmp2; caller2.system.m_writable_dependencies) + { + if (cmp == cmp2) + { + exclusion[index++] = caller2; + continue out_for; + } + } + } + foreach (cmp; caller.system.m_writable_dependencies) + { + foreach (cmp2; caller2.system.m_readonly_dependencies) + { + if (cmp == cmp2) + { + exclusion[index++] = caller2; + continue out_for; + } + } + foreach (cmp2; caller2.system.m_writable_dependencies) + { + if (cmp == cmp2) + { + exclusion[index++] = caller2; + continue out_for; + } + } + } + + ///check for component dependencies foreach (cmp; caller.system.m_read_only_components) { foreach (cmp2; caller2.system.m_modified_components) @@ -2923,6 +3051,18 @@ export struct EntityManager } } + const (UpdatePass)* getPass(const (char)[] name) + { + ushort id = getPassID(name); + if(id == ushort.max)return null; + return passes[id]; + } + + ushort getPassID(const (char)[] name) + { + return passes_map.get(name, ushort.max); + } + /************************************************************************************************************************ Component info; */ @@ -3350,6 +3490,7 @@ export struct EntityManager HashMap!(char[], ushort) components_map; HashMap!(const(char)[], ushort) events_map; HashMap!(const(char)[], ushort) passes_map; + HashMap!(const(char)[], ushort) external_dependencies_map; Vector!System systems; Vector!ComponentInfo components; Vector!EventInfo events; diff --git a/source/bubel/ecs/system.d b/source/bubel/ecs/system.d index 7343831..5bf750b 100644 --- a/source/bubel/ecs/system.d +++ b/source/bubel/ecs/system.d @@ -129,6 +129,9 @@ package: ushort[] m_read_only_components; ushort[] m_modified_components; + ushort[] m_readonly_dependencies; + ushort[] m_writable_dependencies; + EntityManager.SystemCaller* m_any_system_caller; EventCaller[] m_event_callers; diff --git a/source/bubel/ecs/vector.d b/source/bubel/ecs/vector.d index 3334be2..413bbce 100644 --- a/source/bubel/ecs/vector.d +++ b/source/bubel/ecs/vector.d @@ -62,11 +62,11 @@ public: used = 0; } - export bool empty() { + export bool empty() const { return (used == 0); } - export size_t length() { + export size_t length() const { return used; } @@ -197,9 +197,9 @@ public: assert(ok, "There is no such an element in vector"); } - export ref T opIndex(size_t elemNum) { + export ref T opIndex(size_t elemNum) const { //debug assert(elemNum < used, "Range violation [index]"); - return array.ptr[elemNum]; + return *cast(T*)&array.ptr[elemNum]; } export auto opSlice() { diff --git a/tests/basic.d b/tests/basic.d index 653575f..10d79b8 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -1220,4 +1220,279 @@ unittest assert(*entity2.getComponent!CInt == 13); gEM.end(); +} + +@("SystemDependencies") +unittest +{ + struct TestSystem + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + @readonly CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem2 + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem3 + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + @readonly CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem4 + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + CInt[] int_; + CLong[] long_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem5 + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + @readonly CLong[] 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(0); + gEM.registerSystem!TestSystem2(1); + gEM.registerSystem!TestSystem3(2); + gEM.registerSystem!TestSystem4(3); + gEM.registerSystem!TestSystem5(4); + + gEM.endRegister(); + + const (EntityManager.UpdatePass)* pass = gEM.getPass("update"); + assert(pass != null); + assert(pass.system_callers.length == 5); + assert(pass.system_callers[0].system_id == TestSystem.system_id); + assert(pass.system_callers[1].system_id == TestSystem2.system_id); + assert(pass.system_callers[2].system_id == TestSystem3.system_id); + assert(pass.system_callers[3].system_id == TestSystem4.system_id); + assert(pass.system_callers[4].system_id == TestSystem5.system_id); + assert(pass.system_callers[0].dependencies.length == 0); + assert(pass.system_callers[1].dependencies.length == 1); + assert(pass.system_callers[2].dependencies.length == 1); + assert(pass.system_callers[3].dependencies.length == 3); + assert(pass.system_callers[4].dependencies.length == 1); + assert(pass.system_callers[1].dependencies[0].system_id == TestSystem.system_id); + assert(pass.system_callers[2].dependencies[0].system_id == TestSystem2.system_id); + assert(pass.system_callers[3].dependencies[0].system_id == TestSystem.system_id); + assert(pass.system_callers[3].dependencies[1].system_id == TestSystem2.system_id); + assert(pass.system_callers[3].dependencies[2].system_id == TestSystem3.system_id); + assert(pass.system_callers[4].dependencies[0].system_id == TestSystem4.system_id); +} + +@("ExternalSystemDependencies") +unittest +{ + enum TestDependency = "TestDepencency"; + + struct TestSystem + { + mixin ECS.System; + + mixin ECS.ReadOnlyDependencies!(TestDependency); + + struct EntitiesData + { + uint length; + @readonly CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem2 + { + mixin ECS.System; + + mixin ECS.WritableDependencies!(TestDependency); + + struct EntitiesData + { + uint length; + @readonly CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem3 + { + mixin ECS.System; + + mixin ECS.ReadOnlyDependencies!(TestDependency); + + struct EntitiesData + { + uint thread_id; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem4 + { + mixin ECS.System; + + mixin ECS.WritableDependencies!(TestDependency); + + struct EntitiesData + { + uint length; + @readonly CInt[] int_; + } + + void onUpdate(EntitiesData entities) + { + + } + } + + struct TestSystem5 + { + mixin ECS.System; + + mixin ECS.ReadOnlyDependencies!(TestDependency); + + struct EntitiesData + { + uint length; + @readonly CLong[] 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.registerDependency(TestDependency); + + gEM.registerSystem!TestSystem(0); + gEM.registerSystem!TestSystem2(1); + gEM.registerSystem!TestSystem3(2); + gEM.registerSystem!TestSystem4(3); + gEM.registerSystem!TestSystem5(4); + + gEM.endRegister(); + + const (EntityManager.UpdatePass)* pass = gEM.getPass("update"); + assert(pass != null); + assert(pass.system_callers.length == 5); + assert(pass.system_callers[0].system_id == TestSystem.system_id); + assert(pass.system_callers[1].system_id == TestSystem2.system_id); + assert(pass.system_callers[2].system_id == TestSystem3.system_id); + assert(pass.system_callers[3].system_id == TestSystem4.system_id); + assert(pass.system_callers[4].system_id == TestSystem5.system_id); + assert(pass.system_callers[0].dependencies.length == 0); + assert(pass.system_callers[1].dependencies.length == 1); + assert(pass.system_callers[2].dependencies.length == 1); + assert(pass.system_callers[3].dependencies.length == 3); + assert(pass.system_callers[4].dependencies.length == 2); + assert(pass.system_callers[1].dependencies[0].system_id == TestSystem.system_id); + assert(pass.system_callers[2].dependencies[0].system_id == TestSystem2.system_id); + assert(pass.system_callers[3].dependencies[0].system_id == TestSystem.system_id); + assert(pass.system_callers[3].dependencies[1].system_id == TestSystem2.system_id); + assert(pass.system_callers[3].dependencies[2].system_id == TestSystem3.system_id); + assert(pass.system_callers[4].dependencies[0].system_id == TestSystem2.system_id); + assert(pass.system_callers[4].dependencies[1].system_id == TestSystem4.system_id); } \ No newline at end of file -- 2.47.2 From 1b925b7ab190be050012262d2dffcd03188b662a Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 12 May 2020 17:30:57 +0200 Subject: [PATCH 13/37] Change component name from "modified" to "writable" --- source/bubel/ecs/manager.d | 18 +++++++++--------- source/bubel/ecs/system.d | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index c1eb62a..15d1f6e 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -104,8 +104,8 @@ export struct EntityManager Mallocator.dispose(system.jobs); if (system.m_read_only_components) Mallocator.dispose(system.m_read_only_components); - if (system.m_modified_components) - Mallocator.dispose(system.m_modified_components); + if (system.m_writable_components) + Mallocator.dispose(system.m_writable_components); if (system.m_components) Mallocator.dispose(system.m_components); if (system.m_excluded_components) @@ -656,7 +656,7 @@ export struct EntityManager size_t opt = components_info.optional.length; size_t excluded = components_info.excluded.length; size_t read_only = components_info.readonly.length; - size_t modified = components_info.mutable.length; + size_t writable = components_info.mutable.length; size_t read_only_deps = components_info.readonlyDeps.length; size_t writable_deps = components_info.writableDeps.length; @@ -668,8 +668,8 @@ export struct EntityManager system.m_excluded_components = Mallocator.makeArray!ushort(excluded); if (read_only > 0) system.m_read_only_components = Mallocator.makeArray!ushort(read_only); - if (modified > 0) - system.m_modified_components = Mallocator.makeArray!ushort(modified); + if (writable > 0) + system.m_writable_components = Mallocator.makeArray!ushort(writable); if (read_only_deps > 0) system.m_readonly_dependencies = Mallocator.makeArray!ushort(read_only_deps); if (writable_deps > 0) @@ -870,7 +870,7 @@ export struct EntityManager else assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof ~ "\" due to non existing component \"" ~ comp_info.type ~ "\"."); - system.m_modified_components[iii] = comp; + system.m_writable_components[iii] = comp; } } @@ -2954,7 +2954,7 @@ export struct EntityManager ///check for component dependencies foreach (cmp; caller.system.m_read_only_components) { - foreach (cmp2; caller2.system.m_modified_components) + foreach (cmp2; caller2.system.m_writable_components) { if (cmp == cmp2) { @@ -2963,7 +2963,7 @@ export struct EntityManager } } } - foreach (cmp; caller.system.m_modified_components) + foreach (cmp; caller.system.m_writable_components) { foreach (cmp2; caller2.system.m_read_only_components) { @@ -2973,7 +2973,7 @@ export struct EntityManager continue out_for; } } - foreach (cmp2; caller2.system.m_modified_components) + foreach (cmp2; caller2.system.m_writable_components) { if (cmp == cmp2) { diff --git a/source/bubel/ecs/system.d b/source/bubel/ecs/system.d index 5bf750b..7bcaf01 100644 --- a/source/bubel/ecs/system.d +++ b/source/bubel/ecs/system.d @@ -127,7 +127,7 @@ package: //System*[] m_dependencies; ushort[] m_read_only_components; - ushort[] m_modified_components; + ushort[] m_writable_components; ushort[] m_readonly_dependencies; ushort[] m_writable_dependencies; -- 2.47.2 From 9580ee9af9fd40b32651488e3882843f306fff7e Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 12 May 2020 18:21:25 +0200 Subject: [PATCH 14/37] Add bullets collision system to SpaceInvaders demo --- demos/source/demos/space_invaders.d | 252 ++++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 16 deletions(-) diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 30891c8..004a3eb 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -16,10 +16,14 @@ import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +import std.array : staticArray; + enum float px = 1.0/512.0; extern(C): +enum ShootGridDependency = "ShootGridDependency"; + /*####################################################################################################################### ------------------------------------------------ Types ------------------------------------------------------------------ #######################################################################################################################*/ @@ -33,11 +37,18 @@ struct SpaceInvaders EntityTemplate* laser_tmpl; Texture texture; + ShootGrid* shoot_grid; + bool move_system = true; bool draw_system = true; const vec2 map_size = vec2(400,300); const float cell_size = 60; + + ~this() @nogc nothrow + { + if(shoot_grid)Mallocator.dispose(shoot_grid); + } } struct SceneGrid @@ -147,7 +158,7 @@ struct CGuild { mixin ECS.Component; - int guild; + byte guild; } struct CLaser @@ -188,6 +199,11 @@ struct CDepth short depth; } +struct CShootGrid +{ + mixin ECS.Component; +} + /*####################################################################################################################### ------------------------------------------------ Events ------------------------------------------------------------------ #######################################################################################################################*/ @@ -208,6 +224,158 @@ struct EChangeDirection ------------------------------------------------ Systems ------------------------------------------------------------------ #######################################################################################################################*/ +struct ShootGrid +{ + + ~this() @nogc nothrow + { + if(nodes)Mallocator.dispose(nodes); + if(masks)Mallocator.dispose(masks); + } + + struct Node + { + alias entity this; + + EntityID entity; + } + + void create(ivec2 nodes_count, vec2 node_size) + { + this.size = nodes_count; + this.node_size = node_size; + inv_node_size = vec2(1.0/node_size.x, 1.0/node_size.y); + nodes = Mallocator.makeArray!Node(nodes_count.x * nodes_count.y); + masks = Mallocator.makeArray!ubyte(nodes.length); + } + + void mark(EntityID id, vec2 beg, vec2 end, byte mask) + { + ivec2 ibeg = cast(ivec2)(beg * inv_node_size); + ivec2 iend = cast(ivec2)((end * inv_node_size) + 0.5); + if(ibeg.x < 0)ibeg.x = 0; + if(ibeg.y < 0)ibeg.y = 0; + if(iend.x > size.x)iend.x = size.x; + if(iend.y > size.y)iend.y = size.y; + foreach(i; ibeg.y .. iend.y) + { + foreach(j; ibeg.x .. iend.x) + { + nodes[i * size.x + j] = id; + masks[i * size.x +j] = mask; + } + } + } + + void clear() + { + size_t size = nodes.length * EntityID.sizeof; + memset(nodes.ptr, 0, size); + memset(masks.ptr, 0, masks.length); + } + + bool test(out EntityID id, vec2 beg, vec2 end) + { + ivec2 ibeg = cast(ivec2)(beg * inv_node_size); + ivec2 iend = cast(ivec2)((end * inv_node_size) + 0.5); + if(ibeg.x < 0)ibeg.x = 0; + if(ibeg.y < 0)ibeg.y = 0; + if(iend.x > size.x)iend.x = size.x; + if(iend.y > size.y)iend.y = size.y; + foreach(i; ibeg.y .. iend.y) + { + foreach(j; ibeg.x .. iend.x) + { + if(nodes[i * size.x + j].id != 0) + { + id = nodes[i * size.x + j]; + return true; + } + } + } + return false; + } + + bool test(out EntityID id, vec2 pos, ubyte mask) + { + ivec2 ipos = cast(ivec2)(pos * inv_node_size - 0.5); + if(ipos.x < 0)ipos.x = 0; + if(ipos.y < 0)ipos.y = 0; + if(ipos.x >= size.x)ipos.x = size.x - 1; + if(ipos.y >= size.y)ipos.y = size.y - 1; + size_t index = ipos.y * size.x + ipos.x; + if((masks[index] & mask) == 0)return false; + if(nodes[index].id != 0) + { + id = nodes[index]; + return true; + } + return false; + } + + vec2 inv_node_size; + ivec2 size; + vec2 node_size; + Node[] nodes; + ubyte[] masks; +} + +struct ShootGridCleaner +{ + mixin ECS.System!1; + + struct EntitiesData + { + + } + + void onUpdate(EntitiesData data) + { + if(space_invaders.shoot_grid)space_invaders.shoot_grid.clear(); + } +} + +struct ShootGridManager +{ + mixin ECS.System!32; + + mixin ECS.WritableDependencies!(ShootGridDependency); + + struct EntitiesData + { + uint length; + uint thread_id; + const (Entity)[] entity; + @readonly CLocation[] locations; + @readonly CScale[] scale; + @readonly CShootGrid[] grid_flag; + @readonly CGuild[] guild; + } + + ShootGrid* grid; + + void onCreate() + { + grid = space_invaders.shoot_grid; + } + + bool onBegin() + { + if(!grid)return false; + //grid.clear(); + return true; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + vec2 half_scale = data.scale[i] * 0.5; + grid.mark(data.entity[i].id, data.locations[i] - half_scale, data.locations[i] + half_scale, cast(ubyte)(1 << data.guild[i].guild)); + } + } +} + struct DrawSystem { mixin ECS.System!32; @@ -260,10 +428,11 @@ struct LaserShootingSystem mixin ECS.System!32; bool shoot = false; - float[10] laser_shoot_times = [2000,1500,1000,700,500,300,100,50,10,1]; + static float[10] laser_shoot_times = [2000,1500,1000,700,500,300,100,50,10,1]; CLocation* laser_location; CVelocity* laser_velocity; + CGuild* laser_guild; struct EntitiesData { @@ -272,6 +441,7 @@ struct LaserShootingSystem @readonly CShootDirection[] shoot_direction; @readonly @optional CAutoShoot[] auto_shoot; @readonly CLocation[] location; + @readonly CGuild[] guild; CLaserWeapon[] laser; } @@ -285,6 +455,7 @@ struct LaserShootingSystem { laser_location = space_invaders.laser_tmpl.getComponent!CLocation; laser_velocity = space_invaders.laser_tmpl.getComponent!CVelocity; + laser_guild = space_invaders.laser_tmpl.getComponent!CGuild; if(launcher.getKeyState(SDL_SCANCODE_SPACE)) { shoot = true; @@ -307,6 +478,7 @@ struct LaserShootingSystem laser.shoot_time -= laser_shoot_times[laser.level - 1]; laser_location.value = data.location[i]; laser_velocity.value = vec2(randomf()*0.5-0.25,data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); + laser_guild.guild = data.guild[i].guild; launcher.manager.addEntity(space_invaders.laser_tmpl); } } @@ -324,6 +496,36 @@ struct LaserShootingSystem } } +struct LaserCollisionSystem +{ + mixin ECS.System!32; + + mixin ECS.ReadOnlyDependencies!(ShootGridDependency); + + struct EntitiesData + { + ///variable named "length" contain entites count + uint length; + const (Entity)[] entity; + @readonly CLocation[] location; + @readonly CLaser[] laser; + @readonly CGuild[] guild; + } + + void onUpdate(EntitiesData data) + { + EntityID id; + foreach(i; 0..data.length) + { + if(space_invaders.shoot_grid.test(id, data.location[i], cast(ubyte)(~(1 << data.guild[i].guild)))) + { + launcher.manager.removeEntity(data.entity[i].id); + } + } + } + +} + struct ChangeDirectionSystem { mixin ECS.System!32; @@ -591,6 +793,8 @@ struct MovementSystem } } +import core.stdc.math; + /** *System is responsible for movement of objects with CInput component. *In this example every entity has same speed when using movement system. @@ -620,22 +824,24 @@ struct InputMovementSystem move_vector = vec2(0,0); if(launcher.getKeyState(SDL_SCANCODE_W)) { - move_vector = vec2(0,1); - return true; + move_vector += vec2(0,1); } else if(launcher.getKeyState(SDL_SCANCODE_S)) { - move_vector = vec2(0,-1); - return true; + move_vector += vec2(0,-1); } - else if(launcher.getKeyState(SDL_SCANCODE_A)) + if(launcher.getKeyState(SDL_SCANCODE_A)) { - move_vector = vec2(-1,0); - return true; + move_vector += vec2(-1,0); } else if(launcher.getKeyState(SDL_SCANCODE_D)) { - move_vector = vec2(1,0); + move_vector += vec2(1,0); + } + + if(move_vector.x != 0 ||move_vector.y != 0) + { + move_vector = move_vector / sqrtf(move_vector.x * move_vector.x + move_vector.y * move_vector.y); return true; } //don't call system update because no key pressed @@ -682,6 +888,9 @@ void spaceInvadersStart() space_invaders.texture.create(); space_invaders.texture.load("assets/textures/atlas.png"); + space_invaders.shoot_grid = Mallocator.make!ShootGrid; + space_invaders.shoot_grid.create(ivec2(80,60), vec2(5,5)); + launcher.manager.beginRegister(); launcher.manager.registerComponent!CLocation; @@ -696,16 +905,22 @@ void spaceInvadersStart() launcher.manager.registerComponent!CVelocity; launcher.manager.registerComponent!CLaser; launcher.manager.registerComponent!CSideMove; + launcher.manager.registerComponent!CDepth; + launcher.manager.registerComponent!CShootGrid; + launcher.manager.registerComponent!CGuild; launcher.manager.registerEvent!EChangeDirection; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); launcher.manager.registerSystem!InputMovementSystem(-100); + launcher.manager.registerSystem!MovementSystem(-99); + launcher.manager.registerSystem!ClampPositionSystem(-90); launcher.manager.registerSystem!LaserShootingSystem(0); - launcher.manager.registerSystem!MovementSystem(0); - launcher.manager.registerSystem!ClampPositionSystem(1); launcher.manager.registerSystem!ChangeDirectionSystem(0); + launcher.manager.registerSystem!LaserCollisionSystem(-70); + launcher.manager.registerSystem!ShootGridManager(-80); + launcher.manager.registerSystem!ShootGridCleaner(-101); launcher.manager.endRegister(); @@ -718,7 +933,7 @@ void spaceInvadersStart() //launcher.manager.getSystem(CleanSystem.system_id).disable(); { - ushort[7] components = [CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id]; + ushort[9] components = [CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; space_invaders.ship_tmpl = launcher.manager.allocateTemplate(components); CScale* scale_comp = space_invaders.ship_tmpl.getComponent!CScale; @@ -735,7 +950,7 @@ void spaceInvadersStart() } { - ushort[5] components = [CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CLaser.component_id]; + ushort[6] components = [CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CLaser.component_id, CGuild.component_id]; space_invaders.laser_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.laser_tmpl.getComponent!CTexture; @@ -753,7 +968,7 @@ void spaceInvadersStart() EntityID grouped_id; { - ushort[8] components = [CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, CEnemy.component_id, CShootDirection.component_id];//, CVelocity.component_id]; + ushort[10] components = [CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; space_invaders.enemy_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.enemy_tmpl.getComponent!CTexture; @@ -765,6 +980,7 @@ void spaceInvadersStart() shoot_dir_comp.direction = Direction.down; CVelocity* vel_comp = space_invaders.enemy_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0.1,0); + space_invaders.enemy_tmpl.getComponent!CGuild().guild = 1; Entity* current_entity; @@ -802,7 +1018,6 @@ void spaceInvadersStart() void spaceInvadersEnd() { - launcher.manager.getSystem(DrawSystem.system_id).disable(); launcher.manager.getSystem(InputMovementSystem.system_id).disable(); launcher.manager.getSystem(LaserShootingSystem.system_id).disable(); @@ -827,6 +1042,11 @@ void spaceInvadersTool(vec2 position, Tool tool, int size) position.y += (randomf - 0.5) * size; *location = position; } + CLaserWeapon* laser_weapon = tmpl.getComponent!CLaserWeapon; + if(laser_weapon) + { + laser_weapon.shoot_time = randomf * LaserShootingSystem.laser_shoot_times[laser_weapon.level - 1]; + } launcher.manager.addEntity(tmpl); } break; -- 2.47.2 From dd491302afa388e86e7ad5b2a4a5af502a28431b Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 15:31:26 +0200 Subject: [PATCH 15/37] Fixed system reregistration issue and added funtion to allocate EntityTEmplate as copy of different teamplte --- source/bubel/ecs/manager.d | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index 15d1f6e..c6b02a1 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -1150,17 +1150,22 @@ export struct EntityManager ushort sys_id = systems_map.get(cast(char[]) Sys.stringof, ushort.max); if (sys_id < systems.length) { - system.enable(); + systems[sys_id].disable(); + if(systems[sys_id].m_destroy)(cast(void function(void*)) systems[sys_id].m_destroy)(systems[sys_id].m_system_pointer); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); + system.enable(); + system.m_id = sys_id; + system.m_name = systems[sys_id].m_name; systems[sys_id] = system; } else { system.m_name = Mallocator.makeArray(cast(char[]) Sys.stringof); + systems_map.add(system.m_name, cast(ushort) systems.length); system.m_id = cast(ushort)(systems.length); @@ -1723,6 +1728,20 @@ export struct EntityManager return temp; } + /************************************************************************************************************************ + Allocate EntityTemplate copy. + + Params: + copy_tmpl = template which should be copied + */ + export EntityTemplate* allocateTemplate(EntityTemplate* copy_tmpl) + { + EntityTemplate* tmpl = Mallocator.make!EntityTemplate; + tmpl.info = copy_tmpl.info; + tmpl.entity_data = Mallocator.makeArray(copy_tmpl.entity_data); + return tmpl; + } + /************************************************************************************************************************ Returns entity type info. @@ -2344,7 +2363,7 @@ export struct EntityManager Params: template_ = pointer entity template allocated by EntityManager. */ - export void freeTemplate(EntityTemplate* template_) + export void freeTemplate(EntityTemplate* template_) @nogc nothrow { Mallocator.dispose(template_.entity_data); Mallocator.dispose(template_); -- 2.47.2 From 3647fa4b867ff7b4a0c0ecf5829c8b7bd23bb369 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 15:34:24 +0200 Subject: [PATCH 16/37] Demos update -added functionality to detect host CPU threads count -fixed some memory leaks -now textures free memory corectly -added support for up to 32 threads --- demos/external/sources/mmutils/thread_pool.d | 40 ++++++++++++++++++++ demos/source/app.d | 34 ++++++++++++++--- demos/source/demos/snake.d | 16 ++++++-- demos/source/demos/space_invaders.d | 17 ++++++--- demos/utils/source/ecs_utils/gfx/renderer.d | 2 +- demos/utils/source/ecs_utils/gfx/texture.d | 5 ++- 6 files changed, 98 insertions(+), 16 deletions(-) diff --git a/demos/external/sources/mmutils/thread_pool.d b/demos/external/sources/mmutils/thread_pool.d index 143960c..48d2932 100644 --- a/demos/external/sources/mmutils/thread_pool.d +++ b/demos/external/sources/mmutils/thread_pool.d @@ -810,6 +810,46 @@ private: JobData[4] resumeJobs; /// Dummu jobs to resume some thread public: + + static int getCPUCoresCount() + { + version(Windows) + { + import core.sys.windows.winbase : SYSTEM_INFO, GetSystemInfo; + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; + } + else version (linux) + { + version(D_BetterC) + { + import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf; + return cast(int)sysconf(_SC_NPROCESSORS_ONLN); + } + else + { + import core.sys.linux.sched : CPU_COUNT, cpu_set_t, sched_getaffinity; + import core.sys.posix.unistd : _SC_NPROCESSORS_ONLN, sysconf; + + cpu_set_t set = void; + if (sched_getaffinity(0, cpu_set_t.sizeof, &set) == 0) + { + int count = CPU_COUNT(&set); + if (count > 0) + return cast(uint) count; + } + return cast(int)sysconf(_SC_NPROCESSORS_ONLN); + } + } + else version(Posix) + { + import core.sys.posix.unistd; + return cast(int)sysconf(_SC_NPROCESSORS_ONLN); + } + else return -1; + } + int jobsDoneCount() { int sum; diff --git a/demos/source/app.d b/demos/source/app.d index af4b331..10fc80e 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -58,7 +58,7 @@ struct Launcher uint style = 3; uint entities_count; bool multithreading; - int threads; + int threads = 1; ulong timer_freq; double delta_time; uint fps; @@ -103,6 +103,14 @@ struct Launcher manager.update("clean"); manager.end(); + foreach(system; manager.systems) + { + if(system.id != CountSystem.system_id && system.id != CleanSystem.system_id)system.disable(); + } + + /*launcher.manager.getSystem(CountSystem.system_id).enable(); + launcher.manager.getSystem(CleanSystem.system_id).enable();//*/ + if(start)start(); this.loop = loop; this.end = end; @@ -316,11 +324,19 @@ void mainLoop(void* arg) if(igMenuItemBool("Multithreading", null, launcher.multithreading, true)) { launcher.multithreading = !launcher.multithreading; + if(launcher.multithreading) + { + launcher.job_updater.pool.setThreadsNum(launcher.threads); + } + else + { + launcher.job_updater.pool.setThreadsNum(1); + } } igSetNextItemWidth(0); igLabelText("Threads:",null); igSameLine(0,4); - if(igSliderInt("##Threads",&launcher.threads, 1, 12, null))//"Multithreading", null, launcher.multithreading, true)) + if(igSliderInt("##Threads",&launcher.threads, 1, 32, null))//"Multithreading", null, launcher.multithreading, true)) { launcher.job_updater.pool.setThreadsNum(launcher.threads); //launcher.threads = !launcher.multithreading; @@ -547,13 +563,13 @@ void mainLoop(void* arg) launcher.renderer.clear(); double loop_time = launcher.getTime(); - launcher.job_updater.pool.tryWaitCount = 5000; + launcher.job_updater.pool.tryWaitCount = 10000; if(launcher.loop && !launcher.loop()) { quit(); *cast(bool*)arg = false; } - launcher.job_updater.pool.tryWaitCount = 10; + launcher.job_updater.pool.tryWaitCount = 0; loop_time = launcher.getTime() - loop_time; @@ -697,10 +713,10 @@ int main(int argc, char** argv) setStyle(3); - launcher.job_updater = Mallocator.make!ECSJobUpdater(12); + launcher.job_updater = Mallocator.make!ECSJobUpdater(1); //launcher.job_updater.onCreate(); - EntityManager.initialize(12); + EntityManager.initialize(32); launcher.manager = EntityManager.instance; //launcher.manager.m_thread_id_func = &launcher.job_updater.getThreadID; @@ -720,6 +736,10 @@ int main(int argc, char** argv) launcher.renderer.initialize(); + import mmutils.thread_pool : ThreadPool; + launcher.threads = ThreadPool.getCPUCoresCount(); + if(launcher.threads < 2)launcher.threads = 2; + launcher.gui_manager = Mallocator.make!GUIManager(); { @@ -752,6 +772,8 @@ int main(int argc, char** argv) } } + EntityManager.destroy(); + return 0; } diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 94f2654..ecaefd3 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -90,6 +90,16 @@ struct Snake MapElement[map_size * map_size] map; + ~this() @nogc nothrow + { + if(snake_destroy_particle_frames)Mallocator.dispose(snake_destroy_particle_frames); + if(smoke_frames)Mallocator.dispose(smoke_frames); + if(apple_tmpl)launcher.manager.freeTemplate(apple_tmpl); + if(snake_tmpl)launcher.manager.freeTemplate(snake_tmpl); + if(snake_destroy_particle)launcher.manager.freeTemplate(snake_destroy_particle); + texture.destory(); + } + MapElement element(ivec2 pos) { uint index = pos.x + pos.y * map_size; @@ -805,9 +815,9 @@ void snakeStart() snake.addApple(); } - launcher.gui_manager.addTemplate(snake.snake_tmpl, "Snake"); - launcher.gui_manager.addTemplate(snake.apple_tmpl, "Apple"); - launcher.gui_manager.addTemplate(snake.snake_destroy_particle, "Particle"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(snake.snake_tmpl), "Snake"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(snake.apple_tmpl), "Apple"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(snake.snake_destroy_particle), "Particle"); MoveSystem* move_system = launcher.manager.getSystem!MoveSystem(); move_system.setTemplates(); diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 004a3eb..b6fe80d 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -48,6 +48,10 @@ struct SpaceInvaders ~this() @nogc nothrow { if(shoot_grid)Mallocator.dispose(shoot_grid); + launcher.manager.freeTemplate(enemy_tmpl); + launcher.manager.freeTemplate(ship_tmpl); + launcher.manager.freeTemplate(laser_tmpl); + texture.destory(); } } @@ -337,7 +341,7 @@ struct ShootGridCleaner struct ShootGridManager { - mixin ECS.System!32; + mixin ECS.System!128; mixin ECS.WritableDependencies!(ShootGridDependency); @@ -793,7 +797,7 @@ struct MovementSystem } } -import core.stdc.math; +extern(C) float sqrtf(float x) @nogc nothrow @system; /** *System is responsible for movement of objects with CInput component. @@ -893,6 +897,8 @@ void spaceInvadersStart() launcher.manager.beginRegister(); + launcher.manager.registerDependency(ShootGridDependency); + launcher.manager.registerComponent!CLocation; launcher.manager.registerComponent!CTexture; launcher.manager.registerComponent!CInput; @@ -1009,10 +1015,10 @@ void spaceInvadersStart() enemy_tmpl = launcher.manager.allocateTemplate(enemy_id); grouped_tmpl = launcher.manager.allocateTemplate(grouped_id); - launcher.gui_manager.addTemplate(space_invaders.ship_tmpl,"Ship"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.ship_tmpl),"Ship"); launcher.gui_manager.addTemplate(enemy_tmpl,"Enemy"); launcher.gui_manager.addTemplate(grouped_tmpl,"Grouped enemy"); - launcher.gui_manager.addTemplate(space_invaders.laser_tmpl,"Laser"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.laser_tmpl),"Laser"); } @@ -1023,8 +1029,9 @@ void spaceInvadersEnd() launcher.manager.getSystem(LaserShootingSystem.system_id).disable(); launcher.manager.getSystem(MovementSystem.system_id).disable(); launcher.manager.getSystem(ClampPositionSystem.system_id).disable(); + launcher.manager.getSystem(ShootGridCleaner.system_id).disable(); - launcher.manager.freeTemplate(space_invaders.enemy_tmpl); + //launcher.manager.freeTemplate(space_invaders.enemy_tmpl); Mallocator.dispose(space_invaders); } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index ba42cf6..206364f 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -230,7 +230,7 @@ struct Renderer block_stack_mutex.initialize(); - threads = Mallocator.makeArray!Thread(12); + threads = Mallocator.makeArray!Thread(32); foreach(ref Thread thread;threads) { thread.block = getBlock(); diff --git a/demos/utils/source/ecs_utils/gfx/texture.d b/demos/utils/source/ecs_utils/gfx/texture.d index 05e2fd9..d4e2089 100644 --- a/demos/utils/source/ecs_utils/gfx/texture.d +++ b/demos/utils/source/ecs_utils/gfx/texture.d @@ -54,6 +54,8 @@ struct Texture data.data = Mallocator.makeArray!ubyte(surf.w*surf.h*surf.format.BytesPerPixel); data.data[0..$] = (cast(ubyte*)surf.pixels)[0..data.data.length]; + SDL_FreeSurface(surf); + glGenTextures(1, &data.gl_handle); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,data.gl_handle); @@ -78,11 +80,12 @@ struct Texture glBindTexture(GL_TEXTURE_2D, data.gl_handle); } - void destory() + void destory() @nogc nothrow { if(data) { glDeleteTextures(1, &data.gl_handle); + if(data.data)Mallocator.dispose(data.data); Mallocator.dispose(data); data = null; } -- 2.47.2 From d257a6c9f83acc660ddff5383b4ce45730663702 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 16:24:32 +0200 Subject: [PATCH 17/37] Updated codecov.yml --- codecov.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index a6afdbc..f3e0d9b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,12 @@ ignore: - "tests/*" - - "**/traits*" \ No newline at end of file + - "**/traits*" + +coverage: + status: + project: + default: + threshold: 5 + patch: + default: + threshold: 5 \ No newline at end of file -- 2.47.2 From c29ace661bde8b132248c44d810a716de741c608 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 21:27:09 +0200 Subject: [PATCH 18/37] Added laser collision response to SpaceInvaders --- demos/assets/shaders/base.vp | 2 +- demos/source/demos/simple.d | 2 +- demos/source/demos/snake.d | 32 +++---- demos/source/demos/space_invaders.d | 124 +++++++++++++++++++++++++--- 4 files changed, 131 insertions(+), 29 deletions(-) diff --git a/demos/assets/shaders/base.vp b/demos/assets/shaders/base.vp index 2777a24..b054d8e 100644 --- a/demos/assets/shaders/base.vp +++ b/demos/assets/shaders/base.vp @@ -99,7 +99,7 @@ void main() { uv = tex_coords * uv_transform.zw + uv_transform.xy; #endif - color = vcolor; + color = vcolor * 2; gl_Position = vec4(position.xy,depth,1.0); diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 686f9be..1668323 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -62,7 +62,7 @@ struct DrawSystem if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y*64+data.locations[i].x), uint.max, 0, 0, 0, data.thread_id); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y*64+data.locations[i].x), 0x80808080, 0, 0, 0, data.thread_id); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } if(data.thread_id == 0)launcher.renderer.pushData(); diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index ecaefd3..244e5ec 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -356,7 +356,7 @@ struct AnimationRenderSystem { foreach(i;0..data.length) { - launcher.renderer.draw(snake.texture, cast(vec2)cast(ivec2)data.location[i].location, vec2(16,16), data.animation[i].frames[cast(int)(data.animation[i].time)], 0, 0 , 0); + launcher.renderer.draw(snake.texture, cast(vec2)cast(ivec2)data.location[i].location, vec2(16,16), data.animation[i].frames[cast(int)(data.animation[i].time)], -1, 0x80808080); } } } @@ -617,7 +617,7 @@ struct DrawAppleSystem { foreach(i; 0..data.location.length) { - launcher.renderer.draw(snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, uint.max, 0); + launcher.renderer.draw(snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,32*px,16*px,16*px), 0, 0x80808080, 0); } } } @@ -687,16 +687,16 @@ struct DrawSnakeSystem { final switch(cast(ubyte)part) { - case SnakePart.tail_up:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,112,16,16)*px, 0, uint.max, 0);break; - case SnakePart.tail_down:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,112,16,16)*px, 0, uint.max, 0);break; - case SnakePart.tail_left:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,112,16,16)*px, 0, uint.max, 0);break; - case SnakePart.tail_right:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,144,16,16)*px, 0, uint.max, 0);break; - case SnakePart.turn_ld:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,128,16,16)*px, 0, uint.max, 0);break; - case SnakePart.turn_lu:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,144,16,16)*px, 0, uint.max, 0);break; - case SnakePart.turn_rd:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,144,16,16)*px, 0, uint.max, 0);break; - case SnakePart.turn_ru:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,112,16,16)*px, 0, uint.max, 0);break; - case SnakePart.vertical:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,128,16,16)*px, 0, uint.max, 0);break; - case SnakePart.horizontal:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(48,128,16,16)*px, 0, uint.max, 0);break; + case SnakePart.tail_up:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,112,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.tail_down:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,112,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.tail_left:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,112,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.tail_right:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(0,144,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.turn_ld:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,128,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.turn_lu:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(32,144,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.turn_rd:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,144,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.turn_ru:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(64,112,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.vertical:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(16,128,16,16)*px, 0, 0x80808080, 0);break; + case SnakePart.horizontal:launcher.renderer.draw(.snake.texture, cast(vec2)loc, vec2(16,16), vec4(48,128,16,16)*px, 0, 0x80808080, 0);break; } } @@ -708,10 +708,10 @@ struct DrawSnakeSystem scope vec2 loc = cast(vec2)(data.location[i].location * 16); final switch(snake.direction) { - case CMovement.Direction.up:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,112,16,16)*px, 0, uint.max, 0);break; - case CMovement.Direction.down:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,144,16,16)*px, 0, uint.max, 0);break; - case CMovement.Direction.left:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,128,16,16)*px, 0, uint.max, 0);break; - case CMovement.Direction.right:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(32,128,16,16)*px, 0, uint.max, 0);break; + case CMovement.Direction.up:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,112,16,16)*px, 0, 0x80808080, 0);break; + case CMovement.Direction.down:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(48,144,16,16)*px, 0, 0x80808080, 0);break; + case CMovement.Direction.left:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(0,128,16,16)*px, 0, 0x80808080, 0);break; + case CMovement.Direction.right:launcher.renderer.draw(.snake.texture, vec2(data.location[i].x*16,data.location[i].y*16), vec2(16,16), vec4(32,128,16,16)*px, 0, 0x80808080, 0);break; } if(snake.parts.length >1) { diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index b6fe80d..f4e2848 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -208,6 +208,24 @@ struct CShootGrid mixin ECS.Component; } +struct CHitPoints +{ + mixin ECS.Component; + + alias value this; + + int value = 10; +} + +struct CHitMark +{ + mixin ECS.Component; + + alias value this; + + ubyte value = 0; +} + /*####################################################################################################################### ------------------------------------------------ Events ------------------------------------------------------------------ #######################################################################################################################*/ @@ -224,6 +242,18 @@ struct EChangeDirection Direction direction; } +struct EDamage +{ + mixin ECS.Event; + + this(uint damage) + { + this.damage = damage; + } + + uint damage = 0; +} + /*####################################################################################################################### ------------------------------------------------ Systems ------------------------------------------------------------------ #######################################################################################################################*/ @@ -392,22 +422,47 @@ struct DrawSystem @readonly CLocation[] locations; @readonly CScale[] scale; @readonly @optional CDepth[] depth; + @readonly @optional CHitMark[] hit_mark; } void onUpdate(EntitiesData data) { if(!data.depth) - foreach(i; 0..data.length) + { + if(data.hit_mark) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, uint.max, 0, 0, 0, data.thread_id); - //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); + foreach(i; 0..data.length) + { + uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, color, 0, 0, 0, data.thread_id); + } } + else + { + foreach(i; 0..data.length) + { + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, 0x80808080, 0, 0, 0, data.thread_id); + } + } + } else - foreach(i; 0..data.length) + { + if(data.hit_mark) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), uint.max, 0, 0, 0, data.thread_id); - //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); + foreach(i; 0..data.length) + { + uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), color, 0, 0, 0, data.thread_id); + } } + else + { + foreach(i; 0..data.length) + { + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), 0x80808080, 0, 0, 0, data.thread_id); + } + } + } if(data.thread_id == 0)launcher.renderer.pushData(); } } @@ -432,7 +487,7 @@ struct LaserShootingSystem mixin ECS.System!32; bool shoot = false; - static float[10] laser_shoot_times = [2000,1500,1000,700,500,300,100,50,10,1]; + static float[22] laser_shoot_times = [2000,1500,1000,700,500,350,250,170,130,80,50,25,15,10,5,2.5,2,1,0.8,0.5,0.3,0.1]; CLocation* laser_location; CVelocity* laser_velocity; @@ -523,6 +578,7 @@ struct LaserCollisionSystem { if(space_invaders.shoot_grid.test(id, data.location[i], cast(ubyte)(~(1 << data.guild[i].guild)))) { + launcher.manager.sendEvent(id, EDamage(1)); launcher.manager.removeEntity(data.entity[i].id); } } @@ -666,7 +722,7 @@ struct ChangeDirectionSystem } } - void handleEvent(Entity* entity, EChangeDirection event) + /*void handleEvent(Entity* entity, EChangeDirection event) { CSideMove* side_move = entity.getComponent!CSideMove; if(side_move && side_move.group != -1) @@ -691,6 +747,46 @@ struct ChangeDirectionSystem if(velocity.value.x < 0)velocity.value.x = -velocity.value.x; break; } + }*/ +} + +struct HitMarkingSystem +{ + mixin ECS.System!16; + + struct EntitiesData + { + uint length; + CHitMark[] mark; + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + //if(data.mark[i] < 10)data.mark[i] = 0; + //else data.mark[i] -= 1; + data.mark[i] = cast(ubyte)(data.mark[i] * 0.9); + } + } +} + +struct HitPointsSystem +{ + mixin ECS.System; + + struct EntitiesData + { + CHitPoints[] hp; + } + + void handleEvent(Entity* entity, EDamage event) + { + CHitPoints* hp = entity.getComponent!CHitPoints; + *hp -= event.damage; + if(*hp < 0)launcher.manager.removeEntity(entity.id); + CHitMark* hit_mark = entity.getComponent!CHitMark; + if(hit_mark)hit_mark.value = 127; } } @@ -914,8 +1010,11 @@ void spaceInvadersStart() launcher.manager.registerComponent!CDepth; launcher.manager.registerComponent!CShootGrid; launcher.manager.registerComponent!CGuild; + launcher.manager.registerComponent!CHitPoints; + launcher.manager.registerComponent!CHitMark; launcher.manager.registerEvent!EChangeDirection; + launcher.manager.registerEvent!EDamage; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); @@ -927,6 +1026,8 @@ void spaceInvadersStart() launcher.manager.registerSystem!LaserCollisionSystem(-70); launcher.manager.registerSystem!ShootGridManager(-80); launcher.manager.registerSystem!ShootGridCleaner(-101); + launcher.manager.registerSystem!HitPointsSystem(0); + launcher.manager.registerSystem!HitMarkingSystem(-100); launcher.manager.endRegister(); @@ -939,7 +1040,7 @@ void spaceInvadersStart() //launcher.manager.getSystem(CleanSystem.system_id).disable(); { - ushort[9] components = [CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; + ushort[11] components = [CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; space_invaders.ship_tmpl = launcher.manager.allocateTemplate(components); CScale* scale_comp = space_invaders.ship_tmpl.getComponent!CScale; @@ -950,7 +1051,8 @@ void spaceInvadersStart() CLocation* loc_comp = space_invaders.ship_tmpl.getComponent!CLocation; loc_comp.value = vec2(64,64); CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; - weapon.level = 10; + weapon.level = 22; + space_invaders.ship_tmpl.getComponent!CHitPoints().value = 1000; launcher.manager.addEntity(space_invaders.ship_tmpl); } @@ -974,7 +1076,7 @@ void spaceInvadersStart() EntityID grouped_id; { - ushort[10] components = [CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; + ushort[12] components = [CHitMark.component_id, CHitPoints.component_id, CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; space_invaders.enemy_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.enemy_tmpl.getComponent!CTexture; -- 2.47.2 From d26c940b80608ab64cbd18413ae1b71f05c08c15 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 21:30:56 +0200 Subject: [PATCH 19/37] Fixed shader compilation on GLES --- demos/assets/shaders/base.vp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/assets/shaders/base.vp b/demos/assets/shaders/base.vp index b054d8e..6c4c183 100644 --- a/demos/assets/shaders/base.vp +++ b/demos/assets/shaders/base.vp @@ -99,7 +99,7 @@ void main() { uv = tex_coords * uv_transform.zw + uv_transform.xy; #endif - color = vcolor * 2; + color = vcolor * 2.0; gl_Position = vec4(position.xy,depth,1.0); -- 2.47.2 From f731b4cedb1f9f6b408a555312a18eed7bcae3ee Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 13 May 2020 21:51:38 +0200 Subject: [PATCH 20/37] Added Upgrade bonuses to SpaceInvaders demo --- demos/source/demos/space_invaders.d | 82 ++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index f4e2848..1c5b3e4 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -226,6 +226,11 @@ struct CHitMark ubyte value = 0; } +struct CUpgrade +{ + mixin ECS.Component; +} + /*####################################################################################################################### ------------------------------------------------ Events ------------------------------------------------------------------ #######################################################################################################################*/ @@ -242,6 +247,11 @@ struct EChangeDirection Direction direction; } +struct EUpgrade +{ + mixin ECS.Event; +} + struct EDamage { mixin ECS.Event; @@ -487,7 +497,7 @@ struct LaserShootingSystem mixin ECS.System!32; bool shoot = false; - static float[22] laser_shoot_times = [2000,1500,1000,700,500,350,250,170,130,80,50,25,15,10,5,2.5,2,1,0.8,0.5,0.3,0.1]; + static float[18] laser_shoot_times = [500,400,300,200,100,50,25,10,5,2,1,0.8,0.6,0.5,0.4,0.3,0.2,0.1]; CLocation* laser_location; CVelocity* laser_velocity; @@ -583,7 +593,55 @@ struct LaserCollisionSystem } } } +} +struct UpgradeCollisionSystem +{ + mixin ECS.System!32; + + mixin ECS.ReadOnlyDependencies!(ShootGridDependency); + + struct EntitiesData + { + ///variable named "length" contain entites count + uint length; + const (Entity)[] entity; + @readonly CLocation[] location; + @readonly CUpgrade[] upgrade; + } + + void onUpdate(EntitiesData data) + { + EntityID id; + foreach(i; 0..data.length) + { + if(space_invaders.shoot_grid.test(id, data.location[i], cast(ubyte)(0xFF))) + { + launcher.manager.sendEvent(id, EUpgrade()); + launcher.manager.removeEntity(data.entity[i].id); + } + } + } +} + +struct UpgradeSystem +{ + mixin ECS.System; + + struct EntitiesData + { + const (Entity)[] entity; + @readonly CShip[] ship; + } + + void handleEvent(Entity* entity, EUpgrade event) + { + CLaserWeapon* laser = entity.getComponent!CLaserWeapon; + if(laser) + { + if(laser.level < LaserShootingSystem.laser_shoot_times.length)laser.level++; + } + } } struct ChangeDirectionSystem @@ -893,6 +951,7 @@ struct MovementSystem } } + extern(C) float sqrtf(float x) @nogc nothrow @system; /** @@ -1012,9 +1071,11 @@ void spaceInvadersStart() launcher.manager.registerComponent!CGuild; launcher.manager.registerComponent!CHitPoints; launcher.manager.registerComponent!CHitMark; + launcher.manager.registerComponent!CUpgrade; launcher.manager.registerEvent!EChangeDirection; launcher.manager.registerEvent!EDamage; + launcher.manager.registerEvent!EUpgrade; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); @@ -1028,6 +1089,8 @@ void spaceInvadersStart() launcher.manager.registerSystem!ShootGridCleaner(-101); launcher.manager.registerSystem!HitPointsSystem(0); launcher.manager.registerSystem!HitMarkingSystem(-100); + launcher.manager.registerSystem!UpgradeCollisionSystem(-70); + launcher.manager.registerSystem!UpgradeSystem(-100); launcher.manager.endRegister(); @@ -1051,7 +1114,7 @@ void spaceInvadersStart() CLocation* loc_comp = space_invaders.ship_tmpl.getComponent!CLocation; loc_comp.value = vec2(64,64); CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; - weapon.level = 22; + weapon.level = 1; space_invaders.ship_tmpl.getComponent!CHitPoints().value = 1000; launcher.manager.addEntity(space_invaders.ship_tmpl); @@ -1112,6 +1175,18 @@ void spaceInvadersStart() grouped_id = current_entity.id; //grouped_tmpl = launcher.manager.allocateTemplate(current_entity.id); } + + EntityTemplate* upgrade_tmpl; + + { + upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id].staticArray); + CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; + tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(0*px,32*px,16*px,16*px); + CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; + vel_comp.value = vec2(0,-0.1); + } + launcher.manager.commit(); enemy_tmpl = launcher.manager.allocateTemplate(enemy_id); @@ -1121,6 +1196,7 @@ void spaceInvadersStart() launcher.gui_manager.addTemplate(enemy_tmpl,"Enemy"); launcher.gui_manager.addTemplate(grouped_tmpl,"Grouped enemy"); launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.laser_tmpl),"Laser"); + launcher.gui_manager.addTemplate(upgrade_tmpl,"Upgrade"); } @@ -1149,6 +1225,8 @@ void spaceInvadersTool(vec2 position, Tool tool, int size) { position.x += (randomf - 0.5) * size; position.y += (randomf - 0.5) * size; + if(position.y < 16)position.y = 16; + else if(position.y > 299)position.y = 299; *location = position; } CLaserWeapon* laser_weapon = tmpl.getComponent!CLaserWeapon; -- 2.47.2 From 9589a5cb2d051e18f32039daad4077150b72870f Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 14 May 2020 22:18:57 +0200 Subject: [PATCH 21/37] FIxed GDC compilation (basic betterC WIP) and some improvements -fixed issue with adding/removing entities inside events handling -fixed EntityMeta.getComponent() (added check if component_id is valid) -added function hasComponent to entity to check if component exists --- demos/external/sources/mmutils/thread_pool.d | 7 +- demos/source/demos/snake.d | 2 +- demos/utils/source/ecs_utils/utils.d | 14 +- dub.json | 14 +- source/bubel/ecs/entity.d | 18 ++ source/bubel/ecs/manager.d | 164 ++++++++++--------- source/bubel/ecs/std.d | 8 + source/bubel/ecs/vector.d | 14 +- tests/access_perf.d | 9 +- tests/basic.d | 9 +- tests/perf.d | 9 +- 11 files changed, 174 insertions(+), 94 deletions(-) diff --git a/demos/external/sources/mmutils/thread_pool.d b/demos/external/sources/mmutils/thread_pool.d index 48d2932..52136d4 100644 --- a/demos/external/sources/mmutils/thread_pool.d +++ b/demos/external/sources/mmutils/thread_pool.d @@ -182,6 +182,12 @@ void instructionPause() __builtin_ia32_pause(); } + else version(GNU) + { + import gcc.builtins; + + __builtin_ia32_pause(); + } else version (DigitalMars) { asm @@ -189,7 +195,6 @@ void instructionPause() rep; nop; } - } else { diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 244e5ec..3c62ef6 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -17,7 +17,7 @@ import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; -import std.array : staticArray; +//import std.array : staticArray; enum float px = 1.0/512.0; diff --git a/demos/utils/source/ecs_utils/utils.d b/demos/utils/source/ecs_utils/utils.d index 4c143fd..8ce5cd1 100644 --- a/demos/utils/source/ecs_utils/utils.d +++ b/demos/utils/source/ecs_utils/utils.d @@ -21,7 +21,19 @@ float randomRangef(float min, float max) return rand()%4096; }*/ -extern(C) int printf(scope const char* format, ...) @nogc nothrow @system; +version(GNU) +{ + public import core.stdc.stdio : printf; + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else +{ + extern(C) int printf(scope const char* format, ...) @nogc nothrow @system; + public import std.array : staticArray; +} extern(C) int rand(); version(D_BetterC) diff --git a/dub.json b/dub.json index c23a4e6..01cf85c 100755 --- a/dub.json +++ b/dub.json @@ -74,6 +74,9 @@ "dflags": [ "-betterC", "-defaultlib=" + ], + "dflags-gdc": [ + "-fno-druntime" ] }, { @@ -88,9 +91,7 @@ ], "dflags-gdc": [ "-fno-druntime", - "-fvisibility=hidden" - ], - "lflags-gdc": [ + "-fvisibility=hidden", "-lpthread" ] }, @@ -105,9 +106,7 @@ "-betterC" ], "dflags-gdc": [ - "-fno-druntime" - ], - "lflags-gdc": [ + "-fno-druntime", "-lpthread" ] }, @@ -118,6 +117,9 @@ "-betterC", "-unittest" ], + "dflags-gdc": [ + "-fno-druntime" + ], "sourcePaths": ["source/","tests/"], "mainSourceFile":"tests/runner.d", "excludedSourceFiles":[ diff --git a/source/bubel/ecs/entity.d b/source/bubel/ecs/entity.d index 10720a2..1e98ff2 100644 --- a/source/bubel/ecs/entity.d +++ b/source/bubel/ecs/entity.d @@ -46,6 +46,14 @@ struct Entity return cast(T*)(cast(void*)block + info.deltas[T.component_id] + ind * T.sizeof); } + bool hasComponent(ushort component_id) + { + EntityManager.EntitiesBlock* block = gEM.getMetaData(&this); + EntityManager.EntityInfo* info = block.type_info; + if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false; + return true; + } + EntityMeta getMeta() { EntityMeta meta; @@ -65,8 +73,18 @@ struct EntityMeta T* getComponent(T)() const { + const (EntityManager.EntityInfo)* info = block.type_info; + if (T.component_id >= info.deltas.length || info.deltas[T.component_id] == 0) + return null; return cast(T*)(cast(void*)block + block.type_info.deltas[T.component_id] + index * T.sizeof); } + + bool hasComponent(ushort component_id) + { + EntityManager.EntityInfo* info = block.type_info; + if (component_id >= info.deltas.length || info.deltas[component_id] == 0)return false; + return true; + } } /************************************************************************************************************************ diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index c6b02a1..7b6a983 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -126,7 +126,7 @@ export struct EntityManager //if(info.components)Mallocator.dispose(info.components); Mallocator.dispose(info); - } //*/ + } foreach (UpdatePass* pass; passes) { @@ -472,50 +472,52 @@ export struct EntityManager if (member == "length" || member == "thread_id" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { - continue; + //continue; } - - string name; - static if (isArray!MemberType) - { // Workaround. This code is never called with: not an array type, but compiler prints an error - name = Unqual!(ForeachType!MemberType).stringof; - } - - bool is_optional; - bool is_read_only; - - if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) + else { - is_read_only = true; - } - - foreach (att; __traits(getAttributes, __traits(getMember, - Sys.EntitiesData, member))) - { - if (att == "optional") - { - is_optional = true; + string name; + static if (isArray!MemberType) + { // Workaround. This code is never called with: not an array type, but compiler prints an error + name = Unqual!(ForeachType!MemberType).stringof; } - if (att == "readonly") + + bool is_optional; + bool is_read_only; + + if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) { is_read_only = true; } - } - if (is_read_only) - { - components_counts.readonly++; - } - else - { - components_counts.mutable++; - } - if (is_optional) - { - components_counts.optional++; - } - else - { - components_counts.req++; + + foreach (att; __traits(getAttributes, __traits(getMember, + Sys.EntitiesData, member))) + { + if (att == "optional") + { + is_optional = true; + } + if (att == "readonly") + { + is_read_only = true; + } + } + if (is_read_only) + { + components_counts.readonly++; + } + else + { + components_counts.mutable++; + } + if (is_optional) + { + components_counts.optional++; + } + else + { + components_counts.req++; + } } } @@ -695,50 +697,52 @@ export struct EntityManager { if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) components_info.entites_array = member; - continue; + //continue; } - - string name; - static if (isArray!MemberType) - { // Workaround. This code is never called with: not an array type, but compiler prints an error - name = Unqual!(ForeachType!MemberType).stringof; - } - - bool is_optional; - bool is_read_only; - - if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) + else { - is_read_only = true; - } - - foreach (att; __traits(getAttributes, __traits(getMember, - Sys.EntitiesData, member))) - { - if (att == "optional") - { - is_optional = true; + string name; + static if (isArray!MemberType) + { // Workaround. This code is never called with: not an array type, but compiler prints an error + name = Unqual!(ForeachType!MemberType).stringof; } - if (att == "readonly") + + bool is_optional; + bool is_read_only; + + if (is(CopyConstness!(ForeachType!(MemberType), int) == const(int))) { is_read_only = true; } - } - if (is_read_only) - { - components_info.addReadonly(CompInfo(member, name)); - } - else - { - components_info.addMutable(CompInfo(member, name)); - } - if (is_optional) - { - components_info.addOptional(CompInfo(member, name)); - } - else - { - components_info.addReq(CompInfo(member, name)); + + foreach (att; __traits(getAttributes, __traits(getMember, + Sys.EntitiesData, member))) + { + if (att == "optional") + { + is_optional = true; + } + if (att == "readonly") + { + is_read_only = true; + } + } + if (is_read_only) + { + components_info.addReadonly(CompInfo(member, name)); + } + else + { + components_info.addMutable(CompInfo(member, name)); + } + if (is_optional) + { + components_info.addOptional(CompInfo(member, name)); + } + else + { + components_info.addReq(CompInfo(member, name)); + } } } @@ -874,7 +878,7 @@ export struct EntityManager } } - static void fillInputData(ref Sys.EntitiesData input_data, EntityInfo* info, + static void fillInputData()(ref Sys.EntitiesData input_data, EntityInfo* info, EntitiesBlock* block, uint offset, uint entities_count, System* system) { //enum ComponentsIndices components_info = getComponentsInfo(); @@ -2863,7 +2867,11 @@ export struct EntityManager id_manager.optimize(); updateBlocks(); changeEntities(); + updateEvents(); + + id_manager.optimize(); + updateBlocks(); removeEntities(); event_manager.clearEvents(); } diff --git a/source/bubel/ecs/std.d b/source/bubel/ecs/std.d index 4cec197..6658639 100644 --- a/source/bubel/ecs/std.d +++ b/source/bubel/ecs/std.d @@ -113,6 +113,14 @@ else version(D_BetterC) alloca_pos += length; return ret; } + + version(GNU) + { + extern(C) void __gdc_personality_v0() + { + + } + } } else { diff --git a/source/bubel/ecs/vector.d b/source/bubel/ecs/vector.d index 413bbce..1ec8a93 100644 --- a/source/bubel/ecs/vector.d +++ b/source/bubel/ecs/vector.d @@ -55,7 +55,8 @@ public: /*foreach (ref el; array[0 .. used]) { destroy(el); }*/ - freeData(cast(void[]) array); + //freeData(cast(void[]) array); + freeData((cast(void*)array.ptr)[0 .. array.length * T.sizeof]); gVectorsDestroyed++; } array = null; @@ -78,7 +79,9 @@ public: } } else { foreach (ref el; array[newLength .. used]) { - destroy(el); + //destroy(el); + static if(__traits(hasMember, T, "__xdtor"))el.__xdtor(); + else static if(__traits(hasMember, T, "__dtor"))el.__dtor(); } } used = newLength; @@ -122,7 +125,8 @@ public: T* memory = cast(T*) malloc(newSize); memcpy(cast(void*) memory, cast(void*) oldArray.ptr, oldSize); array = memory[0 .. newNumOfElements]; - return cast(void[]) oldArray; + //return cast(void[]) oldArray; + return (cast(void*)oldArray.ptr)[0 .. oldArray.length * T.sizeof]; } export Vector!T copy()() { @@ -169,7 +173,9 @@ public: } export void remove(size_t elemNum) { - destroy(array[elemNum]); + //destroy(array[elemNum]); + static if(__traits(hasMember, T, "__xdtor"))array[elemNum].__xdtor(); + else static if(__traits(hasMember, T, "__dtor"))array[elemNum].__dtor(); //swap(array[elemNum], array[used - 1]); array[elemNum] = array[used - 1]; used--; diff --git a/tests/access_perf.d b/tests/access_perf.d index 141a2e6..36da0c0 100644 --- a/tests/access_perf.d +++ b/tests/access_perf.d @@ -6,7 +6,14 @@ import bubel.ecs.core; import bubel.ecs.manager; import bubel.ecs.entity; -import std.array : staticArray; +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; import core.stdc.stdio; diff --git a/tests/basic.d b/tests/basic.d index 10d79b8..3bf2814 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -5,7 +5,14 @@ import bubel.ecs.manager; import bubel.ecs.system; import bubel.ecs.attributes; -import std.array : staticArray; +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; struct CInt { diff --git a/tests/perf.d b/tests/perf.d index 135dc54..b6a2692 100644 --- a/tests/perf.d +++ b/tests/perf.d @@ -6,7 +6,14 @@ import bubel.ecs.core; import bubel.ecs.manager; import bubel.ecs.entity; -import std.array : staticArray; +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; import core.stdc.stdio; -- 2.47.2 From 6c3c803d1ea1e2c4f5916efd7da06fb27595382e Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 14 May 2020 22:20:41 +0200 Subject: [PATCH 22/37] SpaceShip demo update -fixed issue with firing lasers in multithreaded mode -fix: enies can't grab upgrade anymore -there is a change that enemy drop bonus upon death --- demos/source/demos/space_invaders.d | 124 ++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 15 deletions(-) diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index 1c5b3e4..bef65fe 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -16,7 +16,7 @@ import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; -import std.array : staticArray; +//import std.array : staticArray; enum float px = 1.0/512.0; @@ -252,6 +252,11 @@ struct EUpgrade mixin ECS.Event; } +struct EDeath +{ + mixin ECS.Event; +} + struct EDamage { mixin ECS.Event; @@ -499,32 +504,72 @@ struct LaserShootingSystem bool shoot = false; static float[18] laser_shoot_times = [500,400,300,200,100,50,25,10,5,2,1,0.8,0.6,0.5,0.4,0.3,0.2,0.1]; - CLocation* laser_location; + /*CLocation* laser_location; CVelocity* laser_velocity; - CGuild* laser_guild; + CGuild* laser_guild;*/ struct EntitiesData { ///variable named "length" contain entites count uint length; + ///variable named "length" contain thread identifier + uint thread_id; @readonly CShootDirection[] shoot_direction; @readonly @optional CAutoShoot[] auto_shoot; @readonly CLocation[] location; @readonly CGuild[] guild; CLaserWeapon[] laser; } + + struct ThreadData + { + EntityTemplate* laser_tmpl; + CLocation* laser_location; + CVelocity* laser_velocity; + CGuild* laser_guild; + } + + ThreadData[] threads; ///Called inside "registerSystem" function - /*void onCreate() + void onCreate() { - laser_location = space_invaders.laser_tmpl.getComponent!CLocation; - }*/ + threads = Mallocator.makeArray!ThreadData(32); + threads[0].laser_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CLaser.component_id, CGuild.component_id].staticArray); + + CTexture* tex_comp = threads[0].laser_tmpl.getComponent!CTexture; + tex_comp.tex = space_invaders.texture;//laser_tex; + tex_comp.coords = vec4(0*px,24*px,2*px,8*px); + CScale* scale_comp = threads[0].laser_tmpl.getComponent!CScale; + scale_comp.value = vec2(2,8); + threads[0].laser_location = threads[0].laser_tmpl.getComponent!CLocation; + threads[0].laser_velocity = threads[0].laser_tmpl.getComponent!CVelocity; + threads[0].laser_guild = threads[0].laser_tmpl.getComponent!CGuild; + + foreach(ref ThreadData thread;threads[1..$]) + { + thread.laser_tmpl = launcher.manager.allocateTemplate(threads[0].laser_tmpl); + thread.laser_location = thread.laser_tmpl.getComponent!CLocation; + thread.laser_velocity = thread.laser_tmpl.getComponent!CVelocity; + thread.laser_guild = thread.laser_tmpl.getComponent!CGuild; + } + //laser_location = space_invaders.laser_tmpl.getComponent!CLocation; + } + + void onDestroy() + { + foreach(ref ThreadData thread;threads[1..$]) + { + launcher.manager.freeTemplate(thread.laser_tmpl); + } + Mallocator.dispose(threads); + } bool onBegin() { - laser_location = space_invaders.laser_tmpl.getComponent!CLocation; + /*laser_location = space_invaders.laser_tmpl.getComponent!CLocation; laser_velocity = space_invaders.laser_tmpl.getComponent!CVelocity; - laser_guild = space_invaders.laser_tmpl.getComponent!CGuild; + laser_guild = space_invaders.laser_tmpl.getComponent!CGuild;*/ if(launcher.getKeyState(SDL_SCANCODE_SPACE)) { shoot = true; @@ -535,6 +580,7 @@ struct LaserShootingSystem void onUpdate(EntitiesData data) { + ThreadData* thread = &threads[data.thread_id]; //conditional branch for whole entities block if(shoot || data.auto_shoot) { @@ -545,10 +591,10 @@ struct LaserShootingSystem while(laser.shoot_time > laser_shoot_times[laser.level - 1]) { laser.shoot_time -= laser_shoot_times[laser.level - 1]; - laser_location.value = data.location[i]; - laser_velocity.value = vec2(randomf()*0.5-0.25,data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); - laser_guild.guild = data.guild[i].guild; - launcher.manager.addEntity(space_invaders.laser_tmpl); + thread.laser_location.value = data.location[i]; + thread.laser_velocity.value = vec2(randomf()*0.5-0.25,data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); + thread.laser_guild.guild = data.guild[i].guild; + launcher.manager.addEntity(thread.laser_tmpl); } } } @@ -617,8 +663,12 @@ struct UpgradeCollisionSystem { if(space_invaders.shoot_grid.test(id, data.location[i], cast(ubyte)(0xFF))) { - launcher.manager.sendEvent(id, EUpgrade()); - launcher.manager.removeEntity(data.entity[i].id); + Entity* entity = launcher.manager.getEntity(id); + if(entity.hasComponent(CShip.component_id)) + { + launcher.manager.sendEvent(id, EUpgrade()); + launcher.manager.removeEntity(data.entity[i].id); + } } } } @@ -676,6 +726,7 @@ struct ChangeDirectionSystem if(direction != cast(Direction)-1)//return true; { has_changes = true; +INFO: Uniform block alig break; } } @@ -833,19 +884,61 @@ struct HitPointsSystem { mixin ECS.System; + EntityTemplate* upgrade_tmpl; + CLocation* upgrade_location; + struct EntitiesData { CHitPoints[] hp; } + void onCreate() + { + upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id].staticArray); + CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; + tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(0*px,32*px,16*px,16*px); + CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; + vel_comp.value = vec2(0,-0.1); + upgrade_location = upgrade_tmpl.getComponent!CLocation; + } + + void onDestroy() + { + launcher.manager.freeTemplate(upgrade_tmpl); + } + void handleEvent(Entity* entity, EDamage event) { CHitPoints* hp = entity.getComponent!CHitPoints; + if(*hp < 0)return; *hp -= event.damage; - if(*hp < 0)launcher.manager.removeEntity(entity.id); + if(*hp < 0) + { + launcher.manager.sendEvent(entity.id, EDeath()); + //launcher.manager.removeEntity(entity.id); + } CHitMark* hit_mark = entity.getComponent!CHitMark; if(hit_mark)hit_mark.value = 127; } + + void handleEvent(Entity* entity, EDeath event) + { + CEnemy* enemy = entity.getComponent!CEnemy; + if(enemy) + { + if(randomRange(0, 1000) < 5) + { + CLocation* location = entity.getComponent!CLocation; + if(location) + { + *upgrade_location = *location; + launcher.manager.addEntity(upgrade_tmpl); + } + } + } + launcher.manager.removeEntity(entity.id); + } } struct ClampPositionSystem @@ -1076,6 +1169,7 @@ void spaceInvadersStart() launcher.manager.registerEvent!EChangeDirection; launcher.manager.registerEvent!EDamage; launcher.manager.registerEvent!EUpgrade; + launcher.manager.registerEvent!EDeath; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); -- 2.47.2 From 0b924ae77c8084d39b360f90b688a8461a04d043 Mon Sep 17 00:00:00 2001 From: Mergul Date: Tue, 19 May 2020 20:27:18 +0200 Subject: [PATCH 23/37] Demos update (SpaceInvaders demo) -added new sprites -added new functions to vectors -fixed rendering rotated sprites -small performance improvement and some bug fixes -added animations support -bullets get initial velocity from parent -added partiles for fire from gun and explosion of enemies --- demos/assets/textures/atlas.png | Bin 24668 -> 35204 bytes demos/dub.json | 3 + demos/source/app.d | 4 +- demos/source/demos/snake.d | 2 +- demos/source/demos/space_invaders.d | 362 ++++++++++++++++++-- demos/utils/source/ecs_utils/gfx/renderer.d | 18 + demos/utils/source/ecs_utils/math/vector.d | 56 +++ 7 files changed, 411 insertions(+), 34 deletions(-) diff --git a/demos/assets/textures/atlas.png b/demos/assets/textures/atlas.png index c628969c0e7149419eb62313fb5c3274457838f3..1e269e513e42c5dc797f568af05816d2562995f0 100644 GIT binary patch literal 35204 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
-U`yoWDlL!y69I7g$; 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 diff --git a/demos/dub.json b/demos/dub.json index 43d248c..1c1fe65 100644 --- a/demos/dub.json +++ b/demos/dub.json @@ -18,6 +18,9 @@ "libs-windows-x86_64": ["libs/windows/x64/SDL2","libs/windows/x64/SDL2_Image","libs/windows/x64/cimgui"], "libs-linux-x86_64": ["cimgui","SDL2","SDL2_image"], "lflags-linux-x86_64": ["-rpath=libs/linux/x64/","-Llibs/linux/x64/"], + "dflags-ldc" : [ + "--ffast-math" + ], "configurations" : [ { "name" : "default", diff --git a/demos/source/app.d b/demos/source/app.d index 10fc80e..3af24b5 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -262,7 +262,7 @@ void mainLoop(void* arg) if(launcher.tool && launcher.tool_repeat != 0 && launcher.mouse.left && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { float range = 500.0 / cast(float)launcher.tool_repeat; - launcher.repeat_time += launcher.delta_time; + launcher.repeat_time += launcher.delta_time * 100; while(launcher.repeat_time > range) { launcher.repeat_time -= range; @@ -716,7 +716,7 @@ int main(int argc, char** argv) launcher.job_updater = Mallocator.make!ECSJobUpdater(1); //launcher.job_updater.onCreate(); - EntityManager.initialize(32); + EntityManager.initialize(32, 1<<16); launcher.manager = EntityManager.instance; //launcher.manager.m_thread_id_func = &launcher.job_updater.getThreadID; diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 3c62ef6..50f7729 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -285,7 +285,7 @@ struct ParticleSystem { uint length; @readonly Entity[] entities; - @readonly CParticle[] particle; + CParticle[] particle; } void onUpdate(EntitiesData data) diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index bef65fe..56d891d 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -16,6 +16,8 @@ import ecs_utils.gfx.texture; import ecs_utils.math.vector; import ecs_utils.utils; +import std.math : PI; + //import std.array : staticArray; enum float px = 1.0/512.0; @@ -121,6 +123,16 @@ struct CScale vec2 value = vec2(16,16); } +struct CRotation +{ + mixin ECS.Component; + + ///use component as it value + alias value this; + + float value = 0; +} + struct CTexture { mixin ECS.Component; @@ -176,6 +188,17 @@ struct CLaserWeapon { mixin ECS.Component; + static struct Level + { + float reload_time; + float dispersion; + } + + __gshared Level[12] levels = [Level(4000,0),Level(4000,0.1), + Level(500,0),Level(350,0),Level(250,0.02),Level(175,0.03),Level(110,0.04), + Level(80,0.05),Level(50,0.08),Level(20,0.1),Level(10,0.12),Level(2,0.14)]; + + ubyte level = 1; float shoot_time = 0; } @@ -208,13 +231,30 @@ struct CShootGrid mixin ECS.Component; } +struct CTargetPartent +{ + mixin ECS.Component; + + EntityID parent; + vec2 rel_pos; +} + struct CHitPoints { mixin ECS.Component; alias value this; - int value = 10; + int value = 3; +} + +struct CMaxHitPoints +{ + mixin ECS.Component; + + alias value this; + + int value = 3; } struct CHitMark @@ -229,6 +269,47 @@ struct CHitMark struct CUpgrade { mixin ECS.Component; + + alias value this; + + enum Upgrade : ubyte + { + hit_points, + regeneration, + laser + } + + Upgrade value; +} + +struct CAnimation +{ + mixin ECS.Component; + + vec4[] frames; + float time = 0; + float speed = 1; +} + +struct CAnimationLooped +{ + mixin ECS.Component; +} + +struct CDamping +{ + mixin ECS.Component; + + alias value this; + + byte value = 0; +} + +struct CParticle +{ + mixin ECS.Component; + + float life = 0; } /*####################################################################################################################### @@ -436,6 +517,7 @@ struct DrawSystem @readonly CTexture[] textures; @readonly CLocation[] locations; @readonly CScale[] scale; + @readonly @optional CRotation[] rotation; @readonly @optional CDepth[] depth; @readonly @optional CHitMark[] hit_mark; } @@ -452,6 +534,13 @@ struct DrawSystem launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, color, 0, 0, 0, data.thread_id); } } + else if(data.rotation) + { + foreach(i; 0..data.length) + { + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)data.locations[i].y, 0x80808080, data.rotation[i], 0, 0, data.thread_id); + } + } else { foreach(i; 0..data.length) @@ -463,18 +552,36 @@ struct DrawSystem else { if(data.hit_mark) + { + if(data.rotation) + { + foreach(i; 0..data.length) + { + uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 4 + data.locations[i].y), color, data.rotation[i], 0, 0, data.thread_id); + } + } + else + { + foreach(i; 0..data.length) + { + uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 4 + data.locations[i].y), color, 0, 0, 0, data.thread_id); + } + } + } + else if(data.rotation) { foreach(i; 0..data.length) { - uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; - launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), color, 0, 0, 0, data.thread_id); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 4 + data.locations[i].y), 0x80808080, data.rotation[i], 0, 0, data.thread_id); } } else { foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 64 + data.locations[i].y), 0x80808080, 0, 0, 0, data.thread_id); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].value, data.scale[i], data.textures[i].coords, cast(short)(data.depth[i] * 4 + data.locations[i].y), 0x80808080, 0, 0, 0, data.thread_id); } } } @@ -502,12 +609,19 @@ struct LaserShootingSystem mixin ECS.System!32; bool shoot = false; - static float[18] laser_shoot_times = [500,400,300,200,100,50,25,10,5,2,1,0.8,0.6,0.5,0.4,0.3,0.2,0.1]; + //static float[18] laser_shoot_times = [500,400,300,200,100,50,25,10,5,2,1,0.8,0.6,0.5,0.4,0.3,0.2,0.1]; + //static float[18] laser_shoot_disp = [0,0,0,0,0.05,0.06,0.08,0.1,0.14,0.18,0.2,0.25,0.26,0.27,0.28,0.29,0.3,0.4]; + + __gshared vec4[] fire_frames = [vec4(96,64,8,16)*px,vec4(104,64,8,16)*px,vec4(112,64,8,16)*px,vec4(120,64,8,16)*px,vec4(128,64,8,16)*px, + vec4(136,64,8,16)*px,vec4(144,64,8,16)*px,vec4(152,64,8,16)*px,vec4(160,64,8,16)*px]; + + // __gshared vec4[] fire_frames = [vec4(0,160,8,16)*px,vec4(16,160,16,16)*px,vec4(32,160,16,16)*px,vec4(48,160,16,16)*px,vec4(64,160,16,16)*px, + // vec4(80,160,16,16)*px,vec4(96,160,16,16)*px,vec4(112,160,16,16)*px]; /*CLocation* laser_location; CVelocity* laser_velocity; CGuild* laser_guild;*/ - + struct EntitiesData { ///variable named "length" contain entites count @@ -518,6 +632,7 @@ struct LaserShootingSystem @readonly @optional CAutoShoot[] auto_shoot; @readonly CLocation[] location; @readonly CGuild[] guild; + @optional @readonly CVelocity[] velocity; CLaserWeapon[] laser; } @@ -527,6 +642,11 @@ struct LaserShootingSystem CLocation* laser_location; CVelocity* laser_velocity; CGuild* laser_guild; + + EntityTemplate* fire_tmpl; + CLocation* fire_location; + CVelocity* fire_velocity; + CRotation* fire_rotation; } ThreadData[] threads; @@ -535,7 +655,10 @@ struct LaserShootingSystem void onCreate() { threads = Mallocator.makeArray!ThreadData(32); - threads[0].laser_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CLaser.component_id, CGuild.component_id].staticArray); + threads[0].laser_tmpl = launcher.manager.allocateTemplate( + [CLocation.component_id, CTexture.component_id, CVelocity.component_id, + CScale.component_id, CLaser.component_id, CGuild.component_id].staticArray + ); CTexture* tex_comp = threads[0].laser_tmpl.getComponent!CTexture; tex_comp.tex = space_invaders.texture;//laser_tex; @@ -546,12 +669,33 @@ struct LaserShootingSystem threads[0].laser_velocity = threads[0].laser_tmpl.getComponent!CVelocity; threads[0].laser_guild = threads[0].laser_tmpl.getComponent!CGuild; + threads[0].fire_tmpl = launcher.manager.allocateTemplate( + [CLocation.component_id, CTexture.component_id, CScale.component_id, + CAnimation.component_id, CParticle.component_id, CRotation.component_id, + CVelocity.component_id, CDamping.component_id].staticArray + ); + + tex_comp = threads[0].fire_tmpl.getComponent!CTexture; + tex_comp.tex = space_invaders.texture;//laser_tex; + tex_comp.coords = vec4(96*px,64*px,8*px,16*px); + scale_comp = threads[0].fire_tmpl.getComponent!CScale; + scale_comp.value = vec2(8,16); + threads[0].fire_location = threads[0].fire_tmpl.getComponent!CLocation; + threads[0].fire_rotation = threads[0].fire_tmpl.getComponent!CRotation; + threads[0].fire_velocity = threads[0].fire_tmpl.getComponent!CVelocity; + threads[0].fire_tmpl.getComponent!(CParticle).life = 300; + *threads[0].fire_tmpl.getComponent!(CAnimation) = CAnimation(fire_frames, 0, 3); + foreach(ref ThreadData thread;threads[1..$]) { thread.laser_tmpl = launcher.manager.allocateTemplate(threads[0].laser_tmpl); thread.laser_location = thread.laser_tmpl.getComponent!CLocation; thread.laser_velocity = thread.laser_tmpl.getComponent!CVelocity; thread.laser_guild = thread.laser_tmpl.getComponent!CGuild; + thread.fire_tmpl = launcher.manager.allocateTemplate(threads[0].fire_tmpl); + thread.fire_location = thread.fire_tmpl.getComponent!CLocation; + thread.fire_rotation = thread.fire_tmpl.getComponent!CRotation; + thread.fire_velocity = thread.fire_tmpl.getComponent!CVelocity; } //laser_location = space_invaders.laser_tmpl.getComponent!CLocation; } @@ -588,13 +732,34 @@ struct LaserShootingSystem { CLaserWeapon* laser = &data.laser[i]; laser.shoot_time += launcher.delta_time; - while(laser.shoot_time > laser_shoot_times[laser.level - 1]) + while(laser.shoot_time > CLaserWeapon.levels[laser.level - 1].reload_time) { - laser.shoot_time -= laser_shoot_times[laser.level - 1]; + laser.shoot_time -= CLaserWeapon.levels[laser.level - 1].reload_time; thread.laser_location.value = data.location[i]; - thread.laser_velocity.value = vec2(randomf()*0.5-0.25,data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); + thread.laser_velocity.value = vec2((randomf()*2-1) * CLaserWeapon.levels[laser.level - 1].dispersion,data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); thread.laser_guild.guild = data.guild[i].guild; + + if(data.velocity) + { + thread.fire_velocity.value = data.velocity[i]; + //thread.laser_velocity.value += data.velocity[i] * 0.5; + } + else thread.fire_velocity.value = vec2(0,0); + launcher.manager.addEntity(thread.laser_tmpl); + + thread.fire_location.value = data.location[i]; + if(data.shoot_direction[i].direction == Direction.down) + { + thread.fire_rotation.value = PI; + thread.fire_location.value.y -= 16; + } + else + { + thread.fire_rotation.value = 0; + thread.fire_location.value.y += 24; + } + launcher.manager.addEntity(thread.fire_tmpl); } } } @@ -604,13 +769,47 @@ struct LaserShootingSystem { CLaserWeapon* laser = &data.laser[i]; laser.shoot_time += launcher.delta_time; - if(laser.shoot_time > laser_shoot_times[laser.level - 1])laser.shoot_time = laser_shoot_times[laser.level - 1]; + if(laser.shoot_time > CLaserWeapon.levels[laser.level - 1].reload_time)laser.shoot_time = CLaserWeapon.levels[laser.level - 1].reload_time; } } } } +struct DampingSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + const (Entity)[] entity; + @readonly CDamping[] damping; + CVelocity[] velocity; + } + + float[10] damp = 0; + + bool onBegin() + { + import core.stdc.math : powf; + foreach(i;0..10) + { + damp[i] = powf((0.98 - cast(float)i * 0.02),launcher.delta_time*0.1); + } + + return true; + } + + void onUpdate(EntitiesData data) + { + foreach(i; 0..data.length) + { + data.velocity[i] = data.velocity[i] * damp[data.damping[i]]; + } + } +} + struct LaserCollisionSystem { mixin ECS.System!32; @@ -689,7 +888,7 @@ struct UpgradeSystem CLaserWeapon* laser = entity.getComponent!CLaserWeapon; if(laser) { - if(laser.level < LaserShootingSystem.laser_shoot_times.length)laser.level++; + if(laser.level < CLaserWeapon.levels.length)laser.level++; } } } @@ -884,9 +1083,15 @@ struct HitPointsSystem { mixin ECS.System; + __gshared vec4[] upgrade_laser_frames = [vec4(96,80,16,16)*px,vec4(112,80,16,16)*px,vec4(128,80,16,16)*px,vec4(144,80,16,16)*px,vec4(128,80,16,16)*px,vec4(112,80,16,16)*px]; + __gshared vec4[] explosion_laser_frames = [vec4(80,128,16,16)*px,vec4(96,128,16,16)*px,vec4(112,128,16,16)*px,vec4(128,128,16,16)*px,vec4(144,128,16,16)*px,vec4(160,128,16,16)*px,vec4(176,128,16,16)*px,vec4(192,128,16,16)*px,vec4(208,128,16,16)*px]; + EntityTemplate* upgrade_tmpl; CLocation* upgrade_location; + EntityTemplate* explosion_tmpl; + CLocation* explosion_location; + struct EntitiesData { CHitPoints[] hp; @@ -894,13 +1099,21 @@ struct HitPointsSystem void onCreate() { - upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id].staticArray); + upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimation.component_id, CAnimationLooped.component_id].staticArray); CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; tex_comp.tex = space_invaders.texture;//ship_tex; tex_comp.coords = vec4(0*px,32*px,16*px,16*px); + *upgrade_tmpl.getComponent!CAnimation = CAnimation(upgrade_laser_frames, 0, 1); CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0,-0.1); upgrade_location = upgrade_tmpl.getComponent!CLocation; + + explosion_tmpl = launcher.manager.allocateTemplate([CDepth.component_id, CParticle.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CAnimation.component_id].staticArray); + explosion_tmpl.getComponent!(CTexture).tex = space_invaders.texture; + *explosion_tmpl.getComponent!CAnimation = CAnimation(explosion_laser_frames, 0, 1.333); + explosion_tmpl.getComponent!(CParticle).life = 600; + *explosion_tmpl.getComponent!CDepth = -1; + explosion_location = explosion_tmpl.getComponent!CLocation; } void onDestroy() @@ -911,9 +1124,9 @@ struct HitPointsSystem void handleEvent(Entity* entity, EDamage event) { CHitPoints* hp = entity.getComponent!CHitPoints; - if(*hp < 0)return; + if(*hp <= 0)return; *hp -= event.damage; - if(*hp < 0) + if(*hp <= 0) { launcher.manager.sendEvent(entity.id, EDeath()); //launcher.manager.removeEntity(entity.id); @@ -927,14 +1140,16 @@ struct HitPointsSystem CEnemy* enemy = entity.getComponent!CEnemy; if(enemy) { - if(randomRange(0, 1000) < 5) + CLocation* location = entity.getComponent!CLocation; + if(location) { - CLocation* location = entity.getComponent!CLocation; - if(location) + if(randomRange(0, 1000) < 5) { *upgrade_location = *location; launcher.manager.addEntity(upgrade_tmpl); } + *explosion_location = *location; + launcher.manager.addEntity(explosion_tmpl); } } launcher.manager.removeEntity(entity.id); @@ -1031,7 +1246,7 @@ struct MovementSystem //@optional const (CLaser)[] laser; const (Entity)[] entities; - @optional CSideMove[] side_move; + //@optional CSideMove[] side_move; } void onUpdate(EntitiesData data) @@ -1044,6 +1259,63 @@ struct MovementSystem } } +struct AnimationSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + CAnimation[] animation; + CTexture[] texture; + @optional @readonly CAnimationLooped[] looped; + } + + void onUpdate(EntitiesData data) + { + float dt = launcher.delta_time * 0.01; + if(data.looped) + { + foreach(i;0..data.length) + { + data.animation[i].time += dt * data.animation[i].speed; + while(data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time -= cast(float)data.animation[i].frames.length; + data.texture[i].coords = data.animation[i].frames[cast(int)(data.animation[i].time)]; + } + } + else + { + foreach(i;0..data.length) + { + data.animation[i].time += dt * data.animation[i].speed; + if(data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time = data.animation[i].frames.length - 0.1; + data.texture[i].coords = data.animation[i].frames[cast(int)(data.animation[i].time)]; + } + } + + } +} + +struct ParticleSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + @readonly Entity[] entitiy; + CParticle[] particle; + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + data.particle[i].life -= launcher.delta_time; + if(data.particle[i].life < 0)launcher.manager.removeEntity(data.entitiy[i].id); + } + } +} extern(C) float sqrtf(float x) @nogc nothrow @system; @@ -1063,7 +1335,8 @@ struct InputMovementSystem //read only components can be marked with @readonly attribute or with const expression instead const (CInput)[] input; //components are treated as required by default - CLocation[] locations; + //CLocation[] locations; + CVelocity[] velocity; CTexture[] textures; } @@ -1108,22 +1381,33 @@ struct InputMovementSystem */ void onUpdate(EntitiesData data) { - if(move_vector.x == 0) + /*if(move_vector.x == 0) { foreach(i; 0..data.length) { data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); } //return; - } + }*/ //move every entity using movement vector + //if(move_vector.x != 0 || move_vector.y != 0) foreach(i; 0..data.length) { - data.locations[i].x += move_vector.x * launcher.delta_time * 0.25; - data.locations[i].y += move_vector.y * launcher.delta_time * 0.25; - if(move_vector.x > 0)data.textures[i].coords = vec4(48*px,80*px,48*px,32*px); - else data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); + data.velocity[i] += move_vector * launcher.delta_time * 0.005; + if(data.velocity[i].x > 0.5)data.velocity[i].x = 0.5; + else if(data.velocity[i].x < -0.5)data.velocity[i].x = -0.5; + if(data.velocity[i].y > 0.5)data.velocity[i].y = 0.5; + else if(data.velocity[i].y < -0.5)data.velocity[i].y = -0.5; + //data.locations[i].x += move_vector.x * launcher.delta_time * 0.25; + //data.locations[i].y += move_vector.y * launcher.delta_time * 0.25; + //if(move_vector.x > 0)data.textures[i].coords = vec4(48*px,80*px,48*px,32*px); + //else data.textures[i].coords = vec4(0*px,80*px,48*px,32*px); } + /*else + foreach(i; 0..data.length) + { + data.velocity[i] = vec2(0,0); + }*/ } } @@ -1165,6 +1449,12 @@ void spaceInvadersStart() launcher.manager.registerComponent!CHitPoints; launcher.manager.registerComponent!CHitMark; launcher.manager.registerComponent!CUpgrade; + launcher.manager.registerComponent!CParticle; + launcher.manager.registerComponent!CMaxHitPoints; + launcher.manager.registerComponent!CAnimation; + launcher.manager.registerComponent!CRotation; + launcher.manager.registerComponent!CAnimationLooped; + launcher.manager.registerComponent!CDamping; launcher.manager.registerEvent!EChangeDirection; launcher.manager.registerEvent!EDamage; @@ -1185,6 +1475,9 @@ void spaceInvadersStart() launcher.manager.registerSystem!HitMarkingSystem(-100); launcher.manager.registerSystem!UpgradeCollisionSystem(-70); launcher.manager.registerSystem!UpgradeSystem(-100); + launcher.manager.registerSystem!ParticleSystem(-100); + launcher.manager.registerSystem!AnimationSystem(-100); + launcher.manager.registerSystem!DampingSystem(-101); launcher.manager.endRegister(); @@ -1197,8 +1490,13 @@ void spaceInvadersStart() //launcher.manager.getSystem(CleanSystem.system_id).disable(); { - ushort[11] components = [CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, CTexture.component_id, CInput.component_id, CShip.component_id, CScale.component_id, CLaserWeapon.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; - space_invaders.ship_tmpl = launcher.manager.allocateTemplate(components); + space_invaders.ship_tmpl = launcher.manager.allocateTemplate( + [CVelocity.component_id, CHitMark.component_id, CHitPoints.component_id, + CLocation.component_id, CTexture.component_id, CInput.component_id, + CShip.component_id, CScale.component_id, CLaserWeapon.component_id, + CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id, + CDamping.component_id].staticArray + ); CScale* scale_comp = space_invaders.ship_tmpl.getComponent!CScale; scale_comp.value = vec2(48,32); @@ -1208,8 +1506,9 @@ void spaceInvadersStart() CLocation* loc_comp = space_invaders.ship_tmpl.getComponent!CLocation; loc_comp.value = vec2(64,64); CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; - weapon.level = 1; + weapon.level = 3; space_invaders.ship_tmpl.getComponent!CHitPoints().value = 1000; + space_invaders.ship_tmpl.getComponent!CDamping().value = 7; launcher.manager.addEntity(space_invaders.ship_tmpl); } @@ -1273,12 +1572,13 @@ void spaceInvadersStart() EntityTemplate* upgrade_tmpl; { - upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id].staticArray); + upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimationLooped.component_id, CAnimation.component_id].staticArray); CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; tex_comp.tex = space_invaders.texture;//ship_tex; tex_comp.coords = vec4(0*px,32*px,16*px,16*px); CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0,-0.1); + *upgrade_tmpl.getComponent!CAnimation = CAnimation(HitPointsSystem.upgrade_laser_frames, 0, 0.75); } launcher.manager.commit(); @@ -1326,7 +1626,7 @@ void spaceInvadersTool(vec2 position, Tool tool, int size) CLaserWeapon* laser_weapon = tmpl.getComponent!CLaserWeapon; if(laser_weapon) { - laser_weapon.shoot_time = randomf * LaserShootingSystem.laser_shoot_times[laser_weapon.level - 1]; + laser_weapon.shoot_time = randomf * CLaserWeapon.levels[laser_weapon.level - 1].reload_time; } launcher.manager.addEntity(tmpl); } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 206364f..95f8aa6 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -535,12 +535,30 @@ struct Renderer verts[item_id*28] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[0] * coss + GfxConfig.meshes[mesh_id].vertices[1] * sinn) * size.x + pos.x) * 8191); verts[item_id*28+1] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[1] * coss - GfxConfig.meshes[mesh_id].vertices[0] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); + verts[item_id*28+3] = cast(short)((GfxConfig.meshes[mesh_id].vertices[3] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+4,mem.ptr,6); + + verts[item_id*28+7] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[4] * coss + GfxConfig.meshes[mesh_id].vertices[5] * sinn) * size.x + pos.x) * 8191); verts[item_id*28+8] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[5] * coss - GfxConfig.meshes[mesh_id].vertices[4] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+9] = cast(short)((GfxConfig.meshes[mesh_id].vertices[6] * coords.z + coords.x)*32767); + verts[item_id*28+10] = cast(short)((GfxConfig.meshes[mesh_id].vertices[7] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+11,mem.ptr,6); + + verts[item_id*28+14] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[8] * coss + GfxConfig.meshes[mesh_id].vertices[9] * sinn) * size.x + pos.x) * 8191); verts[item_id*28+15] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[9] * coss - GfxConfig.meshes[mesh_id].vertices[8] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+16] = cast(short)((GfxConfig.meshes[mesh_id].vertices[10] * coords.z + coords.x)*32767); + verts[item_id*28+17] = cast(short)((GfxConfig.meshes[mesh_id].vertices[11] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+18,mem.ptr,6); + + verts[item_id*28+21] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[12] * coss + GfxConfig.meshes[mesh_id].vertices[13] * sinn) * size.x + pos.x) * 8191); verts[item_id*28+22] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[13] * coss - GfxConfig.meshes[mesh_id].vertices[12] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+23] = cast(short)((GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x)*32767); + verts[item_id*28+24] = cast(short)((GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y)*32767); + memcpy(verts.ptr+item_id*28+25,mem.ptr,6); } /*verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); diff --git a/demos/utils/source/ecs_utils/math/vector.d b/demos/utils/source/ecs_utils/math/vector.d index a74b6db..a8f14f8 100644 --- a/demos/utils/source/ecs_utils/math/vector.d +++ b/demos/utils/source/ecs_utils/math/vector.d @@ -2,6 +2,18 @@ module ecs_utils.math.vector; struct vec2 { + this(float v) @nogc nothrow + { + x = v; + y = v; + } + + this(float x, float y) @nogc nothrow + { + this.x = x; + this.y = y; + } + union { struct @@ -75,6 +87,22 @@ struct vec4 float[4] data; } + this(float v) @nogc nothrow + { + x = v; + y = v; + z = v; + w = v; + } + + this(float x, float y, float z, float w) @nogc nothrow + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + vec4 opBinary(string op)(float v) { static if (op == "+") return vec4(x + v, y + v, z + v, w + v); @@ -96,6 +124,18 @@ struct ivec2 } int[2] data; } + + this(int v) @nogc nothrow + { + x = v; + y = v; + } + + this(int x, int y) @nogc nothrow + { + this.x = x; + this.y = y; + } ivec2 opBinary(string op, T)(T v) { @@ -125,4 +165,20 @@ struct ivec4 } int[4] data; } + + this(int v) @nogc nothrow + { + x = v; + y = v; + z = v; + w = v; + } + + this(int x, int y, int z, int w) @nogc nothrow + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } } \ No newline at end of file -- 2.47.2 From abc4d0c509765bf3d3f66cf1ad466f95bafa81ec Mon Sep 17 00:00:00 2001 From: Mergul Date: Fri, 22 May 2020 15:40:53 +0200 Subject: [PATCH 24/37] Modified codecov.yml --- codecov.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/codecov.yml b/codecov.yml index f3e0d9b..5672706 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,6 +7,4 @@ coverage: project: default: threshold: 5 - patch: - default: - threshold: 5 \ No newline at end of file + patch: off \ No newline at end of file -- 2.47.2 From d89df28f85309bdd5ccad7ffed828973b6636c6c Mon Sep 17 00:00:00 2001 From: Mergul Date: Fri, 22 May 2020 15:41:28 +0200 Subject: [PATCH 25/37] Fixed rendering of rotated entities --- demos/utils/source/ecs_utils/gfx/renderer.d | 28 ++++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 95f8aa6..1b46eaf 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -472,8 +472,7 @@ struct Renderer { //if(item_id >= MaxObjects)return; //pos += view_pos; - size.x *= view_size.x; - size.y *= view_size.y; + pos.x = pos.x * view_size.x + view_pos.x; pos.y = pos.y * view_size.y + view_pos.y;//*/ @@ -491,6 +490,9 @@ struct Renderer if(angle == 0) { + size.x *= view_size.x; + size.y *= view_size.y; + verts[item_id*28] = cast(short)((GfxConfig.meshes[mesh_id].vertices[0] * size.x + pos.x) * 8191); verts[item_id*28+1] = cast(short)((GfxConfig.meshes[mesh_id].vertices[1] * size.y + pos.y) * 8191); verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); @@ -521,8 +523,10 @@ struct Renderer else { //import core.stdc.math; - float sinn = sinf(angle); - float coss = cosf(angle); + float sinx = sinf(angle) * size.x * view_size.y; + float cosx = cosf(angle) * size.x * view_size.x; + float siny = sinf(angle) * size.y * view_size.x; + float cosy = cosf(angle) * size.y * view_size.y; /*batch_vertices[item_id*28] = GfxConfig.meshes[mesh_id].vertices[0] * size.x; batch_vertices[item_id*28+1] = GfxConfig.meshes[mesh_id].vertices[1] * size.y; @@ -533,29 +537,29 @@ struct Renderer batch_vertices[item_id*28+12] = GfxConfig.meshes[mesh_id].vertices[12] * size.x; batch_vertices[item_id*28+13] = GfxConfig.meshes[mesh_id].vertices[13] * size.y;*/ - verts[item_id*28] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[0] * coss + GfxConfig.meshes[mesh_id].vertices[1] * sinn) * size.x + pos.x) * 8191); - verts[item_id*28+1] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[1] * coss - GfxConfig.meshes[mesh_id].vertices[0] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[0] * cosx + GfxConfig.meshes[mesh_id].vertices[1] * siny) + pos.x) * 8191); + verts[item_id*28+1] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[1] * cosy - GfxConfig.meshes[mesh_id].vertices[0] * sinx) + pos.y) * 8191); verts[item_id*28+2] = cast(short)((GfxConfig.meshes[mesh_id].vertices[2] * coords.z + coords.x)*32767); verts[item_id*28+3] = cast(short)((GfxConfig.meshes[mesh_id].vertices[3] * coords.w + coords.y)*32767); memcpy(verts.ptr+item_id*28+4,mem.ptr,6); - verts[item_id*28+7] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[4] * coss + GfxConfig.meshes[mesh_id].vertices[5] * sinn) * size.x + pos.x) * 8191); - verts[item_id*28+8] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[5] * coss - GfxConfig.meshes[mesh_id].vertices[4] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+7] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[4] * cosx + GfxConfig.meshes[mesh_id].vertices[5] * siny) + pos.x) * 8191); + verts[item_id*28+8] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[5] * cosy - GfxConfig.meshes[mesh_id].vertices[4] * sinx) + pos.y) * 8191); verts[item_id*28+9] = cast(short)((GfxConfig.meshes[mesh_id].vertices[6] * coords.z + coords.x)*32767); verts[item_id*28+10] = cast(short)((GfxConfig.meshes[mesh_id].vertices[7] * coords.w + coords.y)*32767); memcpy(verts.ptr+item_id*28+11,mem.ptr,6); - verts[item_id*28+14] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[8] * coss + GfxConfig.meshes[mesh_id].vertices[9] * sinn) * size.x + pos.x) * 8191); - verts[item_id*28+15] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[9] * coss - GfxConfig.meshes[mesh_id].vertices[8] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+14] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[8] * cosx + GfxConfig.meshes[mesh_id].vertices[9] * siny) + pos.x) * 8191); + verts[item_id*28+15] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[9] * cosy - GfxConfig.meshes[mesh_id].vertices[8] * sinx) + pos.y) * 8191); verts[item_id*28+16] = cast(short)((GfxConfig.meshes[mesh_id].vertices[10] * coords.z + coords.x)*32767); verts[item_id*28+17] = cast(short)((GfxConfig.meshes[mesh_id].vertices[11] * coords.w + coords.y)*32767); memcpy(verts.ptr+item_id*28+18,mem.ptr,6); - verts[item_id*28+21] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[12] * coss + GfxConfig.meshes[mesh_id].vertices[13] * sinn) * size.x + pos.x) * 8191); - verts[item_id*28+22] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[13] * coss - GfxConfig.meshes[mesh_id].vertices[12] * sinn) * size.y + pos.y) * 8191); + verts[item_id*28+21] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[12] * cosx + GfxConfig.meshes[mesh_id].vertices[13] * siny) + pos.x) * 8191); + verts[item_id*28+22] = cast(short)(((GfxConfig.meshes[mesh_id].vertices[13] * cosy - GfxConfig.meshes[mesh_id].vertices[12] * sinx) + pos.y) * 8191); verts[item_id*28+23] = cast(short)((GfxConfig.meshes[mesh_id].vertices[14] * coords.z + coords.x)*32767); verts[item_id*28+24] = cast(short)((GfxConfig.meshes[mesh_id].vertices[15] * coords.w + coords.y)*32767); memcpy(verts.ptr+item_id*28+25,mem.ptr,6); -- 2.47.2 From 43f8755a39da1d795a01dc916365973ffeef4f26 Mon Sep 17 00:00:00 2001 From: Mergul Date: Fri, 22 May 2020 15:44:31 +0200 Subject: [PATCH 26/37] Fixed ECS bug related to adding/removing entities inside onAdd/onRemove entity callback Now whole committing process is called in specific order: - UpdateBlocks - ChangeEntities - RemoveEntities - HandleEvents Whole process is repeated until there will be no more changes to commit --- source/bubel/ecs/manager.d | 178 +++++++++++++++++++++++++------------ 1 file changed, 123 insertions(+), 55 deletions(-) diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index 7b6a983..dc3b9f5 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -2074,10 +2074,10 @@ export struct EntityManager { ThreadData* data = &threads[threadID]; uint num = cast(uint) del_ids.length; - data.change_entities_list.add(0); - data.change_entities_list.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); - data.change_entities_list.add((cast(ubyte*)&num)[0 .. uint.sizeof]); - data.change_entities_list.add((cast(ubyte*) del_ids.ptr)[0 .. num * 2]); + data.changeEntitiesList.add(0); + data.changeEntitiesList.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); + data.changeEntitiesList.add((cast(ubyte*)&num)[0 .. uint.sizeof]); + data.changeEntitiesList.add((cast(ubyte*) del_ids.ptr)[0 .. num * 2]); } private void __removeComponents(EntityID entity_id, ushort[] del_ids) @@ -2349,13 +2349,13 @@ export struct EntityManager } ThreadData* data = &threads[threadID]; - data.change_entities_list.add(cast(ubyte) 1u); - data.change_entities_list.add((cast(ubyte*)&entity_id)[0 .. EntityID.sizeof]); - data.change_entities_list.add((cast(ubyte*)&num)[0 .. uint.sizeof]); - data.change_entities_list.add(cast(ubyte[]) new_ids); + 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.change_entities_list.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); + data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); } //__addComponents(entity_id, new_ids, pointers); @@ -2416,7 +2416,7 @@ export struct EntityManager } if (new_index == 1) - threads[threadID].blocks_to_update.add(new_block); + threads[threadID].blockToUpdate.add(new_block); Entity* new_entity = cast(Entity*) start; //add_mutex.lock_nothrow(); @@ -2466,7 +2466,7 @@ export struct EntityManager } if (index == 1) - threads[threadID].blocks_to_update.add(block); + threads[threadID].blockToUpdate.add(block); Entity* entity = cast(Entity*) start; //add_mutex.lock_nothrow(); @@ -2557,7 +2557,7 @@ export struct EntityManager */ export void removeEntity(EntityID id) { - threads[threadID].entities_to_remove.add(id); + threads[threadID].entitesToRemove.add(id); } private void __removeEntity(EntityID id) nothrow @nogc @@ -2646,47 +2646,51 @@ export struct EntityManager return cast(EntitiesBlock*)(cast(size_t) pointer & (~cast(size_t)(m_page_size - 1))); } - private void changeEntities() + private bool changeEntities() { - foreach (ref thread; threads) + bool has_work = false; + //foreach (ref ThreadData thread; threads)thread.swapToChange(); + foreach (ref ThreadData thread; threads) { uint index = 0; - uint len = cast(uint) thread.change_entities_list.length; + uint len = cast(uint) thread.changeEntitiesListPrev.length; + if(len)has_work = true; void*[32] pointers; // = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; while (index < len) { - if (!thread.change_entities_list[index++]) + if (!thread.changeEntitiesListPrev[index++]) { - EntityID id = *cast(EntityID*)&thread.change_entities_list[index]; + EntityID id = *cast(EntityID*)&thread.changeEntitiesListPrev[index]; index += EntityID.sizeof; - uint num = *cast(uint*)&thread.change_entities_list[index]; + uint num = *cast(uint*)&thread.changeEntitiesListPrev[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; - ids = (cast(ushort*)&thread.change_entities_list[index])[0 .. num]; + ids = (cast(ushort*)&thread.changeEntitiesListPrev[index])[0 .. num]; index += ushort.sizeof * num; __removeComponents(id, ids); } else { - EntityID id = *cast(EntityID*)&thread.change_entities_list[index]; + EntityID id = *cast(EntityID*)&thread.changeEntitiesListPrev[index]; index += EntityID.sizeof; - uint num = *cast(uint*)&thread.change_entities_list[index]; + uint num = *cast(uint*)&thread.changeEntitiesListPrev[index]; index += uint.sizeof; ushort[] ids; // = (cast(ushort*) alloca(num * ushort.sizeof))[0 .. num]; - ids = (cast(ushort*)&thread.change_entities_list[index])[0 .. num]; + ids = (cast(ushort*)&thread.changeEntitiesListPrev[index])[0 .. num]; index += ushort.sizeof * num; //void*[] pointers = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; foreach (i; 0 .. num) { - pointers[i] = &thread.change_entities_list[index]; + pointers[i] = &thread.changeEntitiesListPrev[index]; index += components[ids[i]].size; } __addComponents(id, ids, pointers[0 .. num]); } } - thread.change_entities_list.clear(); + thread.changeEntitiesListPrev.clear(); } + return has_work; } private void callAddEntityListeners(EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow @@ -2767,11 +2771,15 @@ export struct EntityManager (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_change_entity)(data); } - private void updateBlocks() + private bool updateBlocks() { - foreach (ref thread; threads) + bool has_work = false; + //foreach (ref ThreadData thread; threads)thread.swapToUpdate(); + foreach (ref ThreadData thread; threads) { - foreach (block; thread.blocks_to_update) + //thread.swapToUpdate(); + if(thread.blockToUpdatePrev.length)has_work = true; + foreach (block; thread.blockToUpdatePrev) { EntityInfo* info = block.type_info; ushort entities_count = block.entities_count; @@ -2786,30 +2794,35 @@ export struct EntityManager { callAddEntityListeners(info, block, entities_count, block.entities_count); } - } - thread.blocks_to_update.clear(); + thread.blockToUpdatePrev.clear(); } + return has_work; } - private void removeEntities() nothrow @nogc + private bool removeEntities() nothrow @nogc { - foreach (i, ref thread; threads) + bool has_work = false; + //foreach (ref ThreadData thread; threads)thread.swapToRemove(); + foreach (ref ThreadData thread; threads) { - foreach (id; thread.entities_to_remove) + if(thread.entitiesToRemovePrev.length)has_work = true; + foreach (id; thread.entitiesToRemovePrev) { __removeEntity(id); } - thread.entities_to_remove.clear(); + thread.entitiesToRemovePrev.clear(); } + return has_work; } - private void updateEvents() nothrow @nogc + private bool updateEvents() nothrow @nogc { - bool empty = true; - while (1) - { - event_manager.swapCurrent(); + bool has_work = false; + // bool empty = true; + //while (1) + //{ + //event_manager.swapCurrent(); uint current_index; if (event_manager.current_index == 0) current_index = cast(uint) threads.length; @@ -2821,8 +2834,11 @@ export struct EntityManager .. current_index + threads.length]) { EventManager.EventBlock* block = first_block; - if (block) - empty = false; + if (block)has_work = true; + // { + // has_work = true; + // //empty = false; + // } while (block) { EventCallData call_data; @@ -2856,23 +2872,37 @@ export struct EntityManager } } } - if (empty) - break; - empty = true; + // if (empty) + // break; + // empty = true; + //} + return has_work; + } + + private void swapData() nothrow @nogc + { + event_manager.swapCurrent(); + foreach(ref ThreadData thread; threads) + { + thread.swapData(); } } export void commit() { - id_manager.optimize(); - updateBlocks(); - changeEntities(); - - updateEvents(); + bool has_work = true; + while(has_work) + { + swapData(); - id_manager.optimize(); - updateBlocks(); - removeEntities(); + has_work = false; + id_manager.optimize(); + has_work |= updateBlocks(); + has_work |= changeEntities(); + has_work |= removeEntities(); + + has_work |= updateEvents(); + } event_manager.clearEvents(); } @@ -3445,10 +3475,48 @@ export struct EntityManager struct ThreadData { - Vector!EntityID entities_to_remove; - //Vector!ubyte change_entities_list; - SimpleVector change_entities_list; - Vector!(EntitiesBlock*) blocks_to_update; + ref Vector!EntityID entitesToRemove() @nogc nothrow + { + return entities_to_remove[data_index]; + } + + ref SimpleVector changeEntitiesList() @nogc nothrow + { + return change_entities_list[data_index]; + } + + ref Vector!(EntitiesBlock*) blockToUpdate() @nogc nothrow + { + return blocks_to_update[data_index]; + } + + ref Vector!EntityID entitiesToRemovePrev() @nogc nothrow + { + return entities_to_remove[1 - data_index]; + } + + ref SimpleVector changeEntitiesListPrev() @nogc nothrow + { + return change_entities_list[1 - data_index]; + } + + ref Vector!(EntitiesBlock*) blockToUpdatePrev() @nogc nothrow + { + return blocks_to_update[1 - data_index]; + } + + private: + + void swapData() @nogc nothrow + { + data_index = cast(ubyte)(1 - data_index); + } + + Vector!EntityID[2] entities_to_remove; + SimpleVector[2] change_entities_list; + Vector!(EntitiesBlock*)[2] blocks_to_update; + + ubyte data_index = 0; } export struct UpdatePass -- 2.47.2 From bfc0da47e495e7d7e62910d8a95b3c115d01c65b Mon Sep 17 00:00:00 2001 From: Mergul Date: Fri, 22 May 2020 15:58:35 +0200 Subject: [PATCH 27/37] SpaceShip demo updage -Update sprites texture atlas -added support for separate collider and graphical scale -weapon now can be located on specific sprite relative position -SpaceShip has two lasers which are separate entities -added following parent system -added Boss -parent destroy children after being destroyed -added towers (Tower consist of 3 entities, parent take damage and one is weapon) - added targeting system which rotate entity to target --- demos/assets/textures/atlas.png | Bin 35204 -> 41948 bytes demos/source/demos/space_invaders.d | 869 ++++++++++++++++++++++++---- 2 files changed, 772 insertions(+), 97 deletions(-) diff --git a/demos/assets/textures/atlas.png b/demos/assets/textures/atlas.png index 1e269e513e42c5dc797f568af05816d2562995f0..a799eb7706feddc336f6f1e6e75f780dcbea5467 100644 GIT binary patch literal 41948 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 literal 35204 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

-U`yoWDlL!y69I7g$; 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 space_invaders.map_size.x) + { + if(data.side_move[i].group == -1) + { + if(data.velocity[i].x > 0)data.velocity[i].x = -data.velocity[i].x; + } + else + { + groups_directions[data.side_move[i].group] = Direction.left; + } + } + } + } else { foreach(i;0..data.length) @@ -1024,38 +1472,9 @@ INFO: Uniform block alig groups_directions[data.side_move[i].group] = Direction.left; } } - //if(data.locations[i].y < 0) data.locations[i].y = 0; - //else if(data.locations[i].y > space_invaders.map_size.y)data.locations[i].y = space_invaders.map_size.y; } } } - - /*void handleEvent(Entity* entity, EChangeDirection event) - { - CSideMove* side_move = entity.getComponent!CSideMove; - if(side_move && side_move.group != -1) - { - groups_directions[side_move.group] = event.direction; - return; - } - //Entity* entity = launcher.manager.getEntity(event.entity_id); - CVelocity* velocity = entity.getComponent!CVelocity; - final switch(event.direction) - { - case Direction.up: - if(velocity.value.y > 0)velocity.value.y = -velocity.value.y; - break; - case Direction.down: - if(velocity.value.y < 0)velocity.value.y = -velocity.value.y; - break; - case Direction.left: - if(velocity.value.x > 0)velocity.value.x = -velocity.value.x; - break; - case Direction.right: - if(velocity.value.x < 0)velocity.value.x = -velocity.value.x; - break; - } - }*/ } struct HitMarkingSystem @@ -1101,7 +1520,7 @@ struct HitPointsSystem { upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimation.component_id, CAnimationLooped.component_id].staticArray); CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.texture;//ship_tex; + //tex_comp.tex = space_invaders.texture;//ship_tex; tex_comp.coords = vec4(0*px,32*px,16*px,16*px); *upgrade_tmpl.getComponent!CAnimation = CAnimation(upgrade_laser_frames, 0, 1); CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; @@ -1109,7 +1528,7 @@ struct HitPointsSystem upgrade_location = upgrade_tmpl.getComponent!CLocation; explosion_tmpl = launcher.manager.allocateTemplate([CDepth.component_id, CParticle.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CAnimation.component_id].staticArray); - explosion_tmpl.getComponent!(CTexture).tex = space_invaders.texture; + //explosion_tmpl.getComponent!(CTexture).tex = space_invaders.texture; *explosion_tmpl.getComponent!CAnimation = CAnimation(explosion_laser_frames, 0, 1.333); explosion_tmpl.getComponent!(CParticle).life = 600; *explosion_tmpl.getComponent!CDepth = -1; @@ -1156,6 +1575,47 @@ struct HitPointsSystem } } +struct ChildDestroySystem +{ + mixin ECS.System; + + struct EntitiesData + { + CTargetPartent[] parent; + } + + void handleEvent(Entity* entity, EDeath event) + { + CTargetPartent* parent = entity.getComponent!CTargetPartent; + if(parent) + { + launcher.manager.sendEvent(parent.parent, EDestroyedChild(entity.id)); + } + } +} + +struct PartsDestroySystem +{ + mixin ECS.System; + + struct EntitiesData + { + CInit[] init; + CChildren[] children; + CParts[] parts; + } + + void handleEvent(Entity* entity, EDestroyedChild event) + { + CParts* parts = entity.getComponent!CParts; + parts.count--; + if(parts.count == 0) + { + launcher.manager.addComponents(entity.id, CHitPoints(100), CShootGrid()); + } + } +} + struct ClampPositionSystem { mixin ECS.System!32; @@ -1168,7 +1628,10 @@ struct ClampPositionSystem //components are treated as required by default CLocation[] locations; + @optional @readonly CColliderScale[] collider_scale; + @optional @readonly CScale[] scale; @optional const (CLaser)[] laser; + @optional const (CUpgrade)[] upgrade; //@optional CVelocity[] velocity; //@optional const (CSideMove)[] side_move; } @@ -1177,7 +1640,7 @@ struct ClampPositionSystem void onUpdate(EntitiesData data) { - if(data.laser) + if(data.laser || data.upgrade) { foreach(i;0..data.length) { @@ -1219,6 +1682,28 @@ struct ClampPositionSystem else if(data.locations[i].y > space_invaders.map_size.y)data.locations[i].y = space_invaders.map_size.y; } }*/ + else if(data.collider_scale) + { + foreach(i;0..data.length) + { + vec2 hscale = data.collider_scale[i] * 0.5; + if(data.locations[i].x - hscale.x < 0)data.locations[i].x = hscale.x; + else if(data.locations[i].x + hscale.x > space_invaders.map_size.x)data.locations[i].x = space_invaders.map_size.x - hscale.x; + if(data.locations[i].y - hscale.y < 0)data.locations[i].y = hscale.y; + else if(data.locations[i].y + hscale.y > space_invaders.map_size.y)data.locations[i].y = space_invaders.map_size.y - hscale.y; + } + } + else if(data.scale) + { + foreach(i;0..data.length) + { + vec2 hscale = data.scale[i] * 0.5; + if(data.locations[i].x - hscale.x < 0)data.locations[i].x = hscale.x; + else if(data.locations[i].x + hscale.x > space_invaders.map_size.x)data.locations[i].x = space_invaders.map_size.x - hscale.x; + if(data.locations[i].y - hscale.y < 0)data.locations[i].y = hscale.y; + else if(data.locations[i].y + hscale.y > space_invaders.map_size.y)data.locations[i].y = space_invaders.map_size.y - hscale.y; + } + } else { foreach(i;0..data.length) @@ -1317,7 +1802,116 @@ struct ParticleSystem } } +struct RotateToTargetSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + int length; + @readonly CTarget[] target; + @readonly CLocation[] location; + CRotation[] rotation; + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + Entity* target = launcher.manager.getEntity(data.target[i].target); + if(target) + { + CLocation* target_loc = target.getComponent!CLocation; + if(target_loc) + { + vec2 rel_pos = target_loc.value - data.location[i]; + float length = sqrtf(rel_pos.x*rel_pos.x + rel_pos.y*rel_pos.y); + if(rel_pos.x > 0)data.rotation[i] = acosf(rel_pos.y/length); + else data.rotation[i] = 2 * PI - acosf(rel_pos.y/length); + + } + } + //CLocation* target_loc = + //vec2 rel_pos = d + //data.rotation = 0; + } + } +} + +struct ShipTargetSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + int length; + @readonly CTargetPlayerShip[] target_player; + CTarget[] target; + } + + EntityID player_ship; + + void iterateShips(CShipIterator.EntitiesData data) + { + player_ship = data.entity[0].id; + } + + void onAddEntity(EntitiesData data) + { + foreach(i;0..data.length) + { + data.target[i].target = player_ship; + } + } + + bool onBegin() + { + Entity* ship = launcher.manager.getEntity(player_ship); + if(ship is null) + { + launcher.manager.callEntitiesFunction!CShipIterator(&iterateShips); + ship = launcher.manager.getEntity(player_ship); + if(ship is null)return false; + return true; + } + return false; + } + + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length) + { + data.target[i].target = player_ship; + } + } +} + +struct CShipIterator +{ + mixin ECS.System!1; + + struct EntitiesData + { + @readonly Entity[] entity; + @readonly CShip[] ship; + } + + bool onBegin() + { + return false; + } + + void onUpdate(EntitiesData data) + { + + } +} + extern(C) float sqrtf(float x) @nogc nothrow @system; +extern(C) float acosf(float x) @nogc nothrow @system; +extern(C) float sinf(float x) @nogc nothrow @system; +extern(C) float cosf(float x) @nogc nothrow @system; +extern(C) float powf(float x, float y) @nogc nothrow @system; /** *System is responsible for movement of objects with CInput component. @@ -1455,11 +2049,23 @@ void spaceInvadersStart() launcher.manager.registerComponent!CRotation; launcher.manager.registerComponent!CAnimationLooped; launcher.manager.registerComponent!CDamping; + launcher.manager.registerComponent!CTargetPartent; + launcher.manager.registerComponent!CTarget; + launcher.manager.registerComponent!CTargetPlayerShip; + launcher.manager.registerComponent!CChildren; + launcher.manager.registerComponent!CWeaponLocation; + launcher.manager.registerComponent!CInit; + launcher.manager.registerComponent!CBoss; + launcher.manager.registerComponent!CParts; + launcher.manager.registerComponent!CColliderScale; + launcher.manager.registerComponent!CParticleEmitter; + launcher.manager.registerComponent!CParticleEmitterTime; launcher.manager.registerEvent!EChangeDirection; launcher.manager.registerEvent!EDamage; launcher.manager.registerEvent!EUpgrade; launcher.manager.registerEvent!EDeath; + launcher.manager.registerEvent!EDestroyedChild; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); @@ -1478,7 +2084,16 @@ void spaceInvadersStart() launcher.manager.registerSystem!ParticleSystem(-100); launcher.manager.registerSystem!AnimationSystem(-100); launcher.manager.registerSystem!DampingSystem(-101); - + launcher.manager.registerSystem!MoveToParentTargetSystem(99); + launcher.manager.registerSystem!ParentOwnerSystem(-101); + launcher.manager.registerSystem!ShipWeaponSystem(-100); + + launcher.manager.registerSystem!RotateToTargetSystem(-100); + launcher.manager.registerSystem!ShipTargetSystem(-110); + launcher.manager.registerSystem!CShipIterator(-100); + launcher.manager.registerSystem!PartsDestroySystem(-80); + launcher.manager.registerSystem!ChildDestroySystem(-110); + launcher.manager.endRegister(); launcher.gui_manager.addSystem(DrawSystem.system_id,"Draw System"); @@ -1487,28 +2102,38 @@ void spaceInvadersStart() launcher.gui_manager.addSystem(MovementSystem.system_id,"Movement System"); launcher.gui_manager.addSystem(ClampPositionSystem.system_id,"Clamp Position System"); launcher.gui_manager.addSystem(ChangeDirectionSystem.system_id,"Change Direction System"); + launcher.gui_manager.addSystem(LaserCollisionSystem.system_id,"Draw System"); + launcher.gui_manager.addSystem(ShootGridManager.system_id,"Shoot Grid Manager"); + launcher.gui_manager.addSystem(ShootGridCleaner.system_id,"Shoot Grid Cleaner"); + launcher.gui_manager.addSystem(HitPointsSystem.system_id,"Hit Points System"); + launcher.gui_manager.addSystem(HitMarkingSystem.system_id,"Hit Matking System"); + launcher.gui_manager.addSystem(UpgradeCollisionSystem.system_id,"Upgrade Collision System"); + launcher.gui_manager.addSystem(UpgradeSystem.system_id,"Upgrade System"); + launcher.gui_manager.addSystem(ParticleSystem.system_id,"Particle System"); + launcher.gui_manager.addSystem(AnimationSystem.system_id,"Animation System"); + launcher.gui_manager.addSystem(DampingSystem.system_id,"Damping System"); + launcher.gui_manager.addSystem(MoveToParentTargetSystem.system_id,"Move To Target System"); + launcher.gui_manager.addSystem(ParentOwnerSystem.system_id,"Parent Owner System System"); + launcher.gui_manager.addSystem(ShipWeaponSystem.system_id,"Ship Weapon System"); //launcher.manager.getSystem(CleanSystem.system_id).disable(); { space_invaders.ship_tmpl = launcher.manager.allocateTemplate( [CVelocity.component_id, CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, CTexture.component_id, CInput.component_id, - CShip.component_id, CScale.component_id, CLaserWeapon.component_id, + CShip.component_id, CScale.component_id, CColliderScale.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id, - CDamping.component_id].staticArray + CDamping.component_id, CChildren.component_id, CInit.component_id].staticArray ); - - CScale* scale_comp = space_invaders.ship_tmpl.getComponent!CScale; - scale_comp.value = vec2(48,32); - CTexture* tex_comp = space_invaders.ship_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(0*px,80*px,48*px,32*px); - CLocation* loc_comp = space_invaders.ship_tmpl.getComponent!CLocation; - loc_comp.value = vec2(64,64); - CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; - weapon.level = 3; + //CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; + //weapon.level = 3; + space_invaders.ship_tmpl.getComponent!CTexture().coords = vec4(0*px,80*px,48*px,32*px); + space_invaders.ship_tmpl.getComponent!CScale().value = vec2(48,32); + space_invaders.ship_tmpl.getComponent!CLocation().value = vec2(64,64); space_invaders.ship_tmpl.getComponent!CHitPoints().value = 1000; space_invaders.ship_tmpl.getComponent!CDamping().value = 7; + space_invaders.ship_tmpl.getComponent!CInit().type = CInit.Type.space_ship; + space_invaders.ship_tmpl.getComponent!CColliderScale().value = vec2(26,24); launcher.manager.addEntity(space_invaders.ship_tmpl); } @@ -1518,7 +2143,7 @@ void spaceInvadersStart() space_invaders.laser_tmpl = launcher.manager.allocateTemplate(components); CTexture* tex_comp = space_invaders.laser_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.texture;//laser_tex; + //tex_comp.tex = 0;//space_invaders.texture;//laser_tex; tex_comp.coords = vec4(0*px,24*px,2*px,8*px); CScale* scale_comp = space_invaders.laser_tmpl.getComponent!CScale; scale_comp.value = vec2(2,8); @@ -1528,15 +2153,63 @@ void spaceInvadersStart() EntityTemplate* enemy_tmpl; EntityTemplate* grouped_tmpl; + EntityTemplate* tower_tmpl; + EntityTemplate* boss_tmpl; + //EntityTemplate* tower_weapon_tmpl; EntityID enemy_id; EntityID grouped_id; { - ushort[12] components = [CHitMark.component_id, CHitPoints.component_id, CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id]; - space_invaders.enemy_tmpl = launcher.manager.allocateTemplate(components); + boss_tmpl = launcher.manager.allocateTemplate( + [CHitMark.component_id, CParts.component_id, CLocation.component_id, + CTexture.component_id, CScale.component_id, CEnemy.component_id, + CBoss.component_id, CGuild.component_id, CInit.component_id, + CChildren.component_id, CSideMove.component_id, CVelocity.component_id, + CDepth.component_id].staticArray + ); + + CTexture* tex_comp = boss_tmpl.getComponent!CTexture; + //tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(128*px,0*px,96*px,48*px); + CLocation* loc_comp = boss_tmpl.getComponent!CLocation; + loc_comp.value = vec2(64,space_invaders.map_size.y - 16); + boss_tmpl.getComponent!CGuild().guild = 1; + boss_tmpl.getComponent!CInit().type = CInit.Type.boss; + boss_tmpl.getComponent!CScale().value = vec2(96,48); + boss_tmpl.getComponent!CDepth().depth = -1; + boss_tmpl.getComponent!CParts().count = 4; + boss_tmpl.getComponent!CVelocity().value = vec2(0.05,0); + } + + { + tower_tmpl = launcher.manager.allocateTemplate( + [CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, + CTexture.component_id, CScale.component_id, CEnemy.component_id, + CShootGrid.component_id, CGuild.component_id, CInit.component_id, + CChildren.component_id].staticArray + ); + + CTexture* tex_comp = tower_tmpl.getComponent!CTexture; + //tex_comp.tex = space_invaders.texture;//ship_tex; + tex_comp.coords = vec4(96*px,96*px,16*px,16*px); + CLocation* loc_comp = tower_tmpl.getComponent!CLocation; + loc_comp.value = vec2(64,space_invaders.map_size.y - 16); + tower_tmpl.getComponent!CGuild().guild = 1; + tower_tmpl.getComponent!CInit().type = CInit.Type.tower; + tower_tmpl.getComponent!CHitPoints().value = 10; + } + + { + space_invaders.enemy_tmpl = launcher.manager.allocateTemplate( + [CWeaponLocation.component_id, CHitMark.component_id, CHitPoints.component_id, + CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, + CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, + CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, + CGuild.component_id].staticArray + ); CTexture* tex_comp = space_invaders.enemy_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.texture;//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 - 16); @@ -1545,6 +2218,7 @@ void spaceInvadersStart() CVelocity* vel_comp = space_invaders.enemy_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0.1,0); space_invaders.enemy_tmpl.getComponent!CGuild().guild = 1; + space_invaders.enemy_tmpl.getComponent!CWeaponLocation().rel_pos = vec2(0,-15); Entity* current_entity; @@ -1568,13 +2242,13 @@ void spaceInvadersStart() grouped_id = current_entity.id; //grouped_tmpl = launcher.manager.allocateTemplate(current_entity.id); } - + EntityTemplate* upgrade_tmpl; { upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimationLooped.component_id, CAnimation.component_id].staticArray); CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; - tex_comp.tex = space_invaders.texture;//ship_tex; + //tex_comp.tex = space_invaders.texture;//ship_tex; tex_comp.coords = vec4(0*px,32*px,16*px,16*px); CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; vel_comp.value = vec2(0,-0.1); @@ -1586,22 +2260,23 @@ void spaceInvadersStart() enemy_tmpl = launcher.manager.allocateTemplate(enemy_id); grouped_tmpl = launcher.manager.allocateTemplate(grouped_id); - launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.ship_tmpl),"Ship"); launcher.gui_manager.addTemplate(enemy_tmpl,"Enemy"); launcher.gui_manager.addTemplate(grouped_tmpl,"Grouped enemy"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.ship_tmpl),"Ship"); launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.laser_tmpl),"Laser"); launcher.gui_manager.addTemplate(upgrade_tmpl,"Upgrade"); - + launcher.gui_manager.addTemplate(tower_tmpl,"Tower"); + launcher.gui_manager.addTemplate(boss_tmpl,"Boss"); } void spaceInvadersEnd() { - launcher.manager.getSystem(DrawSystem.system_id).disable(); + /*launcher.manager.getSystem(DrawSystem.system_id).disable(); launcher.manager.getSystem(InputMovementSystem.system_id).disable(); launcher.manager.getSystem(LaserShootingSystem.system_id).disable(); launcher.manager.getSystem(MovementSystem.system_id).disable(); launcher.manager.getSystem(ClampPositionSystem.system_id).disable(); - launcher.manager.getSystem(ShootGridCleaner.system_id).disable(); + launcher.manager.getSystem(ShootGridCleaner.system_id).disable();*/ //launcher.manager.freeTemplate(space_invaders.enemy_tmpl); Mallocator.dispose(space_invaders); -- 2.47.2 From 233f4abd47a89dccc4f86289dfedebf117f045aa Mon Sep 17 00:00:00 2001 From: Mergul Date: Sat, 23 May 2020 10:53:36 +0200 Subject: [PATCH 28/37] Added job_id parameter to EntitiesData (index of currently executing job) --- source/bubel/ecs/manager.d | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index dc3b9f5..d5892bb 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -469,7 +469,7 @@ export struct EntityManager foreach (member; __traits(allMembers, Sys.EntitiesData)) { alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); - if (member == "length" || member == "thread_id" + if (member == "length" || member == "thread_id" || member == "job_id" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { //continue; @@ -692,7 +692,7 @@ export struct EntityManager foreach (member; __traits(allMembers, Sys.EntitiesData)) { alias MemberType = typeof(__traits(getMember, Sys.EntitiesData, member)); - if (member == "length" || member == "thread_id" + if (member == "length" || member == "thread_id" || member == "job_id" || is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) { if (is(MemberType == Entity[]) || is(MemberType == const(Entity)[])) @@ -809,6 +809,13 @@ export struct EntityManager static assert(MemberType.sizeof > 1, "EntitiesData 'thread_id' member can't be byte or ubyte."); } + else static if (member == "job_id") + { + static assert(isIntegral!(MemberType), + "EntitiesData 'job_id' member must be integral type."); + static assert(MemberType.sizeof > 1, + "EntitiesData 'job_id' member can't be byte or ubyte."); + } else static if (!(isArray!(MemberType))) static assert(0, "EntitiesData members should be arrays of elements!"); } @@ -1009,6 +1016,12 @@ export struct EntityManager .thread_id; } + static if (hasMember!(Sys.EntitiesData, "job_id")) + { + input_data.job_id = cast(typeof(input_data.job_id)) data + .job_id; + } + //s.onUpdate(input_data); (cast(typeof(&__traits(getOverloads, s, "onUpdate")[OnUpdateOverloadNum])) data.update_delegate)(input_data); @@ -1037,6 +1050,12 @@ export struct EntityManager input_data.thread_id = cast(typeof(input_data.thread_id)) data.thread_id; } + static if (hasMember!(Sys.EntitiesData, "job_id")) + { + input_data.job_id = cast(typeof(input_data.job_id)) data + .job_id; + } + (cast(typeof(&__traits(getOverloads, s, "onUpdate")[OnUpdateOverloadNum])) data.update_delegate)(input_data); } @@ -1406,6 +1425,7 @@ export struct EntityManager memcpy(callers.ptr, &tmp_datas[0], CallData.sizeof * tmp_datas.length); tmp_datas.clear(); sys.jobs[job_id].callers = callers; + sys.jobs[job_id].id = job_id; job_id++; } @@ -3415,6 +3435,8 @@ export struct EntityManager ushort end; ///current thread index uint thread_id; + //current job index + uint job_id; } struct ListenerCallData @@ -3428,6 +3450,7 @@ export struct EntityManager struct Job { CallData[] callers; + uint id; export void execute() nothrow @nogc { @@ -3435,6 +3458,7 @@ export struct EntityManager foreach (ref caller; callers) { caller.thread_id = EntityManager.instance.threadID(); + caller.job_id = id; caller.update(); } } -- 2.47.2 From 3d98b0ee5eeac61a71cff1015e4d8cc59a652761 Mon Sep 17 00:00:00 2001 From: Mergul Date: Sat, 23 May 2020 10:55:31 +0200 Subject: [PATCH 29/37] Demos update -fixed critical bug with demos switching -change multithreaded rendering method (now draw order is keeped even witch multithreading there is no popping) -added particle emitter components and systems (WIP) -bullets (laser) now sending EBullet event insead of EDamage which gives possibility to not destroy bullet if shooted entity is already killed --- demos/source/app.d | 6 +- demos/source/demos/simple.d | 6 +- demos/source/demos/space_invaders.d | 319 +++++++++++++++----- demos/utils/source/ecs_utils/gfx/renderer.d | 40 ++- 4 files changed, 284 insertions(+), 87 deletions(-) diff --git a/demos/source/app.d b/demos/source/app.d index 3af24b5..9017f8d 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -103,7 +103,7 @@ struct Launcher manager.update("clean"); manager.end(); - foreach(system; manager.systems) + foreach(ref system; manager.systems) { if(system.id != CountSystem.system_id && system.id != CleanSystem.system_id)system.disable(); } @@ -744,7 +744,9 @@ int main(int argc, char** argv) { import demos.simple; - launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); + import demos.space_invaders; + launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,&spaceInvadersTool,SpaceInvaders.tips); + // launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); } int key_num; diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 1668323..5e82d27 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -53,6 +53,7 @@ struct DrawSystem { uint length; uint thread_id; + uint job_id; @readonly CTexture[] textures; @readonly CLocation[] locations; } @@ -62,10 +63,11 @@ struct DrawSystem if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached foreach(i; 0..data.length) { - launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y*64+data.locations[i].x), 0x80808080, 0, 0, 0, data.thread_id); + launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), cast(ushort)(data.locations[i].y), 0x80808080, 0, 0, 0, data.job_id); + // launcher.renderer.draw(data.textures[i].tex, data.locations[i].location, vec2(16,16), vec4(0,0,1,1), 0, 0x80808080, 0, 0, 0, data.job_id); //draw(renderer, data.textures[i].tex, data.locations[i], vec2(32,32), vec4(0,0,1,1)); } - if(data.thread_id == 0)launcher.renderer.pushData(); + //if(data.thread_id == 0)launcher.renderer.pushData(); } } diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index f61df5c..c047d25 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -196,10 +196,10 @@ struct CLaser { mixin ECS.Component; - float damage = 1.0f; + int damage = 1; } -struct CLaserWeapon +struct CWeapon { mixin ECS.Component; @@ -207,15 +207,25 @@ struct CLaserWeapon { float reload_time; float dispersion; + int damage; } __gshared Level[12] levels = [Level(4000,0),Level(4000,0.1), Level(500,0),Level(350,0),Level(250,0.02),Level(175,0.03),Level(110,0.04), Level(80,0.05),Level(50,0.08),Level(20,0.1),Level(10,0.12),Level(2,0.14)]; + enum Type : ubyte + { + laser, + enemy_laser, + blaster, + canon, + plasma + } - ubyte level = 1; float shoot_time = 0; + Type type; + ubyte level = 1; } struct CWeaponLocation @@ -253,7 +263,7 @@ struct CShootGrid mixin ECS.Component; } -struct CTargetPartent +struct CTargetParent { mixin ECS.Component; @@ -386,6 +396,10 @@ struct CParticleEmitter vec2 range; vec2 time_range; + ///due to multithreading there should be separate template for every thread. + ///It can be array of tempaltes or (like in this demo) simply index of template; + uint tmpl_id; + //EntityTemplate* tmpl; } ///Due to perfarmance reason emitter time and attributes are divided into seprate components. @@ -397,6 +411,11 @@ struct CParticleEmitterTime float time; } +struct CShootWaveUponDeath +{ + mixin ECS.Component; +} + /*####################################################################################################################### ------------------------------------------------ Events ------------------------------------------------------------------ #######################################################################################################################*/ @@ -435,6 +454,20 @@ struct EDamage uint damage = 0; } +struct EBulletHit +{ + mixin ECS.Event; + + this(EntityID id, uint damage) + { + this.id = id; + this.damage = damage; + } + + EntityID id; + uint damage; +} + struct EDestroyedChild { mixin ECS.Event; @@ -652,9 +685,9 @@ struct ShipWeaponSystem CChildren* children = entity.getComponent!CChildren; if(children is null || children.childern.length != 0)return; EntityID[3] weapons; - laser1_tmpl.getComponent!CTargetPartent().parent = entity.id; - laser2_tmpl.getComponent!CTargetPartent().parent = entity.id; - main_weapon_tmpl.getComponent!CTargetPartent().parent = entity.id; + laser1_tmpl.getComponent!CTargetParent().parent = entity.id; + laser2_tmpl.getComponent!CTargetParent().parent = entity.id; + main_weapon_tmpl.getComponent!CTargetParent().parent = entity.id; weapons[0] = launcher.manager.addEntity(laser1_tmpl).id; weapons[1] = launcher.manager.addEntity(laser2_tmpl).id; weapons[2] = launcher.manager.addEntity(main_weapon_tmpl).id; @@ -663,13 +696,13 @@ struct ShipWeaponSystem void create() { - laser1_tmpl = launcher.manager.allocateTemplate([CLaserWeapon.component_id, CLocation.component_id, CShootDirection.component_id, CTargetPartent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); - main_weapon_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CShootDirection.component_id, CTargetPartent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); - *laser1_tmpl.getComponent!CLaserWeapon = CLaserWeapon(3,0.0); - laser1_tmpl.getComponent!CTargetPartent().rel_pos = vec2(10,13); - main_weapon_tmpl.getComponent!CTargetPartent().rel_pos = vec2(0,4); + laser1_tmpl = launcher.manager.allocateTemplate([CWeapon.component_id, CLocation.component_id, CShootDirection.component_id, CTargetParent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); + main_weapon_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CShootDirection.component_id, CTargetParent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); + *laser1_tmpl.getComponent!CWeapon = CWeapon(0,CWeapon.Type.laser,3); + laser1_tmpl.getComponent!CTargetParent().rel_pos = vec2(10,13); + main_weapon_tmpl.getComponent!CTargetParent().rel_pos = vec2(0,4); laser2_tmpl = launcher.manager.allocateTemplate(laser1_tmpl); - laser2_tmpl.getComponent!CTargetPartent().rel_pos = vec2(-10,13); + laser2_tmpl.getComponent!CTargetParent().rel_pos = vec2(-10,13); } ~this() @@ -691,10 +724,10 @@ struct ShipWeaponSystem if(children is null || children.childern.length != 0)return; CDepth* depth = entity.getComponent!CDepth; EntityID[2] weapons; - weapon_tmpl.getComponent!CTargetPartent().parent = entity.id; + weapon_tmpl.getComponent!CTargetParent().parent = entity.id; if(depth)weapon_tmpl.getComponent!CDepth().depth = cast(short)(depth.depth - 1); else weapon_tmpl.getComponent!CDepth().depth = -1; - top_tmpl.getComponent!CTargetPartent().parent = entity.id; + top_tmpl.getComponent!CTargetParent().parent = entity.id; if(depth)top_tmpl.getComponent!CDepth().depth = cast(short)(depth.depth - 2); else top_tmpl.getComponent!CDepth().depth = -2; @@ -706,24 +739,24 @@ struct ShipWeaponSystem void create() { weapon_tmpl = launcher.manager.allocateTemplate( - [CLaserWeapon.component_id, CLocation.component_id, CShootDirection.component_id, - CTargetPartent.component_id, CGuild.component_id, CVelocity.component_id, + [CWeapon.component_id, CLocation.component_id, CShootDirection.component_id, + CTargetParent.component_id, CGuild.component_id, CVelocity.component_id, CAutoShoot.component_id, CTarget.component_id, CTargetPlayerShip.component_id, CRotation.component_id, CScale.component_id, CTexture.component_id, CDepth.component_id, CWeaponLocation.component_id].staticArray); - *weapon_tmpl.getComponent!CLaserWeapon = CLaserWeapon(3,0.0); - weapon_tmpl.getComponent!CTargetPartent().rel_pos = vec2(0,0); + *weapon_tmpl.getComponent!CWeapon = CWeapon(0,CWeapon.Type.laser,3); + weapon_tmpl.getComponent!CTargetParent().rel_pos = vec2(0,0); weapon_tmpl.getComponent!CGuild().guild = 1; weapon_tmpl.getComponent!CScale().value = vec2(4,16); - weapon_tmpl.getComponent!CLaserWeapon().level = 1; + weapon_tmpl.getComponent!CWeapon().level = 1; weapon_tmpl.getComponent!CDepth().depth = -1; weapon_tmpl.getComponent!CTexture().coords = vec4(136,96,4,16)*px; weapon_tmpl.getComponent!CWeaponLocation().rel_pos = vec2(0,12); top_tmpl = launcher.manager.allocateTemplate( - [CLocation.component_id, CTargetPartent.component_id, CScale.component_id, + [CLocation.component_id, CTargetParent.component_id, CScale.component_id, CTexture.component_id, CDepth.component_id].staticArray); - top_tmpl.getComponent!CTargetPartent().rel_pos = vec2(0,1); + top_tmpl.getComponent!CTargetParent().rel_pos = vec2(0,1); top_tmpl.getComponent!CScale().value = vec2(10,11); top_tmpl.getComponent!CDepth().depth = -2; top_tmpl.getComponent!CTexture().coords = vec4(112,96,10,11)*px; @@ -751,10 +784,10 @@ struct ShipWeaponSystem CParts* parts = entity.getComponent!CParts; if(parts)parts.count = 4; EntityID[4] towers; - tower1_tmpl.getComponent!CTargetPartent().parent = entity.id; - tower2_tmpl.getComponent!CTargetPartent().parent = entity.id; - tower3_tmpl.getComponent!CTargetPartent().parent = entity.id; - tower4_tmpl.getComponent!CTargetPartent().parent = entity.id; + tower1_tmpl.getComponent!CTargetParent().parent = entity.id; + tower2_tmpl.getComponent!CTargetParent().parent = entity.id; + tower3_tmpl.getComponent!CTargetParent().parent = entity.id; + tower4_tmpl.getComponent!CTargetParent().parent = entity.id; towers[0] = launcher.manager.addEntity(tower1_tmpl).id; towers[1] = launcher.manager.addEntity(tower2_tmpl).id; towers[2] = launcher.manager.addEntity(tower3_tmpl).id; @@ -768,7 +801,7 @@ struct ShipWeaponSystem [CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CEnemy.component_id, CShootGrid.component_id, CGuild.component_id, CInit.component_id, - CChildren.component_id, CDepth.component_id, CTargetPartent.component_id].staticArray + CChildren.component_id, CDepth.component_id, CTargetParent.component_id].staticArray ); CTexture* tex_comp = tower1_tmpl.getComponent!CTexture; @@ -779,17 +812,19 @@ struct ShipWeaponSystem tower1_tmpl.getComponent!CGuild().guild = 1; tower1_tmpl.getComponent!CInit().type = CInit.Type.tower; tower1_tmpl.getComponent!CHitPoints().value = 10; - tower1_tmpl.getComponent!CDepth().depth = -5; - tower1_tmpl.getComponent!CTargetPartent().rel_pos = vec2(-33,2); + tower1_tmpl.getComponent!CDepth().depth = -2; + tower1_tmpl.getComponent!CTargetParent().rel_pos = vec2(-33,2); tower2_tmpl = launcher.manager.allocateTemplate(tower1_tmpl); - tower2_tmpl.getComponent!CTargetPartent().rel_pos = vec2(33,2); + tower2_tmpl.getComponent!CTargetParent().rel_pos = vec2(33,2); tower3_tmpl = launcher.manager.allocateTemplate(tower1_tmpl); - tower3_tmpl.getComponent!CTargetPartent().rel_pos = vec2(-39,-15); + tower3_tmpl.getComponent!CDepth().depth = 0; + tower3_tmpl.getComponent!CTargetParent().rel_pos = vec2(-40,-15); tower4_tmpl = launcher.manager.allocateTemplate(tower1_tmpl); - tower4_tmpl.getComponent!CTargetPartent().rel_pos = vec2(39,-15); + tower4_tmpl.getComponent!CDepth().depth = 0; + tower4_tmpl.getComponent!CTargetParent().rel_pos = vec2(40,-15); } ~this() @@ -810,13 +845,13 @@ struct ShipWeaponSystem ship.create(); tower.create(); boss.create(); - /*ship.laser1_tmpl = launcher.manager.allocateTemplate([CLaserWeapon.component_id, CLocation.component_id, CShootDirection.component_id, CTargetPartent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); - ship.main_weapon_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CShootDirection.component_id, CTargetPartent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); - *ship.laser1_tmpl.getComponent!CLaserWeapon = CLaserWeapon(3,0.0); - ship.laser1_tmpl.getComponent!CTargetPartent().rel_pos = vec2(10,13); - ship.main_weapon_tmpl.getComponent!CTargetPartent().rel_pos = vec2(0,4); + /*ship.laser1_tmpl = launcher.manager.allocateTemplate([CWeapon.component_id, CLocation.component_id, CShootDirection.component_id, CTargetParent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); + ship.main_weapon_tmpl = launcher.manager.allocateTemplate([CLocation.component_id, CShootDirection.component_id, CTargetParent.component_id, CGuild.component_id, CVelocity.component_id].staticArray); + *ship.laser1_tmpl.getComponent!CWeapon = CWeapon(3,0.0); + ship.laser1_tmpl.getComponent!CTargetParent().rel_pos = vec2(10,13); + ship.main_weapon_tmpl.getComponent!CTargetParent().rel_pos = vec2(0,4); ship.laser2_tmpl = launcher.manager.allocateTemplate(ship.laser1_tmpl); - ship.laser2_tmpl.getComponent!CTargetPartent().rel_pos = vec2(-10,13);*/ + ship.laser2_tmpl.getComponent!CTargetParent().rel_pos = vec2(-10,13);*/ } void onDestroy() @@ -832,9 +867,9 @@ struct ShipWeaponSystem { /*if(data.children[i].childern.length != 0)continue; EntityID[3] weapons; - laser1_tmpl.getComponent!CTargetPartent().parent = data.entity[i].id; - laser2_tmpl.getComponent!CTargetPartent().parent = data.entity[i].id; - main_weapon_tmpl.getComponent!CTargetPartent().parent = data.entity[i].id; + laser1_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; + laser2_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; + main_weapon_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; weapons[0] = launcher.manager.addEntity(laser1_tmpl).id; weapons[1] = launcher.manager.addEntity(laser2_tmpl).id; weapons[2] = launcher.manager.addEntity(main_weapon_tmpl).id; @@ -859,7 +894,7 @@ struct MoveToParentTargetSystem int length; CLocation[] location; @optional CVelocity[] velocity; - @readonly CTargetPartent[] target; + @readonly CTargetParent[] target; } void onUpdate(EntitiesData data) @@ -908,6 +943,7 @@ struct DrawSystem { uint length; uint thread_id; + uint job_id; @readonly CTexture[] textures; @readonly CLocation[] locations; @readonly CScale[] scale; @@ -925,24 +961,24 @@ struct DrawSystem foreach(i; 0..data.length) { uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; - short depth = cast(short)(data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, 0, 0, 0, data.thread_id); + short depth = cast(short)(data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, 0, 0, 0, data.job_id); } } else if(data.rotation) { foreach(i; 0..data.length) { - short depth = cast(short)(data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, data.rotation[i], 0, 0, data.thread_id); + short depth = cast(short)(data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, data.rotation[i], 0, 0, data.job_id); } } else { foreach(i; 0..data.length) { - short depth = cast(short)(data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, 0, 0, 0, data.thread_id); + short depth = cast(short)(data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, 0, 0, 0, data.job_id); } } } @@ -955,8 +991,8 @@ struct DrawSystem foreach(i; 0..data.length) { uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; - short depth = cast(short)(data.depth[i] * 16 + data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, data.rotation[i], 0, 0, data.thread_id); + short depth = cast(short)(data.depth[i] * 8 + data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, data.rotation[i], 0, 0, data.job_id); } } else @@ -964,8 +1000,8 @@ struct DrawSystem foreach(i; 0..data.length) { uint color = 0x80808080 + 0x01010101 * data.hit_mark[i]; - short depth = cast(short)(data.depth[i] * 16 + data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, 0, 0, 0, data.thread_id); + short depth = cast(short)(data.depth[i] * 8 + data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, color, 0, 0, 0, data.job_id); } } } @@ -973,20 +1009,20 @@ struct DrawSystem { foreach(i; 0..data.length) { - short depth = cast(short)(data.depth[i] * 16 + data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, data.rotation[i], 0, 0, data.thread_id); + short depth = cast(short)(data.depth[i] * 8 + data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, data.rotation[i], 0, 0, data.job_id); } } else { foreach(i; 0..data.length) { - short depth = cast(short)(data.depth[i] * 16 + data.locations[i].y*8 + data.locations[i].x); - launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, 0, 0, 0, data.thread_id); + short depth = cast(short)(data.depth[i] * 8 + data.locations[i].y); + launcher.renderer.draw(space_invaders.texture, data.locations[i].value, data.scale[i], data.textures[i].coords, depth, 0x80808080, 0, 0, 0, data.job_id); } } } - if(data.thread_id == 0)launcher.renderer.pushData(); + //if(data.thread_id == 0)launcher.renderer.pushData(); } } @@ -1029,7 +1065,7 @@ struct LaserShootingSystem uint length; ///variable named "length" contain thread identifier uint thread_id; - CLaserWeapon[] laser; + CWeapon[] laser; @readonly CLocation[] location; @readonly CGuild[] guild; @@ -1134,14 +1170,14 @@ struct LaserShootingSystem { foreach(i;0..data.length) { - CLaserWeapon* laser = &data.laser[i]; + CWeapon* laser = &data.laser[i]; laser.shoot_time += launcher.delta_time; - while(laser.shoot_time > CLaserWeapon.levels[laser.level - 1].reload_time) + while(laser.shoot_time > CWeapon.levels[laser.level - 1].reload_time) { - laser.shoot_time -= CLaserWeapon.levels[laser.level - 1].reload_time; + laser.shoot_time -= CWeapon.levels[laser.level - 1].reload_time; thread.laser_location.value = data.location[i]; - thread.laser_velocity.value = vec2((randomf()*2-1) * CLaserWeapon.levels[laser.level - 1].dispersion,1);//data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); + thread.laser_velocity.value = vec2((randomf()*2-1) * CWeapon.levels[laser.level - 1].dispersion,1);//data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); if(data.shoot_direction && data.shoot_direction[i].direction == Direction.down)thread.laser_velocity.y = -1; thread.laser_guild.guild = data.guild[i].guild; @@ -1197,9 +1233,9 @@ struct LaserShootingSystem { foreach(i;0..data.length) { - CLaserWeapon* laser = &data.laser[i]; + CWeapon* laser = &data.laser[i]; laser.shoot_time += launcher.delta_time; - if(laser.shoot_time > CLaserWeapon.levels[laser.level - 1].reload_time)laser.shoot_time = CLaserWeapon.levels[laser.level - 1].reload_time; + if(laser.shoot_time > CWeapon.levels[laser.level - 1].reload_time)laser.shoot_time = CWeapon.levels[laser.level - 1].reload_time; } } @@ -1262,8 +1298,83 @@ struct LaserCollisionSystem { if(space_invaders.shoot_grid.test(id, data.location[i], cast(ubyte)(~(1 << data.guild[i].guild)))) { - launcher.manager.sendEvent(id, EDamage(1)); - launcher.manager.removeEntity(data.entity[i].id); + launcher.manager.sendEvent(id, EBulletHit(data.entity[i].id,1)); + //launcher.manager.removeEntity(data.entity[i].id); + } + } + } +} + +struct ParticleEmittingSystem +{ + mixin ECS.System!32; + + struct EntitiesData + { + uint length; + uint thread_id; + CParticleEmitterTime[] emit_time; + @readonly CLocation[] location; + @readonly CParticleEmitter[] emitter; + + @optional @readonly CVelocity[] velocity; + } + + struct Thread + { + EntityTemplate*[1] templates; + } + + Thread[] threads; + + void onCreate() + { + threads = Mallocator.makeArray!Thread(32); + + threads[0].templates[0] = launcher.manager.allocateTemplate( + [CLocation.component_id, CTexture.component_id, CScale.component_id, + CAnimation.component_id, CParticle.component_id, CRotation.component_id, + CVelocity.component_id, CDamping.component_id].staticArray); + + foreach(ref thread;threads[1 .. $]) + { + thread.templates[0] = launcher.manager.allocateTemplate(threads[0].templates[0]); + } + } + + void onDestroy() + { + foreach(ref thread;threads[1 .. $]) + { + foreach(tmpl; thread.templates) + { + launcher.manager.freeTemplate(tmpl); + } + } + Mallocator.dispose(threads); + } + + void onUpdate(EntitiesData data) + { + Thread* thread = &threads[data.thread_id]; + foreach(i;0..data.length) + { + data.emit_time[i].time -= launcher.delta_time; + while(data.emit_time[i].time < 0) + { + CParticleEmitter* emitter = &data.emitter[i]; + data.emit_time[i].time += emitter.time_range.x + randomf() * emitter.time_range.y; + + EntityTemplate* tmpl = thread.templates[emitter.tmpl_id]; + CLocation* location = tmpl.getComponent!CLocation; + if(location)location.value = data.location[i]; + + if(data.velocity) + { + CVelocity* velocity = tmpl.getComponent!CVelocity; + if(velocity)velocity.value = data.velocity[i]; + } + launcher.manager.addEntity(tmpl); } } } @@ -1314,10 +1425,10 @@ struct UpgradeSystem void handleEvent(Entity* entity, EUpgrade event) { - CLaserWeapon* laser = entity.getComponent!CLaserWeapon; + CWeapon* laser = entity.getComponent!CWeapon; if(laser) { - if(laser.level < CLaserWeapon.levels.length)laser.level++; + if(laser.level < CWeapon.levels.length)laser.level++; } CShip* ship = entity.getComponent!CShip; if(ship) @@ -1553,6 +1664,21 @@ struct HitPointsSystem CHitMark* hit_mark = entity.getComponent!CHitMark; if(hit_mark)hit_mark.value = 127; } + + void handleEvent(Entity* entity, EBulletHit event) + { + CHitPoints* hp = entity.getComponent!CHitPoints; + if(*hp <= 0)return; + launcher.manager.removeEntity(event.id); + *hp -= event.damage; + if(*hp <= 0) + { + launcher.manager.sendEvent(entity.id, EDeath()); + //launcher.manager.removeEntity(entity.id); + } + CHitMark* hit_mark = entity.getComponent!CHitMark; + if(hit_mark)hit_mark.value = 127; + } void handleEvent(Entity* entity, EDeath event) { @@ -1581,12 +1707,12 @@ struct ChildDestroySystem struct EntitiesData { - CTargetPartent[] parent; + CTargetParent[] parent; } void handleEvent(Entity* entity, EDeath event) { - CTargetPartent* parent = entity.getComponent!CTargetPartent; + CTargetParent* parent = entity.getComponent!CTargetParent; if(parent) { launcher.manager.sendEvent(parent.parent, EDestroyedChild(entity.id)); @@ -1605,13 +1731,50 @@ struct PartsDestroySystem CParts[] parts; } + EntityTemplate* flashes_emitter; + + void onCreate() + { + flashes_emitter = launcher.manager.allocateTemplate( + [ + CVelocity.component_id, CLocation.component_id, CParticleEmitter.component_id, + CParticleEmitterTime.component_id, CTargetParent.component_id + ].staticArray); + *flashes_emitter.getComponent!CParticleEmitter() = CParticleEmitter(vec2(0,0), vec2(400,400), 0); + } + void handleEvent(Entity* entity, EDestroyedChild event) { CParts* parts = entity.getComponent!CParts; parts.count--; + + CInit* init = entity.getComponent!CInit; + if(init.type == CInit.Type.boss) + { + CChildren* children = entity.getComponent!CChildren; + foreach(EntityID child; children.childern) + { + if(child == event.id) + { + Entity* child_entity = launcher.manager.getEntity(child); + if(child_entity) + { + CTargetParent* target_parent = child_entity.getComponent!CTargetParent; + + *flashes_emitter.getComponent!CTargetParent() = *target_parent; + launcher.manager.addEntity(flashes_emitter); + } + break; + } + } + } + if(parts.count == 0) { - launcher.manager.addComponents(entity.id, CHitPoints(100), CShootGrid()); + if(init.type == CInit.Type.boss) + { + launcher.manager.addComponents(entity.id, CHitPoints(100), CShootGrid()); + } } } } @@ -2033,7 +2196,7 @@ void spaceInvadersStart() launcher.manager.registerComponent!CScale; launcher.manager.registerComponent!CShootDirection; launcher.manager.registerComponent!CAutoShoot; - launcher.manager.registerComponent!CLaserWeapon; + launcher.manager.registerComponent!CWeapon; launcher.manager.registerComponent!CVelocity; launcher.manager.registerComponent!CLaser; launcher.manager.registerComponent!CSideMove; @@ -2049,7 +2212,7 @@ void spaceInvadersStart() launcher.manager.registerComponent!CRotation; launcher.manager.registerComponent!CAnimationLooped; launcher.manager.registerComponent!CDamping; - launcher.manager.registerComponent!CTargetPartent; + launcher.manager.registerComponent!CTargetParent; launcher.manager.registerComponent!CTarget; launcher.manager.registerComponent!CTargetPlayerShip; launcher.manager.registerComponent!CChildren; @@ -2066,6 +2229,7 @@ void spaceInvadersStart() launcher.manager.registerEvent!EUpgrade; launcher.manager.registerEvent!EDeath; launcher.manager.registerEvent!EDestroyedChild; + launcher.manager.registerEvent!EBulletHit; //launcher.manager.registerSystem!MoveSystem(0); launcher.manager.registerSystem!DrawSystem(100); @@ -2087,6 +2251,7 @@ void spaceInvadersStart() launcher.manager.registerSystem!MoveToParentTargetSystem(99); launcher.manager.registerSystem!ParentOwnerSystem(-101); launcher.manager.registerSystem!ShipWeaponSystem(-100); + launcher.manager.registerSystem!ParticleEmittingSystem(-100); launcher.manager.registerSystem!RotateToTargetSystem(-100); launcher.manager.registerSystem!ShipTargetSystem(-110); @@ -2125,7 +2290,7 @@ void spaceInvadersStart() CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id, CDamping.component_id, CChildren.component_id, CInit.component_id].staticArray ); - //CLaserWeapon* weapon = space_invaders.ship_tmpl.getComponent!CLaserWeapon; + //CWeapon* weapon = space_invaders.ship_tmpl.getComponent!CWeapon; //weapon.level = 3; space_invaders.ship_tmpl.getComponent!CTexture().coords = vec4(0*px,80*px,48*px,32*px); space_invaders.ship_tmpl.getComponent!CScale().value = vec2(48,32); @@ -2203,7 +2368,7 @@ void spaceInvadersStart() space_invaders.enemy_tmpl = launcher.manager.allocateTemplate( [CWeaponLocation.component_id, CHitMark.component_id, CHitPoints.component_id, CVelocity.component_id, CAutoShoot.component_id, CLocation.component_id, - CTexture.component_id, CScale.component_id, CLaserWeapon.component_id, + CTexture.component_id, CScale.component_id, CWeapon.component_id, CEnemy.component_id, CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id].staticArray ); @@ -2298,10 +2463,10 @@ void spaceInvadersTool(vec2 position, Tool tool, int size) else if(position.y > 299)position.y = 299; *location = position; } - CLaserWeapon* laser_weapon = tmpl.getComponent!CLaserWeapon; + CWeapon* laser_weapon = tmpl.getComponent!CWeapon; if(laser_weapon) { - laser_weapon.shoot_time = randomf * CLaserWeapon.levels[laser_weapon.level - 1].reload_time; + laser_weapon.shoot_time = randomf * CWeapon.levels[laser_weapon.level - 1].reload_time; } launcher.manager.addEntity(tmpl); } diff --git a/demos/utils/source/ecs_utils/gfx/renderer.d b/demos/utils/source/ecs_utils/gfx/renderer.d index 1b46eaf..fcd6a88 100644 --- a/demos/utils/source/ecs_utils/gfx/renderer.d +++ b/demos/utils/source/ecs_utils/gfx/renderer.d @@ -114,7 +114,7 @@ struct Renderer void freeBlocks() { - block_stack_mutex.lock(); + /*block_stack_mutex.lock(); render_blocks = 0; current_block = 0; foreach(VertexBlock block; blocks) @@ -124,13 +124,38 @@ struct Renderer blocks.clear; prepared_items=0; draw_list.clear(); - block_stack_mutex.unlock(); + block_stack_mutex.unlock();*/ + foreach(ref Thread thread; threads) + { + foreach(VertexBlock block; thread.blocks) + { + allocator.freeBlock(block.memory); + } + thread.blocks.clear(); + } + render_blocks = 0; + current_block = 0; + prepared_items = 0; + draw_list.clear(); } void pushData() { + foreach(ref Thread thread; threads) + { + foreach(VertexBlock block; thread.blocks) + { + uint items = block.items; + if(items + item_id >= MaxObjects)items = MaxObjects - item_id; + batch_vbo[0].bufferSubData(Buffer.BindTarget.array,items*4*14,item_id*4*14,block.batch_vertices.ptr); + batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,items*2*6,item_id*2*6,block.batch_indices.ptr); + draw_list.add(DrawCall(item_id,items)); + item_id += items; + } + //thread.blocks.clear(); + } //if(!isRemainingBlocks())return; - while(isRemainingBlocks()) + /* while(isRemainingBlocks()) { VertexBlock block = fetchBlock(); uint items = block.items; @@ -139,14 +164,15 @@ struct Renderer batch_ibo[0].bufferSubData(Buffer.BindTarget.element_array,items*2*6,item_id*2*6,block.batch_indices.ptr); draw_list.add(DrawCall(item_id,items)); item_id += items; - } + }*/ } void pushThreadsBlocks() { foreach(i, ref Thread thread; threads) { - pushBlock(thread.block); + //pushBlock(thread.block); + thread.blocks.add(thread.block); thread.block = getBlock(); } } @@ -156,6 +182,7 @@ struct Renderer //Vector!VertexBlock block; RenderData[] render_list; VertexBlock block; + Vector!VertexBlock blocks; } Thread[] threads; @@ -609,7 +636,8 @@ struct Renderer threads[thread_id].block.items++; if(threads[thread_id].block.items >= VertexBlock.max_items) { - pushBlock(threads[thread_id].block); + //pushBlock(threads[thread_id].block); + threads[thread_id].blocks.add(threads[thread_id].block); threads[thread_id].block = getBlock(); } } -- 2.47.2 From 15cd57dbcb10cf2a5e843ba728c3cfde4bd082ca Mon Sep 17 00:00:00 2001 From: Mergul Date: Sun, 24 May 2020 21:57:48 +0200 Subject: [PATCH 30/37] Basic update, multithreading emplate support, fixed -Added possibility to add entity form template + components to replace template data -basic tests for new functionality -small performance improvement for events -added ComponentRef structure which contain data pointer and component ID -now events are called before entities removing --- source/bubel/ecs/block_allocator.d | 1 + source/bubel/ecs/core.d | 5 ++ source/bubel/ecs/entity.d | 11 +++ source/bubel/ecs/events.d | 10 +-- source/bubel/ecs/manager.d | 120 +++++++++++++++++++++++++---- tests/basic.d | 10 ++- 6 files changed, 137 insertions(+), 20 deletions(-) diff --git a/source/bubel/ecs/block_allocator.d b/source/bubel/ecs/block_allocator.d index 740b762..5a2d02c 100644 --- a/source/bubel/ecs/block_allocator.d +++ b/source/bubel/ecs/block_allocator.d @@ -62,6 +62,7 @@ private: { next_block = cast(void*) Mallocator.alignAlloc( block_size * blocks_in_allocation, block_size); + if(next_block is null)assert(0); if(pointers is null)pointers = Mallocator.make!BlockPointers; if(pointers.numberof >= 32) diff --git a/source/bubel/ecs/core.d b/source/bubel/ecs/core.d index 0487665..4a90928 100644 --- a/source/bubel/ecs/core.d +++ b/source/bubel/ecs/core.d @@ -74,6 +74,11 @@ static struct ECS mixin template Component() { __gshared ushort component_id = ushort.max; + + ComponentRef ref_() @nogc nothrow + { + return ComponentRef(&this,component_id); + } } /************************************************************************************************************************ diff --git a/source/bubel/ecs/entity.d b/source/bubel/ecs/entity.d index 1e98ff2..f24ddf3 100644 --- a/source/bubel/ecs/entity.d +++ b/source/bubel/ecs/entity.d @@ -114,3 +114,14 @@ export struct EntityTemplate return cast(T*)(entity_data.ptr + info.tmpl_deltas[T.component_id]); } } + +/************************************************************************************************************************ +ComponentRef contain component ID and pointer to it. It used to add component data to entity. +*/ +export struct ComponentRef +{ + ///pointer to component + void* ptr; + ///component index + ushort component_id; +} diff --git a/source/bubel/ecs/events.d b/source/bubel/ecs/events.d index 772d5b5..25b864d 100644 --- a/source/bubel/ecs/events.d +++ b/source/bubel/ecs/events.d @@ -40,10 +40,9 @@ package struct EventManager if(block is null) { event_block_alloc_mutex.lock(); - scope (exit) - event_block_alloc_mutex.unlock(); - block = cast(EventBlock*) allocator.getBlock(); + event_block_alloc_mutex.unlock(); + *block = EventBlock(); data.first_blocks[block_id] = block; data.blocks[block_id] = block; @@ -52,10 +51,9 @@ package struct EventManager if(block.count >= data.max_events) { event_block_alloc_mutex.lock(); - scope (exit) - event_block_alloc_mutex.unlock(); - EventBlock* new_block = cast(EventBlock*) allocator.getBlock(); + event_block_alloc_mutex.unlock(); + *new_block = EventBlock(); block.next = new_block; block = new_block; diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index d5892bb..3ac16e8 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -2356,12 +2356,7 @@ export struct EntityManager void addComponents(Components...)(const EntityID entity_id, Components comps) nothrow @nogc { const uint num = Components.length; - //Entity* entity = id_manager.getEntityPointer(entity_id); - //EntitiesBlock* block = getMetaData(entity); - //EntityInfo* info = block.type_info; - /*ushort[] ids = (cast(ushort*) alloca(ushort.sizeof * (info.components.length + num)))[0 - .. info.components.length + num];*/ - ushort[num] new_ids; + /*ushort[num] new_ids; static foreach (i, comp; Components) { @@ -2376,9 +2371,39 @@ export struct EntityManager static foreach (i, comp; comps) { data.changeEntitiesList.add((cast(ubyte*)&comp)[0 .. comp.sizeof]); - } + }*/ //__addComponents(entity_id, new_ids, pointers); + + ComponentRef[num] _comps; + static foreach(i, comp; comps) + { + _comps[i] = comp.ref_; + } + addComponents(entity_id, _comps); + + } + + export void addComponents(const EntityID entity_id, ComponentRef[] comps) nothrow @nogc + { + uint num = cast(uint)comps.length; + 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]); + foreach(ref_; comps) + { + data.changeEntitiesList.add((cast(ubyte*)&ref_.component_id)[0 .. ushort.sizeof]); + } + foreach(ref_; comps) + { + data.changeEntitiesList.add((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]); + }*/ } /************************************************************************************************************************ @@ -2455,6 +2480,58 @@ export struct EntityManager tmpl = pointer entity template allocated by EntityManager. */ export Entity* addEntity(EntityTemplate* tmpl) + { + /*EntityInfo* info = tmpl.info; + + ushort index = 0; + EntitiesBlock* block; + do + { + block = findBlockWithFreeSpaceMT(info); + index = block.added_count.atomicOp!"+="(1); + } + while (block.entities_count + index > info.max_entities); + + uint id = (block.entities_count + index - 1); //block.added_count); + + void* data_begin = block.dataBegin(); + void* start = data_begin + EntityID.sizeof * id; + + foreach (comp; info.components) + { + memcpy(cast(void*) block + info.deltas[comp] + components[comp].size * id, + tmpl.entity_data.ptr + info.tmpl_deltas[comp], components[comp].size); + + if (components[comp].create_callback) + { + components[comp].create_callback( + cast(void*) block + info.deltas[comp] + id * components[comp].size); + } + + } + + if (index == 1) + threads[threadID].blockToUpdate.add(block); + + Entity* entity = cast(Entity*) start; + //add_mutex.lock_nothrow(); + entity.id = id_manager.getNewID(); + //add_mutex.unlock_nothrow(); + id_manager.update(*entity); //entity.updateID(); + + return entity;*/ + return addEntity(tmpl, null); + } + + /************************************************************************************************************************ + Add entity to system. Returen pointer is valid only before one from commit(), begin() or end() will be called. To save entity to further + use you should save ID instead of pointer. + + Params: + 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.) + */ + export Entity* addEntity(EntityTemplate* tmpl, ComponentRef[] replacement) { EntityInfo* info = tmpl.info; @@ -2472,17 +2549,34 @@ export struct EntityManager void* data_begin = block.dataBegin(); void* start = data_begin + EntityID.sizeof * id; + foreach (comp; info.components) + { + uint size = components[comp].size; + memcpy(cast(void*) block + info.deltas[comp] + size * id, + tmpl.entity_data.ptr + info.tmpl_deltas[comp], size); + } + + foreach(comp; replacement) + { + if(comp.component_id < info.deltas.length) + { + ushort delta = info.deltas[comp.component_id]; + if(delta != ushort.max) + { + uint size = components[comp.component_id].size; + memcpy(cast(void*) block + delta + size * id, + comp.ptr, size); + } + } + } + foreach (i, comp; info.components) { - memcpy(cast(void*) block + info.deltas[comp] + components[comp].size * id, - tmpl.entity_data.ptr + info.tmpl_deltas[comp], components[comp].size); - if (components[comp].create_callback) { components[comp].create_callback( cast(void*) block + info.deltas[comp] + id * components[comp].size); } - } if (index == 1) @@ -2916,12 +3010,12 @@ export struct EntityManager swapData(); has_work = false; + has_work |= updateEvents(); + id_manager.optimize(); has_work |= updateBlocks(); has_work |= changeEntities(); has_work |= removeEntities(); - - has_work |= updateEvents(); } event_manager.clearEvents(); } diff --git a/tests/basic.d b/tests/basic.d index 3bf2814..ab9acec 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -153,6 +153,14 @@ unittest assert(entity2.getComponent!CFloat); assert(*entity2.getComponent!CInt == 2); assert(*entity2.getComponent!CFloat == 2.0); + + CInt cint = CInt(10); + CLong clong; + Entity* entity3 = gEM.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray); + assert(entity3.getComponent!CInt); + assert(entity3.getComponent!CFloat); + assert(*entity3.getComponent!CInt == 10); + assert(*entity3.getComponent!CFloat == 2.0); } //allocate templates @@ -1142,7 +1150,7 @@ unittest entity = gEM.getEntity(id); assert(*entity.getComponent!CLong == 66); - assert(*entity.getComponent!CInt == 36); + assert(*entity.getComponent!CInt == 2);//36); //test for multiple event blocks long result = *entity.getComponent!CLong; -- 2.47.2 From 6929f5a7487a1c96239d03c256456f2952dab448 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 27 May 2020 17:03:44 +0200 Subject: [PATCH 31/37] Mostly bugfix update + empty components support and remove EntityID from Event structure -empty components now take no memory, so flag components is now far better -added test for critical bug -fixed critical bug with adding/removing entities form inside events -fixed small bug with TestRunner -improve basic tests -fixed betterC compilation on DMD -remove EntityID form Event structure -added "return" attribute to some functions -moved some code from Tempalte side to actual implementation -fixed bug with EntityTemplate copying -commented out some possibliy unused code -use code formatter --- source/bubel/ecs/atomic.d | 125 +++++++---- source/bubel/ecs/attributes.d | 2 +- source/bubel/ecs/block_allocator.d | 21 +- source/bubel/ecs/core.d | 9 +- source/bubel/ecs/entity.d | 11 +- source/bubel/ecs/events.d | 63 +++--- source/bubel/ecs/hash_map.d | 186 ++++++++++------ source/bubel/ecs/id_manager.d | 54 +++-- source/bubel/ecs/manager.d | 330 ++++++++++++++++------------- source/bubel/ecs/package.d | 2 +- source/bubel/ecs/simple_vector.d | 40 ++-- source/bubel/ecs/std.d | 215 +++++++++++-------- source/bubel/ecs/vector.d | 173 ++++++++++----- tests/basic.d | 153 ++++++++----- tests/bugs.d | 142 +++++++++++++ tests/runner.d | 6 +- 16 files changed, 988 insertions(+), 544 deletions(-) create mode 100644 tests/bugs.d diff --git a/source/bubel/ecs/atomic.d b/source/bubel/ecs/atomic.d index 9155c8f..8ea43df 100644 --- a/source/bubel/ecs/atomic.d +++ b/source/bubel/ecs/atomic.d @@ -9,9 +9,9 @@ License: BSD 3-clause, see LICENSE file in project root folder. */ module bubel.ecs.atomic; -version(Emscripten)version = ECSEmscripten; +version (Emscripten) version = ECSEmscripten; -version(ECSEmscripten) +version (ECSEmscripten) { import std.traits; @@ -24,74 +24,109 @@ version(ECSEmscripten) 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_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_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_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_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; + 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 (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); + 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 == "-=") + 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); + 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) + 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); + 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) + 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); + 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) + 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); + 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; -} \ No newline at end of file +} diff --git a/source/bubel/ecs/attributes.d b/source/bubel/ecs/attributes.d index 2bc0aec..d094aad 100644 --- a/source/bubel/ecs/attributes.d +++ b/source/bubel/ecs/attributes.d @@ -25,4 +25,4 @@ module bubel.ecs.attributes; ///Used to mark optional components for system. enum optional = "optional"; ///Used to mark readonly components for system. "const" can be used insted. -enum readonly = "readonly"; \ No newline at end of file +enum readonly = "readonly"; diff --git a/source/bubel/ecs/block_allocator.d b/source/bubel/ecs/block_allocator.d index 5a2d02c..d3070c4 100644 --- a/source/bubel/ecs/block_allocator.d +++ b/source/bubel/ecs/block_allocator.d @@ -35,7 +35,7 @@ struct BlockAllocator */ void freeBlock(void* block) nothrow @nogc { - *cast(void**)block = next_block; + *cast(void**) block = next_block; next_block = block; } @@ -44,9 +44,9 @@ struct BlockAllocator */ void freeMemory() nothrow @nogc { - while(pointers) + while (pointers) { - foreach(i;0..pointers.numberof) + foreach (i; 0 .. pointers.numberof) { Mallocator.alignDispose(pointers.blocks[i]); } @@ -60,12 +60,14 @@ private: void allocBlock() nothrow @nogc { - next_block = cast(void*) Mallocator.alignAlloc( - block_size * blocks_in_allocation, block_size); - if(next_block is null)assert(0); + next_block = cast(void*) Mallocator.alignAlloc(block_size * blocks_in_allocation, + block_size); + if (next_block is null) + assert(0); - if(pointers is null)pointers = Mallocator.make!BlockPointers; - if(pointers.numberof >= 32) + if (pointers is null) + pointers = Mallocator.make!BlockPointers; + if (pointers.numberof >= 32) { BlockPointers* new_pointers = Mallocator.make!BlockPointers; new_pointers.next_pointers = pointers; @@ -78,8 +80,7 @@ private: void** pointer = cast(void**)(next_block + i * block_size); *pointer = next_block + (i + 1) * block_size; } - void** pointer = cast(void**)( - next_block + (blocks_in_allocation - 1) * block_size); + void** pointer = cast(void**)(next_block + (blocks_in_allocation - 1) * block_size); *pointer = null; } diff --git a/source/bubel/ecs/core.d b/source/bubel/ecs/core.d index 4a90928..c032d21 100644 --- a/source/bubel/ecs/core.d +++ b/source/bubel/ecs/core.d @@ -75,19 +75,18 @@ static struct ECS { __gshared ushort component_id = ushort.max; - ComponentRef ref_() @nogc nothrow + ComponentRef ref_() @nogc nothrow return { - return ComponentRef(&this,component_id); + return ComponentRef(&this, component_id); } } /************************************************************************************************************************ Mark structure as Event. Should be added on top of structure (before any data). */ - mixin template Event() + mixin template Event() { __gshared ushort event_id = ushort.max; - EntityID entity_id; } /************************************************************************************************************************ @@ -113,4 +112,4 @@ static struct ECS { alias WritableDependencies = T; } -} \ No newline at end of file +} diff --git a/source/bubel/ecs/entity.d b/source/bubel/ecs/entity.d index f24ddf3..6a64d50 100644 --- a/source/bubel/ecs/entity.d +++ b/source/bubel/ecs/entity.d @@ -39,11 +39,7 @@ struct Entity if (T.component_id >= info.deltas.length || info.deltas[T.component_id] == 0) return null; - static if (EntityID.sizeof == 8) - uint ind = cast(uint)((cast(void*)&this - block.dataBegin()) >> 3); - else - uint ind = cast(uint)((cast(void*)&this - block.dataBegin()) / EntityID.sizeof()); - return cast(T*)(cast(void*)block + info.deltas[T.component_id] + ind * T.sizeof); + return cast(T*)(cast(void*)block + info.deltas[T.component_id] + block.entityIndex(&this) * T.sizeof); } bool hasComponent(ushort component_id) @@ -58,10 +54,7 @@ struct Entity { EntityMeta meta; meta.block = gEM.getMetaData(&this); - static if (EntityID.sizeof == 8) - meta.index = cast(ushort)((cast(void*)&this - meta.block.dataBegin()) >> 3); - else - meta.index = cast(ushort)((cast(void*)&this - meta.block.dataBegin()) / EntityID.sizeof()); + meta.index = meta.block.entityIndex(&this); return meta; } } diff --git a/source/bubel/ecs/events.d b/source/bubel/ecs/events.d index 25b864d..64e0c79 100644 --- a/source/bubel/ecs/events.d +++ b/source/bubel/ecs/events.d @@ -20,7 +20,7 @@ package struct EventManager void destroy() nothrow @nogc { - if(event_block_alloc_mutex) + if (event_block_alloc_mutex) { event_block_alloc_mutex.destroy(); Mallocator.dispose(event_block_alloc_mutex); @@ -30,14 +30,14 @@ package struct EventManager export void sendEvent(Ev)(EntityID id, Ev event, uint thread_id = 0) nothrow @nogc { - uint block_id = current_index+thread_id; + uint block_id = current_index + thread_id; EventData* data = &events[Ev.event_id]; EventBlock* block = data.blocks[block_id]; //EntityManager.EventInfo* info = &manager.events[Ev.event_id]; - event.entity_id = id; + //event.entity_id = id; - if(block is null) + if (block is null) { event_block_alloc_mutex.lock(); block = cast(EventBlock*) allocator.getBlock(); @@ -48,35 +48,42 @@ package struct EventManager data.blocks[block_id] = block; } - if(block.count >= data.max_events) + if (block.count >= data.max_events) { event_block_alloc_mutex.lock(); EventBlock* new_block = cast(EventBlock*) allocator.getBlock(); event_block_alloc_mutex.unlock(); - + *new_block = EventBlock(); block.next = new_block; block = new_block; data.blocks[block_id] = block; } - Ev* event_array = cast(Ev*)(cast(void*)block + data.data_offset); - event_array[block.count] = event; + uint size = Ev.sizeof + EntityID.sizeof; + void* ptr = cast(void*) block + data.data_offset + block.count * size; + *cast(EntityID*)ptr = id; + *cast(Ev*)(ptr + EntityID.sizeof) = event; + //Ev* event_array = cast(Ev*)(cast(void*) block + data.data_offset); + //event_array[block.count] = event; block.count++; } void swapCurrent() nothrow @nogc { - uint threads_count = cast(uint)manager.threads.length; - if(current_index == 0)current_index = threads_count; - else current_index = 0; + uint threads_count = cast(uint) manager.threads.length; + if (current_index == 0) + current_index = threads_count; + else + current_index = 0; - foreach(ref event;events) + foreach (ref event; events) { - foreach(ref first_block; event.first_blocks[current_index .. current_index + threads_count]) + foreach (ref first_block; event.first_blocks[current_index + .. current_index + threads_count]) { EventBlock* block = first_block; - while(block) + while (block) { EventBlock* to_dispose = block; block = block.next; @@ -84,7 +91,7 @@ package struct EventManager } first_block = null; } - foreach(ref block; event.blocks[current_index .. current_index + threads_count]) + foreach (ref block; event.blocks[current_index .. current_index + threads_count]) { block = null; } @@ -94,12 +101,12 @@ package struct EventManager void clearEvents() nothrow @nogc { //uint threads_count = cast(uint)manager.threads.length; - foreach(ref event;events) + foreach (ref event; events) { - foreach(ref first_block; event.first_blocks) + foreach (ref first_block; event.first_blocks) { EventBlock* block = first_block; - while(block) + while (block) { EventBlock* to_dispose = block; block = block.next; @@ -107,7 +114,7 @@ package struct EventManager } first_block = null; } - foreach(ref block; event.blocks) + foreach (ref block; event.blocks) { block = null; } @@ -118,23 +125,25 @@ package struct EventManager { disposeData(); events = Mallocator.makeArray!EventData(manager.events.length); - foreach(i,ref event;events) + foreach (i, ref event; events) { - event.blocks = Mallocator.makeArray!(EventBlock*)(threads_count*2); - event.first_blocks = Mallocator.makeArray!(EventBlock*)(threads_count*2); - event.data_offset = EventBlock.sizeof;//manager.events[i]. + event.blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2); + event.first_blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2); + event.data_offset = EventBlock.sizeof; //manager.events[i]. manager.alignNum(event.data_offset, manager.events[i].alignment); - event.max_events = cast(ushort)((events_block_size - event.data_offset) / manager.events[i].size); + uint size = manager.events[i].size + EntityID.sizeof; + event.max_events = cast(ushort)( + (events_block_size - event.data_offset) / size); } } private void disposeData() nothrow @nogc { clearEvents(); - if(events) + if (events) { - foreach(ref event;events) + foreach (ref event; events) { Mallocator.dispose(event.blocks); Mallocator.dispose(event.first_blocks); @@ -166,7 +175,7 @@ package struct EventManager ushort max_events; EventBlock*[] blocks; EventBlock*[] first_blocks; - + //EventBlock*[] current_blocks; } diff --git a/source/bubel/ecs/hash_map.d b/source/bubel/ecs/hash_map.d index 1f7a1f8..6ae6a79 100755 --- a/source/bubel/ecs/hash_map.d +++ b/source/bubel/ecs/hash_map.d @@ -11,36 +11,44 @@ private enum HASH_EMPTY = 0; private enum HASH_DELETED = 0x1; private enum HASH_FILLED_MARK = ulong(1) << 8 * ulong.sizeof - 1; -export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc { - static if (isIntegral!(T)) { +export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc +{ + static if (isIntegral!(T)) + { return hashInt(t); - } else { - return 0;//hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts + } + else + { + return 0; //hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts } } // Can turn bad hash function to good one -export ulong hashInt(ulong x) nothrow @nogc @safe { +export ulong hashInt(ulong x) nothrow @nogc @safe +{ x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; x = x ^ (x >> 31); return x; } -struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { +struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) +{ alias Key = KeyPar; alias Value = ValuePar; - nothrow: +nothrow: enum rehashFactor = 0.75; enum size_t getIndexEmptyValue = size_t.max; - static struct KeyVal { + static struct KeyVal + { Key key; Value value; } - static struct Bucket { + static struct Bucket + { ulong hash; KeyVal keyValue; } @@ -49,58 +57,74 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { size_t length; // Used to compute loadFactor size_t markerdDeleted; - export void clear() { + export void clear() + { elements.clear(); length = 0; markerdDeleted = 0; } - export void reset() { + export void reset() + { elements.reset(); length = 0; markerdDeleted = 0; } - export bool isIn(ref Key el) { + export bool isIn(ref Key el) + { return getIndex(el) != getIndexEmptyValue; } - export bool isIn(Key el) { + export bool isIn(Key el) + { return getIndex(el) != getIndexEmptyValue; } - export Value* getPtr()(auto ref Key k) { + export Value* getPtr()(auto ref Key k) + { size_t index = getIndex(k); - if (index == getIndexEmptyValue) { + if (index == getIndexEmptyValue) + { return null; - } else { + } + else + { return &elements[index].keyValue.value; } } - export ref Value get()(auto ref Key k) { + export ref Value get()(auto ref Key k) + { size_t index = getIndex(k); assert(index != getIndexEmptyValue); return elements[index].keyValue.value; } deprecated("Use get with second parameter.") export auto ref Value getDefault()( - auto ref Key k, auto ref Value defaultValue) { + auto ref Key k, auto ref Value defaultValue) + { return get(k, defaultValue); } - export auto ref Value get()(auto ref Key k, auto ref Value defaultValue) { + export auto ref Value get()(auto ref Key k, auto ref Value defaultValue) + { size_t index = getIndex(k); - if (index == getIndexEmptyValue) { + if (index == getIndexEmptyValue) + { return defaultValue; - } else { + } + else + { return elements[index].keyValue.value; } } - export ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue) { + export ref Value getInsertDefault()(auto ref Key k, auto ref Value defaultValue) + { size_t index = getIndex(k); - if (index == getIndexEmptyValue) { + if (index == getIndexEmptyValue) + { add(k, defaultValue); } index = getIndex(k); @@ -109,9 +133,11 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { } - export bool tryRemove(Key el) { + export bool tryRemove(Key el) + { size_t index = getIndex(el); - if (index == getIndexEmptyValue) { + if (index == getIndexEmptyValue) + { return false; } length--; @@ -120,28 +146,34 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { return true; } - export void remove(Key el) { + export void remove(Key el) + { bool ok = tryRemove(el); assert(ok); } - export ref Value opIndex()(auto ref Key key) { + export ref Value opIndex()(auto ref Key key) + { return get(key); } - export void opIndexAssign()(auto ref Value value, auto ref Key key) { + export void opIndexAssign()(auto ref Value value, auto ref Key key) + { add(key, value); } - export void add()(auto ref Key key, auto ref Value value) { + export void add()(auto ref Key key, auto ref Value value) + { size_t index = getIndex(key); - if (index != getIndexEmptyValue) { + if (index != getIndexEmptyValue) + { elements[index].keyValue.value = value; return; } if (getLoadFactor(length + 1) > rehashFactor - || getLoadFactor(length + markerdDeleted) > rehashFactor) { + || getLoadFactor(length + markerdDeleted) > rehashFactor) + { rehash(); } length++; @@ -150,10 +182,13 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { immutable size_t rotateMask = elements.length - 1; index = hash & rotateMask; // Starting point - while (true) { + while (true) + { Bucket* gr = &elements[index]; - if ((gr.hash & HASH_FILLED_MARK) == 0) { - if (gr.hash == HASH_DELETED) { + if ((gr.hash & HASH_FILLED_MARK) == 0) + { + if (gr.hash == HASH_DELETED) + { markerdDeleted--; } gr.hash = hash; @@ -171,15 +206,18 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { //int numA; //int numB; - export size_t getIndex(Key el) { + export size_t getIndex(Key el) + { return getIndex(el); } - export size_t getIndex(ref Key el) { + export size_t getIndex(ref Key el) + { mixin(doNotInline); immutable size_t groupsLength = elements.length; - if (groupsLength == 0) { + if (groupsLength == 0) + { return getIndexEmptyValue; } @@ -188,13 +226,16 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { size_t index = hash & rotateMask; // Starting point //numA++; - while (true) { + while (true) + { //numB++; Bucket* gr = &elements[index]; - if (gr.hash == hash && gr.keyValue.key == el) { + if (gr.hash == hash && gr.keyValue.key == el) + { return index; } - if (gr.hash == HASH_EMPTY) { + if (gr.hash == HASH_EMPTY) + { return getIndexEmptyValue; } @@ -204,21 +245,26 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { } - export float getLoadFactor(size_t forElementsNum) { - if (elements.length == 0) { + export float getLoadFactor(size_t forElementsNum) + { + if (elements.length == 0) + { return 1; } return cast(float) forElementsNum / (elements.length); } - export void rehash()() { + export void rehash()() + { mixin(doNotInline); // Get all elements Vector!KeyVal allElements; allElements.reserve(elements.length); - foreach (ref Bucket el; elements) { - if ((el.hash & HASH_FILLED_MARK) == 0) { + foreach (ref Bucket el; elements) + { + if ((el.hash & HASH_FILLED_MARK) == 0) + { el.hash = HASH_EMPTY; continue; } @@ -227,12 +273,14 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { } - if (getLoadFactor(length + 1) > rehashFactor) { // Reallocate + if (getLoadFactor(length + 1) > rehashFactor) + { // Reallocate elements.length = (elements.length ? elements.length : 4) << 1; // Power of two, initially 8 elements } // Insert elements - foreach (i, ref el; allElements) { + foreach (i, ref el; allElements) + { add(el.key, el.value); } length = allElements.length; @@ -240,19 +288,29 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { } // foreach support - export int opApply(DG)(scope DG dg) { + export int opApply(DG)(scope DG dg) + { int result; - foreach (ref Bucket gr; elements) { - if ((gr.hash & HASH_FILLED_MARK) == 0) { + foreach (ref Bucket gr; elements) + { + if ((gr.hash & HASH_FILLED_MARK) == 0) + { continue; } - static if (isForeachDelegateWithTypes!(DG, Key)) { + static if (isForeachDelegateWithTypes!(DG, Key)) + { result = dg(gr.keyValue.key); - } else static if (isForeachDelegateWithTypes!(DG, Value)) { + } + else static if (isForeachDelegateWithTypes!(DG, Value)) + { result = dg(gr.keyValue.value); - } else static if (isForeachDelegateWithTypes!(DG, Key, Value)) { + } + else static if (isForeachDelegateWithTypes!(DG, Key, Value)) + { result = dg(gr.keyValue.key, gr.keyValue.value); - } else { + } + else + { static assert(0); } if (result) @@ -263,9 +321,11 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { return result; } - export int byKey(scope int delegate(Key k) nothrow dg) { + export int byKey(scope int delegate(Key k) nothrow dg) + { int result; - foreach (ref Key k; this) { + foreach (ref Key k; this) + { result = dg(k); if (result) break; @@ -273,9 +333,11 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { return result; } - export int byValue(scope int delegate(ref Value k) nothrow dg) { + export int byValue(scope int delegate(ref Value k) nothrow dg) + { int result; - foreach (ref Value v; this) { + foreach (ref Value v; this) + { result = dg(v); if (result) break; @@ -283,13 +345,15 @@ struct HashMap(KeyPar, ValuePar, alias hashFunc = defaultHashFunc) { return result; } - export int byKeyValue(scope int delegate(ref Key k, ref Value v) nothrow dg) { + export int byKeyValue(scope int delegate(ref Key k, ref Value v) nothrow dg) + { int result; - foreach (ref Key k, ref Value v; this) { + foreach (ref Key k, ref Value v; this) + { result = dg(k, v); if (result) break; } return result; } -} \ No newline at end of file +} diff --git a/source/bubel/ecs/id_manager.d b/source/bubel/ecs/id_manager.d index 4c3a2c3..084a947 100644 --- a/source/bubel/ecs/id_manager.d +++ b/source/bubel/ecs/id_manager.d @@ -18,7 +18,7 @@ struct IDManager pragma(inline, false) EntityID getNewID() nothrow @nogc { int current = m_stack_top.atomicOp!"-="(1) + 1; - if(current < 0) + if (current < 0) { uint add_id = m_last_id.atomicOp!"+="(1) - 1; @@ -29,7 +29,7 @@ struct IDManager if (block_id >= m_blocks_count) { add_mutex.lock(); - if(block_id >= m_blocks_count) + if (block_id >= m_blocks_count) { m_blocks[m_blocks_count].alloc(); m_blocks_count++; @@ -112,9 +112,11 @@ struct IDManager */ export bool isExist(EntityID id) nothrow @nogc { - if(id.id >= m_ids_array.length)return false; + if (id.id >= m_ids_array.length) + return false; Data* data = &m_ids_array[id.id]; - if(data.entity is null)return false; + if (data.entity is null) + return false; return data.counter == id.counter; } @@ -126,7 +128,8 @@ struct IDManager m_ids_array = Mallocator.makeArray!Data(65536); m_free_stack = Mallocator.makeArray!uint(65536); m_blocks = Mallocator.makeArray!Block(64); - foreach(ref block;m_blocks)block = Block(); + foreach (ref block; m_blocks) + block = Block(); m_blocks_count = 1; m_blocks[0].alloc(); @@ -142,20 +145,23 @@ struct IDManager */ void deinitialize() @trusted @nogc nothrow { - if(m_ids_array)Mallocator.dispose(m_ids_array); - if(m_free_stack)Mallocator.dispose(m_free_stack); - if(m_blocks) + if (m_ids_array) + Mallocator.dispose(m_ids_array); + if (m_free_stack) + Mallocator.dispose(m_free_stack); + if (m_blocks) { - foreach(ref block;m_blocks) + foreach (ref block; m_blocks) { - if(block.data)block.free(); + if (block.data) + block.free(); } Mallocator.dispose(m_blocks); } - if(add_mutex) + if (add_mutex) { add_mutex.destroy(); - Mallocator.dispose(add_mutex);//cast(void*)add_mutex); //workaround for compiler bug + Mallocator.dispose(add_mutex); //cast(void*)add_mutex); //workaround for compiler bug add_mutex = null; } } @@ -165,27 +171,31 @@ struct IDManager */ void optimize() nothrow @nogc { - if(m_stack_top < -1)m_stack_top = -1; - if(m_last_id > m_ids_array.length) + if (m_stack_top < -1) + m_stack_top = -1; + if (m_last_id > m_ids_array.length) { - uint begin = cast(uint)m_ids_array.length; + uint begin = cast(uint) m_ids_array.length; Data[] new_array = Mallocator.makeArray!Data(begin + (m_blocks_count << 16)); memcpy(new_array.ptr, m_ids_array.ptr, m_ids_array.length * Data.sizeof); Mallocator.dispose(m_ids_array); m_ids_array = new_array; uint[] new_stack = Mallocator.makeArray!uint(m_ids_array.length); - memcpy(new_stack.ptr,m_free_stack.ptr,m_free_stack.length * uint.sizeof); + memcpy(new_stack.ptr, m_free_stack.ptr, m_free_stack.length * uint.sizeof); Mallocator.dispose(m_free_stack); m_free_stack = new_stack; - foreach(block;m_blocks[0..m_blocks_count-1]) + foreach (block; m_blocks[0 .. m_blocks_count - 1]) { - memcpy(cast(void*)m_ids_array.ptr + begin * Data.sizeof, block.data.ptr, 65536 * Data.sizeof); + memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof, + block.data.ptr, 65536 * Data.sizeof); begin += 65536; } - memcpy(cast(void*)m_ids_array.ptr + begin * Data.sizeof, m_blocks[m_blocks_count-1].data.ptr, (m_last_id - begin) * Data.sizeof); - foreach(ref block;m_blocks[1..m_blocks_count])block.free(); + memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof, + m_blocks[m_blocks_count - 1].data.ptr, (m_last_id - begin) * Data.sizeof); + foreach (ref block; m_blocks[1 .. m_blocks_count]) + block.free(); m_blocks_count = 1; } } @@ -217,12 +227,12 @@ struct IDManager private: Mutex* add_mutex; - + Data[] m_ids_array = null; uint m_blocks_count = 0; Block[] m_blocks; uint[] m_free_stack = null; - + align(64) shared uint m_last_id = 0; align(64) shared int m_stack_top = -1; } diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index 3ac16e8..cf3712d 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -126,7 +126,7 @@ export struct EntityManager //if(info.components)Mallocator.dispose(info.components); Mallocator.dispose(info); - } + } foreach (UpdatePass* pass; passes) { @@ -402,7 +402,7 @@ export struct EntityManager Sys* data_system = cast(Sys*) data.system_pointer; Type* event = cast(Type*) data.event; - data_system.handleEvent(gEM.getEntity(event.entity_id), *event); + data_system.handleEvent(data.entity, *event); } void setEventCallers(Sys)(ref System system) @@ -474,7 +474,7 @@ export struct EntityManager { //continue; } - else + else { string name; static if (isArray!MemberType) @@ -1018,8 +1018,7 @@ export struct EntityManager static if (hasMember!(Sys.EntitiesData, "job_id")) { - input_data.job_id = cast(typeof(input_data.job_id)) data - .job_id; + input_data.job_id = cast(typeof(input_data.job_id)) data.job_id; } //s.onUpdate(input_data); @@ -1052,8 +1051,7 @@ export struct EntityManager static if (hasMember!(Sys.EntitiesData, "job_id")) { - input_data.job_id = cast(typeof(input_data.job_id)) data - .job_id; + input_data.job_id = cast(typeof(input_data.job_id)) data.job_id; } (cast(typeof(&__traits(getOverloads, s, @@ -1146,7 +1144,8 @@ export struct EntityManager foreach (iii, comp_info; components_info.readonlyDeps) { - ushort comp = external_dependencies_map.get(cast(const (char)[]) comp_info.type, ushort.max); + ushort comp = external_dependencies_map.get(cast(const(char)[]) comp_info.type, + ushort.max); version (D_BetterC) assert(comp != ushort.max, "Can't register system \"" ~ Sys.stringof @@ -1156,7 +1155,7 @@ export struct EntityManager ~ "\" due to non existing dependency \"" ~ comp_info.type ~ "\"."); system.m_readonly_dependencies[iii] = comp; } - + foreach (iii, comp_info; components_info.writableDeps) { ushort comp = external_dependencies_map.get(cast(char[]) comp_info.type, ushort.max); @@ -1174,7 +1173,9 @@ export struct EntityManager if (sys_id < systems.length) { systems[sys_id].disable(); - if(systems[sys_id].m_destroy)(cast(void function(void*)) systems[sys_id].m_destroy)(systems[sys_id].m_system_pointer); + if (systems[sys_id].m_destroy) + (cast(void function(void*)) systems[sys_id].m_destroy)( + systems[sys_id].m_system_pointer); if (system.m_create) (cast(void function(void*)) system.m_create)(system.m_system_pointer); @@ -1236,7 +1237,7 @@ export struct EntityManager export void registerDependency(const(char)[] name) { - return external_dependencies_map.add(name, cast(ushort)external_dependencies_map.length); + return external_dependencies_map.add(name, cast(ushort) external_dependencies_map.length); } /************************************************************************************************************************ @@ -1274,7 +1275,10 @@ export struct EntityManager info.create_callback = &callCreate; } - info.size = Comp.sizeof; + static if (Comp.sizeof == 1 && Fields!(Comp).length == 0) + info.size = 0; + else + info.size = Comp.sizeof; info.alignment = Comp.alignof; //8; info.init_data = Mallocator.makeArray!ubyte(Comp.sizeof); *cast(Comp*) info.init_data.ptr = Comp.init; // = Comp(); @@ -1340,7 +1344,9 @@ 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(SetFunctionAttributes!(T,functionLinkage!(s.onUpdate), functionAttributes!(s.onUpdate)) == 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); @@ -1612,6 +1618,8 @@ export struct EntityManager //fill components with default data foreach (comp; info.components) { + if (components[comp].size == 0) + continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } @@ -1621,6 +1629,8 @@ export struct EntityManager ushort index = block.entityIndex(entity); foreach (comp; info.components) { + if (components[comp].size == 0) + continue; memcpy(cast(void*) temp.entity_data.ptr + info.tmpl_deltas[comp], cast(void*) block + info.deltas[comp] + components[comp].size * index, components[comp].size); @@ -1669,6 +1679,8 @@ export struct EntityManager //fill components with default data foreach (comp; info.components) { + if (components[comp].size == 0) + continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } @@ -1736,14 +1748,19 @@ export struct EntityManager //fill components with default data and copy from base template foreach (comp; info.components) { - if (comp < base_tmpl.info.deltas.length && base_tmpl.info.deltas[comp] != ushort.max) //copy data from base component - { + if (comp < base_tmpl.info.tmpl_deltas.length + && base_tmpl.info.tmpl_deltas[comp] != ushort.max) //copy data from base component + { + if (components[comp].size == 0) + continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], base_tmpl.entity_data.ptr + base_tmpl.info.tmpl_deltas[comp], components[comp].size); } else //fill with default data { + if (components[comp].size == 0) + continue; memcpy(temp.entity_data.ptr + info.tmpl_deltas[comp], components[comp].init_data.ptr, components[comp].size); } @@ -1833,8 +1850,8 @@ export struct EntityManager } info.comp_add_info = Mallocator.makeArray!(EntityInfo*)(instance.components.length); - info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(instance.components.length, - info); + //info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(instance.components.length); + info.comp_rem_info = Mallocator.makeArray!(EntityInfo*)(info.deltas.length); foreach (comp; info.components) { @@ -1894,11 +1911,11 @@ 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]; - } + if (add_len < tmp_add.length) + for (int k = add_len; k > j; k--) + { + tmp_add[k] = tmp_add[k - 1]; + } //assign listener tmp_add[j] = cast(ushort) i; } @@ -1914,11 +1931,11 @@ 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]; - } + if (rem_len < tmp_add.length) + for (int k = rem_len; k > j; k--) + { + tmp_rem[k] = tmp_rem[k - 1]; + } //assign listener tmp_rem[j] = cast(ushort) i; } @@ -1934,11 +1951,11 @@ 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]; - } + if (ch_len < tmp_add.length) + for (int k = ch_len; k > j; k--) + { + tmp_ch[k] = tmp_ch[k - 1]; + } //assign listener tmp_ch[j] = cast(ushort) i; } @@ -2160,6 +2177,8 @@ export struct EntityManager foreach (comp; new_info.components) { uint comp_size = components[comp].size; + if (comp_size == 0) + continue; memcpy(cast(void*) new_block + new_info.deltas[comp] + new_block.entities_count * comp_size, cast(void*) block + info.deltas[comp] + ind * comp_size, comp_size); } @@ -2296,24 +2315,29 @@ export struct EntityManager foreach (id; new_info.components) //ids[0 .. len]) { - void* dst = cast(void*) new_block + new_info.deltas[id] + ( - new_block.entities_count) * components[id].size; uint size = components[id].size; + void* dst = void; + if (size != 0) + dst = cast(void*) new_block + new_info.deltas[id] + (new_block.entities_count) + * size; if (k >= new_ids.length) { - memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); + if (size != 0) + memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } else if (j >= info.components.length || id == new_ids[k]) { - memcpy(dst, data_pointers[k], size); + if (size != 0) + memcpy(dst, data_pointers[k], size); k++; } else { assert(id != new_ids[0]); - memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); + if (size != 0) + memcpy(dst, cast(void*) block + info.deltas[id] + ind * size, size); j++; } } @@ -2376,28 +2400,30 @@ export struct EntityManager //__addComponents(entity_id, new_ids, pointers); ComponentRef[num] _comps; - static foreach(i, comp; comps) + static foreach (i, comp; comps) { _comps[i] = comp.ref_; } addComponents(entity_id, _comps); - + } export void addComponents(const EntityID entity_id, ComponentRef[] comps) nothrow @nogc { - uint num = cast(uint)comps.length; + uint num = cast(uint) comps.length; 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]); - foreach(ref_; comps) + foreach (ref_; comps) { data.changeEntitiesList.add((cast(ubyte*)&ref_.component_id)[0 .. ushort.sizeof]); } - foreach(ref_; comps) + foreach (ref_; comps) { - data.changeEntitiesList.add((cast(ubyte*)ref_.ptr)[0 .. components[ref_.component_id].size]); + if (components[ref_.component_id].size != 0) + data.changeEntitiesList.add( + (cast(ubyte*) ref_.ptr)[0 .. components[ref_.component_id].size]); } /*data.changeEntitiesList.add(cast(ubyte[]) new_ids); static foreach (i, comp; comps) @@ -2449,14 +2475,15 @@ export struct EntityManager foreach (i, comp; info.components) { - memcpy(cast(void*) new_block + info.deltas[comp] + components[comp].size * new_id, - cast(void*) block + info.deltas[comp] + components[comp].size * index, - components[comp].size); + ushort size = components[comp].size; + if (size != 0) + memcpy(cast(void*) new_block + info.deltas[comp] + size * new_id, + cast(void*) block + info.deltas[comp] + size * index, size); if (components[comp].create_callback) { - components[comp].create_callback(cast( - void*) block + info.deltas[comp] + new_id * components[comp].size); + components[comp].create_callback( + cast(void*) block + info.deltas[comp] + new_id * size); } } @@ -2552,20 +2579,21 @@ export struct EntityManager foreach (comp; info.components) { uint size = components[comp].size; - memcpy(cast(void*) block + info.deltas[comp] + size * id, - tmpl.entity_data.ptr + info.tmpl_deltas[comp], size); + if (size != 0) + memcpy(cast(void*) block + info.deltas[comp] + size * id, + tmpl.entity_data.ptr + info.tmpl_deltas[comp], size); } - foreach(comp; replacement) + foreach (comp; replacement) { - if(comp.component_id < info.deltas.length) + if (comp.component_id < info.deltas.length) { ushort delta = info.deltas[comp.component_id]; - if(delta != ushort.max) + if (delta != ushort.max) { uint size = components[comp.component_id].size; - memcpy(cast(void*) block + delta + size * id, - comp.ptr, size); + if (size != 0) + memcpy(cast(void*) block + delta + size * id, comp.ptr, size); } } } @@ -2678,8 +2706,10 @@ export struct EntityManager { //get entity and block meta data pointers Entity* entity = id_manager.getEntityPointer(id); + if (entity is null) return; //return if entity doesn't exist + EntitiesBlock* block = getMetaData(entity); EntityInfo* info = block.type_info; @@ -2700,6 +2730,9 @@ export struct EntityManager { EntityInfo* info = block.type_info; + if (info.last_block.added_count) + updateBlock(info.last_block); + info.last_block.entities_count--; uint pos = block.entityIndex(entity); @@ -2720,9 +2753,11 @@ export struct EntityManager { foreach (comp; info.components) { + uint size = components[comp].size; + if (size == 0) + continue; void* src = cast(void*) info.last_block + info.deltas[comp]; void* dst = cast(void*) block + info.deltas[comp]; - uint size = components[comp].size; memcpy(dst + pos * size, src + info.last_block.entities_count * size, size); } @@ -2768,7 +2803,8 @@ export struct EntityManager { uint index = 0; uint len = cast(uint) thread.changeEntitiesListPrev.length; - if(len)has_work = true; + if (len) + has_work = true; void*[32] pointers; // = (cast(void**) alloca(num * (void*).sizeof))[0 .. num]; while (index < len) { @@ -2798,7 +2834,7 @@ export struct EntityManager pointers[i] = &thread.changeEntitiesListPrev[index]; index += components[ids[i]].size; } - + __addComponents(id, ids, pointers[0 .. num]); } } @@ -2885,6 +2921,23 @@ export struct EntityManager (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_change_entity)(data); } + private void updateBlock(EntitiesBlock* block) @nogc nothrow + { + EntityInfo* info = block.type_info; + ushort entities_count = block.entities_count; + block.entities_count += block.added_count; + if (block.entities_count > block.type_info.max_entities) + { + block.entities_count = block.type_info.max_entities; + } + block.added_count.atomicStore(cast(ushort) 0); + + if (info.add_listeners) + { + callAddEntityListeners(info, block, entities_count, block.entities_count); + } + } + private bool updateBlocks() { bool has_work = false; @@ -2892,22 +2945,11 @@ export struct EntityManager foreach (ref ThreadData thread; threads) { //thread.swapToUpdate(); - if(thread.blockToUpdatePrev.length)has_work = true; + if (thread.blockToUpdatePrev.length) + has_work = true; foreach (block; thread.blockToUpdatePrev) { - EntityInfo* info = block.type_info; - ushort entities_count = block.entities_count; - block.entities_count += block.added_count; - if (block.entities_count > block.type_info.max_entities) - { - block.entities_count = block.type_info.max_entities; - } - block.added_count.atomicStore(cast(ushort) 0); - - if (info.add_listeners) - { - callAddEntityListeners(info, block, entities_count, block.entities_count); - } + updateBlock(block); } thread.blockToUpdatePrev.clear(); } @@ -2920,7 +2962,8 @@ export struct EntityManager //foreach (ref ThreadData thread; threads)thread.swapToRemove(); foreach (ref ThreadData thread; threads) { - if(thread.entitiesToRemovePrev.length)has_work = true; + if (thread.entitiesToRemovePrev.length) + has_work = true; foreach (id; thread.entitiesToRemovePrev) { __removeEntity(id); @@ -2936,59 +2979,60 @@ export struct EntityManager // bool empty = true; //while (1) //{ - //event_manager.swapCurrent(); - uint current_index; - if (event_manager.current_index == 0) - current_index = cast(uint) threads.length; - else - current_index = 0; - foreach (i, event; event_manager.events) + //event_manager.swapCurrent(); + uint current_index; + if (event_manager.current_index == 0) + current_index = cast(uint) threads.length; + else + current_index = 0; + foreach (i, event; event_manager.events) + { + foreach (first_block; event.first_blocks[current_index .. current_index + threads + .length]) { - foreach (first_block; event.first_blocks[current_index - .. current_index + threads.length]) + EventManager.EventBlock* block = first_block; + if (block) + has_work = true; + // { + // has_work = true; + // //empty = false; + // } + while (block) { - EventManager.EventBlock* block = first_block; - if (block)has_work = true; - // { - // has_work = true; - // //empty = false; - // } - while (block) + EventCallData call_data; + void* event_pointer = cast(void*) block + event.data_offset; + foreach (j; 0 .. block.count) { - EventCallData call_data; - void* event_pointer = cast(void*) block + event.data_offset; - foreach (j; 0 .. block.count) + call_data.event = event_pointer + EntityID.sizeof; + EntityID entity_id = *cast(EntityID*)(event_pointer); + Entity* entity = id_manager.getEntityPointer(entity_id); + if (entity) { - call_data.event = event_pointer; - EntityID entity_id = *cast(EntityID*) event_pointer; - Entity* entity = id_manager.getEntityPointer(entity_id); - if (entity) - { - call_data.block = getMetaData(entity); - call_data.id = call_data.block.entityIndex(entity); + call_data.block = getMetaData(entity); + call_data.id = call_data.block.entityIndex(entity); + call_data.entity = entity; - foreach (caller; events[i].callers) - { - if ( - call_data.block.type_info.systems[caller.system.m_id] - == false) - continue; - call_data.system_pointer = caller.system.m_system_pointer; - (cast(void function( - ref EventCallData) nothrow @nogc) caller.callback)( - call_data); - } + foreach (caller; events[i].callers) + { + if (call_data.block.type_info.systems[caller.system.m_id] == false + || !caller.system.enabled || !caller.system.willExecute) + continue; + call_data.system_pointer = caller.system.m_system_pointer; + (cast(void function(ref EventCallData) nothrow @nogc) caller + .callback)(call_data); } - if(events[i].destroy_callback)events[i].destroy_callback(event_pointer); - event_pointer += events[i].size; } - block = block.next; + if (events[i].destroy_callback) + events[i].destroy_callback(event_pointer); + event_pointer += events[i].size + EntityID.sizeof; } + block = block.next; } } - // if (empty) - // break; - // empty = true; + } + // if (empty) + // break; + // empty = true; //} return has_work; } @@ -2996,7 +3040,7 @@ export struct EntityManager private void swapData() nothrow @nogc { event_manager.swapCurrent(); - foreach(ref ThreadData thread; threads) + foreach (ref ThreadData thread; threads) { thread.swapData(); } @@ -3005,13 +3049,13 @@ export struct EntityManager export void commit() { bool has_work = true; - while(has_work) - { + while (has_work) + { swapData(); has_work = false; has_work |= updateEvents(); - + id_manager.optimize(); has_work |= updateBlocks(); has_work |= changeEntities(); @@ -3222,14 +3266,15 @@ export struct EntityManager } } - const (UpdatePass)* getPass(const (char)[] name) + const(UpdatePass)* getPass(const(char)[] name) { ushort id = getPassID(name); - if(id == ushort.max)return null; + if (id == ushort.max) + return null; return passes[id]; } - ushort getPassID(const (char)[] name) + ushort getPassID(const(char)[] name) { return passes_map.get(name, ushort.max); } @@ -3267,6 +3312,7 @@ export struct EntityManager EntitiesBlock* block; void* system_pointer; void* event; + Entity* entity; ushort id; } @@ -3357,9 +3403,9 @@ export struct EntityManager return new_info; } - EntityInfo* getNewInfoRemove(ushort id) + EntityInfo* getNewInfoRemove(ushort id) return { - if (comp_rem_info.length <= id) + /*if (comp_rem_info.length <= id) { EntityInfo*[] new_infos = Mallocator.makeArray!(EntityInfo*)( instance.components.length, &this); @@ -3371,7 +3417,7 @@ export struct EntityManager Mallocator.dispose(comp_rem_info); } comp_rem_info = new_infos; - } + }*/ if (comp_rem_info[id]) return comp_rem_info[id]; @@ -3386,8 +3432,9 @@ export struct EntityManager ids[len++] = comp; } } - if (len == components.length) - return &this; + assert(len != components.length); + //if (len == components.length) + // return &this; assert(len == components.length - 1); @@ -3455,23 +3502,14 @@ export struct EntityManager */ struct EntitiesBlock { - ///return distance (in bytes) from begin of block to data - ///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 + export void* dataBegin() nothrow @nogc pure return { ushort dif = EntitiesBlock.sizeof; return cast(void*)&this + dif; } - export ushort entityIndex(Entity* entity) nothrow @nogc pure + export ushort entityIndex(const(Entity)* entity) nothrow @nogc pure { static if (EntityID.sizeof == 8) return cast(ushort)((cast(void*) entity - dataBegin()) >> 3); @@ -3593,32 +3631,32 @@ export struct EntityManager struct ThreadData { - ref Vector!EntityID entitesToRemove() @nogc nothrow + ref Vector!EntityID entitesToRemove() @nogc nothrow return { return entities_to_remove[data_index]; } - ref SimpleVector changeEntitiesList() @nogc nothrow + ref SimpleVector changeEntitiesList() @nogc nothrow return { return change_entities_list[data_index]; } - ref Vector!(EntitiesBlock*) blockToUpdate() @nogc nothrow + ref Vector!(EntitiesBlock*) blockToUpdate() @nogc nothrow return { return blocks_to_update[data_index]; } - - ref Vector!EntityID entitiesToRemovePrev() @nogc nothrow + + ref Vector!EntityID entitiesToRemovePrev() @nogc nothrow return { return entities_to_remove[1 - data_index]; } - ref SimpleVector changeEntitiesListPrev() @nogc nothrow + ref SimpleVector changeEntitiesListPrev() @nogc nothrow return { return change_entities_list[1 - data_index]; } - ref Vector!(EntitiesBlock*) blockToUpdatePrev() @nogc nothrow + ref Vector!(EntitiesBlock*) blockToUpdatePrev() @nogc nothrow return { return blocks_to_update[1 - data_index]; } diff --git a/source/bubel/ecs/package.d b/source/bubel/ecs/package.d index ee3f62b..51da325 100644 --- a/source/bubel/ecs/package.d +++ b/source/bubel/ecs/package.d @@ -7,4 +7,4 @@ public import bubel.ecs.system; import bubel.ecs.events; import bubel.ecs.id_manager; -import bubel.ecs.std; \ No newline at end of file +import bubel.ecs.std; diff --git a/source/bubel/ecs/simple_vector.d b/source/bubel/ecs/simple_vector.d index 1de026e..bb4b610 100644 --- a/source/bubel/ecs/simple_vector.d +++ b/source/bubel/ecs/simple_vector.d @@ -16,10 +16,12 @@ struct SimpleVector ///Add element to vector void add(ubyte el) nothrow @nogc { - while(used >= data.length) + while (used >= data.length) { - if(data is null)data = Mallocator.makeArray!ubyte(1024); - else data = Mallocator.expandArray(data,data.length); + if (data is null) + data = Mallocator.makeArray!ubyte(1024); + else + data = Mallocator.expandArray(data, data.length); } data[used++] = el; } @@ -27,10 +29,12 @@ struct SimpleVector ///Add array of elements to vector void add(ubyte[] el) nothrow @nogc { - while(used + el.length >= data.length) + while (used + el.length >= data.length) { - if(data is null)data = Mallocator.makeArray!ubyte(1024); - else data = Mallocator.expandArray(data,data.length); + if (data is null) + data = Mallocator.makeArray!ubyte(1024); + else + data = Mallocator.expandArray(data, data.length); } memcpy(data.ptr + used, el.ptr, el.length); used += el.length; @@ -44,23 +48,23 @@ struct SimpleVector export ref ubyte opIndex(size_t pos) nothrow @nogc { - return data[pos]; - } + return data[pos]; + } - export ubyte[] opSlice() nothrow @nogc + export ubyte[] opSlice() nothrow @nogc { - return data[0 .. used]; - } + return data[0 .. used]; + } - export ubyte[] opSlice(size_t x, size_t y) nothrow @nogc + export ubyte[] opSlice(size_t x, size_t y) nothrow @nogc { - return data[x .. y]; - } + return data[x .. y]; + } - export size_t opDollar() nothrow @nogc + export size_t opDollar() nothrow @nogc { - return used; - } + return used; + } ///set vector length to 0 void clear() nothrow @nogc @@ -70,4 +74,4 @@ struct SimpleVector ubyte[] data = null; size_t used = 0; -} \ No newline at end of file +} diff --git a/source/bubel/ecs/std.d b/source/bubel/ecs/std.d index 6658639..a7dd846 100644 --- a/source/bubel/ecs/std.d +++ b/source/bubel/ecs/std.d @@ -7,45 +7,50 @@ License: BSD 3-clause, see LICENSE file in project root folder. */ module bubel.ecs.std; -version(Emscripten)version = ECSEmscripten; +version (Emscripten) version = ECSEmscripten; import std.traits; -version(ECSEmscripten) +version (ECSEmscripten) { - extern(C) struct pthread_mutex_t - { - union - { - int[6] __i; - void[6] *__p; + extern (C) struct pthread_mutex_t + { + union + { + int[6] __i; + void[6]* __p; } - } - - extern(C) struct pthread_mutexattr_t - { - uint __attr; } - extern(C) int memcmp (const void *s1, const void *s2, size_t size); - extern(C) void exit (int status) nothrow @nogc; - extern(C) void __assert(const(char)* msg, const(char)* file, uint line) { exit(-20);} - extern(C) void free(void*) @nogc nothrow @system; - extern(C) void* malloc(size_t size) @nogc nothrow @system; - extern(C) void* realloc(void*, size_t size) @nogc nothrow @system; - extern(C) void* memcpy(return void*, scope const void*, size_t size) @nogc nothrow @system; - extern(C) void* memset(void*, int val, size_t size) @nogc nothrow @system; - extern(C) int posix_memalign(void**, size_t, size_t) @nogc nothrow @system; - extern(C) void qsort(void* base, size_t num, size_t size, int function(const void*,const void*) compar) @nogc nothrow @system; + extern (C) struct pthread_mutexattr_t + { + uint __attr; + } - extern(C) int pthread_mutex_lock(pthread_mutex_t *mutex) @nogc nothrow; - extern(C) int pthread_mutex_trylock(pthread_mutex_t *mutex) @nogc nothrow; - extern(C) int pthread_mutex_unlock(pthread_mutex_t *mutex) @nogc nothrow; - extern(C) void pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) @nogc nothrow; - extern(C) void pthread_mutexattr_destroy(pthread_mutexattr_t *attr) @nogc nothrow; - extern(C) int pthread_mutexattr_init(pthread_mutexattr_t *attr) @nogc nothrow; - extern(C) int pthread_mutex_destroy(pthread_mutex_t *mutex) @nogc nothrow; - extern(C) int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) @nogc nothrow; + extern (C) int memcmp(const void* s1, const void* s2, size_t size); + extern (C) void exit(int status) nothrow @nogc; + extern (C) void __assert(const(char)* msg, const(char)* file, uint line) + { + exit(-20); + } + + extern (C) void free(void*) @nogc nothrow @system; + extern (C) void* malloc(size_t size) @nogc nothrow @system; + extern (C) void* realloc(void*, size_t size) @nogc nothrow @system; + extern (C) void* memcpy(return void*, scope const void*, size_t size) @nogc nothrow @system; + extern (C) void* memset(void*, int val, size_t size) @nogc nothrow @system; + extern (C) int posix_memalign(void**, size_t, size_t) @nogc nothrow @system; + extern (C) void qsort(void* base, size_t num, size_t size, + int function(const void*, const void*) compar) @nogc nothrow @system; + + extern (C) int pthread_mutex_lock(pthread_mutex_t* mutex) @nogc nothrow; + extern (C) int pthread_mutex_trylock(pthread_mutex_t* mutex) @nogc nothrow; + extern (C) int pthread_mutex_unlock(pthread_mutex_t* mutex) @nogc nothrow; + extern (C) void pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type) @nogc nothrow; + extern (C) void pthread_mutexattr_destroy(pthread_mutexattr_t* attr) @nogc nothrow; + extern (C) int pthread_mutexattr_init(pthread_mutexattr_t* attr) @nogc nothrow; + extern (C) int pthread_mutex_destroy(pthread_mutex_t* mutex) @nogc nothrow; + extern (C) int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) @nogc nothrow; } else @@ -55,23 +60,24 @@ else public import core.stdc.stdlib : qsort; } -version(ECSEmscripten) +version (ECSEmscripten) { } else version (Windows) { import core.sys.windows.windows; - extern(Windows) void* _aligned_malloc(size_t size,size_t alignment) @nogc nothrow @system; - extern(Windows) void _aligned_free(void* ptr) @nogc nothrow @system; - - version(LDC) + + extern (Windows) void* _aligned_malloc(size_t size, size_t alignment) @nogc nothrow @system; + extern (Windows) void _aligned_free(void* ptr) @nogc nothrow @system; + + version (LDC) { /*extern(Windows) void* __alloca(size_t size) @nogc nothrow @system; alias alloca = __alloca;*/ - extern(Windows) void ___chkstk_ms() @nogc nothrow @system; - - extern(Windows) void __chkstk() + extern (Windows) void ___chkstk_ms() @nogc nothrow @system; + + extern (Windows) void __chkstk() { ___chkstk_ms(); } @@ -80,17 +86,18 @@ else version (Windows) else version (Posix) { import core.sys.posix.pthread; - import core.sys.posix.stdlib; + import core.sys.posix.stdlib : posix_memalign; } -version(ECSEmscripten) +version (ECSEmscripten) { - private const uint max_alloca = 10000; + private const uint max_alloca = 10000; private __gshared byte[max_alloca] alloca_array; private __gshared uint alloca_pos = 0; - export extern(C) void* alloca(size_t length) @nogc nothrow + export extern (C) void* alloca(size_t length) @nogc nothrow { - if(alloca_pos + length > max_alloca)alloca_pos = 0; + if (alloca_pos + length > max_alloca) + alloca_pos = 0; void* ret = &alloca_array[alloca_pos]; alloca_pos += length; return ret; @@ -101,28 +108,44 @@ version(ECSEmscripten) return null; }*/ } -else version(D_BetterC) +else version (D_BetterC) { - private const uint max_alloca = 10000; + private const uint max_alloca = 10000; private __gshared byte[max_alloca] alloca_array; private uint alloca_pos = 0; - export extern(C) void* alloca(size_t length) @nogc nothrow + export extern (C) void* __alloca(size_t length) @nogc nothrow { - if(alloca_pos + length > max_alloca)alloca_pos = 0; + if (alloca_pos + length > max_alloca) + alloca_pos = 0; void* ret = &alloca_array[alloca_pos]; alloca_pos += length; return ret; } - version(GNU) + alias alloca = __alloca; + + version (DigitalMars) { - extern(C) void __gdc_personality_v0() + export extern (C) float* _memsetFloat(float* p, float value, size_t count) @nogc nothrow + { + float* pstart = p; + float* ptop; + + for (ptop = &p[count]; p < ptop; p++) + *p = value; + return pstart; + } + } + + version (GNU) + { + extern (C) void __gdc_personality_v0() { } } } -else +else { public import core.stdc.stdlib : alloca; } @@ -131,13 +154,13 @@ static struct Mallocator { static T[] makeArray(T)(size_t length) nothrow @nogc { - T[] ret = (cast(T*)malloc(T.sizeof * length))[0 .. length]; + T[] ret = (cast(T*) malloc(T.sizeof * length))[0 .. length]; - static if(__traits(isPOD, T)) + static if (__traits(isPOD, T)) { static immutable T init = T.init; - - foreach(i;0..ret.length) + + foreach (i; 0 .. ret.length) { memcpy(&ret[i], &init, T.sizeof); } @@ -145,7 +168,8 @@ static struct Mallocator else { static import std.conv; - foreach(i;0..ret.length) + + foreach (i; 0 .. ret.length) { std.conv.emplace(&ret[i]); } @@ -155,78 +179,94 @@ static struct Mallocator static T[] makeArray(T)(size_t length, T initializer) nothrow @nogc { - T[] ret = (cast(T*)malloc(T.sizeof * length))[0 .. length]; - foreach(ref v; ret)v = initializer; + T[] ret = (cast(T*) malloc(T.sizeof * length))[0 .. length]; + foreach (ref v; ret) + v = initializer; return ret; } static T[] expandArray(T)(T[] array, size_t length) nothrow @nogc { size_t new_length = array.length + length; - return (cast(T*)realloc(array.ptr, T.sizeof * new_length))[0 .. new_length]; + return (cast(T*) realloc(array.ptr, T.sizeof * new_length))[0 .. new_length]; } static T[] makeArray(T)(T[] array) nothrow @nogc { - T[] ret = (cast(T*)malloc(T.sizeof * array.length))[0 .. array.length];//Mallocator.makeArray!(T)(array.length); - foreach(i, ref v;ret)v = array[i]; + T[] ret = (cast(T*) malloc(T.sizeof * array.length))[0 .. array.length]; //Mallocator.makeArray!(T)(array.length); + foreach (i, ref v; ret) + v = array[i]; return ret; } - static T* make(T, Args...)(Args args) + static T* make(T, Args...)(Args args) { - T* ret = cast(T*)malloc(T.sizeof); + T* ret = cast(T*) malloc(T.sizeof); static import std.conv; - static if(__traits(isPOD, T)) + + static if (__traits(isPOD, T)) { static immutable T init = T.init; memcpy(ret, &init, T.sizeof); } - else static if(is(T == struct))std.conv.emplace(ret, args); + else static if (is(T == struct)) + std.conv.emplace(ret, args); return ret; } static void* alignAlloc(size_t length, size_t alignment) nothrow @nogc { void* ret; - version(Posix)posix_memalign(&ret, alignment, length);//ret = aligned_alloc(alignment, length); - else version(Windows)ret = _aligned_malloc(length, alignment); - else version(ECSEmscripten)posix_memalign(&ret, alignment, length);//malloc(length); - else static assert(0, "Unimplemented platform!"); + version (Posix) + posix_memalign(&ret, alignment, length); //ret = aligned_alloc(alignment, length); + else version (Windows) + ret = _aligned_malloc(length, alignment); + else version (ECSEmscripten) + posix_memalign(&ret, alignment, length); //malloc(length); + else + static assert(0, "Unimplemented platform!"); return ret; } static void dispose(T)(T object) nothrow @nogc { - static if(__traits(hasMember, T, "__xdtor"))object.__xdtor(); - else static if(__traits(hasMember, T, "__dtor"))object.__dtor(); - free(cast(void*)object); + static if (__traits(hasMember, T, "__xdtor")) + object.__xdtor(); + else static if (__traits(hasMember, T, "__dtor")) + object.__dtor(); + free(cast(void*) object); } static void alignDispose(T)(T object) { - static if(__traits(hasMember, T, "__xdtor"))object.__xdtor(); - else static if(__traits(hasMember, T, "__dtor"))object.__dtor(); - version(Posix)free(cast(void*)object); - else version(Windows)_aligned_free(cast(void*)object); - else version(ECSEmscripten)free(cast(void*)object); - else static assert(0, "Unimplemented platform!"); + static if (__traits(hasMember, T, "__xdtor")) + object.__xdtor(); + else static if (__traits(hasMember, T, "__dtor")) + object.__dtor(); + version (Posix) + free(cast(void*) object); + else version (Windows) + _aligned_free(cast(void*) object); + else version (ECSEmscripten) + free(cast(void*) object); + else + static assert(0, "Unimplemented platform!"); } } struct Mutex { - version(ECSEmscripten) + version (ECSEmscripten) { void initialize() nothrow @nogc { pthread_mutexattr_t attr = void; //pthread_mutexattr_init(&attr); - + //pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(cast(pthread_mutex_t*) &m_handle, &attr); + pthread_mutex_init(cast(pthread_mutex_t*)&m_handle, &attr); //pthread_mutexattr_destroy(&attr); } @@ -257,7 +297,7 @@ struct Mutex { void initialize() nothrow @nogc { - InitializeCriticalSection(cast(CRITICAL_SECTION*) &m_handle); + InitializeCriticalSection(cast(CRITICAL_SECTION*)&m_handle); } void destroy() nothrow @nogc @@ -280,7 +320,7 @@ struct Mutex return TryEnterCriticalSection(&m_handle) != 0; } - CRITICAL_SECTION m_handle; + CRITICAL_SECTION m_handle; } else version (Posix) { @@ -289,9 +329,9 @@ struct Mutex pthread_mutexattr_t attr = void; pthread_mutexattr_init(&attr); - + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(cast(pthread_mutex_t*) &m_handle, &attr); + pthread_mutex_init(cast(pthread_mutex_t*)&m_handle, &attr); pthread_mutexattr_destroy(&attr); } @@ -318,5 +358,6 @@ struct Mutex private pthread_mutex_t m_handle; } - else static assert(0, "unsupported platform!"); -} \ No newline at end of file + else + static assert(0, "unsupported platform!"); +} diff --git a/source/bubel/ecs/vector.d b/source/bubel/ecs/vector.d index 1ec8a93..ca6cd6c 100644 --- a/source/bubel/ecs/vector.d +++ b/source/bubel/ecs/vector.d @@ -1,30 +1,36 @@ module bubel.ecs.vector; import core.bitop; + //import core.stdc.stdlib : free, malloc; import bubel.ecs.std; + //import core.stdc.string : memcpy, memset; //import std.algorithm : swap; import std.conv : emplace; import std.traits : hasMember, isCopyable, TemplateOf, Unqual; -export @nogc @safe nothrow pure size_t nextPow2(size_t num) { +export @nogc @safe nothrow pure size_t nextPow2(size_t num) +{ return 1 << bsr(num) + 1; } export __gshared size_t gVectorsCreated = 0; export __gshared size_t gVectorsDestroyed = 0; -struct Vector(T) { +struct Vector(T) +{ T[] array; size_t used; public: - export this()(T t) { + export this()(T t) + { add(t); } - export this(X)(X[] t) if (is(Unqual!X == Unqual!T)) { + export this(X)(X[] t) if (is(Unqual!X == Unqual!T)) + { add(t); } @@ -42,79 +48,101 @@ public: @disable this(this); - export ~this() { + export ~this() + { clear(); } - export void clear() { + export void clear() + { removeAll(); } - export void removeAll() { - if (array !is null) { + export void removeAll() + { + if (array !is null) + { /*foreach (ref el; array[0 .. used]) { destroy(el); }*/ //freeData(cast(void[]) array); - freeData((cast(void*)array.ptr)[0 .. array.length * T.sizeof]); + freeData((cast(void*) array.ptr)[0 .. array.length * T.sizeof]); gVectorsDestroyed++; } array = null; used = 0; } - export bool empty() const { + export bool empty() const + { return (used == 0); } - export size_t length() const { + export size_t length() const + { return used; } - export void length(size_t newLength) { - if (newLength > used) { + export void length(size_t newLength) + { + if (newLength > used) + { reserve(newLength); - foreach (ref el; array[used .. newLength]) { + foreach (ref el; array[used .. newLength]) + { emplace(&el); } - } else { - foreach (ref el; array[newLength .. used]) { + } + else + { + foreach (ref el; array[newLength .. used]) + { //destroy(el); - static if(__traits(hasMember, T, "__xdtor"))el.__xdtor(); - else static if(__traits(hasMember, T, "__dtor"))el.__dtor(); + static if (__traits(hasMember, T, "__xdtor")) + el.__xdtor(); + else static if (__traits(hasMember, T, "__dtor")) + el.__dtor(); } } used = newLength; } - export void reset() { + export void reset() + { used = 0; } - export void reserve(size_t numElements) { - if (numElements > array.length) { + export void reserve(size_t numElements) + { + if (numElements > array.length) + { extend(numElements); } } - export size_t capacity() { + export size_t capacity() + { return array.length - used; } - export void extend(size_t newNumOfElements) { + export void extend(size_t newNumOfElements) + { auto oldArray = manualExtend(array, newNumOfElements); - if (oldArray !is null) { + if (oldArray !is null) + { freeData(oldArray); } } - export @nogc void freeData(void[] data) { + export @nogc void freeData(void[] data) + { // 0x0F probably invalid value for pointers and other types memset(data.ptr, 0x0F, data.length); // Makes bugs show up xD free(data.ptr); } - export static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0) { + export static void[] manualExtend(ref T[] array, size_t newNumOfElements = 0) + { if (newNumOfElements == 0) newNumOfElements = 2; if (array.length == 0) @@ -126,22 +154,26 @@ public: memcpy(cast(void*) memory, cast(void*) oldArray.ptr, oldSize); array = memory[0 .. newNumOfElements]; //return cast(void[]) oldArray; - return (cast(void*)oldArray.ptr)[0 .. oldArray.length * T.sizeof]; + return (cast(void*) oldArray.ptr)[0 .. oldArray.length * T.sizeof]; } - export Vector!T copy()() { + export Vector!T copy()() + { Vector!T duplicate; duplicate.reserve(used); duplicate ~= array[0 .. used]; return duplicate; } - export bool canAddWithoutRealloc(uint elemNum = 1) { + export bool canAddWithoutRealloc(uint elemNum = 1) + { return used + elemNum <= array.length; } - export void add()(T t) { - if (used >= array.length) { + export void add()(T t) + { + if (used >= array.length) + { extend(nextPow2(used + 1)); } emplace(&array[used], t); @@ -149,48 +181,62 @@ public: } /// Add element at given position moving others - export void add()(T t, size_t pos) { + export void add()(T t, size_t pos) + { assert(pos <= used); - if (used >= array.length) { + if (used >= array.length) + { extend(array.length * 2); } - foreach_reverse (size_t i; pos .. used) { + foreach_reverse (size_t i; pos .. used) + { //swap(array[i + 1], array[i]); - array[i+1] = array[i]; + array[i + 1] = array[i]; } emplace(&array[pos], t); used++; } - export void add(X)(X[] t) if (is(Unqual!X == Unqual!T)) { - if (used + t.length > array.length) { + export void add(X)(X[] t) if (is(Unqual!X == Unqual!T)) + { + if (used + t.length > array.length) + { extend(nextPow2(used + t.length)); } - foreach (i; 0 .. t.length) { + foreach (i; 0 .. t.length) + { emplace(&array[used + i], t[i]); } used += t.length; } - export void remove(size_t elemNum) { + export void remove(size_t elemNum) + { //destroy(array[elemNum]); - static if(__traits(hasMember, T, "__xdtor"))array[elemNum].__xdtor(); - else static if(__traits(hasMember, T, "__dtor"))array[elemNum].__dtor(); + static if (__traits(hasMember, T, "__xdtor")) + array[elemNum].__xdtor(); + else static if (__traits(hasMember, T, "__dtor")) + array[elemNum].__dtor(); //swap(array[elemNum], array[used - 1]); array[elemNum] = array[used - 1]; used--; } - export void removeStable()(size_t elemNum) { + export void removeStable()(size_t elemNum) + { used--; - foreach (i; 0 .. used) { + foreach (i; 0 .. used) + { array[i] = array[i + 1]; } } - export bool tryRemoveElement()(T elem) { - foreach (i, ref el; array[0 .. used]) { - if (el == elem) { + export bool tryRemoveElement()(T elem) + { + foreach (i, ref el; array[0 .. used]) + { + if (el == elem) + { remove(i); return true; } @@ -198,55 +244,66 @@ public: return false; } - export void removeElement()(T elem) { + export void removeElement()(T elem) + { bool ok = tryRemoveElement(elem); assert(ok, "There is no such an element in vector"); } - export ref T opIndex(size_t elemNum) const { + export ref T opIndex(size_t elemNum) const + { //debug assert(elemNum < used, "Range violation [index]"); return *cast(T*)&array.ptr[elemNum]; } - export auto opSlice() { + export auto opSlice() + { return array.ptr[0 .. used]; } - export T[] opSlice(size_t x, size_t y) { + export T[] opSlice(size_t x, size_t y) + { assert(y <= used); return array.ptr[x .. y]; } - export size_t opDollar() { + export size_t opDollar() + { return used; } - export void opAssign(X)(X[] slice) { + export void opAssign(X)(X[] slice) + { reset(); this ~= slice; } - export void opOpAssign(string op)(T obj) { + export void opOpAssign(string op)(T obj) + { //static assert(op == "~"); add(obj); } - export void opOpAssign(string op, X)(X[] obj) { + export void opOpAssign(string op, X)(X[] obj) + { //static assert(op == "~"); add(obj); } - export void opIndexAssign()(T obj, size_t elemNum) { + export void opIndexAssign()(T obj, size_t elemNum) + { assert(elemNum < used, "Range viloation"); array[elemNum] = obj; } - export void opSliceAssign()(T[] obj, size_t a, size_t b) { + export void opSliceAssign()(T[] obj, size_t a, size_t b) + { assert(b <= used && a <= b, "Range viloation"); array.ptr[a .. b] = obj; } - export bool opEquals()(auto ref const Vector!(T) r) const { + export bool opEquals()(auto ref const Vector!(T) r) const + { return used == r.used && array.ptr[0 .. used] == r.array.ptr[0 .. r.used]; } -} \ No newline at end of file +} diff --git a/tests/basic.d b/tests/basic.d index ab9acec..b8e1d2c 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -68,6 +68,11 @@ struct CUnregistered short value = 12; } +struct CFlag +{ + mixin ECS.Component; +} + struct LongAddSystem { mixin ECS.System; @@ -110,6 +115,7 @@ struct EmptySystem void beforeEveryTest() { + CUnregistered.component_id = ushort.max; gEM.initialize(0); gEM.beginRegister(); @@ -119,6 +125,7 @@ void beforeEveryTest() gEM.registerComponent!CDouble; gEM.registerComponent!CLong; gEM.registerComponent!CShort; + gEM.registerComponent!CFlag; gEM.endRegister(); } @@ -131,11 +138,12 @@ void afterEveryTest() @("AddEntity") unittest { - ushort[2] ids = [CInt.component_id, CFloat.component_id]; - EntityTemplate* tmpl_ = gEM.allocateTemplate(ids); - assert(tmpl_.info.components.length == 2); + EntityTemplate* tmpl_ = gEM.allocateTemplate([CInt.component_id, CFloat.component_id, CFlag.component_id].staticArray); + assert(tmpl_.info.components.length == 3); + assert(tmpl_.info.size == (CInt.sizeof + CFloat.sizeof + EntityID.sizeof)); assert(tmpl_.getComponent!CInt); assert(tmpl_.getComponent!CFloat); + assert(tmpl_.getComponent!CFlag); assert(!tmpl_.getComponent!CLong); assert(!tmpl_.getComponent!CUnregistered); assert(*tmpl_.getComponent!CInt == 1); @@ -154,13 +162,66 @@ unittest assert(*entity2.getComponent!CInt == 2); assert(*entity2.getComponent!CFloat == 2.0); - CInt cint = CInt(10); - CLong clong; - Entity* entity3 = gEM.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray); + //CInt cint = CInt(10); + //CLong clong; + //Entity* entity3 = gEM.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray); + Entity* entity3 = gEM.addEntity(tmpl_, [CInt(10).ref_, CLong().ref_, CFlag().ref_].staticArray); + EntityID id = entity3.id; assert(entity3.getComponent!CInt); assert(entity3.getComponent!CFloat); assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CFloat == 2.0); + + gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_]); + gEM.commit(); + entity3 = gEM.getEntity(id); + assert(entity3.getComponent!CInt); + assert(entity3.getComponent!CFloat); + assert(entity3.getComponent!CFlag); + assert(entity3.getComponent!CShort); + assert(*entity3.getComponent!CInt == 10); + assert(*entity3.getComponent!CFloat == 2.0); + assert(*entity3.getComponent!CShort == 2); + + gEM.removeComponents(entity3.id, [CFlag().component_id,CShort(2).component_id]); + gEM.commit(); + entity3 = gEM.getEntity(id); + assert(entity3.getComponent!CInt); + assert(entity3.getComponent!CFloat); + assert(!entity3.getComponent!CFlag); + assert(!entity3.getComponent!CShort); + assert(*entity3.getComponent!CInt == 10); + assert(*entity3.getComponent!CFloat == 2.0); + + gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_]); + gEM.removeComponents(entity3.id, [CUnregistered.component_id]); + gEM.commit(); + entity3 = gEM.getEntity(id); + assert(entity3.getComponent!CInt); + assert(entity3.getComponent!CFloat); + assert(entity3.getComponent!CFlag); + assert(entity3.getComponent!CShort); + assert(*entity3.getComponent!CInt == 10); + assert(*entity3.getComponent!CFloat == 2.0); + assert(*entity3.getComponent!CShort == 2); + + gEM.beginRegister(); + + gEM.registerComponent!CUnregistered; + + gEM.endRegister(); + + gEM.addComponents(entity3.id, [CUnregistered(4).ref_]); + gEM.commit(); + entity3 = gEM.getEntity(id); + assert(entity3.getComponent!CUnregistered); + assert(*entity3.getComponent!CUnregistered == 4); + + gEM.removeComponents(entity3.id, [CUnregistered.component_id]); + gEM.commit(); + entity3 = gEM.getEntity(id); + assert(!entity3.getComponent!CUnregistered); + } //allocate templates @@ -171,32 +232,48 @@ unittest 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); + EntityTemplate* tmpl_cp = gEM.allocateTemplate(tmpl_); assert(tmpl_d.info == tmpl_.info); + assert(tmpl_cp.info == tmpl_cp.info); 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_cp.getComponent!CFloat); + assert(tmpl_cp.getComponent!CInt); + assert(tmpl_.getComponent!CInt != tmpl_cp.getComponent!CInt); + assert(tmpl_.getComponent!CFloat != tmpl_cp.getComponent!CFloat); + assert(*tmpl_.getComponent!CInt == *tmpl_cp.getComponent!CInt); + assert(*tmpl_.getComponent!CFloat == *tmpl_cp.getComponent!CFloat); *tmpl_.getComponent!CInt = 4; *tmpl_.getComponent!CFloat = 5.0; - //allocate template from template with additional component - ushort[1] ids2 = [CDouble.component_id]; + //allocate template from template with additional components + ushort[2] ids2 = [CDouble.component_id,CFlag.component_id]; EntityTemplate* tmpl_2 = gEM.allocateTemplate(tmpl_, ids2); - assert(tmpl_2.info.components.length == 3); + assert(tmpl_2.info.components.length == 4); assert(tmpl_2.getComponent!CInt); assert(tmpl_2.getComponent!CFloat); assert(tmpl_2.getComponent!CDouble); + assert(tmpl_2.getComponent!CFlag); assert(*tmpl_2.getComponent!CInt == 4); assert(*tmpl_2.getComponent!CFloat == 5.0); assert(*tmpl_2.getComponent!CDouble == 3.0); + assert(tmpl_.info.blocksCount() == 0); + Entity* entity = gEM.addEntity(tmpl_); - gEM.addComponents(entity.id, CDouble(8.0)); + gEM.addComponents(entity.id, CFloat(100)); + gEM.addComponents(entity.id, CDouble(8.0), CFloat(100)); + + assert(tmpl_.info.blocksCount() == 1); //apply entity changes gEM.commit(); + assert(tmpl_.info.blocksCount() == 0); + //allocate template as entity copy EntityTemplate* tmpl_3 = gEM.allocateTemplate(entity.id); assert(tmpl_3.info.components.length == 3); @@ -217,18 +294,20 @@ unittest assert(*tmpl_4.getComponent!CFloat == 2.0); assert(*tmpl_4.getComponent!CDouble == 3.0); - //allocate template from template with two additional component - ushort[2] ids3 = [CDouble.component_id, CLong.component_id]; - EntityTemplate* tmpl_5 = gEM.allocateTemplate(tmpl_, ids3); - assert(tmpl_5.info.components.length == 4); + //allocate template from template with three additional component + ushort[3] ids3 = [CDouble.component_id, CLong.component_id, CShort.component_id]; + EntityTemplate* tmpl_5 = gEM.allocateTemplate(tmpl_2, ids3); + assert(tmpl_5.info.components.length == 6); assert(tmpl_5.getComponent!CInt); assert(tmpl_5.getComponent!CFloat); assert(tmpl_5.getComponent!CDouble); assert(tmpl_5.getComponent!CLong); + assert(tmpl_5.getComponent!CShort); assert(*tmpl_5.getComponent!CInt == 4); assert(*tmpl_5.getComponent!CFloat == 5.0); assert(*tmpl_5.getComponent!CDouble == 3.0); assert(*tmpl_5.getComponent!CLong == 10); + assert(*tmpl_5.getComponent!CShort == 12); //allocate template from template without one component ushort[1] rem_ids = [CFloat.component_id]; @@ -239,7 +318,7 @@ unittest //allocate template from template without one component and two additional EntityTemplate* tmpl_7 = gEM.allocateTemplate(tmpl_, ids3, rem_ids); - assert(tmpl_7.info.components.length == 3); + assert(tmpl_7.info.components.length == 4); assert(tmpl_7.getComponent!CInt); assert(tmpl_7.getComponent!CDouble); assert(tmpl_7.getComponent!CLong); @@ -496,6 +575,10 @@ unittest gEM.endRegister(); + assert(gEM.getPass("custom")); + assert(!gEM.getPass("custommm")); + + LongAddSystem* system = gEM.getSystem!LongAddSystem; assert(system !is null); assert(system.updates_count == 0); @@ -1131,9 +1214,9 @@ unittest assert(*entity2.getComponent!CShort == 12); gEM.sendEvent(id,ETest()); - gEM.sendEvent(id,ETest2(id,10)); + gEM.sendEvent(id,ETest2(10)); gEM.sendEvent(id2,ETest()); - gEM.sendEvent(id2,ETest2(id2,12)); + gEM.sendEvent(id2,ETest2(12)); gEM.commit(); assert(ETest2.destory == 2); @@ -1144,7 +1227,7 @@ unittest gEM.addComponents(id, CInt(2), CShort(1)); gEM.sendEvent(id,ETest()); - gEM.sendEvent(id,ETest2(id,2)); + gEM.sendEvent(id,ETest2(2)); gEM.commit(); assert(ETest2.destory == 3); @@ -1157,7 +1240,7 @@ unittest foreach(i;0..10000) { gEM.sendEvent(id,ETest()); - gEM.sendEvent(id,ETest2(id,4)); + gEM.sendEvent(id,ETest2(4)); result += 16; result += 8; } @@ -1321,22 +1404,6 @@ unittest } } - 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(0); @@ -1462,22 +1529,6 @@ unittest } } - 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.registerDependency(TestDependency); diff --git a/tests/bugs.d b/tests/bugs.d new file mode 100644 index 0000000..49e3b12 --- /dev/null +++ b/tests/bugs.d @@ -0,0 +1,142 @@ +module tests.bugs; + +import tests.basic; + +import bubel.ecs.core; +import bubel.ecs.manager; +import bubel.ecs.system; +import bubel.ecs.attributes; + +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; + +@("Bug0001") +unittest +{ + struct Event1 + { + mixin ECS.Event; + + EntityID id; + } + + struct Event2 + { + mixin ECS.Event; + } + + struct System1 + { + mixin ECS.System; + + struct EntitiesData + { + CInt[] int_; + } + + EntityTemplate* tmpl; + EntityID id; + + void onCreate() + { + tmpl = gEM.allocateTemplate([CInt.component_id, CLong.component_id].staticArray); + } + + void onDestroy() + { + gEM.freeTemplate(tmpl); + } + + void handleEvent(Entity* entity, Event1 event) + { + gEM.removeEntity(event.id); + gEM.sendEvent(entity.id,Event2()); + } + + void handleEvent(Entity* entity, Event2 event) + { + id = gEM.addEntity(tmpl,[CInt(2).ref_, CLong(8).ref_].staticArray).id; + } + } + + struct System2 + { + mixin ECS.System; + + struct EntitiesData + { + Entity[] entity; + } + + ///check if every entity was removed correctly + void onUpdate(EntitiesData data) + { + assert(0); + } + } + + struct System3 + { + mixin ECS.System; + + struct EntitiesData + { + uint length; + Entity[] entity; + } + + ///remove every entity + void onUpdate(EntitiesData data) + { + foreach(i;0..data.length)gEM.removeEntity(data.entity[i].id); + } + } + + gEM.initialize(0); + + gEM.beginRegister(); + + gEM.registerComponent!CInt; + gEM.registerComponent!CFloat; + gEM.registerComponent!CDouble; + gEM.registerComponent!CLong; + gEM.registerComponent!CShort; + gEM.registerComponent!CFlag; + + gEM.registerEvent!Event1; + gEM.registerEvent!Event2; + + gEM.registerSystem!System1(0); + gEM.registerSystem!System2(-200); + gEM.registerSystem!System3(-200); + + gEM.endRegister(); + + EntityTemplate* tmpl = gEM.allocateTemplate([CInt.component_id, CLong.component_id].staticArray); + EntityID id = gEM.addEntity(tmpl,[CLong(10).ref_, CInt(6).ref_].staticArray).id; + EntityID id2 = gEM.addEntity(tmpl,[CInt(4).ref_].staticArray).id; + gEM.freeTemplate(tmpl); + gEM.commit(); + + gEM.sendEvent(id2, Event1(id)); + + gEM.getSystem(System2.system_id).disable(); + + gEM.begin(); + gEM.update(); + gEM.end(); + + gEM.getSystem(System2.system_id).enable(); + + gEM.begin(); + gEM.update(); + gEM.end(); + + gEM.destroy(); +} \ No newline at end of file diff --git a/tests/runner.d b/tests/runner.d index f299664..b14dafe 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -210,7 +210,7 @@ struct TestRunner(Args...) else test.name = attributes[0]; - static if (__traits(hasMember, module_, "beforeEveryTest")) + static if (__traits(hasMember, module_, "beforeEveryTest") && __traits(compiles, module_.beforeEveryTest())) module_.beforeEveryTest(); if(before)before(); @@ -256,7 +256,7 @@ struct TestRunner(Args...) else suite.failed++; suite.tests ~= test; - static if (__traits(hasMember, module_, "afterEveryTest")) + static if (__traits(hasMember, module_, "afterEveryTest") && __traits(compiles, module_.beforeEveryTest())) module_.afterEveryTest(); } passed += suite.passed; @@ -420,7 +420,7 @@ extern (C) int main(int argc, char** args) } } - TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf) runner; + TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf, tests.bugs) runner; runner.runTests(include[], exclude[]); -- 2.47.2 From 2f827a94db33c34445044a11056580f3c85563a5 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 27 May 2020 18:22:55 +0200 Subject: [PATCH 32/37] Fixed betterC compilation --- tests/basic.d | 12 ++++++------ tests/bugs.d | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/basic.d b/tests/basic.d index b8e1d2c..aefdc7e 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -172,7 +172,7 @@ unittest assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CFloat == 2.0); - gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_]); + gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_].staticArray); gEM.commit(); entity3 = gEM.getEntity(id); assert(entity3.getComponent!CInt); @@ -183,7 +183,7 @@ unittest assert(*entity3.getComponent!CFloat == 2.0); assert(*entity3.getComponent!CShort == 2); - gEM.removeComponents(entity3.id, [CFlag().component_id,CShort(2).component_id]); + gEM.removeComponents(entity3.id, [CFlag().component_id,CShort(2).component_id].staticArray); gEM.commit(); entity3 = gEM.getEntity(id); assert(entity3.getComponent!CInt); @@ -193,8 +193,8 @@ unittest assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CFloat == 2.0); - gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_]); - gEM.removeComponents(entity3.id, [CUnregistered.component_id]); + gEM.addComponents(entity3.id, [CFlag().ref_,CShort(2).ref_].staticArray); + gEM.removeComponents(entity3.id, [CUnregistered.component_id].staticArray); gEM.commit(); entity3 = gEM.getEntity(id); assert(entity3.getComponent!CInt); @@ -211,13 +211,13 @@ unittest gEM.endRegister(); - gEM.addComponents(entity3.id, [CUnregistered(4).ref_]); + gEM.addComponents(entity3.id, [CUnregistered(4).ref_].staticArray); gEM.commit(); entity3 = gEM.getEntity(id); assert(entity3.getComponent!CUnregistered); assert(*entity3.getComponent!CUnregistered == 4); - gEM.removeComponents(entity3.id, [CUnregistered.component_id]); + gEM.removeComponents(entity3.id, [CUnregistered.component_id].staticArray); gEM.commit(); entity3 = gEM.getEntity(id); assert(!entity3.getComponent!CUnregistered); diff --git a/tests/bugs.d b/tests/bugs.d index 49e3b12..4c4153a 100644 --- a/tests/bugs.d +++ b/tests/bugs.d @@ -61,7 +61,7 @@ unittest void handleEvent(Entity* entity, Event2 event) { - id = gEM.addEntity(tmpl,[CInt(2).ref_, CLong(8).ref_].staticArray).id; + id = gEM.addEntity(tmpl).id; } } -- 2.47.2 From f964d7bf85e4dafd15b63d85cb6a1812ca243956 Mon Sep 17 00:00:00 2001 From: Mergul Date: Wed, 27 May 2020 19:46:11 +0200 Subject: [PATCH 33/37] Added more tests -added Vector test -added HashMap test -added EntityMeta test -added default hashing function to hashmap --- source/bubel/ecs/hash_map.d | 13 ++++++++- source/bubel/ecs/vector.d | 4 +-- tests/basic.d | 22 +++++++++++++-- tests/map.d | 53 +++++++++++++++++++++++++++++++++++++ tests/runner.d | 2 +- tests/vector.d | 46 +++++++++++++++++++++++++++++++- 6 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 tests/map.d diff --git a/source/bubel/ecs/hash_map.d b/source/bubel/ecs/hash_map.d index 6ae6a79..764374c 100755 --- a/source/bubel/ecs/hash_map.d +++ b/source/bubel/ecs/hash_map.d @@ -19,10 +19,21 @@ export ulong defaultHashFunc(T)(auto ref T t) nothrow @nogc } else { - return 0; //hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts + static if(isArray!T)return hashInt(hash((cast(byte*)t.ptr)[0 .. t.length * ForeachType!(T).sizeof])); + else return hashInt(hash((cast(byte*)&t)[0 .. T.sizeof])); //hashInt(t.hashOf); // hashOf is not giving proper distribution between H1 and H2 hash parts } } +ulong hash(byte[] array) nothrow @nogc +{ + ulong hash = 0; + + foreach(c;array) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} + // Can turn bad hash function to good one export ulong hashInt(ulong x) nothrow @nogc @safe { diff --git a/source/bubel/ecs/vector.d b/source/bubel/ecs/vector.d index ca6cd6c..019673b 100644 --- a/source/bubel/ecs/vector.d +++ b/source/bubel/ecs/vector.d @@ -165,10 +165,10 @@ public: return duplicate; } - export bool canAddWithoutRealloc(uint elemNum = 1) + /*export bool canAddWithoutRealloc(uint elemNum = 1) { return used + elemNum <= array.length; - } + }*/ export void add()(T t) { diff --git a/tests/basic.d b/tests/basic.d index aefdc7e..5046dff 100644 --- a/tests/basic.d +++ b/tests/basic.d @@ -135,6 +135,24 @@ void afterEveryTest() gEM.destroy(); } +@("EntityMeta") +unittest +{ + EntityTemplate* tmpl_ = gEM.allocateTemplate([CInt.component_id, CFloat.component_id, CFlag.component_id].staticArray); + Entity* entity = gEM.addEntity(tmpl_); + EntityMeta meta = entity.getMeta(); + assert(meta.hasComponent(CInt.component_id)); + assert(meta.getComponent!CInt); + assert(meta.hasComponent(CFloat.component_id)); + assert(meta.getComponent!CFloat); + assert(!meta.getComponent!CLong); + assert(!meta.hasComponent(CLong.component_id)); + assert(!meta.getComponent!CUnregistered); + assert(!meta.hasComponent(CUnregistered.component_id)); + assert(*meta.getComponent!CInt == 1); + assert(*meta.getComponent!CFloat == 2.0); +} + @("AddEntity") unittest { @@ -167,8 +185,8 @@ unittest //Entity* entity3 = gEM.addEntity(tmpl_, [cint.ref_, clong.ref_].staticArray); Entity* entity3 = gEM.addEntity(tmpl_, [CInt(10).ref_, CLong().ref_, CFlag().ref_].staticArray); EntityID id = entity3.id; - assert(entity3.getComponent!CInt); - assert(entity3.getComponent!CFloat); + assert(entity3.hasComponent(CInt.component_id)); + assert(entity3.hasComponent(CFloat.component_id)); assert(*entity3.getComponent!CInt == 10); assert(*entity3.getComponent!CFloat == 2.0); diff --git a/tests/map.d b/tests/map.d new file mode 100644 index 0000000..a82985b --- /dev/null +++ b/tests/map.d @@ -0,0 +1,53 @@ +module tests.map; + +import bubel.ecs.hash_map; + +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; + +@("HashMap") +unittest +{ + HashMap!(string, int) map; + + assert(map.length == 0); + map.add("asd",1); + assert(map.length == 1); + map.clear(); + assert(map.length == 0); + map.add("asd",1); + assert(map.length == 1); + map.reset(); + assert(map.length == 0); + + map.add("asd",1); + string asd = "asd"; + assert(map.isIn("asd")); + assert(map.isIn(asd)); + assert(!map.isIn("asdf")); + map.tryRemove("asdf"); + map.tryRemove("asd"); + assert(map.length == 0); + map.add("asdf",1); + map.add("asd",2); + assert(map["asd"] == 2); + assert(map["asdf"] == 1); + assert(map.length == 2); + map.tryRemove("asdf"); + assert(map.length == 1); + map.remove("asd"); + assert(map.length == 0); + + map.add("asd",1); + map.add("asdwwghe",6); + foreach(k,v;&map.byKeyValue) + { + assert(map[k] == v); + } +} \ No newline at end of file diff --git a/tests/runner.d b/tests/runner.d index b14dafe..7e3dd07 100644 --- a/tests/runner.d +++ b/tests/runner.d @@ -420,7 +420,7 @@ extern (C) int main(int argc, char** args) } } - TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf, tests.bugs) runner; + TestRunner!(tests.id_manager, tests.vector, tests.basic, tests.perf, tests.access_perf, tests.bugs, tests.map) runner; runner.runTests(include[], exclude[]); diff --git a/tests/vector.d b/tests/vector.d index d733f0f..1756518 100644 --- a/tests/vector.d +++ b/tests/vector.d @@ -1,7 +1,16 @@ module tests.vector; import bubel.ecs.simple_vector; -//import bubel.ecs.vector; +import bubel.ecs.vector; + +version(GNU) +{ + pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) + { + return a; + } +} +else import std.array : staticArray; @("simple-vector") unittest @@ -33,3 +42,38 @@ unittest assert(vector2[1023] == 'a'); assert(vector2[1024] == 'b'); } + +@("Vector") +unittest +{ + struct G + { + int a; + } + + Vector!G vector; + assert(vector.empty()); + vector.add(G(1)); + assert(!vector.empty()); + vector.clear(); + assert(vector.empty()); + vector.add(G(1)); + assert(!vector.empty()); + vector.reset(); + assert(vector.empty()); + + vector.add(G(1)); + vector.add([G(2),G(5)].staticArray); + assert(vector.length == 3); + assert(vector.capacity == 1); + + Vector!G vector2; + vector2.add([G(1),G(2),G(5)].staticArray); + assert(vector == vector2); + vector2.remove(1); + assert(vector != vector2); + assert(vector2.length == 2); + assert(vector2[1] == G(5)); + vector2.add(G(2),1); + assert(vector == vector2); +} -- 2.47.2 From 3719cdaee0f28a610ef59078f775b3c7f3951cb8 Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 28 May 2020 09:39:17 +0200 Subject: [PATCH 34/37] Some small fixes --- source/bubel/ecs/id_manager.d | 1 + source/bubel/ecs/manager.d | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/bubel/ecs/id_manager.d b/source/bubel/ecs/id_manager.d index 084a947..aaeef30 100644 --- a/source/bubel/ecs/id_manager.d +++ b/source/bubel/ecs/id_manager.d @@ -56,6 +56,7 @@ struct IDManager */ void releaseID(EntityID id) nothrow @nogc { + optimize(); Data* data = &m_ids_array[id.id]; if (data.counter != id.counter) return; diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index cf3712d..a8954e0 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -2923,6 +2923,7 @@ export struct EntityManager private void updateBlock(EntitiesBlock* block) @nogc nothrow { + if(block.added_count == 0)return; EntityInfo* info = block.type_info; ushort entities_count = block.entities_count; block.entities_count += block.added_count; @@ -3056,7 +3057,7 @@ export struct EntityManager has_work = false; has_work |= updateEvents(); - id_manager.optimize(); + //id_manager.optimize(); has_work |= updateBlocks(); has_work |= changeEntities(); has_work |= removeEntities(); -- 2.47.2 From 7a0ddf7494b94e26b828a77e18dd115a85c6efb4 Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 28 May 2020 15:53:05 +0200 Subject: [PATCH 35/37] Bugfix update -fixed critical bug with adding entities -fixed small bug with adding entity with replacement components which entity hasn't -added multiple asserts to faster bug detection in future --- source/bubel/ecs/manager.d | 136 +++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 43 deletions(-) diff --git a/source/bubel/ecs/manager.d b/source/bubel/ecs/manager.d index a8954e0..0982531 100644 --- a/source/bubel/ecs/manager.d +++ b/source/bubel/ecs/manager.d @@ -2154,6 +2154,8 @@ export struct EntityManager return;*/ EntitiesBlock* new_block = findBlockWithFreeSpace(new_info); + updateEntityInfoBlocks(new_info); + assert(new_block.added_count == 0); void* start = new_block.dataBegin() + new_block.entities_count * EntityID.sizeof; @@ -2183,6 +2185,10 @@ export struct EntityManager cast(void*) block + info.deltas[comp] + ind * comp_size, comp_size); } + new_block.entities_count++; + if (new_block != new_info.update_block) + new_info.update_block = new_block; + if (new_info.add_listeners) { foreach (listener; new_info.add_listeners) @@ -2190,7 +2196,7 @@ export struct EntityManager if (!info.systems[listener]) { callAddEntityListener(&systems[listener], new_info, new_block, - new_block.entities_count, new_block.entities_count + 1); + new_block.entities_count - 1, new_block.entities_count); } } } @@ -2202,13 +2208,11 @@ export struct EntityManager if (info.systems[listener]) { callChangeEntityListener(&systems[listener], new_info, new_block, - new_block.entities_count, new_block.entities_count + 1, del_ids); + new_block.entities_count - 1, new_block.entities_count, del_ids); } } } - new_block.entities_count++; - removeEntityNoID(entity, block); } @@ -2291,12 +2295,14 @@ export struct EntityManager //EntityInfo* new_info = getEntityInfo(ids[0 .. len]); EntitiesBlock* new_block = findBlockWithFreeSpace(new_info); + updateEntityInfoBlocks(new_info); + assert(new_block.added_count == 0); void* start = new_block.dataBegin() + new_block.entities_count * EntityID.sizeof; Entity* new_entity = cast(Entity*) start; new_entity.id = entity.id; - id_manager.update(*new_entity); //new_entity.updateID(); + id_manager.update(*new_entity); uint j = 0; uint k = 0; @@ -2342,6 +2348,10 @@ export struct EntityManager } } + new_block.entities_count++; + if (new_block != new_info.update_block) + new_info.update_block = new_block; + if (new_info.add_listeners) { foreach (listener; new_info.add_listeners) @@ -2349,7 +2359,7 @@ export struct EntityManager if (!info.systems[listener]) { callAddEntityListener(&systems[listener], new_info, new_block, - new_block.entities_count, new_block.entities_count + 1); + new_block.entities_count - 1, new_block.entities_count); } } } @@ -2361,12 +2371,11 @@ export struct EntityManager if (info.systems[listener]) { callChangeEntityListener(&systems[listener], new_info, new_block, - new_block.entities_count, new_block.entities_count + 1, new_ids); + new_block.entities_count - 1, new_block.entities_count, new_ids); } } } - new_block.entities_count++; removeEntityNoID(entity, block); } @@ -2487,14 +2496,12 @@ export struct EntityManager } } - if (new_index == 1) - threads[threadID].blockToUpdate.add(new_block); + if (new_index == 1 && info.update_block == block) + threads[threadID].infosToUpdate.add(info); Entity* new_entity = cast(Entity*) start; - //add_mutex.lock_nothrow(); new_entity.id = id_manager.getNewID(); - //add_mutex.unlock_nothrow(); - id_manager.update(*new_entity); //new_entity.updateID(); + id_manager.update(*new_entity); return new_entity; } @@ -2538,12 +2545,10 @@ export struct EntityManager } if (index == 1) - threads[threadID].blockToUpdate.add(block); + threads[threadID].infosToUpdate.add(block); Entity* entity = cast(Entity*) start; - //add_mutex.lock_nothrow(); entity.id = id_manager.getNewID(); - //add_mutex.unlock_nothrow(); id_manager.update(*entity); //entity.updateID(); return entity;*/ @@ -2589,7 +2594,7 @@ export struct EntityManager if (comp.component_id < info.deltas.length) { ushort delta = info.deltas[comp.component_id]; - if (delta != ushort.max) + if (delta != 0) { uint size = components[comp.component_id].size; if (size != 0) @@ -2607,14 +2612,12 @@ export struct EntityManager } } - if (index == 1) - threads[threadID].blockToUpdate.add(block); + if (index == 1 && info.update_block == block) + threads[threadID].infosToUpdate.add(info); Entity* entity = cast(Entity*) start; - //add_mutex.lock_nothrow(); entity.id = id_manager.getNewID(); - //add_mutex.unlock_nothrow(); - id_manager.update(*entity); //entity.updateID(); + id_manager.update(*entity); return entity; } @@ -2633,6 +2636,7 @@ export struct EntityManager block.id = 0; info.first_block = block; info.last_block = block; + info.update_block = block; } else if (block.entities_count >= info.max_entities) { @@ -2643,6 +2647,12 @@ export struct EntityManager new_block.id = cast(ushort)(block.id + 1); block = new_block; info.last_block = block; + ///make sure that update_block point to unfilled block + if (info.update_block.entities_count == info.max_entities) + { + assert(!info.update_block.added_count); + info.update_block = block; + } } return block; } @@ -2668,8 +2678,9 @@ export struct EntityManager block.id = 0; info.first_block = block; info.last_block = block; + info.update_block = block; } - else if (block.entities_count + block.added_count > info.max_entities) + else if (block.entities_count + block.added_count >= info.max_entities) { EntitiesBlock* last_block = info.last_block; @@ -2687,6 +2698,12 @@ export struct EntityManager new_block.id = cast(ushort)(block.id + 1); block = new_block; info.last_block = block; + ///make sure that update_block point to unfilled block + if (info.update_block.entities_count == info.max_entities) + { + assert(!info.update_block.added_count); + info.update_block = block; + } } return block; } @@ -2730,8 +2747,10 @@ export struct EntityManager { EntityInfo* info = block.type_info; - if (info.last_block.added_count) - updateBlock(info.last_block); + updateEntityInfoBlocks(info); + + assert(info.last_block.added_count == 0); + assert(info.last_block.entities_count > 0); info.last_block.entities_count--; @@ -2764,12 +2783,13 @@ export struct EntityManager block = info.last_block; entity.id = *cast(EntityID*)(block.dataBegin() + block.entities_count * EntityID.sizeof); - id_manager.update(*entity); //entity.updateID(); + id_manager.update(*entity); } block = info.last_block; if (block.entities_count == 0) { + assert(info.update_block is block); info.last_block = block.prev_block; if (info.first_block is block) { @@ -2778,7 +2798,9 @@ export struct EntityManager if (block.prev_block) { block.prev_block.next_block = null; - block.prev_block.added_count = 0; + info.update_block = block.prev_block; + assert(block.prev_block.added_count == 0); + //block.prev_block.added_count.atomicStore(cast(ushort)0); } allocator.freeBlock(block); } @@ -2852,7 +2874,7 @@ export struct EntityManager } } - private void callAddEntityListener(System* system, EntityInfo* info, + private static void callAddEntityListener(System* system, EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { ListenerCallData data; @@ -2873,8 +2895,8 @@ export struct EntityManager } } - private void callRemoveEntityListener(System* system, EntityInfo* info, - EntitiesBlock* block, int begin, int end) @nogc nothrow + private static void callRemoveEntityListener(System* system, + EntityInfo* info, EntitiesBlock* block, int begin, int end) @nogc nothrow { ListenerCallData data; data.system = system; @@ -2921,9 +2943,34 @@ export struct EntityManager (cast(void function(ref ListenerCallData) nothrow @nogc) system.m_change_entity)(data); } + private void updateEntityInfoBlocks(EntityInfo* info) nothrow @nogc + { + while (info.last_block.added_count) + { + EntitiesBlock* block = info.update_block; + assert(block !is null); + if (block.entities_count == info.max_entities) + { + assert(!block.added_count); + block = block.next_block; + } + assert(!block.prev_block || !block.prev_block.added_count); + info.update_block = info.last_block; + + while (block) + { + assert(block.added_count.atomicLoad() > 0); + updateBlock(block); + block = block.next_block; + } + } + assert(info.last_block is info.update_block); + } + private void updateBlock(EntitiesBlock* block) @nogc nothrow { - if(block.added_count == 0)return; + //if(block.added_count == 0)return; + assert(block.added_count != 0); EntityInfo* info = block.type_info; ushort entities_count = block.entities_count; block.entities_count += block.added_count; @@ -2942,17 +2989,15 @@ export struct EntityManager private bool updateBlocks() { bool has_work = false; - //foreach (ref ThreadData thread; threads)thread.swapToUpdate(); foreach (ref ThreadData thread; threads) { - //thread.swapToUpdate(); - if (thread.blockToUpdatePrev.length) + if (thread.infosToUpdatePrev.length) has_work = true; - foreach (block; thread.blockToUpdatePrev) + foreach (info; thread.infosToUpdatePrev) { - updateBlock(block); + updateEntityInfoBlocks(info); } - thread.blockToUpdatePrev.clear(); + thread.infosToUpdatePrev.clear(); } return has_work; } @@ -3055,6 +3100,9 @@ export struct EntityManager swapData(); has_work = false; + // has_work |= updateBlocks(); + // has_work |= changeEntities(); + // has_work |= removeEntities(); has_work |= updateEvents(); //id_manager.optimize(); @@ -3477,7 +3525,7 @@ export struct EntityManager EntityInfo*[] comp_rem_info; ///alignment of whole entity - ushort alignment; //unused in linear-layout + ushort alignment; //unused in linear-layout TODO: to remove ///size of entity (with alignment respect) ushort size; ///max number of entities in block @@ -3496,6 +3544,8 @@ export struct EntityManager EntitiesBlock* first_block; ///pointer to last block EntitiesBlock* last_block; + ///pointer to last updated block + EntitiesBlock* update_block; } /************************************************************************************************************************ @@ -3642,9 +3692,9 @@ export struct EntityManager return change_entities_list[data_index]; } - ref Vector!(EntitiesBlock*) blockToUpdate() @nogc nothrow return + ref Vector!(EntityInfo*) infosToUpdate() @nogc nothrow return { - return blocks_to_update[data_index]; + return infos_to_update[data_index]; } ref Vector!EntityID entitiesToRemovePrev() @nogc nothrow return @@ -3657,9 +3707,9 @@ export struct EntityManager return change_entities_list[1 - data_index]; } - ref Vector!(EntitiesBlock*) blockToUpdatePrev() @nogc nothrow return + ref Vector!(EntityInfo*) infosToUpdatePrev() @nogc nothrow return { - return blocks_to_update[1 - data_index]; + return infos_to_update[1 - data_index]; } private: @@ -3671,7 +3721,7 @@ export struct EntityManager Vector!EntityID[2] entities_to_remove; SimpleVector[2] change_entities_list; - Vector!(EntitiesBlock*)[2] blocks_to_update; + Vector!(EntityInfo*)[2] infos_to_update; ubyte data_index = 0; } -- 2.47.2 From 9af1cee60b3ce3bf07f3e06876749de3257d6224 Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 28 May 2020 16:11:27 +0200 Subject: [PATCH 36/37] SpaceShip demo update -added wave shooted upon death of boss tower -cleanup code a little bit -moved to new entityAdding method -fixed bug with changeing demos --- demos/source/app.d | 9 +- demos/source/demos/chipmunk2d.d | 19 - demos/source/demos/events.d | 19 - demos/source/demos/flag_component.d | 19 - demos/source/demos/simple.d | 2 +- demos/source/demos/snake.d | 47 +-- demos/source/demos/space_invaders.d | 549 +++++++++++++++++----------- demos/source/gui/manager.d | 1 + 8 files changed, 368 insertions(+), 297 deletions(-) delete mode 100644 demos/source/demos/chipmunk2d.d delete mode 100644 demos/source/demos/events.d delete mode 100644 demos/source/demos/flag_component.d diff --git a/demos/source/app.d b/demos/source/app.d index 9017f8d..ede93b5 100644 --- a/demos/source/app.d +++ b/demos/source/app.d @@ -96,6 +96,7 @@ struct Launcher void switchDemo(void function() start, bool function() loop, void function() end, void function(SDL_Event*) event, void function(vec2, Tool, int) tool, const (char)* tips) { gui_manager.clear(); + //launcher.ent if(this.end)this.end(); @@ -262,7 +263,7 @@ void mainLoop(void* arg) if(launcher.tool && launcher.tool_repeat != 0 && launcher.mouse.left && !igIsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !igIsWindowFocused(ImGuiFocusedFlags_AnyWindow)) { float range = 500.0 / cast(float)launcher.tool_repeat; - launcher.repeat_time += launcher.delta_time * 100; + launcher.repeat_time += launcher.delta_time; while(launcher.repeat_time > range) { launcher.repeat_time -= range; @@ -463,7 +464,7 @@ void mainLoop(void* arg) if(launcher.show_demo_wnd) { igSetNextWindowPos(ImVec2(launcher.window_size.x - 260, 30), ImGuiCond_Once, ImVec2(0,0)); - igSetNextWindowSize(ImVec2(250, 250), ImGuiCond_Once); + igSetNextWindowSize(ImVec2(250, 500), ImGuiCond_Once); if(igBegin("Demo",&launcher.show_demo_wnd,0)) { ImDrawList* draw_list = igGetWindowDrawList(); @@ -745,8 +746,8 @@ int main(int argc, char** argv) { import demos.simple; import demos.space_invaders; - launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,&spaceInvadersTool,SpaceInvaders.tips); - // launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); + // launcher.switchDemo(&spaceInvadersStart,&spaceInvadersLoop,&spaceInvadersEnd,&spaceInvadersEvent,&spaceInvadersTool,SpaceInvaders.tips); + launcher.switchDemo(&simpleStart,&simpleLoop,&simpleEnd,&simpleEvent,&simpleTool,Simple.tips); } int key_num; diff --git a/demos/source/demos/chipmunk2d.d b/demos/source/demos/chipmunk2d.d deleted file mode 100644 index 96e5fa6..0000000 --- a/demos/source/demos/chipmunk2d.d +++ /dev/null @@ -1,19 +0,0 @@ -module demos.chipmunk2d; - -import app; - -import bindbc.sdl; - -import cimgui.cimgui; - -import bubel.ecs.attributes; -import bubel.ecs.core; -import bubel.ecs.entity; -import bubel.ecs.manager; -import bubel.ecs.std; - -import ecs_utils.gfx.texture; -import ecs_utils.math.vector; -import ecs_utils.utils; - -extern(C): \ No newline at end of file diff --git a/demos/source/demos/events.d b/demos/source/demos/events.d deleted file mode 100644 index 6215160..0000000 --- a/demos/source/demos/events.d +++ /dev/null @@ -1,19 +0,0 @@ -module demos.events; - -import app; - -import bindbc.sdl; - -import cimgui.cimgui; - -import bubel.ecs.attributes; -import bubel.ecs.core; -import bubel.ecs.entity; -import bubel.ecs.manager; -import bubel.ecs.std; - -import ecs_utils.gfx.texture; -import ecs_utils.math.vector; -import ecs_utils.utils; - -extern(C): \ No newline at end of file diff --git a/demos/source/demos/flag_component.d b/demos/source/demos/flag_component.d deleted file mode 100644 index 392164e..0000000 --- a/demos/source/demos/flag_component.d +++ /dev/null @@ -1,19 +0,0 @@ -module demos.flag_component; - -import app; - -import bindbc.sdl; - -import cimgui.cimgui; - -import bubel.ecs.attributes; -import bubel.ecs.core; -import bubel.ecs.entity; -import bubel.ecs.manager; -import bubel.ecs.std; - -import ecs_utils.gfx.texture; -import ecs_utils.math.vector; -import ecs_utils.utils; - -extern(C): \ No newline at end of file diff --git a/demos/source/demos/simple.d b/demos/source/demos/simple.d index 5e82d27..313cc00 100644 --- a/demos/source/demos/simple.d +++ b/demos/source/demos/simple.d @@ -52,7 +52,7 @@ struct DrawSystem struct EntitiesData { uint length; - uint thread_id; + //uint thread_id; uint job_id; @readonly CTexture[] textures; @readonly CLocation[] locations; diff --git a/demos/source/demos/snake.d b/demos/source/demos/snake.d index 50f7729..01e9a36 100644 --- a/demos/source/demos/snake.d +++ b/demos/source/demos/snake.d @@ -129,9 +129,10 @@ struct Snake } if(base_pos.x == random_pos.x && base_pos.y == random_pos.y)return; } - CILocation* location = apple_tmpl.getComponent!CILocation; - *location = random_pos; - Entity* apple = launcher.manager.addEntity(apple_tmpl); + //CILocation* location = apple_tmpl.getComponent!CILocation; + //*location = random_pos; + //Entity* apple = + launcher.manager.addEntity(apple_tmpl,[CILocation(random_pos).ref_].staticArray); } } @@ -366,8 +367,8 @@ struct MoveSystem mixin ECS.System!64; EntityTemplate* destroy_template; - CLocation* destroy_location; - CParticleVector* destroy_vector; + //CLocation* destroy_location; + //CParticleVector* destroy_vector; struct EntitiesData { @@ -381,8 +382,8 @@ struct MoveSystem void setTemplates() { destroy_template = snake.snake_destroy_particle; - destroy_location = destroy_template.getComponent!CLocation; - destroy_vector = destroy_template.getComponent!CParticleVector; + //destroy_location = destroy_template.getComponent!CLocation; + //destroy_vector = destroy_template.getComponent!CParticleVector; } void moveLocation(ref CILocation location, CMovement.Direction direction) @@ -436,24 +437,26 @@ struct MoveSystem case MapElement.Type.snake: foreach(loc; data.snakes[i].parts) { - destroy_location.x = loc.x * 16; - destroy_location.y = loc.y * 16; + //destroy_location.x = loc.x * 16; + //destroy_location.y = loc.y * 16; snake.element(MapElement(MapElement.Type.empty, EntityID()),loc); - launcher.manager.addEntity(snake.snake_destroy_particle); + launcher.manager.addEntity(snake.snake_destroy_particle,[CLocation(cast(vec2)(loc * 16)).ref_].staticArray); + + CLocation destroy_location; foreach(j;0..10) { destroy_location.x = loc.x * 16 + randomf() * 8 - 4; destroy_location.y = loc.y * 16 + randomf() * 8 - 4; - destroy_vector.velocity = vec2(randomf(),randomf())*0.4-0.2; + //destroy_vector.velocity = vec2(randomf(),randomf())*0.4-0.2; snake.element(MapElement(MapElement.Type.empty, EntityID()),loc); - launcher.manager.addEntity(snake.snake_destroy_particle); + launcher.manager.addEntity(snake.snake_destroy_particle, [destroy_location.ref_, CParticleVector(vec2(randomf(),randomf())*0.4-0.2).ref_].staticArray); } } - destroy_location.x = new_location.x * 16; - destroy_location.y = new_location.y * 16; + //destroy_location.x = new_location.x * 16; + //destroy_location.y = new_location.y * 16; snake.element(MapElement(MapElement.Type.empty, EntityID()),new_location); - launcher.manager.addEntity(snake.snake_destroy_particle); + launcher.manager.addEntity(snake.snake_destroy_particle,[CLocation(cast(vec2)(new_location * 16)).ref_].staticArray); launcher.manager.removeEntity(data.entities[i].id); break; @@ -796,9 +799,9 @@ void snakeStart() { ushort[4] components = [CILocation.component_id, CSnake.component_id, CMovement.component_id, CInput.component_id]; snake.snake_tmpl = launcher.manager.allocateTemplate(components); - CILocation* loc_comp = snake.snake_tmpl.getComponent!CILocation; - *loc_comp = ivec2(2,2); - launcher.manager.addEntity(snake.snake_tmpl); + //CILocation* loc_comp = snake.snake_tmpl.getComponent!CILocation; + //*loc_comp = ivec2(2,2); + launcher.manager.addEntity(snake.snake_tmpl,[CILocation(ivec2(2,2)).ref_].staticArray); } { @@ -846,15 +849,15 @@ void snakeTool(vec2 position, Tool tool, int size) CLocation* location = tmpl.getComponent!CLocation; if(location) { - position.x += (randomf - 0.5) * size; - position.y += (randomf - 0.5) * size; + position.x += (randomf() - 0.5) * size; + position.y += (randomf() - 0.5) * size; *location = position; } CILocation* ilocation = tmpl.getComponent!CILocation; if(ilocation) { - position.x += (randomf - 0.5) * size; - position.y += (randomf - 0.5) * size; + position.x += (randomf() - 0.5) * size; + position.y += (randomf() - 0.5) * size; ivec2 ipos; ipos.x = cast(int)(position.x / 16); ipos.y = cast(int)(position.y / 16); diff --git a/demos/source/demos/space_invaders.d b/demos/source/demos/space_invaders.d index c047d25..5615f0d 100644 --- a/demos/source/demos/space_invaders.d +++ b/demos/source/demos/space_invaders.d @@ -40,6 +40,7 @@ struct SpaceInvaders EntityTemplate* enemy_tmpl; EntityTemplate* ship_tmpl; EntityTemplate* laser_tmpl; + EntityTemplate*[5] bullet_tmpl; Texture texture; ShootGrid* shoot_grid; @@ -55,9 +56,13 @@ struct SpaceInvaders ~this() @nogc nothrow { if(shoot_grid)Mallocator.dispose(shoot_grid); - launcher.manager.freeTemplate(enemy_tmpl); - launcher.manager.freeTemplate(ship_tmpl); - launcher.manager.freeTemplate(laser_tmpl); + if(enemy_tmpl)launcher.manager.freeTemplate(enemy_tmpl); + if(ship_tmpl)launcher.manager.freeTemplate(ship_tmpl); + if(laser_tmpl)launcher.manager.freeTemplate(laser_tmpl); + foreach (EntityTemplate* tmpl; bullet_tmpl) + { + if(tmpl)launcher.manager.freeTemplate(tmpl); + } texture.destory(); } } @@ -115,7 +120,7 @@ struct CLocation alias value this; - vec2 value; + vec2 value = vec2(0); } struct CScale @@ -192,7 +197,7 @@ struct CGuild byte guild; } -struct CLaser +struct CBullet { mixin ECS.Component; @@ -408,12 +413,35 @@ struct CParticleEmitterTime { mixin ECS.Component; - float time; + float time = 0; } +///You can create separate component for every kind of spawned entities but it's not practial due to archetype fragmentation. +///Second approach can be commented code. It's gives good flexibility inchoosing entity, but it limits to one entity. +///Instead of entity it can be array of templates which is good solution, but if possibilities is known at time of game development it +///can be simply index/enum for type of spawn. Bad thing about this solution is problem witch merging multiple spawning types during +///gameplay, i.e. giving buff which cast firebols upon death. +struct CSpawnUponDeath +{ + mixin ECS.Component; + + enum Type + { + flashes_emitter, + } + + //EntityID parent; + //EntityTemplate* tmpl; + Type type; +} + +///This component can be replaced by "CSpawnUponDeath" but I want to gives possibility to add this component to every entity +///during gameplay. End application works exacly the same way for every demo so I can't use different way as adding component. struct CShootWaveUponDeath { mixin ECS.Component; + + CWeapon.Type bullet_type; } /*####################################################################################################################### @@ -604,7 +632,7 @@ struct ShootGridManager struct EntitiesData { uint length; - uint thread_id; + //uint thread_id; const (Entity)[] entity; @readonly CLocation[] locations; @readonly CShootGrid[] grid_flag; @@ -748,7 +776,8 @@ struct ShipWeaponSystem weapon_tmpl.getComponent!CTargetParent().rel_pos = vec2(0,0); weapon_tmpl.getComponent!CGuild().guild = 1; weapon_tmpl.getComponent!CScale().value = vec2(4,16); - weapon_tmpl.getComponent!CWeapon().level = 1; + //weapon_tmpl.getComponent!CWeapon().level = 1; + *weapon_tmpl.getComponent!CWeapon() = CWeapon(0,CWeapon.Type.canon,1); weapon_tmpl.getComponent!CDepth().depth = -1; weapon_tmpl.getComponent!CTexture().coords = vec4(136,96,4,16)*px; weapon_tmpl.getComponent!CWeaponLocation().rel_pos = vec2(0,12); @@ -801,7 +830,8 @@ struct ShipWeaponSystem [CHitMark.component_id, CHitPoints.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CEnemy.component_id, CShootGrid.component_id, CGuild.component_id, CInit.component_id, - CChildren.component_id, CDepth.component_id, CTargetParent.component_id].staticArray + CChildren.component_id, CDepth.component_id, CTargetParent.component_id, + CSpawnUponDeath.component_id, CShootWaveUponDeath.component_id].staticArray ); CTexture* tex_comp = tower1_tmpl.getComponent!CTexture; @@ -813,6 +843,7 @@ struct ShipWeaponSystem tower1_tmpl.getComponent!CInit().type = CInit.Type.tower; tower1_tmpl.getComponent!CHitPoints().value = 10; tower1_tmpl.getComponent!CDepth().depth = -2; + tower1_tmpl.getComponent!CShootWaveUponDeath().bullet_type = CWeapon.Type.canon; tower1_tmpl.getComponent!CTargetParent().rel_pos = vec2(-33,2); tower2_tmpl = launcher.manager.allocateTemplate(tower1_tmpl); @@ -865,22 +896,12 @@ struct ShipWeaponSystem { foreach(i; 0..data.length) { - /*if(data.children[i].childern.length != 0)continue; - EntityID[3] weapons; - laser1_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; - laser2_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; - main_weapon_tmpl.getComponent!CTargetParent().parent = data.entity[i].id; - weapons[0] = launcher.manager.addEntity(laser1_tmpl).id; - weapons[1] = launcher.manager.addEntity(laser2_tmpl).id; - weapons[2] = launcher.manager.addEntity(main_weapon_tmpl).id; - data.children[i].childern = Mallocator.makeArray(weapons);*/ final switch(data.init[i].type) { case CInit.Type.space_ship:ship.add(&data.entity[i]);break; case CInit.Type.tower:tower.add(&data.entity[i]);break; case CInit.Type.boss:boss.add(&data.entity[i]);break; } - } } } @@ -942,7 +963,7 @@ struct DrawSystem struct EntitiesData { uint length; - uint thread_id; + //uint thread_id; uint job_id; @readonly CTexture[] textures; @readonly CLocation[] locations; @@ -1046,8 +1067,6 @@ struct LaserShootingSystem mixin ECS.System!32; bool shoot = false; - //static float[18] laser_shoot_times = [500,400,300,200,100,50,25,10,5,2,1,0.8,0.6,0.5,0.4,0.3,0.2,0.1]; - //static float[18] laser_shoot_disp = [0,0,0,0,0.05,0.06,0.08,0.1,0.14,0.18,0.2,0.25,0.26,0.27,0.28,0.29,0.3,0.4]; __gshared vec4[] fire_frames = [vec4(96,64,8,16)*px,vec4(104,64,8,16)*px,vec4(112,64,8,16)*px,vec4(120,64,8,16)*px,vec4(128,64,8,16)*px, vec4(136,64,8,16)*px,vec4(144,64,8,16)*px,vec4(152,64,8,16)*px,vec4(160,64,8,16)*px]; @@ -1055,16 +1074,10 @@ struct LaserShootingSystem // __gshared vec4[] fire_frames = [vec4(0,160,8,16)*px,vec4(16,160,16,16)*px,vec4(32,160,16,16)*px,vec4(48,160,16,16)*px,vec4(64,160,16,16)*px, // vec4(80,160,16,16)*px,vec4(96,160,16,16)*px,vec4(112,160,16,16)*px]; - /*CLocation* laser_location; - CVelocity* laser_velocity; - CGuild* laser_guild;*/ - struct EntitiesData { ///variable named "length" contain entites count uint length; - ///variable named "length" contain thread identifier - uint thread_id; CWeapon[] laser; @readonly CLocation[] location; @readonly CGuild[] guild; @@ -1076,77 +1089,53 @@ struct LaserShootingSystem @optional @readonly CRotation[] rotation; } - struct ThreadData - { - EntityTemplate* laser_tmpl; - CLocation* laser_location; - CVelocity* laser_velocity; - CGuild* laser_guild; + //EntityTemplate* laser_tmpl; + EntityTemplate* fire_tmpl; - EntityTemplate* fire_tmpl; - CLocation* fire_location; - CVelocity* fire_velocity; - CRotation* fire_rotation; - } - - ThreadData[] threads; + //EntityTemplate*[5] bullet_tmpl; ///Called inside "registerSystem" function void onCreate() { - threads = Mallocator.makeArray!ThreadData(32); - threads[0].laser_tmpl = launcher.manager.allocateTemplate( + /*bullet_tmpl[0] = launcher.manager.allocateTemplate( [CLocation.component_id, CTexture.component_id, CVelocity.component_id, - CScale.component_id, CLaser.component_id, CGuild.component_id].staticArray + CScale.component_id, CBullet.component_id, CGuild.component_id].staticArray ); + bullet_tmpl[0].getComponent!CTexture().coords = vec4(0,24,2,8)*px; + bullet_tmpl[0].getComponent!CScale().value = vec2(2,8); - CTexture* tex_comp = threads[0].laser_tmpl.getComponent!CTexture; - //tex_comp.tex = space_invaders.texture;//laser_tex; - tex_comp.coords = vec4(0*px,24*px,2*px,8*px); - CScale* scale_comp = threads[0].laser_tmpl.getComponent!CScale; - scale_comp.value = vec2(2,8); - threads[0].laser_location = threads[0].laser_tmpl.getComponent!CLocation; - threads[0].laser_velocity = threads[0].laser_tmpl.getComponent!CVelocity; - threads[0].laser_guild = threads[0].laser_tmpl.getComponent!CGuild; + bullet_tmpl[1] = launcher.manager.allocateTemplate(bullet_tmpl[0]); + bullet_tmpl[2] = launcher.manager.allocateTemplate(bullet_tmpl[0]); + bullet_tmpl[2].getComponent!CTexture().coords = vec4(64,32,8,16)*px; + bullet_tmpl[2].getComponent!CScale().value = vec2(8,16); + bullet_tmpl[3] = launcher.manager.allocateTemplate(bullet_tmpl[0]); + bullet_tmpl[3].getComponent!CTexture().coords = vec4(56,32,2,2)*px; + bullet_tmpl[3].getComponent!CScale().value = vec2(2,2); + // bullet_tmpl[3].getComponent!CTexture().coords = vec4(48,32,8,8)*px; + // bullet_tmpl[3].getComponent!CScale().value = vec2(8,8); + bullet_tmpl[4] = launcher.manager.allocateTemplate(bullet_tmpl[0]);*/ - threads[0].fire_tmpl = launcher.manager.allocateTemplate( + + fire_tmpl = launcher.manager.allocateTemplate( [CLocation.component_id, CTexture.component_id, CScale.component_id, CAnimation.component_id, CParticle.component_id, CRotation.component_id, CVelocity.component_id, CDamping.component_id].staticArray ); - tex_comp = threads[0].fire_tmpl.getComponent!CTexture; - //tex_comp.tex = space_invaders.texture;//laser_tex; - tex_comp.coords = vec4(96*px,64*px,8*px,16*px); - scale_comp = threads[0].fire_tmpl.getComponent!CScale; - scale_comp.value = vec2(8,16); - threads[0].fire_location = threads[0].fire_tmpl.getComponent!CLocation; - threads[0].fire_rotation = threads[0].fire_tmpl.getComponent!CRotation; - threads[0].fire_velocity = threads[0].fire_tmpl.getComponent!CVelocity; - threads[0].fire_tmpl.getComponent!(CParticle).life = 300; - *threads[0].fire_tmpl.getComponent!(CAnimation) = CAnimation(fire_frames, 0, 3); - - foreach(ref ThreadData thread;threads[1..$]) - { - thread.laser_tmpl = launcher.manager.allocateTemplate(threads[0].laser_tmpl); - thread.laser_location = thread.laser_tmpl.getComponent!CLocation; - thread.laser_velocity = thread.laser_tmpl.getComponent!CVelocity; - thread.laser_guild = thread.laser_tmpl.getComponent!CGuild; - thread.fire_tmpl = launcher.manager.allocateTemplate(threads[0].fire_tmpl); - thread.fire_location = thread.fire_tmpl.getComponent!CLocation; - thread.fire_rotation = thread.fire_tmpl.getComponent!CRotation; - thread.fire_velocity = thread.fire_tmpl.getComponent!CVelocity; - } - //laser_location = space_invaders.laser_tmpl.getComponent!CLocation; + fire_tmpl.getComponent!CTexture().coords = vec4(96,64,8,16)*px; + fire_tmpl.getComponent!CScale().value = vec2(8,16); + fire_tmpl.getComponent!(CParticle).life = 300; + *fire_tmpl.getComponent!(CAnimation) = CAnimation(fire_frames, 0, 3); } void onDestroy() { - foreach(ref ThreadData thread;threads[1..$]) + //launcher.manager.freeTemplate(laser_tmpl); + /*foreach (EntityTemplate* tmpl; bullet_tmpl) { - launcher.manager.freeTemplate(thread.laser_tmpl); - } - Mallocator.dispose(threads); + launcher.manager.freeTemplate(tmpl); + }*/ + launcher.manager.freeTemplate(fire_tmpl); } bool onBegin() @@ -1164,7 +1153,6 @@ struct LaserShootingSystem void onUpdate(EntitiesData data) { - ThreadData* thread = &threads[data.thread_id]; //conditional branch for whole entities block if(shoot || data.auto_shoot) { @@ -1174,58 +1162,65 @@ struct LaserShootingSystem laser.shoot_time += launcher.delta_time; while(laser.shoot_time > CWeapon.levels[laser.level - 1].reload_time) { + CVelocity laser_velocity; + CGuild laser_guild; + CLocation laser_location; + CVelocity fire_velocity; + CLocation fire_location; + CRotation fire_rotation; + laser.shoot_time -= CWeapon.levels[laser.level - 1].reload_time; - thread.laser_location.value = data.location[i]; + laser_location.value = data.location[i]; - thread.laser_velocity.value = vec2((randomf()*2-1) * CWeapon.levels[laser.level - 1].dispersion,1);//data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); - if(data.shoot_direction && data.shoot_direction[i].direction == Direction.down)thread.laser_velocity.y = -1; + laser_velocity.value = vec2((randomf()*2-1) * CWeapon.levels[laser.level - 1].dispersion,1);//data.shoot_direction[i].direction == Direction.up ? 1.0 : -1.0); + if(data.shoot_direction && data.shoot_direction[i].direction == Direction.down)laser_velocity.y = -1; - thread.laser_guild.guild = data.guild[i].guild; + laser_guild.guild = data.guild[i].guild; - if(laser.level < 3)thread.laser_velocity.value = thread.laser_velocity.value * 0.4f; + if(laser.level < 3)laser_velocity.value = laser_velocity.value * 0.4f; if(data.velocity) { - thread.fire_velocity.value = data.velocity[i]; - //thread.laser_velocity.value += data.velocity[i] * 0.5; + fire_velocity.value = data.velocity[i]; + //laser_velocity.value += data.velocity[i] * 0.5; } - else thread.fire_velocity.value = vec2(0,0); + else fire_velocity.value = vec2(0,0); - thread.fire_location.value = data.location[i]; + fire_location.value = data.location[i]; if(data.shoot_direction[i].direction == Direction.down) { - thread.fire_rotation.value = PI; - //thread.fire_location.value.y -= 16; + fire_rotation.value = PI; + //fire_location.value.y -= 16; } else { - thread.fire_rotation.value = 0; - //thread.fire_location.value.y += 24; + fire_rotation.value = 0; + //fire_location.value.y += 24; } if(data.rotation) { float sinn = sinf(data.rotation[i]); float coss = cosf(data.rotation[i]); - float x = thread.laser_velocity.y*sinn + thread.laser_velocity.x*coss; - float y = thread.laser_velocity.y*coss + thread.laser_velocity.x*sinn; - thread.laser_velocity.value = vec2(x,y); - thread.fire_rotation.value = data.rotation[i]; + float x = laser_velocity.y*sinn + laser_velocity.x*coss; + float y = laser_velocity.y*coss + laser_velocity.x*sinn; + laser_velocity.value = vec2(x,y); + fire_rotation.value = data.rotation[i]; if(data.weapon_location) { vec2 rel_pos = vec2(data.weapon_location[i].rel_pos.y*sinn+data.weapon_location[i].rel_pos.x*coss, data.weapon_location[i].rel_pos.y*coss+data.weapon_location[i].rel_pos.x*sinn); - thread.laser_location.value += rel_pos; - thread.fire_location.value += rel_pos; + laser_location.value += rel_pos; + fire_location.value += rel_pos; } } else if(data.weapon_location) { - thread.laser_location.value += data.weapon_location[i].rel_pos; - thread.fire_location.value += data.weapon_location[i].rel_pos; + laser_location.value += data.weapon_location[i].rel_pos; + fire_location.value += data.weapon_location[i].rel_pos; } - launcher.manager.addEntity(thread.laser_tmpl); - launcher.manager.addEntity(thread.fire_tmpl); + launcher.manager.addEntity(space_invaders.bullet_tmpl[data.laser[i].type],[laser_velocity.ref_, laser_guild.ref_, laser_location.ref_].staticArray); + launcher.manager.addEntity(fire_tmpl,[fire_location.ref_, fire_rotation.ref_, fire_velocity.ref_].staticArray); } } } @@ -1287,7 +1282,7 @@ struct LaserCollisionSystem uint length; const (Entity)[] entity; @readonly CLocation[] location; - @readonly CLaser[] laser; + @readonly CBullet[] laser; @readonly CGuild[] guild; } @@ -1312,69 +1307,63 @@ struct ParticleEmittingSystem struct EntitiesData { uint length; - uint thread_id; + //uint thread_id; CParticleEmitterTime[] emit_time; @readonly CLocation[] location; @readonly CParticleEmitter[] emitter; @optional @readonly CVelocity[] velocity; + @optional @readonly CDepth[] depth; } + + __gshared vec4[] flashes = [vec4(224,0,16,16)*px,vec4(240,0,16,16)*px,vec4(256,0,16,16)*px,vec4(272,0,16,16)*px,vec4(288,0,16,16)*px, + vec4(304,0,16,16)*px,vec4(320,0,16,16)*px]; - struct Thread - { - EntityTemplate*[1] templates; - } - - Thread[] threads; + EntityTemplate*[1] templates; void onCreate() { - threads = Mallocator.makeArray!Thread(32); - - threads[0].templates[0] = launcher.manager.allocateTemplate( + templates[0] = launcher.manager.allocateTemplate( [CLocation.component_id, CTexture.component_id, CScale.component_id, CAnimation.component_id, CParticle.component_id, CRotation.component_id, - CVelocity.component_id, CDamping.component_id].staticArray); - - foreach(ref thread;threads[1 .. $]) - { - thread.templates[0] = launcher.manager.allocateTemplate(threads[0].templates[0]); - } + CVelocity.component_id, CDamping.component_id, CDepth.component_id].staticArray); + *templates[0].getComponent!CAnimation() = CAnimation(flashes,0,2); + *templates[0].getComponent!CParticle() = CParticle(350); + //*templates[0].getComponent!CDepth() = CDepth(-3); } void onDestroy() { - foreach(ref thread;threads[1 .. $]) + foreach(tmpl; templates) { - foreach(tmpl; thread.templates) - { - launcher.manager.freeTemplate(tmpl); - } - } - Mallocator.dispose(threads); + launcher.manager.freeTemplate(tmpl); + } } void onUpdate(EntitiesData data) { - Thread* thread = &threads[data.thread_id]; foreach(i;0..data.length) { data.emit_time[i].time -= launcher.delta_time; while(data.emit_time[i].time < 0) { + CVelocity velocity; + CDepth depth; + CParticleEmitter* emitter = &data.emitter[i]; data.emit_time[i].time += emitter.time_range.x + randomf() * emitter.time_range.y; - EntityTemplate* tmpl = thread.templates[emitter.tmpl_id]; - CLocation* location = tmpl.getComponent!CLocation; - if(location)location.value = data.location[i]; - if(data.velocity) { - CVelocity* velocity = tmpl.getComponent!CVelocity; - if(velocity)velocity.value = data.velocity[i]; + velocity.value = data.velocity[i]; } - launcher.manager.addEntity(tmpl); + + if(data.depth) + { + depth.depth = data.depth[i]; + } + + launcher.manager.addEntity(templates[0],[data.location[i].ref_,velocity.ref_,depth.ref_].staticArray); } } } @@ -1617,10 +1606,7 @@ struct HitPointsSystem __gshared vec4[] explosion_laser_frames = [vec4(80,128,16,16)*px,vec4(96,128,16,16)*px,vec4(112,128,16,16)*px,vec4(128,128,16,16)*px,vec4(144,128,16,16)*px,vec4(160,128,16,16)*px,vec4(176,128,16,16)*px,vec4(192,128,16,16)*px,vec4(208,128,16,16)*px]; EntityTemplate* upgrade_tmpl; - CLocation* upgrade_location; - EntityTemplate* explosion_tmpl; - CLocation* explosion_location; struct EntitiesData { @@ -1629,29 +1615,31 @@ struct HitPointsSystem void onCreate() { - upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimation.component_id, CAnimationLooped.component_id].staticArray); - CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; + upgrade_tmpl = launcher.manager.allocateTemplate( + [CVelocity.component_id, CLocation.component_id, CTexture.component_id, + CScale.component_id, CUpgrade.component_id, CAnimation.component_id, + CAnimationLooped.component_id].staticArray); //tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(0*px,32*px,16*px,16*px); + upgrade_tmpl.getComponent!CTexture().coords = vec4(0*px,32*px,16*px,16*px); *upgrade_tmpl.getComponent!CAnimation = CAnimation(upgrade_laser_frames, 0, 1); - CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; - vel_comp.value = vec2(0,-0.1); - upgrade_location = upgrade_tmpl.getComponent!CLocation; + upgrade_tmpl.getComponent!CVelocity().value = vec2(0,-0.1); - explosion_tmpl = launcher.manager.allocateTemplate([CDepth.component_id, CParticle.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CAnimation.component_id].staticArray); + explosion_tmpl = launcher.manager.allocateTemplate( + [CDepth.component_id, CParticle.component_id, CLocation.component_id, + CTexture.component_id, CScale.component_id, CAnimation.component_id].staticArray); //explosion_tmpl.getComponent!(CTexture).tex = space_invaders.texture; *explosion_tmpl.getComponent!CAnimation = CAnimation(explosion_laser_frames, 0, 1.333); explosion_tmpl.getComponent!(CParticle).life = 600; *explosion_tmpl.getComponent!CDepth = -1; - explosion_location = explosion_tmpl.getComponent!CLocation; } void onDestroy() { launcher.manager.freeTemplate(upgrade_tmpl); + launcher.manager.freeTemplate(explosion_tmpl); } - void handleEvent(Entity* entity, EDamage event) + /*void handleEvent(Entity* entity, EDamage event) { CHitPoints* hp = entity.getComponent!CHitPoints; if(*hp <= 0)return; @@ -1663,7 +1651,7 @@ struct HitPointsSystem } CHitMark* hit_mark = entity.getComponent!CHitMark; if(hit_mark)hit_mark.value = 127; - } + }*/ void handleEvent(Entity* entity, EBulletHit event) { @@ -1690,11 +1678,9 @@ struct HitPointsSystem { if(randomRange(0, 1000) < 5) { - *upgrade_location = *location; - launcher.manager.addEntity(upgrade_tmpl); + launcher.manager.addEntity(upgrade_tmpl,[location.ref_].staticArray); } - *explosion_location = *location; - launcher.manager.addEntity(explosion_tmpl); + launcher.manager.addEntity(explosion_tmpl,[location.ref_].staticArray); } } launcher.manager.removeEntity(entity.id); @@ -1720,6 +1706,55 @@ struct ChildDestroySystem } } +struct ShootWaveSystem +{ + mixin ECS.System; + + struct EntitiesData + { + CLocation[] location; + CShootWaveUponDeath[] shoot_wave; + } + + vec2[] dirs; + + void onCreate() + { + enum count = 24; + dirs = Mallocator.makeArray!vec2(count); + float step = 2 * PI / cast(float)count; + foreach(i;0..count) + { + float angle = step * i; + dirs[i] = vec2(sinf(angle),cosf(angle)) * 0.2; + } + } + + void onDestroy() + { + Mallocator.dispose(dirs); + } + + void handleEvent(Entity* entity, EDeath event) + { + + CShootWaveUponDeath* wave = entity.getComponent!CShootWaveUponDeath; + CLocation* location = entity.getComponent!CLocation; + CGuild* guild = entity.getComponent!CGuild; + + //LaserShootingSystem.bullet_tmpl + EntityTemplate* tmpl = space_invaders.bullet_tmpl[wave.bullet_type]; + foreach(dir;dirs) + { + if(guild)launcher.manager.addEntity(tmpl,[location.ref_,guild.ref_,CVelocity(dir).ref_].staticArray); + else launcher.manager.addEntity(tmpl,[location.ref_,CVelocity(dir).ref_].staticArray); + } + //launcher.manager.addEntity(tmpl);//,[location.ref_].staticArray); + + //launcher.manager.addEntity(space_invaders.bullet_tmpl[0]); + } +} + struct PartsDestroySystem { mixin ECS.System; @@ -1738,9 +1773,14 @@ struct PartsDestroySystem flashes_emitter = launcher.manager.allocateTemplate( [ CVelocity.component_id, CLocation.component_id, CParticleEmitter.component_id, - CParticleEmitterTime.component_id, CTargetParent.component_id + CParticleEmitterTime.component_id, CTargetParent.component_id, CDepth.component_id ].staticArray); - *flashes_emitter.getComponent!CParticleEmitter() = CParticleEmitter(vec2(0,0), vec2(400,400), 0); + *flashes_emitter.getComponent!CParticleEmitter() = CParticleEmitter(vec2(0,0), vec2(800,1600), 0); + } + + void onDestroy() + { + launcher.manager.freeTemplate(flashes_emitter); } void handleEvent(Entity* entity, EDestroyedChild event) @@ -1752,17 +1792,24 @@ struct PartsDestroySystem if(init.type == CInit.Type.boss) { CChildren* children = entity.getComponent!CChildren; - foreach(EntityID child; children.childern) + foreach(ref EntityID child; children.childern) { if(child == event.id) { Entity* child_entity = launcher.manager.getEntity(child); if(child_entity) { + CLocation location; CTargetParent* target_parent = child_entity.getComponent!CTargetParent; + CDepth* target_depth = child_entity.getComponent!CDepth; + CLocation* target_location = child_entity.getComponent!CLocation; + //CVelocity* velocity = child_entity.getComponent!CTargetParent; + + if(target_location)location = *target_location; *flashes_emitter.getComponent!CTargetParent() = *target_parent; - launcher.manager.addEntity(flashes_emitter); + if(target_depth)child = launcher.manager.addEntity(flashes_emitter, [target_depth.ref_, location.ref_].staticArray).id; + else child = launcher.manager.addEntity(flashes_emitter, [location.ref_].staticArray).id; } break; } @@ -1793,7 +1840,7 @@ struct ClampPositionSystem @optional @readonly CColliderScale[] collider_scale; @optional @readonly CScale[] scale; - @optional const (CLaser)[] laser; + @optional const (CBullet)[] laser; @optional const (CUpgrade)[] upgrade; //@optional CVelocity[] velocity; //@optional const (CSideMove)[] side_move; @@ -1891,7 +1938,7 @@ struct MovementSystem const (CVelocity)[] velocity; //components are treated as required by default CLocation[] locations; - //@optional const (CLaser)[] laser; + //@optional const (CBullet)[] laser; const (Entity)[] entities; //@optional CSideMove[] side_move; @@ -1927,8 +1974,10 @@ struct AnimationSystem foreach(i;0..data.length) { data.animation[i].time += dt * data.animation[i].speed; - while(data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time -= cast(float)data.animation[i].frames.length; - data.texture[i].coords = data.animation[i].frames[cast(int)(data.animation[i].time)]; + while(cast(uint)data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time -= cast(float)data.animation[i].frames.length; + if(cast(uint)(data.animation[i].time) >= data.animation[i].frames.length)assert(0); + uint index = cast(uint)(data.animation[i].time); + if(index < data.animation[i].frames.length)data.texture[i].coords = data.animation[i].frames[index]; } } else @@ -1936,8 +1985,9 @@ struct AnimationSystem foreach(i;0..data.length) { data.animation[i].time += dt * data.animation[i].speed; - if(data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time = data.animation[i].frames.length - 0.1; - data.texture[i].coords = data.animation[i].frames[cast(int)(data.animation[i].time)]; + if(cast(uint)data.animation[i].time >= data.animation[i].frames.length)data.animation[i].time = data.animation[i].frames.length - 0.9; + uint index = cast(uint)(data.animation[i].time); + if(index < data.animation[i].frames.length)data.texture[i].coords = data.animation[i].frames[index]; } } @@ -2070,6 +2120,66 @@ struct CShipIterator } } +/*struct SpawnUponDeathSystem +{ + mixin ECS.System; + + struct EntitiesData + { + @readonly CSpawnUponDeath[] spawn; + @optional CTargetParent[] parent; + } + + EntityTemplate* flashes_emitter; + + void onCreate() + { + flashes_emitter = launcher.manager.allocateTemplate( + [ + CVelocity.component_id, CLocation.component_id, CParticleEmitter.component_id, + CParticleEmitterTime.component_id, CTargetParent.component_id + ].staticArray); + *flashes_emitter.getComponent!CParticleEmitter() = CParticleEmitter(vec2(0,0), vec2(400,400), 0); + } + + void onDestroy() + { + launcher.manager.freeTemplate(flashes_emitter); + } + + void onRemoveEntity(EntitiesData data) + { + //CSpawnUponDeath[] spawn = + switch(data.spawn[0].type) + { + case CSpawnUponDeath.Type.flashes_emitter: + if(data.parent) + { + /*Entity* parent_entity = launcher.manager.getEntity(data.parent[0].parent); + CChildren* children = entity.getComponent!CChildren; + foreach(ref EntityID child; children.childern) + { + if(child == event.id) + { + Entity* child_entity = launcher.manager.getEntity(child); + if(child_entity) + { + *flashes_emitter.getComponent!CTargetParent = data.parent[0]; + launcher.manager.addEntity(flashes_emitter); + //child = launcher.manager.addEntity(flashes_emitter); + } + break; + } + } + } + break; + default:break; + } + } + + //void handleEvent(Entity* entity, ) +}//*/ + extern(C) float sqrtf(float x) @nogc nothrow @system; extern(C) float acosf(float x) @nogc nothrow @system; extern(C) float sinf(float x) @nogc nothrow @system; @@ -2198,7 +2308,7 @@ void spaceInvadersStart() launcher.manager.registerComponent!CAutoShoot; launcher.manager.registerComponent!CWeapon; launcher.manager.registerComponent!CVelocity; - launcher.manager.registerComponent!CLaser; + launcher.manager.registerComponent!CBullet; launcher.manager.registerComponent!CSideMove; launcher.manager.registerComponent!CDepth; launcher.manager.registerComponent!CShootGrid; @@ -2223,6 +2333,8 @@ void spaceInvadersStart() launcher.manager.registerComponent!CColliderScale; launcher.manager.registerComponent!CParticleEmitter; launcher.manager.registerComponent!CParticleEmitterTime; + launcher.manager.registerComponent!CSpawnUponDeath; + launcher.manager.registerComponent!CShootWaveUponDeath; launcher.manager.registerEvent!EChangeDirection; launcher.manager.registerEvent!EDamage; @@ -2248,16 +2360,17 @@ void spaceInvadersStart() launcher.manager.registerSystem!ParticleSystem(-100); launcher.manager.registerSystem!AnimationSystem(-100); launcher.manager.registerSystem!DampingSystem(-101); - launcher.manager.registerSystem!MoveToParentTargetSystem(99); + launcher.manager.registerSystem!MoveToParentTargetSystem(-98); launcher.manager.registerSystem!ParentOwnerSystem(-101); launcher.manager.registerSystem!ShipWeaponSystem(-100); - launcher.manager.registerSystem!ParticleEmittingSystem(-100); - + launcher.manager.registerSystem!ParticleEmittingSystem(-95); launcher.manager.registerSystem!RotateToTargetSystem(-100); launcher.manager.registerSystem!ShipTargetSystem(-110); launcher.manager.registerSystem!CShipIterator(-100); launcher.manager.registerSystem!PartsDestroySystem(-80); launcher.manager.registerSystem!ChildDestroySystem(-110); + launcher.manager.registerSystem!ShootWaveSystem(-100); + //launcher.manager.registerSystem!SpawnUponDeathSystem(-110); launcher.manager.endRegister(); @@ -2267,19 +2380,26 @@ void spaceInvadersStart() launcher.gui_manager.addSystem(MovementSystem.system_id,"Movement System"); launcher.gui_manager.addSystem(ClampPositionSystem.system_id,"Clamp Position System"); launcher.gui_manager.addSystem(ChangeDirectionSystem.system_id,"Change Direction System"); - launcher.gui_manager.addSystem(LaserCollisionSystem.system_id,"Draw System"); + launcher.gui_manager.addSystem(LaserCollisionSystem.system_id,"Laser Collision System"); launcher.gui_manager.addSystem(ShootGridManager.system_id,"Shoot Grid Manager"); launcher.gui_manager.addSystem(ShootGridCleaner.system_id,"Shoot Grid Cleaner"); launcher.gui_manager.addSystem(HitPointsSystem.system_id,"Hit Points System"); - launcher.gui_manager.addSystem(HitMarkingSystem.system_id,"Hit Matking System"); + launcher.gui_manager.addSystem(HitMarkingSystem.system_id,"Hit Marking System"); launcher.gui_manager.addSystem(UpgradeCollisionSystem.system_id,"Upgrade Collision System"); launcher.gui_manager.addSystem(UpgradeSystem.system_id,"Upgrade System"); launcher.gui_manager.addSystem(ParticleSystem.system_id,"Particle System"); launcher.gui_manager.addSystem(AnimationSystem.system_id,"Animation System"); launcher.gui_manager.addSystem(DampingSystem.system_id,"Damping System"); launcher.gui_manager.addSystem(MoveToParentTargetSystem.system_id,"Move To Target System"); - launcher.gui_manager.addSystem(ParentOwnerSystem.system_id,"Parent Owner System System"); + launcher.gui_manager.addSystem(ParentOwnerSystem.system_id,"Parent Owner System"); launcher.gui_manager.addSystem(ShipWeaponSystem.system_id,"Ship Weapon System"); + launcher.gui_manager.addSystem(ParticleEmittingSystem.system_id,"Particle Emitting System"); + launcher.gui_manager.addSystem(RotateToTargetSystem.system_id,"Rotate To Target System"); + launcher.gui_manager.addSystem(ShipTargetSystem.system_id,"Ship Target System"); + launcher.gui_manager.addSystem(PartsDestroySystem.system_id,"Parts Destroy System"); + launcher.gui_manager.addSystem(ChildDestroySystem.system_id,"Child Destroy System"); + launcher.gui_manager.addSystem(ShootWaveSystem.system_id,"Shoot Wave System"); + //launcher.gui_manager.addSystem(SpawnUponDeathSystem.system_id,"Child Destroy System"); //launcher.manager.getSystem(CleanSystem.system_id).disable(); { @@ -2290,30 +2410,23 @@ void spaceInvadersStart() CShootDirection.component_id, CShootGrid.component_id, CGuild.component_id, CDamping.component_id, CChildren.component_id, CInit.component_id].staticArray ); - //CWeapon* weapon = space_invaders.ship_tmpl.getComponent!CWeapon; - //weapon.level = 3; - space_invaders.ship_tmpl.getComponent!CTexture().coords = vec4(0*px,80*px,48*px,32*px); + space_invaders.ship_tmpl.getComponent!CTexture().coords = vec4(0,80,48,32)*px; space_invaders.ship_tmpl.getComponent!CScale().value = vec2(48,32); - space_invaders.ship_tmpl.getComponent!CLocation().value = vec2(64,64); space_invaders.ship_tmpl.getComponent!CHitPoints().value = 1000; space_invaders.ship_tmpl.getComponent!CDamping().value = 7; space_invaders.ship_tmpl.getComponent!CInit().type = CInit.Type.space_ship; space_invaders.ship_tmpl.getComponent!CColliderScale().value = vec2(26,24); - launcher.manager.addEntity(space_invaders.ship_tmpl); + launcher.manager.addEntity(space_invaders.ship_tmpl,[CLocation(vec2(64,64)).ref_].staticArray); } { - ushort[6] components = [CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CLaser.component_id, CGuild.component_id]; + ushort[6] components = [CLocation.component_id, CTexture.component_id, CVelocity.component_id, CScale.component_id, CBullet.component_id, CGuild.component_id]; space_invaders.laser_tmpl = launcher.manager.allocateTemplate(components); - CTexture* tex_comp = space_invaders.laser_tmpl.getComponent!CTexture; - //tex_comp.tex = 0;//space_invaders.texture;//laser_tex; - tex_comp.coords = vec4(0*px,24*px,2*px,8*px); - CScale* scale_comp = space_invaders.laser_tmpl.getComponent!CScale; - scale_comp.value = vec2(2,8); - CVelocity* vel_comp = space_invaders.laser_tmpl.getComponent!CVelocity; - vel_comp.value = vec2(0,1); + space_invaders.laser_tmpl.getComponent!CTexture().coords = vec4(0,24,2,8)*px; + space_invaders.laser_tmpl.getComponent!CScale().value = vec2(2,8); + space_invaders.laser_tmpl.getComponent!CVelocity().value = vec2(0,1); } EntityTemplate* enemy_tmpl; @@ -2333,11 +2446,12 @@ void spaceInvadersStart() CDepth.component_id].staticArray ); - CTexture* tex_comp = boss_tmpl.getComponent!CTexture; + //CTexture* tex_comp = boss_tmpl.getComponent!CTexture; //tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(128*px,0*px,96*px,48*px); - CLocation* loc_comp = boss_tmpl.getComponent!CLocation; - loc_comp.value = vec2(64,space_invaders.map_size.y - 16); + //tex_comp.coords = vec4(128*px,0*px,96*px,48*px); + //CLocation* loc_comp = boss_tmpl.getComponent!CLocation; + //loc_comp.value = vec2(64,space_invaders.map_size.y - 16); + boss_tmpl.getComponent!CTexture().coords = vec4(128,0,96,48)*px; boss_tmpl.getComponent!CGuild().guild = 1; boss_tmpl.getComponent!CInit().type = CInit.Type.boss; boss_tmpl.getComponent!CScale().value = vec2(96,48); @@ -2354,11 +2468,7 @@ void spaceInvadersStart() CChildren.component_id].staticArray ); - CTexture* tex_comp = tower_tmpl.getComponent!CTexture; - //tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(96*px,96*px,16*px,16*px); - CLocation* loc_comp = tower_tmpl.getComponent!CLocation; - loc_comp.value = vec2(64,space_invaders.map_size.y - 16); + tower_tmpl.getComponent!CTexture().coords = vec4(96,96,16,16)*px; tower_tmpl.getComponent!CGuild().guild = 1; tower_tmpl.getComponent!CInit().type = CInit.Type.tower; tower_tmpl.getComponent!CHitPoints().value = 10; @@ -2373,35 +2483,29 @@ void spaceInvadersStart() CGuild.component_id].staticArray ); - CTexture* tex_comp = space_invaders.enemy_tmpl.getComponent!CTexture; - //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 - 16); - CShootDirection* shoot_dir_comp = space_invaders.enemy_tmpl.getComponent!CShootDirection; - shoot_dir_comp.direction = Direction.down; - CVelocity* vel_comp = space_invaders.enemy_tmpl.getComponent!CVelocity; - vel_comp.value = vec2(0.1,0); + space_invaders.enemy_tmpl.getComponent!CTexture().coords = vec4(32,32,16,16)*px; + space_invaders.enemy_tmpl.getComponent!CShootDirection().direction = Direction.down; + space_invaders.enemy_tmpl.getComponent!CVelocity().value = vec2(0.1,0); space_invaders.enemy_tmpl.getComponent!CGuild().guild = 1; space_invaders.enemy_tmpl.getComponent!CWeaponLocation().rel_pos = vec2(0,-15); Entity* current_entity; - current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); + current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl,[CLocation(vec2(32,space_invaders.map_size.y - 16)).ref_].staticArray); launcher.manager.addComponents(current_entity.id,CSideMove(0)); - loc_comp.value = vec2(128,space_invaders.map_size.y - 16); - current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); + //loc_comp.value = vec2(128,space_invaders.map_size.y - 16); + current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl,[CLocation(vec2(128,space_invaders.map_size.y - 16)).ref_].staticArray); launcher.manager.addComponents(current_entity.id,CSideMove(-1)); enemy_id = current_entity.id; //enemy_tmpl = launcher.manager.allocateTemplate(current_entity.id); - loc_comp.value = vec2(256,space_invaders.map_size.y - 16); - launcher.manager.addEntity(space_invaders.enemy_tmpl); + //loc_comp.value = vec2(256,space_invaders.map_size.y - 16); + launcher.manager.addEntity(space_invaders.enemy_tmpl,[CLocation(vec2(256,space_invaders.map_size.y - 16)).ref_].staticArray); - loc_comp.value = vec2(0,space_invaders.map_size.y - 16); - current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl); + //loc_comp.value = vec2(0,space_invaders.map_size.y - 16); + current_entity = launcher.manager.addEntity(space_invaders.enemy_tmpl,[CLocation(vec2(0,space_invaders.map_size.y - 16)).ref_].staticArray); launcher.manager.addComponents(current_entity.id,CSideMove(0)); grouped_id = current_entity.id; @@ -2412,11 +2516,8 @@ void spaceInvadersStart() { upgrade_tmpl = launcher.manager.allocateTemplate([CVelocity.component_id, CLocation.component_id, CTexture.component_id, CScale.component_id, CUpgrade.component_id, CAnimationLooped.component_id, CAnimation.component_id].staticArray); - CTexture* tex_comp = upgrade_tmpl.getComponent!CTexture; - //tex_comp.tex = space_invaders.texture;//ship_tex; - tex_comp.coords = vec4(0*px,32*px,16*px,16*px); - CVelocity* vel_comp = upgrade_tmpl.getComponent!CVelocity; - vel_comp.value = vec2(0,-0.1); + upgrade_tmpl.getComponent!CTexture().coords = vec4(0,32,16,16)*px; + upgrade_tmpl.getComponent!CVelocity().value = vec2(0,-0.1); *upgrade_tmpl.getComponent!CAnimation = CAnimation(HitPointsSystem.upgrade_laser_frames, 0, 0.75); } @@ -2425,6 +2526,24 @@ void spaceInvadersStart() enemy_tmpl = launcher.manager.allocateTemplate(enemy_id); grouped_tmpl = launcher.manager.allocateTemplate(grouped_id); + space_invaders.bullet_tmpl[0] = launcher.manager.allocateTemplate( + [CLocation.component_id, CTexture.component_id, CVelocity.component_id, + CScale.component_id, CBullet.component_id, CGuild.component_id].staticArray + ); + space_invaders.bullet_tmpl[0].getComponent!CTexture().coords = vec4(0,24,2,8)*px; + space_invaders.bullet_tmpl[0].getComponent!CScale().value = vec2(2,8); + + space_invaders.bullet_tmpl[1] = launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[0]); + space_invaders.bullet_tmpl[2] = launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[0]); + space_invaders.bullet_tmpl[2].getComponent!CTexture().coords = vec4(64,32,8,16)*px; + space_invaders.bullet_tmpl[2].getComponent!CScale().value = vec2(8,16); + space_invaders.bullet_tmpl[3] = launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[0]); + space_invaders.bullet_tmpl[3].getComponent!CTexture().coords = vec4(56,32,2,2)*px; + space_invaders.bullet_tmpl[3].getComponent!CScale().value = vec2(2,2); + // bullet_tmpl[3].getComponent!CTexture().coords = vec4(48,32,8,8)*px; + // bullet_tmpl[3].getComponent!CScale().value = vec2(8,8); + space_invaders.bullet_tmpl[4] = launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[0]); + launcher.gui_manager.addTemplate(enemy_tmpl,"Enemy"); launcher.gui_manager.addTemplate(grouped_tmpl,"Grouped enemy"); launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.ship_tmpl),"Ship"); @@ -2432,6 +2551,9 @@ void spaceInvadersStart() launcher.gui_manager.addTemplate(upgrade_tmpl,"Upgrade"); launcher.gui_manager.addTemplate(tower_tmpl,"Tower"); launcher.gui_manager.addTemplate(boss_tmpl,"Boss"); + launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[3]),"Cannon bullet"); + //launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[4]),"Laser"); + //launcher.gui_manager.addTemplate(launcher.manager.allocateTemplate(space_invaders.bullet_tmpl[5]),"Laser"); } void spaceInvadersEnd() @@ -2445,6 +2567,7 @@ void spaceInvadersEnd() //launcher.manager.freeTemplate(space_invaders.enemy_tmpl); Mallocator.dispose(space_invaders); + space_invaders = null; } void spaceInvadersTool(vec2 position, Tool tool, int size) diff --git a/demos/source/gui/manager.d b/demos/source/gui/manager.d index 185fcd1..4d452ad 100644 --- a/demos/source/gui/manager.d +++ b/demos/source/gui/manager.d @@ -30,6 +30,7 @@ struct GUIManager systems.clear(); templates.clear(); + selected_tempalte = 0; } EntityTemplate* getSelectedTemplate() -- 2.47.2 From 2ad238841bb98048faddad9af63dd3f087b8a467 Mon Sep 17 00:00:00 2001 From: Mergul Date: Thu, 28 May 2020 16:41:58 +0200 Subject: [PATCH 37/37] Fixed .gitlab-ci.yml emscripten build --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a2d26ff..376aead 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -76,7 +76,7 @@ emscripten: stage: build_emscripten image: "registry.gitlab.com/mergul/bubel-ecs/emscripten:latest" dependencies: - - build_code + - build_wasm script: - /bin/bash /build.sh rules: -- 2.47.2