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 __gshared
26 {
27     IWindow _window;
28     IRenderer _renderer;
29     FPSManager _fps;
30     Listener _listener;
31 }
32 
33 /// Window global object.
34 @property IWindow window() @trusted
35 {
36     return _window;
37 }
38 
39 /// Render global object.
40 @property IRenderer renderer() @trusted
41 {
42     return _renderer;
43 }
44 
45 /// FPS global object
46 @property FPSManager fps() @trusted
47 {
48     return _fps;
49 }
50 
51 /// Listener global object
52 @property Listener listener() @trusted
53 {
54     return _listener;
55 }
56 
57 /++
58 Game initialization information.
59 +/
60 struct GameConfig
61 {
62     import tida.color;
63 
64 public:
65     uint windowWidth = 640; /// Window width.
66     uint windowHeight = 480; /// Window height.
67     string windowTitle = "Tida"; /// Window title.
68     Color!ubyte background = rgb(255, 255, 255); /// Window background.
69     int positionWindowX = 100; /// Window position x-axis.
70     int positionWindowY = 100; /// Window position y-axis.
71     string icon; /// Icon path.
72 }
73 
74 class Game
75 {
76 private:
77     EventHandler event;
78     InstanceThread[] threads;
79     bool isGame = true;
80 
81 public:
82     int result = 0;
83 
84 @trusted:
85     this(GameConfig config)
86     {
87         _window = new Window(config.windowWidth, config.windowHeight, config.windowTitle);
88         (cast(Window) _window).windowInitialize!(WithContext)(config.positionWindowX,config.positionWindowY);
89         loadGraphicsLibrary();
90         _renderer = createRenderer(_window);
91         _renderer.background = config.background;
92         if (config.icon != "")
93             _window.icon = new Image().load(config.icon);
94 
95         initSceneManager();
96         event = new EventHandler((cast(Window) window));
97         _fps = new FPSManager();
98         _loader = new Loader();
99         _listener = new Listener();
100     }
101 
102     void threadClose(uint value) @safe
103     {
104         import std.algorithm : remove;
105 
106         threads[value].exit();
107         foreach(i; value .. threads.length) threads[i].rebindThreadID(i - 1);
108         threads.remove(value);
109     }
110 
111     private void exit() @safe
112     {
113         import std.algorithm : each;
114 
115         isGame = false;
116         threads.each!((e) { if(e !is null) e.exit(); });
117         sceneManager.callGameExit();
118     }
119 
120     void run() @trusted
121     {
122         sceneManager.callGameStart();
123 
124         _fps = new FPSManager();
125 
126         while (isGame)
127         {
128             fps.countDown();
129 
130             scope (failure)
131             {
132                 sceneManager.callOnError();
133             }
134 
135             while (event.nextEvent)
136             {
137                 if (event.isQuit)
138                 {
139                     exit();
140                     return;
141                 }
142 
143                 sceneManager.callEvent(event);
144                 if (listener !is null) listener.eventHandle(event);
145             }
146 
147             if (listener !is null) listener.timerHandle();
148 
149             foreach (response; sceneManager.api)
150             {
151                 if (response.code == APIType.ThreadClose)
152                 {
153                     if (response.value == 0)
154                     {
155                         exit();
156                         return;
157                     } else
158                     {
159                         if (response.value >= threads.length)
160                         {
161                             sceneManager.apiError[response.code] = APIError.ThreadIsNotExists;
162                             continue;
163                         } else
164                         {
165                             threadClose(response.value);
166                         }
167                     }
168                 }else
169                 if (response.code == APIType.GameClose)
170                 {
171                     result = response.value;
172                     exit();
173                     return;
174                 } else
175                 if (response.code == APIType.ThreadPause)
176                 {
177                     if(response.value >= threads.length)
178                     {
179                         sceneManager.apiError[response.code] = APIError.ThreadIsNotExists;
180                         continue;
181                     } else
182                     {
183                         threads[response.value].pause();
184                     }
185                 } else
186                 if (response.code == APIType.ThreadResume)
187                 {
188                     if (response.value >= threads.length)
189                     {
190                         sceneManager.apiError[response.code] = APIError.ThreadIsNotExists;
191                         continue;
192                     } else
193                     {
194                         threads[response.value].resume();
195                     }
196                 } else
197                 if (response.code == APIType.ThreadCreate)
198                 {
199                     auto thread = new InstanceThread(threads.length,renderer);
200                     threads ~= thread;
201 
202                     thread.start();
203                 }
204             }
205 
206             sceneManager.api = []; // GC, please, clear this
207 
208             sceneManager.callStep(0,renderer);
209 
210             renderer.clear();
211             sceneManager.callDraw(renderer);
212             renderer.drawning();
213 
214             fps.control();
215         }
216     }
217 }
218 
219 template GameRun(GameConfig config, T...)
220 {
221     import tida.runtime;
222     import tida.scenemanager;
223 
224     version(unittest) {} else
225     int main(string[] args)
226     {
227         TidaRuntime.initialize(args, AllLibrary);
228         Game game = new Game(config);
229 
230         static foreach (e; T)
231         {
232             sceneManager.add(new e());
233         }
234 
235         debug(single)
236         {
237             static foreach (e; T)
238             {
239             mixin("
240             debug(single_" ~ e.stringof ~ ")
241             {
242                 sceneManager.gotoin!(" ~ e.stringof ~ ");
243             }
244             ");
245             }
246         }else
247             sceneManager.inbegin();
248 
249         game.run();
250         return game.result;
251     }
252 }