1 // Written by David Megginson, started 2000-12 2 // 3 // Copyright (C) 2000 David Megginson, david@megginson.com 4 // 5 // This program is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU General Public License as 7 // published by the Free Software Foundation; either version 2 of the 8 // License, or (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, but 11 // WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with this program; if not, write to the Free Software 17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 // 19 // $Id$ 20 21 22 #ifndef __SUBSYSTEM_MGR_HXX 23 #define __SUBSYSTEM_MGR_HXX 1 24 25 26 #include <simgear/compiler.h> 27 28 #include <string> 29 #include <map> 30 #include <vector> 31 #include <functional> 32 33 #include <simgear/timing/timestamp.hxx> 34 #include <simgear/structure/SGSharedPtr.hxx> 35 #include <simgear/misc/strutils.hxx> 36 #include <simgear/props/propsfwd.hxx> 37 38 class TimingInfo 39 { 40 private: 41 std::string eventName; 42 SGTimeStamp time; 43 44 public: TimingInfo(const std::string & name,const SGTimeStamp & t)45 TimingInfo(const std::string& name, const SGTimeStamp &t) : 46 eventName(name), time(t) 47 { } getName() const48 const std::string& getName() const { return eventName; } getTime() const49 const SGTimeStamp& getTime() const { return time; } 50 }; 51 52 // forward decls 53 class SampleStatistic; 54 class SGSubsystemGroup; 55 class SGSubsystemMgr; 56 57 typedef std::vector<TimingInfo> eventTimeVec; 58 typedef std::vector<TimingInfo>::iterator eventTimeVecIterator; 59 60 typedef void (*SGSubsystemTimingCb)(void* userData, const std::string& name, SampleStatistic* pStatistic); 61 62 /** 63 * Basic interface for all FlightGear subsystems. 64 * 65 * <p>This is an abstract interface that all FlightGear subsystems 66 * will eventually implement. It defines the basic operations for 67 * each subsystem: initialization, property binding and unbinding, and 68 * updating. Interfaces may define additional methods, but the 69 * preferred way of exchanging information with other subsystems is 70 * through the property tree.</p> 71 * 72 * <p>To publish information through a property, a subsystem should 73 * bind it to a variable or (if necessary) a getter/setter pair in the 74 * bind() method, and release the property in the unbind() method:</p> 75 * 76 * <pre> 77 * void MySubsystem::bind () 78 * { 79 * fgTie("/controls/flight/elevator", &_elevator); 80 * fgSetArchivable("/controls/flight/elevator"); 81 * } 82 * 83 * void MySubsystem::unbind () 84 * { 85 * fgUntie("/controls/flight/elevator"); 86 * } 87 * </pre> 88 * 89 * <p>To reference a property (possibly) from another subsystem, there 90 * are two alternatives. If the property will be referenced only 91 * infrequently (say, in the init() method), then the fgGet* methods 92 * declared in fg_props.hxx are the simplest:</p> 93 * 94 * <pre> 95 * void MySubsystem::init () 96 * { 97 * _errorMargin = fgGetFloat("/display/error-margin-pct"); 98 * } 99 * </pre> 100 * 101 * <p>On the other hand, if the property will be referenced frequently 102 * (say, in the update() method), then the hash-table lookup required 103 * by the fgGet* methods might be too expensive; instead, the 104 * subsystem should obtain a reference to the actual property node in 105 * its init() function and use that reference in the main loop:</p> 106 * 107 * <pre> 108 * void MySubsystem::init () 109 * { 110 * _errorNode = fgGetNode("/display/error-margin-pct", true); 111 * } 112 * 113 * void MySubsystem::update (double delta_time_sec) 114 * { 115 * do_something(_errorNode.getFloatValue()); 116 * } 117 * </pre> 118 * 119 * <p>The node returned will always be a pointer to SGPropertyNode, 120 * and the subsystem should <em>not</em> delete it in its destructor 121 * (the pointer belongs to the property tree, not the subsystem).</p> 122 * 123 * <p>The program may ask the subsystem to suspend or resume 124 * sim-time-dependent operations; by default, the suspend() and 125 * resume() methods set the protected variable <var>_suspended</var>, 126 * which the subsystem can reference in its update() method, but 127 * subsystems may also override the suspend() and resume() methods to 128 * take different actions.</p> 129 */ 130 131 class SGSubsystem : public SGReferenced 132 { 133 public: 134 using TimerStats = std::map<std::string, double>; 135 /** 136 * Default constructor. 137 */ 138 SGSubsystem (); 139 140 /** 141 * Virtual destructor to ensure that subclass destructors are called. 142 */ 143 virtual ~SGSubsystem (); 144 145 146 /** 147 * Initialize the subsystem. 148 * 149 * <p>This method should set up the state of the subsystem, but 150 * should not bind any properties. Note that any dependencies on 151 * the state of other subsystems should be placed here rather than 152 * in the constructor, so that FlightGear can control the 153 * initialization order.</p> 154 */ 155 virtual void init (); 156 157 typedef enum 158 { 159 INIT_DONE, ///< subsystem is fully initialised 160 INIT_CONTINUE ///< init should be called again 161 } InitStatus; 162 163 virtual InitStatus incrementalInit (); 164 165 /** 166 * Initialize parts that depend on other subsystems having been initialized. 167 * 168 * <p>This method should set up all parts that depend on other 169 * subsystems. One example is the scripting/Nasal subsystem, which 170 * is initialized last. So, if a subsystem wants to execute Nasal 171 * code in subsystem-specific configuration files, it has to do that 172 * in its postinit() method.</p> 173 */ 174 virtual void postinit (); 175 176 177 /** 178 * Reinitialize the subsystem. 179 * 180 * <p>This method should cause the subsystem to reinitialize itself, 181 * and (normally) to reload any configuration files.</p> 182 */ 183 virtual void reinit (); 184 185 186 /** 187 * Shutdown the subsystem. 188 * 189 * <p>Release any state associated with subsystem. Shutdown happens in 190 * the reverse order to init(), so this is the correct place to do 191 * shutdown that depends on other subsystems. 192 * </p> 193 */ 194 virtual void shutdown (); 195 196 /** 197 * Acquire the subsystem's property bindings. 198 * 199 * <p>This method should bind all properties that the subsystem 200 * publishes. It will be invoked after init, but before any 201 * invocations of update.</p> 202 */ 203 virtual void bind (); 204 205 206 /** 207 * Release the subsystem's property bindings. 208 * 209 * <p>This method should release all properties that the subsystem 210 * publishes. It will be invoked by FlightGear (not the destructor) 211 * just before the subsystem is removed.</p> 212 */ 213 virtual void unbind (); 214 215 216 /** 217 * Update the subsystem. 218 * 219 * <p>FlightGear invokes this method every time the subsystem should 220 * update its state.</p> 221 * 222 * @param delta_time_sec The delta time, in seconds, since the last 223 * update. On first update, delta time will be 0. 224 */ 225 virtual void update (double delta_time_sec) = 0; 226 227 228 /** 229 * Suspend operation of this subsystem. 230 * 231 * <p>This method instructs the subsystem to suspend 232 * sim-time-dependent operations until asked to resume. The update 233 * method will still be invoked so that the subsystem can take any 234 * non-time-dependent actions, such as updating the display.</p> 235 * 236 * <p>It is not an error for the suspend method to be invoked when 237 * the subsystem is already suspended; the invocation should simply 238 * be ignored.</p> 239 */ 240 virtual void suspend (); 241 242 243 /** 244 * Suspend or resume operation of this subsystem. 245 * 246 * @param suspended true if the subsystem should be suspended, false 247 * otherwise. 248 */ 249 virtual void suspend (bool suspended); 250 251 252 /** 253 * Resume operation of this subsystem. 254 * 255 * <p>This method instructs the subsystem to resume 256 * sim-time-depended operations. It is not an error for the resume 257 * method to be invoked when the subsystem is not suspended; the 258 * invocation should simply be ignored.</p> 259 */ 260 virtual void resume (); 261 262 263 /** 264 * Test whether this subsystem is suspended. 265 * 266 * @return true if the subsystem is suspended, false if it is not. 267 */ 268 virtual bool is_suspended () const; 269 270 /** 271 * Trigger the callback to report timing information for all subsystems. 272 */ 273 void reportTiming(void); 274 275 virtual void reportTimingStats(TimerStats *_lastValues); 276 /** 277 * Place time stamps at strategic points in the execution of subsystems 278 * update() member functions. Predominantly for debugging purposes. 279 */ 280 void stamp(const std::string& name); 281 282 /** 283 * composite name for this subsystem (type name & optional instance name) 284 */ subsystemId() const285 std::string subsystemId() const 286 { return _subsystemId; } 287 288 /** 289 * @brief the type (class)-specific part of the subsystem name. 290 */ 291 std::string subsystemClassId() const; 292 293 /** 294 * @brief the instance part of the subsystem name. Empty if this 295 * subsystem is not instanced 296 */ 297 std::string subsystemInstanceId() const; 298 is_group() const299 virtual bool is_group() const 300 { return false; } 301 302 virtual SGSubsystemMgr* get_manager() const; 303 304 /// get the parent group of this subsystem 305 SGSubsystemGroup* get_group() const; 306 307 // ordering here is exceptionally important, due to 308 // liveness of ranges. If you're extending this consider 309 // carefully where the new state lies and position it correctly. 310 // Also extend the tests to ensure you handle all cases. 311 enum class State { 312 INVALID = -1, 313 ADD = 0, 314 BIND, 315 INIT, 316 POSTINIT, 317 REINIT, 318 SUSPEND, 319 RESUME, 320 SHUTDOWN, 321 UNBIND, 322 REMOVE 323 }; 324 325 /** 326 * debug helper, print a state as a string 327 */ 328 static std::string nameForState(State s); 329 330 /** 331 * gets fine grained stats of time elapsed since last clear 332 * returns map of ident and time 333 */ getTimerStats()334 virtual const TimerStats &getTimerStats() { 335 return _timerStats; 336 } 337 338 /** 339 * clear fine grained stats that are over the specified value. 340 */ resetTimerStats(double val=0)341 virtual void resetTimerStats(double val = 0) { } 342 343 protected: 344 friend class SGSubsystemMgr; 345 friend class SGSubsystemGroup; 346 347 void set_name(const std::string& n); 348 349 void set_group(SGSubsystemGroup* group); 350 351 /// composite name for the subsystem (type name and instance name if this 352 /// is an instanced subsystem. (Since this member was originally defined as 353 /// protected, not private, we can't rename it easily) 354 std::string _name; 355 356 bool _suspended = false; 357 358 eventTimeVec timingInfo; 359 360 static SGSubsystemTimingCb reportTimingCb; 361 static void* reportTimingUserData; 362 static bool reportTimingStatsRequest; 363 static int maxTimePerFrame_ms; 364 365 private: 366 /// composite name for the subsystem (type name and instance name if this 367 /// is an instanced subsystem. (Since this member was originally defined as 368 /// protected, not private, we can't rename it easily) 369 std::string _subsystemId; 370 371 SGSubsystemGroup* _group = nullptr; 372 protected: 373 TimerStats _timerStats, _lastTimerStats; 374 double _executionTime; 375 double _lastExecutionTime; 376 377 }; 378 379 typedef SGSharedPtr<SGSubsystem> SGSubsystemRef; 380 381 /** 382 * A group of FlightGear subsystems. 383 */ 384 class SGSubsystemGroup : public SGSubsystem 385 { 386 public: 387 SGSubsystemGroup (); 388 virtual ~SGSubsystemGroup (); 389 390 // Subsystem API. 391 void bind() override; 392 InitStatus incrementalInit() override; 393 void init() override; 394 void postinit() override; 395 void reinit() override; 396 void resume() override; 397 void shutdown() override; 398 void suspend() override; 399 void unbind() override; 400 void update(double delta_time_sec) override; 401 bool is_suspended() const override; 402 403 virtual void set_subsystem (const std::string &name, 404 SGSubsystem * subsystem, 405 double min_step_sec = 0); 406 407 void set_subsystem (SGSubsystem * subsystem, double min_step_sec = 0); 408 409 virtual SGSubsystem * get_subsystem (const std::string &name); 410 bool remove_subsystem (const std::string &name); 411 virtual bool has_subsystem (const std::string &name) const; 412 413 void reportTimingStats(TimerStats *_lastValues) override; 414 /** 415 * Remove all subsystems. 416 */ 417 virtual void clearSubsystems(); 418 419 void reportTiming(void); 420 421 /** 422 * 423 */ 424 void set_fixed_update_time(double fixed_dt); 425 426 /** 427 * retrive list of member subsystem names 428 */ 429 string_list member_names() const; 430 431 template<class T> get_subsystem()432 T* get_subsystem() 433 { 434 return dynamic_cast<T*>(get_subsystem(T::staticSubsystemClassId())); 435 } 436 is_group() const437 bool is_group() const override 438 { return true; } 439 440 SGSubsystemMgr* get_manager() const override; 441 442 private: 443 void forEach(std::function<void(SGSubsystem*)> f); 444 void reverseForEach(std::function<void(SGSubsystem*)> f); 445 446 void notifyWillChange(SGSubsystem* sub, SGSubsystem::State s); 447 void notifyDidChange(SGSubsystem* sub, SGSubsystem::State s); 448 449 friend class SGSubsystemMgr; 450 451 void set_manager(SGSubsystemMgr* manager); 452 453 class Member; 454 Member* get_member (const std::string &name, bool create = false); 455 456 using MemberVec = std::vector<Member*>; 457 MemberVec _members; 458 459 // track the state of this group, so we can transition added/removed 460 // members correctly 461 SGSubsystem::State _state = SGSubsystem::State::INVALID; 462 double _fixedUpdateTime; 463 double _updateTimeRemainder; 464 465 /// index of the member we are currently init-ing 466 int _initPosition; 467 468 /// back-pointer to the manager, for the root groups. (sub-groups 469 /// will have this as null, and chain via their parent) 470 SGSubsystemMgr* _manager = nullptr; 471 }; 472 473 typedef SGSharedPtr<SGSubsystemGroup> SGSubsystemGroupRef; 474 475 /** 476 * Manage subsystems for FlightGear. 477 * 478 * This top-level subsystem will eventually manage all of the 479 * subsystems in FlightGear: it broadcasts its life-cycle events 480 * (init, bind, etc.) to all of the subsystems it manages. Subsystems 481 * are grouped to guarantee order of initialization and execution -- 482 * currently, the only two groups are INIT and GENERAL, but others 483 * will appear in the future. 484 * 485 * All subsystems are named as well as grouped, and subsystems can be 486 * looked up by name and cast to the appropriate subtype when another 487 * subsystem needs to invoke specialized methods. 488 * 489 * The subsystem manager owns the pointers to all the subsystems in 490 * it. 491 */ 492 class SGSubsystemMgr : public SGSubsystem 493 { 494 public: 495 SGSubsystemMgr(const SGSubsystemMgr&) = delete; 496 SGSubsystemMgr& operator=(const SGSubsystemMgr&) = delete; 497 498 /** 499 * Types of subsystem groups. 500 */ 501 enum GroupType { 502 INVALID = -1, 503 INIT = 0, 504 GENERAL, 505 FDM, ///< flight model, autopilot, instruments that run coupled 506 POST_FDM, ///< certain subsystems depend on FDM data 507 DISPLAY, ///< view, camera, rendering updates 508 SOUND/*I want to be last!*/, ///< needs to run AFTER display, to allow concurrent GPU/sound processing 509 MAX_GROUPS 510 }; 511 512 SGSubsystemMgr (); 513 virtual ~SGSubsystemMgr (); 514 515 // Subsystem API. 516 void bind() override; 517 void init() override; 518 InitStatus incrementalInit() override; 519 void postinit() override; 520 void reinit() override; 521 void resume() override; 522 void shutdown() override; 523 void suspend() override; 524 void unbind() override; 525 void update(double delta_time_sec) override; 526 bool is_suspended() const override; 527 528 // Subsystem identification. staticSubsystemClassId()529 static const char* staticSubsystemClassId() { return "subsystem-mgr"; } 530 531 virtual void add (const char * name, 532 SGSubsystem * subsystem, 533 GroupType group = GENERAL, 534 double min_time_sec = 0); 535 536 /** 537 * remove a subsystem, and return true on success 538 * returns false if the subsystem was not found 539 */ 540 bool remove(const char* name); 541 542 virtual SGSubsystemGroup * get_group (GroupType group); 543 544 SGSubsystem* get_subsystem(const std::string &name) const; 545 546 SGSubsystem* get_subsystem(const std::string &name, const std::string& subsystemInstanceId) const; 547 548 void reportTiming(); setReportTimingCb(void * userData,SGSubsystemTimingCb cb)549 void setReportTimingCb(void* userData, SGSubsystemTimingCb cb) { reportTimingCb = cb; reportTimingUserData = userData; } setReportTimingStats(bool v)550 void setReportTimingStats(bool v) { reportTimingStatsRequest = v; } 551 552 /** 553 * @brief set the root property node for this subsystem manager 554 * subsystems can retrieve this value during init/bind (or at any time) 555 * to base their own properties trees off of. 556 */ 557 void set_root_node(SGPropertyNode_ptr node); 558 559 /** 560 * @brief retrieve the root property node for this subsystem manager 561 */ 562 SGPropertyNode_ptr root_node() const; 563 564 template<class T> get_subsystem() const565 T* get_subsystem() const 566 { 567 return dynamic_cast<T*>(get_subsystem(T::staticSubsystemClassId())); 568 } 569 570 // instanced overloads, for both raw char* and std::string 571 // these concatenate the subsystem type name with the instance name 572 template<class T> get_subsystem(const char * subsystemInstanceId) const573 T* get_subsystem(const char* subsystemInstanceId) const 574 { 575 return dynamic_cast<T*>(get_subsystem(T::staticSubsystemClassId(), subsystemInstanceId)); 576 } 577 578 template<class T> get_subsystem(const std::string & subsystemInstanceId) const579 T* get_subsystem(const std::string& subsystemInstanceId) const 580 { 581 return dynamic_cast<T*>(get_subsystem(T::staticSubsystemClassId(), subsystemInstanceId)); 582 } 583 584 /** 585 * @brief Subsystem dependency structure. 586 */ 587 struct Dependency { 588 enum Type { 589 HARD, ///< The subsystem cannot run without this subsystem dependency. 590 SOFT, ///< The subsystem uses this subsystem dependency, but can run without it. 591 SEQUENCE, ///< Used for ordering subsystem initialisation. 592 NONSUBSYSTEM_HARD, ///< The subsystem cannot run without this non-subsystem dependency. 593 NONSUBSYSTEM_SOFT, ///< The subsystem uses this non-subsystem dependency, but can run without it. 594 PROPERTY ///< The subsystem requires this property to exist to run. 595 }; 596 597 std::string name; 598 Type type; 599 }; 600 601 using DependencyVec = std::vector<SGSubsystemMgr::Dependency>; 602 using SubsystemFactoryFunctor = std::function<SGSubsystemRef()>; 603 604 /** 605 * @brief register a subsytem with the manager 606 * 607 */ 608 static void registerSubsystem(const std::string& name, 609 SubsystemFactoryFunctor f, 610 GroupType group, 611 bool isInstanced = false, 612 double updateInterval = 0.0, 613 std::initializer_list<Dependency> deps = {}); 614 615 template<class T> 616 class Registrant 617 { 618 public: Registrant(GroupType group=GENERAL,std::initializer_list<Dependency> deps={},double updateInterval=0.0)619 Registrant(GroupType group = GENERAL, 620 std::initializer_list<Dependency> deps = {}, 621 double updateInterval = 0.0) 622 { __anon74f1a66a0202()623 SubsystemFactoryFunctor factory = [](){ return new T; }; 624 SGSubsystemMgr::registerSubsystem(T::staticSubsystemClassId(), 625 factory, group, 626 false, updateInterval, 627 deps); 628 } 629 630 // could implement a dtor to unregister but not needed at the moment 631 }; 632 633 template<class T> 634 class InstancedRegistrant 635 { 636 public: InstancedRegistrant(GroupType group=GENERAL,std::initializer_list<Dependency> deps={},double updateInterval=0.0)637 InstancedRegistrant(GroupType group = GENERAL, 638 std::initializer_list<Dependency> deps = {}, 639 double updateInterval = 0.0) 640 { __anon74f1a66a0302()641 SubsystemFactoryFunctor factory = [](){ return new T; }; 642 SGSubsystemMgr::registerSubsystem(T::staticSubsystemClassId(), 643 factory, group, 644 true, updateInterval, 645 deps); 646 } 647 648 // could implement a dtor to unregister but not needed at the moment 649 }; 650 651 /** 652 * @brief templated add function, subsystem is deduced automatically 653 * 654 */ 655 template <class T> add(GroupType customGroup=INVALID,double customInterval=0.0)656 SGSharedPtr<T> add(GroupType customGroup = INVALID, double customInterval = 0.0) 657 { 658 auto ref = create(T::staticSubsystemClassId()); 659 660 661 const GroupType group = (customGroup == INVALID) ? 662 defaultGroupFor(T::staticSubsystemClassId()) : customGroup; 663 const double interval = (customInterval == 0.0) ? 664 defaultUpdateIntervalFor(T::staticSubsystemClassId()) : customInterval; 665 add(ref->subsystemId().c_str(), ref.ptr(), group, interval); 666 return dynamic_cast<T*>(ref.ptr()); 667 } 668 669 /** 670 * @brief templated creation function, only makes the instance but 671 * doesn't add to the manager or group heirarchy 672 */ 673 template <class T> create()674 SGSharedPtr<T> create() 675 { 676 auto ref = create(T::staticSubsystemClassId()); 677 return dynamic_cast<T*>(ref.ptr()); 678 } 679 680 SGSubsystemRef create(const std::string& name); 681 682 template <class T> createInstance(const std::string & subsystemInstanceId)683 SGSharedPtr<T> createInstance(const std::string& subsystemInstanceId) 684 { 685 auto ref = createInstance(T::staticSubsystemClassId(), subsystemInstanceId); 686 return dynamic_cast<T*>(ref.ptr()); 687 } 688 689 SGSubsystemRef createInstance(const std::string& name, const std::string& subsystemInstanceId); 690 691 static GroupType defaultGroupFor(const char* name); 692 static double defaultUpdateIntervalFor(const char* name); 693 static const DependencyVec& dependsFor(const char* name); 694 695 /** 696 * @brief delegate to recieve notifications when the subsystem 697 * configuration changes. For any event/state change, a before (will) 698 * and after (did) change notifications are sent. 699 */ 700 class Delegate 701 { 702 public: 703 virtual void willChange(SGSubsystem* sub, SGSubsystem::State newState); 704 virtual void didChange(SGSubsystem* sub, SGSubsystem::State currentState); 705 }; 706 707 void addDelegate(Delegate * d); 708 void removeDelegate(Delegate * d); 709 710 /** 711 * @brief return a particular subsystem manager by name. Passing an 712 * empty string retrived the default/global subsystem manager, assuming it 713 * has been created. 714 */ 715 static SGSubsystemMgr* getManager(const std::string& id); 716 private: 717 friend class SGSubsystem; 718 friend class SGSubsystemGroup; 719 720 void notifyDelegatesWillChange(SGSubsystem* sub, State newState); 721 void notifyDelegatesDidChange(SGSubsystem* sub, State statee); 722 723 std::vector<SGSubsystemGroupRef> _groups; 724 unsigned int _initPosition = 0; 725 bool _destructorActive = false; 726 SGPropertyNode_ptr _rootNode; 727 728 // non-owning reference, this is to accelerate lookup 729 // by name which otherwise needs a full walk of the entire tree 730 using SubsystemDict = std::map<std::string, SGSubsystem*>; 731 mutable SubsystemDict _subsystemNameCache; 732 733 using DelegateVec = std::vector<Delegate*>; 734 DelegateVec _delegates; 735 }; 736 737 #endif // __SUBSYSTEM_MGR_HXX 738