diff --git a/Nuake/Engine.cpp b/Nuake/Engine.cpp index 041dae87..52e4b895 100644 --- a/Nuake/Engine.cpp +++ b/Nuake/Engine.cpp @@ -14,6 +14,7 @@ #include "src/Threading/JobSystem.h" #include "src/Core/RegisterCoreTypes.h" #include "src/Modules/Modules.h" +#include "src/Subsystems/EngineSubsystemScriptable.h" #include #include @@ -39,6 +40,8 @@ namespace Nuake void Engine::Init() { + ScriptingEngineNet::Get().AddListener(&Engine::OnScriptingEngineGameAssemblyLoaded); + AudioManager::Get().Initialize(); PhysicsManager::Get().Init(); NavManager::Get().Initialize(); @@ -53,6 +56,8 @@ namespace Nuake RegisterCoreTypes::RegisterCoreComponents(); Modules::StartupModules(); + + InitializeCoreSubsystems(); } void Engine::Tick() @@ -91,11 +96,27 @@ namespace Nuake } } + + float scaledTimeStep = timeStep * timeScale; + + // Tick all subsystems + if (Engine::IsPlayMode()) + { + for (auto subsystem : subsystems) + { + if (subsystem == nullptr) + continue; + + if (subsystem->CanEverTick()) + { + subsystem->Tick(scaledTimeStep); + } + } + } // Dont update if no scene is loaded. if (currentWindow->GetScene()) { - float scaledTimeStep = timeStep * timeScale; currentWindow->Update(scaledTimeStep); // Play mode update all the entities, Editor does not. @@ -124,11 +145,13 @@ namespace Nuake lastFrameTime = (float)glfwGetTime(); // Reset timestep timer. // Dont trigger init if already in player mode. - if (GetGameState() == GameState::Playing) + if (GetGameState() == GameState::Playing || GetGameState() == GameState::Loading) { - Logger::Log("Cannot enter play mode if is already in play mode.", "engine", WARNING); + Logger::Log("Cannot enter play mode if is already in play mode or is loading.", "engine", WARNING); return; } + + SetGameState(GameState::Loading); PhysicsManager::Get().ReInit(); @@ -216,6 +239,63 @@ namespace Nuake return currentProject; } + Ref Engine::GetScriptedSubsystem(const std::string& subsystemName) + { + if (scriptedSubsystemMap.contains(subsystemName)) + { + return scriptedSubsystemMap[subsystemName]; + } + return nullptr; + } + + Ref Engine::GetScriptedSubsystem(const int subsystemId) + { + if (subsystemId >= subsystems.size()) + { + return nullptr; + } + return std::reinterpret_pointer_cast(subsystems[subsystemId]); + } + + void Engine::InitializeCoreSubsystems() + { + } + + void Engine::OnScriptingEngineGameAssemblyLoaded() + { + if (!Engine::IsPlayMode() && Engine::GetGameState() != GameState::Loading) + { + return; + } + + subsystems.clear(); + scriptedSubsystemMap.clear(); + + const Coral::ManagedAssembly& gameAssembly = ScriptingEngineNet::Get().GetGameAssembly(); + + const auto scriptTypeEngineSubsystem = gameAssembly.GetType("Nuake.Net.EngineSubsystem"); + + const auto& types = gameAssembly.GetTypes(); + for (const auto& type : types) + { + // Initialize all subsystems + if (type->IsSubclassOf(scriptTypeEngineSubsystem)) + { + const std::string typeName = std::string(type->GetFullName()); + Logger::Log("Creating Scripted Subsystem " + typeName); + + Coral::ManagedObject scriptedSubsystem = type->CreateInstance(); + scriptedSubsystem.SetPropertyValue("EngineSubsystemID", subsystems.size()); + Ref subsystemScript = CreateRef(scriptedSubsystem); + subsystems.push_back(subsystemScript); + + scriptedSubsystemMap[typeName] = subsystemScript; + + subsystemScript->Initialize(); + } + } + } + bool Engine::LoadProject(Ref project) { currentProject = project; @@ -236,4 +316,4 @@ namespace Nuake { return currentWindow; } -} \ No newline at end of file +} diff --git a/Nuake/Engine.h b/Nuake/Engine.h index 07bb1969..d20ca984 100644 --- a/Nuake/Engine.h +++ b/Nuake/Engine.h @@ -1,4 +1,5 @@ #pragma once + #include "src/Core/Core.h" #include "src/Core/Logger.h" #include "src/Window.h" @@ -8,9 +9,12 @@ namespace Nuake { class Project; class Scene; + class EngineSubsystem; + class EngineSubsystemScriptable; enum GameState { + Loading, Playing, Paused, Stopped @@ -50,12 +54,22 @@ namespace Nuake static bool LoadProject(Ref project); static Ref GetProject(); + static Ref GetScriptedSubsystem(const std::string& subsystemName); + static Ref GetScriptedSubsystem(const int subsystemId); + + protected: + static void InitializeCoreSubsystems(); + static void OnScriptingEngineGameAssemblyLoaded(); + private: static Ref currentWindow; static Ref currentProject; static Ref currentScene; static std::string queuedScene; + static inline std::vector> subsystems; + static inline std::unordered_map> scriptedSubsystemMap; + static GameState gameState; static float lastFrameTime; diff --git a/Nuake/src/Scripting/NetModules/EngineNetAPI.cpp b/Nuake/src/Scripting/NetModules/EngineNetAPI.cpp index 3c9a13d3..551fb7eb 100644 --- a/Nuake/src/Scripting/NetModules/EngineNetAPI.cpp +++ b/Nuake/src/Scripting/NetModules/EngineNetAPI.cpp @@ -1,10 +1,15 @@ #include "EngineNetAPI.h" -#include -#include -#include + +#include "src/Core/Maths.h" #include "src/Rendering/SceneRenderer.h" +#include "Engine.h" +#include "src/Physics/PhysicsManager.h" + +#include +#include #include -#include +#include "Coral/Type.hpp" +#include "..\..\Subsystems\EngineSubsystemScriptable.h" namespace Nuake { @@ -112,10 +117,22 @@ namespace Nuake { Engine::QueueSceneSwitch(std::string(path)); } + Coral::ManagedObject GetEngineSubsystemByName(Coral::String subsystemName) + { + const Ref scriptedSubsystem = Engine::GetScriptedSubsystem(subsystemName); + if (scriptedSubsystem == nullptr) + { + return {}; + } + + return scriptedSubsystem->GetManagedObjectInstance(); + } + void EngineNetAPI::RegisterMethods() { RegisterMethod("Engine.LoadSceneIcall", &LoadScene); RegisterMethod("Engine.LoggerLogIcall", (void*)(&Log)); + RegisterMethod("Engine.GetSubsystemByNameIcall", &GetEngineSubsystemByName); // Debug renderer RegisterMethod("Debug.DrawLineIcall", &DrawLine); diff --git a/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.cpp b/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.cpp new file mode 100644 index 00000000..eedc766b --- /dev/null +++ b/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.cpp @@ -0,0 +1,37 @@ +#include "EngineSubsystemNetAPI.h" + +#include "Engine.h" +#include "src/Subsystems/EngineSubsystemScriptable.h" + +namespace Nuake +{ + void SetCanTick(int subsystemId, bool tick) + { + auto subsystem = Engine::GetScriptedSubsystem(subsystemId); + if (subsystem == nullptr) + { + Logger::Log("Subsystem isn't a valid scripted subsystem", "EngineSubsystemNetAPI", WARNING); + return; + } + + subsystem->SetCanTick(tick); + } + + bool GetCanTick(int subsystemId) + { + auto subsystem = Engine::GetScriptedSubsystem(subsystemId); + if (subsystem == nullptr) + { + Logger::Log("Subsystem isn't a valid scripted subsystem", "EngineSubsystemNetAPI", WARNING); + return false; + } + + return subsystem->CanEverTick(); + } + + void EngineSubsystemNetAPI::RegisterMethods() + { + RegisterMethod("EngineSubsystem.SetCanTickIcall", &SetCanTick); + RegisterMethod("EngineSubsystem.GetCanTickIcall", &GetCanTick); + } +} diff --git a/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.h b/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.h new file mode 100644 index 00000000..f827118c --- /dev/null +++ b/Nuake/src/Scripting/NetModules/EngineSubsystemNetAPI.h @@ -0,0 +1,14 @@ +#pragma once + +#include "NetAPIModule.h" + +namespace Nuake +{ + class EngineSubsystemNetAPI : public Nuake::NetAPIModule + { + public: + virtual const std::string GetModuleName() const override { return "EngineSubsystem"; } + virtual void RegisterMethods() override; + }; +} + diff --git a/Nuake/src/Scripting/ScriptingEngineNet.cpp b/Nuake/src/Scripting/ScriptingEngineNet.cpp index 04c32f3b..6a7d0716 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.cpp +++ b/Nuake/src/Scripting/ScriptingEngineNet.cpp @@ -8,6 +8,7 @@ #include "src/Scene/Components/NetScriptComponent.h" #include "NetModules/EngineNetAPI.h" +#include "NetModules/EngineSubsystemNetAPI.h" #include "NetModules/InputNetAPI.h" #include "NetModules/SceneNetAPI.h" #include "NetModules/UINetAPI.h" @@ -53,6 +54,7 @@ namespace Nuake modules = { CreateRef(), + CreateRef(), CreateRef(), CreateRef(), CreateRef() @@ -324,6 +326,15 @@ 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); @@ -525,6 +536,11 @@ namespace Nuake } } } + + for (auto& delegate : listenersGameAssemblyLoaded) + { + delegate(); + } } } diff --git a/Nuake/src/Scripting/ScriptingEngineNet.h b/Nuake/src/Scripting/ScriptingEngineNet.h index 2eb7a596..fc347104 100644 --- a/Nuake/src/Scripting/ScriptingEngineNet.h +++ b/Nuake/src/Scripting/ScriptingEngineNet.h @@ -73,6 +73,9 @@ namespace Nuake class ScriptingEngineNet { + public: + using GameAssemblyLoadedDelegate = std::function; + public: static ScriptingEngineNet& Get(); @@ -83,6 +86,7 @@ namespace Nuake Coral::HostInstance* GetHostInstance() { return hostInstance; } Coral::AssemblyLoadContext& GetLoadContext() { return loadContext; } Coral::ManagedAssembly GetNuakeAssembly() const { return nuakeAssembly; } + Coral::ManagedAssembly& GetGameAssembly() { return gameAssembly; } Coral::ManagedAssembly ReloadEngineAPI(Coral::AssemblyLoadContext & context); @@ -110,6 +114,9 @@ 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); + private: const std::string m_Scope = "Nuake.Net"; const std::string m_EngineAssemblyName = "NuakeNet.dll"; @@ -140,6 +147,8 @@ namespace Nuake std::unordered_map entityToManagedObjects; std::map, Coral::ManagedObject> widgetUUIDToManagedObjects; + + std::vector listenersGameAssemblyLoaded; ScriptingEngineNet(); ~ScriptingEngineNet(); @@ -148,4 +157,4 @@ namespace Nuake std::string GenerateGUID(); std::vector ExtractErrors(const std::string& input); }; -} \ No newline at end of file +} diff --git a/Nuake/src/Subsystems/EngineSubsystem.cpp b/Nuake/src/Subsystems/EngineSubsystem.cpp new file mode 100644 index 00000000..d3dc51a1 --- /dev/null +++ b/Nuake/src/Subsystems/EngineSubsystem.cpp @@ -0,0 +1,11 @@ +#include "EngineSubsystem.h" + +void Nuake::EngineSubsystem::SetCanTick(bool canTick) +{ + canEverTick = canTick; +} + +bool Nuake::EngineSubsystem::CanEverTick() const +{ + return canEverTick; +} diff --git a/Nuake/src/Subsystems/EngineSubsystem.h b/Nuake/src/Subsystems/EngineSubsystem.h new file mode 100644 index 00000000..f1c25b30 --- /dev/null +++ b/Nuake/src/Subsystems/EngineSubsystem.h @@ -0,0 +1,21 @@ +#pragma once + +/** + * 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 EngineSubsystem + { + public: + void SetCanTick(bool canTick); + bool CanEverTick() const; + + virtual void Initialize() {} + virtual void Tick(float deltaTime) {} + + private: + bool canEverTick = false; + }; +} diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp new file mode 100644 index 00000000..4076ae5f --- /dev/null +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.cpp @@ -0,0 +1,35 @@ +#include "EngineSubsystemScriptable.h" + +namespace Nuake +{ + +EngineSubsystemScriptable::EngineSubsystemScriptable(const Coral::ManagedObject& object) + : cSharpObjectInstance(object) +{ + +} + +Coral::ManagedObject& EngineSubsystemScriptable::GetManagedObjectInstance() +{ + return cSharpObjectInstance; +} + +void EngineSubsystemScriptable::Initialize() +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("Initialize"); +} + +void EngineSubsystemScriptable::Tick(float deltaTime) +{ + if (!cSharpObjectInstance.IsValid()) + return; + + cSharpObjectInstance.InvokeMethod("OnTick", deltaTime); +} + +} + + diff --git a/Nuake/src/Subsystems/EngineSubsystemScriptable.h b/Nuake/src/Subsystems/EngineSubsystemScriptable.h new file mode 100644 index 00000000..69acae01 --- /dev/null +++ b/Nuake/src/Subsystems/EngineSubsystemScriptable.h @@ -0,0 +1,25 @@ +#pragma once + +#include "EngineSubsystem.h" + +#include + +/** + * Essentially just a wrapper for C# subsystems + */ +namespace Nuake +{ + class EngineSubsystemScriptable : public EngineSubsystem + { + public: + EngineSubsystemScriptable(const Coral::ManagedObject& object); + + Coral::ManagedObject& GetManagedObjectInstance(); + + virtual void Initialize() override; + virtual void Tick(float deltaTime) override; + + private: + Coral::ManagedObject cSharpObjectInstance; + }; +} diff --git a/Nuake/src/Subsystems/SceneSubsystem.cpp b/Nuake/src/Subsystems/SceneSubsystem.cpp new file mode 100644 index 00000000..a3dfd458 --- /dev/null +++ b/Nuake/src/Subsystems/SceneSubsystem.cpp @@ -0,0 +1 @@ +#include "SceneSubsystem.h" diff --git a/Nuake/src/Subsystems/SceneSubsystem.h b/Nuake/src/Subsystems/SceneSubsystem.h new file mode 100644 index 00000000..24e01684 --- /dev/null +++ b/Nuake/src/Subsystems/SceneSubsystem.h @@ -0,0 +1,9 @@ +#pragma once + +// Currently unused, but it's meant to be the base for all ECS "systems" at some point +// that can be extended with a script. +class SceneSubsystem +{ +public: + +}; diff --git a/NuakeNet/src/EngineSubsystem.cs b/NuakeNet/src/EngineSubsystem.cs new file mode 100644 index 00000000..dfb2eb67 --- /dev/null +++ b/NuakeNet/src/EngineSubsystem.cs @@ -0,0 +1,25 @@ +namespace Nuake.Net +{ + public class EngineSubsystem + { + internal static unsafe delegate* SetCanTickIcall; + internal static unsafe delegate* GetCanTickIcall; + + public int EngineSubsystemID { get; protected set; } + + public bool CanTick + { + set + { + unsafe { SetCanTickIcall(EngineSubsystemID, value); } + } + get + { + unsafe { return GetCanTickIcall(EngineSubsystemID); } + } + } + + public virtual void Initialize() {} + public virtual void OnTick(float deltaTime) {} + } +} diff --git a/NuakeNet/src/main.cs b/NuakeNet/src/main.cs index af203984..733334de 100644 --- a/NuakeNet/src/main.cs +++ b/NuakeNet/src/main.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Drawing; using System.Numerics; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -18,6 +19,8 @@ public class Engine { internal static unsafe delegate* LoggerLogIcall; internal static unsafe delegate* LoadSceneIcall; + internal static unsafe delegate*> GetSubsystemByNameIcall; + public Engine() { } public static void LoadScene(string path) @@ -33,6 +36,14 @@ public static void Log(string input) { unsafe { LoggerLogIcall(input); } } + + public static T? GetSubsystem() where T : EngineSubsystem + { + unsafe + { + return (T?)GetSubsystemByNameIcall(typeof(T).FullName); + } + } } public struct AABB diff --git a/premake5.lua b/premake5.lua index 5f0e97b1..28aa39f1 100644 --- a/premake5.lua +++ b/premake5.lua @@ -127,6 +127,8 @@ project "Nuake" "%{prj.name}/src/Threading/**.cpp", "%{prj.name}/src/UI/**.h", "%{prj.name}/src/UI/**.cpp", + "%{prj.name}/src/Subsystems/**.h", + "%{prj.name}/src/Subsystems/**.cpp", "%{prj.name}/src/Vendors/**.h", "%{prj.name}/src/Vendors/**.cpp",