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