// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "PluginManager.h" #include "FlaxEngine.Gen.h" #include "GamePlugin.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Scripting/ManagedCLR/MAssembly.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Engine/EngineService.h" #include "Engine/Core/Log.h" #include "Engine/Scripting/ManagedCLR/MField.h" Plugin::Plugin(const SpawnParams& params) : ScriptingObject(params) { _description.Name = String(GetType().GetName()); _description.Category = TEXT("Other"); _description.Version = Version(1, 0); } GamePlugin::GamePlugin(const SpawnParams& params) : Plugin(params) { } #if USE_EDITOR #include "EditorPlugin.h" #include "Engine/Scripting/ManagedCLR/MException.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" EditorPlugin::EditorPlugin(const SpawnParams& params) : Plugin(params) { } void EditorPlugin::Initialize() { Plugin::Initialize(); MObject* exception = nullptr; TypeInitializer.GetType().ManagedClass->GetMethod("Initialize_Internal")->Invoke(GetOrCreateManagedInstance(), nullptr, &exception); if (exception) { MException ex(exception); ex.Log(LogType::Error,TEXT("EditorPlugin")); } } void EditorPlugin::Deinitialize() { MObject* exception = nullptr; TypeInitializer.GetType().ManagedClass->GetMethod("Deinitialize_Internal")->Invoke(GetOrCreateManagedInstance(), nullptr, &exception); if (exception) { MException ex(exception); ex.Log(LogType::Error,TEXT("EditorPlugin")); } Plugin::Deinitialize(); } #endif Delegate PluginManager::PluginLoading; Delegate PluginManager::PluginLoaded; Delegate PluginManager::PluginUnloading; Delegate PluginManager::PluginUnloaded; Action PluginManager::PluginsChanged; namespace PluginManagerImpl { Array GamePlugins; Array EditorPlugins; void LoadPlugin(MClass* klass, bool isEditor); void OnAssemblyLoaded(MAssembly* assembly); void OnAssemblyUnloading(MAssembly* assembly); void OnBinaryModuleLoaded(BinaryModule* module); void OnScriptsReloading(); Array SortGamePlugins(Array& plugins, MClass* pluginLoadOrderAttribute, MField* typeField); Array SortPlugins(Array plugins, MClass* pluginLoadOrderAttribute, MField* typeField); } using namespace PluginManagerImpl; class PluginManagerService : public EngineService { public: PluginManagerService() : EngineService(TEXT("Plugin Manager"), 100) { } bool Init() override; void Dispose() override; static void InvokeInitialize(Plugin* plugin); static void InvokeDeinitialize(Plugin* plugin); }; PluginManagerService PluginManagerServiceInstance; void PluginManagerService::InvokeInitialize(Plugin* plugin) { if (plugin->_initialized) return; StringAnsiView typeName = plugin->GetType().GetName(); PROFILE_CPU(); ZoneName(typeName.Get(), typeName.Length()); LOG(Info, "Loading plugin {}", plugin->ToString()); PluginManager::PluginLoading(plugin); plugin->Initialize(); plugin->_initialized = true; PluginManager::PluginLoaded(plugin); } void PluginManagerService::InvokeDeinitialize(Plugin* plugin) { if (!plugin->_initialized) return; StringAnsiView typeName = plugin->GetType().GetName(); PROFILE_CPU(); ZoneName(typeName.Get(), typeName.Length()); LOG(Info, "Unloading plugin {}", plugin->ToString()); PluginManager::PluginUnloading(plugin); plugin->Deinitialize(); plugin->_initialized = false; PluginManager::PluginUnloaded(plugin); } void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor) { // Create and check if use it auto plugin = (Plugin*)Scripting::NewObject(klass); if (!plugin) return; if (!isEditor) { GamePlugins.Add((GamePlugin*)plugin); } #if USE_EDITOR else { EditorPlugins.Add(plugin); } #endif PluginManager::PluginsChanged(); } void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly) { PROFILE_CPU_NAMED("Load Assembly Plugins"); const auto gamePluginClass = GamePlugin::GetStaticClass(); if (gamePluginClass == nullptr) { LOG(Warning, "Missing GamePlugin class."); return; } #if USE_EDITOR const auto editorPluginClass = ((ManagedBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.EditorPlugin"); if (editorPluginClass == nullptr) { LOG(Warning, "Missing EditorPlugin class."); return; } #endif // Process all classes to find plugins auto& classes = assembly->GetClasses(); for (auto i = classes.Begin(); i.IsNotEnd(); ++i) { auto mclass = i->Value; // Skip invalid classes if (mclass->IsGeneric() || mclass->IsStatic() || mclass->IsAbstract()) continue; if (mclass->IsSubClassOf(gamePluginClass)) { LoadPlugin(mclass, false); } #if USE_EDITOR if (mclass->IsSubClassOf(editorPluginClass)) { LoadPlugin(mclass, true); } #endif } } void PluginManagerImpl::OnAssemblyUnloading(MAssembly* assembly) { bool changed = false; auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); ASSERT(beforeTypeField); auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--) { auto plugin = editorPlugins[i]; if (plugin->GetType().ManagedClass->GetAssembly() == assembly) { PluginManagerService::InvokeDeinitialize(plugin); EditorPlugins.Remove(plugin); changed = true; } } auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--) { auto plugin = gamePlugins[i]; if (plugin->GetType().ManagedClass->GetAssembly() == assembly) { PluginManagerService::InvokeDeinitialize(plugin); GamePlugins.Remove(plugin); changed = true; } } if (changed) PluginManager::PluginsChanged(); } void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module) { // Skip special modules if (module == GetBinaryModuleFlaxEngine() || module == GetBinaryModuleCorlib()) return; // Skip non-managed modules // TODO: search native-only modules too auto managedModule = dynamic_cast(module); if (!managedModule) return; // Process already loaded C# assembly if (managedModule->Assembly->IsLoaded()) { OnAssemblyLoaded(managedModule->Assembly); } // Track C# assembly changes managedModule->Assembly->Loaded.Bind(OnAssemblyLoaded); managedModule->Assembly->Unloading.Bind(OnAssemblyUnloading); } void PluginManagerImpl::OnScriptsReloading() { // When scripting is reloading (eg. for hot-reload in Editor) we have to deinitialize plugins (Scripting service destroys C# objects later on) bool changed = false; auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); ASSERT(beforeTypeField); auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--) { auto plugin = editorPlugins[i]; { PluginManagerService::InvokeDeinitialize(plugin); EditorPlugins.Remove(plugin); changed = true; } } auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--) { auto plugin = gamePlugins[i]; { PluginManagerService::InvokeDeinitialize(plugin); GamePlugins.Remove(plugin); changed = true; } } if (changed) PluginManager::PluginsChanged(); } Array PluginManagerImpl::SortGamePlugins(Array& plugins, MClass* pluginLoadOrderAttribute, MField* typeField) { // Sort game plugins Array newPlugins; for(int i = 0; i < plugins.Count(); i++) { GamePlugin* plugin = plugins[i]; // Sort game plugin as needed int insertIndex = -1; for(int j = 0; j < newPlugins.Count(); j++) { // Get first instance where a game plugin needs another one before it auto attribute = newPlugins[j]->GetClass()->GetAttribute(pluginLoadOrderAttribute); if (attribute == nullptr || MCore::Object::GetClass(attribute) != pluginLoadOrderAttribute) continue; // Check if attribute references a valid class MTypeObject* refType = nullptr; typeField->GetValue(attribute, &refType); if (refType == nullptr) continue; MType* type = INTERNAL_TYPE_OBJECT_GET(refType); if (type == nullptr) continue; MClass* typeClass = MCore::Type::GetClass(type); if (plugin->GetClass() == typeClass) { insertIndex = j; break; } } if (insertIndex == -1) newPlugins.Add(plugin); else newPlugins.Insert(insertIndex, plugin); } return newPlugins; } Array PluginManagerImpl::SortPlugins(Array plugins, MClass* pluginLoadOrderAttribute, MField* typeField) { // Sort plugins Array newPlugins; for(int i = 0; i < plugins.Count(); i++) { Plugin* plugin = plugins[i]; // Sort plugin as needed int insertIndex = -1; for(int j = 0; j < newPlugins.Count(); j++) { // Get first instance where a game plugin needs another one before it auto attribute = newPlugins[j]->GetClass()->GetAttribute(pluginLoadOrderAttribute); if (attribute == nullptr || MCore::Object::GetClass(attribute) != pluginLoadOrderAttribute) continue; // Check if attribute references a valid class MTypeObject* refType = nullptr; typeField->GetValue(attribute, &refType); if (refType == nullptr) continue; MType* type = INTERNAL_TYPE_OBJECT_GET(refType); if (type == nullptr) continue; MClass* typeClass = MCore::Type::GetClass(type); if (plugin->GetClass() == typeClass) { insertIndex = j; break; } } if (insertIndex == -1) newPlugins.Add(plugin); else newPlugins.Insert(insertIndex, plugin); } return newPlugins; } bool PluginManagerService::Init() { // Process already loaded modules for (auto module : BinaryModule::GetModules()) { OnBinaryModuleLoaded(module); } // Initialize plugins auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter"); ASSERT(afterTypeField); #if !USE_EDITOR auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField); // Initalize game plugins for (auto plugin : gamePlugins) { PluginManagerService::InvokeInitialize(plugin); } #endif #if USE_EDITOR auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, afterTypeField); // Initialize editor plugins for (auto plugin : editorPlugins) { PluginManagerService::InvokeInitialize(plugin); } #endif // Register for new binary modules load actions Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Bind(&OnScriptsReloading); return false; } void PluginManagerService::Dispose() { Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); // Cleanup all plugins PROFILE_CPU_NAMED("Dispose Plugins"); const int32 pluginsCount = EditorPlugins.Count() + GamePlugins.Count(); if (pluginsCount == 0) return; LOG(Info, "Unloading {0} plugins", pluginsCount); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); ASSERT(beforeTypeField); auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = editorPlugins.Count() - 1; i >= 0 && editorPlugins.Count() > 0; i--) { auto plugin = editorPlugins[i]; InvokeDeinitialize(plugin); EditorPlugins.Remove(plugin); } auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = gamePlugins.Count() - 1; i >= 0 && gamePlugins.Count() > 0; i--) { auto plugin = gamePlugins[i]; InvokeDeinitialize(plugin); GamePlugins.Remove(plugin); } PluginManager::PluginsChanged(); } const Array& PluginManager::GetGamePlugins() { return GamePlugins; } const Array& PluginManager::GetEditorPlugins() { return EditorPlugins; } Plugin* PluginManager::GetPlugin(const StringView& name) { for (Plugin* p : EditorPlugins) { if (p->GetDescription().Name == name) return p; } for (GamePlugin* gp : GamePlugins) { if (gp->GetDescription().Name == name) return gp; } return nullptr; } Plugin* PluginManager::GetPlugin(const MClass* type) { CHECK_RETURN(type, nullptr); for (Plugin* p : EditorPlugins) { if (p->GetClass()->IsSubClassOf(type)) return p; } for (GamePlugin* gp : GamePlugins) { if (gp->GetClass()->IsSubClassOf(type)) return gp; } return nullptr; } Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type) { CHECK_RETURN(type, nullptr); for (Plugin* p : EditorPlugins) { if (p->Is(type)) return p; } for (GamePlugin* gp : GamePlugins) { if (gp->Is(type)) return gp; } return nullptr; } #if USE_EDITOR void PluginManager::InitializeGamePlugins() { PROFILE_CPU(); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter"); ASSERT(afterTypeField); auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField); for (int32 i = 0; i < gamePlugins.Count(); i++) { PluginManagerService::InvokeInitialize(gamePlugins[i]); } } void PluginManager::DeinitializeGamePlugins() { PROFILE_CPU(); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); ASSERT(beforeTypeField); auto gamePlugins = SortGamePlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = gamePlugins.Count() - 1; i >= 0; i--) { PluginManagerService::InvokeDeinitialize(gamePlugins[i]); } } #endif