// Copyright (c) 2012-2024 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 { bool Initialized = false; Array GamePlugins; Array EditorPlugins; void OnAssemblyLoaded(MAssembly* assembly); void OnAssemblyUnloading(MAssembly* assembly); void OnBinaryModuleLoaded(BinaryModule* module); void OnScriptsReloading(); void InitializePlugins(); void DeinitializePlugins(); template Array SortPlugins(Array plugins, MClass* pluginLoadOrderAttribute, MField* typeField) { // Sort plugins Array newPlugins; for (int i = 0; i < plugins.Count(); i++) { PluginType* plugin = plugins[i]; int32 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; } } 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; const 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; const 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::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 bool loadedAnyPlugin = false; 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) #if USE_EDITOR || mclass->IsSubClassOf(editorPluginClass) #endif ) { auto plugin = (Plugin*)Scripting::NewObject(mclass); if (plugin) { #if USE_EDITOR if (mclass->IsSubClassOf(editorPluginClass)) { EditorPlugins.Add(plugin); } else #endif { GamePlugins.Add((GamePlugin*)plugin); } loadedAnyPlugin = true; } } } // Send event and initialize newly added plugins (ignore during startup) if (loadedAnyPlugin && Initialized) { InitializePlugins(); PluginManager::PluginsChanged(); } } 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); #if USE_EDITOR 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; } } #endif auto gamePlugins = SortPlugins(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) DeinitializePlugins(); } void PluginManagerImpl::InitializePlugins() { if (EditorPlugins.Count() + GamePlugins.Count() == 0) return; PROFILE_CPU_NAMED("InitializePlugins"); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto afterTypeField = pluginLoadOrderAttribute->GetField("InitializeAfter"); ASSERT(afterTypeField); #if USE_EDITOR auto editorPlugins = SortPlugins(EditorPlugins, pluginLoadOrderAttribute, afterTypeField); for (auto plugin : editorPlugins) { PluginManagerService::InvokeInitialize(plugin); } #else // Game plugins are managed via InitializeGamePlugins/DeinitializeGamePlugins by Editor for play mode auto gamePlugins = SortPlugins(GamePlugins, pluginLoadOrderAttribute, afterTypeField); for (auto plugin : gamePlugins) { PluginManagerService::InvokeInitialize(plugin); } #endif } void PluginManagerImpl::DeinitializePlugins() { if (EditorPlugins.Count() + GamePlugins.Count() == 0) return; PROFILE_CPU_NAMED("DeinitializePlugins"); auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; auto pluginLoadOrderAttribute = engineAssembly->GetClass("FlaxEngine.PluginLoadOrderAttribute"); auto beforeTypeField = pluginLoadOrderAttribute->GetField("DeinitializeBefore"); ASSERT(beforeTypeField); #if USE_EDITOR 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); } #endif auto gamePlugins = SortPlugins(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); } PluginManager::PluginsChanged(); } bool PluginManagerService::Init() { Initialized = false; // Process already loaded modules for (auto module : BinaryModule::GetModules()) { OnBinaryModuleLoaded(module); } // Invoke plugins initialization for all of them InitializePlugins(); // Register for new binary modules load actions Initialized = true; Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Bind(&OnScriptsReloading); return false; } void PluginManagerService::Dispose() { // Unregister from new modules loading Initialized = false; Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded); Scripting::ScriptsReloading.Unbind(&OnScriptsReloading); // Cleanup all plugins DeinitializePlugins(); } const Array& PluginManager::GetGamePlugins() { return GamePlugins; } const Array& PluginManager::GetEditorPlugins() { return EditorPlugins; } Plugin* PluginManager::GetPlugin(const StringView& name) { #if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->GetDescription().Name == name) return p; } #endif for (GamePlugin* gp : GamePlugins) { if (gp->GetDescription().Name == name) return gp; } return nullptr; } Plugin* PluginManager::GetPlugin(const MClass* type) { CHECK_RETURN(type, nullptr); #if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->GetClass()->IsSubClassOf(type)) return p; } #endif for (GamePlugin* gp : GamePlugins) { if (gp->GetClass()->IsSubClassOf(type)) return gp; } return nullptr; } Plugin* PluginManager::GetPlugin(const ScriptingTypeHandle& type) { CHECK_RETURN(type, nullptr); #if USE_EDITOR for (Plugin* p : EditorPlugins) { if (p->Is(type)) return p; } #endif 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 = SortPlugins(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 = SortPlugins(GamePlugins, pluginLoadOrderAttribute, beforeTypeField); for (int32 i = gamePlugins.Count() - 1; i >= 0; i--) { PluginManagerService::InvokeDeinitialize(gamePlugins[i]); } } #endif