Refactor Plugins system to support plugins in C++ scripts

This commit is contained in:
Wojtek Figat
2022-07-28 21:05:03 +02:00
parent 6f35b27c29
commit bdb69d57dd
23 changed files with 522 additions and 485 deletions

View File

@@ -128,7 +128,9 @@ namespace FlaxEditor
// Plugin assets
foreach (var plugin in PluginManager.GamePlugins)
{
plugin.OnCollectAssets(list);
var pluginRefs = plugin.GetReferences();
if (pluginRefs != null)
list.AddRange(pluginRefs);
}
if (list.Count == 0)

View File

@@ -144,7 +144,7 @@ namespace FlaxEditor.States
_duplicateScenes.GatherSceneData();
Editor.Internal_SetPlayMode(true);
IsPaused = false;
PluginManager.InitializeGamePlugins();
PluginManager.Internal_InitializeGamePlugins();
_duplicateScenes.CreateScenes();
SceneDuplicated?.Invoke();
RestoreSelection();
@@ -180,7 +180,7 @@ namespace FlaxEditor.States
// Restore editor scene
SceneRestoring?.Invoke();
_duplicateScenes.DeletedScenes();
PluginManager.DeinitializeGamePlugins();
PluginManager.Internal_DeinitializeGamePlugins();
Editor.Internal_SetPlayMode(false);
_duplicateScenes.RestoreSceneData();
SceneRestored?.Invoke();

View File

@@ -190,9 +190,11 @@ namespace FlaxEditor.Windows
private void OnPluginsChanged()
{
List<PluginEntry> toRemove = null;
var gamePlugins = PluginManager.GamePlugins;
var editorPlugins = PluginManager.EditorPlugins;
foreach (var e in _entries)
{
if (!PluginManager.EditorPlugins.Contains(e.Key) && !PluginManager.GamePlugins.Contains(e.Key))
if (!editorPlugins.Contains(e.Key) && !gamePlugins.Contains(e.Key))
{
if (toRemove == null)
toRemove = new List<PluginEntry>();
@@ -204,9 +206,9 @@ namespace FlaxEditor.Windows
foreach (var plugin in toRemove)
OnPluginRemove(plugin);
}
foreach (var plugin in PluginManager.GamePlugins)
foreach (var plugin in gamePlugins)
OnPluginAdd(plugin);
foreach (var plugin in PluginManager.EditorPlugins)
foreach (var plugin in editorPlugins)
OnPluginAdd(plugin);
}
@@ -288,7 +290,7 @@ namespace FlaxEditor.Windows
foreach (var e in _entries.Keys)
{
if (e.GetType() == pluginType)
if (e.GetType() == pluginType && _entries.ContainsKey(e))
return _entries[e];
}

View File

@@ -3,6 +3,30 @@
#include "Version.h"
#include "Engine/Core/Types/StringBuilder.h"
Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = Math::Max(revision, 0);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = -1;
}
Version::Version(int32 major, int32 minor)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = -1;
_revision = -1;
}
int32 Version::CompareTo(const Version& value) const
{
if (_major != value._major)

View File

@@ -9,7 +9,7 @@
/// <summary>
/// Represents the version number made of major, minor, build and revision numbers.
/// </summary>
struct FLAXENGINE_API Version
API_STRUCT(InBuild) struct FLAXENGINE_API Version
{
private:
int32 _major;
@@ -25,13 +25,7 @@ public:
/// <param name="minor">The minor version number.</param>
/// <param name="build">The build number.</param>
/// <param name="revision">The revision number.</param>
Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = Math::Max(revision, 0);
}
Version(int32 major, int32 minor, int32 build, int32 revision);
/// <summary>
/// Initializes a new instance of the Version class using the specified major, minor, and build values.
@@ -39,26 +33,14 @@ public:
/// <param name="major">The major version number.</param>
/// <param name="minor">The minor version number.</param>
/// <param name="build">The build number.</param>
Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = -1;
}
Version(int32 major, int32 minor, int32 build);
/// <summary>
/// Initializes a new instance of the Version class using the specified major and minor values.
/// </summary>
/// <param name="major">The major version number.</param>
/// <param name="minor">The minor version number.</param>
Version(int32 major, int32 minor)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = -1;
_revision = -1;
}
Version(int32 major, int32 minor);
/// <summary>
/// Initializes a new instance of the Version class.
@@ -67,8 +49,8 @@ public:
{
_major = 0;
_minor = 0;
_revision = 0;
_build = 0;
_revision = -1;
_build = -1;
}
public:

View File

@@ -38,6 +38,7 @@
#include "Engine/Platform/MessageBox.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Serialization/JsonSerializer.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#endif
#if USE_LARGE_WORLDS
@@ -950,7 +951,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
LOG(Error, "Cannot load scene without game modules loaded.");
#if USE_EDITOR
if (!CommandLine::Options.Headless.IsTrue())
MessageBox::Show(TEXT("Cannot load scene without game script modules. Please fix the compilation issues. See logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error);
{
if (ScriptsBuilder::LastCompilationFailed())
MessageBox::Show(TEXT("Scripts compilation failed. Cannot load scene without game script modules. Please fix the compilation issues. See logs for more info."), TEXT("Failed to compile scripts"), MessageBoxButtons::OK, MessageBoxIcon::Error);
else
MessageBox::Show(TEXT("Failed to load scripts. Cannot load scene without game script modules. See logs for more info."), TEXT("Missing game modules"), MessageBoxButtons::OK, MessageBoxIcon::Error);
}
#endif
return true;
}

View File

@@ -571,6 +571,17 @@ String ScriptingType::ToString() const
return String(Fullname.Get(), Fullname.Length());
}
StringAnsiView ScriptingType::GetName() const
{
int32 lastDotIndex = Fullname.FindLast('.');
if (lastDotIndex != -1)
{
lastDotIndex++;
return StringAnsiView(Fullname.Get() + lastDotIndex, Fullname.Length() - lastDotIndex);
}
return Fullname;
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
@@ -686,7 +697,7 @@ ManagedBinaryModule::ManagedBinaryModule(MAssembly* assembly)
// Bind for C# assembly events
assembly->Loading.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnLoading>(this);
assembly->Loaded.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnLoaded>(this);
assembly->Unloading.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnUnloading>(this);
assembly->Unloaded.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnUnloaded>(this);
if (Assembly->IsLoaded())
{
@@ -1084,7 +1095,7 @@ void ManagedBinaryModule::InitType(MClass* mclass)
#endif
}
void ManagedBinaryModule::OnUnloading(MAssembly* assembly)
void ManagedBinaryModule::OnUnloaded(MAssembly* assembly)
{
PROFILE_CPU();

View File

@@ -309,7 +309,7 @@ private:
void OnLoading(MAssembly* assembly);
void OnLoaded(MAssembly* assembly);
void InitType(MClass* mclass);
void OnUnloading(MAssembly* assembly);
void OnUnloaded(MAssembly* assembly);
public:

View File

@@ -5,6 +5,7 @@
#include "MType.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Types/Version.h"
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Math/BoundingSphere.h"
#include "Engine/Core/Math/Rectangle.h"
@@ -1279,4 +1280,18 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa
return nullptr;
}
MonoObject* MUtils::ToManaged(const Version& value)
{
auto obj = mono_object_new(mono_domain_get(), Scripting::FindClassNative("System.Version"));
Platform::MemoryCopy((byte*)obj + sizeof(MonoObject), &value, sizeof(Version));
return obj;
}
Version MUtils::ToNative(MonoObject* value)
{
if (value)
return *(Version*)((byte*)value + sizeof(MonoObject));
return Version();
}
#endif

View File

@@ -14,6 +14,8 @@
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
struct Version;
namespace MUtils
{
extern FLAXENGINE_API StringView ToString(MonoString* str);
@@ -500,6 +502,9 @@ namespace MUtils
}
extern void* VariantToManagedArgPtr(Variant& value, const MType& type, bool& failed);
extern MonoObject* ToManaged(const Version& value);
extern Version ToNative(MonoObject* value);
};
#endif

View File

@@ -1,24 +0,0 @@
// Copyright (c) 2012-2022 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
{
#if FLAX_EDITOR
/// <summary>
/// Event called during game cooking in Editor to collect any assets that this plugin uses. Can be used to inject content for plugins.
/// </summary>
/// <param name="assets">The result assets list (always valid).</param>
public virtual void OnCollectAssets(System.Collections.Generic.List<System.Guid> assets)
{
}
#endif
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "Plugin.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
/// Base class for all plugins used at runtime in game.
/// </summary>
API_CLASS(Abstract) class FLAXENGINE_API GamePlugin : public Plugin
{
DECLARE_SCRIPTING_TYPE(GamePlugin);
public:
#if USE_EDITOR
/// <summary>
/// Function called during game cooking in Editor to collect any assets that this plugin uses. Can be used to inject content for plugins.
/// </summary>
/// <returns>The result assets list.</returns>
API_FUNCTION() virtual Array<Guid> GetReferences() const
{
return Array<Guid>();
}
#endif
};

View File

@@ -1,44 +0,0 @@
// Copyright (c) 2012-2022 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
{
internal bool _initialized;
/// <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()
{
}
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "PluginDescription.h"
#include "Engine/Scripting/ScriptingObject.h"
/// <summary>
/// Base class for game engine editor plugins.
/// </summary>
API_CLASS(Abstract) class FLAXENGINE_API Plugin : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(Plugin);
private:
friend class PluginManagerService;
bool _initialized = false;
protected:
/// <remarks>
/// Plugin description. Should be a constant part of the plugin created in constructor and valid before calling <see cref="Initialize"/>.
/// </remarks>
API_FIELD() PluginDescription _description;
public:
/// <summary>
/// Gets the description.
/// </summary>
API_PROPERTY() const PluginDescription& GetDescription() const
{
return _description;
}
/// <summary>
/// Initialization method called when this plugin is loaded to the memory and can be used.
/// </summary>
API_FUNCTION() virtual void Initialize()
{
}
/// <summary>
/// Cleanup method called when this plugin is being unloaded or reloaded or engine is closing.
/// </summary>
API_FUNCTION() virtual void Deinitialize()
{
}
};

View File

@@ -1,62 +0,0 @@
// Copyright (c) 2012-2022 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;
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Version.h"
#include "Engine/Scripting/ScriptingType.h"
/// <summary>
/// The engine plugin description container.
/// </summary>
API_STRUCT() struct PluginDescription
{
DECLARE_SCRIPTING_TYPE_MINIMAL(PluginDescription);
public:
/// <summary>
/// The name of the plugin.
/// </summary>
API_FIELD() String Name;
/// <summary>
/// The version of the plugin.
/// </summary>
API_FIELD() Version Version;
/// <summary>
/// The name of the author of the plugin.
/// </summary>
API_FIELD() String Author;
/// <summary>
/// The plugin author website URL.
/// </summary>
API_FIELD() String AuthorUrl;
/// <summary>
/// The homepage URL for the plugin.
/// </summary>
API_FIELD() String HomepageUrl;
/// <summary>
/// The plugin repository URL (for open-source plugins).
/// </summary>
API_FIELD() String RepositoryUrl;
/// <summary>
/// The plugin description.
/// </summary>
API_FIELD() String Description;
/// <summary>
/// The plugin category.
/// </summary>
API_FIELD() String Category;
/// <summary>
/// True if plugin is during Beta tests (before release).
/// </summary>
API_FIELD() bool IsBeta = false;
/// <summary>
/// True if plugin is during Alpha tests (early version).
/// </summary>
API_FIELD() bool IsAlpha = false;
};

View File

@@ -2,22 +2,41 @@
#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/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"
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)
{
}
Delegate<Plugin*> PluginManager::PluginLoading;
Delegate<Plugin*> PluginManager::PluginLoaded;
Delegate<Plugin*> PluginManager::PluginUnloading;
Delegate<Plugin*> PluginManager::PluginUnloaded;
Action PluginManager::PluginsChanged;
namespace PluginManagerImpl
{
MMethod* Internal_LoadPlugin = nullptr;
Array<GamePlugin*> GamePlugins;
Array<Plugin*> EditorPlugins;
void LoadPlugin(MClass* klass, bool isEditor);
void OnAssemblyLoaded(MAssembly* assembly);
@@ -30,7 +49,6 @@ using namespace PluginManagerImpl;
class PluginManagerService : public EngineService
{
public:
PluginManagerService()
: EngineService(TEXT("Plugin Manager"), 130)
{
@@ -38,50 +56,79 @@ public:
bool Init() override;
void Dispose() override;
static void InvokeInitialize(Plugin* plugin);
static void InvokeDeinitialize(Plugin* plugin);
};
PluginManagerService PluginManagerServiceInstance;
void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor)
void PluginManagerService::InvokeInitialize(Plugin* plugin)
{
#if !COMPILE_WITHOUT_CSHARP
if (Internal_LoadPlugin == nullptr)
{
Internal_LoadPlugin = PluginManager::GetStaticClass()->GetMethod("Internal_LoadPlugin", 2);
ASSERT(Internal_LoadPlugin);
if (plugin->_initialized)
return;
LOG(Info, "Loading plugin {}", plugin->ToString());
PluginManager::PluginLoading(plugin);
plugin->Initialize();
plugin->_initialized = true;
PluginManager::PluginLoaded(plugin);
}
MObject* exception = nullptr;
void* params[2];
params[0] = MUtils::GetType(klass->GetNative());
params[1] = &isEditor;
Internal_LoadPlugin->Invoke(nullptr, params, &exception);
if (exception)
void PluginManagerService::InvokeDeinitialize(Plugin* plugin)
{
DebugLog::LogException(exception);
if (!plugin->_initialized)
return;
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
PluginManagerService::InvokeInitialize(plugin);
#endif
}
#if USE_EDITOR
else
{
EditorPlugins.Add(plugin);
PluginManagerService::InvokeInitialize(plugin);
}
#endif
PluginManager::PluginsChanged();
}
void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
{
PROFILE_CPU_NAMED("Load Assembly Plugins");
// Prepare FlaxEngine
auto engineAssembly = ((NativeBinaryModule*)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");
const auto gamePluginClass = GamePlugin::GetStaticClass();
if (gamePluginClass == nullptr)
{
LOG(Warning, "Missing GamePlugin class.");
return;
}
#if USE_EDITOR
const auto editorPluginClass = engineAssembly->GetClass("FlaxEditor.EditorPlugin");
const auto editorPluginClass = ((ManagedBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.EditorPlugin");
if (editorPluginClass == nullptr)
{
LOG(Warning, "Missing EditorPlugin class.");
@@ -115,19 +162,29 @@ void PluginManagerImpl::OnAssemblyLoaded(MAssembly* assembly)
void PluginManagerImpl::OnAssemblyUnloading(MAssembly* assembly)
{
#if !COMPILE_WITHOUT_CSHARP
// Cleanup plugins from this assembly
const auto disposeMethod = PluginManager::GetStaticClass()->GetMethod("Internal_Dispose", 1);
ASSERT(disposeMethod);
MObject* exception = nullptr;
void* params[1];
params[0] = assembly->GetNative();
disposeMethod->Invoke(nullptr, params, &exception);
if (exception)
bool changed = false;
for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
{
DebugLog::LogException(exception);
auto plugin = EditorPlugins[i];
if (plugin->GetType().ManagedClass->GetAssembly() == assembly)
{
PluginManagerService::InvokeDeinitialize(plugin);
EditorPlugins.RemoveAtKeepOrder(i);
changed = true;
}
#endif
}
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.RemoveAtKeepOrder(i);
changed = true;
}
}
if (changed)
PluginManager::PluginsChanged();
}
void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module)
@@ -138,6 +195,7 @@ void PluginManagerImpl::OnBinaryModuleLoaded(BinaryModule* module)
return;
// Skip non-managed modules
// TODO: search native-only modules too
auto managedModule = dynamic_cast<ManagedBinaryModule*>(module);
if (!managedModule)
return;
@@ -171,17 +229,86 @@ void PluginManagerService::Dispose()
{
Scripting::BinaryModuleLoaded.Unbind(&OnBinaryModuleLoaded);
PROFILE_CPU_NAMED("Dispose Plugins");
#if !COMPILE_WITHOUT_CSHARP
// Cleanup all plugins
const auto disposeMethod = PluginManager::GetStaticClass()->GetMethod("Internal_Dispose");
ASSERT(disposeMethod);
MObject* exception = nullptr;
disposeMethod->Invoke(nullptr, nullptr, &exception);
if (exception)
PROFILE_CPU_NAMED("Dispose Plugins");
const int32 pluginsCount = EditorPlugins.Count() + GamePlugins.Count();
if (pluginsCount == 0)
return;
LOG(Info, "Unloading {0} plugins", pluginsCount);
for (int32 i = EditorPlugins.Count() - 1; i >= 0 && EditorPlugins.Count() > 0; i--)
{
DebugLog::LogException(exception);
auto plugin = EditorPlugins[i];
InvokeDeinitialize(plugin);
EditorPlugins.RemoveAtKeepOrder(i);
}
for (int32 i = GamePlugins.Count() - 1; i >= 0 && GamePlugins.Count() > 0; i--)
{
auto plugin = GamePlugins[i];
InvokeDeinitialize(plugin);
GamePlugins.RemoveAtKeepOrder(i);
}
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;
}
#if USE_EDITOR
void PluginManager::InitializeGamePlugins()
{
PROFILE_CPU();
for (int32 i = 0; i < GamePlugins.Count(); i++)
{
PluginManagerService::InvokeInitialize(GamePlugins[i]);
}
}
void PluginManager::DeinitializeGamePlugins()
{
PROFILE_CPU();
for (int32 i = GamePlugins.Count() - 1; i >= 0; i--)
{
PluginManagerService::InvokeDeinitialize(GamePlugins[i]);
}
}
#endif
}

View File

@@ -1,282 +1,17 @@
// Copyright (c) 2012-2022 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
partial class PluginManager
{
private static readonly List<GamePlugin> _gamePlugins = new List<GamePlugin>();
private static readonly List<Plugin> _editorPlugins = new List<Plugin>();
/// <summary>
/// Gets the game plugins.
/// </summary>
public static IReadOnlyList<GamePlugin> GamePlugins => _gamePlugins;
/// <summary>
/// Gets the 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>
/// Occurs when plugins collection gets edited (added or removed plugin).
/// </summary>
public static event Action PluginsChanged;
/// <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;
}
#if FLAX_EDITOR
internal static void InitializeGamePlugins()
{
Profiler.BeginEvent("PluginManager.InitializeGamePlugins");
for (var i = 0; i < _gamePlugins.Count; i++)
{
InvokeInitialize(_gamePlugins[i]);
}
Profiler.EndEvent();
}
internal static void DeinitializeGamePlugins()
{
Profiler.BeginEvent("PluginManager.DeinitializeGamePlugins");
for (var i = _gamePlugins.Count - 1; i >= 0; i--)
{
InvokeDeinitialize(_gamePlugins[i]);
}
Profiler.EndEvent();
}
#endif
private static void InvokeInitialize(Plugin plugin)
{
if (plugin._initialized)
return;
try
{
Debug.Write(LogType.Info, "Loading plugin " + plugin);
PluginLoading?.Invoke(plugin);
plugin.Initialize();
plugin._initialized = true;
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)
{
if (!plugin._initialized)
return;
try
{
Debug.Write(LogType.Info, "Unloading plugin " + plugin);
PluginUnloading?.Invoke(plugin);
plugin.Deinitialize();
plugin._initialized = false;
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
public static T GetPlugin<T>() where T : Plugin
{
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;
}
if (!isEditor)
{
_gamePlugins.Add((GamePlugin)plugin);
#if !FLAX_EDITOR
InvokeInitialize(plugin);
#endif
}
#if FLAX_EDITOR
else
{
_editorPlugins.Add(plugin);
InvokeInitialize(plugin);
}
#endif
PluginsChanged?.Invoke();
}
internal static void Internal_Dispose(Assembly assembly)
{
bool changed = false;
for (int i = _editorPlugins.Count - 1; i >= 0 && _editorPlugins.Count > 0; i--)
{
var plugin = _editorPlugins[i];
if (plugin.GetType().Assembly == assembly)
{
InvokeDeinitialize(plugin);
_editorPlugins.RemoveAt(i);
changed = true;
}
}
for (int i = _gamePlugins.Count - 1; i >= 0 && _gamePlugins.Count > 0; i--)
{
var plugin = _gamePlugins[i];
if (plugin.GetType().Assembly == assembly)
{
InvokeDeinitialize(plugin);
_gamePlugins.RemoveAt(i);
changed = true;
}
}
if (changed)
PluginsChanged?.Invoke();
}
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--)
{
var plugin = _editorPlugins[i];
InvokeDeinitialize(plugin);
_editorPlugins.RemoveAt(i);
}
for (int i = _gamePlugins.Count - 1; i >= 0 && _gamePlugins.Count > 0; i--)
{
var plugin = _gamePlugins[i];
InvokeDeinitialize(plugin);
_gamePlugins.RemoveAt(i);
}
PluginsChanged?.Invoke();
return (T)GetPlugin(typeof(T));
}
}
}

View File

@@ -2,8 +2,7 @@
#pragma once
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Collections/Array.h"
#include "GamePlugin.h"
/// <summary>
/// Game and Editor plugins management service.
@@ -11,4 +10,72 @@
API_CLASS(Static) class PluginManager
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PluginManager);
public:
/// <summary>
/// Gets the game plugins.
/// </summary>
API_PROPERTY() static const Array<GamePlugin*>& GetGamePlugins();
/// <summary>
/// Gets the editor plugins.
/// </summary>
API_PROPERTY() static const Array<Plugin*>& GetEditorPlugins();
public:
/// <summary>
/// Occurs before loading plugin.
/// </summary>
API_EVENT() static Delegate<Plugin*> PluginLoading;
/// <summary>
/// Occurs when plugin gets loaded and initialized.
/// </summary>
API_EVENT() static Delegate<Plugin*> PluginLoaded;
/// <summary>
/// Occurs before unloading plugin.
/// </summary>
API_EVENT() static Delegate<Plugin*> PluginUnloading;
/// <summary>
/// Occurs when plugin gets unloaded. It should not be used anymore.
/// </summary>
API_EVENT() static Delegate<Plugin*> PluginUnloaded;
/// <summary>
/// Occurs when plugins collection gets edited (added or removed plugin).
/// </summary>
API_EVENT() static Action PluginsChanged;
public:
/// <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>
API_FUNCTION() static Plugin* GetPlugin(const StringView& name);
/// <summary>
/// Returns the first plugin of the provided type.
/// </summary>
/// <param name="type">Type of the plugin to search for. Includes any plugin base class derived from the type.</param>
/// <returns>The plugin or null.</returns>
API_FUNCTION() static Plugin* GetPlugin(API_PARAM(Attributes="TypeReference(typeof(Plugin))") const MClass* type);
/// <summary>
/// Returns the first plugin of the provided type.
/// </summary>
/// <returns>The plugin or null.</returns>
template<typename T>
FORCE_INLINE static T* GetPlugin()
{
return (T*)GetPlugin(T::GetStaticClass());
}
private:
#if USE_EDITOR
// Internal bindings
API_FUNCTION(NoProxy) static void InitializeGamePlugins();
API_FUNCTION(NoProxy) static void DeinitializeGamePlugins();
#endif
};

View File

@@ -12,6 +12,7 @@
#include "Engine/Platform/File.h"
#include "Engine/Debug/DebugLog.h"
#if USE_EDITOR
#include "Engine/Level/Level.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#endif
#include "ManagedCLR/MAssembly.h"
@@ -20,7 +21,6 @@
#include "ManagedCLR/MDomain.h"
#include "ManagedCLR/MCore.h"
#include "MException.h"
#include "Engine/Level/Level.h"
#include "Engine/Core/ObjectsRemovalService.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -40,7 +40,6 @@ extern void registerFlaxEngineInternalCalls();
class ScriptingService : public EngineService
{
public:
ScriptingService()
: EngineService(TEXT("Scripting"), -20)
{
@@ -733,6 +732,48 @@ ScriptingTypeHandle Scripting::FindScriptingType(const StringAnsiView& fullname)
return ScriptingTypeHandle();
}
ScriptingObject* Scripting::NewObject(const MClass* type)
{
if (type == nullptr)
{
LOG(Error, "Invalid type.");
return nullptr;
}
MonoClass* typeClass = type->GetNative();
#if USE_MONO
// Get the assembly with that class
auto module = ManagedBinaryModule::FindModule(typeClass);
if (module == nullptr)
{
LOG(Error, "Cannot find scripting assembly for type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
return nullptr;
}
// Try to find the scripting type for this class
int32 typeIndex;
if (!module->ClassToTypeIndex.TryGet(typeClass, typeIndex))
{
LOG(Error, "Cannot spawn objects of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
return nullptr;
}
const ScriptingType& scriptingType = module->Types[typeIndex];
// Create unmanaged object
const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
ScriptingObject* obj = scriptingType.Script.Spawn(params);
if (obj == nullptr)
{
LOG(Error, "Failed to spawn object of type \'{0}.{1}\'.", String(mono_class_get_namespace(typeClass)), String(mono_class_get_name(typeClass)));
return nullptr;
}
#else
LOG(Error, "Not supported object creation from Managed class.");
#endif
return obj;
}
FLAXENGINE_API ScriptingObject* FindObject(const Guid& id, MClass* type)
{
return Scripting::FindObject(id, type);

View File

@@ -109,6 +109,13 @@ public:
/// <returns>The scripting type or invalid type if missing.</returns>
static ScriptingTypeHandle FindScriptingType(const StringAnsiView& fullname);
/// <summary>
/// Creates a new instance of the given class object (native construction).
/// </summary>
/// <param name="type">The Managed type class.</param>
/// <returns>The created object or null if failed.</returns>
static ScriptingObject* NewObject(const MClass* type);
public:
typedef Dictionary<Guid, Guid, HeapAllocation> IdsMappingTable;

View File

@@ -319,6 +319,7 @@ struct FLAXENGINE_API ScriptingType
void SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex);
void HackObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex);
String ToString() const;
StringAnsiView GetName() const;
};
/// <summary>

View File

@@ -418,6 +418,9 @@ namespace Flax.Build.Bindings
case "CultureInfo":
type = "void*";
return "MUtils::ToManaged({0})";
case "Version":
type = "MonoObject*";
return "MUtils::ToManaged({0})";
default:
// Object reference property
if ((typeInfo.Type == "ScriptingObjectReference" ||
@@ -590,6 +593,9 @@ namespace Flax.Build.Bindings
case "CultureInfo":
type = "void*";
return "MUtils::ToNative({0})";
case "Version":
type = "MonoObject*";
return "MUtils::ToNative({0})";
default:
// Object reference property
if ((typeInfo.Type == "ScriptingObjectReference" ||