You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
9.6 KiB
C++
363 lines
9.6 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
#include "EditorDefs.h"
|
|
|
|
#include "PluginManager.h"
|
|
|
|
// Qt
|
|
#include <QLibrary>
|
|
|
|
// Editor
|
|
#include "Include/IPlugin.h"
|
|
|
|
|
|
typedef IPlugin* (* TPfnCreatePluginInstance)(PLUGIN_INIT_PARAM* pInitParam);
|
|
typedef void (* TPfnQueryPluginSettings)(SPluginSettings&);
|
|
|
|
CPluginManager::CPluginManager()
|
|
{
|
|
m_currentUUID = 0;
|
|
}
|
|
|
|
CPluginManager::~CPluginManager()
|
|
{
|
|
ReleaseAllPlugins();
|
|
UnloadAllPlugins();
|
|
}
|
|
|
|
|
|
void CPluginManager::ReleaseAllPlugins()
|
|
{
|
|
CLogFile::WriteLine("[Plugin Manager] Releasing all previous plugins");
|
|
|
|
for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
|
{
|
|
if (it->pPlugin)
|
|
{
|
|
it->pPlugin->Release();
|
|
it->pPlugin = nullptr;
|
|
}
|
|
}
|
|
|
|
m_pluginEventMap.clear();
|
|
m_uuidPluginMap.clear();
|
|
}
|
|
|
|
void CPluginManager::UnloadAllPlugins()
|
|
{
|
|
CLogFile::WriteLine("[Plugin Manager] Unloading all previous plugins");
|
|
|
|
for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
|
{
|
|
if (it->pPlugin)
|
|
{
|
|
it->pPlugin->Release();
|
|
it->pPlugin = nullptr;
|
|
}
|
|
|
|
if (it->hLibrary)
|
|
{
|
|
it->hLibrary->unload();
|
|
delete it->hLibrary;
|
|
}
|
|
}
|
|
|
|
m_plugins.clear();
|
|
m_pluginEventMap.clear();
|
|
m_uuidPluginMap.clear();
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
IPlugin* SafeCallFactory(
|
|
TPfnCreatePluginInstance pfnFactory,
|
|
PLUGIN_INIT_PARAM* pInitParam,
|
|
const char* szFilePath)
|
|
{
|
|
IPlugin* pIPlugin = pfnFactory(pInitParam);
|
|
if (!pIPlugin)
|
|
{
|
|
if (AZ::Debug::Trace::IsDebuggerPresent())
|
|
{
|
|
AZ::Debug::Trace::Break();
|
|
}
|
|
CLogFile::FormatLine("Can't initialize plugin '%s'! Possible binary version incompatibility. Please reinstall this plugin.", szFilePath);
|
|
}
|
|
|
|
return pIPlugin;
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct SPlugin
|
|
{
|
|
QString m_path;
|
|
QString m_name;
|
|
};
|
|
|
|
// This does a topological sort on the plugin list. It will also remove plugins that have
|
|
// missing dependencies or there is a cycle in the dependency tree.
|
|
void SortPluginsByDependency(std::list<SPlugin>& plugins)
|
|
{
|
|
std::list<SPlugin> finalList;
|
|
std::set<QString, stl::less_stricmp<QString> > loadedPlugins;
|
|
|
|
while (!plugins.empty())
|
|
{
|
|
for (auto iter = plugins.begin(); iter != plugins.end(); )
|
|
{
|
|
finalList.push_back(*iter);
|
|
loadedPlugins.insert(iter->m_name);
|
|
iter = plugins.erase(iter);
|
|
}
|
|
}
|
|
plugins = finalList;
|
|
}
|
|
}
|
|
|
|
bool CPluginManager::LoadPlugins(const char* pPathWithMask)
|
|
{
|
|
QString strPath = QtUtil::ToQString(PathUtil::GetPath(pPathWithMask));
|
|
QString strMask = QString::fromUtf8(PathUtil::GetFile(pPathWithMask));
|
|
|
|
CLogFile::WriteLine("[Plugin Manager] Loading plugins...");
|
|
|
|
if (!QFileInfo::exists(strPath))
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot find plugin directory '%s'", strPath.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
std::list<SPlugin> plugins;
|
|
{
|
|
// LY_EDITOR_PLUGINS is defined by the CMakeLists.txt. The editor plugins add themselves to a variable that
|
|
// the editor uses to pass it to the build. Once a plugin is deleted, it will stop being in such variable producing
|
|
// the editor to not load that plugin anymore, even if it is in the output folder.
|
|
#if defined(LY_EDITOR_PLUGINS)
|
|
QDir qPath(strPath);
|
|
AZStd::vector<AZStd::string> tokens;
|
|
AZ::StringFunc::Tokenize(AZStd::string_view(LY_EDITOR_PLUGINS), tokens, ',');
|
|
for (const AZStd::string& token : tokens)
|
|
{
|
|
SPlugin plugin;
|
|
plugin.m_name = QString(token.c_str());
|
|
plugin.m_path = qPath.absoluteFilePath(plugin.m_name);
|
|
plugins.push_back(plugin);
|
|
}
|
|
#endif
|
|
}
|
|
if (plugins.size() == 0)
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot find any plugins in plugin directory '%s'", strPath.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
// Sort plugins by dependency
|
|
SortPluginsByDependency(plugins);
|
|
|
|
for (auto iter = plugins.begin(); iter != plugins.end(); ++iter)
|
|
{
|
|
// Load the plugin's DLL
|
|
QLibrary *hPlugin = new QLibrary(iter->m_path);
|
|
hPlugin->setLoadHints(QLibrary::DeepBindHint);
|
|
|
|
AZStd::string pathUtf8(iter->m_path.toUtf8().constData());
|
|
|
|
if (!hPlugin->load())
|
|
{
|
|
AZStd::string errorMsg(hPlugin->errorString().toUtf8().constData());
|
|
CLogFile::FormatLine("[Plugin Manager] Can't load plugin DLL '%s' message '%s' !", pathUtf8.c_str(), errorMsg.c_str());
|
|
delete hPlugin;
|
|
continue;
|
|
}
|
|
|
|
// Open 3D Engine:
|
|
// Query the plugin settings, check for manual load...
|
|
TPfnQueryPluginSettings pfnQuerySettings = reinterpret_cast<TPfnQueryPluginSettings>(hPlugin->resolve("QueryPluginSettings"));
|
|
|
|
if (pfnQuerySettings != nullptr)
|
|
{
|
|
SPluginSettings settings {
|
|
0
|
|
};
|
|
pfnQuerySettings(settings);
|
|
if (!settings.autoLoad)
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Skipping plugin DLL '%s' because it is marked as non-autoLoad!", pathUtf8.c_str());
|
|
hPlugin->unload();
|
|
delete hPlugin;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Query the factory pointer
|
|
TPfnCreatePluginInstance pfnFactory = reinterpret_cast<TPfnCreatePluginInstance>(hPlugin->resolve("CreatePluginInstance"));
|
|
|
|
if (!pfnFactory)
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot query plugin DLL '%s' factory pointer (is it a Sandbox plugin?)", pathUtf8.c_str());
|
|
hPlugin->unload();
|
|
delete hPlugin;
|
|
continue;
|
|
}
|
|
|
|
IPlugin* pPlugin = NULL;
|
|
PLUGIN_INIT_PARAM sInitParam =
|
|
{
|
|
GetIEditor(),
|
|
SANDBOX_PLUGIN_SYSTEM_VERSION,
|
|
IPlugin::eError_None
|
|
};
|
|
|
|
// Create an instance of the plugin
|
|
pPlugin = SafeCallFactory(pfnFactory, &sInitParam, iter->m_path.toUtf8().data());
|
|
if (!pPlugin)
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot initialize plugin '%s'! Possible binary version incompatibility. Please reinstall this plugin.", pathUtf8.c_str());
|
|
assert(pPlugin);
|
|
hPlugin->unload();
|
|
delete hPlugin;
|
|
continue;
|
|
}
|
|
|
|
if (!pPlugin)
|
|
{
|
|
if (sInitParam.outErrorCode == IPlugin::eError_VersionMismatch)
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Version mismatch. Please update the plugin.", pathUtf8.c_str());
|
|
}
|
|
else
|
|
{
|
|
CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Error code %u.", pathUtf8.c_str(), sInitParam.outErrorCode);
|
|
}
|
|
|
|
hPlugin->unload();
|
|
delete hPlugin;
|
|
|
|
continue;
|
|
}
|
|
|
|
RegisterPlugin(hPlugin, pPlugin);
|
|
|
|
// Write log string about plugin
|
|
CLogFile::FormatLine("[Plugin Manager] Successfully loaded plugin '%s', version '%i' (GUID: %s)",
|
|
pPlugin->GetPluginName(), pPlugin->GetPluginVersion(), pPlugin->GetPluginGUID());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CPluginManager::RegisterPlugin(QLibrary* dllHandle, IPlugin* pPlugin)
|
|
{
|
|
SPluginEntry entry;
|
|
|
|
entry.hLibrary = dllHandle;
|
|
entry.pPlugin = pPlugin;
|
|
m_plugins.push_back(entry);
|
|
m_uuidPluginMap[m_currentUUID] = pPlugin;
|
|
++m_currentUUID;
|
|
}
|
|
|
|
IPlugin* CPluginManager::GetPluginByGUID(const char* pGUID)
|
|
{
|
|
for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
|
{
|
|
const char* pPluginGuid = it->pPlugin->GetPluginGUID();
|
|
|
|
if (pPluginGuid && !strcmp(pPluginGuid, pGUID))
|
|
{
|
|
return it->pPlugin;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
IPlugin* CPluginManager::GetPluginByUIID(uint8 iUserInterfaceID)
|
|
{
|
|
TUIIDPluginIt it;
|
|
|
|
it = m_uuidPluginMap.find(iUserInterfaceID);
|
|
|
|
if (it == m_uuidPluginMap.end())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return (*it).second;
|
|
}
|
|
|
|
IUIEvent* CPluginManager::GetEventByIDAndPluginID(uint8 aPluginID, uint8 aEventID)
|
|
{
|
|
// Return the event interface of a user interface element which is
|
|
// specified by its ID and the user interface ID of the plugin which
|
|
// created the UI element
|
|
|
|
IPlugin* pPlugin = NULL;
|
|
TEventHandlerIt eventIt;
|
|
TPluginEventIt pluginIt;
|
|
|
|
pPlugin = GetPluginByUIID(aPluginID);
|
|
|
|
if (!pPlugin)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pluginIt = m_pluginEventMap.find(pPlugin);
|
|
|
|
if (pluginIt == m_pluginEventMap.end())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
eventIt = (*pluginIt).second.find(aEventID);
|
|
|
|
if (eventIt == (*pluginIt).second.end())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return (*eventIt).second;
|
|
}
|
|
|
|
bool CPluginManager::CanAllPluginsExitNow()
|
|
{
|
|
for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
|
{
|
|
if (it->pPlugin)
|
|
{
|
|
if (!it->pPlugin->CanExitNow())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CPluginManager::AddHandlerForCmdID(IPlugin* pPlugin, uint8 aCmdID, IUIEvent* pEvent)
|
|
{
|
|
m_pluginEventMap[pPlugin][aCmdID] = pEvent;
|
|
}
|
|
|
|
void CPluginManager::NotifyPlugins(EEditorNotifyEvent aEventId)
|
|
{
|
|
for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
|
|
{
|
|
if (it->pPlugin)
|
|
{
|
|
it->pPlugin->OnEditorNotify(aEventId);
|
|
}
|
|
}
|
|
}
|