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 }