Files
FlaxEngine/Source/Engine/Scripting/Plugins/PluginManager.cpp
2023-11-06 10:33:11 +01:00

515 lines
15 KiB
C++

// 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<Plugin*> PluginManager::PluginLoading;
Delegate<Plugin*> PluginManager::PluginLoaded;
Delegate<Plugin*> PluginManager::PluginUnloading;
Delegate<Plugin*> PluginManager::PluginUnloaded;
Action PluginManager::PluginsChanged;
namespace PluginManagerImpl
{
Array<GamePlugin*> GamePlugins;
Array<Plugin*> EditorPlugins;
void LoadPlugin(MClass* klass, bool isEditor);
void OnAssemblyLoaded(MAssembly* assembly);
void OnAssemblyUnloading(MAssembly* assembly);
void OnBinaryModuleLoaded(BinaryModule* module);
void OnScriptsReloading();
template<typename PluginType = Plugin>
Array<PluginType*> SortPlugins(Array<PluginType*> plugins, MClass* pluginLoadOrderAttribute, MField* typeField)
{
// Sort plugins
Array<PluginType*> 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;
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 = 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<ManagedBinaryModule*>(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 = 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);
changed = true;
}
}
if (changed)
PluginManager::PluginsChanged();
}
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 = SortPlugins(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<GamePlugin*>& PluginManager::GetGamePlugins()
{
return GamePlugins;
}
const Array<Plugin*>& 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 = 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