1 /++ 2 Scene description module. 3 4 A stage is an object that manages instances, collisions between them and serves 5 as a place for them (reserves them in a separate array). Such an object can 6 also be programmable using events (see tida.localevent). It does not have 7 properties that define behavior without functions, it can only contain 8 instances that, through it, can refer to other instances. 9 10 WARNING: 11 Don't pass any arguments to the scene constructor. 12 This breaks the scene restart mechanism. 13 14 Macros: 15 LREF = <a href="#$1">$1</a> 16 HREF = <a href="$1">$2</a> 17 PHOBREF = <a href="https://dlang.org/phobos/$1.html#$2">$2</a> 18 19 Authors: $(HREF https://github.com/TodNaz,TodNaz) 20 Copyright: Copyright (c) 2020 - 2021, TodNaz. 21 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT) 22 +/ 23 module tida.scene; 24 25 enum 26 { 27 InMemory, /// Delete with memory 28 InScene /// Delete with scene 29 } 30 31 /++ 32 A template that checks if the type is a scene. 33 +/ 34 template isScene(T) 35 { 36 enum isScene = is(T : Scene); 37 } 38 39 struct SceneEvents 40 { 41 import tida.event; 42 import tida.render; 43 import tida.localevent; 44 import tida.instance; 45 46 struct FEEntry 47 { 48 void delegate() @safe func; 49 size_t argsLength; 50 51 static FEEntry create(T...)(void delegate() @safe func) @trusted 52 { 53 FEEntry entry; 54 entry.func = func; 55 entry.appendArguments!T(); 56 57 return entry; 58 } 59 60 void appendArguments(T...)() @trusted 61 { 62 static foreach (Arg; T) 63 { 64 this.argsLength += Arg.sizeof; 65 } 66 } 67 68 bool validArgs(T...)(T args) @trusted 69 { 70 import std.stdio; 71 72 size_t length; 73 foreach (arg; args) 74 { 75 length += arg.sizeof; 76 } 77 78 return length == argsLength; 79 } 80 81 void opCall(T...)(T args) @trusted 82 in (validArgs(args)) 83 { 84 void delegate(T) @safe callFunction = cast(void delegate(T) @safe) func; 85 callFunction(args); 86 } 87 } 88 89 alias FEInit = FEEntry; 90 alias FERestart = FEEntry; 91 92 alias FEStep = void delegate() @safe; 93 alias FELeave = void delegate() @safe; 94 alias FEGameStart = void delegate() @safe; 95 alias FEGameExit = void delegate() @safe; 96 alias FEGameRestart = void delegate() @safe; 97 alias FEEventHandle = void delegate(EventHandler) @safe; 98 alias FEDraw = void delegate(IRenderer) @safe; 99 alias FEOnError = void delegate() @safe; 100 alias FECollision = void delegate(Instance) @safe; 101 alias FETrigger = void delegate() @safe; 102 alias FEDestroy = void delegate(Instance) @safe; 103 alias FEATrigger = void delegate(string) @safe; 104 105 struct SRTrigger 106 { 107 Trigger ev; 108 FETrigger fun; 109 } 110 111 FEInit[] InitFunctions; 112 FEStep[] StepFunctions; 113 FEStep[][size_t] StepThreadFunctions; 114 FERestart[] RestartFunctions; 115 FEEntry[] EntryFunctions; 116 FELeave[] LeaveFunctions; 117 FEGameStart[] GameStartFunctions; 118 FEGameExit[] GameExitFunctions; 119 FEGameRestart[] GameRestartFunctions; 120 FEEventHandle[] EventHandleFunctions; 121 FEDraw[] DrawFunctions; 122 FEOnError[] OnErrorFunctions; 123 SRTrigger[] OnTriggerFunctions; 124 FEDestroy[] OnDestroyFunctions; 125 FEATrigger[] OnAnyTriggerFunctions; 126 FECollision[] OnAnyCollisionFunctions; 127 } 128 129 /++ 130 Scene object. 131 +/ 132 class Scene 133 { 134 import tida.scenemanager; 135 import tida.instance; 136 import tida.component; 137 import tida.render; 138 import tida.event; 139 140 package(tida): 141 bool isInit = false; 142 143 protected: 144 Instance[] instances; 145 Instance[] erentInstances; 146 Instance[][] bufferThread; 147 148 public: 149 Camera camera; /// Camera scene 150 string name = ""; /// Scene name 151 SceneEvents events; 152 153 @safe: 154 this() nothrow 155 { 156 bufferThread = [[]]; 157 } 158 159 /++ 160 Returns a list of instances. 161 +/ 162 @property final Instance[] list() nothrow 163 { 164 return instances; 165 } 166 167 /++ 168 Returns a buffer of instances from the thread. 169 170 Params: 171 index = Thread id. 172 +/ 173 final Instance[] getThreadList(size_t index) nothrow 174 { 175 return bufferThread[index]; 176 } 177 178 /++ 179 Is there such a thread buffer. 180 181 Params: 182 index = Thread id. 183 +/ 184 final bool isThreadExists(size_t index) nothrow 185 { 186 return index < bufferThread.length; 187 } 188 189 /++ 190 Creates an instance buffer for the thread. 191 +/ 192 final void initThread(size_t count = 1) nothrow 193 { 194 foreach (_; 0 .. count) 195 { 196 bufferThread ~= [[]]; 197 } 198 } 199 200 /++ 201 Adds an instance to the scene for interpreting its actions. 202 203 Params: 204 instance = Instance. 205 threadID = In which thread to add execution. 206 +/ 207 final void add(T)(T instance, size_t threadID = 0) 208 if (isInstance!T) 209 in(instance, "Instance is not a create!") 210 do 211 { 212 if (threadID >= bufferThread.length) threadID = 0; 213 214 this.instances ~= instance; 215 instance.id = this.instances.length - 1; 216 instance.threadid = threadID; 217 218 bufferThread[threadID] ~= instance; 219 220 if (instance.name.length == 0) 221 instance.name = T.stringof; 222 223 sceneManager.instanceExplore!T(this, instance); 224 225 this.sort(); 226 } 227 228 /++ 229 Adds multiple instances at a time. 230 231 Params: 232 instances = Instances. 233 threadID = In which thread to add execution. 234 +/ 235 final void add(Instance[] instances, size_t threadID = 0) 236 { 237 foreach (instance; instances) 238 { 239 add(instance,threadID); 240 } 241 } 242 243 final void add(T...)(T args) 244 { 245 foreach (instance; args) 246 { 247 add(instance); 248 } 249 } 250 251 /++ 252 Returns a assorted list of instances. 253 +/ 254 final Instance[] getAssortedInstances() 255 { 256 return erentInstances; 257 } 258 259 /++ 260 Whether this instance is on the list. 261 262 Params: 263 instance = Instance. 264 +/ 265 final bool hasInstance(Instance instance) nothrow 266 { 267 foreach(ins; instances) 268 { 269 if(instance is ins) 270 return true; 271 } 272 273 return false; 274 } 275 276 /++ 277 Checks instances for collisions. 278 +/ 279 void worldCollision() @trusted 280 { 281 import tida.collision; 282 import std.algorithm : canFind, each; 283 import std.range : empty; 284 285 foreach(first; list()) 286 { 287 if (!first.solid) 288 continue; 289 290 foreach(second; list()) 291 { 292 if(first !is second && first.solid && second.solid) 293 { 294 if(first.active && second.active) 295 { 296 if ( 297 isCollide( first.mask, 298 second.mask, 299 first.position, 300 second.position) 301 ) 302 { 303 //auto firstColliders = first.colliders(); 304 //auto secondColliders = second.colliders(); 305 306 auto firstFunctions = first.collisionFunctions(); 307 auto secondFunctions = second.collisionFunctions(); 308 309 firstFunctions.each!((fun) => fun(second)); 310 secondFunctions.each!((fun) => fun(first)); 311 312 void checkColliders(Instance origin, Instance cls) @safe 313 { 314 foreach (cd; origin.colliders()) 315 { 316 if (cd.ev.name.empty) 317 { 318 if (!cd.ev.tag.empty) 319 { 320 if (cls.tags.canFind(cd.ev.tag)) 321 { 322 cd.fun(cls); 323 } 324 } 325 } else 326 { 327 if (cd.ev.tag.empty) 328 { 329 if (cd.ev.name == cls.name) 330 { 331 cd.fun(cls); 332 } 333 } else 334 { 335 if (cd.ev.name == cls.name && 336 cls.tags.canFind(cd.ev.tag)) 337 { 338 cd.fun(cls); 339 } 340 } 341 } 342 } 343 } 344 345 checkColliders(first, second); 346 checkColliders(second, first); 347 } 348 } 349 } 350 } 351 } 352 } 353 354 /++ 355 Removes an instance from the list and, if a delete method is 356 specified in the template, from memory. 357 358 Params: 359 instance = Instance. 360 isRemoveHandle = State remove function pointers in scene manager. 361 362 Type: 363 `InScene` - Removes only from the scene, does not free memory. 364 `InMemory` - Removes permanently, from the scene and from memory 365 (by the garbage collector). 366 +/ 367 final void instanceDestroy(ubyte type)(Instance instance, bool isRemoveHandle = true) @trusted 368 in(hasInstance(instance)) 369 do 370 { 371 import std.algorithm : each; 372 373 // dont remove, it's succes work. 374 void remove(T)(ref T[] obj, size_t index) @trusted nothrow 375 { 376 auto dump = obj.dup; 377 foreach (i; index .. dump.length) 378 { 379 import core.exception : RangeError; 380 try 381 { 382 dump[i] = dump[i + 1]; 383 } 384 catch (RangeError e) 385 { 386 continue; 387 } 388 } 389 obj = dump[0 .. $-1]; 390 } 391 392 remove(instances, instance.id); 393 foreach (size_t i; 0 .. bufferThread[instance.threadid].length) 394 { 395 if (bufferThread[instance.threadid][i] is instance) 396 { 397 remove(bufferThread[instance.threadid],i); 398 break; 399 } 400 } 401 402 if (this.instances.length != 0) 403 { 404 this.instances[instance.id .. $].each!((ref e) => e.id--); 405 } 406 407 if (sceneManager !is null) 408 { 409 sceneManager.destroyEventCall(instance); 410 sceneManager.destroyEventSceneCall(this, instance); 411 } 412 413 if (sceneManager !is null && isRemoveHandle) 414 sceneManager.removeHandle(this, instance); 415 416 static if(type == InMemory) 417 { 418 instance.dissconnectAll(); 419 destroy(instance); 420 } 421 } 422 423 /++ 424 Destroys an instance from the scene or from memory, depending on the template argument, by its class. 425 426 Params: 427 type = Type destroy. 428 Name = Instance class. 429 430 Type: 431 `InScene` - Removes only from the scene, does not free memory. 432 `InMemory` - Removes permanently, from the scene and from memory 433 (by the garbage collector). 434 +/ 435 final void instanceDestroy(ubyte type, Name)() @trusted 436 in(isInstance!Name) 437 do 438 { 439 instanceDestroy!type(getInstanceByClass!Name); 440 } 441 442 /++ 443 Returns an instance by name. 444 445 Params: 446 name = Instance name. 447 +/ 448 final Instance getInstanceByName(string name) nothrow 449 { 450 synchronized 451 { 452 foreach (instance; list()) 453 { 454 if (instance.name == name) 455 return instance; 456 } 457 458 return null; 459 } 460 } 461 462 /++ 463 Returns an instances by name. 464 465 Params: 466 name = Instance name. 467 +/ 468 final Instance[] getInstancesByName(string name) nothrow 469 { 470 import std.algorithm : find; 471 472 Instance[] finded; 473 474 foreach (e; list()) 475 { 476 if (e.name == name) 477 { 478 finded ~= e; 479 } 480 } 481 482 return finded; 483 } 484 485 @trusted unittest 486 { 487 initSceneManager(); 488 489 Scene test = new Scene(); 490 491 Instance a = new Instance(); 492 a.name = "bread1"; 493 494 Instance b = new Instance(); 495 b.name = "bread3"; 496 497 Instance c = new Instance(); 498 c.name = "bread1"; 499 500 Instance d = new Instance(); 501 d.name = "bread3"; 502 503 Instance e = new Instance(); 504 e.name = "bread1"; 505 506 test.add(a, b, c, d, e); 507 508 assert(test.getInstancesByName("bread1").length == 3); 509 } 510 511 /++ 512 Returns an instance by name and tag. 513 514 Params: 515 name = Instance name. 516 tag = Instance tag. 517 +/ 518 final Instance getInstanceByNameTag(string name, string tag) nothrow 519 { 520 synchronized 521 { 522 foreach (instance; list()) 523 { 524 if (instance.name == name) 525 { 526 foreach (tage; instance.tags) 527 if (tag == tage) 528 return instance; 529 } 530 } 531 532 return null; 533 } 534 } 535 536 /++ 537 Returns an object by its instance inheritor. 538 539 Params: 540 T = Class name. 541 +/ 542 final T getInstanceByClass(T)() nothrow 543 in(isInstance!T) 544 do 545 { 546 synchronized 547 { 548 foreach (instance; list) 549 { 550 if ((cast(T) instance) !is null) 551 return cast(T) instance; 552 } 553 554 return null; 555 } 556 } 557 558 import tida.shape, tida.vector; 559 560 /++ 561 Returns instance(-s) by its mask. 562 563 Params: 564 shape = Shape mask. 565 position = Instance position. 566 +/ 567 final Instance getInstanceByMask(Shapef shape, Vecf position) 568 { 569 import tida.collision; 570 571 synchronized 572 { 573 foreach (instance; list()) 574 { 575 if (instance.solid) 576 if (isCollide(shape,instance.mask,position,instance.position)) 577 { 578 return instance; 579 } 580 } 581 582 return null; 583 } 584 } 585 586 /// ditto 587 final Instance[] getInstancesByMask(Shapef shape,Vecf position) @safe 588 { 589 import tida.collision; 590 591 Instance[] result; 592 593 synchronized 594 { 595 foreach(instance; list()) 596 { 597 if(instance.solid) 598 if(isCollide(shape,instance.mask,position,instance.position)) { 599 result ~= instance; 600 continue; 601 } 602 } 603 } 604 605 return result; 606 } 607 608 /// Clear sorted list of instances. 609 void sortClear() @safe 610 { 611 this.erentInstances = null; 612 } 613 614 /// Sort list of instances. 615 void sort() @trusted 616 { 617 void sortErent(T)(ref T[] data, bool delegate(T a, T b) @safe nothrow func) @trusted nothrow 618 { 619 T tmp; 620 for (size_t i = 0; i < data.length; i++) 621 { 622 for (size_t j = (data.length-1); j >= (i + 1); j--) 623 { 624 if (func(data[j],data[j-1])) 625 { 626 tmp = data[j]; 627 data[j] = data[j-1]; 628 data[j-1] = tmp; 629 } 630 } 631 } 632 } 633 634 sortClear(); 635 636 erentInstances = instances.dup; 637 sortErent!Instance(erentInstances,(a, b) @safe nothrow => a.depth > b.depth); 638 } 639 } 640 641 unittest 642 { 643 import tida.instance; 644 import tida.scenemanager; 645 646 initSceneManager(); 647 648 class A : Instance { this() @safe { name = "A"; tags = ["A"]; }} 649 class B : Instance { this() @safe { name = "B"; tags = ["B"]; }} 650 651 Scene scene = new Scene(); 652 653 auto a = new A(); 654 auto b = new B(); 655 scene.add([a,b]); 656 657 assert(scene.getInstanceByClass!A is (a)); 658 assert(scene.getInstanceByClass!B is (b)); 659 660 assert(scene.getInstanceByName("A") is (a)); 661 assert(scene.getInstanceByName("B") is (b)); 662 663 assert(scene.getInstanceByNameTag("A", "A") is (a)); 664 assert(scene.getInstanceByNameTag("B", "B") is (b)); 665 } 666 667 unittest 668 { 669 import tida.instance; 670 import tida.scenemanager; 671 672 initSceneManager(); 673 674 Scene scene = new Scene(); 675 676 Instance a = new Instance(), 677 b = new Instance(); 678 679 a.depth = 3; 680 b.depth = 7; 681 682 scene.add([a,b]); 683 684 assert(scene.getAssortedInstances == ([b,a])); 685 }