diff --git a/Nuake/Engine.cpp b/Nuake/Engine.cpp index 52e4b895..5dec3ecd 100644 --- a/Nuake/Engine.cpp +++ b/Nuake/Engine.cpp @@ -40,7 +40,9 @@ namespace Nuake void Engine::Init() { - ScriptingEngineNet::Get().AddListener(&Engine::OnScriptingEngineGameAssemblyLoaded); + Window::Get()->OnWindowSetScene().AddStatic(&Engine::OnWindowSetScene); + + ScriptingEngineNet::Get().OnGameAssemblyLoaded().AddStatic(&Engine::OnScriptingEngineGameAssemblyLoaded); AudioManager::Get().Initialize(); PhysicsManager::Get().Init(); @@ -257,6 +259,26 @@ namespace Nuake return std::reinterpret_pointer_cast(subsystems[subsystemId]); } + void Engine::OnWindowSetScene(Ref oldScene, Ref newScene) + { + // Inform the subsystems that we are going to destroy/swap out the old scene + for (auto subsystem : subsystems) + { + 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); + } + } + void Engine::InitializeCoreSubsystems() { } @@ -296,6 +318,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..c9563837 100644 --- a/Nuake/Engine.h +++ b/Nuake/Engine.h @@ -58,9 +58,15 @@ namespace Nuake static Ref GetScriptedSubsystem(const int subsystemId); protected: + static void OnWindowSetScene(Ref oldScene, Ref newScene); static void InitializeCoreSubsystems(); + + static void OnScriptingEngineUninitialize(); 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..ac5349e2 --- /dev/null +++ b/Nuake/src/Core/MulticastDelegate.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +#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: + // Add a callable with bound variables (supports no arguments as well) + template + DelegateHandle AddStatic(Callable&& func, BoundArgs&&... boundArgs) + { + size_t id = GetNextID(); + auto boundFunction = [=](Args... args) { + if constexpr (sizeof...(Args) > 0) + { + func(boundArgs..., std::forward(args)...); + } + else + { + func(boundArgs...); + } + }; + SetDelegate(id, boundFunction); + return DelegateHandle{ id }; + } + + template + DelegateHandle AddRaw(Obj* object, Callable&& func, BoundArgs&&... boundArgs) + { + size_t id = GetNextID(); + auto boundFunction = [=](Args... args) { + if constexpr (sizeof...(Args) > 0) + { + (object->*func)(boundArgs..., std::forward(args)...); + } + else + { + (object->*func)(boundArgs...); + } + }; + SetDelegate(id, boundFunction); + return DelegateHandle{ id }; + } + + // Remove a callable using the token returned by Add() + void Remove(DelegateHandle& handle) + { + ASSERT(handle.IsValid()); + + if (handle.IsValid() && handle.id < delegates.size()) + { + 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& delegate : delegates) + { + if (delegate.active) + { + delegate.function(std::forward(args)...); + } + } + } + +private: + 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/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/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 f1c25b30..180190ea 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,7 +19,13 @@ namespace Nuake virtual void Initialize() {} virtual void Tick(float deltaTime) {} - private: + virtual void OnScenePreInitialize(Ref scene) {} + virtual void OnScenePostInitialize(Ref scene) {} + virtual void OnScenePreDestroy(Ref scene) {} + + protected: + void OnScriptEngineUninitialize(); + bool canEverTick = false; }; } diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp index 4076ae5f..6feaa93d 100644 --- a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp @@ -1,5 +1,9 @@ #include "EngineSubsystemScriptable.h" +#include "src/Scripting/ScriptingEngineNet.h" + +#include + namespace Nuake { @@ -9,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; @@ -16,6 +30,8 @@ Coral::ManagedObject& EngineSubsystemScriptable::GetManagedObjectInstance() void EngineSubsystemScriptable::Initialize() { + scriptEngineUninitializeDelegateHandle = ScriptingEngineNet::Get().OnUninitialize().AddRaw(this, &EngineSubsystemScriptable::OnScriptEngineUninitialize); + if (!cSharpObjectInstance.IsValid()) return; @@ -29,6 +45,38 @@ void EngineSubsystemScriptable::Tick(float deltaTime) cSharpObjectInstance.InvokeMethod("OnTick", deltaTime); } + +void EngineSubsystemScriptable::OnScenePreInitialize(Ref scene) +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("InternalOnScenePreInitialize"); +} + +void EngineSubsystemScriptable::OnScenePostInitialize(Ref scene) +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("InternalOnSceneReady"); +} + +void EngineSubsystemScriptable::OnScenePreDestroy(Ref scene) +{ + if (!cSharpObjectInstance.IsValid()) + 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 69acae01..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,13 +14,22 @@ namespace Nuake { public: EngineSubsystemScriptable(const Coral::ManagedObject& object); + virtual ~EngineSubsystemScriptable(); Coral::ManagedObject& GetManagedObjectInstance(); virtual void Initialize() override; virtual void Tick(float deltaTime) override; - private: + virtual void OnScenePreInitialize(Ref scene) override; + virtual void OnScenePostInitialize(Ref scene) override; + virtual void OnScenePreDestroy(Ref scene) override; + + protected: + void OnScriptEngineUninitialize(); + + DelegateHandle scriptEngineUninitializeDelegateHandle; + Coral::ManagedObject cSharpObjectInstance; }; } diff --git a/Nuake/src/Window.cpp b/Nuake/src/Window.cpp index 18508d7a..81b90c70 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; + windowSetSceneDelegate.Broadcast(scene, newScene); + + scene = newScene; + return true; } diff --git a/Nuake/src/Window.h b/Nuake/src/Window.h index 63817314..c40e1f7f 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, 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,10 @@ 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: const std::string DEFAULT_TITLE = "Untitled Window"; const uint32_t DEFAULT_WIDTH = 1280; @@ -85,6 +92,8 @@ namespace Nuake void InitImgui(); + OnWindowSetSceneDelegate windowSetSceneDelegate; + private: }; } diff --git a/NuakeNet/src/EngineSubsystem.cs b/NuakeNet/src/EngineSubsystem.cs index dfb2eb67..a4fddc71 100644 --- a/NuakeNet/src/EngineSubsystem.cs +++ b/NuakeNet/src/EngineSubsystem.cs @@ -20,6 +20,29 @@ } public virtual void Initialize() {} + + 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()); + } } }