From d0c5ef1a1d1a041e1e18ac3d0977acf1bc6896f5 Mon Sep 17 00:00:00 2001 From: WiggleWizard <1405402+WiggleWizard@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:52:01 +0100 Subject: [PATCH 1/3] Initial infrastructure for C# scene callbacks in subsystems --- Nuake/Engine.cpp | 33 +++++++ Nuake/Engine.h | 4 + Nuake/src/Core/MulticastDelegate.h | 86 +++++++++++++++++++ Nuake/src/Scene/Scene.cpp | 4 + Nuake/src/Scene/Scene.h | 10 +++ Nuake/src/Scene/Systems/ScriptingSystem.cpp | 4 + Nuake/src/Scene/Systems/ScriptingSystem.h | 1 + Nuake/src/Scene/Systems/System.h | 9 ++ Nuake/src/Subsystems/EngineSubsystem.h | 7 ++ .../Subsystems/EngineSubsystemScriptable.cpp | 12 +++ .../Subsystems/EngineSubsystemScriptable.h | 3 + Nuake/src/Window.cpp | 7 +- Nuake/src/Window.h | 11 ++- NuakeNet/src/EngineSubsystem.cs | 3 + 14 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 Nuake/src/Core/MulticastDelegate.h diff --git a/Nuake/Engine.cpp b/Nuake/Engine.cpp index 52e4b895..20deae85 100644 --- a/Nuake/Engine.cpp +++ b/Nuake/Engine.cpp @@ -40,6 +40,8 @@ namespace Nuake void Engine::Init() { + Window::Get()->OnWindowSetScene().AddStatic(&Engine::OnWindowSetScene); + ScriptingEngineNet::Get().AddListener(&Engine::OnScriptingEngineGameAssemblyLoaded); AudioManager::Get().Initialize(); @@ -257,6 +259,15 @@ namespace Nuake return std::reinterpret_pointer_cast(subsystems[subsystemId]); } + void Engine::OnWindowSetScene(Ref scene) + { + if (scene != nullptr) + { + scene->OnPreInitialize().AddStatic(&Engine::OnScenePreInitialize, scene); + scene->OnPostInitialize().AddStatic(&Engine::OnScenePostInitialize, scene); + } + } + void Engine::InitializeCoreSubsystems() { } @@ -296,6 +307,28 @@ namespace Nuake } } + void Engine::OnScenePreInitialize(Ref scene) + { + for (auto subsystem : subsystems) + { + if (subsystem == nullptr) + continue; + + subsystem->OnScenePreInitialize(scene); + } + } + + void Engine::OnScenePostInitialize(Ref scene) + { + for (auto subsystem : subsystems) + { + if (subsystem == nullptr) + continue; + + subsystem->OnScenePostInitialize(scene); + } + } + bool Engine::LoadProject(Ref project) { currentProject = project; diff --git a/Nuake/Engine.h b/Nuake/Engine.h index d20ca984..00bc623e 100644 --- a/Nuake/Engine.h +++ b/Nuake/Engine.h @@ -58,9 +58,13 @@ namespace Nuake static Ref GetScriptedSubsystem(const int subsystemId); protected: + static void OnWindowSetScene(Ref scene); static void InitializeCoreSubsystems(); static void OnScriptingEngineGameAssemblyLoaded(); + static void OnScenePreInitialize(Ref scene); + static void OnScenePostInitialize(Ref scene); + private: static Ref currentWindow; static Ref currentProject; diff --git a/Nuake/src/Core/MulticastDelegate.h b/Nuake/src/Core/MulticastDelegate.h new file mode 100644 index 00000000..fb2a3160 --- /dev/null +++ b/Nuake/src/Core/MulticastDelegate.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include + +#define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName; + +template +class MulticastDelegate +{ +public: + using DelegateID = size_t; + + // Add a callable with bound variables (supports no arguments as well) + template + DelegateID AddStatic(Callable&& func, BoundArgs&&... boundArgs) + { + DelegateID id = nextID++; + auto boundFunction = [=](Args... args) { + if constexpr (sizeof...(Args) > 0) + { + func(boundArgs..., std::forward(args)...); + } + else + { + func(boundArgs...); + } + }; + delegates.push_back({id, boundFunction}); + return id; + } + + template + DelegateID AddObject(Callable&& func, Obj* object, BoundArgs&&... boundArgs) + { + DelegateID id = nextID++; + auto boundFunction = [=](Args... args) { + if constexpr (sizeof...(Args) > 0) + { + (object->*func)(boundArgs..., std::forward(args)...); + } + else + { + (object->*func)(boundArgs...); + } + }; + delegates.push_back({id, boundFunction}); + return id; + } + + // Remove a callable using the token returned by Add() + void Remove(DelegateID id) + { + auto it = std::remove_if(delegates.begin(), delegates.end(), [id](const auto& pair) + { + return pair.first == id; + }); + + if (it != delegates.end()) + { + delegates.erase(it, delegates.end()); + } + } + + // Clear all delegates + void Clear() + { + delegates.clear(); + } + + // Invoke all callables + void Broadcast(Args... args) + { + for (auto& [id, delegate] : delegates) + { + delegate(std::forward(args)...); + } + } + +private: + using DelegatePair = std::pair>; + + std::vector delegates; // Vector of (ID, callable) pairs + DelegateID nextID = 0; // Unique ID generator +}; diff --git a/Nuake/src/Scene/Scene.cpp b/Nuake/src/Scene/Scene.cpp index 09cc2154..4cdb06da 100644 --- a/Nuake/src/Scene/Scene.cpp +++ b/Nuake/src/Scene/Scene.cpp @@ -243,6 +243,8 @@ namespace Nuake bool Scene::OnInit() { + preInitializeDelegate.Broadcast(); + for (auto& system : m_Systems) { if (!system->Init()) @@ -250,6 +252,8 @@ namespace Nuake return false; } } + + postInitializeDelegate.Broadcast(); return true; } diff --git a/Nuake/src/Scene/Scene.h b/Nuake/src/Scene/Scene.h index aa3e6ff7..fadf220a 100644 --- a/Nuake/src/Scene/Scene.h +++ b/Nuake/src/Scene/Scene.h @@ -19,6 +19,9 @@ namespace Nuake class Entity; class SceneRenderer; + DECLARE_MULTICAST_DELEGATE(PreInitializeDelegate) + DECLARE_MULTICAST_DELEGATE(PostInitializeDelegate) + class Scene : public ISerializable { friend Entity; @@ -92,6 +95,13 @@ namespace Nuake // Component specific utilies void CreateSkeleton(Entity& entity); + PreInitializeDelegate& OnPreInitialize() { return preInitializeDelegate; } + PostInitializeDelegate& OnPostInitialize() { return postInitializeDelegate; } + + protected: + PreInitializeDelegate preInitializeDelegate; + PostInitializeDelegate postInitializeDelegate; + private: void CreateSkeletonTraverse(Entity& entity, SkeletonNode& skeletonNode); }; diff --git a/Nuake/src/Scene/Systems/ScriptingSystem.cpp b/Nuake/src/Scene/Systems/ScriptingSystem.cpp index 6aaaae8c..9c094ea2 100644 --- a/Nuake/src/Scene/Systems/ScriptingSystem.cpp +++ b/Nuake/src/Scene/Systems/ScriptingSystem.cpp @@ -18,6 +18,8 @@ namespace Nuake { Logger::Log("Initializing ScriptingSystem"); + preInitDelegate.Broadcast(); + auto& scriptingEngineNet = ScriptingEngineNet::Get(); scriptingEngineNet.Uninitialize(); scriptingEngineNet.Initialize(); @@ -70,6 +72,8 @@ namespace Nuake } } + postInitDelegate.Broadcast(); + return true; } diff --git a/Nuake/src/Scene/Systems/ScriptingSystem.h b/Nuake/src/Scene/Systems/ScriptingSystem.h index 106e7ba8..20f46794 100644 --- a/Nuake/src/Scene/Systems/ScriptingSystem.h +++ b/Nuake/src/Scene/Systems/ScriptingSystem.h @@ -1,5 +1,6 @@ #pragma once #include "System.h" +#include "src/Core/MulticastDelegate.h" namespace Nuake { class Scene; diff --git a/Nuake/src/Scene/Systems/System.h b/Nuake/src/Scene/Systems/System.h index 382fa84d..fa954e7a 100644 --- a/Nuake/src/Scene/Systems/System.h +++ b/Nuake/src/Scene/Systems/System.h @@ -1,6 +1,8 @@ #pragma once + #include "src/Core/Timestep.h" #include "src/Core/Core.h" +#include "src/Core/MulticastDelegate.h" namespace Nuake { @@ -17,5 +19,12 @@ namespace Nuake virtual void FixedUpdate(Timestep ts) = 0; virtual void EditorUpdate() {} virtual void Exit() = 0; + + MulticastDelegate<>& OnPreInit() { return preInitDelegate; } + MulticastDelegate<>& OnPostInit() { return postInitDelegate; } + + protected: + MulticastDelegate<> preInitDelegate; + MulticastDelegate<> postInitDelegate; }; } diff --git a/Nuake/src/Subsystems/EngineSubsystem.h b/Nuake/src/Subsystems/EngineSubsystem.h index f1c25b30..3fcc93b5 100644 --- a/Nuake/src/Subsystems/EngineSubsystem.h +++ b/Nuake/src/Subsystems/EngineSubsystem.h @@ -1,11 +1,15 @@ #pragma once +#include "src/Core/Core.h" + /** * Specific type of subsystem that runs within the context of the engine, being created at the start of the * engine's lifetime and destroyed just before the engine shuts down. */ namespace Nuake { + class Scene; + class EngineSubsystem { public: @@ -15,6 +19,9 @@ namespace Nuake virtual void Initialize() {} virtual void Tick(float deltaTime) {} + virtual void OnScenePreInitialize(Ref scene) {} + virtual void OnScenePostInitialize(Ref scene) {} + private: bool canEverTick = false; }; diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp index 4076ae5f..c5d8a17d 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp @@ -29,6 +29,18 @@ void EngineSubsystemScriptable::Tick(float deltaTime) cSharpObjectInstance.InvokeMethod("OnTick", deltaTime); } + +void EngineSubsystemScriptable::OnScenePreInitialize(Ref scene) +{ +} + +void EngineSubsystemScriptable::OnScenePostInitialize(Ref scene) +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("OnScenePostInitialize", scene); +} } diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.h b/Nuake/src/Subsystems/EngineSubsystemScriptable.h index 69acae01..14823401 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.h +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.h @@ -19,6 +19,9 @@ namespace Nuake virtual void Initialize() override; virtual void Tick(float deltaTime) override; + virtual void OnScenePreInitialize(Ref scene) override; + virtual void OnScenePostInitialize(Ref scene) override; + private: Coral::ManagedObject cSharpObjectInstance; }; diff --git a/Nuake/src/Window.cpp b/Nuake/src/Window.cpp index 18508d7a..fc03b7ff 100644 --- a/Nuake/src/Window.cpp +++ b/Nuake/src/Window.cpp @@ -303,9 +303,12 @@ Ref Window::GetScene() return this->scene; } -bool Window::SetScene(Ref scene) +bool Window::SetScene(Ref newScene) { - this->scene = scene; + scene = newScene; + + windowSetSceneDelegate.Broadcast(newScene); + return true; } diff --git a/Nuake/src/Window.h b/Nuake/src/Window.h index 63817314..b62bd186 100644 --- a/Nuake/src/Window.h +++ b/Nuake/src/Window.h @@ -1,11 +1,12 @@ #pragma once + #include "Core/Core.h" #include "Core/Maths.h" +#include "Core/MulticastDelegate.h" #include "Core/Timestep.h" #include - struct GLFWwindow; namespace Nuake @@ -13,6 +14,8 @@ namespace Nuake class Scene; class FrameBuffer; + DECLARE_MULTICAST_DELEGATE(OnWindowSetSceneDelegate, Ref) + class Window { public: @@ -44,7 +47,7 @@ namespace Nuake void Center(); Ref GetScene(); - bool SetScene(Ref scene); + bool SetScene(Ref newScene); void SetTitle(const std::string& title); std::string GetTitle(); @@ -63,6 +66,8 @@ namespace Nuake void SetOnWindowClosedCallback(std::function callback); void SetOnDragNDropCallback(std::function& paths)> callback); + OnWindowSetSceneDelegate& OnWindowSetScene() { return windowSetSceneDelegate; } + private: const std::string DEFAULT_TITLE = "Untitled Window"; const uint32_t DEFAULT_WIDTH = 1280; @@ -85,6 +90,8 @@ namespace Nuake void InitImgui(); + OnWindowSetSceneDelegate windowSetSceneDelegate; + private: }; } diff --git a/NuakeNet/src/EngineSubsystem.cs b/NuakeNet/src/EngineSubsystem.cs index dfb2eb67..09acd603 100644 --- a/NuakeNet/src/EngineSubsystem.cs +++ b/NuakeNet/src/EngineSubsystem.cs @@ -20,6 +20,9 @@ } public virtual void Initialize() {} + public virtual void OnScenePreInit(Scene scene) {} + public virtual void OnScenePostInit(Scene scene) {} + public virtual void OnSceneUnloaded(Scene scene) {} public virtual void OnTick(float deltaTime) {} } } From edce52bb17139a2a1d11cee2331ce02431914d4f Mon Sep 17 00:00:00 2001 From: WiggleWizard <1405402+WiggleWizard@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:50:52 +0100 Subject: [PATCH 2/3] Finished up MVP for scene callbacks --- Nuake/Engine.cpp | 19 +++++++++++--- Nuake/Engine.h | 2 +- Nuake/src/Subsystems/EngineSubsystem.h | 1 + .../Subsystems/EngineSubsystemScriptable.cpp | 18 ++++++++++++- .../Subsystems/EngineSubsystemScriptable.h | 1 + Nuake/src/Window.cpp | 4 +-- Nuake/src/Window.h | 4 ++- NuakeNet/src/EngineSubsystem.cs | 26 ++++++++++++++++--- 8 files changed, 63 insertions(+), 12 deletions(-) diff --git a/Nuake/Engine.cpp b/Nuake/Engine.cpp index 20deae85..93439389 100644 --- a/Nuake/Engine.cpp +++ b/Nuake/Engine.cpp @@ -259,12 +259,23 @@ namespace Nuake return std::reinterpret_pointer_cast(subsystems[subsystemId]); } - void Engine::OnWindowSetScene(Ref scene) + void Engine::OnWindowSetScene(Ref oldScene, Ref newScene) { - if (scene != nullptr) + // Inform the subsystems that we are going to destroy/swap out the old scene + for (auto subsystem : subsystems) { - scene->OnPreInitialize().AddStatic(&Engine::OnScenePreInitialize, scene); - scene->OnPostInitialize().AddStatic(&Engine::OnScenePostInitialize, scene); + if (subsystem == nullptr) + continue; + + subsystem->OnScenePreDestroy(oldScene); + } + + // Hook into when the internal pieces of the scene are just about to be ready and when the scene is finally + // ready to present (ie, all initialized and loaded). + if (newScene != nullptr) + { + newScene->OnPreInitialize().AddStatic(&Engine::OnScenePreInitialize, newScene); + newScene->OnPostInitialize().AddStatic(&Engine::OnScenePostInitialize, newScene); } } diff --git a/Nuake/Engine.h b/Nuake/Engine.h index 00bc623e..42ea5b20 100644 --- a/Nuake/Engine.h +++ b/Nuake/Engine.h @@ -58,7 +58,7 @@ namespace Nuake static Ref GetScriptedSubsystem(const int subsystemId); protected: - static void OnWindowSetScene(Ref scene); + static void OnWindowSetScene(Ref oldScene, Ref newScene); static void InitializeCoreSubsystems(); static void OnScriptingEngineGameAssemblyLoaded(); diff --git a/Nuake/src/Subsystems/EngineSubsystem.h b/Nuake/src/Subsystems/EngineSubsystem.h index 3fcc93b5..f3e94b97 100644 --- a/Nuake/src/Subsystems/EngineSubsystem.h +++ b/Nuake/src/Subsystems/EngineSubsystem.h @@ -21,6 +21,7 @@ namespace Nuake virtual void OnScenePreInitialize(Ref scene) {} virtual void OnScenePostInitialize(Ref scene) {} + virtual void OnScenePreDestroy(Ref scene) {} private: bool canEverTick = false; diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp index c5d8a17d..f0f3f240 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp @@ -1,5 +1,7 @@ #include "EngineSubsystemScriptable.h" +#include "Coral/Type.hpp" + namespace Nuake { @@ -32,6 +34,10 @@ void EngineSubsystemScriptable::Tick(float deltaTime) void EngineSubsystemScriptable::OnScenePreInitialize(Ref scene) { + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("InternalOnScenePreInitialize"); } void EngineSubsystemScriptable::OnScenePostInitialize(Ref scene) @@ -39,9 +45,19 @@ void EngineSubsystemScriptable::OnScenePostInitialize(Ref scene) if (!cSharpObjectInstance.IsValid()) return; - cSharpObjectInstance.InvokeMethod("OnScenePostInitialize", scene); + cSharpObjectInstance.InvokeMethod("InternalOnSceneReady"); } + +void EngineSubsystemScriptable::OnScenePreDestroy(Ref scene) +{ + if (!cSharpObjectInstance.IsValid()) + return; + + if (cSharpObjectInstance.GetType().GetTypeId() == -1) + return; + cSharpObjectInstance.InvokeMethod("InternalOnScenePreDestroy"); +} } diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.h b/Nuake/src/Subsystems/EngineSubsystemScriptable.h index 14823401..2934431c 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.h +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.h @@ -21,6 +21,7 @@ namespace Nuake virtual void OnScenePreInitialize(Ref scene) override; virtual void OnScenePostInitialize(Ref scene) override; + virtual void OnScenePreDestroy(Ref scene) override; private: Coral::ManagedObject cSharpObjectInstance; diff --git a/Nuake/src/Window.cpp b/Nuake/src/Window.cpp index fc03b7ff..81b90c70 100644 --- a/Nuake/src/Window.cpp +++ b/Nuake/src/Window.cpp @@ -305,9 +305,9 @@ Ref Window::GetScene() bool Window::SetScene(Ref newScene) { + windowSetSceneDelegate.Broadcast(scene, newScene); + scene = newScene; - - windowSetSceneDelegate.Broadcast(newScene); return true; } diff --git a/Nuake/src/Window.h b/Nuake/src/Window.h index b62bd186..c40e1f7f 100644 --- a/Nuake/src/Window.h +++ b/Nuake/src/Window.h @@ -14,7 +14,7 @@ namespace Nuake class Scene; class FrameBuffer; - DECLARE_MULTICAST_DELEGATE(OnWindowSetSceneDelegate, Ref) + DECLARE_MULTICAST_DELEGATE(OnWindowSetSceneDelegate, Ref, Ref) class Window { @@ -66,6 +66,8 @@ namespace Nuake void SetOnWindowClosedCallback(std::function callback); void SetOnDragNDropCallback(std::function& paths)> callback); + // Delegate is broadcasted BEFORE the actual internal scene has been reassigned, this is to keep + // the potential old scene relevant before its ultimate destruction. OnWindowSetSceneDelegate& OnWindowSetScene() { return windowSetSceneDelegate; } private: diff --git a/NuakeNet/src/EngineSubsystem.cs b/NuakeNet/src/EngineSubsystem.cs index 09acd603..a4fddc71 100644 --- a/NuakeNet/src/EngineSubsystem.cs +++ b/NuakeNet/src/EngineSubsystem.cs @@ -20,9 +20,29 @@ } public virtual void Initialize() {} - public virtual void OnScenePreInit(Scene scene) {} - public virtual void OnScenePostInit(Scene scene) {} - public virtual void OnSceneUnloaded(Scene scene) {} + + public virtual void OnScenePreInitialize(Scene scene) {} + public virtual void OnSceneReady(Scene scene) {} + public virtual void OnScenePreDestroy(Scene scene) {} + public virtual void OnTick(float deltaTime) {} + + // Since the engine doesn't have the concept of scene instances, we just pass + // a new `Scene` here since all functions in Scene are statics. This is largely + // to keep the API a little more stable going forward. + private void InternalOnScenePreInitialize() + { + OnScenePreInitialize(new Scene()); + } + + private void InternalOnSceneReady() + { + OnSceneReady(new Scene()); + } + + private void InternalOnScenePreDestroy() + { + OnScenePreDestroy(new Scene()); + } } } From a487d31c485ec374bb0e2a829e32d477f20538f1 Mon Sep 17 00:00:00 2001 From: WiggleWizard <1405402+WiggleWizard@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:43:04 +0100 Subject: [PATCH 3/3] Completed work on exposing subsystems to scene events --- Nuake/Engine.cpp | 2 +- Nuake/Engine.h | 2 + Nuake/src/Core/MulticastDelegate.h | 100 +++++++++++++----- Nuake/src/Scripting/ScriptingEngineNet.cpp | 16 +-- Nuake/src/Scripting/ScriptingEngineNet.h | 10 +- Nuake/src/Subsystems/EngineSubsystem.cpp | 19 ++-- Nuake/src/Subsystems/EngineSubsystem.h | 4 +- .../Subsystems/EngineSubsystemScriptable.cpp | 28 ++++- .../Subsystems/EngineSubsystemScriptable.h | 8 +- 9 files changed, 134 insertions(+), 55 deletions(-) diff --git a/Nuake/Engine.cpp b/Nuake/Engine.cpp index 93439389..5dec3ecd 100644 --- a/Nuake/Engine.cpp +++ b/Nuake/Engine.cpp @@ -42,7 +42,7 @@ namespace Nuake { Window::Get()->OnWindowSetScene().AddStatic(&Engine::OnWindowSetScene); - ScriptingEngineNet::Get().AddListener(&Engine::OnScriptingEngineGameAssemblyLoaded); + ScriptingEngineNet::Get().OnGameAssemblyLoaded().AddStatic(&Engine::OnScriptingEngineGameAssemblyLoaded); AudioManager::Get().Initialize(); PhysicsManager::Get().Init(); diff --git a/Nuake/Engine.h b/Nuake/Engine.h index 42ea5b20..c9563837 100644 --- a/Nuake/Engine.h +++ b/Nuake/Engine.h @@ -60,6 +60,8 @@ namespace Nuake protected: static void OnWindowSetScene(Ref oldScene, Ref newScene); static void InitializeCoreSubsystems(); + + static void OnScriptingEngineUninitialize(); static void OnScriptingEngineGameAssemblyLoaded(); static void OnScenePreInitialize(Ref scene); diff --git a/Nuake/src/Core/MulticastDelegate.h b/Nuake/src/Core/MulticastDelegate.h index fb2a3160..ac5349e2 100644 --- a/Nuake/src/Core/MulticastDelegate.h +++ b/Nuake/src/Core/MulticastDelegate.h @@ -2,21 +2,32 @@ #include #include -#include -#define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName; +#define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName; + +struct DelegateHandle +{ + size_t id = InvalidHandle; + + static inline size_t InvalidHandle = static_cast(-1); + + bool IsValid() const { return id != InvalidHandle; } + void Reset() { id = InvalidHandle; } + + // Comparison operators for convenience + bool operator==(const DelegateHandle& other) const { return id == other.id; } + bool operator!=(const DelegateHandle& other) const { return id != other.id; } +}; template class MulticastDelegate { public: - using DelegateID = size_t; - // Add a callable with bound variables (supports no arguments as well) template - DelegateID AddStatic(Callable&& func, BoundArgs&&... boundArgs) + DelegateHandle AddStatic(Callable&& func, BoundArgs&&... boundArgs) { - DelegateID id = nextID++; + size_t id = GetNextID(); auto boundFunction = [=](Args... args) { if constexpr (sizeof...(Args) > 0) { @@ -27,14 +38,14 @@ public: func(boundArgs...); } }; - delegates.push_back({id, boundFunction}); - return id; + SetDelegate(id, boundFunction); + return DelegateHandle{ id }; } - template - DelegateID AddObject(Callable&& func, Obj* object, BoundArgs&&... boundArgs) + template + DelegateHandle AddRaw(Obj* object, Callable&& func, BoundArgs&&... boundArgs) { - DelegateID id = nextID++; + size_t id = GetNextID(); auto boundFunction = [=](Args... args) { if constexpr (sizeof...(Args) > 0) { @@ -45,42 +56,79 @@ public: (object->*func)(boundArgs...); } }; - delegates.push_back({id, boundFunction}); - return id; + SetDelegate(id, boundFunction); + return DelegateHandle{ id }; } // Remove a callable using the token returned by Add() - void Remove(DelegateID id) + void Remove(DelegateHandle& handle) { - auto it = std::remove_if(delegates.begin(), delegates.end(), [id](const auto& pair) - { - return pair.first == id; - }); + ASSERT(handle.IsValid()); - if (it != delegates.end()) + if (handle.IsValid() && handle.id < delegates.size()) { - delegates.erase(it, delegates.end()); + delegates[handle.id].active = false; + + // Mark this slot as reusable + freeIDs.push_back(handle.id); } + + // Invalidate the handle + handle.Reset(); } // Clear all delegates void Clear() { delegates.clear(); + freeIDs.clear(); + nextID = 0; } // Invoke all callables void Broadcast(Args... args) { - for (auto& [id, delegate] : delegates) + for (auto& delegate : delegates) { - delegate(std::forward(args)...); + if (delegate.active) + { + delegate.function(std::forward(args)...); + } } } private: - using DelegatePair = std::pair>; - - std::vector delegates; // Vector of (ID, callable) pairs - DelegateID nextID = 0; // Unique ID generator + struct Delegate + { + bool active = false; + std::function function; + }; + + // A vector of delegates with active state + std::vector delegates; + // List of reusable slots + std::vector freeIDs; + size_t nextID = 0; + + // Get the next available ID, either by reusing a free slot or by creating a new one + size_t GetNextID() + { + if (!freeIDs.empty()) + { + size_t id = freeIDs.back(); + freeIDs.pop_back(); + return id; + } + + return nextID++; + } + + // Set the delegate in the vector, makes the array larger if necessary + void SetDelegate(size_t id, const std::function& func) + { + if (id >= delegates.size()) + delegates.resize(id + 1); + + delegates[id] = { true, func }; + } }; diff --git a/Nuake/src/Scripting/ScriptingEngineNet.cpp b/Nuake/src/Scripting/ScriptingEngineNet.cpp index 75a404a5..b4371a6c 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.cpp +++ b/Nuake/src/Scripting/ScriptingEngineNet.cpp @@ -203,6 +203,8 @@ namespace Nuake managedObject.Destroy(); } + onUninitializeDelegate.Broadcast(); + Coral::GC::Collect(); Coral::GC::WaitForPendingFinalizers(); @@ -331,15 +333,6 @@ namespace Nuake return widgetUUIDToManagedObjects[std::make_pair(canvasUUID, uuid)]; } - template - void ScriptingEngineNet::AddListener(const T& delegate) {} - - template <> - void ScriptingEngineNet::AddListener(const GameAssemblyLoadedDelegate& delegate) - { - listenersGameAssemblyLoaded.push_back(delegate); - } - std::vector ScriptingEngineNet::BuildProjectAssembly(Ref project) { const std::string sanitizedProjectName = String::Sanitize(project->Name); @@ -541,10 +534,7 @@ namespace Nuake } } - for (auto& delegate : listenersGameAssemblyLoaded) - { - delegate(); - } + onGameAssemblyLoadedDelegate.Broadcast(); } } diff --git a/Nuake/src/Scripting/ScriptingEngineNet.h b/Nuake/src/Scripting/ScriptingEngineNet.h index 01b8ca2d..6d5ea092 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.h +++ b/Nuake/src/Scripting/ScriptingEngineNet.h @@ -25,6 +25,9 @@ namespace Nuake { class Project; + DECLARE_MULTICAST_DELEGATE(OnGameAssemblyLoadedDelegate) + DECLARE_MULTICAST_DELEGATE(OnUninitializeDelegate) + enum class ExposedVarTypes { Bool, @@ -115,8 +118,8 @@ namespace Nuake std::unordered_map GetPointEntities() const { return pointEntityTypes; } std::unordered_map GetUIWidgets() const { return uiWidgets; } - template void AddListener(const T& delegate); - template<> void AddListener(const GameAssemblyLoadedDelegate& delegate); + OnGameAssemblyLoadedDelegate& OnUninitialize() { return onUninitializeDelegate; } + OnUninitializeDelegate& OnGameAssemblyLoaded() { return onGameAssemblyLoadedDelegate; } private: const std::string m_Scope = "Nuake.Net"; @@ -149,7 +152,8 @@ namespace Nuake std::unordered_map entityToManagedObjects; std::map, Coral::ManagedObject> widgetUUIDToManagedObjects; - std::vector listenersGameAssemblyLoaded; + OnGameAssemblyLoadedDelegate onGameAssemblyLoadedDelegate; + OnUninitializeDelegate onUninitializeDelegate; ScriptingEngineNet(); ~ScriptingEngineNet(); diff --git a/Nuake/src/Subsystems/EngineSubsystem.cpp b/Nuake/src/Subsystems/EngineSubsystem.cpp index d3dc51a1..06193c7b 100644 --- a/Nuake/src/Subsystems/EngineSubsystem.cpp +++ b/Nuake/src/Subsystems/EngineSubsystem.cpp @@ -1,11 +1,18 @@ #include "EngineSubsystem.h" -void Nuake::EngineSubsystem::SetCanTick(bool canTick) +#include "src/Scripting/ScriptingEngineNet.h" + +namespace Nuake { - canEverTick = canTick; + void EngineSubsystem::SetCanTick(bool canTick) + { + canEverTick = canTick; + } + + bool EngineSubsystem::CanEverTick() const + { + return canEverTick; + } } -bool Nuake::EngineSubsystem::CanEverTick() const -{ - return canEverTick; -} + diff --git a/Nuake/src/Subsystems/EngineSubsystem.h b/Nuake/src/Subsystems/EngineSubsystem.h index f3e94b97..180190ea 100644 --- a/Nuake/src/Subsystems/EngineSubsystem.h +++ b/Nuake/src/Subsystems/EngineSubsystem.h @@ -23,7 +23,9 @@ namespace Nuake virtual void OnScenePostInitialize(Ref scene) {} virtual void OnScenePreDestroy(Ref scene) {} - private: + protected: + void OnScriptEngineUninitialize(); + bool canEverTick = false; }; } diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp index f0f3f240..6feaa93d 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp @@ -1,6 +1,8 @@ #include "EngineSubsystemScriptable.h" -#include "Coral/Type.hpp" +#include "src/Scripting/ScriptingEngineNet.h" + +#include namespace Nuake { @@ -11,6 +13,16 @@ EngineSubsystemScriptable::EngineSubsystemScriptable(const Coral::ManagedObject& } +EngineSubsystemScriptable::~EngineSubsystemScriptable() +{ + if (!cSharpObjectInstance.IsValid()) + return; + + ScriptingEngineNet::Get().OnUninitialize().Remove(scriptEngineUninitializeDelegateHandle); + + cSharpObjectInstance.Destroy(); +} + Coral::ManagedObject& EngineSubsystemScriptable::GetManagedObjectInstance() { return cSharpObjectInstance; @@ -18,6 +30,8 @@ Coral::ManagedObject& EngineSubsystemScriptable::GetManagedObjectInstance() void EngineSubsystemScriptable::Initialize() { + scriptEngineUninitializeDelegateHandle = ScriptingEngineNet::Get().OnUninitialize().AddRaw(this, &EngineSubsystemScriptable::OnScriptEngineUninitialize); + if (!cSharpObjectInstance.IsValid()) return; @@ -52,12 +66,18 @@ void EngineSubsystemScriptable::OnScenePreDestroy(Ref scene) { if (!cSharpObjectInstance.IsValid()) return; - - if (cSharpObjectInstance.GetType().GetTypeId() == -1) - return; cSharpObjectInstance.InvokeMethod("InternalOnScenePreDestroy"); } + +void EngineSubsystemScriptable::OnScriptEngineUninitialize() +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.Destroy(); +} + } diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.h b/Nuake/src/Subsystems/EngineSubsystemScriptable.h index 2934431c..91c0f19c 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.h +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.h @@ -1,6 +1,7 @@ #pragma once #include "EngineSubsystem.h" +#include "src/Core/MulticastDelegate.h" #include @@ -13,6 +14,7 @@ namespace Nuake { public: EngineSubsystemScriptable(const Coral::ManagedObject& object); + virtual ~EngineSubsystemScriptable(); Coral::ManagedObject& GetManagedObjectInstance(); @@ -23,7 +25,11 @@ namespace Nuake virtual void OnScenePostInitialize(Ref scene) override; virtual void OnScenePreDestroy(Ref scene) override; - private: + protected: + void OnScriptEngineUninitialize(); + + DelegateHandle scriptEngineUninitializeDelegateHandle; + Coral::ManagedObject cSharpObjectInstance; }; }