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 }