From bdb69d57dd4cc37513105d1c6ecb8b59f7c66647 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 Jul 2022 21:05:03 +0200 Subject: [PATCH] Refactor Plugins system to support plugins in C++ scripts --- Source/Editor/Cooker/GameCooker.cs | 4 +- Source/Editor/States/PlayingState.cs | 4 +- Source/Editor/Windows/PluginsWindow.cs | 10 +- Source/Engine/Core/Types/Version.cpp | 24 ++ Source/Engine/Core/Types/Version.h | 30 +- Source/Engine/Level/Level.cpp | 8 +- Source/Engine/Scripting/BinaryModule.cpp | 15 +- Source/Engine/Scripting/BinaryModule.h | 2 +- Source/Engine/Scripting/ManagedCLR/MUtils.cpp | 15 + Source/Engine/Scripting/ManagedCLR/MUtils.h | 5 + Source/Engine/Scripting/Plugins/GamePlugin.cs | 24 -- Source/Engine/Scripting/Plugins/GamePlugin.h | 25 ++ Source/Engine/Scripting/Plugins/Plugin.cs | 44 --- Source/Engine/Scripting/Plugins/Plugin.h | 46 +++ .../Scripting/Plugins/PluginDescription.cs | 62 ---- .../Scripting/Plugins/PluginDescription.h | 65 +++++ .../Scripting/Plugins/PluginManager.cpp | 221 +++++++++++--- .../Engine/Scripting/Plugins/PluginManager.cs | 271 +----------------- .../Engine/Scripting/Plugins/PluginManager.h | 73 ++++- Source/Engine/Scripting/Scripting.cpp | 45 ++- Source/Engine/Scripting/Scripting.h | 7 + Source/Engine/Scripting/ScriptingType.h | 1 + .../Bindings/BindingsGenerator.Cpp.cs | 6 + 23 files changed, 522 insertions(+), 485 deletions(-) delete mode 100644 Source/Engine/Scripting/Plugins/GamePlugin.cs create mode 100644 Source/Engine/Scripting/Plugins/GamePlugin.h delete mode 100644 Source/Engine/Scripting/Plugins/Plugin.cs create mode 100644 Source/Engine/Scripting/Plugins/Plugin.h delete mode 100644 Source/Engine/Scripting/Plugins/PluginDescription.cs create mode 100644 Source/Engine/Scripting/Plugins/PluginDescription.h diff --git a/Source/Editor/Cooker/GameCooker.cs b/Source/Editor/Cooker/GameCooker.cs index f23a38103..6176d6d4b 100644 --- a/Source/Editor/Cooker/GameCooker.cs +++ b/Source/Editor/Cooker/GameCooker.cs @@ -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) diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 334179ec6..7915069ca 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -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(); diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 303a38e13..68c00c281 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -190,9 +190,11 @@ namespace FlaxEditor.Windows private void OnPluginsChanged() { List 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(); @@ -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]; } diff --git a/Source/Engine/Core/Types/Version.cpp b/Source/Engine/Core/Types/Version.cpp index 1b78f56c6..a598307e4 100644 --- a/Source/Engine/Core/Types/Version.cpp +++ b/Source/Engine/Core/Types/Version.cpp @@ -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) diff --git a/Source/Engine/Core/Types/Version.h b/Source/Engine/Core/Types/Version.h index 189351865..d71e76551 100644 --- a/Source/Engine/Core/Types/Version.h +++ b/Source/Engine/Core/Types/Version.h @@ -9,7 +9,7 @@ /// /// Represents the version number made of major, minor, build and revision numbers. /// -struct FLAXENGINE_API Version +API_STRUCT(InBuild) struct FLAXENGINE_API Version { private: int32 _major; @@ -25,13 +25,7 @@ public: /// The minor version number. /// The build number. /// The revision number. - 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); /// /// Initializes a new instance of the Version class using the specified major, minor, and build values. @@ -39,26 +33,14 @@ public: /// The major version number. /// The minor version number. /// The build number. - 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); /// /// Initializes a new instance of the Version class using the specified major and minor values. /// /// The major version number. /// The minor version number. - Version(int32 major, int32 minor) - { - _major = Math::Max(major, 0); - _minor = Math::Max(minor, 0); - _build = -1; - _revision = -1; - } + Version(int32 major, int32 minor); /// /// 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: diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index c60bf6cc6..db7c52aab 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -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; } diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 4eee9ee84..8a7f8ddbe 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -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(this); assembly->Loaded.Bind(this); - assembly->Unloading.Bind(this); + assembly->Unloaded.Bind(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(); diff --git a/Source/Engine/Scripting/BinaryModule.h b/Source/Engine/Scripting/BinaryModule.h index 73144c449..ede625cc1 100644 --- a/Source/Engine/Scripting/BinaryModule.h +++ b/Source/Engine/Scripting/BinaryModule.h @@ -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: diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index f7c81ba28..1b0c17386 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -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 diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index e60a9b9fb..4fe2b55a8 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -14,6 +14,8 @@ #include #include +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 diff --git a/Source/Engine/Scripting/Plugins/GamePlugin.cs b/Source/Engine/Scripting/Plugins/GamePlugin.cs deleted file mode 100644 index 9c95d4f91..000000000 --- a/Source/Engine/Scripting/Plugins/GamePlugin.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. - -namespace FlaxEngine -{ - /// - /// Base class for all plugins used at runtime in game. - /// - /// - /// Plugins should have a public and parameter-less constructor. - /// - /// - public abstract class GamePlugin : Plugin - { -#if FLAX_EDITOR - /// - /// Event called during game cooking in Editor to collect any assets that this plugin uses. Can be used to inject content for plugins. - /// - /// The result assets list (always valid). - public virtual void OnCollectAssets(System.Collections.Generic.List assets) - { - } -#endif - } -} diff --git a/Source/Engine/Scripting/Plugins/GamePlugin.h b/Source/Engine/Scripting/Plugins/GamePlugin.h new file mode 100644 index 000000000..d616c2f11 --- /dev/null +++ b/Source/Engine/Scripting/Plugins/GamePlugin.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Plugin.h" +#include "Engine/Core/Collections/Array.h" + +/// +/// Base class for all plugins used at runtime in game. +/// +API_CLASS(Abstract) class FLAXENGINE_API GamePlugin : public Plugin +{ + DECLARE_SCRIPTING_TYPE(GamePlugin); +public: +#if USE_EDITOR + /// + /// Function called during game cooking in Editor to collect any assets that this plugin uses. Can be used to inject content for plugins. + /// + /// The result assets list. + API_FUNCTION() virtual Array GetReferences() const + { + return Array(); + } +#endif +}; diff --git a/Source/Engine/Scripting/Plugins/Plugin.cs b/Source/Engine/Scripting/Plugins/Plugin.cs deleted file mode 100644 index b83969d3a..000000000 --- a/Source/Engine/Scripting/Plugins/Plugin.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// Base class for game engine editor plugins. - /// - /// - /// Plugins should have a public and parameter-less constructor. - /// - public abstract class Plugin - { - internal bool _initialized; - - /// - /// Gets the description. - /// - /// - /// Plugin description should be a constant part of the plugin created in constructor and valid before calling . - /// - public virtual PluginDescription Description => new PluginDescription - { - Name = GetType().Name, - Category = "Other", - Version = new Version(1, 0), - }; - - /// - /// Initialization method called when this plugin is loaded to the memory and can be used. - /// - public virtual void Initialize() - { - } - - /// - /// Cleanup method called when this plugin is being unloaded or reloaded or engine is closing. - /// - public virtual void Deinitialize() - { - } - } -} diff --git a/Source/Engine/Scripting/Plugins/Plugin.h b/Source/Engine/Scripting/Plugins/Plugin.h new file mode 100644 index 000000000..380b9a3c2 --- /dev/null +++ b/Source/Engine/Scripting/Plugins/Plugin.h @@ -0,0 +1,46 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "PluginDescription.h" +#include "Engine/Scripting/ScriptingObject.h" + +/// +/// Base class for game engine editor plugins. +/// +API_CLASS(Abstract) class FLAXENGINE_API Plugin : public ScriptingObject +{ + DECLARE_SCRIPTING_TYPE(Plugin); +private: + friend class PluginManagerService; + bool _initialized = false; + +protected: + /// + /// Plugin description. Should be a constant part of the plugin created in constructor and valid before calling . + /// + API_FIELD() PluginDescription _description; + +public: + /// + /// Gets the description. + /// + API_PROPERTY() const PluginDescription& GetDescription() const + { + return _description; + } + + /// + /// Initialization method called when this plugin is loaded to the memory and can be used. + /// + API_FUNCTION() virtual void Initialize() + { + } + + /// + /// Cleanup method called when this plugin is being unloaded or reloaded or engine is closing. + /// + API_FUNCTION() virtual void Deinitialize() + { + } +}; diff --git a/Source/Engine/Scripting/Plugins/PluginDescription.cs b/Source/Engine/Scripting/Plugins/PluginDescription.cs deleted file mode 100644 index ee66576d4..000000000 --- a/Source/Engine/Scripting/Plugins/PluginDescription.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. - -using System; - -namespace FlaxEngine -{ - /// - /// The engine plugin description container. - /// - public struct PluginDescription - { - /// - /// The name of the plugin. - /// - public string Name; - - /// - /// The version of the plugin. - /// - public Version Version; - - /// - /// The name of the author of the plugin. - /// - public string Author; - - /// - /// The plugin author website URL. - /// - public string AuthorUrl; - - /// - /// The homepage URL for the plugin. - /// - public string HomepageUrl; - - /// - /// The plugin repository URL (for open-source plugins). - /// - public string RepositoryUrl; - - /// - /// The plugin description. - /// - public string Description; - - /// - /// The plugin category. - /// - public string Category; - - /// - /// True if plugin is during Beta tests (before release). - /// - public bool IsBeta; - - /// - /// True if plugin is during Alpha tests (early version). - /// - public bool IsAlpha; - } -} diff --git a/Source/Engine/Scripting/Plugins/PluginDescription.h b/Source/Engine/Scripting/Plugins/PluginDescription.h new file mode 100644 index 000000000..cf7ed7ffd --- /dev/null +++ b/Source/Engine/Scripting/Plugins/PluginDescription.h @@ -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" + +/// +/// The engine plugin description container. +/// +API_STRUCT() struct PluginDescription +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(PluginDescription); +public: + /// + /// The name of the plugin. + /// + API_FIELD() String Name; + + /// + /// The version of the plugin. + /// + API_FIELD() Version Version; + + /// + /// The name of the author of the plugin. + /// + API_FIELD() String Author; + + /// + /// The plugin author website URL. + /// + API_FIELD() String AuthorUrl; + + /// + /// The homepage URL for the plugin. + /// + API_FIELD() String HomepageUrl; + + /// + /// The plugin repository URL (for open-source plugins). + /// + API_FIELD() String RepositoryUrl; + + /// + /// The plugin description. + /// + API_FIELD() String Description; + + /// + /// The plugin category. + /// + API_FIELD() String Category; + + /// + /// True if plugin is during Beta tests (before release). + /// + API_FIELD() bool IsBeta = false; + + /// + /// True if plugin is during Alpha tests (early version). + /// + API_FIELD() bool IsAlpha = false; +}; diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cpp b/Source/Engine/Scripting/Plugins/PluginManager.cpp index ce2bd1e86..0d0fdcb12 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cpp +++ b/Source/Engine/Scripting/Plugins/PluginManager.cpp @@ -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 PluginManager::PluginLoading; +Delegate PluginManager::PluginLoaded; +Delegate PluginManager::PluginUnloading; +Delegate PluginManager::PluginUnloaded; +Action PluginManager::PluginsChanged; + namespace PluginManagerImpl { - MMethod* Internal_LoadPlugin = nullptr; + Array GamePlugins; + Array 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 PluginManagerService::InvokeInitialize(Plugin* plugin) +{ + if (plugin->_initialized) + return; + + 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; + + LOG(Info, "Unloading plugin {}", plugin->ToString()); + + PluginManager::PluginUnloading(plugin); + + plugin->Deinitialize(); + plugin->_initialized = false; + + PluginManager::PluginUnloaded(plugin); +} + void PluginManagerImpl::LoadPlugin(MClass* klass, bool isEditor) { -#if !COMPILE_WITHOUT_CSHARP - if (Internal_LoadPlugin == nullptr) - { - Internal_LoadPlugin = PluginManager::GetStaticClass()->GetMethod("Internal_LoadPlugin", 2); - ASSERT(Internal_LoadPlugin); - } + // Create and check if use it + auto plugin = (Plugin*)Scripting::NewObject(klass); + if (!plugin) + return; - MObject* exception = nullptr; - void* params[2]; - params[0] = MUtils::GetType(klass->GetNative()); - params[1] = &isEditor; - Internal_LoadPlugin->Invoke(nullptr, params, &exception); - if (exception) + if (!isEditor) { - DebugLog::LogException(exception); + 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(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); } -#endif + 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& 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; +} + +#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 diff --git a/Source/Engine/Scripting/Plugins/PluginManager.cs b/Source/Engine/Scripting/Plugins/PluginManager.cs index f592547bb..f376a0826 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.cs +++ b/Source/Engine/Scripting/Plugins/PluginManager.cs @@ -1,282 +1,17 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -using System; -using System.Collections.Generic; -using System.Reflection; - namespace FlaxEngine { - /// - /// Plugin related event delegate type. - /// - /// The plugin. - [HideInEditor] - public delegate void PluginDelegate(Plugin plugin); - - public static partial class PluginManager + partial class PluginManager { - private static readonly List _gamePlugins = new List(); - private static readonly List _editorPlugins = new List(); - - /// - /// Gets the game plugins. - /// - public static IReadOnlyList GamePlugins => _gamePlugins; - - /// - /// Gets the editor plugins. - /// - public static IReadOnlyList EditorPlugins => _editorPlugins; - - /// - /// Occurs before loading plugin. - /// - public static event PluginDelegate PluginLoading; - - /// - /// Occurs when plugin gets loaded and initialized. - /// - public static event PluginDelegate PluginLoaded; - - /// - /// Occurs before unloading plugin. - /// - public static event PluginDelegate PluginUnloading; - - /// - /// Occurs when plugin gets unloaded. It should not be used anymore. - /// - public static event PluginDelegate PluginUnloaded; - - /// - /// Occurs when plugins collection gets edited (added or removed plugin). - /// - public static event Action PluginsChanged; - - /// - /// Determines whether can load the specified plugin. - /// - /// The plugin description. - /// True if load it, otherwise false. - public delegate bool CanLoadPluginDelegate(ref PluginDescription pluginDesc); - - /// - /// Determines whether can load the specified plugin. - /// - public static CanLoadPluginDelegate CanLoadPlugin = DefaultCanLoadPlugin; - - /// - /// Plugin related event delegate type. - /// - /// The plugin. - public delegate void PluginDelegate(Plugin plugin); - - /// - /// The default implementation for . - /// - /// The plugin description. - /// True if load it, otherwise false. - 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); - } - } - /// /// Returns the first plugin of the provided type. /// /// The plugin type. /// The plugin, or null if not loaded. - public static T GetPlugin() where T : class + public static T GetPlugin() 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; - } - - /// - /// Return the first plugin using the provided name. - /// - /// /// The plugin name. - /// The plugin, or null if not loaded. - 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)); } } } diff --git a/Source/Engine/Scripting/Plugins/PluginManager.h b/Source/Engine/Scripting/Plugins/PluginManager.h index e4a2469ed..8640041a7 100644 --- a/Source/Engine/Scripting/Plugins/PluginManager.h +++ b/Source/Engine/Scripting/Plugins/PluginManager.h @@ -2,13 +2,80 @@ #pragma once -#include "Engine/Scripting/ScriptingType.h" -#include "Engine/Core/Collections/Array.h" +#include "GamePlugin.h" /// /// Game and Editor plugins management service. /// API_CLASS(Static) class PluginManager { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(PluginManager); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PluginManager); +public: + /// + /// Gets the game plugins. + /// + API_PROPERTY() static const Array& GetGamePlugins(); + + /// + /// Gets the editor plugins. + /// + API_PROPERTY() static const Array& GetEditorPlugins(); + +public: + /// + /// Occurs before loading plugin. + /// + API_EVENT() static Delegate PluginLoading; + + /// + /// Occurs when plugin gets loaded and initialized. + /// + API_EVENT() static Delegate PluginLoaded; + + /// + /// Occurs before unloading plugin. + /// + API_EVENT() static Delegate PluginUnloading; + + /// + /// Occurs when plugin gets unloaded. It should not be used anymore. + /// + API_EVENT() static Delegate PluginUnloaded; + + /// + /// Occurs when plugins collection gets edited (added or removed plugin). + /// + API_EVENT() static Action PluginsChanged; + +public: + /// + /// Return the first plugin using the provided name. + /// + // The plugin name. + /// The plugin, or null if not loaded. + API_FUNCTION() static Plugin* GetPlugin(const StringView& name); + + /// + /// Returns the first plugin of the provided type. + /// + /// Type of the plugin to search for. Includes any plugin base class derived from the type. + /// The plugin or null. + API_FUNCTION() static Plugin* GetPlugin(API_PARAM(Attributes="TypeReference(typeof(Plugin))") const MClass* type); + + /// + /// Returns the first plugin of the provided type. + /// + /// The plugin or null. + template + 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 }; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 1d9d9f932..d90a18c58 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -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); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index 31c1d21d4..81739e1c8 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -109,6 +109,13 @@ public: /// The scripting type or invalid type if missing. static ScriptingTypeHandle FindScriptingType(const StringAnsiView& fullname); + /// + /// Creates a new instance of the given class object (native construction). + /// + /// The Managed type class. + /// The created object or null if failed. + static ScriptingObject* NewObject(const MClass* type); + public: typedef Dictionary IdsMappingTable; diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index 1e674cbf2..118c81e89 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -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; }; /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e96365d8b..9d0e22401 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -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" ||