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 }