Files
simgear/simgear/structure/subsystem_mgr.cxx
T
James Turner 8b4ace6fb8 Subsystems: stub code for smart add+remove
Groups track their state, which will enable them to correctly transition
added and removed children in the future. Only stubbed for now, to avoid
breakage on the FG side.
2018-04-25 22:20:12 +01:00

1013 lines
24 KiB
C++

// Written by David Megginson, started 2000-12
//
// Copyright (C) 2000 David Megginson, david@megginson.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include <simgear_config.h>
#include <algorithm>
#include <simgear/debug/logstream.hxx>
#include <simgear/timing/timestamp.hxx>
#include "exception.hxx"
#include "subsystem_mgr.hxx"
#include <simgear/props/props.hxx>
#include <simgear/math/SGMath.hxx>
#include "SGSmplstat.hxx"
const int SG_MAX_SUBSYSTEM_EXCEPTIONS = 4;
using std::string;
using State = SGSubsystem::State;
////////////////////////////////////////////////////////////////////////
// Implementation of SGSubsystem
////////////////////////////////////////////////////////////////////////
SGSubsystemTimingCb SGSubsystem::reportTimingCb = NULL;
void* SGSubsystem::reportTimingUserData = NULL;
SGSubsystem::SGSubsystem ()
{
}
SGSubsystem::~SGSubsystem ()
{
}
void
SGSubsystem::init ()
{
}
SGSubsystem::InitStatus
SGSubsystem::incrementalInit ()
{
init();
return INIT_DONE;
}
void
SGSubsystem::postinit ()
{
}
void
SGSubsystem::reinit ()
{
}
void
SGSubsystem::shutdown ()
{
}
void
SGSubsystem::bind ()
{
}
void
SGSubsystem::unbind ()
{
}
void
SGSubsystem::suspend ()
{
suspend(true);
}
void
SGSubsystem::suspend (bool suspended)
{
// important we don't use is_suspended() here since SGSubsystemGroup
// overries. We record the actual state correctly here, even for groups,
// so this works out, whereas is_suspended() is always false for groups
if (_suspended == suspended)
return;
const auto newState = suspended ? State::SUSPEND : State::RESUME;
const auto manager = get_manager();
if (manager)
manager->notifyDelegatesWillChange(this, newState);
_suspended = suspended;
if (manager)
manager->notifyDelegatesDidChange(this, newState);
}
void
SGSubsystem::resume ()
{
suspend(false);
}
bool
SGSubsystem::is_suspended () const
{
return _suspended;
}
void SGSubsystem::stamp(const string& name)
{
timingInfo.push_back(TimingInfo(name, SGTimeStamp::now()));
}
void SGSubsystem::set_name(const std::string &n)
{
assert(_name.empty());
_name = n;
}
void SGSubsystem::set_group(SGSubsystemGroup* group)
{
_group = group;
}
SGSubsystemGroup* SGSubsystem::get_group() const
{
return _group;
}
SGSubsystemMgr* SGSubsystem::get_manager() const
{
if (!get_group())
throw sg_exception("SGSubsystem::get_manager: subsystem " + name() + " has no group");
return get_group()->get_manager();
}
std::string SGSubsystem::nameForState(State s)
{
switch (s) {
case State::INVALID: return "invalid";
case State::INIT: return "init";
case State::REINIT: return "reinit";
case State::POSTINIT: return "post-init";
case State::SHUTDOWN: return "shutdown";
case State::BIND: return "bind";
case State::UNBIND: return "unbind";
case State::ADD: return "add";
case State::REMOVE: return "remove";
case State::SUSPEND: return "suspend";
case State::RESUME: return "resume";
}
return {};
}
////////////////////////////////////////////////////////////////////////
// Implementation of SGSubsystemGroup.
////////////////////////////////////////////////////////////////////////
class SGSubsystemGroup::Member
{
private:
Member (const Member &member);
public:
Member ();
~Member ();
void update (double delta_time_sec);
void reportTiming(void) { if (reportTimingCb) reportTimingCb(reportTimingUserData, name, &timeStat); }
void updateExecutionTime(double time) { timeStat += time;}
SampleStatistic timeStat;
std::string name;
SGSubsystemRef subsystem;
double min_step_sec;
double elapsed_sec;
bool collectTimeStats;
int exceptionCount;
int initTime;
};
SGSubsystemGroup::SGSubsystemGroup () :
_fixedUpdateTime(-1.0),
_updateTimeRemainder(0.0),
_initPosition(-1)
{
}
SGSubsystemGroup::~SGSubsystemGroup ()
{
clearSubsystems();
}
void
SGSubsystemGroup::init ()
{
assert(_state == State::BIND);
forEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::INIT);
s->init();
this->notifyDidChange(s, State::INIT);
});
_state = State::INIT;
}
SGSubsystem::InitStatus
SGSubsystemGroup::incrementalInit()
{
// special case this, simplifies the logic below
if (_members.empty()) {
return INIT_DONE;
}
// termination test
if (_initPosition >= static_cast<int>(_members.size())) {
_state = State::INIT;
return INIT_DONE;
}
if (_initPosition < 0) {
// first call
assert(_state == State::BIND);
_initPosition = 0;
notifyWillChange(_members.front()->subsystem, State::INIT);
}
const auto m = _members[_initPosition];
SGTimeStamp st;
st.stamp();
const InitStatus memberStatus = m->subsystem->incrementalInit();
m->initTime += st.elapsedMSec();
if (memberStatus == INIT_DONE) {
// complete init of this one
notifyDidChange(m->subsystem, State::INIT);
++_initPosition;
if (_initPosition < _members.size()) {
// start init of the next one
notifyWillChange( _members[_initPosition]->subsystem, State::INIT);
}
}
return INIT_CONTINUE;
}
void SGSubsystemGroup::forEach(std::function<void(SGSubsystem*)> f)
{
for (auto m : _members) {
f(m->subsystem);
}
}
void SGSubsystemGroup::reverseForEach(std::function<void(SGSubsystem*)> f)
{
for (auto it = _members.rbegin(); it != _members.rend(); ++it) {
f((*it)->subsystem);
}
}
void
SGSubsystemGroup::postinit ()
{
assert(_state == State::INIT);
forEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::POSTINIT);
s->postinit();
this->notifyDidChange(s, State::POSTINIT);
});
_state = State::POSTINIT;
}
void
SGSubsystemGroup::reinit ()
{
forEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::REINIT);
s->reinit();
this->notifyDidChange(s, State::REINIT);
});
}
void
SGSubsystemGroup::shutdown ()
{
reverseForEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::SHUTDOWN);
s->shutdown();
this->notifyDidChange(s, State::SHUTDOWN);
});
_state = State::SHUTDOWN;
_initPosition = -1;
}
void
SGSubsystemGroup::bind ()
{
forEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::BIND);
s->bind();
this->notifyDidChange(s, State::BIND);
});
_state = State::BIND;
}
void
SGSubsystemGroup::unbind ()
{
reverseForEach([this](SGSubsystem* s){
this->notifyWillChange(s, State::UNBIND);
s->unbind();
this->notifyDidChange(s, State::UNBIND);
});
_state = State::UNBIND;
}
void
SGSubsystemGroup::update (double delta_time_sec)
{
int loopCount = 1;
// if dt == 0.0, we are paused, so we need to run one iteration
// of our members; if we have a fixed update time, we compute a
// loop count, and locally adjust dt
if ((delta_time_sec > 0.0) && (_fixedUpdateTime > 0.0)) {
double localDelta = delta_time_sec + _updateTimeRemainder;
loopCount = SGMiscd::roundToInt(localDelta / _fixedUpdateTime);
loopCount = std::max(0, loopCount);
_updateTimeRemainder = delta_time_sec - (loopCount * _fixedUpdateTime);
delta_time_sec = _fixedUpdateTime;
}
const bool recordTime = (reportTimingCb != nullptr);
SGTimeStamp timeStamp;
while (loopCount-- > 0) {
for (auto member : _members) {
if (recordTime)
timeStamp = SGTimeStamp::now();
member->update(delta_time_sec); // indirect call
if (recordTime && reportTimingCb) {
timeStamp = SGTimeStamp::now() - timeStamp;
member->updateExecutionTime(timeStamp.toUSecs());
}
}
} // of multiple update loop
}
void
SGSubsystemGroup::reportTiming(void)
{
for (auto member : _members) {
member->reportTiming();
}
}
void
SGSubsystemGroup::suspend ()
{
SGSubsystem::suspend();
forEach([](SGSubsystem* s) { s->suspend(); });
}
void
SGSubsystemGroup::resume ()
{
forEach([](SGSubsystem* s) { s->resume(); });
SGSubsystem::resume();
}
string_list
SGSubsystemGroup::member_names() const
{
string_list result;
for (auto member : _members) {
result.push_back(member->name);
}
return result;
}
bool
SGSubsystemGroup::is_suspended () const
{
// important so suspended groups still count dt for their members
// but this does mean we need to be careful when notifying suspend
// and resume on groups
return false;
}
void
SGSubsystemGroup::set_subsystem (const string &name, SGSubsystem * subsystem,
double min_step_sec)
{
if (name.empty()) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "adding subsystem to group without a name");
// TODO, make this an exception in the future
} else if (name != subsystem->name()) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "adding subsystem to group with name '" << name
<< "', but name() returns '" << subsystem->name() << "'");
}
notifyWillChange(subsystem, State::ADD);
Member* member = get_member(name, true);
member->name = name;
member->subsystem = subsystem;
member->min_step_sec = min_step_sec;
subsystem->set_group(this);
notifyDidChange(subsystem, State::ADD);
if (_state != State::INVALID) {
SG_LOG(SG_GENERAL, SG_DEV_WARN, "TODO: implement SGSubsystemGroup transition when adding after init");
// transition to the correct state
// bind
// init
// post-init
}
}
void
SGSubsystemGroup::set_subsystem (SGSubsystem * subsystem, double min_step_sec)
{
set_subsystem(subsystem->name(), subsystem, min_step_sec);
}
SGSubsystem *
SGSubsystemGroup::get_subsystem (const string &name)
{
if (has_subsystem(name)) {
const Member* member = get_member(name, false);
return member->subsystem;
}
// recursive search
for (const auto& m : _members) {
if (m->subsystem->is_group()) {
auto s = static_cast<SGSubsystemGroup*>(m->subsystem.ptr())->get_subsystem(name);
if (s)
return s;
}
}
return {};
}
bool
SGSubsystemGroup::remove_subsystem(const string &name)
{
// direct membership
auto it = std::find_if(_members.begin(), _members.end(), [name](const Member* m)
{ return m->name == name; });
if (it != _members.end()) {
// found it, great
const auto sub = (*it)->subsystem;
if (_state != State::INVALID) {
// transition out correctly
SG_LOG(SG_GENERAL, SG_DEV_WARN, "TODO: implement SGSubsystemGroup transition when removing before shutdown");
// shutdown
// unbind
}
notifyWillChange(sub, State::REMOVE);
delete *it;
_members.erase(it);
notifyDidChange(sub, State::REMOVE);
return true;
}
// recursive removal
for (const auto& m : _members) {
if (m->subsystem->is_group()) {
auto group = static_cast<SGSubsystemGroup*>(m->subsystem.ptr());
bool ok = group->remove_subsystem(name);
if (ok) {
return true;
}
}
}
return false;
}
void
SGSubsystemGroup::notifyWillChange(SGSubsystem* sub, SGSubsystemMgr::State s)
{
auto manager = get_manager();
if (manager) {
manager->notifyDelegatesWillChange(sub, s);
}
}
void
SGSubsystemGroup::notifyDidChange(SGSubsystem* sub, SGSubsystemMgr::State s)
{
auto manager = get_manager();
if (manager) {
manager->notifyDelegatesDidChange(sub, s);
}
}
//------------------------------------------------------------------------------
void SGSubsystemGroup::clearSubsystems()
{
// reverse order to prevent order dependency problems
for (auto it = _members.rbegin(); it != _members.rend(); ++it)
{
// hold a ref here in case (as is very likely) this is the
// final ref to the subsystem, so that we can notify-did-change safely
SGSubsystemRef sub = (*it)->subsystem;
notifyWillChange(sub, State::REMOVE);
delete *it;
notifyDidChange(sub, State::REMOVE);
}
_members.clear();
}
void
SGSubsystemGroup::set_fixed_update_time(double dt)
{
_fixedUpdateTime = dt;
}
bool
SGSubsystemGroup::has_subsystem (const string &name) const
{
auto it = std::find_if(_members.begin(), _members.end(), [name](const Member* m)
{ return m->name == name; });
return it != _members.end();
}
auto SGSubsystemGroup::get_member(const string &name, bool create) -> Member*
{
auto it = std::find_if(_members.begin(), _members.end(), [name](const Member* m)
{ return m->name == name; });
if (it == _members.end()) {
if (!create)
return nullptr;
Member* m = new Member;
m->name = name;
_members.push_back(m);
return _members.back();
}
return *it;
}
SGSubsystemMgr* SGSubsystemGroup::get_manager() const
{
auto parentGroup = get_group();
if (parentGroup) {
return parentGroup->get_manager();
}
return _manager;
}
void SGSubsystemGroup::set_manager(SGSubsystemMgr *manager)
{
_manager = manager;
}
////////////////////////////////////////////////////////////////////////
// Implementation of SGSubsystemGroup::Member
////////////////////////////////////////////////////////////////////////
SGSubsystemGroup::Member::Member ()
: name(""),
subsystem(0),
min_step_sec(0),
elapsed_sec(0),
exceptionCount(0),
initTime(0)
{
}
// This shouldn't be called due to subsystem pointer ownership issues.
SGSubsystemGroup::Member::Member (const Member &)
{
}
SGSubsystemGroup::Member::~Member ()
{
}
void
SGSubsystemGroup::Member::update (double delta_time_sec)
{
elapsed_sec += delta_time_sec;
if (elapsed_sec < min_step_sec) {
return;
}
if (subsystem->is_suspended()) {
return;
}
try {
subsystem->update(elapsed_sec);
elapsed_sec = 0;
} catch (sg_exception& e) {
SG_LOG(SG_GENERAL, SG_ALERT, "caught exception processing subsystem:" << name
<< "\nmessage:" << e.getMessage());
if (++exceptionCount > SG_MAX_SUBSYSTEM_EXCEPTIONS) {
SG_LOG(SG_GENERAL, SG_ALERT, "(exceptionCount=" << exceptionCount <<
", suspending)");
subsystem->suspend();
}
}
}
////////////////////////////////////////////////////////////////////////
// Implementation of SGSubsystemMgr.
////////////////////////////////////////////////////////////////////////
SGSubsystemMgr::SGSubsystemMgr () :
_groups(MAX_GROUPS)
{
for (int i = 0; i < MAX_GROUPS; i++) {
auto g = new SGSubsystemGroup;
g->set_manager(this);
_groups[i].reset(g);
}
}
SGSubsystemMgr::~SGSubsystemMgr ()
{
_destructorActive = true;
_groups.clear();
}
void
SGSubsystemMgr::init ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->init();
}
SGSubsystem::InitStatus
SGSubsystemMgr::incrementalInit()
{
if (_initPosition >= MAX_GROUPS)
return INIT_DONE;
InitStatus memberStatus = _groups[_initPosition]->incrementalInit();
if (memberStatus == INIT_DONE)
++_initPosition;
return INIT_CONTINUE;
}
void
SGSubsystemMgr::postinit ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->postinit();
}
void
SGSubsystemMgr::reinit ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->reinit();
}
void
SGSubsystemMgr::shutdown ()
{
// reverse order to prevent order dependency problems
for (int i = MAX_GROUPS-1; i >= 0; i--)
_groups[i]->shutdown();
_initPosition = 0;
}
void
SGSubsystemMgr::bind ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->bind();
}
void
SGSubsystemMgr::unbind ()
{
// reverse order to prevent order dependency problems
for (int i = MAX_GROUPS-1; i >= 0; i--)
_groups[i]->unbind();
}
void
SGSubsystemMgr::update (double delta_time_sec)
{
for (int i = 0; i < MAX_GROUPS; i++) {
_groups[i]->update(delta_time_sec);
}
}
void
SGSubsystemMgr::suspend ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->suspend();
}
void
SGSubsystemMgr::resume ()
{
for (int i = 0; i < MAX_GROUPS; i++)
_groups[i]->resume();
}
bool
SGSubsystemMgr::is_suspended () const
{
return false;
}
void
SGSubsystemMgr::add (const char * name, SGSubsystem * subsystem,
GroupType group, double min_time_sec)
{
if (get_subsystem(name) != nullptr)
throw sg_exception("Duplicate add of subsystem: " + std::string(name));
SG_LOG(SG_GENERAL, SG_DEBUG, "Adding subsystem " << name);
get_group(group)->set_subsystem(name, subsystem, min_time_sec);
}
bool
SGSubsystemMgr::remove(const char* name)
{
// drop the cache
_subsystemNameCache.clear();
// we don't know which group the subsystem belongs too
// fortunately this is a very infrequently used code path, so the slow
// search is not a problem
for (auto group : _groups) {
bool didRemove = group->remove_subsystem(name);
if (didRemove) {
return true;
}
} // of groups iteration
SG_LOG(SG_GENERAL, SG_WARN, "SGSubsystemMgr::remove: not found: " << name);
return false;
}
SGSubsystemGroup *
SGSubsystemMgr::get_group (GroupType group)
{
return _groups[group];
}
SGSubsystem *
SGSubsystemMgr::get_subsystem (const string &name) const
{
if (_destructorActive)
return nullptr;
auto s =_subsystemNameCache.find(name);
if (s != _subsystemNameCache.end()) {
// in the cache, excellent
return s->second;
}
for (auto g : _groups) {
auto sub = g->get_subsystem(name);
if (sub) {
// insert into the cache
_subsystemNameCache[name] = sub;
return sub;
}
}
return nullptr;
}
SGSubsystem*
SGSubsystemMgr::get_subsystem(const std::string &name, const std::string& instanceName) const
{
return get_subsystem(name + "-" + instanceName);
}
/** Trigger the timing callback to report data for all subsystems. */
void
SGSubsystemMgr::reportTiming()
{
for (int i = 0; i < MAX_GROUPS; i++) {
_groups[i]->reportTiming();
} // of groups iteration
}
// anonymous namespace to hold registration informatipno
namespace {
struct RegisteredSubsystemData
{
RegisteredSubsystemData(const std::string& aName, bool aInstanced,
SGSubsystemMgr::SubsystemFactoryFunctor aFunctor,
SGSubsystemMgr::GroupType aGroup,
double aInterval) :
name(aName),
instanced(aInstanced),
functor(aFunctor),
defaultGroup(aGroup),
defaultUpdateInterval(aInterval)
{
}
std::string name;
bool instanced = false;
SGSubsystemMgr::SubsystemFactoryFunctor functor;
SGSubsystemMgr::GroupType defaultGroup;
double defaultUpdateInterval = 0.0;
SGSubsystemMgr::DependencyVec depends;
};
using SybsystemRegistrationVec = std::vector<RegisteredSubsystemData>;
SybsystemRegistrationVec global_registrations;
SybsystemRegistrationVec::const_iterator findRegistration(const std::string& name)
{
auto it = std::find_if(global_registrations.begin(),
global_registrations.end(),
[name](const RegisteredSubsystemData& d)
{ return name == d.name; });
return it;
}
} // of anonymous namespace
void SGSubsystemMgr::registerSubsystem(const std::string& name,
SubsystemFactoryFunctor f,
GroupType group,
bool instanced,
double updateInterval,
std::initializer_list<Dependency> deps)
{
if (findRegistration(name) != global_registrations.end()) {
throw sg_exception("duplicate subsystem registration for: " + name);
}
global_registrations.push_back({name, instanced, f, group, updateInterval});
if (deps.size() > 0) {
global_registrations.back().depends = deps;
}
}
auto SGSubsystemMgr::defaultGroupFor(const char* name) -> GroupType
{
auto it = findRegistration(name);
if (it == global_registrations.end()) {
throw sg_exception("unknown subsystem registration for: " + std::string(name));
}
return it->defaultGroup;
}
double SGSubsystemMgr::defaultUpdateIntervalFor(const char* name)
{
auto it = findRegistration(name);
if (it == global_registrations.end()) {
throw sg_exception("unknown subsystem registration for: " + std::string(name));
}
return it->defaultUpdateInterval;
}
const SGSubsystemMgr::DependencyVec&
SGSubsystemMgr::dependsFor(const char* name)
{
auto it = findRegistration(name);
if (it == global_registrations.end()) {
throw sg_exception("unknown subsystem registration for: " + std::string(name));
}
return it->depends;
}
SGSubsystemRef
SGSubsystemMgr::create(const std::string& name)
{
auto it = findRegistration(name);
if (it == global_registrations.end()) {
return {}; // or should this throw with a 'not registered'?
}
if (it->instanced) {
throw sg_exception("SGSubsystemMgr::create: using non-instanced mode for instanced subsytem");
}
SGSubsystemRef ref = it->functor();
if (!ref) {
throw sg_exception("SGSubsystemMgr::create: functor failed to create subsystem implementation: " + name);
}
ref->set_name(name);
return ref;
}
SGSubsystemRef
SGSubsystemMgr::createInstance(const std::string& name, const std::string& instanceName)
{
auto it = findRegistration(name);
if (it == global_registrations.end()) {
return {}; // or should this throw with a 'not registered'?
}
if (!it->instanced) {
throw sg_exception("SGSubsystemMgr::create: using instanced mode for non-instanced subsytem");
}
SGSubsystemRef ref = it->functor();
if (!ref) {
throw sg_exception("SGSubsystemMgr::create: functor failed to create an instsance of " + name);
}
const auto combinedName = name + "-" + instanceName;
ref->set_name(combinedName);
return ref;
}
void SGSubsystemMgr::Delegate::willChange(SGSubsystem*, State)
{
}
void SGSubsystemMgr::Delegate::didChange(SGSubsystem*, State)
{
}
void SGSubsystemMgr::addDelegate(Delegate * d)
{
assert(d);
_delegates.push_back(d);
}
void SGSubsystemMgr::removeDelegate(Delegate * d)
{
assert(d);
auto it = std::find(_delegates.begin(), _delegates.end(), d);
if (it == _delegates.end()) {
SG_LOG(SG_GENERAL, SG_DEV_ALERT, "removeDelegate: unknown delegate");
return;
}
_delegates.erase(it);
}
void SGSubsystemMgr::notifyDelegatesWillChange(SGSubsystem* sub, State newState)
{
std::for_each(_delegates.begin(), _delegates.end(), [sub, newState](Delegate* d)
{ d->willChange(sub, newState); });
}
void SGSubsystemMgr::notifyDelegatesDidChange(SGSubsystem* sub, State state)
{
std::for_each(_delegates.begin(), _delegates.end(), [sub, state](Delegate* d)
{ d->didChange(sub, state); });
}
void SGSubsystemMgr::set_root_node(SGPropertyNode_ptr node)
{
_rootNode = node;
}
SGPropertyNode_ptr
SGSubsystemMgr::root_node() const
{
return _rootNode;
}
// end of subsystem_mgr.cxx