1 /++ 2 Scene and instance control module. 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.scenemanager; 13 14 import tida.instance; 15 import tida.localevent; 16 import tida.scene; 17 import tida.render; 18 import tida.instance; 19 import tida.component; 20 import tida.event; 21 import tida.fps; 22 import core.thread; 23 24 /++ 25 Mistakes of using communication between the manager and the game cycle. 26 +/ 27 enum APIError : uint 28 { 29 succes, /// Errors are not detected. 30 ThreadIsNotExists, /// The stream with which it was necessary to interact - does not exist. 31 UnkownResponse 32 } 33 34 /++ 35 Commands that should execute the game cycle. 36 +/ 37 enum APIType : uint 38 { 39 None, /// None 40 ThreadCreate, /// Create the specified number of threads. 41 ThreadPause, 42 ThreadResume, 43 ThreadClose, 44 GameClose, 45 ThreadClosed, 46 ThreadRebindThreadID 47 } 48 49 /++ 50 Container to send a message to the game cycle. 51 +/ 52 struct APIResponse 53 { 54 uint code; /// Command thah should execute the game cycle. 55 uint value; /// Value response 56 } 57 58 __gshared SceneManager _sceneManager; 59 60 /// Scene manager instance. 61 SceneManager sceneManager() @trusted 62 { 63 return _sceneManager; 64 } 65 66 /// Allocates memory under the scene manager. 67 void initSceneManager() @trusted 68 { 69 _sceneManager = new SceneManager(); 70 } 71 72 version (unittest) 73 { 74 auto defaultCamera() @safe 75 { 76 return null; 77 } 78 } else 79 auto defaultCamera() @safe 80 { 81 import tida.game : renderer, window; 82 import tida.shape; 83 import tida.vector; 84 import tida.runtime; 85 86 auto camera = new Camera(); 87 camera.shape = Shape!float.Rectangle( 88 vecZero!float, 89 window.fullscreen ? 90 vec!float(runtime.monitorSize) : 91 vec!float(window.width, window.height) 92 ); 93 camera.port = camera.shape; 94 95 return camera; 96 } 97 98 /++ 99 Class describing scene manager. 100 101 Performs the functions of switching the context of the scenes, memorize 102 the list for subsequent circulation, the ability to execute elementary 103 events, give an instance access to the current scene or scene, which is 104 involved in the event. 105 106 To transfer the context, use the `gotoin`. Learn the current scene - `current`. 107 previous - `previous` Contact precisely to the global object - `scenemanager`. 108 +/ 109 final class SceneManager 110 { 111 import std.algorithm : canFind; 112 import core.sync.mutex; 113 114 private: 115 alias RecoveryDelegate = void delegate(ref Scene) @safe; 116 alias LazyInfo = Scene delegate() @safe; 117 alias LazyGroupFunction = Scene[string] delegate() @safe; 118 119 struct LazyGroupInfo 120 { 121 string[] names; 122 LazyGroupFunction spawnFunction; 123 } 124 125 Scene[string] _scenes; 126 Scene _current; 127 Scene _previous; 128 Scene _ofbegin; 129 Scene _ofend; 130 Scene _initable; 131 Scene _restarted; 132 133 RecoveryDelegate[string] recovDelegates; 134 LazyInfo[string] recovLazySpawns; 135 136 LazyInfo[string] lazySpawns; 137 LazyGroupInfo[] lazyGroupSpawns; 138 139 bool _thereGoto; 140 141 bool updateThreads = false; 142 143 shared(Mutex) instanceMutex; 144 145 public @safe: 146 size_t countStartThreads = 0; 147 size_t maxThreads = 3; 148 size_t functionPerThread = 80; 149 150 this() @safe 151 { 152 instanceMutex = new shared Mutex; 153 } 154 155 /++ 156 A state indicating whether an instance transition is in progress. 157 Needed to synchronize the stream. 158 +/ 159 @property bool isThereGoto() nothrow pure 160 { 161 return _thereGoto; 162 } 163 164 /// List scenes 165 @property Scene[string] scenes() nothrow pure 166 { 167 return _scenes; 168 } 169 170 /++ 171 The first added scene. 172 173 It can be overridden so that when the game 174 is restarted, the manager will jump to the 175 scene from this line: 176 --- 177 sceneManager.ofbegin = myScene; 178 sceneManager.gameRestart(); 179 --- 180 +/ 181 @property Scene ofbegin() nothrow pure 182 { 183 return _ofbegin; 184 } 185 186 /++ 187 The last added scene. 188 +/ 189 @property Scene ofend() nothrow pure 190 { 191 return _ofend; 192 } 193 194 /++ 195 The previous scene that was active. 196 +/ 197 @property Scene previous() nothrow pure 198 { 199 return _previous; 200 } 201 202 /++ 203 A scene that restarts at the moment. 204 +/ 205 @property Scene restarted() nothrow pure 206 { 207 return _restarted; 208 } 209 210 /++ 211 Restarting the game. 212 213 Please note that this function causes complete deletion and creation of all 214 scenes in the framework. Therefore, it is recommended to load all resources 215 through the resource manager so that when you restart all scenes of the 216 constructor of such scenes, the resources are not loaded again. 217 218 Also note that there are resources in the game that may not be reloaded by 219 this function. For this there is an event `GameRestart`, in it put the 220 implementation of the function that fixes such problems. 221 +/ 222 void gameRestart() @trusted 223 { 224 foreach (ref scene; scenes) 225 { 226 _restarted = scene; 227 228 foreach (fun; scene.events.GameRestartFunctions) fun(); 229 foreach (instance; scene.list()) 230 { 231 foreach (fun; instance.events.IGameRestartFunctions) fun(); 232 scene.instanceDestroy!InMemory(instance); 233 } 234 235 recovDelegates[scene.name](scene); 236 237 _restarted = null; 238 } 239 240 gotoin(ofbegin); 241 } 242 243 /++ 244 Link to the current scene. 245 246 Please note that such a pointer is correct only in those events that 247 differ from `init`,` restart`, `leave`, they can not go at all on the 248 current one that you hoped. Example: In the initialization event, you 249 want access to the scene, which is initialized, but here you can make 250 a mistake - the pointer leads to the previous scene. You can access 251 the current through `sceneManager.initable`. 252 253 See_Also: 254 tida.scene.manager.SceneManager.initable 255 +/ 256 @property Scene current() nothrow pure 257 { 258 return _current; 259 } 260 261 /++ 262 The reference to the scene, which is undergoing context change 263 processing. 264 265 The use of such a link is permissible only in context transmission 266 events, otherwise, it is possible to detect the scene leading nowhere. 267 +/ 268 @property Scene initable() nothrow pure 269 { 270 return _initable; 271 } 272 273 /++ 274 The reference to the current stage, as if it is under initialization, 275 whether it is during a restart or without them. 276 277 This link is selected depending on what is happening. If this is caused 278 during the change of context, it will lead exactly the scene that 279 receives the context. If the manager restarts the game, the link leads 280 to the scene, which is now restarting if there are no such events, then 281 the scene leads to the current working scene. 282 283 Examples: 284 --- 285 @FunEvent!Init 286 void Initialization() @safe 287 { 288 assert(sceneManager.initable is sceneManager.context); // ok 289 } 290 291 @FunEvent!Step 292 void Move() @safe 293 { 294 assert(sceneManager.current is sceneManager.context); // ok 295 } 296 297 @FunEvent!GameRestart 298 void onGameRestart() @safe 299 { 300 assert(sceneManager.restarted is sceneManager.context); // ok 301 } 302 --- 303 +/ 304 @property Scene context() nothrow pure 305 { 306 return _initable is null ? (_restarted is null ? _current : _restarted) : _initable; 307 } 308 309 /++ 310 Calls a trigger for the current scene, as well as its instances. 311 312 Triggers are required for custom signal and events. By calling, you can 313 force to pull functions with special attributes, for example: 314 --- 315 alias SpecEvent = Trigger("SpecialEvent"); 316 317 @SpecEvent 318 void onSpec() @safe { ... } 319 ... 320 sceneManager.trigger("SpecialEvent"); 321 // Will cause the exact event to be called by calling the function, 322 // only for the scene that is being held in the context. 323 --- 324 325 Params: 326 name = Trigger name. 327 +/ 328 void trigger(string name) @trusted 329 { 330 auto scene = this.context(); 331 332 foreach (fun; scene.events.OnTriggerFunctions) 333 { 334 if (fun.ev.name == name) 335 { 336 fun.fun(); 337 } 338 } 339 340 foreach (instance; scene.list()) 341 { 342 foreach (fun; instance.events.IOnTriggerFunctions) 343 { 344 if (fun.ev.name == name) 345 { 346 fun.fun(); 347 } 348 } 349 } 350 } 351 352 unittest 353 { 354 enum onSaysHi = Trigger("onSaysHi"); 355 356 initSceneManager(); 357 358 static class Test : Scene 359 { 360 bool isSayHi = false; 361 362 @onSaysHi void onSayHi() @safe 363 { 364 isSayHi = true; 365 } 366 } 367 368 Test test; 369 370 sceneManager.add (test = new Test()); 371 sceneManager.gotoin(test); 372 373 sceneManager.trigger("onSaysHi"); 374 375 assert (test.isSayHi); 376 } 377 378 /++ 379 Challenge the trigger in all scenes and copies that will be there. 380 It is the call that goes from everyone, and not the current scene 381 382 Params: 383 name = Trigger name. 384 +/ 385 void globalTrigger(string name) @trusted 386 { 387 foreach (scene; scenes) 388 { 389 foreach (fun; scene.events.OnTriggerFunctions) 390 { 391 if (fun.ev.name == name) 392 { 393 fun.fun(); 394 } 395 } 396 397 foreach (instance; scene.list()) 398 { 399 foreach (fun; instance.events.IOnTriggerFunctions) 400 { 401 if (fun.ev.name == name) 402 { 403 fun.fun(); 404 } 405 } 406 } 407 } 408 } 409 410 unittest 411 { 412 enum onSaysHi = Trigger("onSaysHi"); 413 414 initSceneManager(); 415 416 static class Test : Scene 417 { 418 bool isSayHi = false; 419 420 @onSaysHi void onSayHi() @safe 421 { 422 isSayHi = true; 423 } 424 } 425 426 static class Test2 : Scene 427 { 428 bool isSayHi = false; 429 430 @onSaysHi void onSayHi() @safe 431 { 432 isSayHi = true; 433 } 434 } 435 436 Test test; 437 Test2 test2; 438 439 sceneManager.add (test = new Test()); 440 sceneManager.add (test2 = new Test2()); 441 sceneManager.inbegin(); 442 443 sceneManager.trigger("onSaysHi"); 444 assert (!test2.isSayHi); 445 446 sceneManager.globalTrigger("onSaysHi"); 447 assert (test2.isSayHi); 448 } 449 450 /++ 451 Checks if the scene is in the scene list. 452 453 Params: 454 scene = Scene. 455 +/ 456 bool hasScene(Scene scene) @trusted 457 { 458 return _scenes.values.canFind(scene); 459 } 460 461 /++ 462 Checks for the existence of a scene by its original class. 463 464 Params: 465 Name = Class name. 466 +/ 467 bool hasScene(Name)() 468 { 469 return _scenes.values.canFind!(e => (cast(Name) e) !is null); 470 } 471 472 /++ 473 Checks if there is a scene with the specified name. 474 475 Params: 476 name = Scene name. 477 +/ 478 bool hasScene(string name) 479 { 480 return _scenes.values.canFind!(e => e.name == name); 481 } 482 483 /++ 484 Function for lazy loading scenes. In the chalon only specifies the scene. 485 At the same time, it will be listed in the scene list, however, 486 its resources will not be loaded until the control goes to it. 487 After the context change, it does not need to be re-shipped. 488 489 Params: 490 T = Lazy scene. 491 +/ 492 void lazyAdd(T)() 493 if (isScene!T) 494 { 495 auto fun = { 496 T scene = new T(); 497 if (scene.name == []) 498 scene.name = T.stringof; 499 500 add!T(scene); 501 502 size_t countThreads = maxThreads; 503 if (countStartThreads > countThreads) 504 countThreads = countStartThreads; 505 scene.initThread(countThreads); 506 507 return scene; 508 }; 509 510 lazySpawns[T.stringof] = fun; 511 recovLazySpawns[T.stringof] = fun; 512 } 513 514 unittest 515 { 516 initSceneManager(); 517 518 static class LazyScene : Scene 519 { 520 int a = 0; 521 522 this() @safe 523 { 524 a = 32; 525 } 526 } 527 528 sceneManager.lazyAdd!(LazyScene)(); 529 assert(!sceneManager.hasScene!LazyScene); 530 531 sceneManager.gotoin!LazyScene; 532 533 assert(sceneManager.hasScene!LazyScene); 534 } 535 536 void lazyGroupAdd(T...)() 537 { 538 size_t countThreads = 0; 539 540 auto fun = () @trusted { 541 Scene[string] rtScenes; 542 543 static foreach (SceneType; T) 544 { 545 static assert(isScene!SceneType, "It not a Scene!"); 546 547 mixin(" 548 SceneType __scene" ~ SceneType.stringof ~ " = new SceneType(); 549 if (__scene" ~ SceneType.stringof ~ ".name.length == 0) 550 __scene" ~ SceneType.stringof ~ ".name = \"" ~ SceneType.stringof ~ "\"; 551 552 add(__scene" ~ SceneType.stringof ~ "); 553 554 countThreads = maxThreads; 555 if (countStartThreads > countThreads) 556 countThreads = countStartThreads; 557 558 __scene" ~ SceneType.stringof ~ ".initThread(countThreads); 559 560 rtScenes[\"" ~ SceneType.stringof ~ "\"] = __scene" ~ SceneType.stringof ~ "; 561 "); 562 } 563 564 return rtScenes; 565 }; 566 567 string[] names; 568 static foreach (SceneType; T) 569 { 570 names ~= SceneType.stringof; 571 } 572 573 this.lazyGroupSpawns ~= LazyGroupInfo(names, fun); 574 } 575 576 unittest 577 { 578 initSceneManager(); 579 580 static class LazyScene1 : Scene 581 { 582 int a = 0; 583 584 this() @safe 585 { 586 a = 32; 587 } 588 } 589 590 static class LazyScene2 : Scene 591 { 592 int a = 0; 593 594 this() @safe 595 { 596 a = 48; 597 } 598 } 599 600 static class LazyScene3 : Scene 601 { 602 int a = 0; 603 604 this() @safe 605 { 606 a = 64; 607 } 608 } 609 610 static class LazyScene4 : Scene 611 { 612 int a = 0; 613 614 this() @safe 615 { 616 a = 98; 617 } 618 } 619 620 sceneManager.lazyGroupAdd!(LazyScene1, LazyScene2); 621 sceneManager.lazyGroupAdd!(LazyScene3, LazyScene4); 622 623 assert(!sceneManager.hasScene!LazyScene1); 624 assert(!sceneManager.hasScene!LazyScene3); 625 626 sceneManager.gotoin!LazyScene2; 627 628 assert(sceneManager.hasScene!LazyScene1); 629 assert(!sceneManager.hasScene!LazyScene3); 630 631 sceneManager.gotoin!LazyScene4; 632 633 assert(sceneManager.hasScene!LazyScene1); 634 assert(sceneManager.hasScene!LazyScene3); 635 } 636 637 /++ 638 Adds a scene to the list. 639 640 Params: 641 scene = Scene. 642 +/ 643 void add(T)(T scene) 644 { 645 static assert(isScene!T, "`" ~ T.stringof ~ "` is not a scene!"); 646 exploreScene!T(scene); 647 648 if (scene.name == "") 649 scene.name = T.stringof; 650 651 if (_ofbegin is null) 652 _ofbegin = scene; 653 654 recovDelegates[scene.name] = (ref Scene bscene) @safe 655 { 656 bool isSceneBegin = (ofbegin is bscene); 657 658 bscene = new T(); 659 exploreScene!T(cast(T) bscene); 660 _scenes[bscene.name] = bscene; 661 662 if (isSceneBegin) _ofbegin = bscene; 663 }; 664 665 _scenes[scene.name] = scene; 666 } 667 668 template hasMatch(alias attrib, alias AttribType) 669 { 670 enum hasMatch = is(typeof(attrib) == AttribType) || is(attrib == AttribType) || 671 is(typeof(attrib) : AttribType) || is(attrib : AttribType); 672 } 673 674 template hasAttrib(T, AttribType, string member) 675 { 676 import std.traits : isFunction, isSafe, getUDAs; 677 678 alias same = __traits(getMember, T, member); 679 680 static if (isFunction!(same) && isSafe!(same)) 681 { 682 alias attributes = __traits(getAttributes, same); 683 684 static if (attributes.length != 0) 685 { 686 static foreach (attrib; attributes) 687 { 688 static if (hasMatch!(attrib, AttribType)) 689 { 690 static assert(isSafe!(same), 691 "The function `" ~ member ~"` does not guarantee safe execution."); 692 693 enum hasAttrib = true; 694 enum found = true; 695 } 696 } 697 698 static if (!__traits(compiles, found)) 699 { 700 enum hasAttrib = false; 701 } 702 } else 703 { 704 enum hasAttrib = false; 705 } 706 } else 707 { 708 enum hasAttrib = false; 709 } 710 } 711 712 template attributeIn(T, AttribType, string member) 713 { 714 alias same = __traits(getMember, T, member); 715 alias attributes = __traits(getAttributes, same); 716 717 static foreach (attrib; attributes) 718 { 719 static if (hasMatch!(attrib, AttribType)) 720 { 721 enum attributeIn = attrib; 722 } 723 } 724 } 725 726 /++ 727 A function to receive events that were described inside 728 the object's implementation. 729 730 It is necessary if you need to manually call any functions 731 without using the scene manager. (The object doesn't have to be added somewhere for the function to work). 732 733 Params: 734 instance = Instance implementation object. 735 736 Returns: 737 Returns a structure with the event fields that it could detect. 738 +/ 739 InstanceEvents getInstanceEvents(T)(T instance) @trusted 740 if (isInstance!T) 741 { 742 import std.algorithm : canFind, remove; 743 import std.traits : getUDAs, TemplateArgsOf, Parameters; 744 745 InstanceEvents events; 746 747 events.IInitFunctions = []; 748 events.IStepFunctions = []; 749 events.IEntryFunctions = []; 750 events.IRestartFunctions = []; 751 events.ILeaveFunctions = []; 752 events.IGameStartFunctions = []; 753 events.IGameExitFunctions = []; 754 events.IGameRestartFunctions = []; 755 events.IEventHandleFunctions = []; 756 events.IDrawFunctions = []; 757 events.IOnErrorFunctions = []; 758 events.IColliderStructs = []; 759 events.IOnTriggerFunctions = []; 760 events.IOnDestroyFunctions = []; 761 events.ICollisionFunctions = []; 762 events.IOnAnyTriggerFunctions = []; 763 events.IStepThreadFunctions = [0:null]; 764 765 void delegate() @safe[]* minStepTh; 766 size_t minStepLen; 767 768 static foreach (member; __traits(allMembers, T)) 769 { 770 static if (hasAttrib!(T, tida.localevent.event, member)) 771 { 772 static if (attributeIn!(T, event, member).type == Init) 773 { 774 static if (getUDAs!(__traits(getMember, instance, member), args).length != 0) 775 { 776 events.IInitFunctions ~= InstanceEvents.FEEntry.create!( 777 getUDAs!(__traits(getMember, instance, member), args)[0].members 778 )(cast(void delegate() @safe) &__traits(getMember, instance, member)); 779 } else 780 { 781 static assert ( 782 Parameters!(__traits(getMember, instance, member)).length == 0, 783 "An initialization event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 784 ); 785 786 events.IInitFunctions ~= InstanceEvents.FEEntry.create(cast(void delegate() @safe) &__traits(getMember, instance, member)); 787 } 788 } else 789 static if (attributeIn!(T, event, member).type == Restart) 790 { 791 static if (getUDAs!(__traits(getMember, instance, member), args).length != 0) 792 { 793 events.IRestartFunctions ~= InstanceEvents.FEEntry.create!( 794 getUDAs!(__traits(getMember, instance, member), args)[0].members 795 )(cast(void delegate() @safe) &__traits(getMember, instance, member)); 796 } else 797 { 798 static assert ( 799 Parameters!(__traits(getMember, instance, member)).length == 0, 800 "An restart event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 801 ); 802 803 events.IRestartFunctions ~= InstanceEvents.FEEntry.create(cast(void delegate() @safe) &__traits(getMember, instance, member)); 804 } 805 } else 806 static if (attributeIn!(T, event, member).type == Entry) 807 { 808 static if (getUDAs!(__traits(getMember, instance, member), args).length != 0) 809 { 810 events.IEntryFunctions ~= InstanceEvents.FEEntry.create!( 811 getUDAs!(__traits(getMember, instance, member), args)[0].members 812 )(cast(void delegate() @safe) &__traits(getMember, instance, member)); 813 } else 814 { 815 static assert ( 816 Parameters!(__traits(getMember, instance, member)).length == 0, 817 "An entry event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 818 ); 819 820 events.IEntryFunctions ~= InstanceEvents.FEEntry.create(cast(void delegate() @safe) &__traits(getMember, instance, member)); 821 } 822 } else 823 static if (attributeIn!(T, event, member).type == Leave) 824 { 825 events.ILeaveFunctions ~= &__traits(getMember, instance, member); 826 } else 827 static if (attributeIn!(T, event, member).type == Step) 828 { 829 events.IStepFunctions ~= &__traits(getMember, instance, member); 830 } else 831 static if (attributeIn!(T, event, member).type == GameStart) 832 { 833 events.IGameStartFunctions ~= &__traits(getMember, instance, member); 834 } else 835 static if (attributeIn!(T, event, member).type == GameExit) 836 { 837 events.IGameExitFunctions ~= &__traits(getMember, instance, member); 838 } else 839 static if (attributeIn!(T, event, member).type == GameRestart) 840 { 841 events.IGameRestartFunctions ~= &__traits(getMember, instance, member); 842 } else 843 static if (attributeIn!(T, event, member).type == Input) 844 { 845 events.IEventHandleFunctions ~= &__traits(getMember, instance, member); 846 } else 847 static if (attributeIn!(T, event, member).type == Draw) 848 { 849 events.IDrawFunctions ~= &__traits(getMember, instance, member); 850 } else 851 static if (attributeIn!(T, event, member).type == AnyTrigger) 852 { 853 events.IOnAnyTriggerFunctions ~= &__traits(getMember, instance, member); 854 } else 855 static if (attributeIn!(T, event, member).type == AnyCollision) 856 { 857 events.ICollisionFunctions ~= &__traits(getMember, instance, member); 858 } else 859 static if (attributeIn!(T, event, member).type == Destroy) 860 { 861 events.IOnDestroyFunctions ~= &__traits(getMember, instance, member); 862 } else 863 static if (attributeIn!(T, event, member).type == GameError) 864 { 865 events.IOnErrorFunctions ~= &__traits(getMember, instance, member); 866 } 867 } else 868 static if (hasAttrib!(T, Trigger, member)) 869 { 870 events.IOnTriggerFunctions ~= InstanceEvents.SRTrigger (attributeIn!(T, Trigger, member), 871 &__traits(getMember, instance, member)); 872 } else 873 static if (hasAttrib!(T, StepThread, member)) 874 { 875 events.IStepThreadFunctions 876 [attributeIn!(T, StepThread, member).id] ~= &__traits(getMember, instance, member); 877 878 if (countStartThreads < attributeIn!(T, StepThread, member).id) 879 { 880 countStartThreads = attributeIn!(T, StepThread, member).id; 881 } 882 } else 883 static if (hasAttrib!(T, Collision, member)) 884 { 885 events.IColliderStructs ~= InstanceEvents.SRCollider(attributeIn!(T, Collision, member), 886 &__traits(getMember, instance, member)); 887 }else 888 static if (hasAttrib!(T, stepThreadSafe, member)) 889 { 890 import std.algorithm : maxElement; 891 892 if (events.IStepThreadFunctions.length != 1) 893 { 894 minStepLen = events.IStepThreadFunctions.values.maxElement!(a => a.length).length; 895 foreach (key, value; events.IStepThreadFunctions) 896 { 897 if (value.length < minStepLen) 898 { 899 minStepTh = &events.IStepThreadFunctions[key]; 900 minStepLen = value.length; 901 } 902 } 903 } else 904 { 905 foreach (i; 1 .. maxThreads + 1) 906 events.IStepThreadFunctions[i] = []; 907 908 minStepTh = &events.IStepThreadFunctions[maxThreads]; 909 minStepLen = events.IStepThreadFunctions[maxThreads].length; 910 } 911 912 if (minStepLen > functionPerThread) 913 { 914 events.IStepFunctions ~= &__traits(getMember, instance, member); 915 } 916 else 917 { 918 *minStepTh ~= &__traits(getMember, instance, member); 919 } 920 } 921 } 922 923 return events; 924 } 925 926 ComponentEvents getComponentEvents(T)(Instance instance, T component) @safe 927 { 928 ComponentEvents events; 929 930 events.CStepFunctions = []; 931 events.CLeaveFunctions = []; 932 events.CEventHandleFunctions = []; 933 events.CDrawFunctions = []; 934 events.COnErrorFunctions = []; 935 events.COnTriggerFunctions = []; 936 events.COnAnyTriggerFunctions = []; 937 938 static foreach (member; __traits(allMembers, T)) 939 { 940 static if (hasAttrib!(T, tida.localevent.event, member)) 941 { 942 static if (attributeIn!(T, event, member).type == Init) 943 { 944 events.CInitFunctions ~= ComponentEvents.FEInit( 945 instance, 946 &__traits(getMember, component, member) 947 ); 948 } else 949 static if (attributeIn!(T, event, member).type == Leave) 950 { 951 events.CLeaveFunctions ~= &__traits(getMember, component, member); 952 } else 953 static if (attributeIn!(T, event, member).type == Step) 954 { 955 events.CStepFunctions ~= &__traits(getMember, component, member); 956 } else 957 static if (attributeIn!(T, event, member).type == Input) 958 { 959 events.CEventHandleFunctions ~= &__traits(getMember, component, member); 960 } else 961 static if (attributeIn!(T, event, member).type == Draw) 962 { 963 events.CDrawFunctions ~= &__traits(getMember, component, member); 964 } else 965 static if (attributeIn!(T, event, member).type == AnyTrigger) 966 { 967 events.COnAnyTriggerFunctions ~= &__traits(getMember, component, member); 968 } else 969 static if (attributeIn!(T, event, member).type == AnyCollision) 970 { 971 events.COnAnyCollisionFunctions ~= &__traits(getMember, component, member); 972 } else 973 static if (attributeIn!(T, event, member).type == GameError) 974 { 975 events.COnErrorFunctions ~= &__traits(getMember, component, member); 976 } 977 } else 978 static if (hasAttrib!(T, Trigger, member)) 979 { 980 events.COnTriggerFunctions ~= ComponentEvents.SRTrigger (attributeIn!(T, Trigger, member), 981 &__traits(getMember, component, member)); 982 } 983 } 984 985 return events; 986 } 987 988 SceneEvents getSceneEvents(T)(T scene) @trusted 989 { 990 import std.traits : hasUDA, getUDAs, TemplateArgsOf, Parameters; 991 992 SceneEvents events; 993 994 events.InitFunctions = []; 995 events.StepFunctions = []; 996 events.EntryFunctions = []; 997 events.RestartFunctions = []; 998 events.LeaveFunctions = []; 999 events.GameStartFunctions = []; 1000 events.GameExitFunctions = []; 1001 events.GameRestartFunctions = []; 1002 events.EventHandleFunctions = []; 1003 events.DrawFunctions = []; 1004 events.OnErrorFunctions = []; 1005 events.OnTriggerFunctions = []; 1006 events.OnDestroyFunctions = []; 1007 events.StepThreadFunctions = [0:null]; 1008 1009 void delegate() @safe[]* minStepTh; 1010 size_t minStepLen; 1011 1012 static foreach (member; __traits(allMembers, T)) 1013 { 1014 static if (hasAttrib!(T, tida.localevent.event, member)) 1015 { 1016 static if (attributeIn!(T, event, member).type == Init) 1017 { 1018 static if (getUDAs!(__traits(getMember, scene, member), args).length != 0) 1019 { 1020 events.InitFunctions ~= SceneEvents.FEEntry.create!( 1021 getUDAs!(__traits(getMember, scene, member), args)[0].members 1022 )(cast(void delegate() @safe) &__traits(getMember, scene, member)); 1023 } else 1024 { 1025 static assert ( 1026 Parameters!(__traits(getMember, scene, member)).length == 0, 1027 "An initialization event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 1028 ); 1029 1030 events.InitFunctions ~= SceneEvents.FEEntry.create(cast(void delegate() @safe) &__traits(getMember, scene, member)); 1031 } 1032 } else 1033 static if (attributeIn!(T, event, member).type == Restart) 1034 { 1035 static if (getUDAs!(__traits(getMember, scene, member), args).length != 0) 1036 { 1037 events.RestartFunctions ~= SceneEvents.FEEntry.create!( 1038 getUDAs!(__traits(getMember, scene, member), args)[0].members 1039 )(cast(void delegate() @safe) &__traits(getMember, scene, member)); 1040 } else 1041 { 1042 static assert ( 1043 Parameters!(__traits(getMember, scene, member)).length == 0, 1044 "An restart event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 1045 ); 1046 1047 events.RestartFunctions ~= SceneEvents.FEEntry.create(&__traits(getMember, scene, member)); 1048 } 1049 } else 1050 static if (attributeIn!(T, event, member).type == Entry) 1051 { 1052 static if (getUDAs!(__traits(getMember, scene, member), args).length != 0) 1053 { 1054 events.EntryFunctions ~= SceneEvents.FEEntry.create!( 1055 getUDAs!(__traits(getMember, scene, member), args)[0].members 1056 )(cast(void delegate() @safe) &__traits(getMember, scene, member)); 1057 } else 1058 { 1059 static assert ( 1060 Parameters!(__traits(getMember, scene, member)).length == 0, 1061 "An entry event cannot have any arguments. To do this, add an attribute `@args!(params...)`" 1062 ); 1063 1064 events.EntryFunctions ~= SceneEvents.FEEntry.create(&__traits(getMember, scene, member)); 1065 } 1066 } else 1067 static if (attributeIn!(T, event, member).type == Leave) 1068 { 1069 events.LeaveFunctions ~= &__traits(getMember, scene, member); 1070 } else 1071 static if (attributeIn!(T, event, member).type == Step) 1072 { 1073 events.StepFunctions ~= &__traits(getMember, scene, member); 1074 } else 1075 static if (attributeIn!(T, event, member).type == GameStart) 1076 { 1077 events.GameStartFunctions ~= &__traits(getMember, scene, member); 1078 } else 1079 static if (attributeIn!(T, event, member).type == GameExit) 1080 { 1081 events.GameExitFunctions ~= &__traits(getMember, scene, member); 1082 } else 1083 static if (attributeIn!(T, event, member).type == GameRestart) 1084 { 1085 events.GameRestartFunctions ~= &__traits(getMember, scene, member); 1086 } else 1087 static if (attributeIn!(T, event, member).type == Input) 1088 { 1089 events.EventHandleFunctions ~= &__traits(getMember, scene, member); 1090 } else 1091 static if (attributeIn!(T, event, member).type == Draw) 1092 { 1093 events.DrawFunctions ~= &__traits(getMember, scene, member); 1094 } else 1095 static if (attributeIn!(T, event, member).type == AnyTrigger) 1096 { 1097 events.OnAnyTriggerFunctions ~= &__traits(getMember, scene, member); 1098 } else 1099 static if (attributeIn!(T, event, member).type == AnyCollision) 1100 { 1101 events.OnAnyCollisionFunctions ~= &__traits(getMember, scene, member); 1102 } else 1103 static if (attributeIn!(T, event, member).type == Destroy) 1104 { 1105 events.OnDestroyFunctions ~= &__traits(getMember, scene, member); 1106 } else 1107 static if (attributeIn!(T, event, member).type == GameError) 1108 { 1109 events.OnErrorFunctions ~= &__traits(getMember, scene, member); 1110 } 1111 } else 1112 static if (hasAttrib!(T, Trigger, member)) 1113 { 1114 events.OnTriggerFunctions ~= SceneEvents.SRTrigger (attributeIn!(T, Trigger, member), 1115 &__traits(getMember, scene, member)); 1116 } else 1117 static if (hasAttrib!(T, StepThread, member)) 1118 { 1119 events.StepThreadFunctions 1120 [attributeIn!(T, StepThread, member).id] ~= &__traits(getMember, scene, member); 1121 1122 if (countStartThreads < attributeIn!(T, StepThread, member).id) 1123 { 1124 countStartThreads = attributeIn!(T, StepThread, member).id; 1125 } 1126 } else 1127 static if (hasAttrib!(T, stepThreadSafe, member)) 1128 { 1129 import std.algorithm : maxElement; 1130 1131 if (events.StepThreadFunctions.length != 1) 1132 { 1133 minStepLen = events.StepThreadFunctions.values.maxElement!(a => a.length).length; 1134 foreach (key, value; events.StepThreadFunctions) 1135 { 1136 if (value.length < minStepLen) 1137 { 1138 minStepTh = &events.StepThreadFunctions[key]; 1139 minStepLen = value.length; 1140 } 1141 } 1142 } else 1143 { 1144 foreach (i; 1 .. maxThreads + 1) 1145 events.StepThreadFunctions[i] = []; 1146 1147 minStepTh = &events.StepThreadFunctions[maxThreads]; 1148 minStepLen = events.StepThreadFunctions[maxThreads].length; 1149 } 1150 1151 if (minStepLen > functionPerThread) 1152 { 1153 events.StepFunctions ~= &__traits(getMember, scene, member); 1154 } 1155 else 1156 { 1157 *minStepTh ~= &__traits(getMember, scene, member); 1158 } 1159 } 1160 } 1161 1162 return events; 1163 } 1164 1165 unittest 1166 { 1167 initSceneManager(); 1168 1169 static class A : Instance 1170 { 1171 @event(AnyTrigger) 1172 void onInit(string member) @safe { } 1173 1174 @event(Draw) 1175 void onDraw(IRenderer render) @safe { } 1176 } 1177 1178 A a = new A(); 1179 InstanceEvents evs = sceneManager.getInstanceEvents(a); 1180 1181 assert (evs.IOnAnyTriggerFunctions[0] == &a.onInit); 1182 assert (evs.IDrawFunctions[0] == &a.onDraw); 1183 } 1184 1185 /++ 1186 Raise the event of destruction of the instance. (@FunEvent!Destroy) 1187 1188 Params: 1189 instance = Instance. 1190 +/ 1191 void destroyEventCall(T)(T instance) @trusted 1192 if (isInstance!T) 1193 { 1194 foreach(func; instance.events.IOnDestroyFunctions) 1195 func(instance); 1196 } 1197 1198 /++ 1199 Reise the event of destruction in current scene. (@FunEvent!Destroy) 1200 1201 Params: 1202 scene = Current scene. 1203 instance = Instance. 1204 +/ 1205 void destroyEventSceneCall(T, R)(T scene, R instance) @trusted 1206 { 1207 static assert(isScene!T, "`" ~ T.stringof ~ "` is not a scene!"); 1208 static assert(isInstance!R, "`" ~ R.stringof ~ "` is not a instance!"); 1209 1210 foreach(func; scene.events.OnDestroyFunctions) func(instance); 1211 } 1212 1213 package(tida) void componentExplore(T)(Instance instance, T component) @trusted 1214 if (isComponent!T) 1215 { 1216 component.events = getComponentEvents!T(instance, component); 1217 1218 foreach (fun; component.events.CInitFunctions) 1219 fun.fun (fun.instance); 1220 } 1221 1222 private void exploreScene(T)(T scene) @trusted 1223 { 1224 scene.events = getSceneEvents!T(scene); 1225 } 1226 1227 package(tida) void instanceExplore(T)(Scene scene, T instance) @trusted 1228 if (isInstance!T) 1229 { 1230 if (instance.events == InstanceEvents.init) 1231 instance.events = getInstanceEvents!T(instance); 1232 } 1233 1234 package(tida) void removeHandle(Scene scene, Instance instance) @trusted 1235 { 1236 // TODO: Implement this 1237 } 1238 1239 /++ 1240 Creates and adds a scene to the list. 1241 1242 Params: 1243 T = Scene name. 1244 1245 Example: 1246 --- 1247 sceneManager.add!MyScene; 1248 --- 1249 +/ 1250 void add(T)() @trusted 1251 { 1252 auto scene = new T(); 1253 if (scene.name == "") 1254 scene.name = T.stringof; 1255 1256 add!T(scene); 1257 } 1258 1259 void remove(T)(T scene) @trusted 1260 { 1261 scenes.remove(scene.name); 1262 destroy(scene); 1263 } 1264 1265 void remove(T)() @trusted 1266 { 1267 static assert(isScene!T, "`" ~ T.stringof ~ "` is not a scene!"); 1268 1269 foreach(scene; scenes) 1270 { 1271 if((cast(T) scene) !is null) 1272 { 1273 remove(scene); 1274 return; 1275 } 1276 } 1277 } 1278 1279 void remove(string name) @trusted 1280 { 1281 foreach(scene; scenes) 1282 { 1283 if(scene.name == name) 1284 { 1285 remove(scene); 1286 return; 1287 } 1288 } 1289 } 1290 1291 public 1292 { 1293 /++ 1294 Array of requests. At each stroke of the cycle, it is checked, 1295 processed and cleaned. If an error occurs during the request, 1296 they are added to `apiError`. 1297 +/ 1298 APIResponse[] api; 1299 1300 /++ 1301 An array of pre-known commands to execute without chasing data for each thread. 1302 +/ 1303 APIResponse[][size_t] threadAPI; 1304 1305 /++ 1306 An array of request errors associated with the request type. 1307 +/ 1308 uint[uint] apiError; 1309 } 1310 1311 /++ 1312 Exits the game with a successful error code. 1313 +/ 1314 void close(int code = 0) @safe 1315 { 1316 api ~= APIResponse(APIType.GameClose, code); 1317 } 1318 1319 /++ 1320 Creates the specified count of anonymous threads. 1321 1322 Params: 1323 count = Count anonymous threads. 1324 +/ 1325 void initThread(uint count = 1) @safe 1326 { 1327 api ~= APIResponse(APIType.ThreadCreate, count); 1328 } 1329 1330 /++ 1331 Pauses said thread. 1332 1333 Params: 1334 value = Thread identificator. 1335 +/ 1336 void pauseThread(uint value) @safe 1337 { 1338 api ~= APIResponse(APIType.ThreadPause, value); 1339 } 1340 1341 /++ 1342 Resumes said thread. 1343 1344 Params: 1345 value = Thread identificator. 1346 +/ 1347 void resumeThread(uint value) @safe 1348 { 1349 api ~= APIResponse(APIType.ThreadResume, value); 1350 } 1351 1352 void stopThread(uint value) @safe 1353 { 1354 api ~= APIResponse(APIType.ThreadClose, value); 1355 } 1356 1357 /++ 1358 Goes to the first scene added. 1359 +/ 1360 void inbegin() @safe 1361 { 1362 gotoin(_ofbegin); 1363 } 1364 1365 /++ 1366 Goes to the scene by its string name. 1367 1368 Params: 1369 name = Scene name. 1370 +/ 1371 void gotoin(T...)(string name, T args) 1372 { 1373 import std.algorithm : remove; 1374 1375 foreach (inscene; scenes) 1376 { 1377 if(inscene.name == name) 1378 { 1379 gotoin(inscene, args); 1380 break; 1381 } 1382 } 1383 1384 foreach (key, value; lazySpawns) 1385 { 1386 if (key == name) 1387 { 1388 auto scene = value(); 1389 lazySpawns.remove(key); 1390 gotoin(scene, args); 1391 return; 1392 } 1393 } 1394 1395 foreach (i; 0 .. lazyGroupSpawns.length) 1396 { 1397 auto e = lazyGroupSpawns[i]; 1398 1399 if (e.names.canFind(name)) 1400 { 1401 lazyGroupSpawns = remove(lazyGroupSpawns, i); 1402 auto lazyScenes = e.spawnFunction(); 1403 gotoin(lazyScenes[name], args); 1404 return; 1405 } 1406 } 1407 } 1408 1409 /++ 1410 Goes to the scene by its class. 1411 1412 Params: 1413 Name = Scene. 1414 +/ 1415 void gotoin(Name, T...)(T args) 1416 { 1417 import std.algorithm : remove; 1418 1419 foreach (s; scenes) 1420 { 1421 if ((cast(Name) s) !is null) 1422 { 1423 gotoin(s, args); 1424 return; 1425 } 1426 } 1427 1428 foreach (key, value; lazySpawns) 1429 { 1430 if (key == Name.stringof) 1431 { 1432 auto scene = value(); 1433 lazySpawns.remove(key); 1434 gotoin(scene, args); 1435 return; 1436 } 1437 } 1438 1439 foreach (i; 0 .. lazyGroupSpawns.length) 1440 { 1441 auto e = lazyGroupSpawns[i]; 1442 1443 if (e.names.canFind(Name.stringof)) 1444 { 1445 lazyGroupSpawns = remove(lazyGroupSpawns, i); 1446 auto lazyScenes = e.spawnFunction(); 1447 gotoin(lazyScenes[Name.stringof], args); 1448 return; 1449 } 1450 } 1451 1452 throw new Exception("Not find this scene!"); 1453 } 1454 1455 /++ 1456 Moves to the scene at the pointer. 1457 1458 It is such a function that generates initialization events, entry, 1459 transferring the context to the scene and causing the corresponding 1460 events to lose the context. 1461 1462 Params: 1463 scene = Scene heir. 1464 +/ 1465 void gotoin(T...)(Scene scene, T args) @trusted 1466 { 1467 import tida.game : renderer, window; 1468 import tida.shape; 1469 import tida.vector; 1470 1471 _previous = current; 1472 _thereGoto = true; 1473 1474 if (current !is null) 1475 { 1476 foreach (fun; current.events.LeaveFunctions) 1477 { 1478 fun(); 1479 } 1480 1481 foreach (instance; current.list()) 1482 { 1483 foreach (fun; instance.events.ILeaveFunctions) 1484 { 1485 fun(); 1486 } 1487 } 1488 } 1489 1490 if (current !is null) 1491 { 1492 Instance[] persistents; 1493 1494 foreach (instance; _previous.list()) 1495 { 1496 if (instance.persistent) 1497 persistents ~= instance; 1498 } 1499 1500 foreach (e; persistents) 1501 { 1502 auto threadID = e.threadid; 1503 current.instanceDestroy!InScene(e, false); 1504 1505 if (scene.isThreadExists(threadID)) 1506 scene.add(e, threadID); 1507 else 1508 scene.add(e); 1509 } 1510 } 1511 1512 _initable = scene; 1513 1514 if (!scene.isInit) 1515 { 1516 foreach (fun; scene.events.InitFunctions) 1517 { 1518 fun(args); 1519 } 1520 1521 foreach (instance; scene.list()) 1522 { 1523 foreach (fun; instance.events.IInitFunctions) 1524 { 1525 fun(args); 1526 } 1527 } 1528 1529 scene.isInit = true; 1530 }else 1531 { 1532 foreach (fun; scene.events.RestartFunctions) 1533 { 1534 fun(args); 1535 } 1536 1537 foreach (instance; scene.list()) 1538 { 1539 foreach (fun; instance.events.IRestartFunctions) 1540 { 1541 fun(args); 1542 } 1543 } 1544 } 1545 1546 foreach(fun; scene.events.EntryFunctions) 1547 { 1548 fun(args); 1549 } 1550 1551 foreach (instance; scene.list()) 1552 { 1553 foreach (fun; instance.events.IEntryFunctions) 1554 { 1555 fun(args); 1556 } 1557 } 1558 1559 _initable = null; 1560 1561 _current = scene; 1562 1563 version (unittest) 1564 { 1565 // avoid 1566 } else 1567 { 1568 if (scene.camera !is null) 1569 { 1570 renderer.camera = scene.camera; 1571 } 1572 else 1573 { 1574 renderer.camera = defaultCamera(); 1575 } 1576 } 1577 1578 _thereGoto = false; 1579 } 1580 1581 /++ 1582 Calling the game launch event. 1583 1584 Should be called before all events, before the beginning of the 1585 cycle of life. 1586 +/ 1587 void callGameStart() @trusted 1588 { 1589 foreach (scene; scenes) 1590 { 1591 foreach (fun; scene.events.GameStartFunctions) 1592 { 1593 fun(); 1594 } 1595 1596 foreach (instance; scene.list()) 1597 { 1598 if (instance.active && !instance.onlyDraw) 1599 { 1600 foreach (fun; instance.events.IGameStartFunctions) 1601 { 1602 fun(); 1603 } 1604 } 1605 } 1606 } 1607 } 1608 1609 /++ 1610 Game completion call events (successful). 1611 The unsuccessful event should raise the `onError` event. 1612 +/ 1613 void callGameExit() @trusted 1614 { 1615 foreach (scene; scenes) 1616 { 1617 foreach (fun; scene.events.GameExitFunctions) 1618 { 1619 fun(); 1620 } 1621 1622 foreach (instance; scene.list()) 1623 { 1624 if (instance.active && !instance.onlyDraw) 1625 { 1626 foreach (fun; instance.events.IGameExitFunctions) 1627 { 1628 fun(); 1629 } 1630 } 1631 } 1632 } 1633 } 1634 1635 unittest 1636 { 1637 initSceneManager(); 1638 1639 static class Test : Scene 1640 { 1641 int trigger = int.init; 1642 1643 this() @safe 1644 { 1645 name = "Test"; 1646 } 1647 } 1648 1649 sceneManager.add (new Test()); 1650 1651 sceneManager.gotoin("Test"); 1652 1653 assert ((cast (Test) sceneManager.scenes["Test"]).trigger == 0); 1654 (cast (Test) sceneManager.scenes["Test"]).trigger = 32; 1655 assert ((cast (Test) sceneManager.scenes["Test"]).trigger == 32); 1656 1657 sceneManager.gameRestart(); 1658 1659 assert ((cast (Test) sceneManager.scenes["Test"]).trigger == 0); 1660 } 1661 1662 /++ 1663 Triggering an emergency event. 1664 1665 Does not terminate the game, should be called on exceptions. After that, 1666 the programmer himself decides what to do next (if he implements his own 1667 life cycle). Called usually on `scope (failure)`, however, it will not 1668 throw a specific exception. 1669 +/ 1670 void callOnError() @trusted 1671 { 1672 if (current !is null) 1673 { 1674 foreach (fun; current.events.OnErrorFunctions) 1675 { 1676 fun(); 1677 } 1678 1679 foreach (instance; current.list()) 1680 { 1681 if (instance.active && !instance.onlyDraw) 1682 { 1683 foreach (fun; instance.events.IOnErrorFunctions) 1684 { 1685 fun(); 1686 } 1687 } 1688 } 1689 } 1690 } 1691 1692 /++ 1693 Calling a game step. Should always be called during a loop step in an 1694 exception when the thread is suspended. 1695 1696 Params: 1697 thread = Thread identificator. 1698 rend = Renderer instance. 1699 +/ 1700 void callStep(size_t thread, IRenderer rend) @trusted 1701 { 1702 if (current !is null) 1703 { 1704 if (thread == 0) 1705 { 1706 current.worldCollision(); 1707 1708 if (current.camera !is null) 1709 current.camera.followObject(); 1710 1711 foreach (fun; current.events.StepFunctions) 1712 { 1713 fun(); 1714 } 1715 } 1716 1717 if (thread in current.events.StepThreadFunctions) 1718 { 1719 foreach (fun; current.events.StepThreadFunctions[thread]) 1720 fun(); 1721 } 1722 1723 foreach (instance; current.getThreadList(thread)) 1724 { 1725 if (instance.isDestroy) 1726 { 1727 current.instanceDestroy!InMemory(instance); 1728 current.sort(); 1729 continue; 1730 } 1731 1732 if (!instance.active || instance.onlyDraw) continue; 1733 1734 foreach (fun; instance.events.IStepFunctions) 1735 { 1736 fun(); 1737 } 1738 1739 foreach (component; instance.getComponents()) 1740 { 1741 foreach (fun; component.events.CStepFunctions) 1742 { 1743 fun(); 1744 } 1745 } 1746 } 1747 1748 foreach(instance; current.list()) 1749 { 1750 if (!instance.active || instance.onlyDraw) 1751 continue; 1752 1753 if (thread in instance.events.IStepThreadFunctions) 1754 { 1755 foreach (fun; instance.events.IStepThreadFunctions[thread]) 1756 fun(); 1757 } 1758 1759 foreach (component; instance.getComponents()) 1760 { 1761 if (thread in component.events.CStepThreadFunctions) 1762 { 1763 foreach(fun; component.events.CStepThreadFunctions[thread]) 1764 { 1765 fun(); 1766 } 1767 } 1768 } 1769 } 1770 } 1771 } 1772 1773 /++ 1774 System event event for scenes and instances of the current context. 1775 1776 Params: 1777 event = System event handler instance. 1778 +/ 1779 void callEvent(EventHandler event) @trusted 1780 { 1781 if (current !is null) 1782 { 1783 foreach (fun; current.events.EventHandleFunctions) 1784 { 1785 fun(event); 1786 } 1787 1788 foreach (instance; current.list()) 1789 { 1790 if (instance.active && !instance.onlyDraw) 1791 { 1792 foreach(fun; instance.events.IEventHandleFunctions) 1793 { 1794 fun(event); 1795 } 1796 1797 foreach (component; instance.getComponents()) 1798 { 1799 foreach(fun; component.events.CEventHandleFunctions) 1800 { 1801 fun(event); 1802 } 1803 } 1804 } 1805 } 1806 } 1807 } 1808 1809 /++ 1810 Calling an event to render scenes and instances of the current context. 1811 1812 Params: 1813 render = Render instance. 1814 +/ 1815 void callDraw(IRenderer render) @trusted 1816 { 1817 import tida.vector; 1818 1819 if (current !is null) 1820 { 1821 foreach (fun; current.events.DrawFunctions) 1822 { 1823 fun(render); 1824 } 1825 1826 foreach (instance; current.getAssortedInstances()) 1827 { 1828 if (instance.active && instance.visible) 1829 { 1830 render.draw(instance.spriteDraw(), instance.position); 1831 1832 foreach (fun; instance.events.IDrawFunctions) 1833 { 1834 fun(render); 1835 } 1836 1837 foreach (component; instance.getComponents()) 1838 { 1839 foreach (fun; component.events.CDrawFunctions) 1840 { 1841 fun(render); 1842 } 1843 } 1844 } 1845 } 1846 } 1847 } 1848 1849 /// Free memory. 1850 void free() @safe 1851 { 1852 _scenes = null; 1853 } 1854 1855 ~this() @safe 1856 { 1857 free(); 1858 } 1859 } 1860 1861 unittest 1862 { 1863 initSceneManager(); 1864 1865 static class A : Scene 1866 { 1867 this() @safe 1868 { 1869 name = "Test"; 1870 } 1871 } 1872 1873 sceneManager.add(new A()); 1874 assert(("Test" in sceneManager.scenes) !is null); 1875 } 1876 1877 unittest 1878 { 1879 initSceneManager(); 1880 1881 static class A : Scene 1882 { 1883 @event(Init) 1884 void onInit() @safe { } 1885 } 1886 1887 A obj = new A(); 1888 sceneManager.add(obj); 1889 1890 assert((obj.events.InitFunctions[0].func.ptr) == ((&obj.onInit).ptr)); 1891 } 1892 1893 unittest 1894 { 1895 initSceneManager(); 1896 1897 static class A : Scene 1898 { 1899 this() @safe 1900 { 1901 name = "Test"; 1902 } 1903 } 1904 1905 sceneManager.add(new A()); 1906 assert(sceneManager.hasScene("Test")); 1907 } 1908 1909 unittest 1910 { 1911 initSceneManager(); 1912 1913 struct Data 1914 { 1915 int first; 1916 string second; 1917 } 1918 1919 static class A : Scene 1920 { 1921 bool state = false; 1922 1923 this() @safe 1924 { 1925 name = "A"; 1926 } 1927 1928 @event(Entry) void onEntry() @safe 1929 { 1930 state = true; 1931 } 1932 } 1933 1934 static class B : Scene 1935 { 1936 int first; 1937 Data second; 1938 1939 this() @safe 1940 { 1941 name = "B"; 1942 } 1943 1944 @args!(int, Data) 1945 @event(Init) void onInit(int first, Data second) @safe 1946 { 1947 this.first = first; 1948 this.second = second; 1949 } 1950 } 1951 1952 A a; 1953 B b; 1954 1955 sceneManager.add (a = new A()); 1956 sceneManager.add (b = new B()); 1957 1958 sceneManager.gotoin ("A"); 1959 assert (a.state == true); 1960 1961 sceneManager.gotoin ("B", 7, Data(9, "test")); 1962 assert (b.first == 7 && b.second == Data(9, "test")); 1963 } 1964 1965 unittest 1966 { 1967 initSceneManager(); 1968 1969 struct Data 1970 { 1971 int first; 1972 string second; 1973 } 1974 1975 static class A : Scene 1976 { 1977 bool state = false; 1978 1979 this() @safe 1980 { 1981 name = "A"; 1982 } 1983 1984 @event(Entry) void onEntry() @safe 1985 { 1986 state = true; 1987 } 1988 } 1989 1990 static class B : Scene 1991 { 1992 int first; 1993 Data second; 1994 1995 this() @safe 1996 { 1997 name = "B"; 1998 } 1999 2000 @args!(int, Data) 2001 @event(Init) void onInit(int first, Data second) @safe 2002 { 2003 this.first = first; 2004 this.second = second; 2005 } 2006 } 2007 2008 static class C : Instance 2009 { 2010 Data data; 2011 2012 this() @safe 2013 { 2014 name = "C"; 2015 } 2016 2017 @args!(int, Data) 2018 @event(Init) void onInit(int first, Data second) @safe 2019 { 2020 this.data = second; 2021 } 2022 } 2023 2024 A a; 2025 B b; 2026 C c; 2027 2028 sceneManager.add (a = new A()); 2029 sceneManager.add (b = new B()); 2030 2031 b.add (c = new C()); 2032 2033 sceneManager.gotoin ("A"); 2034 2035 sceneManager.gotoin ("B", 7, Data(9, "test")); 2036 assert (c.data == Data(9, "test")); 2037 }