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 }