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 }