1 module demos.simple; 2 3 import app; 4 5 import bindbc.sdl; 6 7 import bubel.ecs.attributes; 8 import bubel.ecs.core; 9 import bubel.ecs.entity; 10 import bubel.ecs.manager; 11 import bubel.ecs.std; 12 13 import cimgui.cimgui; 14 15 import ecs_utils.gfx.texture; 16 import ecs_utils.math.vector; 17 import ecs_utils.utils; 18 19 import game_core.basic; 20 import game_core.rendering; 21 22 extern(C): 23 24 enum float px = 1.0/512.0; 25 26 /************************************************** 27 All demos uses same patten. Every demo is self contained except systems and components which was splitted into different files to enable sharing them between demos. 28 Every demo has same functions: 29 * register - called on start for registering proces 30 * initialize - called after register to initialize demo data 31 * deinitiliaze - called when demo is switching. There data is deisposed 32 * loop - it's called every frame 33 34 Demos uses some non-ECS functions to register components, systems and templates into GUI. Then components are showing by GUI and can be added to entities. 35 */ 36 37 /*####################################################################################################################### 38 ------------------------------------------------ Components ------------------------------------------------------------------ 39 #######################################################################################################################*/ 40 41 //CLocation component was moved to game_code.basic 42 /*struct CLocation 43 { 44 mixin ECS.Component; 45 46 alias location this; 47 48 vec2 location; 49 }*/ 50 51 /*####################################################################################################################### 52 ------------------------------------------------ Systems ------------------------------------------------------------------ 53 #######################################################################################################################*/ 54 //DrawSystem was moved to game_code.basic 55 /* 56 struct DrawSystem 57 { 58 mixin ECS.System!32; 59 60 struct EntitiesData 61 { 62 uint length; 63 //uint thread_id; 64 uint job_id; 65 @readonly CLocation[] locations; 66 } 67 68 void onUpdate(EntitiesData data) 69 { 70 if(launcher.renderer.prepared_items >= launcher.renderer.MaxObjects)return;//simple leave loop if max visible objects count was reached 71 import ecs_utils.gfx.renderer; 72 Renderer.DrawData draw_data; 73 draw_data.size = vec2(16,16); 74 draw_data.coords = vec4(0,0,1,1); 75 draw_data.color = 0x80808080; 76 draw_data.material_id = 0; 77 draw_data.thread_id = data.job_id; 78 draw_data.texture = simple.texture; 79 80 foreach(i; 0..data.length) 81 { 82 draw_data.position = data.locations[i]; 83 draw_data.depth = cast(ushort)(data.locations[i].y); 84 launcher.renderer.draw(draw_data); 85 } 86 } 87 }*/ 88 89 //simple system which moves entities 90 struct MoveSystem 91 { 92 //system will generate up to 64 jobs for multithreaded extecution 93 mixin ECS.System!64; 94 95 //structe contains components used by system 96 struct EntitiesData 97 { 98 uint length; 99 //system will use one component which is required. Only entities with CLocation component will be processed by this system 100 CLocation[] locations; 101 } 102 103 //onUpdate is called several times and covers all entities 104 void onUpdate(EntitiesData data) 105 { 106 //loop over entities in batch 107 foreach(i; 0..data.length) 108 { 109 //inscrease entity position in 'y' coordinate 110 data.locations[i].y = data.locations[i].y + 1; 111 //move entity to 0 if exceeded 300 112 if(data.locations[i].y > 300)data.locations[i].y = 0; 113 } 114 } 115 } 116 117 /*####################################################################################################################### 118 ------------------------------------------------ Functions ------------------------------------------------------------------ 119 #######################################################################################################################*/ 120 121 struct Simple 122 { 123 //tips showed in GUI 124 __gshared const (char)* tips = "Use \"space\" to spwan entities.\n\nSystems can be enabled/disabled from \"Demo\" window. 125 \"Tools\" window exists of three tools which can be used to manipulate game. 126 Options: 127 * Show Tool - enable/disable rendering of blue circle around cursor 128 * Show Filtered - enable/disable higliting filtered entities. For \"Component manipulator\" tool it shows entities which has selected component. 129 * Add/Remove - select primary action. LMB - primary action, RMB - secondary action 130 * Tools size - size of tool 131 * Tool repeat - how many times in one second tool should take action (e.g. 1000 means every second \"Entity spawner\" will spawn 1000 enties) 132 * Override - enabled means that \"Component manipulator\" will override components data if entity already has that component 133 Tools: 134 * Entity spawner - used to spawn new entities 135 * Component manipulator - used to add/remove components to/from entities 136 * Selector - allow to select entity, show and modify his data. Only one entity can be selected, selector selects entity nearest co cursor. 137 138 ShortCuts: 139 * CRTL*1/2/3 - change tool 140 * Mouse wheel - change tool size 141 * SHIFT + Mouse wheel - change entity/component in tool list 142 * LBM - primary action (default: add entity / add component) 143 * RMB - secondary action (default: remove entity / remove component) 144 145 \"Statistic\" windows shows FPS and entities count. 146 147 From top menu bar (upper left corner) you can select different demos or change some options. Multihtreading is highly recommended, but it can not working on mobile phones or Firefox browser. 148 149 Demo is capable rendering of hundreds of thousands of entities. Playable area is heavily too small to show that count of entities, but you can try it :)"; 150 151 EntityTemplate* tmpl; 152 Texture texture; 153 } 154 155 __gshared Simple* simple; 156 157 //called when demo starts 158 void simpleRegister() 159 { 160 simple = Mallocator.make!Simple; 161 162 //load texture (atlas) 163 simple.texture.create(); 164 simple.texture.load("assets/textures/atlas.png"); 165 166 //start registering process 167 gEntityManager.beginRegister(); 168 169 //register basic components and systems 170 registerRenderingModule(gEntityManager); 171 172 //register location component. It also registered inside registerRenderingModule() function, but it's there for clarity 173 gEntityManager.registerComponent!CLocation; 174 175 gEntityManager.registerSystem!MoveSystem(0); 176 // DrawSystem is registered as RenderingModule 177 // gEntityManager.registerSystem!DrawSystem(1); 178 179 //end registering process 180 gEntityManager.endRegister(); 181 } 182 183 //called after simpleRegister 184 void simpleStart() 185 { 186 //get DrawSystem instance and change some data 187 DrawSystem* draw_system = gEntityManager.getSystem!DrawSystem; 188 draw_system.default_data.color = 0x80808080; 189 draw_system.default_data.texture = simple.texture; 190 draw_system.default_data.size = vec2(16,16); 191 draw_system.default_data.coords = vec4(0,48,16,16)*px;//vec4(0,0,1,1); 192 193 //add systems to GUI. It's non ECS part 194 launcher.gui_manager.addSystem(becsID!MoveSystem,"Move Up System"); 195 launcher.gui_manager.addSystem(becsID!DrawSystem,"Draw System"); 196 197 //add components to GUI. It's non ECS part 198 launcher.gui_manager.addComponent(CLocation(), "Location"); 199 launcher.gui_manager.addComponent(CDrawDefault(), "DrawDefault"); 200 201 //allocate new template with two components 202 simple.tmpl = gEntityManager.allocateTemplate([becsID!CLocation, becsID!CDrawDefault].staticArray); 203 204 //add template to GUI. It's non ECS part 205 launcher.gui_manager.addTemplate(simple.tmpl, "Basic"); 206 207 //add 100 entities 208 foreach(i; 0..10) 209 foreach(j; 0..10) 210 { 211 //add entities in grid locations. "ref_" return ComponentRef structure. I'm not sure if adding component inside array generation isn't undefined behaviour but it works on tested platforms 212 gEntityManager.addEntity(simple.tmpl,[CLocation(vec2(i*16+64,j*16+64)).ref_].staticArray); 213 } 214 } 215 216 //called when demo is switched to different one 217 void simpleEnd() 218 { 219 //disable systems used by this demo. 220 gEntityManager.getSystem(becsID!MoveSystem).disable(); 221 gEntityManager.getSystem(becsID!DrawSystem).disable(); 222 223 //free texture memory 224 simple.texture.destroy(); 225 226 //GUI manager will free template 227 //gEntityManager.freeTemplate(simple.tmpl); 228 Mallocator.dispose(simple); 229 } 230 231 void simpleEvent(SDL_Event* event) 232 { 233 } 234 235 void spawnEntity() 236 { 237 //spawn entity in random location 238 gEntityManager.addEntity(simple.tmpl,[CLocation(vec2(randomf() * 400,0)).ref_].staticArray); 239 } 240 241 bool simpleLoop() 242 { 243 launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5; 244 245 if(launcher.getKeyState(SDL_SCANCODE_SPACE)) 246 { 247 foreach(i;0..20)spawnEntity(); 248 } 249 250 //begin frame 251 gEntityManager.begin(); 252 //if multithreading is enabled different path is used 253 if(launcher.multithreading) 254 { 255 //prepare data for multithreading. Clear previous jobs data. 256 launcher.job_updater.begin(); 257 //generate jobs 258 gEntityManager.updateMT(); 259 //call jobs in multithreaded fashion 260 launcher.job_updater.call(); 261 } 262 else 263 { 264 //update call will call inUpdate for all systems 265 gEntityManager.update(); 266 267 } 268 //end ECS frame 269 gEntityManager.end(); 270 271 return true; 272 } 273 274 DemoCallbacks getSimpleDemo() 275 { 276 DemoCallbacks demo; 277 demo.register = &simpleRegister; 278 demo.initialize = &simpleStart; 279 demo.deinitialize = &simpleEnd; 280 demo.loop = &simpleLoop; 281 demo.tips = simple.tips; 282 return demo; 283 }