PluginManager.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "PluginManager.h"
  10. // Qt
  11. #include <QLibrary>
  12. // Editor
  13. #include "Include/IPlugin.h"
  14. using TPfnCreatePluginInstance = IPlugin* (*)(PLUGIN_INIT_PARAM* pInitParam);
  15. using TPfnQueryPluginSettings = void (*)(SPluginSettings&);
  16. CPluginManager::CPluginManager()
  17. {
  18. m_currentUUID = 0;
  19. }
  20. CPluginManager::~CPluginManager()
  21. {
  22. ReleaseAllPlugins();
  23. UnloadAllPlugins();
  24. }
  25. void CPluginManager::ReleaseAllPlugins()
  26. {
  27. CLogFile::WriteLine("[Plugin Manager] Releasing all previous plugins");
  28. for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
  29. {
  30. if (it->pPlugin)
  31. {
  32. it->pPlugin->Release();
  33. it->pPlugin = nullptr;
  34. }
  35. }
  36. m_pluginEventMap.clear();
  37. m_uuidPluginMap.clear();
  38. }
  39. void CPluginManager::UnloadAllPlugins()
  40. {
  41. CLogFile::WriteLine("[Plugin Manager] Unloading all previous plugins");
  42. for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
  43. {
  44. if (it->pPlugin)
  45. {
  46. it->pPlugin->Release();
  47. it->pPlugin = nullptr;
  48. }
  49. if (it->hLibrary)
  50. {
  51. it->hLibrary->unload();
  52. delete it->hLibrary;
  53. }
  54. }
  55. m_plugins.clear();
  56. m_pluginEventMap.clear();
  57. m_uuidPluginMap.clear();
  58. }
  59. namespace
  60. {
  61. IPlugin* SafeCallFactory(
  62. TPfnCreatePluginInstance pfnFactory,
  63. PLUGIN_INIT_PARAM* pInitParam,
  64. const char* szFilePath)
  65. {
  66. IPlugin* pIPlugin = pfnFactory(pInitParam);
  67. if (!pIPlugin)
  68. {
  69. if (AZ::Debug::Trace::Instance().IsDebuggerPresent())
  70. {
  71. AZ::Debug::Trace::Instance().Break();
  72. }
  73. CLogFile::FormatLine("Can't initialize plugin '%s'! Possible binary version incompatibility. Please reinstall this plugin.", szFilePath);
  74. }
  75. return pIPlugin;
  76. }
  77. }
  78. namespace
  79. {
  80. struct SPlugin
  81. {
  82. QString m_path;
  83. QString m_name;
  84. };
  85. // This does a topological sort on the plugin list. It will also remove plugins that have
  86. // missing dependencies or there is a cycle in the dependency tree.
  87. void SortPluginsByDependency(std::list<SPlugin>& plugins)
  88. {
  89. std::list<SPlugin> finalList;
  90. std::set<QString, stl::less_stricmp<QString> > loadedPlugins;
  91. while (!plugins.empty())
  92. {
  93. for (auto iter = plugins.begin(); iter != plugins.end(); )
  94. {
  95. finalList.push_back(*iter);
  96. loadedPlugins.insert(iter->m_name);
  97. iter = plugins.erase(iter);
  98. }
  99. }
  100. plugins = finalList;
  101. }
  102. }
  103. bool CPluginManager::LoadPlugins(const char* pluginsPath)
  104. {
  105. QString strPath{ pluginsPath };
  106. CLogFile::WriteLine("[Plugin Manager] Loading plugins...");
  107. if (!QFileInfo::exists(strPath))
  108. {
  109. CLogFile::FormatLine("[Plugin Manager] Cannot find plugin directory '%s'", strPath.toUtf8().data());
  110. return false;
  111. }
  112. std::list<SPlugin> plugins;
  113. {
  114. // LY_EDITOR_PLUGINS is defined by the CMakeLists.txt. The editor plugins add themselves to a variable that
  115. // the editor uses to pass it to the build. Once a plugin is deleted, it will stop being in such variable producing
  116. // the editor to not load that plugin anymore, even if it is in the output folder.
  117. #if defined(LY_EDITOR_PLUGINS)
  118. QDir qPath(strPath);
  119. AZStd::vector<AZStd::string> tokens;
  120. AZ::StringFunc::Tokenize(AZStd::string_view(LY_EDITOR_PLUGINS), tokens, ',');
  121. for (const AZStd::string& token : tokens)
  122. {
  123. SPlugin plugin;
  124. plugin.m_name = QString(token.c_str());
  125. plugin.m_path = qPath.absoluteFilePath(plugin.m_name);
  126. plugins.push_back(plugin);
  127. }
  128. #endif
  129. }
  130. if (plugins.empty())
  131. {
  132. CLogFile::FormatLine("[Plugin Manager] Cannot find any plugins in plugin directory '%s'", strPath.toUtf8().data());
  133. return false;
  134. }
  135. // Sort plugins by dependency
  136. SortPluginsByDependency(plugins);
  137. for (auto iter = plugins.begin(); iter != plugins.end(); ++iter)
  138. {
  139. // Load the plugin's DLL
  140. QLibrary *hPlugin = new QLibrary(iter->m_path);
  141. hPlugin->setLoadHints(QLibrary::DeepBindHint);
  142. AZStd::string pathUtf8(iter->m_path.toUtf8().constData());
  143. if (!hPlugin->load())
  144. {
  145. AZStd::string errorMsg(hPlugin->errorString().toUtf8().constData());
  146. CLogFile::FormatLine("[Plugin Manager] Can't load plugin DLL '%s' message '%s' !", pathUtf8.c_str(), errorMsg.c_str());
  147. delete hPlugin;
  148. continue;
  149. }
  150. // Open 3D Engine:
  151. // Query the plugin settings, check for manual load...
  152. TPfnQueryPluginSettings pfnQuerySettings = reinterpret_cast<TPfnQueryPluginSettings>(hPlugin->resolve("QueryPluginSettings"));
  153. if (pfnQuerySettings != nullptr)
  154. {
  155. SPluginSettings settings {
  156. 0
  157. };
  158. pfnQuerySettings(settings);
  159. if (!settings.autoLoad)
  160. {
  161. CLogFile::FormatLine("[Plugin Manager] Skipping plugin DLL '%s' because it is marked as non-autoLoad!", pathUtf8.c_str());
  162. hPlugin->unload();
  163. delete hPlugin;
  164. continue;
  165. }
  166. }
  167. // Query the factory pointer
  168. TPfnCreatePluginInstance pfnFactory = reinterpret_cast<TPfnCreatePluginInstance>(hPlugin->resolve("CreatePluginInstance"));
  169. if (!pfnFactory)
  170. {
  171. CLogFile::FormatLine("[Plugin Manager] Cannot query plugin DLL '%s' factory pointer (is it a Sandbox plugin?)", pathUtf8.c_str());
  172. hPlugin->unload();
  173. delete hPlugin;
  174. continue;
  175. }
  176. IPlugin* pPlugin = nullptr;
  177. PLUGIN_INIT_PARAM sInitParam =
  178. {
  179. GetIEditor(),
  180. SANDBOX_PLUGIN_SYSTEM_VERSION,
  181. IPlugin::eError_None
  182. };
  183. // Create an instance of the plugin
  184. pPlugin = SafeCallFactory(pfnFactory, &sInitParam, iter->m_path.toUtf8().data());
  185. if (!pPlugin)
  186. {
  187. CLogFile::FormatLine("[Plugin Manager] Cannot initialize plugin '%s'! Possible binary version incompatibility. Please reinstall this plugin.", pathUtf8.c_str());
  188. assert(pPlugin);
  189. hPlugin->unload();
  190. delete hPlugin;
  191. continue;
  192. }
  193. if (!pPlugin)
  194. {
  195. if (sInitParam.outErrorCode == IPlugin::eError_VersionMismatch)
  196. {
  197. CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Version mismatch. Please update the plugin.", pathUtf8.c_str());
  198. }
  199. else
  200. {
  201. CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Error code %u.", pathUtf8.c_str(), sInitParam.outErrorCode);
  202. }
  203. hPlugin->unload();
  204. delete hPlugin;
  205. continue;
  206. }
  207. RegisterPlugin(hPlugin, pPlugin);
  208. // Write log string about plugin
  209. CLogFile::FormatLine("[Plugin Manager] Successfully loaded plugin '%s', version '%i' (GUID: %s)",
  210. pPlugin->GetPluginName(), pPlugin->GetPluginVersion(), pPlugin->GetPluginGUID());
  211. }
  212. return true;
  213. }
  214. void CPluginManager::RegisterPlugin(QLibrary* dllHandle, IPlugin* pPlugin)
  215. {
  216. SPluginEntry entry;
  217. entry.hLibrary = dllHandle;
  218. entry.pPlugin = pPlugin;
  219. m_plugins.push_back(entry);
  220. m_uuidPluginMap[static_cast<unsigned char>(m_currentUUID)] = pPlugin;
  221. ++m_currentUUID;
  222. }
  223. IPlugin* CPluginManager::GetPluginByGUID(const char* pGUID)
  224. {
  225. for (const SPluginEntry& pluginEntry : m_plugins)
  226. {
  227. const char* pPluginGuid = pluginEntry.pPlugin->GetPluginGUID();
  228. if (pPluginGuid && !strcmp(pPluginGuid, pGUID))
  229. {
  230. return pluginEntry.pPlugin;
  231. }
  232. }
  233. return nullptr;
  234. }
  235. IPlugin* CPluginManager::GetPluginByUIID(uint8 iUserInterfaceID)
  236. {
  237. TUIIDPluginIt it;
  238. it = m_uuidPluginMap.find(iUserInterfaceID);
  239. if (it == m_uuidPluginMap.end())
  240. {
  241. return nullptr;
  242. }
  243. return (*it).second;
  244. }
  245. IUIEvent* CPluginManager::GetEventByIDAndPluginID(uint8 aPluginID, uint8 aEventID)
  246. {
  247. // Return the event interface of a user interface element which is
  248. // specified by its ID and the user interface ID of the plugin which
  249. // created the UI element
  250. IPlugin* pPlugin = nullptr;
  251. TEventHandlerIt eventIt;
  252. TPluginEventIt pluginIt;
  253. pPlugin = GetPluginByUIID(aPluginID);
  254. if (!pPlugin)
  255. {
  256. return nullptr;
  257. }
  258. pluginIt = m_pluginEventMap.find(pPlugin);
  259. if (pluginIt == m_pluginEventMap.end())
  260. {
  261. return nullptr;
  262. }
  263. eventIt = (*pluginIt).second.find(aEventID);
  264. if (eventIt == (*pluginIt).second.end())
  265. {
  266. return nullptr;
  267. }
  268. return (*eventIt).second;
  269. }
  270. bool CPluginManager::CanAllPluginsExitNow()
  271. {
  272. for (const SPluginEntry& pluginEntry : m_plugins)
  273. {
  274. if (pluginEntry.pPlugin && !pluginEntry.pPlugin->CanExitNow())
  275. {
  276. return false;
  277. }
  278. }
  279. return true;
  280. }
  281. void CPluginManager::AddHandlerForCmdID(IPlugin* pPlugin, uint8 aCmdID, IUIEvent* pEvent)
  282. {
  283. m_pluginEventMap[pPlugin][aCmdID] = pEvent;
  284. }
  285. void CPluginManager::NotifyPlugins(EEditorNotifyEvent aEventId)
  286. {
  287. for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
  288. {
  289. if (it->pPlugin)
  290. {
  291. it->pPlugin->OnEditorNotify(aEventId);
  292. }
  293. }
  294. }