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 Macros:
11     LREF = <a href="#$1">$1</a>
12     HREF = <a href="$1">$2</a>
13     PHOBREF = <a href="https://dlang.org/phobos/$1.html#$2">$2</a>
14 
15 Authors: $(HREF https://github.com/TodNaz,TodNaz)
16 Copyright: Copyright (c) 2020 - 2021, TodNaz.
17 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
18 +/
19 module tida.scene;
20 
21 enum
22 {
23     InMemory, /// Delete with memory
24     InScene /// Delete with scene
25 }
26 
27 /++
28 Scene object.
29 +/
30 class Scene
31 {
32     import tida.scenemanager;
33     import tida.instance;
34     import tida.component;
35     import tida.render;
36     import tida.event;
37 
38 package(tida):
39     bool isInit = false;
40 
41 protected:
42     Instance[] instances;
43     Instance[] erentInstances;
44     Instance[][] bufferThread;
45 
46 public:
47     Camera camera; /// Camera scene
48     string name = ""; /// Scene name
49 
50 @safe:
51     this() nothrow
52     {
53         bufferThread = [[]];
54     }
55 
56     /++
57     Returns a list of instances.
58     +/
59     @property final Instance[] list() nothrow
60     {
61         return instances;
62     }
63 
64     /++
65     Returns a buffer of instances from the thread.
66 
67     Params:
68         index = Thread id.
69     +/
70     final Instance[] getThreadList(size_t index) nothrow
71     {
72         return bufferThread[index];
73     }
74 
75     /++
76     Is there such a thread buffer.
77 
78     Params:
79         index = Thread id.
80     +/
81     final bool isThreadExists(size_t index) nothrow
82     {
83         return index < bufferThread.length;
84     }
85 
86     /++
87     Creates an instance buffer for the thread.
88     +/
89     final void initThread(size_t count = 1) nothrow
90     {
91         foreach (_; 0 .. count)
92         {
93             bufferThread ~= [[]];
94         }
95     }
96 
97     /++
98     Adds an instance to the scene for interpreting its actions.
99 
100     Params:
101         instance = Instance.
102         threadID = In which thread to add execution.
103     +/
104     final void add(T)(T instance,size_t threadID = 0)
105     in(instance, "Instance is not a create!")
106     do
107     {
108         static assert(isInstance!T, T.stringof ~ " is not a instance!");
109         if (threadID >= bufferThread.length) threadID = 0;
110 
111         this.instances ~= instance;
112         instance.id = this.instances.length - 1;
113         instance.threadid = threadID;
114 
115         bufferThread[threadID] ~= instance;
116 
117         sceneManager.instanceExplore!T(this, instance);
118 
119         this.sort();
120     }
121 
122     /++
123     Creates and adds an instance to the scene.
124 
125     Params:
126         Name = Name class.
127         threadID = In which thread to add execution.
128 
129     Note:
130         There must be no arguments in the constructor.
131     +/
132     final void add(Name)(size_t threadID = 0)
133     {
134         static assert(isInstance!T, T.stringof ~ " is not a instance!");
135         auto instance = new Name();
136 
137         add(instance, threadID);
138     }
139 
140     /++
141     Adds multiple instances at a time.
142 
143     Params:
144         instances = Instances.
145         threadID = In which thread to add execution.
146     +/
147     final void add(Instance[] instances,size_t threadID = 0)
148     {
149         foreach(instance; instances) {
150             add(instance,threadID);
151         }
152     }
153 
154     /++
155     Adds multiple instances at a time.
156 
157     Params:
158         Names = Names instances.
159         threadID = In which thread to add execution.
160     +/
161     final void add(Names...)(size_t threadID = 0)
162     {
163         static foreach (Name; Names) {
164             add!Name(threadID);
165         }
166     }
167 
168     /++
169     Returns a assorted list of instances.
170     +/
171     final Instance[] getAssortedInstances()
172     {
173         return erentInstances;
174     }
175 
176     /++
177     Whether this instance is on the list.
178 
179     Params:
180         instance = Instance.
181     +/
182     final bool hasInstance(Instance instance) nothrow
183     {
184         foreach(ins; instances) {
185             if(instance is ins)
186                 return true;
187         }
188 
189         return false;
190     }
191 
192     /++
193     Checks instances for collisions.
194     +/
195     void worldCollision() @trusted
196     {
197         import tida.collision;
198 
199         auto collstragt = sceneManager.collisionFunctions;
200         auto coll = sceneManager.colliders;
201 
202         foreach(first; list())
203         {
204             foreach(second; list())
205             {
206                 if(first !is second && first.solid && second.solid) {
207                     if(first.active && second.active)
208                     {
209                         import std.algorithm : canFind, each;
210 
211                         if (
212                             isCollide(  first.mask,
213                                         second.mask,
214                                         first.position,
215                                         second.position)
216                         ) {
217                             if (first in collstragt)
218                                 collstragt[first].each!(fun => fun(second));
219 
220                             if (second in collstragt)
221                                 collstragt[second].each!(fun => fun(first));
222 
223                             if (first in coll)
224                             {
225                                 foreach (cls; coll[first])
226                                 {
227                                     if (cls.ev.name != "")
228                                     {
229                                         if (cls.ev.name == second.name)
230                                         {
231                                             if (cls.ev.tag != "")
232                                             {
233                                                 if (second.tags.canFind(cls.ev.tag))
234                                                 {
235                                                     cls.fun(second);
236                                                 }
237                                             } else
238                                                 cls.fun(second);
239                                         }
240                                     } else
241                                     {
242                                         if (cls.ev.tag != "")
243                                         {
244                                             if (second.tags.canFind(cls.ev.tag))
245                                             {
246                                                 cls.fun(second);
247                                             }
248                                         } else
249                                         {
250                                             cls.fun(second);
251                                         }
252                                     }
253                                 }
254                             }
255 
256                             if (second in coll)
257                             {
258                                 foreach (cls; coll[second])
259                                 {
260                                     if (cls.ev.name != "")
261                                     {
262                                         if (cls.ev.name == first.name)
263                                         {
264                                             if (cls.ev.tag != "")
265                                             {
266                                                 if (first.tags.canFind(cls.ev.tag))
267                                                 {
268                                                     cls.fun(first);
269                                                 }
270                                             } else
271                                                 cls.fun(first);
272                                         }
273                                     } else
274                                     {
275                                         if (first.tags.canFind(cls.ev.tag))
276                                         {
277                                             cls.fun(first);
278                                         }
279                                     }
280                                 }
281                             }
282                         } else
283                         {
284                             if (first in collstragt)
285                                 collstragt[first].each!(fun => fun(null));
286 
287                             if (second in collstragt)
288                                 collstragt[second].each!(fun => fun(null));
289                         }
290                     }
291                 }
292             }
293         }
294     }
295 
296     /++
297     Removes an instance from the list and, if a delete method is
298     specified in the template, from memory.
299 
300     Params:
301         type = Type destroy.
302         instance = Instance.
303 
304     Type:
305         `InScene`  - Removes only from the scene, does not free memory.
306         `InMemory` - Removes permanently, from the scene and from memory
307                      (by the garbage collector).
308     +/
309     final void instanceDestroy(ubyte type)(Instance instance, bool isRemoveHandle = true) @trusted
310     in(hasInstance(instance))
311     do
312     {
313         import std.algorithm : each;
314 
315         // dont remove, it's succes work.
316         void remove(T)(ref T[] obj, size_t index) @trusted nothrow
317         {
318             auto dump = obj.dup;
319             foreach (i; index .. dump.length)
320             {
321                 import core.exception : RangeError;
322                 try
323                 {
324                     dump[i] = dump[i + 1];
325                 }
326                 catch (RangeError e)
327                 {
328                     continue;
329                 }
330             }
331             obj = dump[0 .. $-1];
332         }
333 
334         remove(instances, instance.id);
335         foreach (size_t i; 0 .. bufferThread[instance.threadid].length)
336         {
337             if (bufferThread[instance.threadid][i] is instance)
338             {
339                 remove(bufferThread[instance.threadid],i);
340                 break;
341             }
342         }
343 
344         if (this.instances.length != 0)
345         {
346             this.instances[instance.id .. $].each!((ref e) => e.id--);
347         }
348 
349         if (sceneManager !is null)
350         {
351             sceneManager.destroyEventCall(instance);
352             sceneManager.destroyEventSceneCall(this, instance);
353         }
354 
355         if (sceneManager !is null && isRemoveHandle)
356             sceneManager.removeHandle(this, instance);
357 
358         static if(type == InMemory)
359         {
360             instance.dissconnectAll();
361             destroy(instance);
362         }
363     }
364 
365     /++
366     Destroys an instance from the scene or from memory, depending on the template argument, by its class.
367 
368     Params:
369         type = Type destroy.
370         Name = Instance class.
371 
372     Type:
373         `InScene`  - Removes only from the scene, does not free memory.
374         `InMemory` - Removes permanently, from the scene and from memory
375                      (by the garbage collector).
376     +/
377     final void instanceDestroy(ubyte type, Name)() @trusted
378     in(isInstance!Name)
379     do
380     {
381         instanceDestroy!type(getInstanceByClass!Name);
382     }
383 
384     /++
385     Returns an instance by name.
386 
387     Params:
388         name = Instance name.
389     +/
390     final Instance getInstanceByName(string name) nothrow
391     {
392         foreach (instance; list())
393         {
394             if (instance.name == name)
395                 return instance;
396         }
397 
398         return null;
399     }
400 
401     /++
402     Returns an instance by name and tag.
403 
404     Params:
405         name = Instance name.
406         tag = Instance tag.
407     +/
408     final Instance getInstanceByNameTag(string name, string tag) nothrow
409     {
410         foreach (instance; list())
411         {
412             if (instance.name == name)
413             {
414                 foreach (tage; instance.tags)
415                     if (tag == tage)
416                         return instance;
417             }
418         }
419 
420         return null;
421     }
422 
423     /++
424     Returns an object by its instance inheritor.
425 
426     Params:
427         T = Class name.
428     +/
429     final T getInstanceByClass(T)() nothrow
430     in(isInstance!T)
431     do
432     {
433         foreach (instance; list)
434         {
435             if ((cast(T) instance) !is null)
436                 return cast(T) instance;
437         }
438 
439         return null;
440     }
441 
442     import tida.shape, tida.vector;
443 
444     /++
445     Returns instance(-s) by its mask.
446 
447     Params:
448         shape = Shape mask.
449         position = Instance position.
450     +/
451     final Instance getInstanceByMask(Shapef shape, Vecf position)
452     {
453         import tida.collision;
454 
455         foreach (instance; list())
456         {
457             if (instance.solid)
458             if (isCollide(shape,instance.mask,position,instance.position))
459             {
460                 return instance;
461             }
462         }
463 
464         return null;
465     }
466 
467     /// ditto
468     final Instance[] getInstancesByMask(Shapef shape,Vecf position) @safe
469     {
470         import tida.collision;
471 
472         Instance[] result;
473 
474         foreach(instance; list())
475         {
476             if(instance.solid)
477             if(isCollide(shape,instance.mask,position,instance.position)) {
478                 result ~= instance;
479                 continue;
480             }
481         }
482 
483         return result;
484     }
485 
486     /// Clear sorted list of instances.
487     void sortClear() @safe
488     {
489         this.erentInstances = null;
490     }
491 
492     /// Sort list of instances.
493     void sort() @trusted
494     {
495         void sortErent(T)(ref T[] data, bool delegate(T a, T b) @safe nothrow func) @trusted nothrow
496         {
497             T tmp;
498             for (size_t i = 0; i < data.length; i++)
499             {
500                 for (size_t j = (data.length-1); j >= (i + 1); j--)
501                 {
502                     if (func(data[j],data[j-1]))
503                     {
504                         tmp = data[j];
505                         data[j] = data[j-1];
506                         data[j-1] = tmp;
507                     }
508                 }
509             }
510         }
511 
512         sortClear();
513 
514         erentInstances = instances.dup;
515         sortErent!Instance(erentInstances,(a, b) @safe nothrow => a.depth > b.depth);
516     }
517 }
518 
519 unittest
520 {
521     import tida.instance;
522     import tida.scenemanager;
523 
524     initSceneManager();
525 
526     class A : Instance { this() @safe { name = "A"; tags = ["A"]; }}
527     class B : Instance { this() @safe { name = "B"; tags = ["B"]; }}
528 
529     Scene scene = new Scene();
530 
531     auto a = new A();
532     auto b = new B();
533     scene.add([a,b]);
534 
535     assert(scene.getInstanceByClass!A == a);
536     assert(scene.getInstanceByClass!B == b);
537 
538     assert(scene.getInstanceByName("A") == a);
539     assert(scene.getInstanceByName("B") == b);
540 
541     assert(scene.getInstanceByNameTag("A", "A") == a);
542     assert(scene.getInstanceByNameTag("B", "B") == b);
543 }
544 
545 unittest
546 {
547     import tida.instance;
548     import tida.scenemanager;
549 
550     initSceneManager();
551 
552     Scene scene = new Scene();
553 
554     Instance a = new Instance(),
555              b = new Instance();
556 
557     a.depth = 3;
558     b.depth = 7;
559 
560     scene.add([a,b]);
561 
562     assert(scene.getAssortedInstances == [b,a]);
563 }