1 /**
2 System management module.
3 
4 Copyright: © 2015-2016 Claude Merle
5 Authors: Claude Merle
6 License: This file is part of EntitySysD.
7 
8 EntitySysD is free software: you can redistribute it and/or modify it
9 under the terms of the Lesser GNU General Public License as published
10 by the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12 
13 EntitySysD is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 Lesser GNU General Public License for more details.
17 
18 You should have received a copy of the Lesser GNU General Public License
19 along with EntitySysD. If not, see $(LINK http://www.gnu.org/licenses/).
20 */
21 
22 module entitysysd.system;
23 
24 public import entitysysd.stat;
25 
26 import entitysysd.entity;
27 import entitysysd.event;
28 import entitysysd.exception;
29 import std.algorithm;
30 import std.container;
31 import std.format;
32 import std.range;
33 import std.typecons;
34 
35 
36 /**
37  * Enum allowing to give special order of a system when registering it to the
38  * $(D SystemManager).
39  * $(D Order.first) places it first in the list.
40  * $(D Order.last) places it last in the list.
41  * $(D Order.before(mySystem) places it before mySystem in the list.
42  * $(D Order.after(mySystem) places it after mySystem in the list.
43  * For $(D before) and $(D after), it assumes mySystem is already registered.
44  */
45 struct Order
46 {
47 public:
48     static auto first() @property
49     {
50         return Order(true, null);
51     }
52     static auto last() @property
53     {
54         return Order(false, null);
55     }
56     static auto before(S : System)(S system)
57     {
58         return Order(true, cast(System)system);
59     }
60     static auto after(S : System)(S system)
61     {
62         return Order(false, cast(System)system);
63     }
64 
65 private:
66     bool   mIsFirstOrBefore;
67     System mSystem;
68 }
69 
70 
71 deprecated("Please, use the abstract class `System` instead.")
72 alias ISystem = System;
73 
74 /**
75  * System abstract class. System classes may derive from it and override
76  * $(D prepare), $(D run) or $(D unprepare).
77  */
78 abstract class System
79 {
80 protected:
81     /**
82      * Prepare any data for the frame before a proper run.
83      */
84     void prepare(EntityManager entities, EventManager events, Duration dt)
85     {
86     }
87 
88     /**
89      * Called by the system-manager anytime its method run is called.
90      */
91     void run(EntityManager entities, EventManager events, Duration dt)
92     {
93     }
94 
95     /**
96      * Unprepare any data for the frame after the run.
97      */
98     void unprepare(EntityManager entities, EventManager events, Duration dt)
99     {
100     }
101 
102 public:
103     /**
104      * Change ordering of the system in the system-manager list.
105      *
106      * Throw:
107      * - A $(D SystemException) if the system is not registered.
108      */
109     final void reorder(Order order)
110     {
111         enforce!SystemException(mManager !is null);
112 
113         auto sr = mManager.mSystems[].find(this);
114         enforce!SystemException(!sr.empty);
115 
116         mManager.mSystems.linearRemove(sr.take(1));
117 
118         mManager.insert(this, order);
119     }
120 
121     /**
122      * Name of system (given once at the registration by the system-manager).
123      */
124     final string name() @property const
125     {
126         return mName;
127     }
128 
129     /**
130      * Reference on the system statistics.
131      */
132     final ref const(Stat) stat() @property const
133     {
134         return mStat;
135     }
136 
137 private:
138     string        mName;
139     SystemManager mManager;
140     Stat          mStat;
141 }
142 
143 
144 /**
145  * Entry point for systems. Allow to register systems.
146  */
147 class SystemManager
148 {
149 public:
150     this(EntityManager entityManager,
151          EventManager  eventManager)
152     {
153         mEntityManager = entityManager;
154         mEventManager  = eventManager;
155     }
156 
157     /**
158      * Register a new system.
159      *
160      * If `flag` is `Yes.AutoSubscribe` (default), this will automatically
161      * subscribe `system` to any events for which it implements `Receiver`.
162      * Note that this will not work if `system` is passed as `System` -- it
163      * should be passed as its full type.
164      *
165      * Throws: SystemException if the system was already registered.
166      */
167     void register(S : System)
168                  (S system,
169                   Order order = Order.last,
170                   Flag!"AutoSubscribe" flag = Yes.AutoSubscribe)
171     {
172         // Check system is not already registered
173         auto sr = mSystems[].find(system);
174         enforce!SystemException(sr.empty);
175 
176         insert(system, order);
177 
178         auto s = cast(System)system;
179         s.mName = S.stringof ~ format("@%04x", cast(ushort)cast(void*)system);
180         s.mManager = this;
181 
182         // auto-subscribe to events
183         if (flag)
184         {
185             import std.traits : InterfacesTuple;
186             foreach (Interface ; InterfacesTuple!S)
187             {
188                 static if (is(Interface : IReceiver!E, E))
189                     mEventManager.subscribe!E(system);
190             }
191         }
192     }
193 
194     /// ditto
195     void register(S : System)
196                  (S system, Flag!"AutoSubscribe" flag)
197     {
198         register(system, Order.last, flag);
199     }
200 
201     /**
202      * Unregister a system.
203      *
204      * If `flag` is `Yes.AutoSubscribe` (default), this will automatically
205      * unsubscribe `system` from any events for which it implements `Receiver`.
206      * Note that this will not work if `system` is passed as `System` -- it
207      * should be passed as its full type.
208      *
209      * Throws: SystemException if the system was not registered.
210      */
211     void unregister(S : System)(S system,
212                                 Flag!"AutoSubscribe" flag = Yes.AutoSubscribe)
213     {
214         auto sr = mSystems[].find(system);
215         enforce!SystemException(!sr.empty);
216 
217         mSystems.linearRemove(sr.take(1));
218 
219         auto s = cast(System)system;
220         s.mManager = null;
221 
222         // auto-unsubscribe from events
223         if (flag)
224         {
225             import std.traits : InterfacesTuple;
226             foreach (Interface ; InterfacesTuple!S)
227             {
228                 static if (is(Interface : IReceiver!E, E))
229                     mEventManager.unsubscribe!E(system);
230             }
231         }
232     }
233 
234     /**
235      * Prepare all the registered systems.
236      *
237      * They are prepared in the order that they were registered.
238      */
239     void prepare(Duration dt)
240     {
241         foreach (s; mSystems)
242             s.prepare(mEntityManager, mEventManager, dt);
243     }
244 
245     /**
246      * Run all the registered systems.
247      *
248      * They are runt in the order that they were registered.
249      */
250     void run(Duration dt)
251     {
252         if (mStatEnabled)
253             mStatRun.start();
254 
255         foreach (sys; mSystems)
256         {
257             if (mStatEnabled)
258                 sys.mStat.start();
259             sys.run(mEntityManager, mEventManager, dt);
260             if (mStatEnabled)
261                 sys.mStat.stop();
262         }
263 
264         if (mStatEnabled)
265             mStatRun.stop();
266     }
267 
268     /**
269      * Unprepare all the registered systems.
270      *
271      * They are unprepared in the reverse order that they were registered.
272      */
273     void unprepare(Duration dt)
274     {
275         foreach_reverse (s; mSystems)
276             s.unprepare(mEntityManager, mEventManager, dt);
277     }
278 
279     /**
280      * Prepare, run and unprepare all the registered systems.
281      */
282     void runFull(Duration dt)
283     {
284         if (mStatEnabled)
285             mStatAll.start();
286 
287         prepare(dt);
288         run(dt);
289         unprepare(dt);
290 
291         if (mStatEnabled)
292         {
293             mStatAll.stop();
294 
295             if (mStatAll.elapsedTime >= mStatRate)
296             {
297                 mStatRun.update();
298                 mStatAll.update();
299                 foreach (sys; mSystems)
300                     sys.mStat.update();
301 
302                 if (mStatDg !is null)
303                     mStatDg();
304             }
305         }
306     }
307 
308 
309     /**
310      * Return a bidirectional range on the list of the registered systems.
311      */
312     auto opSlice()
313     {
314         return mSystems[];
315     }
316 
317     /**
318      * Reference on profiling statistics of all the system's run.
319      */
320     final ref const(Stat) statRun() @property const
321     {
322         return mStatRun;
323     }
324 
325     /**
326      * Reference on profiling statistics of all the system's prepare, unprepare
327      * and run.
328      */
329     final ref const(Stat) statAll() @property const
330     {
331         return mStatAll;
332     }
333 
334     /**
335      * Enable statistics profiling on the system-manager and all its
336      * registered systems.
337      * A delegate $(D dg) can be given, the $(D rate) at which it will be called
338      * to provide significant stat's.
339      */
340     void enableStat(Duration rate = seconds(0), void delegate() dg = null)
341     {
342         mStatEnabled = true;
343         mStatRate    = rate;
344         mStatDg      = dg;
345     }
346 
347     /**
348      * Disable statistics profiling on the system-manager and all its
349      * registered systems.
350      */
351     void disableStat()
352     {
353         mStatEnabled = false;
354         mStatRun.clear();
355         mStatAll.clear();
356         foreach (sys; mSystems)
357             sys.mStat.clear();
358     }
359 
360     /**
361      * Tells whether statistics profiling is enabled or not.
362      */
363     bool statEnabled() @property const
364     {
365         return mStatEnabled;
366     }
367 
368 private:
369     void insert(System system, Order order)
370     {
371         if (order == Order.first)
372         {
373             mSystems.insertFront(cast(System)system);
374         }
375         else if (order == Order.last)
376         {
377             mSystems.insertBack(cast(System)system);
378         }
379         else if (order.mIsFirstOrBefore)
380         {
381             auto or = mSystems[].find(order.mSystem);
382             enforce!SystemException(!or.empty);
383             mSystems.insertBefore(or, cast(System)system);
384         }
385         else //if (!order.mIsFirstOrBefore)
386         {
387             auto or = mSystems[];
388             enforce!SystemException(!or.empty);
389             //xxx dodgy, but DList's are tricky
390             while (or.back != order.mSystem)
391             {
392                 or.popBack();
393                 enforce!SystemException(!or.empty);
394             }
395             mSystems.insertAfter(or, cast(System)system);
396         }
397     }
398 
399     EntityManager   mEntityManager;
400     EventManager    mEventManager;
401     DList!System    mSystems;
402     bool            mStatEnabled;
403     Duration        mStatRate;
404     void delegate() mStatDg;
405     Stat            mStatAll;
406     Stat            mStatRun;
407 }
408 
409 
410 //******************************************************************************
411 //***** UNIT-TESTS
412 //******************************************************************************
413 
414 // validate event auto-subscription/unsubscription
415 unittest
416 {
417     @event struct EventA
418     {
419     }
420 
421     @event struct EventB
422     {
423     }
424 
425     class MySys : System, IReceiver!EventA, IReceiver!EventB
426     {
427         int eventCount;
428         void receive(EventA ev)
429         {
430             ++eventCount;
431         }
432         void receive(EventB ev)
433         {
434             ++eventCount;
435         }
436     }
437 
438     auto events = new EventManager;
439     auto entities = new EntityManager(events);
440     auto systems = new SystemManager(entities, events);
441 
442     auto sys = new MySys;
443 
444     // registering the system should subscribe to MyEvent
445     systems.register(sys);
446     events.emit!EventA();
447     events.emit!EventB();
448     assert(sys.eventCount == 2);
449 
450     // registering the system should unsubscribe from MyEvent
451     systems.unregister(sys);
452     events.emit!EventA();
453     events.emit!EventB();
454     assert(sys.eventCount == 2);
455 
456     // explicitly disallow auto-subscription
457     systems.register(sys, Order.last, No.AutoSubscribe);
458     events.emit!EventA();
459     events.emit!EventB();
460     assert(sys.eventCount == 2);
461 
462     // unregister without unsubscribing
463     systems.unregister(sys);
464     systems.register(sys, Yes.AutoSubscribe);
465     systems.unregister(sys, No.AutoSubscribe);
466     events.emit!EventA();
467     events.emit!EventB();
468     assert(sys.eventCount == 4);
469 }
470 
471 
472 // validate ordering
473 unittest
474 {
475     class MySys0 : System
476     {
477     }
478 
479     class MySys1 : System
480     {
481     }
482 
483     auto events = new EventManager;
484     auto entities = new EntityManager(events);
485     auto systems = new SystemManager(entities, events);
486 
487     auto sys0 = new MySys0;
488     auto sys1 = new MySys1;
489     auto sys2 = new MySys0;
490     auto sys3 = new MySys1;
491     auto sys4 = new MySys0;
492     auto sys5 = new MySys1;
493     auto sys6 = new MySys0;
494     auto sys7 = new MySys1;
495 
496     // registering the systems
497     systems.register(sys0);
498     systems.register(sys1, Order.last);
499     systems.register(sys2, Order.first);
500     systems.register(sys3, Order.first);
501     systems.register(sys4, Order.after(sys2));
502     systems.register(sys5, Order.before(sys3));
503     systems.register(sys6, Order.after(sys1));
504     systems.register(sys7, Order.before(sys4));
505 
506     // check order is correct
507     auto sysRange = systems[];
508     assert(sysRange.front == sys5);
509     sysRange.popFront();
510     assert(sysRange.front == sys3);
511     sysRange.popFront();
512     assert(sysRange.front == sys2);
513     sysRange.popFront();
514     assert(sysRange.front == sys7);
515     sysRange.popFront();
516     assert(sysRange.front == sys4);
517     sysRange.popFront();
518     assert(sysRange.front == sys0);
519     sysRange.popFront();
520     assert(sysRange.front == sys1);
521     sysRange.popFront();
522     assert(sysRange.front == sys6);
523     sysRange.popFront();
524     assert(sysRange.empty);
525 
526     // check re-ordering works
527     sys3.reorder(Order.first);
528 
529     sysRange = systems[];
530     assert(sysRange.front == sys3);
531     sysRange.popFront();
532     assert(sysRange.front == sys5);
533     sysRange.popFront();
534     assert(sysRange.front == sys2);
535     sysRange.popFront();
536     assert(!sysRange.empty);
537 
538     // check exceptions
539     auto sysNA = new MySys0;
540     auto sysNB = new MySys1;
541 
542     assert(collectException!SystemException(
543             systems.register(sys1))
544             !is null);
545     assert(collectException!SystemException(
546             systems.unregister(sysNA))
547             !is null);
548     assert(collectException!SystemException(
549             systems.register(sysNA, Order.after(sysNB)))
550             !is null);
551     assert(collectException!SystemException(
552             systems.register(sysNA, Order.before(sysNB)))
553             !is null);
554 }