PluginManager.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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* pPathWithMask)
  104. {
  105. QString strPath = PathUtil::GetPath(pPathWithMask).c_str();
  106. QString strMask = PathUtil::GetFile(pPathWithMask);
  107. CLogFile::WriteLine("[Plugin Manager] Loading plugins...");
  108. if (!QFileInfo::exists(strPath))
  109. {
  110. CLogFile::FormatLine("[Plugin Manager] Cannot find plugin directory '%s'", strPath.toUtf8().data());
  111. return false;
  112. }
  113. std::list<SPlugin> plugins;
  114. {
  115. // LY_EDITOR_PLUGINS is defined by the CMakeLists.txt. The editor plugins add themselves to a variable that
  116. // the editor uses to pass it to the build. Once a plugin is deleted, it will stop being in such variable producing
  117. // the editor to not load that plugin anymore, even if it is in the output folder.
  118. #if defined(LY_EDITOR_PLUGINS)
  119. QDir qPath(strPath);
  120. AZStd::vector<AZStd::string> tokens;
  121. AZ::StringFunc::Tokenize(AZStd::string_view(LY_EDITOR_PLUGINS), tokens, ',');
  122. for (const AZStd::string& token : tokens)
  123. {
  124. SPlugin plugin;
  125. plugin.m_name = QString(token.c_str());
  126. plugin.m_path = qPath.absoluteFilePath(plugin.m_name);
  127. plugins.push_back(plugin);
  128. }
  129. #endif
  130. }
  131. if (plugins.empty())
  132. {
  133. CLogFile::FormatLine("[Plugin Manager] Cannot find any plugins in plugin directory '%s'", strPath.toUtf8().data());
  134. return false;
  135. }
  136. // Sort plugins by dependency
  137. SortPluginsByDependency(plugins);
  138. for (auto iter = plugins.begin(); iter != plugins.end(); ++iter)
  139. {
  140. // Load the plugin's DLL
  141. QLibrary *hPlugin = new QLibrary(iter->m_path);
  142. hPlugin->setLoadHints(QLibrary::DeepBindHint);
  143. AZStd::string pathUtf8(iter->m_path.toUtf8().constData());
  144. if (!hPlugin->load())
  145. {
  146. AZStd::string errorMsg(hPlugin->errorString().toUtf8().constData());
  147. CLogFile::FormatLine("[Plugin Manager] Can't load plugin DLL '%s' message '%s' !", pathUtf8.c_str(), errorMsg.c_str());
  148. delete hPlugin;
  149. continue;
  150. }
  151. // Open 3D Engine:
  152. // Query the plugin settings, check for manual load...
  153. TPfnQueryPluginSettings pfnQuerySettings = reinterpret_cast<TPfnQueryPluginSettings>(hPlugin->resolve("QueryPluginSettings"));
  154. if (pfnQuerySettings != nullptr)
  155. {
  156. SPluginSettings settings {
  157. 0
  158. };
  159. pfnQuerySettings(settings);
  160. if (!settings.autoLoad)
  161. {
  162. CLogFile::FormatLine("[Plugin Manager] Skipping plugin DLL '%s' because it is marked as non-autoLoad!", pathUtf8.c_str());
  163. hPlugin->unload();
  164. delete hPlugin;
  165. continue;
  166. }
  167. }
  168. // Query the factory pointer
  169. TPfnCreatePluginInstance pfnFactory = reinterpret_cast<TPfnCreatePluginInstance>(hPlugin->resolve("CreatePluginInstance"));
  170. if (!pfnFactory)
  171. {
  172. CLogFile::FormatLine("[Plugin Manager] Cannot query plugin DLL '%s' factory pointer (is it a Sandbox plugin?)", pathUtf8.c_str());
  173. hPlugin->unload();
  174. delete hPlugin;
  175. continue;
  176. }
  177. IPlugin* pPlugin = nullptr;
  178. PLUGIN_INIT_PARAM sInitParam =
  179. {
  180. GetIEditor(),
  181. SANDBOX_PLUGIN_SYSTEM_VERSION,
  182. IPlugin::eError_None
  183. };
  184. // Create an instance of the plugin
  185. pPlugin = SafeCallFactory(pfnFactory, &sInitParam, iter->m_path.toUtf8().data());
  186. if (!pPlugin)
  187. {
  188. CLogFile::FormatLine("[Plugin Manager] Cannot initialize plugin '%s'! Possible binary version incompatibility. Please reinstall this plugin.", pathUtf8.c_str());
  189. assert(pPlugin);
  190. hPlugin->unload();
  191. delete hPlugin;
  192. continue;
  193. }
  194. if (!pPlugin)
  195. {
  196. if (sInitParam.outErrorCode == IPlugin::eError_VersionMismatch)
  197. {
  198. CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Version mismatch. Please update the plugin.", pathUtf8.c_str());
  199. }
  200. else
  201. {
  202. CLogFile::FormatLine("[Plugin Manager] Cannot create instance of plugin DLL '%s'! Error code %u.", pathUtf8.c_str(), sInitParam.outErrorCode);
  203. }
  204. hPlugin->unload();
  205. delete hPlugin;
  206. continue;
  207. }
  208. RegisterPlugin(hPlugin, pPlugin);
  209. // Write log string about plugin
  210. CLogFile::FormatLine("[Plugin Manager] Successfully loaded plugin '%s', version '%i' (GUID: %s)",
  211. pPlugin->GetPluginName(), pPlugin->GetPluginVersion(), pPlugin->GetPluginGUID());
  212. }
  213. return true;
  214. }
  215. void CPluginManager::RegisterPlugin(QLibrary* dllHandle, IPlugin* pPlugin)
  216. {
  217. SPluginEntry entry;
  218. entry.hLibrary = dllHandle;
  219. entry.pPlugin = pPlugin;
  220. m_plugins.push_back(entry);
  221. m_uuidPluginMap[static_cast<unsigned char>(m_currentUUID)] = pPlugin;
  222. ++m_currentUUID;
  223. }
  224. IPlugin* CPluginManager::GetPluginByGUID(const char* pGUID)
  225. {
  226. for (const SPluginEntry& pluginEntry : m_plugins)
  227. {
  228. const char* pPluginGuid = pluginEntry.pPlugin->GetPluginGUID();
  229. if (pPluginGuid && !strcmp(pPluginGuid, pGUID))
  230. {
  231. return pluginEntry.pPlugin;
  232. }
  233. }
  234. return nullptr;
  235. }
  236. IPlugin* CPluginManager::GetPluginByUIID(uint8 iUserInterfaceID)
  237. {
  238. TUIIDPluginIt it;
  239. it = m_uuidPluginMap.find(iUserInterfaceID);
  240. if (it == m_uuidPluginMap.end())
  241. {
  242. return nullptr;
  243. }
  244. return (*it).second;
  245. }
  246. IUIEvent* CPluginManager::GetEventByIDAndPluginID(uint8 aPluginID, uint8 aEventID)
  247. {
  248. // Return the event interface of a user interface element which is
  249. // specified by its ID and the user interface ID of the plugin which
  250. // created the UI element
  251. IPlugin* pPlugin = nullptr;
  252. TEventHandlerIt eventIt;
  253. TPluginEventIt pluginIt;
  254. pPlugin = GetPluginByUIID(aPluginID);
  255. if (!pPlugin)
  256. {
  257. return nullptr;
  258. }
  259. pluginIt = m_pluginEventMap.find(pPlugin);
  260. if (pluginIt == m_pluginEventMap.end())
  261. {
  262. return nullptr;
  263. }
  264. eventIt = (*pluginIt).second.find(aEventID);
  265. if (eventIt == (*pluginIt).second.end())
  266. {
  267. return nullptr;
  268. }
  269. return (*eventIt).second;
  270. }
  271. bool CPluginManager::CanAllPluginsExitNow()
  272. {
  273. for (const SPluginEntry& pluginEntry : m_plugins)
  274. {
  275. if (pluginEntry.pPlugin && !pluginEntry.pPlugin->CanExitNow())
  276. {
  277. return false;
  278. }
  279. }
  280. return true;
  281. }
  282. void CPluginManager::AddHandlerForCmdID(IPlugin* pPlugin, uint8 aCmdID, IUIEvent* pEvent)
  283. {
  284. m_pluginEventMap[pPlugin][aCmdID] = pEvent;
  285. }
  286. void CPluginManager::NotifyPlugins(EEditorNotifyEvent aEventId)
  287. {
  288. for (auto it = m_plugins.begin(); it != m_plugins.end(); ++it)
  289. {
  290. if (it->pPlugin)
  291. {
  292. it->pPlugin->OnEditorNotify(aEventId);
  293. }
  294. }
  295. }