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.
o3de/Code/Editor/PluginManager.cpp

360 lines
9.7 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "PluginManager.h"
// Qt
#include <QLibrary>
// Editor
#include "Include/IPlugin.h"
using TPfnCreatePluginInstance = IPlugin* (*)(PLUGIN_INIT_PARAM* pInitParam);
using TPfnQueryPluginSettings = void (*)(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 = PathUtil::GetPath(pPathWithMask).c_str();
QString strMask = 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.empty())
{
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 = nullptr;
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[static_cast<unsigned char>(m_currentUUID)] = pPlugin;
++m_currentUUID;
}
IPlugin* CPluginManager::GetPluginByGUID(const char* pGUID)
{
for (const SPluginEntry& pluginEntry : m_plugins)
{
const char* pPluginGuid = pluginEntry.pPlugin->GetPluginGUID();
if (pPluginGuid && !strcmp(pPluginGuid, pGUID))
{
return pluginEntry.pPlugin;
}
}
return nullptr;
}
IPlugin* CPluginManager::GetPluginByUIID(uint8 iUserInterfaceID)
{
TUIIDPluginIt it;
it = m_uuidPluginMap.find(iUserInterfaceID);
if (it == m_uuidPluginMap.end())
{
return nullptr;
}
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 = nullptr;
TEventHandlerIt eventIt;
TPluginEventIt pluginIt;
pPlugin = GetPluginByUIID(aPluginID);
if (!pPlugin)
{
return nullptr;
}
pluginIt = m_pluginEventMap.find(pPlugin);
if (pluginIt == m_pluginEventMap.end())
{
return nullptr;
}
eventIt = (*pluginIt).second.find(aEventID);
if (eventIt == (*pluginIt).second.end())
{
return nullptr;
}
return (*eventIt).second;
}
bool CPluginManager::CanAllPluginsExitNow()
{
for (const SPluginEntry& pluginEntry : m_plugins)
{
if (pluginEntry.pPlugin && !pluginEntry.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);
}
}
}