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