1 /++
2 A module for playing and organizing a game cycle with a scene manager.
3 
4 Macros:
5     LREF = <a href="#$1">$1</a>
6     HREF = <a href="$1">$2</a>
7 
8 Authors: $(HREF https://github.com/TodNaz,TodNaz)
9 Copyright: Copyright (c) 2020 - 2021, TodNaz.
10 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
11 +/
12 module tida.game;
13 
14 import tida.window;
15 import tida.render;
16 import tida.fps;
17 import tida.scenemanager;
18 import tida.scene;
19 import tida.event;
20 import tida.image;
21 import tida.listener;
22 import tida.loader;
23 import tida.gl;
24 
25 import std.concurrency;
26 
27 __gshared
28 {
29     IWindow _window;
30     IRenderer _renderer;
31     FPSManager _fps;
32     Listener _listener;
33 }
34 
35 /// Window global object.
36 @property IWindow window() @trusted
37 {
38     return _window;
39 }
40 
41 /// Render global object.
42 @property IRenderer renderer() @trusted
43 {
44     return _renderer;
45 }
46 
47 /// FPS global object
48 @property FPSManager fps() @trusted
49 {
50     return _fps;
51 }
52 
53 /// Listener global object
54 @property Listener listener() @trusted
55 {
56     return _listener;
57 }
58 
59 struct ClosedThreadMessage { }
60 
61 /++
62 Game initialization information.
63 +/
64 struct GameConfig
65 {
66     import tida.color;
67 
68 public:
69     uint windowWidth = 640; /// Window width.
70     uint windowHeight = 480; /// Window height.
71     string windowTitle = "Tida"; /// Window title.
72     Color!ubyte background = rgb(255, 255, 255); /// Window background.
73     int positionWindowX = 100; /// Window position x-axis.
74     int positionWindowY = 100; /// Window position y-axis.
75     string icon; /// Icon path.
76     
77     size_t maxThreads = 3;
78     size_t functionPerThread = 80;
79 }
80 
81 private void workerThread(Tid owner, size_t id)
82 {
83     bool isRun = true;
84     bool isPaused = false;
85     FPSManager threadFPS = new FPSManager();
86     
87     while (isRun)
88     {
89         threadFPS.countDown();
90         
91         if (id in sceneManager.threadAPI)
92         {   
93             import std.range : popFront, front, empty;  
94             while(!sceneManager.threadAPI[id].empty)
95             {
96                 immutable response = sceneManager.threadAPI[id].front;
97                 sceneManager.threadAPI[id].popFront();
98                 
99                 switch (response.code)
100                 {
101                     case APIType.ThreadClose:
102                         isRun = 0;
103                         owner.send(APIType.ThreadClosed);
104                         return;
105                         
106                     case APIType.ThreadPause:
107                         isPaused = true;
108                     break;
109                     
110                     case APIType.ThreadResume:
111                         isPaused = false;
112                     break;
113 
114                     case APIType.ThreadRebindThreadID:
115                         id = response.value;
116                     break;
117                     
118                     default:
119                         continue;
120                 }
121             }
122         }
123         
124         if (isPaused)
125             continue;
126         
127         if (!sceneManager.isThereGoto)
128             sceneManager.callStep(id, null);
129         
130         threadFPS.control();
131     }
132 }
133 
134 class Game
135 {
136 private:
137     EventHandler event;
138     Tid[] threads;
139     
140     bool isGame = true;
141 
142 public:
143     int result = 0;
144 
145 @trusted:
146     this(GameConfig config)
147     {
148         _window = new Window(config.windowWidth, config.windowHeight, config.windowTitle);
149         (cast(Window) _window).windowInitialize!(WithContext)(config.positionWindowX,config.positionWindowY);
150         loadGraphicsLibrary();
151         _renderer = createRenderer(_window);
152         _renderer.background = config.background;
153         if (config.icon != "")
154             _window.icon = new Image().load(config.icon);
155 
156         initSceneManager();
157         event = new EventHandler((cast(Window) window));
158         _fps = new FPSManager();
159         _loader = new Loader();
160         _listener = new Listener();
161         
162         sceneManager.maxThreads = config.maxThreads;
163         sceneManager.functionPerThread = config.functionPerThread;
164     }
165 
166     void threadClose(uint value) @trusted
167     {
168         import std.algorithm : remove;
169 
170         threads[value].send(APIType.ThreadClose, 0);
171 
172         foreach(i; value .. threads.length) 
173         {
174             threads[i].send(APIType.ThreadRebindThreadID, i - 1);
175         }
176         
177         threads.remove(value);
178     }
179 
180     private void exit() @trusted
181     {
182         isGame = false;
183         
184         foreach (size_t i, ref e; threads)
185         {
186             sceneManager.threadAPI[i + 1] ~= APIResponse(APIType.ThreadClose, cast(uint) i);
187         }
188         
189         sceneManager.callGameExit();
190     }
191 
192     /++
193     Starts the game loop of the game.
194     +/
195     void run() @trusted
196     {
197         sceneManager.callGameStart();
198 
199         version (manualThreadControl)
200         {
201             // Manual support thread...
202         } else
203         {
204             size_t countThreads = sceneManager.maxThreads;
205             if (sceneManager.countStartThreads > countThreads)
206                 countThreads = sceneManager.countStartThreads;
207         
208             foreach (i; 0 .. countThreads)
209             {
210                 immutable id = i + 1;
211                 threads ~= spawn(&workerThread, thisTid, id);
212             }
213 
214             foreach (e; sceneManager.scenes)
215             {
216                 e.initThread(countThreads);
217             }
218 
219             sceneManager.countStartThreads = 0;
220         }
221 
222         while (isGame)
223         {
224             _fps.countDown();
225 
226             scope (failure)
227             {
228                 sceneManager.callOnError();
229                 exit();
230             }
231 
232             while (event.nextEvent)
233             {
234                 if (event.isQuit)
235                 {
236                     exit();
237                     return;
238                 }
239 
240                 sceneManager.callEvent(event);
241                 if (listener !is null) listener.eventHandle(event);
242             }
243 
244             if (listener !is null) listener.timerHandle();
245 
246             foreach (response; sceneManager.api)
247             {
248                 switch (response.code)
249                 {
250                     case APIType.ThreadCreate:
251                     {
252                         foreach (i; 0 .. response.value)
253                         {
254                             immutable id = i + 1;
255                             threads ~= spawn(&workerThread, thisTid, id);
256                         }
257                     }
258                     break;
259                     
260                     case APIType.ThreadPause, APIType.ThreadResume:
261                     {
262                         if (response.value > threads.length)
263                         {
264                             sceneManager.apiError[response.code] = APIError.ThreadIsNotExists;
265                             continue;
266                         }
267                         
268                         sceneManager.threadAPI[response.value] ~= response;
269                     }
270                     break;
271                     
272                     case APIType.ThreadClose:
273                     {
274                         if (response.value > threads.length)
275                         {
276                             sceneManager.apiError[response.code] = APIError.ThreadIsNotExists;
277                             continue;
278                         }
279                         
280                         if (response.value == 0)
281                             exit();
282                         else
283                             sceneManager.threadAPI[response.value] ~= response;
284                     }
285                     break;
286                     
287                     case APIType.GameClose:
288                     {
289                     	result = response.value;
290                     	exit();
291                     }
292                     break;
293                     
294                     default:
295                         sceneManager.apiError[response.code] = APIError.UnkownResponse;
296                         break;
297                 }
298             }
299 
300             sceneManager.api = []; // GC, please, clear this
301 
302             sceneManager.callStep(0, renderer);
303 
304             renderer.clear();
305             sceneManager.callDraw(renderer);
306             renderer.drawning();
307 
308             _fps.control();
309         }
310     }
311 }
312 
313 template Lazy (T)
314 if (isScene!T)
315 {
316     struct Lazy
317     {
318         T scene;
319     }
320 }
321 
322 template isLazy (T)
323 {
324     import std.traits : TemplateOf;
325     
326     enum isLazy = __traits(isSame, TemplateOf!(T), Lazy);
327 }
328 
329 template lazyScene (T)
330 if (isLazy!T)
331 {
332     alias lazyScene = typeof(T.scene);
333 }
334 
335 template LazyGroup(T...)
336 {
337     struct LazyGroup
338     {
339         alias Groups = T;
340     }
341 }
342 
343 template isLazyGroup (T)
344 {
345     import std.traits : TemplateOf;
346 
347     enum isLazyGroup = __traits(isSame, TemplateOf!(T), LazyGroup);
348 }
349 
350 template lazyGroupScenes (T)
351 if (isLazyGroup!T)
352 {
353     import std.traits : TemplateArgsOf;
354 
355     alias lazyGroupScenes = TemplateArgsOf!T;
356 }
357 
358 /++
359 Game entry point template.
360 
361 After the configuration indicates the list of scenes that do share in the game.
362 If you need to designate the scene on the list, but not to highlight the memory
363 of it first, it is possible to designate it, as lazy to load resources only
364 when moving to it.
365 
366 Params:
367     config =    The input configuration describes what needs to be loaded,
368                 what initial parameters should be.
369 +/
370 template GameRun(GameConfig config, T...)
371 {
372     import tida.runtime;
373     import tida.scenemanager;
374 
375     version(unittest) {} else
376     int main(string[] args)
377     {
378         TidaRuntime.initialize(args, AllLibrary);
379         Game game = new Game(config);
380 
381         static foreach (e; T)
382         {
383             static if (isScene!e)
384             {
385                 sceneManager.add(new e());
386             } else
387             static if (isLazy!e)
388             {
389                 sceneManager.lazyAdd!(lazyScene!e);
390             } else
391             static if (isLazyGroup!e)
392             {
393                 sceneManager.lazyGroupAdd!(lazyGroupScenes!e);
394             }
395         }
396 
397         debug(single)
398         {
399             static foreach (e; T)
400             {
401             mixin("
402             debug(single_" ~ e.stringof ~ ")
403             {
404                 sceneManager.gotoin!(" ~ e.stringof ~ ");
405             }
406             ");
407             }
408         }else
409             sceneManager.inbegin();
410 
411         game.run();
412 
413         return game.result;
414     }
415 }