diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index 7991388..d056fcf 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -13,8 +13,10 @@ # limitations under the License. add_library(ProjectDelta SHARED + "src/delta/platform/compiler.h" "src/delta/core/engine.cpp" "include/delta/core/engine.h" + "include/delta/core/memory.h" "include/delta/definitions.h" "src/delta/platform/os_internal.h" "src/delta/platform/os_win32.cpp" @@ -27,6 +29,8 @@ add_library(ProjectDelta SHARED "src/delta/core/ThreadContext.cpp" "src/delta/core/MemoryConfig.h" "src/delta/core/MemoryConfig.cpp" + "src/delta/core/Worker.h" + "src/delta/core/Worker.cpp" ) target_compile_definitions(ProjectDelta @@ -42,3 +46,9 @@ target_include_directories(ProjectDelta PUBLIC "include" PRIVATE "src" ) + +if (MSVC) + target_compile_options(ProjectDelta PRIVATE /GR-) +else() + target_compile_options(ProjectDelta PRIVATE -fno-rtti) +endif() diff --git a/Engine/include/delta/core/engine.h b/Engine/include/delta/core/engine.h index 98a1bea..40fc429 100644 --- a/Engine/include/delta/core/engine.h +++ b/Engine/include/delta/core/engine.h @@ -27,6 +27,7 @@ namespace delta::Engine typedef void (*GameUpdateFunc)(Context*); typedef void (*GameShutdownFunc)(Context*); - void DLT_API Initialize(Context& context); - void DLT_API Shutdown(Context& context); + DLT_API void Initialize(Context& context); + DLT_API void Update(Context& context); + DLT_API void Shutdown(Context& context); } diff --git a/Engine/include/delta/core/memory.h b/Engine/include/delta/core/memory.h new file mode 100644 index 0000000..5048291 --- /dev/null +++ b/Engine/include/delta/core/memory.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace delta::Engine +{ + enum class AllocationType : uint8_t { TRANSIENT, PERSISTENT }; + + [[nodiscard]] DLT_API void* Allocate(size_t size, AllocationType type, size_t alignment = 8) noexcept; + DLT_API void Free(void* ptr) noexcept; +} + +[[nodiscard]] inline void* operator new(size_t size) { return delta::Engine::Allocate(size, delta::Engine::AllocationType::PERSISTENT); } +[[nodiscard]] inline void* operator new(size_t size, delta::Engine::AllocationType type) { return delta::Engine::Allocate(size, type); } +[[nodiscard]] inline void* operator new[](size_t size) { return delta::Engine::Allocate(size, delta::Engine::AllocationType::PERSISTENT); } +[[nodiscard]] inline void* operator new[](size_t size, delta::Engine::AllocationType type) { return delta::Engine::Allocate(size, type); } + +inline void operator delete(void* ptr) { return delta::Engine::Free(ptr); } +inline void operator delete(void* ptr, delta::Engine::AllocationType) { return delta::Engine::Free(ptr); } +inline void operator delete[](void* ptr) { return delta::Engine::Free(ptr); } +inline void operator delete[](void* ptr, delta::Engine::AllocationType) { return delta::Engine::Free(ptr); } diff --git a/Engine/include/delta/pch.h b/Engine/include/delta/pch.h index 1f841dc..7141abf 100644 --- a/Engine/include/delta/pch.h +++ b/Engine/include/delta/pch.h @@ -4,3 +4,4 @@ #include #include +#include diff --git a/Engine/include/delta/platform/os.h b/Engine/include/delta/platform/os.h index 1eae07d..75e1525 100644 --- a/Engine/include/delta/platform/os.h +++ b/Engine/include/delta/platform/os.h @@ -24,7 +24,6 @@ namespace delta::platform uint32_t cpuPhysicalCoreCount; uint32_t cpuLogicalProcessorCount; - uint32_t maxEngineWorkerCount; uint32_t osPageSize; char cpuBrandString[sizeof(int) * 12 + 1]; diff --git a/Engine/src/delta/core/EngineTypes.h b/Engine/src/delta/core/EngineTypes.h index f01716e..98db880 100644 --- a/Engine/src/delta/core/EngineTypes.h +++ b/Engine/src/delta/core/EngineTypes.h @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include @@ -18,42 +34,78 @@ namespace delta::core uint8_t* backingMemory; size_t capacity; size_t offset; + size_t maxCapacity; + }; + + struct DependencyCounter + { + std::atomic count; }; using task_t = void (*)(void*); using payload_t = void*; + using queue_index_t = int64_t; struct TaskQueue // SoA structure, Chase-Lev queue { - alignas(64) std::atomic top; - alignas(64) std::atomic bottom; + alignas(64) std::atomic top; + alignas(64) std::atomic bottom; - alignas(64) uint64_t size; - uint64_t mask; + alignas(64) queue_index_t size; + queue_index_t mask; task_t* tasks; payload_t* payloads; + DependencyCounter* depCounterPtr; static inline constexpr size_t FIELD_SIZE = sizeof(task_t) + sizeof(payload_t); }; - struct alignas(64) ThreadExecutionContext + enum class ThreadType : uint8_t { MAIN, WORKER }; + + struct alignas(64) GenericExecutionContext { + // SHARED TRAITS + ThreadType type; uint32_t threadIx; - uint32_t threadId; + delta::platform::ThreadHandle threadHandle; ThreadPageCoordinator pageCoordinator; - TaskQueue taskQueue; + ThreadArena transientArena; + delta::platform::Timer perThreadTimer; + }; - alignas(64) ThreadArena transientArena; - ThreadArena componentPoolArena; - ThreadArena sceneArena; + struct alignas(64) MainExecutionContext + { + // SHARED TRAITS + GenericExecutionContext generic; + + // ROLE TRAITS + ThreadArena persistentStorage; + DependencyCounter depCounter; + }; - alignas(64) delta::platform::Timer perThreadTimer; + struct alignas(64) WorkerExecutionContext + { + GenericExecutionContext generic; - uint8_t hardwarePadding[32]; + // ROLE TRAITS + TaskQueue taskQueue; + delta::platform::SemaphoreHandle sleepSemaphore; + std::atomic isAsleep; + std::atomic shouldClose; }; - extern thread_local ThreadExecutionContext* tl_CurrentThreadContext; + template + concept ExecutionContext = + std::is_same_v, GenericExecutionContext> || + std::is_same_v, MainExecutionContext> || + std::is_same_v, WorkerExecutionContext>; + + // COMPILE TIME CONSTANTS + inline constexpr size_t THREAD_EXECUTION_CONTEXT_STRIDE = std::max(sizeof(MainExecutionContext), sizeof(WorkerExecutionContext)); + inline constexpr size_t THREAD_EXECUTION_CONTEXT_SIZE = (THREAD_EXECUTION_CONTEXT_STRIDE + 63) & ~(63); - static_assert((sizeof(ThreadExecutionContext) % 64) == 0); + // EXTERN VARIABLES & FUNCTIONS + extern uintptr_t g_MasterSlabStart; + extern uintptr_t g_MasterSlabEnd; } diff --git a/Engine/src/delta/core/MemoryConfig.cpp b/Engine/src/delta/core/MemoryConfig.cpp index 7d1982e..7858fca 100644 --- a/Engine/src/delta/core/MemoryConfig.cpp +++ b/Engine/src/delta/core/MemoryConfig.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "MemoryConfig.h" #include "EngineTypes.h" #include @@ -7,17 +23,19 @@ namespace delta::core { EngineMemoryConfig g_MemoryConfig{}; - void MemoryConfig_Initialize(size_t physicalRamInstalled, size_t pageSize, uint32_t maxEngineWorkers) + void MemoryConfig_Initialize(size_t physicalRamInstalled, size_t pageSize, uint32_t totalThreads) { g_MemoryConfig.totalPhysicalRam = physicalRamInstalled; g_MemoryConfig.maxAllowedPhysical = (physicalRamInstalled / 10) * 7; // 70% of total capacity + // TODO: Change this. "Environment" has changed. // calculate soft baseline (ideal case, no stealing from the global pool) g_MemoryConfig.threadSoftBaseline = MemoryMap::VIRT_ZONE_TA_BASELINE; - size_t totalControlTrackBytes = maxEngineWorkers * (sizeof(ThreadExecutionContext) + MemoryMap::VIRT_ZONE_QUEUE_SIZE); - size_t totalPrivateArenaBytes = maxEngineWorkers * MemoryMap::VIRT_ZONE_BASELINE_SUM; - size_t rawLockBudget = totalControlTrackBytes + totalPrivateArenaBytes; + // formula optimized on paper to limit arithmetic that has to be done + // precompute whats possible to save some runtime + static constexpr size_t PRECOMPUTABLE_CONST = THREAD_EXECUTION_CONTEXT_SIZE + MemoryMap::VIRT_ZONE_TA_BASELINE + MemoryMap::Worker::VIRT_ZONE_BASELINE_SUM; + size_t rawLockBudget = (totalThreads * PRECOMPUTABLE_CONST) + MemoryMap::Main::VIRT_ZONE_BASELINE_SUM - MemoryMap::Worker::VIRT_ZONE_BASELINE_SUM; g_MemoryConfig.activeLockAllocation = (rawLockBudget + (pageSize - 1)) & ~(pageSize - 1); g_MemoryConfig.globalPoolSize = g_MemoryConfig.maxAllowedPhysical - g_MemoryConfig.activeLockAllocation; diff --git a/Engine/src/delta/core/MemoryConfig.h b/Engine/src/delta/core/MemoryConfig.h index f03f888..8f98b4c 100644 --- a/Engine/src/delta/core/MemoryConfig.h +++ b/Engine/src/delta/core/MemoryConfig.h @@ -1,43 +1,74 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once namespace delta::core { - // FUTURE TODO: Make it configurable via compile definitions namespace MemoryMap { + // Generic inline constexpr size_t VIRT_ZONE_SPACE_LENGTH = (1ull << 35); - // COMPONENTS: - // QUEUE - inline constexpr size_t VIRT_ZONE_QUEUE_OFFSET = 0ull; - inline constexpr size_t VIRT_ZONE_QUEUE_SIZE = (1ull << 16); // 64KB - inline constexpr size_t VIRT_ZONE_QUEUE_BASELINE = UINT64_MAX; // No baseline, we commit it all - - // TRANSIENT ARENA - inline constexpr size_t VIRT_ZONE_TA_OFFSET = VIRT_ZONE_QUEUE_OFFSET + VIRT_ZONE_QUEUE_SIZE; + inline constexpr size_t VIRT_ZONE_TA_OFFSET = 0ull; inline constexpr size_t VIRT_ZONE_TA_SIZE = (1ull << 30); // 1GB - inline constexpr size_t VIRT_ZONE_TA_BASELINE = (1ull << 26); // 64MB - - // COMPONENT POOL ARENA - inline constexpr size_t VIRT_ZONE_CPA_OFFSET = VIRT_ZONE_TA_OFFSET + VIRT_ZONE_TA_SIZE; - inline constexpr size_t VIRT_ZONE_CPA_SIZE = (1ull << 33); // 8GB - inline constexpr size_t VIRT_ZONE_CPA_BASELINE = (1ull << 27); // 128MB - - // SCENE ARENA - inline constexpr size_t VIRT_ZONE_SA_OFFSET = VIRT_ZONE_CPA_OFFSET + VIRT_ZONE_CPA_SIZE; - inline constexpr size_t VIRT_ZONE_SA_SIZE = (1ull << 32); // 4GB - inline constexpr size_t VIRT_ZONE_SA_BASELINE = (1ull << 26); // 64MB - - // IO STREAMING SPACE - inline constexpr size_t VIRT_ZONE_IO_OFFSET = VIRT_ZONE_SA_OFFSET + VIRT_ZONE_SA_SIZE; - inline constexpr size_t VIRT_ZONE_IO_SIZE = (1ull << 30); // 1GB (to be reconsidered) - inline constexpr size_t VIRT_ZONE_IO_BASELINE = UINT64_MAX; // No memory commited, thus no baseline - - // BASELINE SUMMARY - inline constexpr size_t VIRT_ZONE_BASELINE_SUM = - VIRT_ZONE_TA_BASELINE + - VIRT_ZONE_CPA_BASELINE + - VIRT_ZONE_SA_BASELINE; + inline constexpr size_t VIRT_ZONE_TA_BASELINE = (1ull << 26); + + namespace Main + { + inline constexpr size_t VIRT_ZONE_PS_OFFSET = VIRT_ZONE_TA_OFFSET + VIRT_ZONE_TA_SIZE; + inline constexpr size_t VIRT_ZONE_PS_SIZE = (1ull << 31); // 2GB + inline constexpr size_t VIRT_ZONE_PS_BASELINE = (1ull << 26); // 64MB + + inline constexpr size_t VIRT_ZONE_BASELINE_SUM = VIRT_ZONE_PS_SIZE; + } + + namespace Worker + { + // QUEUE + inline constexpr size_t VIRT_ZONE_QUEUE_OFFSET = VIRT_ZONE_TA_OFFSET + VIRT_ZONE_TA_SIZE; + inline constexpr size_t VIRT_ZONE_QUEUE_SIZE = (1ull << 16); // 64KB + inline constexpr size_t VIRT_ZONE_QUEUE_BASELINE = UINT64_MAX; // No baseline, we commit it all + + // TRANSIENT ARENA + inline constexpr size_t VIRT_ZONE_TA_OFFSET = VIRT_ZONE_QUEUE_OFFSET + VIRT_ZONE_QUEUE_SIZE; + inline constexpr size_t VIRT_ZONE_TA_SIZE = (1ull << 30); // 1GB + inline constexpr size_t VIRT_ZONE_TA_BASELINE = (1ull << 26); // 64MB + + // COMPONENT POOL ARENA + inline constexpr size_t VIRT_ZONE_CPA_OFFSET = VIRT_ZONE_TA_OFFSET + VIRT_ZONE_TA_SIZE; + inline constexpr size_t VIRT_ZONE_CPA_SIZE = (1ull << 33); // 8GB + inline constexpr size_t VIRT_ZONE_CPA_BASELINE = (1ull << 27); // 128MB + + // SCENE ARENA + inline constexpr size_t VIRT_ZONE_SA_OFFSET = VIRT_ZONE_CPA_OFFSET + VIRT_ZONE_CPA_SIZE; + inline constexpr size_t VIRT_ZONE_SA_SIZE = (1ull << 32); // 4GB + inline constexpr size_t VIRT_ZONE_SA_BASELINE = (1ull << 26); // 64MB + + // IO STREAMING SPACE + inline constexpr size_t VIRT_ZONE_IO_OFFSET = VIRT_ZONE_SA_OFFSET + VIRT_ZONE_SA_SIZE; + inline constexpr size_t VIRT_ZONE_IO_SIZE = (1ull << 30); // 1GB (to be reconsidered) + inline constexpr size_t VIRT_ZONE_IO_BASELINE = UINT64_MAX; // No memory commited, thus no baseline + + // BASELINE SUMMARY + inline constexpr size_t VIRT_ZONE_BASELINE_SUM = + VIRT_ZONE_TA_BASELINE + + VIRT_ZONE_CPA_BASELINE + + VIRT_ZONE_SA_BASELINE; + } } struct EngineMemoryConfig diff --git a/Engine/src/delta/core/ThreadContext.cpp b/Engine/src/delta/core/ThreadContext.cpp index f9f3b2b..8e3019c 100644 --- a/Engine/src/delta/core/ThreadContext.cpp +++ b/Engine/src/delta/core/ThreadContext.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include #include "ThreadContext.h" #include "MemoryConfig.h" @@ -6,10 +22,13 @@ namespace delta::core { + uintptr_t g_MasterSlabStart = 0; + uintptr_t g_MasterSlabEnd = 0; + static uint32_t g_ThreadCount = 0; static uint32_t g_WorkerCount = 0; - static ThreadExecutionContext* g_ThreadContexts = nullptr; - thread_local ThreadExecutionContext* tl_CurrentThreadContext = nullptr; + static GenericExecutionContext* g_ThreadContexts = nullptr; + static thread_local GenericExecutionContext* tl_CurrentThreadContext = nullptr; DLT_FORCE_INLINE static void InitializePageCoordinator(ThreadPageCoordinator& pageCoord, size_t pageSize, uint8_t* baseAddress) { @@ -19,7 +38,12 @@ namespace delta::core pageCoord.reservedCapacity = MemoryMap::VIRT_ZONE_SPACE_LENGTH; } - DLT_FORCE_INLINE static void InitializeQueue(const ThreadPageCoordinator& pageCoord, TaskQueue& queue, size_t offset, size_t memSize) + DLT_FORCE_INLINE static void InitializeQueue( + const ThreadPageCoordinator& pageCoord, + TaskQueue& queue, + DependencyCounter* depCounter, + size_t offset, + size_t memSize) { queue.size = memSize / TaskQueue::FIELD_SIZE; queue.mask = queue.size - 1; @@ -37,7 +61,12 @@ namespace delta::core queue.payloads = reinterpret_cast(payloadsArrayPtr); } - DLT_FORCE_INLINE static void InitializeArena(const ThreadPageCoordinator& pageCoord, ThreadArena& arena, size_t offset, size_t baseline) + DLT_FORCE_INLINE static void InitializeArena( + const ThreadPageCoordinator& pageCoord, + ThreadArena& arena, + size_t offset, + size_t baseline, + size_t maxCapacity) { uint8_t* pTarget = pageCoord.virtualAddressBase + offset; void* res = delta::platform::Memory_Commit(pTarget, baseline); @@ -48,27 +77,36 @@ namespace delta::core arena.backingMemory = pTarget; arena.capacity = baseline; arena.offset = 0; + arena.maxCapacity = maxCapacity; + } + + template + DLT_FORCE_INLINE ContextType& GetExecutionContext(size_t i) + { + return reinterpret_cast(*(reinterpret_cast(g_ThreadContexts) + i * THREAD_EXECUTION_CONTEXT_SIZE)); } - void ThreadContext_Initialize(uint32_t workerCount, size_t pageSize) + void ThreadContext_Initialize(uint32_t threadCount, size_t pageSize) { - uint32_t totalThreads = workerCount + 1; // include main thread + g_ThreadCount = threadCount; + g_WorkerCount = threadCount - 1; // assign 32Gb of address space per thread // master pool size is rounded up to page size constexpr size_t ADDR_SLICE_PER_THREAD = (1ull << 35); - size_t contextArraySize = sizeof(ThreadExecutionContext) * totalThreads; + size_t contextArraySize = THREAD_EXECUTION_CONTEXT_SIZE * threadCount; size_t alignedContextArraySize = ALIGN(contextArraySize, pageSize); - size_t masterPoolSize = (ADDR_SLICE_PER_THREAD * totalThreads) + alignedContextArraySize; + size_t masterPoolSize = (ADDR_SLICE_PER_THREAD * threadCount) + alignedContextArraySize; uint8_t* masterPoolBase = reinterpret_cast( delta::platform::Memory_Reserve(masterPoolSize) ); assert(masterPoolBase != nullptr && "Failed to reserve master pool"); - g_ThreadCount = workerCount; - g_WorkerCount = workerCount - 1; - g_ThreadContexts = reinterpret_cast( + g_MasterSlabStart = reinterpret_cast(masterPoolBase); + g_MasterSlabEnd = g_MasterSlabStart + masterPoolSize; + + g_ThreadContexts = reinterpret_cast( delta::platform::Memory_Commit(masterPoolBase, alignedContextArraySize) ); assert(g_ThreadContexts != nullptr && "Failed to commit thread context array"); @@ -76,21 +114,60 @@ namespace delta::core if (!delta::platform::Memory_Lock(masterPoolBase, alignedContextArraySize)) std::cout << "[DeltaEngine-Warning] Failed to lock memory resource: Master Thread Context Pool\n"; - uint8_t* virtualRunwayCursor = masterPoolBase + alignedContextArraySize; - for (uint32_t i = 0; i < totalThreads; i++) + // pre-initialize generics + uint8_t* runwayCursor = masterPoolBase + alignedContextArraySize; + for (uint32_t i = 0; i < threadCount; i++) { - ThreadExecutionContext& ctx = g_ThreadContexts[i]; - ctx.threadIx = i; - ctx.threadId = 0; // to be set later + GenericExecutionContext& ctx = GetExecutionContext(i); + delta::platform::Timer_Initialize(&ctx.perThreadTimer); + InitializePageCoordinator(ctx.pageCoordinator, pageSize, runwayCursor); + InitializeArena( + ctx.pageCoordinator, + ctx.transientArena, + MemoryMap::VIRT_ZONE_TA_OFFSET, + MemoryMap::VIRT_ZONE_TA_BASELINE, + MemoryMap::VIRT_ZONE_TA_SIZE + ); + + runwayCursor += MemoryMap::VIRT_ZONE_SPACE_LENGTH; + } - InitializePageCoordinator(ctx.pageCoordinator, pageSize, virtualRunwayCursor); - InitializeQueue(ctx.pageCoordinator, ctx.taskQueue, MemoryMap::VIRT_ZONE_QUEUE_OFFSET, MemoryMap::VIRT_ZONE_QUEUE_SIZE); - InitializeArena(ctx.pageCoordinator, ctx.transientArena, MemoryMap::VIRT_ZONE_TA_OFFSET, MemoryMap::VIRT_ZONE_TA_BASELINE); - InitializeArena(ctx.pageCoordinator, ctx.componentPoolArena, MemoryMap::VIRT_ZONE_CPA_OFFSET, MemoryMap::VIRT_ZONE_CPA_BASELINE); - InitializeArena(ctx.pageCoordinator, ctx.sceneArena, MemoryMap::VIRT_ZONE_SA_OFFSET, MemoryMap::VIRT_ZONE_SA_BASELINE); + // Finish initializing main thread context + DependencyCounter* depCounterPtr = nullptr; + { + MainExecutionContext& ctx = GetExecutionContext(0); + ctx.generic.type = ThreadType::MAIN; + ctx.generic.threadIx = 0; + ctx.generic.threadHandle = delta::platform::Thread_GetCurrentHandle(); + ctx.depCounter.count.store(0, std::memory_order_relaxed); + depCounterPtr = &ctx.depCounter; + + InitializeArena( + ctx.generic.pageCoordinator, + ctx.persistentStorage, + MemoryMap::Main::VIRT_ZONE_PS_OFFSET, + MemoryMap::Main::VIRT_ZONE_PS_BASELINE, + MemoryMap::Main::VIRT_ZONE_PS_SIZE + ); + } - delta::platform::Timer_Initialize(&ctx.perThreadTimer); - virtualRunwayCursor += ADDR_SLICE_PER_THREAD; + for (uint32_t i = 1; i < threadCount; i++) + { + WorkerExecutionContext& ctx = GetExecutionContext(i); + ctx.generic.type = ThreadType::WORKER; + ctx.generic.threadIx = i; + ctx.generic.threadHandle = delta::platform::INVALID_THREAD_HANDLE; // Initialized when thread starts + ctx.isAsleep.store(false, std::memory_order_relaxed); + ctx.shouldClose.store(false, std::memory_order_relaxed); + ctx.sleepSemaphore = delta::platform::Sync_CreateSemaphore(); + + InitializeQueue( + ctx.generic.pageCoordinator, + ctx.taskQueue, + depCounterPtr, + MemoryMap::Worker::VIRT_ZONE_QUEUE_OFFSET, + MemoryMap::Worker::VIRT_ZONE_QUEUE_SIZE + ); } tl_CurrentThreadContext = &g_ThreadContexts[0]; @@ -101,10 +178,25 @@ namespace delta::core delta::platform::Memory_Release(g_ThreadContexts); } + GenericExecutionContext* ThreadContext_GetCurrent() noexcept + { + return tl_CurrentThreadContext; + } + + GenericExecutionContext* ThreadContext_GetForIndex(uint32_t i) noexcept + { + return &GetExecutionContext(i); + } + + void ThreadContext_SetCurrent(GenericExecutionContext* ctx) noexcept + { + tl_CurrentThreadContext = ctx; + } + void TaskQueue_Push(TaskQueue* queue, task_t task, payload_t payload) { - uint64_t b = queue->bottom.load(std::memory_order_relaxed); - uint64_t t = queue->top.load(std::memory_order_acquire); + queue_index_t b = queue->bottom.load(std::memory_order_relaxed); + queue_index_t t = queue->top.load(std::memory_order_acquire); if (b - t > queue->size) { @@ -113,7 +205,7 @@ namespace delta::core return; } - uint64_t ix = b & queue->mask; + queue_index_t ix = b & queue->mask; queue->tasks[ix] = task; queue->payloads[ix] = payload; @@ -123,11 +215,11 @@ namespace delta::core bool TaskQueue_Pop(TaskQueue* queue, task_t* outTask, payload_t* outPayload) { - uint64_t b = queue->bottom.load(std::memory_order_relaxed) - 1; + queue_index_t b = queue->bottom.load(std::memory_order_relaxed) - 1; queue->bottom.store(b, std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_seq_cst); - uint64_t t = queue->top.load(std::memory_order_relaxed); + queue_index_t t = queue->top.load(std::memory_order_relaxed); if (t > b) { @@ -135,13 +227,13 @@ namespace delta::core return false; } - uint64_t ix = b & queue->mask; + queue_index_t ix = b & queue->mask; *outTask = queue->tasks[ix]; *outPayload = queue->payloads[ix]; if (t == b) { - uint64_t expectedTop = t; + queue_index_t expectedTop = t; if (!queue->top.compare_exchange_strong(expectedTop, expectedTop + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { queue->bottom.store(b + 1, std::memory_order_relaxed); @@ -156,15 +248,15 @@ namespace delta::core bool TaskQueue_Steal(TaskQueue* queue, task_t* outTask, payload_t* outPayload) { - uint64_t t = queue->top.load(std::memory_order_acquire); + queue_index_t t = queue->top.load(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst); - uint64_t b = queue->bottom.load(std::memory_order_acquire); + queue_index_t b = queue->bottom.load(std::memory_order_acquire); if (t >= b) return false; - uint64_t ix = t & queue->mask; + queue_index_t ix = t & queue->mask; *outTask = queue->tasks[ix]; *outPayload = queue->payloads[ix]; @@ -176,12 +268,17 @@ namespace delta::core void Scheduler_ProcessTaskBatch(task_t* tasks, payload_t* payloads, size_t length) { - assert(tl_CurrentThreadContext->threadIx == 0); // CAN BE EXECUTED ONLY ON MAIN THREAD! + assert(IsMainThread()); // CAN BE EXECUTED ONLY ON MAIN THREAD! + GetMainContext().depCounter.count.store(length, std::memory_order_relaxed); for (size_t i = 0; i < length; i++) { size_t targetIx = 1 + (i % g_WorkerCount); - TaskQueue_Push(&g_ThreadContexts[targetIx].taskQueue, tasks[i], payloads[i]); + WorkerExecutionContext& ctx = GetExecutionContext(targetIx); + TaskQueue_Push(&ctx.taskQueue, tasks[i], payloads[i]); + + if (ctx.isAsleep.load(std::memory_order_acquire)) + delta::platform::Sync_SignalSemaphore(ctx.sleepSemaphore); } } @@ -194,10 +291,11 @@ namespace delta::core while (consecutiveEmptyQueues < g_ThreadCount) { + WorkerExecutionContext& ctx = GetExecutionContext(workerIx); task_t task = nullptr; payload_t payload = nullptr; - if (TaskQueue_Steal(&g_ThreadContexts[workerIx].taskQueue, &task, &payload)) + if (TaskQueue_Steal(&ctx.taskQueue, &task, &payload)) { consecutiveEmptyQueues = 0; assert(task && payload); @@ -215,6 +313,13 @@ namespace delta::core } } + ThreadArena* GetTransientArena() noexcept + { + auto* ctx = tl_CurrentThreadContext; + assert(ctx); + return &ctx->transientArena; + } + void* ThreadArena_Allocate(ThreadArena* arena, size_t size, size_t alignment) { uintptr_t currentAddress = reinterpret_cast(arena->backingMemory) + arena->offset; @@ -222,6 +327,12 @@ namespace delta::core size_t padding = alignedAddress - currentAddress; size_t totalSpace = padding + size; + if (arena->offset + totalSpace > arena->maxCapacity) + { + // TODO: Handle it better. I don't know how yet, but I will know soon. + assert(false); // arena overflown + } + if (totalSpace > arena->capacity) { // the slow path: assign more pages diff --git a/Engine/src/delta/core/ThreadContext.h b/Engine/src/delta/core/ThreadContext.h index cc5dc17..a4aa6a9 100644 --- a/Engine/src/delta/core/ThreadContext.h +++ b/Engine/src/delta/core/ThreadContext.h @@ -1,5 +1,22 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once +#include #include "EngineTypes.h" namespace delta::core @@ -8,6 +25,11 @@ namespace delta::core void ThreadContext_Initialize(uint32_t workerCount, size_t pageSize); void ThreadContext_Shutdown(); + // Getters + GenericExecutionContext* ThreadContext_GetCurrent() noexcept; + GenericExecutionContext* ThreadContext_GetForIndex(uint32_t ix) noexcept; + void ThreadContext_SetCurrent(GenericExecutionContext* ctx) noexcept; + // Thread Task Queue API void TaskQueue_Push(TaskQueue* queue, task_t task, payload_t payload); bool TaskQueue_Pop(TaskQueue* queue, task_t* outTask, payload_t* outPayload); @@ -18,6 +40,70 @@ namespace delta::core void Scheduler_Sync(); // Engine Arena API - void* ThreadArena_Allocate(ThreadArena* arena, size_t size, size_t alignment = 8); - void ThreadArena_Reset(ThreadArena* arena); + ThreadArena* GetTransientArena() noexcept; + void* ThreadArena_Allocate(ThreadArena* arena, size_t size, size_t alignment = 8); + void ThreadArena_Reset(ThreadArena* arena); + void ThreadArena_Reset(ThreadArena* arena); + + // Inline Helpers + template + [[deprecated]] [[nodiscard]] inline TargetType* ThreadContextCast(GenericExecutionContext* ctx) + { + if (!ctx) return nullptr; + + if constexpr (std::is_same_v) + { + if (ctx->type == ThreadType::MAIN) + return reinterpret_cast(ctx); + } + else if constexpr (std::is_same_v) + { + if (ctx->type == ThreadType::WORKER) + return reinterpret_cast(ctx); + } + + assert(false); // CRITICAL: Casting to invalid type + return nullptr; + } + + inline bool IsCustomAllocated(void* ptr) + { + // range scan + return g_MasterSlabStart <= reinterpret_cast(ptr) && reinterpret_cast(ptr) <= g_MasterSlabEnd; + } + + inline bool IsMainThread() + { + auto* ctx = ThreadContext_GetCurrent(); + return ctx && ctx->type == ThreadType::MAIN; + } + + inline bool IsWorkerThread() + { + auto* ctx = ThreadContext_GetCurrent(); + return ctx && ctx->type == ThreadType::WORKER; + } + + DLT_DISABLE_WARNING_PUSH + DLT_DISABLE_MISSING_RETURN + inline MainExecutionContext& GetMainContext() + { + auto* ctx = ThreadContext_GetCurrent(); + if (ctx->type == ThreadType::MAIN) + return reinterpret_cast(*ctx); + + assert(false); // this shouldn't ever happen + DLT_UNREACHABLE; + } + + inline WorkerExecutionContext& GetWorkerContext() + { + auto* ctx = ThreadContext_GetCurrent(); + if (ctx->type == ThreadType::WORKER) + return reinterpret_cast(*ctx); + + assert(false); + DLT_UNREACHABLE; + } + DLT_DISABLE_WARNING_POP } diff --git a/Engine/src/delta/core/Worker.cpp b/Engine/src/delta/core/Worker.cpp new file mode 100644 index 0000000..7cded61 --- /dev/null +++ b/Engine/src/delta/core/Worker.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Worker.h" +#include +#include + +namespace delta::core +{ + using ThreadHandle = delta::platform::ThreadHandle; + using ThreadCreateInfo = delta::platform::ThreadCreateInfo; + + using SemaphoreHandle = delta::platform::SemaphoreHandle; + + static uint32_t s_WorkerCount; + static ThreadHandle* s_Threads; + + static void WorkerProc(void* args) + { + WorkerExecutionContext* ctx = reinterpret_cast(args); + ThreadContext_SetCurrent((GenericExecutionContext*)ctx); + + task_t task; + payload_t payload; + DependencyCounter& depCounter = reinterpret_cast(*ctx->taskQueue.depCounterPtr); + while (!ctx->shouldClose.load(std::memory_order_acquire)) + { + // TODO: Figure out how to select a worker to steal some work from. + // I'll probably utilize modulo n cyclic groups. (algebra - group theory) + + if (TaskQueue_Pop(&ctx->taskQueue, &task, &payload)) + { + task(payload); + depCounter.count.fetch_sub(1, std::memory_order_release); + continue; + } + + ThreadArena_Reset(GetTransientArena()); + ctx->isAsleep.store(true, std::memory_order_release); + if (!ctx->shouldClose.load(std::memory_order_acquire)) + { + delta::platform::Sync_WaitSemaphore(ctx->sleepSemaphore); + } + + ctx->isAsleep.store(false, std::memory_order_release); + } + } + + void Worker_Init(uint32_t count) + { + s_WorkerCount = count; + + // TODO: Take a look at native thread api and figure out how this could be done better + // TODO: Set thread affinity mask + s_Threads = new(delta::Engine::AllocationType::PERSISTENT) ThreadHandle[count]; + ThreadCreateInfo* cs = new(delta::Engine::AllocationType::TRANSIENT) ThreadCreateInfo[count]; + + for (uint32_t i = 0; i < count; i++) + { + // looping through thread indices, workers start from ix=1 + ThreadCreateInfo& info = cs[i]; + ThreadHandle& handle = s_Threads[i]; + auto* ctx = ThreadContext_GetForIndex(i + 1); + info.fn = WorkerProc; + info.args = (void*)ctx; + handle = delta::platform::Thread_Create(&info); + ctx->threadHandle = handle; + delta::platform::Thread_AssignPhysicalCore(handle, i + 1); + delta::platform::Thread_SetName(handle, "Worker"); + } + } + + void Worker_Shutdown() + { + delta::platform::Thread_JoinMultiple(s_Threads, s_WorkerCount); + } +} diff --git a/Engine/src/delta/core/Worker.h b/Engine/src/delta/core/Worker.h new file mode 100644 index 0000000..6ec2a8b --- /dev/null +++ b/Engine/src/delta/core/Worker.h @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace delta::core +{ + void Worker_Init(uint32_t count); + void Worker_Shutdown(); +} diff --git a/Engine/src/delta/core/engine.cpp b/Engine/src/delta/core/engine.cpp index b12fbbe..bfdcdc7 100644 --- a/Engine/src/delta/core/engine.cpp +++ b/Engine/src/delta/core/engine.cpp @@ -15,25 +15,77 @@ */ #include +#include #include #include -#include -#include -#include -void delta::Engine::Initialize(Context& context) +#include "EngineTypes.h" +#include "MemoryConfig.h" +#include "ThreadContext.h" +#include "Worker.h" + +namespace delta::Engine { - context.isRunning = true; - delta::platform::Initialize(); - const auto* osInfo = delta::platform::getOSInfo(); - const auto memStatus = delta::platform::getMemoryStatus(); + using GenericExecutionContext = delta::core::GenericExecutionContext; - delta::core::MemoryConfig_Initialize(memStatus.physicalInstalled, osInfo->osPageSize, osInfo->maxEngineWorkerCount); - delta::core::ThreadContext_Initialize(osInfo->maxEngineWorkerCount, osInfo->osPageSize); -} + void Initialize(Context& context) + { + context.isRunning = true; + delta::platform::Initialize(); + const auto* osInfo = delta::platform::getOSInfo(); + const auto memStatus = delta::platform::getMemoryStatus(); -void delta::Engine::Shutdown(Context& context) -{ - delta::core::ThreadContext_Shutdown(); - delta::core::MemoryConfig_Shutdown(); + uint32_t totalThreads = osInfo->cpuPhysicalCoreCount; + uint32_t pageSize = osInfo->osPageSize; + delta::core::MemoryConfig_Initialize(memStatus.physicalInstalled, pageSize, totalThreads); + delta::core::ThreadContext_Initialize(totalThreads, pageSize); + + delta::platform::ThreadHandle th = delta::platform::Thread_GetCurrentHandle(); + delta::platform::Thread_AssignPhysicalCore(th, 0); + delta::core::Worker_Init(totalThreads-1); + } + + void Update(Context& context) + { + // blah blah blah + // do something + delta::platform::Sync_Sleep(100); + delta::core::ThreadArena_Reset(delta::core::GetTransientArena()); + } + + void Shutdown(Context& context) + { + delta::core::Worker_Shutdown(); + delta::core::ThreadContext_Shutdown(); + delta::core::MemoryConfig_Shutdown(); + } + + [[nodiscard]] void* Allocate(size_t size, AllocationType type, size_t alignment) noexcept + { + GenericExecutionContext* threadContext = delta::core::ThreadContext_GetCurrent(); + + if (!threadContext) + { + return ::malloc(size); + } + + if (type == AllocationType::TRANSIENT) + { + return core::ThreadArena_Allocate(&threadContext->transientArena, size, alignment); + } + else if (type == AllocationType::PERSISTENT && core::IsMainThread()) + { + core::MainExecutionContext* ctx = reinterpret_cast(threadContext); + return core::ThreadArena_Allocate(&ctx->persistentStorage, size, alignment); + } + + assert(false); // CRITICAL FAILURE: Workers cannot allocate persistent memory! + return nullptr; + } + + void Free(void* ptr) noexcept + { + if (!core::IsCustomAllocated(ptr)) + ::free(ptr); + } } diff --git a/Engine/src/delta/internal_definitions.h b/Engine/src/delta/internal_definitions.h deleted file mode 100644 index 0166698..0000000 --- a/Engine/src/delta/internal_definitions.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#ifdef _MSC_VER - #define DLT_FORCE_INLINE __forceinline -#else - #define DLT_FORCE_INLINE __attribute__((always_inline)) -#endif diff --git a/Engine/src/delta/pch.h b/Engine/src/delta/pch.h index 32f55a2..a8b39f2 100644 --- a/Engine/src/delta/pch.h +++ b/Engine/src/delta/pch.h @@ -6,5 +6,5 @@ #include #include #include - -#include "internal_definitions.h" +#include +#include diff --git a/Engine/src/delta/platform/compiler.h b/Engine/src/delta/platform/compiler.h new file mode 100644 index 0000000..b53804c --- /dev/null +++ b/Engine/src/delta/platform/compiler.h @@ -0,0 +1,35 @@ +#pragma once + +#if defined(_MSC_VER) + #define DLT_DISABLE_WARNING_PUSH __pragma(warning(push)) + #define DLT_DISABLE_WARNING_POP __pragma(warning(pop)) + #define DLT_DISABLE_MISSING_RETURN __pragma(warning(disable : 4715)) + + #define DLT_FORCE_INLINE __forceinline + + #define DLT_UNREACHABLE __assume(0) +#elif defined(__clang__) + #define DLT_DISABLE_WARNING_PUSH _Pragma("clang diagnostic push") + #define DLT_DISABLE_WARNING_POP _Pragma("clang diagnostic pop") + #define DLT_DISABLE_MISSING_RETURN _Pragma("clang diagnostic ignored \"-Wreturn-type\"") + + #define DLT_FORCE_INLINE __attribute__((always_inline)) + + #define DLT_UNREACHABLE __builtin_unreachable() +#elif defined(__GNUC__) || defined(__GNUG__) + #define DLT_DISABLE_WARNING_PUSH _Pragma("GCC diagnostic push") + #define DLT_DISABLE_WARNING_POP _Pragma("GCC diagnostic pop") + #define DLT_DISABLE_MISSING_RETURN _Pragma("GCC diagnostic ignored \"-Wreturn-type\"") + + #define DLT_FORCE_INLINE __attribute__((always_inline)) + + #define DLT_UNREACHABLE __builtin_unreachable() +#else + #define DLT_DISABLE_WARNING_PUSH + #define DLT_DISABLE_WARNING_POP + #define DLT_DISABLE_MISSING_RETURN + + #define DLT_FORCE_INLINE + + #define DLT_UNREACHABLE do {} while(0) +#endif diff --git a/Engine/src/delta/platform/os_internal.h b/Engine/src/delta/platform/os_internal.h index f39bb45..f4c3a70 100644 --- a/Engine/src/delta/platform/os_internal.h +++ b/Engine/src/delta/platform/os_internal.h @@ -21,6 +21,9 @@ namespace delta::platform struct BrandStringCall; struct Timer_Internal; + struct Semaphore; + using SemaphoreHandle = Semaphore*; + struct Timer { alignas(8) uint8_t opaqueData[32]; @@ -41,4 +44,31 @@ namespace delta::platform int64_t Timer_GetTimestamp(); double Timer_TicksToMilliseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); double Timer_TicksToMicroseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); + + // Thread API + struct Thread; + using ThreadHandle = Thread*; + inline constexpr ThreadHandle INVALID_THREAD_HANDLE = nullptr; // random number 696767 + + struct ThreadCreateInfo + { + void (*fn)(void*); + void* args; + }; + + uint32_t Thread_GetCurrentId(); + uint32_t Thread_GetId(ThreadHandle thread); + ThreadHandle Thread_GetCurrentHandle(); + ThreadHandle Thread_Create(ThreadCreateInfo* createInfo); + void Thread_AssignPhysicalCore(ThreadHandle thread, uint32_t coreIndex); + void Thread_SetName(ThreadHandle handle, const char* name); + void Thread_Join(ThreadHandle thread); + void Thread_JoinMultiple(ThreadHandle* threads, uint32_t count); + + // Sync API + SemaphoreHandle Sync_CreateSemaphore(); + void Sync_DestroySemaphore(SemaphoreHandle sem); + void Sync_SignalSemaphore(SemaphoreHandle sem); + void Sync_WaitSemaphore(SemaphoreHandle sem); + void Sync_Sleep(uint32_t milliseconds); } diff --git a/Engine/src/delta/platform/os_win32.cpp b/Engine/src/delta/platform/os_win32.cpp index fd3984e..df4806b 100644 --- a/Engine/src/delta/platform/os_win32.cpp +++ b/Engine/src/delta/platform/os_win32.cpp @@ -16,6 +16,7 @@ #ifdef _WIN32 +#include #include #include "os_internal.h" #include @@ -43,6 +44,27 @@ namespace delta::platform int64_t baseStartTime; }; + struct Thread + { + HANDLE hThread; + }; + + struct Semaphore + { + HANDLE hSemaphore; + }; + + struct THREADNAME_INFO + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + }; + + static_assert(std::is_standard_layout_v, "PAL layout broken: Must be standard layout!"); + static_assert(sizeof(Thread) == sizeof(HANDLE), "PAL layout broken: Size mismatch!"); + enum CpuArchitecture : WORD { INTEL = 0, @@ -178,11 +200,6 @@ namespace delta::platform g_osInfo.cpuLogicalProcessorCount = logicalProcessors; g_osInfo.cpuHasSMT = (logicalProcessors > physicalCores); - if (physicalCores > 1) - g_osInfo.maxEngineWorkerCount = physicalCores - 1; - else - g_osInfo.maxEngineWorkerCount = 1; - bool privilegesSet = SetProcessPrivileges(); if (!privilegesSet) std::cout << "[DeltaEngine-Warning] Failed to elevate SE_INC_WORKING_SET_NAME privilege. You may want to run the game as an administrator.\n"; @@ -288,6 +305,120 @@ namespace delta::platform return (static_cast(elapsedTicks) * 1000000.0) / static_cast(internal->freq); } + + static DWORD WINAPI DeltaThreadProc(LPVOID lParam) + { + ThreadCreateInfo* createInfo = reinterpret_cast(lParam); + createInfo->fn(createInfo->args); + return 0; + } + + uint32_t Thread_GetCurrentId() + { + return GetCurrentThreadId(); + } + + uint32_t Thread_GetId(ThreadHandle thread) + { + return GetThreadId(thread->hThread); + } + + ThreadHandle Thread_GetCurrentHandle() + { + Thread* t = new(delta::Engine::AllocationType::PERSISTENT) Thread(); + t->hThread = GetCurrentThread(); + if (!t->hThread) + return nullptr; + + return t; + } + + void Thread_AssignPhysicalCore(ThreadHandle thread, uint32_t coreIndex) + { + uint32_t logicalCore = coreIndex * 2; + DWORD_PTR affinityMask = (DWORD_PTR)(1ull << logicalCore); + DWORD_PTR prevMask = SetThreadAffinityMask(thread->hThread, affinityMask); + } + + void Thread_SetName(ThreadHandle handle, const char* name) + { + const THREADNAME_INFO info = + { + .dwType = 0x1000, + .szName = name, + .dwThreadID = GetThreadId(handle->hThread), + .dwFlags = 0 + }; + + __try + { + ::RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast(&info)); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + + } + } + + ThreadHandle Thread_Create(ThreadCreateInfo* createInfo) + { + Thread* thread = new(delta::Engine::AllocationType::PERSISTENT) Thread{}; + thread->hThread = CreateThread(nullptr, 0, DeltaThreadProc, (void*)createInfo, 0, 0); + if (thread->hThread == 0) + return nullptr; + + return static_cast(thread); + } + + void Thread_Join(ThreadHandle thread) + { + if (!thread) + return; + + DWORD waitResult = WaitForSingleObject(thread->hThread, INFINITE); + CloseHandle(thread->hThread); + } + + void Thread_JoinMultiple(ThreadHandle* threads, uint32_t count) + { + if (!threads) + return; + + WaitForMultipleObjects(count, reinterpret_cast(threads), TRUE, INFINITE); + } + + SemaphoreHandle Sync_CreateSemaphore() + { + Semaphore* s = new(delta::Engine::AllocationType::PERSISTENT) Semaphore{}; + s->hSemaphore = CreateSemaphoreA(nullptr, 0, 1000000, nullptr); + if (s->hSemaphore == nullptr) + return nullptr; + + return static_cast(s); + } + + void Sync_DestroySemaphore(SemaphoreHandle sem) + { + if (!sem) + return; + + CloseHandle(sem->hSemaphore); + } + + void Sync_SignalSemaphore(SemaphoreHandle handle) + { + ReleaseSemaphore(handle->hSemaphore, 1, nullptr); + } + + void Sync_WaitSemaphore(SemaphoreHandle handle) + { + WaitForSingleObject(handle->hSemaphore, INFINITE); + } + + void Sync_Sleep(uint32_t milliseconds) + { + Sleep(milliseconds); + } } #endif diff --git a/Examples/HelloWorldGame/game.cpp b/Examples/HelloWorldGame/game.cpp index a59d077..44bb162 100644 --- a/Examples/HelloWorldGame/game.cpp +++ b/Examples/HelloWorldGame/game.cpp @@ -58,22 +58,11 @@ extern "C" void GAME_API Game_OnUpdate(delta::Engine::Context* context) { - char input; - std::cout << "\"1\" to continue and \"2\" to exit: "; - std::cin >> input; - - switch (input) - { - case '1': - break; - case '2': - context->isRunning = false; - break; - } + } void GAME_API Game_OnShutdown(delta::Engine::Context* context) { - std::cout << "Shutdown\n"; + } } diff --git a/Launcher/main.cpp b/Launcher/main.cpp index 0a24556..d1525ae 100644 --- a/Launcher/main.cpp +++ b/Launcher/main.cpp @@ -172,6 +172,7 @@ int main(int argc, char** argv) continue; } + delta::Engine::Update(g_context); game.updateFn(&g_context); }