You're breathtaking!
This commit is contained in:
15
Source/Engine/Scripting/Plugins/GamePlugin.cs
Normal file
15
Source/Engine/Scripting/Plugins/GamePlugin.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all plugins used at runtime in game.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plugins should have a public and parameter-less constructor.
|
||||
/// </remarks>
|
||||
/// <seealso cref="FlaxEngine.Plugin" />
|
||||
public abstract class GamePlugin : Plugin
|
||||
{
|
||||
}
|
||||
}
|
||||
42
Source/Engine/Scripting/Plugins/Plugin.cs
Normal file
42
Source/Engine/Scripting/Plugins/Plugin.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for game engine editor plugins.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plugins should have a public and parameter-less constructor.
|
||||
/// </remarks>
|
||||
public abstract class Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plugin description should be a constant part of the plugin created in constructor and valid before calling <see cref="Initialize"/>.
|
||||
/// </remarks>
|
||||
public virtual PluginDescription Description => new PluginDescription
|
||||
{
|
||||
Name = GetType().Name,
|
||||
Category = "Other",
|
||||
Version = new Version(1, 0),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initialization method called when this plugin is loaded to the memory and can be used.
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup method called when this plugin is being unloaded or reloaded or engine is closing.
|
||||
/// </summary>
|
||||
public virtual void Deinitialize()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Source/Engine/Scripting/Plugins/PluginDescription.cs
Normal file
62
Source/Engine/Scripting/Plugins/PluginDescription.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// The engine plugin description container.
|
||||
/// </summary>
|
||||
public struct PluginDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the plugin.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the plugin.
|
||||
/// </summary>
|
||||
public Version Version;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the author of the plugin.
|
||||
/// </summary>
|
||||
public string Author;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin author website URL.
|
||||
/// </summary>
|
||||
public string AuthorUrl;
|
||||
|
||||
/// <summary>
|
||||
/// The homepage URL for the plugin.
|
||||
/// </summary>
|
||||
public string HomepageUrl;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin repository URL (for open-source plugins).
|
||||
/// </summary>
|
||||
public string RepositoryUrl;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin description.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin category.
|
||||
/// </summary>
|
||||
public string Category;
|
||||
|
||||
/// <summary>
|
||||
/// True if plugin is during Beta tests (before release).
|
||||
/// </summary>
|
||||
public bool IsBeta;
|
||||
|
||||
/// <summary>
|
||||
/// True if plugin is during Alpha tests (early version).
|
||||
/// </summary>
|
||||
public bool IsAlpha;
|
||||
}
|
||||
}
|
||||
181
Source/Engine/Scripting/Plugins/PluginManager.cpp
Normal file
181
Source/Engine/Scripting/Plugins/PluginManager.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "PluginManager.h"
|
||||
#include "FlaxEngine.Gen.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/MMethod.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Debug/DebugLog.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
|
||||
namespace PluginManagerImpl
|
||||
{
|
||||
MMethod* Internal_LoadPlugin = nullptr;
|
||||
|
||||
void LoadPlugin(MClass* klass, bool isEditor);
|
||||
void OnAssemblyLoaded(MAssembly* assembly);
|
||||
void OnAssemblyUnloading(MAssembly* assembly);
|
||||
void OnBinaryModuleLoaded(BinaryModule* module);
|
||||
}
|
||||
|
||||
using namespace PluginManagerImpl;
|
||||
|
||||
class PluginManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
PluginManagerService()
|
||||
: EngineService(TEXT("Plugin Manager"), 130)
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
PluginManagerService PluginManagerServiceInstance;
|
||||
|
||||
void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor)
|
||||
{
|
||||
if (Internal_LoadPlugin == nullptr)
|
||||
{
|
||||
Internal_LoadPlugin = PluginManager::GetStaticClass()->GetMethod("Internal_LoadPlugin", 2);
|
||||
ASSERT(Internal_LoadPlugin);
|
||||
}
|
||||
|
||||
MonoObject* exception = nullptr;
|
||||
void* params[2];
|
||||
params[0] = MUtils::GetType(klass->GetNative());
|
||||
params[1] = &isEditor;
|
||||
Internal_LoadPlugin->Invoke(nullptr, params, &exception);
|
||||
if (exception)
|
||||
{
|
||||
DebugLog::LogException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Load Assembly Plugins");
|
||||
|
||||
// Prepare FlaxEngine
|
||||
auto engineAssembly = GetBinaryModuleFlaxEngine()->Assembly;
|
||||
if (!engineAssembly->IsLoaded())
|
||||
{
|
||||
LOG(Warning, "Cannot find plugin class types for assembly {0} because FlaxEngine is not loaded.", assembly->ToString());
|
||||
return;
|
||||
}
|
||||
const auto gamePluginClass = engineAssembly->GetClass("FlaxEngine.GamePlugin");
|
||||
if (gamePluginClass == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing GamePlugin class.");
|
||||
return;
|
||||
}
|
||||
#if USE_EDITOR
|
||||
const auto editorPluginClass = engineAssembly->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)
|
||||
{
|
||||
// Cleanup plugins from this assembly
|
||||
const auto disposeMethod = PluginManager::GetStaticClass()->GetMethod("Internal_Dispose", 1);
|
||||
ASSERT(disposeMethod);
|
||||
MonoObject* exception = nullptr;
|
||||
void* params[1];
|
||||
params[0] = assembly->GetNative();
|
||||
disposeMethod->Invoke(nullptr, params, &exception);
|
||||
if (exception)
|
||||
{
|
||||
DebugLog::LogException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module)
|
||||
{
|
||||
// Skip special modules
|
||||
if (module == GetBinaryModuleFlaxEngine() ||
|
||||
module == GetBinaryModuleCorlib())
|
||||
return;
|
||||
|
||||
// Skip non-managed modules
|
||||
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);
|
||||
}
|
||||
|
||||
bool PluginManagerService::Init()
|
||||
{
|
||||
// Process already loaded modules
|
||||
for (auto module : BinaryModule::GetModules())
|
||||
{
|
||||
OnBinaryModuleLoaded(module);
|
||||
}
|
||||
|
||||
// Register for new binary modules load actions
|
||||
Scripting::BinaryModuleLoaded.Bind(&OnBinaryModuleLoaded);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PluginManagerService::Dispose()
|
||||
{
|
||||
Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded);
|
||||
|
||||
PROFILE_CPU_NAMED("Dispose Plugins");
|
||||
|
||||
// Cleanup all plugins
|
||||
const auto disposeMethod = PluginManager::GetStaticClass()->GetMethod("Internal_Dispose");
|
||||
ASSERT(disposeMethod);
|
||||
MonoObject* exception = nullptr;
|
||||
disposeMethod->Invoke(nullptr, nullptr, &exception);
|
||||
if (exception)
|
||||
{
|
||||
DebugLog::LogException(exception);
|
||||
}
|
||||
}
|
||||
227
Source/Engine/Scripting/Plugins/PluginManager.cs
Normal file
227
Source/Engine/Scripting/Plugins/PluginManager.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin related event delegate type.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
[HideInEditor]
|
||||
public delegate void PluginDelegate(Plugin plugin);
|
||||
|
||||
public static partial class PluginManager
|
||||
{
|
||||
private static readonly List<GamePlugin> _gamePlugins = new List<GamePlugin>();
|
||||
private static readonly List<Plugin> _editorPlugins = new List<Plugin>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loaded and enabled game plugins.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<GamePlugin> GamePlugins => _gamePlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loaded and enabled editor plugins.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<Plugin> EditorPlugins => _editorPlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before loading plugin.
|
||||
/// </summary>
|
||||
public static event PluginDelegate PluginLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when plugin gets loaded and initialized.
|
||||
/// </summary>
|
||||
public static event PluginDelegate PluginLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before unloading plugin.
|
||||
/// </summary>
|
||||
public static event PluginDelegate PluginUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when plugin gets unloaded. It should not be used anymore.
|
||||
/// </summary>
|
||||
public static event PluginDelegate PluginUnloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can load the specified plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginDesc">The plugin description.</param>
|
||||
/// <returns>True if load it, otherwise false.</returns>
|
||||
public delegate bool CanLoadPluginDelegate(ref PluginDescription pluginDesc);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can load the specified plugin.
|
||||
/// </summary>
|
||||
public static CanLoadPluginDelegate CanLoadPlugin = DefaultCanLoadPlugin;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin related event delegate type.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
public delegate void PluginDelegate(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// The default implementation for <see cref="CanLoadPlugin"/>.
|
||||
/// </summary>
|
||||
/// <param name="pluginDesc">The plugin description.</param>
|
||||
/// <returns>True if load it, otherwise false.</returns>
|
||||
public static bool DefaultCanLoadPlugin(ref PluginDescription pluginDesc)
|
||||
{
|
||||
return true;
|
||||
//return !pluginDesc.DisabledByDefault;
|
||||
}
|
||||
|
||||
private static void InvokeInitialize(Plugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Write(LogType.Info, "Loading plugin " + plugin);
|
||||
|
||||
PluginLoading?.Invoke(plugin);
|
||||
|
||||
plugin.Initialize();
|
||||
|
||||
PluginLoaded?.Invoke(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
Debug.LogErrorFormat("Failed to initialize plugin {0}. {1}", plugin, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvokeDeinitialize(Plugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Write(LogType.Info, "Unloading plugin " + plugin);
|
||||
|
||||
PluginUnloading?.Invoke(plugin);
|
||||
|
||||
plugin.Deinitialize();
|
||||
|
||||
PluginUnloaded?.Invoke(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
Debug.LogErrorFormat("Failed to deinitialize plugin {0}. {1}", plugin, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first plugin of the provided type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The plugin type.</typeparam>
|
||||
/// <returns>The plugin, or null if not loaded.</returns>
|
||||
public static T GetPlugin<T>() where T : class
|
||||
{
|
||||
foreach (Plugin p in _editorPlugins)
|
||||
{
|
||||
if (p.GetType() == typeof(T))
|
||||
return (T)Convert.ChangeType(p, typeof(T));
|
||||
}
|
||||
|
||||
foreach (GamePlugin gp in _gamePlugins)
|
||||
{
|
||||
if (gp.GetType() == typeof(T))
|
||||
return (T)Convert.ChangeType(gp, typeof(T));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the first plugin using the provided name.
|
||||
/// </summary>
|
||||
/// /// <param name="name">The plugin name.</param>
|
||||
/// <returns>The plugin, or null if not loaded.</returns>
|
||||
public static Plugin GetPlugin(string name)
|
||||
{
|
||||
foreach (Plugin p in _editorPlugins)
|
||||
{
|
||||
if (p.Description.Name.Equals(name))
|
||||
return p;
|
||||
}
|
||||
|
||||
foreach (GamePlugin gp in _gamePlugins)
|
||||
{
|
||||
if (gp.Description.Name.Equals(name))
|
||||
return gp;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static void Internal_LoadPlugin(Type type, bool isEditor)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
// Create and check if use it
|
||||
var plugin = (Plugin)Activator.CreateInstance(type);
|
||||
var desc = plugin.Description;
|
||||
if (!CanLoadPlugin(ref desc))
|
||||
{
|
||||
Debug.Write(LogType.Info, "Skip loading plugin " + plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Init
|
||||
InvokeInitialize(plugin);
|
||||
|
||||
// Register
|
||||
if (isEditor)
|
||||
_editorPlugins.Add(plugin);
|
||||
else
|
||||
_gamePlugins.Add((GamePlugin)plugin);
|
||||
}
|
||||
|
||||
internal static void Internal_Dispose(Assembly assembly)
|
||||
{
|
||||
for (int i = _editorPlugins.Count - 1; i >= 0 && _editorPlugins.Count > 0; i--)
|
||||
{
|
||||
if (_editorPlugins[i].GetType().Assembly == assembly)
|
||||
{
|
||||
InvokeDeinitialize(_editorPlugins[i]);
|
||||
_editorPlugins.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = _gamePlugins.Count - 1; i >= 0 && _gamePlugins.Count > 0; i--)
|
||||
{
|
||||
if (_gamePlugins[i].GetType().Assembly == assembly)
|
||||
{
|
||||
InvokeDeinitialize(_gamePlugins[i]);
|
||||
_gamePlugins.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Internal_Dispose()
|
||||
{
|
||||
int pluginsCount = _editorPlugins.Count + _gamePlugins.Count;
|
||||
if (pluginsCount == 0)
|
||||
return;
|
||||
Debug.Write(LogType.Info, string.Format("Unloading {0} plugins", pluginsCount));
|
||||
|
||||
for (int i = _editorPlugins.Count - 1; i >= 0 && _editorPlugins.Count > 0; i--)
|
||||
{
|
||||
InvokeDeinitialize(_editorPlugins[i]);
|
||||
_editorPlugins.RemoveAt(i);
|
||||
}
|
||||
|
||||
for (int i = _gamePlugins.Count - 1; i >= 0 && _gamePlugins.Count > 0; i--)
|
||||
{
|
||||
InvokeDeinitialize(_gamePlugins[i]);
|
||||
_gamePlugins.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Source/Engine/Scripting/Plugins/PluginManager.h
Normal file
14
Source/Engine/Scripting/Plugins/PluginManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Game and Editor plugins management service.
|
||||
/// </summary>
|
||||
API_CLASS(Static) class PluginManager
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PluginManager);
|
||||
};
|
||||
Reference in New Issue
Block a user