Merge pull request #91 from WiggleWizard/feature/cs-scene-callbacks

Exposed subsystem to scene events
This commit is contained in:
Antoine Pilote
2024-09-19 16:55:31 -04:00
committed by GitHub
17 changed files with 345 additions and 29 deletions

View File

@@ -40,7 +40,9 @@ namespace Nuake
void Engine::Init()
{
ScriptingEngineNet::Get().AddListener<ScriptingEngineNet::GameAssemblyLoadedDelegate>(&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<EngineSubsystemScriptable>(subsystems[subsystemId]);
}
void Engine::OnWindowSetScene(Ref<Scene> oldScene, Ref<Scene> 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> scene)
{
for (auto subsystem : subsystems)
{
if (subsystem == nullptr)
continue;
subsystem->OnScenePreInitialize(scene);
}
}
void Engine::OnScenePostInitialize(Ref<Scene> scene)
{
for (auto subsystem : subsystems)
{
if (subsystem == nullptr)
continue;
subsystem->OnScenePostInitialize(scene);
}
}
bool Engine::LoadProject(Ref<Project> project)
{
currentProject = project;

View File

@@ -58,9 +58,15 @@ namespace Nuake
static Ref<EngineSubsystemScriptable> GetScriptedSubsystem(const int subsystemId);
protected:
static void OnWindowSetScene(Ref<Scene> oldScene, Ref<Scene> newScene);
static void InitializeCoreSubsystems();
static void OnScriptingEngineUninitialize();
static void OnScriptingEngineGameAssemblyLoaded();
static void OnScenePreInitialize(Ref<Scene> scene);
static void OnScenePostInitialize(Ref<Scene> scene);
private:
static Ref<Window> currentWindow;
static Ref<Project> currentProject;

View File

@@ -0,0 +1,134 @@
#pragma once
#include <functional>
#include <vector>
#define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName;
struct DelegateHandle
{
size_t id = InvalidHandle;
static inline size_t InvalidHandle = static_cast<size_t>(-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<typename... Args>
class MulticastDelegate
{
public:
// Add a callable with bound variables (supports no arguments as well)
template<typename Callable, typename... BoundArgs>
DelegateHandle AddStatic(Callable&& func, BoundArgs&&... boundArgs)
{
size_t id = GetNextID();
auto boundFunction = [=](Args... args) {
if constexpr (sizeof...(Args) > 0)
{
func(boundArgs..., std::forward<Args>(args)...);
}
else
{
func(boundArgs...);
}
};
SetDelegate(id, boundFunction);
return DelegateHandle{ id };
}
template<typename Obj, typename Callable, typename... BoundArgs>
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>(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>(args)...);
}
}
}
private:
struct Delegate
{
bool active = false;
std::function<void(Args...)> function;
};
// A vector of delegates with active state
std::vector<Delegate> delegates;
// List of reusable slots
std::vector<size_t> 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<void(Args...)>& func)
{
if (id >= delegates.size())
delegates.resize(id + 1);
delegates[id] = { true, func };
}
};

View File

@@ -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;
}

View File

@@ -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);
};

View File

@@ -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;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "System.h"
#include "src/Core/MulticastDelegate.h"
namespace Nuake {
class Scene;

View File

@@ -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;
};
}

View File

@@ -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<class T>
void ScriptingEngineNet::AddListener(const T& delegate) {}
template <>
void ScriptingEngineNet::AddListener<ScriptingEngineNet::GameAssemblyLoadedDelegate>(const GameAssemblyLoadedDelegate& delegate)
{
listenersGameAssemblyLoaded.push_back(delegate);
}
std::vector<CompilationError> ScriptingEngineNet::BuildProjectAssembly(Ref<Project> project)
{
const std::string sanitizedProjectName = String::Sanitize(project->Name);
@@ -541,10 +534,7 @@ namespace Nuake
}
}
for (auto& delegate : listenersGameAssemblyLoaded)
{
delegate();
}
onGameAssemblyLoadedDelegate.Broadcast();
}
}

View File

@@ -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<std::string, NetGameScriptObject> GetPointEntities() const { return pointEntityTypes; }
std::unordered_map<std::string, UIWidgetObject> GetUIWidgets() const { return uiWidgets; }
template<class T> 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<uint32_t, Coral::ManagedObject> entityToManagedObjects;
std::map<std::pair<UUID, UUID>, Coral::ManagedObject> widgetUUIDToManagedObjects;
std::vector<GameAssemblyLoadedDelegate> listenersGameAssemblyLoaded;
OnGameAssemblyLoadedDelegate onGameAssemblyLoadedDelegate;
OnUninitializeDelegate onUninitializeDelegate;
ScriptingEngineNet();
~ScriptingEngineNet();

View File

@@ -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;
}

View File

@@ -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> scene) {}
virtual void OnScenePostInitialize(Ref<Scene> scene) {}
virtual void OnScenePreDestroy(Ref<Scene> scene) {}
protected:
void OnScriptEngineUninitialize();
bool canEverTick = false;
};
}

View File

@@ -1,5 +1,9 @@
#include "EngineSubsystemScriptable.h"
#include "src/Scripting/ScriptingEngineNet.h"
#include <Coral/Type.hpp>
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> scene)
{
if (!cSharpObjectInstance.IsValid())
return;
cSharpObjectInstance.InvokeMethod("InternalOnScenePreInitialize");
}
void EngineSubsystemScriptable::OnScenePostInitialize(Ref<Scene> scene)
{
if (!cSharpObjectInstance.IsValid())
return;
cSharpObjectInstance.InvokeMethod("InternalOnSceneReady");
}
void EngineSubsystemScriptable::OnScenePreDestroy(Ref<Scene> scene)
{
if (!cSharpObjectInstance.IsValid())
return;
cSharpObjectInstance.InvokeMethod("InternalOnScenePreDestroy");
}
void EngineSubsystemScriptable::OnScriptEngineUninitialize()
{
if (!cSharpObjectInstance.IsValid())
return;
cSharpObjectInstance.Destroy();
}
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "EngineSubsystem.h"
#include "src/Core/MulticastDelegate.h"
#include <Coral/ManagedObject.hpp>
@@ -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> scene) override;
virtual void OnScenePostInitialize(Ref<Scene> scene) override;
virtual void OnScenePreDestroy(Ref<Scene> scene) override;
protected:
void OnScriptEngineUninitialize();
DelegateHandle scriptEngineUninitializeDelegateHandle;
Coral::ManagedObject cSharpObjectInstance;
};
}

View File

@@ -303,9 +303,12 @@ Ref<Scene> Window::GetScene()
return this->scene;
}
bool Window::SetScene(Ref<Scene> scene)
bool Window::SetScene(Ref<Scene> newScene)
{
this->scene = scene;
windowSetSceneDelegate.Broadcast(scene, newScene);
scene = newScene;
return true;
}

View File

@@ -1,11 +1,12 @@
#pragma once
#include "Core/Core.h"
#include "Core/Maths.h"
#include "Core/MulticastDelegate.h"
#include "Core/Timestep.h"
#include <functional>
struct GLFWwindow;
namespace Nuake
@@ -13,6 +14,8 @@ namespace Nuake
class Scene;
class FrameBuffer;
DECLARE_MULTICAST_DELEGATE(OnWindowSetSceneDelegate, Ref<Scene>, Ref<Scene>)
class Window
{
public:
@@ -44,7 +47,7 @@ namespace Nuake
void Center();
Ref<Scene> GetScene();
bool SetScene(Ref<Scene> scene);
bool SetScene(Ref<Scene> newScene);
void SetTitle(const std::string& title);
std::string GetTitle();
@@ -63,6 +66,10 @@ namespace Nuake
void SetOnWindowClosedCallback(std::function<void(Window& window)> callback);
void SetOnDragNDropCallback(std::function<void(Window&, const std::vector<std::string>& 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:
};
}

View File

@@ -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());
}
}
}