diff --git a/Code/max/Hardware/CPU/Task.cpp b/Code/max/Hardware/CPU/Task.cpp new file mode 100644 index 0000000..2b5cb75 --- /dev/null +++ b/Code/max/Hardware/CPU/Task.cpp @@ -0,0 +1,15 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +namespace max { +namespace Hardware { +namespace CPU { + + Task::~Task() noexcept = default; + +} // namespace CPU +} // namespace Hardware +} // namespace max \ No newline at end of file diff --git a/Code/max/Hardware/CPU/Task.hpp b/Code/max/Hardware/CPU/Task.hpp new file mode 100644 index 0000000..5f7a81d --- /dev/null +++ b/Code/max/Hardware/CPU/Task.hpp @@ -0,0 +1,25 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAX_HARDWARE_CPU_TASK_HPP +#define MAX_HARDWARE_CPU_TASK_HPP + +namespace max { +namespace Hardware { +namespace CPU { + + class Task { + public: + + virtual ~Task() noexcept; + + virtual void Run() noexcept = 0; + + }; + +} // namespace CPU +} // namespace Hardware +} // namespace max + +#endif // #ifndef MAX_HARDWARE_CPU_TASK_HPP \ No newline at end of file diff --git a/Code/max/Hardware/CPU/TaskQueue.cpp b/Code/max/Hardware/CPU/TaskQueue.cpp new file mode 100644 index 0000000..b55a282 --- /dev/null +++ b/Code/max/Hardware/CPU/TaskQueue.cpp @@ -0,0 +1,124 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +#include + +namespace max { +namespace Hardware { +namespace CPU { + + TaskQueue::TaskQueue(HANDLE wake_event) noexcept + : task_queue_mutex_() + , task_queue_() + , wake_event_(std::move(wake_event)) + , shutting_down_(false) + { + } + + TaskQueue::~TaskQueue() noexcept { + BOOL result = CloseHandle(wake_event_); + if (result == 0) { + // GetLastError() + } + } + + TaskQueue::AddTaskError TaskQueue::AddTask(std::unique_ptr task) noexcept { + if (shutting_down_) { + return AddTaskError::ShuttingDown; + } + + { + std::lock_guard task_queue_guard(task_queue_mutex_); + task_queue_.push(std::move(task)); + BOOL result = SetEvent(wake_event_); + if (result == 0) { + // GetLastError() + return AddTaskError::CouldNotSetEvent; + } + } + + return AddTaskError::Okay; + } + + std::unique_ptr TaskQueue::TryPopTask() noexcept { + std::lock_guard task_queue_guard(task_queue_mutex_); + + BOOL result = ResetEvent(wake_event_); + if (result == 0) { + // GetLastError + return nullptr; + } + + if (task_queue_.empty()) { + return nullptr; + } + + std::unique_ptr task = std::move(task_queue_.front()); + task_queue_.pop(); + return task; + } + + TaskQueue::WaitForEventError TaskQueue::WaitForEvent() noexcept { + auto result = WaitForMultipleObjects(1, &wake_event_, TRUE, INFINITE); + if (result != WAIT_OBJECT_0) { + return WaitForEventError::CouldNotWait; + } else { + result = ResetEvent(wake_event_); + if (result == 0) { + // GetLastError() + return WaitForEventError::CouldNotResetEvent; + } + } + + return WaitForEventError::Okay; + } + + TaskQueue::ShutdownError TaskQueue::Shutdown() noexcept { + std::lock_guard task_queue_guard(task_queue_mutex_); + + shutting_down_ = true; + BOOL result = SetEvent(wake_event_); + if (result == 0) { + // GetLastError() + return ShutdownError::CouldNotSetEvent; + } + + return ShutdownError::Okay; + } + + std::expected, CreateTaskQueueError> CreateTaskQueue() noexcept { + HANDLE wake_event = CreateEvent(NULL, TRUE, FALSE, TEXT("wake_event_")); + if (wake_event == NULL) { + // GetLastError() + return std::unexpected(CreateTaskQueueError::CouldNotCreate); + } + + return std::make_unique(std::move(wake_event)); + } + + void TaskRunnerLoop(TaskQueue* task_queue) noexcept { + bool continue_looping = true; + do { + auto task = task_queue->TryPopTask(); + if (task) { + task->Run(); + } + else { + if (task_queue->shutting_down_) { + continue_looping = false; + } + else { + task_queue->WaitForEvent(); + } + } + } while (continue_looping); + } + +} // namespace CPU +} // namespace Hardware +} // namespace max \ No newline at end of file diff --git a/Code/max/Hardware/CPU/TaskQueue.hpp b/Code/max/Hardware/CPU/TaskQueue.hpp new file mode 100644 index 0000000..223e435 --- /dev/null +++ b/Code/max/Hardware/CPU/TaskQueue.hpp @@ -0,0 +1,89 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP +#define MAX_HARDWARE_CPU_TASKQUEUE_HPP + +#include +#include +#include +#include + + +// TODO: Handle other platforms +// TODO: This is only needed for the definition of HANDLE. Can we remove this somehow? +#ifndef WIN32_LEAN_AND_MEAN +#define WIn32_LEAN_AND_MEAN +#endif +#include + +#include + +namespace max { +namespace Hardware { +namespace CPU { + + // TaskQueue uses std::mutex, which cannot be copied nor moved. + // This restriction has a major impact on the design of the class. + // Instead of the usual std::expected CreateT() function, + // we use std::expected CreateT(). + // + // The additional unique_ptr means the actual object doesn't have to move. + + // Assume a 64-byte cache line size, and align to that. + // This is so a vector of TaskQueues does not cause false sharing between threads. + // TODO: VC warns that alignas(64) did indeed pad. And we treat warnings as errors. + class /*alignas(64)*/ TaskQueue { + public: + + // TODO: This is only public so std::make_unique can call it + TaskQueue(HANDLE wake_event) noexcept; + ~TaskQueue() noexcept; + + enum class AddTaskError { + Okay, + ShuttingDown, + CouldNotSetEvent, + }; + AddTaskError AddTask(std::unique_ptr task) noexcept; + + // TODO: Should this return a std::expected so it can provide error values? + std::unique_ptr TryPopTask() noexcept; + + enum class WaitForEventError { + Okay, + CouldNotWait, + CouldNotResetEvent, + }; + WaitForEventError WaitForEvent() noexcept; + + enum class ShutdownError { + Okay, + CouldNotSetEvent, + }; + ShutdownError Shutdown() noexcept; + + volatile bool shutting_down_; + + private: + + std::mutex task_queue_mutex_; + std::queue> task_queue_; + HANDLE wake_event_; + + }; + + enum class CreateTaskQueueError { + CouldNotCreate, + }; + // TODO: Possibly provide an allocator, to allocate the TaskQueue at the right location + std::expected, CreateTaskQueueError> CreateTaskQueue() noexcept; + + void TaskRunnerLoop(TaskQueue* task_queue) noexcept; + +} // namespace CPU +} // namespace Hardware +} // namespace max + +#endif // #ifndef MAX_HARDWARE_CPU_TASKQUEUE_HPP \ No newline at end of file diff --git a/Code/max/Hardware/CPU/TaskThread.cpp b/Code/max/Hardware/CPU/TaskThread.cpp new file mode 100644 index 0000000..ae31518 --- /dev/null +++ b/Code/max/Hardware/CPU/TaskThread.cpp @@ -0,0 +1,33 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include + +namespace max { +namespace Hardware { +namespace CPU { + + TaskThread::TaskThread(std::unique_ptr task_queue, std::thread thread) noexcept + : task_queue_(std::move(task_queue)) + , thread_(std::move(thread)) + {} + + std::optional CreateTaskThread() noexcept { + auto create_task_queue_result = max::Hardware::CPU::CreateTaskQueue(); + if (!create_task_queue_result) { + return std::nullopt; + } + std::unique_ptr task_queue = std::move(*create_task_queue_result); + + auto thread = std::thread(max::Hardware::CPU::TaskRunnerLoop, task_queue.get()); + // TODO: Attempt to lock that thread to the preferred type of core. + + return TaskThread(std::move(task_queue), std::move(thread)); + } + +} // namespace CPU +} // namespace Hardware +} // namespace max \ No newline at end of file diff --git a/Code/max/Hardware/CPU/TaskThread.hpp b/Code/max/Hardware/CPU/TaskThread.hpp new file mode 100644 index 0000000..ce0f0e1 --- /dev/null +++ b/Code/max/Hardware/CPU/TaskThread.hpp @@ -0,0 +1,34 @@ +// Copyright 2025, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP +#define MAX_HARDWARE_CPU_TASKTHREAD_HPP + +#include +#include +#include + +#include + +namespace max { +namespace Hardware { +namespace CPU { + + class TaskThread { + public: + + explicit TaskThread(std::unique_ptr task_queue, std::thread thread) noexcept; + + std::unique_ptr task_queue_; + std::thread thread_; + + }; + + std::optional CreateTaskThread() noexcept; + +} // namespace CPU +} // namespace Hardware +} // namespace max + +#endif // #ifndef MAX_HARDWARE_CPU_TASKTHREAD_HPP \ No newline at end of file diff --git a/Projects/VisualStudio/max/max.vcxproj b/Projects/VisualStudio/max/max.vcxproj index 18e336f..cfaca87 100644 --- a/Projects/VisualStudio/max/max.vcxproj +++ b/Projects/VisualStudio/max/max.vcxproj @@ -202,6 +202,9 @@ true + + + @@ -305,6 +308,9 @@ true + + + diff --git a/Projects/VisualStudio/max/max.vcxproj.filters b/Projects/VisualStudio/max/max.vcxproj.filters index eaa76c9..9d20f27 100644 --- a/Projects/VisualStudio/max/max.vcxproj.filters +++ b/Projects/VisualStudio/max/max.vcxproj.filters @@ -393,6 +393,15 @@ Code\max\Compiling + + Code\max\Hardware\CPU + + + Code\max\Hardware\CPU + + + Code\max\Hardware\CPU + @@ -479,6 +488,15 @@ Code\max\Containers + + Code\max\Hardware\CPU + + + Code\max\Hardware\CPU + + + Code\max\Hardware\CPU +