diff --git a/.gitmodules b/.gitmodules index 203221b..00baa39 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "3rdparty/SDL/SDL"] path = 3rdparty/SDL/SDL url = https://github.com/libsdl-org/SDL.git +[submodule "3rdparty/vgjs/vgjs"] + path = 3rdparty/vgjs/vgjs + url = https://github.com/tuguzT/ViennaGameJobSystem.git diff --git a/3rdparty/vgjs/vgjs b/3rdparty/vgjs/vgjs new file mode 160000 index 0000000..cd9aadd --- /dev/null +++ b/3rdparty/vgjs/vgjs @@ -0,0 +1 @@ +Subproject commit cd9aadd4c1c3ab612ff382eeb27da2dafaaead91 diff --git a/GAME/source/GAME/Layers/MainGameLayer.cpp b/GAME/source/GAME/Layers/MainGameLayer.cpp index 8d56919..d652042 100644 --- a/GAME/source/GAME/Layers/MainGameLayer.cpp +++ b/GAME/source/GAME/Layers/MainGameLayer.cpp @@ -20,13 +20,19 @@ namespace soge_game void MainGameLayer::OnUpdate() { const auto engine = soge::Engine::GetInstance(); - if (const auto inputModule = engine->GetModule(); inputModule->IsKeyPressed(soge::Keys::W)) + const auto inputModule = engine->GetModule(); + if (inputModule == nullptr) { - SOGE_APP_INFO_LOG("Key W pressed!"); + return; + } + + if (inputModule->IsKeyPressed(soge::Keys::W)) + { + SOGE_APP_INFO_LOG("Key W pressed..."); } else if (inputModule->IsKeyPressed(soge::Keys::Escape)) { - SOGE_APP_INFO_LOG("Key Escape pressed - shutting down!"); + SOGE_APP_INFO_LOG("Key Escape pressed: shutting down..."); engine->RequestShutdown(); } } diff --git a/SOGE/include/SOGE/Core/Engine.hpp b/SOGE/include/SOGE/Core/Engine.hpp index d08d709..c5fbcc2 100644 --- a/SOGE/include/SOGE/Core/Engine.hpp +++ b/SOGE/include/SOGE/Core/Engine.hpp @@ -1,6 +1,7 @@ #ifndef SOGE_CORE_ENGINE_HPP #define SOGE_CORE_ENGINE_HPP +#include "SOGE/Core/JobSystem.hpp" #include "SOGE/Core/LayerStack.hpp" #include "SOGE/Core/ModuleManager.hpp" #include "SOGE/DI/Container.hpp" @@ -20,10 +21,12 @@ namespace soge static UniquePtr s_instance; static std::mutex s_mutex; - LayerStack m_renderLayers; bool m_isRunning; std::atomic_bool m_shutdownRequested; + LayerStack m_renderLayers; + JobSystem* m_jobSystem; + di::Container m_container; ModuleManager m_moduleManager; diff --git a/SOGE/include/SOGE/Core/EntryPoint.hpp b/SOGE/include/SOGE/Core/EntryPoint.hpp index 04d3835..c64bdf3 100644 --- a/SOGE/include/SOGE/Core/EntryPoint.hpp +++ b/SOGE/include/SOGE/Core/EntryPoint.hpp @@ -16,12 +16,33 @@ namespace soge [[nodiscard]] inline int Launch(const std::span args) { + class JobSystemGuard + { + public: + explicit JobSystemGuard(const std::uint16_t aThreadCount = 0) + { + JobSystem::Initialize(aThreadCount); + } + + explicit JobSystemGuard(const JobSystemGuard&) = delete; + JobSystemGuard& operator=(const JobSystemGuard&) = delete; + + explicit JobSystemGuard(JobSystemGuard&&) = delete; + JobSystemGuard& operator=(JobSystemGuard&&) = delete; + + ~JobSystemGuard() + { + JobSystem::Terminate(); + } + }; + if (!ConsoleInit(args)) { return EXIT_FAILURE; } Logger::Init(); + JobSystemGuard jobSystemGuard; const auto app = CreateApplication(); app->Run(); diff --git a/SOGE/include/SOGE/Core/JobSystem.hpp b/SOGE/include/SOGE/Core/JobSystem.hpp new file mode 100644 index 0000000..bb523a6 --- /dev/null +++ b/SOGE/include/SOGE/Core/JobSystem.hpp @@ -0,0 +1,41 @@ +#ifndef SOGE_CORE_JOBSYSTEM_HPP +#define SOGE_CORE_JOBSYSTEM_HPP + +#include "SOGE/DI/Dependency.hpp" +#include "SOGE/System/Memory.hpp" + + +namespace soge +{ + class JobSystem final + { + private: + class Impl; + + UniquePtr m_impl; + + public: + static void Initialize(std::uint16_t aThreadCount = 0); + static void Terminate(); + + explicit JobSystem(); + + explicit JobSystem(const JobSystem&) = delete; + JobSystem& operator=(const JobSystem&) = delete; + + explicit JobSystem(JobSystem&&) noexcept = default; + JobSystem& operator=(JobSystem&&) noexcept = default; + + ~JobSystem(); + + [[nodiscard]] + std::uint16_t GetThreadCount(); + + void Schedule(std::function aJob); + void Wait(); + }; +} + +SOGE_DI_REGISTER_NS(soge, JobSystem, df::Single, tag::Final) + +#endif // SOGE_CORE_JOBSYSTEM_HPP diff --git a/SOGE/source/SOGE/Core/Engine.cpp b/SOGE/source/SOGE/Core/Engine.cpp index b630565..ca6966c 100644 --- a/SOGE/source/SOGE/Core/Engine.cpp +++ b/SOGE/source/SOGE/Core/Engine.cpp @@ -1,6 +1,7 @@ #include "sogepch.hpp" #include "SOGE/Core/Engine.hpp" + #include "SOGE/Core/Timestep.hpp" #include "SOGE/Event/EventModule.hpp" #include "SOGE/Input/InputModule.hpp" @@ -37,7 +38,7 @@ namespace soge return s_instance.get(); } - Engine::Engine(AccessTag) : m_isRunning(false), m_shutdownRequested(false) + Engine::Engine(AccessTag) : m_isRunning(false), m_shutdownRequested(false), m_jobSystem(nullptr) { SOGE_INFO_LOG("Initialize engine..."); @@ -70,6 +71,8 @@ namespace soge std::lock_guard lock(s_mutex); constexpr AccessTag tag; + m_jobSystem = &m_container.Provide(); + m_isRunning = true; for (Module& module : m_moduleManager) { @@ -96,6 +99,9 @@ namespace soge { layer->OnUpdate(); } + + m_jobSystem->Schedule([] { SOGE_INFO_LOG("Delta time is: {}", Timestep::DeltaTime()); }); + m_jobSystem->Wait(); } Unload(tag); @@ -106,6 +112,7 @@ namespace soge m_isRunning = false; m_removedModules.clear(); m_container.Clear(); + m_jobSystem = nullptr; } bool Engine::IsRunning() const diff --git a/SOGE/source/SOGE/Core/JobSystem.cpp b/SOGE/source/SOGE/Core/JobSystem.cpp new file mode 100644 index 0000000..0b561a3 --- /dev/null +++ b/SOGE/source/SOGE/Core/JobSystem.cpp @@ -0,0 +1,143 @@ +#include "sogepch.hpp" + +#include "SOGE/Core/JobSystem.hpp" + +#include +#include + + +namespace +{ + constexpr std::size_t g_jobSystemMaxBlocksPerChunk = 1 << 10; + constexpr std::size_t g_jobSystemLargestRequiredPoolBlock = 1 << 10; + + std::pmr::synchronized_pool_resource g_jobSystemMemoryResource{ + std::pmr::pool_options{ + .max_blocks_per_chunk = g_jobSystemMaxBlocksPerChunk, + .largest_required_pool_block = g_jobSystemLargestRequiredPoolBlock, + }, + std::pmr::new_delete_resource(), + }; +} + +namespace soge +{ + class JobSystem::Impl + { + private: + friend JobSystem; + + static std::uint64_t CreateInstanceId() + { + static std::uint64_t instanceId = 0; + return instanceId++; + } + + const std::uint64_t m_instanceId; + std::atomic_uint64_t m_jobCount; + std::atomic_bool m_allJobsCompleted; + + public: + explicit Impl(); + + explicit Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + explicit Impl(Impl&&) = delete; + Impl& operator=(Impl&&) = delete; + + ~Impl() = default; + }; + + JobSystem::Impl::Impl() : m_instanceId(CreateInstanceId()) + { + } + + void JobSystem::Initialize(const std::uint16_t aThreadCount) + { + if (aThreadCount == 0) + { + SOGE_INFO_LOG("Initializing job system with default thread count..."); + } + else + { + SOGE_INFO_LOG("Initializing job system with thread count of {}...", aThreadCount); + } + + const vgjs::thread_count_t threadCount{aThreadCount}; + const vgjs::thread_index_t threadIndex{0}; + vgjs::JobSystem jobSystem{threadCount, threadIndex, &g_jobSystemMemoryResource}; + + SOGE_INFO_LOG("Job system initialized successfully with thread count of {}!", + jobSystem.get_thread_count().value); + } + + void JobSystem::Terminate() + { + SOGE_INFO_LOG("Cleaning up job system..."); + + vgjs::JobSystem jobSystem; + jobSystem.terminate(); + jobSystem.wait_for_termination(); + } + + JobSystem::JobSystem() : m_impl(CreateUnique()) + { + } + + JobSystem::~JobSystem() + { + Wait(); + } + + std::uint16_t JobSystem::GetThreadCount() + { + vgjs::JobSystem jobSystem; + return static_cast(jobSystem.get_thread_count().value); + } + + void JobSystem::Schedule(std::function aJob) + { + // For better debugging experience + Impl& impl = *m_impl; + + // Register job for this job system instance + ++impl.m_jobCount; + + vgjs::Function job{ + [job = std::move(aJob), &impl] { + job(); + + // Unregister job from this job system instance + --impl.m_jobCount; + + // Notify all waiting threads that all jobs were completed + if (impl.m_jobCount == 0) + { + impl.m_allJobsCompleted = true; + impl.m_allJobsCompleted.notify_all(); + } + }, + vgjs::thread_index_t{}, // could be useful + }; + + vgjs::JobSystem jobSystem; + jobSystem.schedule(std::move(job)); + } + + void JobSystem::Wait() + { + // For better debugging experience + Impl& impl = *m_impl; + + // Fast path: nothing to wait + if (impl.m_jobCount == 0) + { + return; + } + + // Slow path: atomic wait + impl.m_allJobsCompleted.wait(false); + impl.m_allJobsCompleted = false; + } +} diff --git a/dependencies.lua b/dependencies.lua index 9f44e28..e607b60 100644 --- a/dependencies.lua +++ b/dependencies.lua @@ -1,4 +1,3 @@ - -- Include directories IncludeThirdpartyDirs = {} IncludeThirdpartyDirs["spdlog"] = "3rdparty/spdlog/spdlog/include" @@ -9,6 +8,7 @@ IncludeThirdpartyDirs["UUID_v4"] = "3rdparty/uuid_v4/uuid_v4/include" IncludeThirdpartyDirs["eventpp"] = "3rdparty/eventpp/eventpp/include" IncludeThirdpartyDirs["kangaru"] = "3rdparty/kangaru/kangaru/include" IncludeThirdpartyDirs["SDL3"] = "3rdparty/SDL/SDL/include" +IncludeThirdpartyDirs["vgjs"] = "3rdparty/vgjs/vgjs/include" -- Include libs Libraries = {} @@ -18,4 +18,4 @@ Libraries["SDL3_LIB_D"] = "3rdparty/SDL/lib/Debug/SDL3.lib" Libraries["SDL_UCLIB_R"] = "3rdparty/SDL/lib/Release/SDL_uclibc.lib" Libraries["SDL3_DLL_R"] = "3rdparty/SDL/lib/Release/SDL3.dll" -Libraries["SDL3_LIB_R"] = "3rdparty/SDL/lib/Release/SDL3.lib" \ No newline at end of file +Libraries["SDL3_LIB_R"] = "3rdparty/SDL/lib/Release/SDL3.lib" diff --git a/premake5.lua b/premake5.lua index bb1c6f5..1afd5d4 100644 --- a/premake5.lua +++ b/premake5.lua @@ -47,7 +47,8 @@ workspace "SOGE" "%{wks.location}/%{IncludeThirdpartyDirs.eventpp}", "%{wks.location}/%{IncludeThirdpartyDirs.kangaru}", "%{wks.location}/%{IncludeThirdpartyDirs.eventpp}", - "%{wks.location}/%{IncludeThirdpartyDirs.SDL3}" + "%{wks.location}/%{IncludeThirdpartyDirs.SDL3}", + "%{wks.location}/%{IncludeThirdpartyDirs.vgjs}" } defines @@ -150,7 +151,8 @@ workspace "SOGE" "%{wks.location}/%{IncludeThirdpartyDirs.eventpp}", "%{wks.location}/%{IncludeThirdpartyDirs.kangaru}", "%{wks.location}/%{IncludeThirdpartyDirs.eventpp}", - "%{wks.location}/%{IncludeThirdpartyDirs.SDL3}" + "%{wks.location}/%{IncludeThirdpartyDirs.SDL3}", + "%{wks.location}/%{IncludeThirdpartyDirs.vgjs}" } links