476 lines
9.5 KiB
C++
476 lines
9.5 KiB
C++
/*
|
|
* ComponentManager.cpp
|
|
* --------------------
|
|
* Purpose: Manages loading of optional components.
|
|
* Notes : (currently none)
|
|
* Authors: Joern Heusipp
|
|
* OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "ComponentManager.h"
|
|
|
|
#include "mpt/mutex/mutex.hpp"
|
|
|
|
#include "Logging.h"
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
|
|
ComponentBase::ComponentBase(ComponentType type)
|
|
: m_Type(type)
|
|
, m_Initialized(false)
|
|
, m_Available(false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
ComponentBase::~ComponentBase()
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
void ComponentBase::SetInitialized()
|
|
{
|
|
m_Initialized = true;
|
|
}
|
|
|
|
|
|
void ComponentBase::SetAvailable()
|
|
{
|
|
m_Available = true;
|
|
}
|
|
|
|
|
|
ComponentType ComponentBase::GetType() const
|
|
{
|
|
return m_Type;
|
|
}
|
|
|
|
|
|
bool ComponentBase::IsInitialized() const
|
|
{
|
|
return m_Initialized;
|
|
}
|
|
|
|
|
|
bool ComponentBase::IsAvailable() const
|
|
{
|
|
return m_Initialized && m_Available;
|
|
}
|
|
|
|
|
|
mpt::ustring ComponentBase::GetVersion() const
|
|
{
|
|
return mpt::ustring();
|
|
}
|
|
|
|
|
|
void ComponentBase::Initialize()
|
|
{
|
|
if(IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
if(DoInitialize())
|
|
{
|
|
SetAvailable();
|
|
}
|
|
SetInitialized();
|
|
}
|
|
|
|
|
|
#if defined(MODPLUG_TRACKER)
|
|
|
|
|
|
ComponentLibrary::ComponentLibrary(ComponentType type)
|
|
: ComponentBase(type)
|
|
, m_BindFailed(false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
ComponentLibrary::~ComponentLibrary()
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
bool ComponentLibrary::AddLibrary(const std::string &libName, const mpt::LibraryPath &libPath)
|
|
{
|
|
if(m_Libraries[libName].IsValid())
|
|
{
|
|
// prefer previous
|
|
return true;
|
|
}
|
|
mpt::Library lib(libPath);
|
|
if(!lib.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
m_Libraries[libName] = lib;
|
|
return true;
|
|
}
|
|
|
|
|
|
void ComponentLibrary::ClearLibraries()
|
|
{
|
|
m_Libraries.clear();
|
|
}
|
|
|
|
|
|
void ComponentLibrary::SetBindFailed()
|
|
{
|
|
m_BindFailed = true;
|
|
}
|
|
|
|
|
|
void ComponentLibrary::ClearBindFailed()
|
|
{
|
|
m_BindFailed = false;
|
|
}
|
|
|
|
|
|
bool ComponentLibrary::HasBindFailed() const
|
|
{
|
|
return m_BindFailed;
|
|
}
|
|
|
|
|
|
mpt::Library ComponentLibrary::GetLibrary(const std::string &libName) const
|
|
{
|
|
const auto it = m_Libraries.find(libName);
|
|
if(it == m_Libraries.end())
|
|
{
|
|
return mpt::Library();
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
|
|
#endif // MODPLUG_TRACKER
|
|
|
|
|
|
#if MPT_COMPONENT_MANAGER
|
|
|
|
|
|
ComponentFactoryBase::ComponentFactoryBase(const std::string &id, const std::string &settingsKey)
|
|
: m_ID(id)
|
|
, m_SettingsKey(settingsKey)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
ComponentFactoryBase::~ComponentFactoryBase()
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
std::string ComponentFactoryBase::GetID() const
|
|
{
|
|
return m_ID;
|
|
}
|
|
|
|
|
|
std::string ComponentFactoryBase::GetSettingsKey() const
|
|
{
|
|
return m_SettingsKey;
|
|
}
|
|
|
|
|
|
void ComponentFactoryBase::PreConstruct() const
|
|
{
|
|
MPT_LOG_GLOBAL(LogInformation, "Components",
|
|
MPT_UFORMAT("Constructing Component {}")
|
|
( mpt::ToUnicode(mpt::Charset::ASCII, m_ID)
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
void ComponentFactoryBase::Initialize(ComponentManager &componentManager, std::shared_ptr<IComponent> component) const
|
|
{
|
|
if(componentManager.IsComponentBlocked(GetSettingsKey()))
|
|
{
|
|
return;
|
|
}
|
|
componentManager.InitializeComponent(component);
|
|
}
|
|
|
|
|
|
// Global list of component register functions.
|
|
// We do not use a global scope static list head because the corresponding
|
|
// mutex would be no POD type and would thus not be safe to be usable in
|
|
// zero-initialized state.
|
|
// Function scope static initialization is guaranteed to be thread safe
|
|
// in C++11.
|
|
// We use this implementation to be future-proof.
|
|
// MSVC currently does not exploit the possibility of using multiple threads
|
|
// for global lifetime object's initialization.
|
|
// An implementation with a simple global list head and no mutex at all would
|
|
// thus work fine for MSVC (currently).
|
|
|
|
static mpt::mutex & ComponentListMutex()
|
|
{
|
|
static mpt::mutex g_ComponentListMutex;
|
|
return g_ComponentListMutex;
|
|
}
|
|
|
|
static ComponentListEntry * & ComponentListHead()
|
|
{
|
|
static ComponentListEntry g_ComponentListHeadEmpty = {nullptr, nullptr};
|
|
static ComponentListEntry *g_ComponentListHead = &g_ComponentListHeadEmpty;
|
|
return g_ComponentListHead;
|
|
}
|
|
|
|
bool ComponentListPush(ComponentListEntry *entry)
|
|
{
|
|
mpt::lock_guard<mpt::mutex> guard(ComponentListMutex());
|
|
#if MPT_MSVC_BEFORE(2019,0)
|
|
// Guard against VS2017 compiler bug causing repeated initialization of inline variables.
|
|
// See <https://developercommunity.visualstudio.com/t/static-inline-variable-gets-destroyed-multiple-tim/297876>.
|
|
if(entry->next)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
entry->next = ComponentListHead();
|
|
ComponentListHead() = entry;
|
|
return true;
|
|
}
|
|
|
|
|
|
static std::shared_ptr<ComponentManager> g_ComponentManager;
|
|
|
|
|
|
void ComponentManager::Init(const IComponentManagerSettings &settings)
|
|
{
|
|
MPT_LOG_GLOBAL(LogInformation, "Components", U_("Init"));
|
|
// cannot use make_shared because the constructor is private
|
|
g_ComponentManager = std::shared_ptr<ComponentManager>(new ComponentManager(settings));
|
|
}
|
|
|
|
|
|
void ComponentManager::Release()
|
|
{
|
|
MPT_LOG_GLOBAL(LogInformation, "Components", U_("Release"));
|
|
g_ComponentManager = nullptr;
|
|
}
|
|
|
|
|
|
std::shared_ptr<ComponentManager> ComponentManager::Instance()
|
|
{
|
|
return g_ComponentManager;
|
|
}
|
|
|
|
|
|
ComponentManager::ComponentManager(const IComponentManagerSettings &settings)
|
|
: m_Settings(settings)
|
|
{
|
|
mpt::lock_guard<mpt::mutex> guard(ComponentListMutex());
|
|
for(ComponentListEntry *entry = ComponentListHead(); entry; entry = entry->next)
|
|
{
|
|
if(entry->reg)
|
|
{
|
|
entry->reg(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ComponentManager::Register(const IComponentFactory &componentFactory)
|
|
{
|
|
if(m_Components.find(componentFactory.GetID()) != m_Components.end())
|
|
{
|
|
return;
|
|
}
|
|
RegisteredComponent registeredComponent;
|
|
registeredComponent.settingsKey = componentFactory.GetSettingsKey();
|
|
registeredComponent.factoryMethod = componentFactory.GetStaticConstructor();
|
|
registeredComponent.instance = nullptr;
|
|
registeredComponent.weakInstance = std::weak_ptr<IComponent>();
|
|
m_Components.insert(std::make_pair(componentFactory.GetID(), registeredComponent));
|
|
}
|
|
|
|
|
|
void ComponentManager::Startup()
|
|
{
|
|
MPT_LOG_GLOBAL(LogDebug, "Components", U_("Startup"));
|
|
if(m_Settings.LoadOnStartup())
|
|
{
|
|
for(auto &it : m_Components)
|
|
{
|
|
it.second.instance = it.second.factoryMethod(*this);
|
|
it.second.weakInstance = it.second.instance;
|
|
}
|
|
}
|
|
if(!m_Settings.KeepLoaded())
|
|
{
|
|
for(auto &it : m_Components)
|
|
{
|
|
it.second.instance = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool ComponentManager::IsComponentBlocked(const std::string &settingsKey) const
|
|
{
|
|
if(settingsKey.empty())
|
|
{
|
|
return false;
|
|
}
|
|
return m_Settings.IsBlocked(settingsKey);
|
|
}
|
|
|
|
|
|
void ComponentManager::InitializeComponent(std::shared_ptr<IComponent> component) const
|
|
{
|
|
if(!component)
|
|
{
|
|
return;
|
|
}
|
|
if(component->IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
component->Initialize();
|
|
}
|
|
|
|
|
|
std::shared_ptr<const IComponent> ComponentManager::GetComponent(const IComponentFactory &componentFactory)
|
|
{
|
|
std::shared_ptr<IComponent> component = nullptr;
|
|
auto it = m_Components.find(componentFactory.GetID());
|
|
if(it != m_Components.end())
|
|
{ // registered component
|
|
if((*it).second.instance)
|
|
{ // loaded
|
|
component = (*it).second.instance;
|
|
} else
|
|
{ // not loaded
|
|
component = (*it).second.weakInstance.lock();
|
|
if(!component)
|
|
{
|
|
component = (*it).second.factoryMethod(*this);
|
|
}
|
|
if(m_Settings.KeepLoaded())
|
|
{ // keep the component loaded
|
|
(*it).second.instance = component;
|
|
}
|
|
(*it).second.weakInstance = component;
|
|
}
|
|
} else
|
|
{ // unregistered component
|
|
component = componentFactory.Construct(*this);
|
|
}
|
|
MPT_ASSERT(component);
|
|
return component;
|
|
}
|
|
|
|
|
|
std::shared_ptr<const IComponent> ComponentManager::ReloadComponent(const IComponentFactory &componentFactory)
|
|
{
|
|
std::shared_ptr<IComponent> component = nullptr;
|
|
auto it = m_Components.find(componentFactory.GetID());
|
|
if(it != m_Components.end())
|
|
{ // registered component
|
|
if((*it).second.instance)
|
|
{ // loaded
|
|
(*it).second.instance = nullptr;
|
|
if(!(*it).second.weakInstance.expired())
|
|
{
|
|
throw std::runtime_error("Component not completely unloaded. Cannot reload.");
|
|
}
|
|
(*it).second.weakInstance = std::weak_ptr<IComponent>();
|
|
}
|
|
// not loaded
|
|
component = (*it).second.factoryMethod(*this);
|
|
if(m_Settings.KeepLoaded())
|
|
{ // keep the component loaded
|
|
(*it).second.instance = component;
|
|
}
|
|
(*it).second.weakInstance = component;
|
|
} else
|
|
{ // unregistered component
|
|
component = componentFactory.Construct(*this);
|
|
}
|
|
MPT_ASSERT(component);
|
|
return component;
|
|
}
|
|
|
|
|
|
std::vector<std::string> ComponentManager::GetRegisteredComponents() const
|
|
{
|
|
std::vector<std::string> result;
|
|
result.reserve(m_Components.size());
|
|
for(const auto &it : m_Components)
|
|
{
|
|
result.push_back(it.first);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
ComponentInfo ComponentManager::GetComponentInfo(std::string name) const
|
|
{
|
|
ComponentInfo result;
|
|
result.name = name;
|
|
result.state = ComponentStateUnregistered;
|
|
result.settingsKey = "";
|
|
result.type = ComponentTypeUnknown;
|
|
const auto it = m_Components.find(name);
|
|
if(it == m_Components.end())
|
|
{
|
|
result.state = ComponentStateUnregistered;
|
|
return result;
|
|
}
|
|
result.settingsKey = it->second.settingsKey;
|
|
if(IsComponentBlocked(it->second.settingsKey))
|
|
{
|
|
result.state = ComponentStateBlocked;
|
|
return result;
|
|
}
|
|
std::shared_ptr<IComponent> component = it->second.instance;
|
|
if(!component)
|
|
{
|
|
component = it->second.weakInstance.lock();
|
|
}
|
|
if(!component)
|
|
{
|
|
result.state = ComponentStateUnintialized;
|
|
return result;
|
|
}
|
|
result.type = component->GetType();
|
|
if(!component->IsInitialized())
|
|
{
|
|
result.state = ComponentStateUnintialized;
|
|
return result;
|
|
}
|
|
if(!component->IsAvailable())
|
|
{
|
|
result.state = ComponentStateUnavailable;
|
|
return result;
|
|
}
|
|
result.state = ComponentStateAvailable;
|
|
return result;
|
|
}
|
|
|
|
|
|
mpt::PathString ComponentManager::GetComponentPath() const
|
|
{
|
|
return m_Settings.Path();
|
|
}
|
|
|
|
|
|
#endif // MPT_COMPONENT_MANAGER
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|