summaryrefslogtreecommitdiff
path: root/src/KingSystem/Utils/Thread
diff options
context:
space:
mode:
authorLéo Lam <leo@leolam.fr>2020-09-10 22:11:27 +0200
committerLéo Lam <leo@leolam.fr>2020-09-15 17:54:16 +0200
commit8b7369dffb1e816b2f27ab0133d2bac218383a45 (patch)
treec5d6a577ec9102e65a6e08272432dfa862670a60 /src/KingSystem/Utils/Thread
parentc5ef0a1fd3629a27dc2fc7de7a46347c6cb3518e (diff)
ksys: Add Task utilities
Diffstat (limited to 'src/KingSystem/Utils/Thread')
-rw-r--r--src/KingSystem/Utils/Thread/Task.cpp277
-rw-r--r--src/KingSystem/Utils/Thread/Task.h193
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueue.cpp7
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueue.h21
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueueBase.cpp491
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueueBase.h157
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueueLock.cpp26
-rw-r--r--src/KingSystem/Utils/Thread/TaskQueueLock.h28
-rw-r--r--src/KingSystem/Utils/Thread/TaskThread.cpp261
-rw-r--r--src/KingSystem/Utils/Thread/TaskThread.h109
10 files changed, 1570 insertions, 0 deletions
diff --git a/src/KingSystem/Utils/Thread/Task.cpp b/src/KingSystem/Utils/Thread/Task.cpp
new file mode 100644
index 00000000..7ab00b55
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/Task.cpp
@@ -0,0 +1,277 @@
+#include "KingSystem/Utils/Thread/Task.h"
+#include <thread/seadThread.h>
+#include "KingSystem/Utils/Thread/TaskQueueBase.h"
+#include "KingSystem/Utils/Thread/TaskQueueLock.h"
+#include "KingSystem/Utils/Thread/TaskThread.h"
+
+namespace ksys::util {
+
+TaskDelegateSetter::TaskDelegateSetter() = default;
+
+TaskDelegateSetter::~TaskDelegateSetter() = default;
+
+void TaskDelegateSetter::setDelegate(TaskDelegate* delegate) {
+ mDelegate = delegate;
+}
+
+Task::Task(sead::Heap* heap) : mEvent(heap, true) {
+ mEvent.setSignal();
+}
+
+Task::Task(sead::Heap* heap, sead::IDisposer::HeapNullOption heap_null_option)
+ : mEvent(heap, heap_null_option, true) {
+ mEvent.setSignal();
+}
+
+Task::~Task() {
+ finalize_();
+}
+
+void Task::deleteDelegate_() {
+ if (mDelegate && mFlags.isOn(Flag::DeleteDelegate) && mFlags.isOff(Flag::DoNotDeleteDelegate)) {
+ delete mDelegate;
+ mDelegate = nullptr;
+ }
+}
+
+// NON_MATCHING: mDelegate2 = nullptr store
+void Task::finalize_() {
+ if (mStatus == Status::Finalized)
+ return;
+
+ removeFromQueue();
+ deleteDelegate_();
+ mUserData = nullptr;
+ mQueue = nullptr;
+ mPostRunCallback = nullptr;
+ mRemoveCallback = nullptr;
+ mStatus = Status::Finalized;
+}
+
+bool Task::setDelegate(const TaskDelegateSetter& setter) {
+ mDelegate = setter.getDelegate();
+ mFlags.reset(Flag::DeleteDelegate);
+ mFlags.reset(Flag::DoNotDeleteDelegate);
+ mFlags.set(Flag::DoNotDeleteDelegate);
+ return onSetDelegate_(setter);
+}
+
+// NON_MATCHING: branching
+bool Task::submitRequest(TaskRequest& request) {
+ // Processing this request is impossible if there is no thread *and* no queue!
+ if (request.mThread == nullptr && request.mQueue == nullptr)
+ return false;
+
+ if (!canSubmitRequest())
+ return false;
+
+ if (request.mSynchronous || request.mHasHandle)
+ mFlags.set(Flag::NeedsToSignalEvent);
+ else
+ mFlags.reset(Flag::NeedsToSignalEvent);
+
+ mFlags.change(Flag::SynchronousRequest, request.mSynchronous);
+
+ if (mListNode.isLinked())
+ return false;
+
+ if (mFlags.isOn(Flag::NeedsToSignalEvent))
+ mEvent.resetSignal();
+
+ mQueue = request.mQueue;
+ if (!mQueue) {
+ mQueue = request.mThread->getTaskQueue();
+ request.mQueue = mQueue;
+ }
+ mUserData = request.mUserData;
+ if (auto* delegate = request.mDelegate) {
+ deleteDelegate_();
+ mFlags.set(Flag::DoNotDeleteDelegate);
+ mDelegate = delegate;
+ }
+ mRemoveCallback = request.mRemoveCallback;
+ mPostRunCallback = request.mPostRunCallback;
+ mName = request.mName;
+
+ prepare_(&request);
+
+ if (request.mSynchronous) {
+ auto* thread = mQueue->getCurrentThread();
+ if (thread) {
+ processOnCurrentThreadDirectly(thread);
+ return true;
+ }
+ }
+
+ TaskQueueBase::PushArg arg;
+ arg.lane_id = request.mLaneId;
+ arg.task = this;
+ const bool push_ok = mQueue->push(arg);
+ bool b;
+ if (push_ok) {
+ if (request.mSynchronous)
+ mEvent.wait();
+ b = true;
+ } else {
+ b = false;
+ }
+ return push_ok || b;
+}
+
+bool Task::canSubmitRequest() const {
+ const bool run_finished_on_current_thread =
+ mThread && mStatus == Status::RunFinished &&
+ mThread == sead::ThreadMgr::instance()->getCurrentThread();
+ const bool cond2 = isInactive();
+ return run_finished_on_current_thread || cond2;
+}
+
+void Task::processOnCurrentThreadDirectly(TaskThread* thread) {
+ {
+ TaskQueueLock lock{thread};
+ mThread = thread;
+ mStatus = Status::Pushed;
+ }
+
+ run();
+
+ {
+ TaskQueueLock lock{thread};
+ onRunFinished();
+ }
+
+ TaskPostRunResult result;
+ invokePostRunCallback(&result);
+
+ {
+ TaskQueueLock lock{thread};
+ if (!result.getResult())
+ finish();
+ }
+}
+
+void Task::removeFromQueue() {
+ if (mQueue)
+ mQueue->removeTask(this, true);
+}
+
+void Task::removeFromQueue2() {
+ // TODO: how does this differ from removeFromQueue?
+ removeFromQueue();
+}
+
+void Task::run_() {
+ if (mDelegate)
+ mDelegateResult = (*mDelegate)(mUserData);
+}
+
+bool Task::wait() {
+ mEvent.wait();
+ return true;
+}
+
+bool Task::wait(const sead::TickSpan& span) {
+ return mEvent.wait(span);
+}
+
+u8 Task::getLaneId() const {
+ return mLaneId;
+}
+
+bool Task::isSuccess() const {
+ return (mStatus == Status::PreFinishCallback || mStatus == Status::RunFinished ||
+ mStatus == Status::PostFinishCallback) &&
+ mDelegateResult;
+}
+
+bool Task::isInactive() const {
+ return mStatus == Status::Uninitialized || mStatus == Status::RemovedFromQueue ||
+ mStatus == Status::PostFinishCallback;
+}
+
+void Task::setStatusPushed() {
+ mStatus = Status::Pushed;
+}
+
+void Task::setThread(TaskThread* thread) {
+ mThread = thread;
+}
+
+void Task::run() {
+ run_();
+ mStatus = Status::RunFinished;
+}
+
+void Task::onRunFinished() {
+ onRunFinished_();
+}
+
+void Task::invokePostRunCallback(TaskPostRunResult* result) {
+ mRemoveCallback = nullptr;
+ TaskPostRunContext context;
+ context.mCancelled = mFlags.isOn(Flag::Cancelled);
+ context.mTask = this;
+ context.mUserData = mUserData;
+ if (auto* delegate = mPostRunCallback) {
+ mPostRunCallback = nullptr;
+ delegate->invoke(result, context);
+ }
+}
+
+void Task::finish() {
+ mStatus = Status::PreFinishCallback;
+ onFinish_();
+ mStatus = Status::PostFinishCallback;
+
+ mThread = nullptr;
+ signalEvent();
+
+ onPostFinish_();
+}
+
+void* Task::getUserData() const {
+ return mUserData;
+}
+
+void Task::cancel() {
+ mEvent.resetSignal();
+ mFlags.set(Flag::Cancelled);
+ mFlags.set(Flag::NeedsToSignalEvent);
+}
+
+void Task::onRemove() {
+ invokeRemoveCallback_();
+
+ TaskQueueLock lock;
+ mQueue->lock(&lock);
+
+ preRemove_();
+
+ mStatus = Status::RemovedFromQueue;
+ mThread = nullptr;
+ signalEvent();
+
+ postRemove_();
+}
+
+void Task::invokeRemoveCallback_() {
+ mPostRunCallback = nullptr;
+ TaskRemoveCallbackContext context;
+ context.mTask = this;
+ context.mUserData = mUserData;
+
+ if (auto* delegate = mRemoveCallback) {
+ mRemoveCallback = nullptr;
+ delegate->invoke(context);
+ }
+}
+
+void Task::setStatusFetched() {
+ mStatus = Status::Fetched;
+}
+
+void Task::setLaneId(u8 id) {
+ mLaneId = id;
+}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/Task.h b/src/KingSystem/Utils/Thread/Task.h
new file mode 100644
index 00000000..df54db8e
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/Task.h
@@ -0,0 +1,193 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+#include <container/seadListImpl.h>
+#include <prim/seadDelegate.h>
+#include <prim/seadRuntimeTypeInfo.h>
+#include <prim/seadSafeString.h>
+#include <prim/seadTypedBitFlag.h>
+#include "KingSystem/Utils/Thread/Event.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::util {
+
+class Task;
+class TaskQueueBase;
+class TaskRequest;
+class TaskThread;
+
+class TaskPostRunResult {
+ SEAD_RTTI_BASE(TaskPostRunResult)
+public:
+ virtual ~TaskPostRunResult() = default;
+
+ bool getResult() const { return mResult; }
+ void setResult(bool result) { mResult = result; }
+
+private:
+ bool mResult = false;
+};
+KSYS_CHECK_SIZE_NX150(TaskPostRunResult, 0x10);
+
+class TaskPostRunContext {
+ SEAD_RTTI_BASE(TaskPostRunContext)
+public:
+ virtual ~TaskPostRunContext() = default;
+
+ bool mCancelled;
+ Task* mTask;
+ void* mUserData;
+};
+KSYS_CHECK_SIZE_NX150(TaskPostRunContext, 0x20);
+
+class TaskRemoveCallbackContext {
+ SEAD_RTTI_BASE(TaskRemoveCallbackContext)
+public:
+ virtual ~TaskRemoveCallbackContext() = default;
+
+ Task* mTask;
+ void* mUserData;
+};
+KSYS_CHECK_SIZE_NX150(TaskRemoveCallbackContext, 0x18);
+
+using TaskDelegate = sead::AnyDelegate1R<void*, bool>;
+using TaskPostRunCallback = sead::IDelegate2<TaskPostRunResult*, const TaskPostRunContext&>;
+using TaskRemoveCallback = sead::IDelegate1<const TaskRemoveCallbackContext&>;
+
+class TaskDelegateSetter {
+ SEAD_RTTI_BASE(TaskDelegateSetter)
+public:
+ TaskDelegateSetter();
+ explicit TaskDelegateSetter(TaskDelegate* delegate) : TaskDelegateSetter() {
+ setDelegate(delegate);
+ }
+ virtual ~TaskDelegateSetter();
+ TaskDelegate* getDelegate() const { return mDelegate; }
+ void setDelegate(TaskDelegate* delegate);
+
+private:
+ TaskDelegate* mDelegate = nullptr;
+};
+KSYS_CHECK_SIZE_NX150(TaskDelegateSetter, 0x10);
+
+class TaskRequest {
+ SEAD_RTTI_BASE(TaskRequest)
+public:
+ virtual ~TaskRequest() = default;
+
+ bool mHasHandle;
+ /// If true, request submissions will block until the request is processed.
+ bool mSynchronous;
+ u8 mLaneId;
+ TaskThread* mThread;
+ TaskQueueBase* mQueue;
+ TaskDelegate* mDelegate;
+ void* mUserData;
+ TaskRemoveCallback* mRemoveCallback;
+ TaskPostRunCallback* mPostRunCallback;
+ sead::SafeString mName;
+};
+KSYS_CHECK_SIZE_NX150(TaskRequest, 0x50);
+
+class Task {
+ SEAD_RTTI_BASE(Task)
+public:
+ enum class Status {
+ Uninitialized = 0,
+ RemovedFromQueue = 1,
+ Pushed = 2,
+ Fetched = 3,
+ PreFinishCallback = 4,
+ RunFinished = 5,
+ PostFinishCallback = 6,
+ Finalized = 7,
+ };
+
+ explicit Task(sead::Heap* heap);
+ Task(sead::Heap* heap, sead::IDisposer::HeapNullOption heap_null_option);
+ virtual ~Task();
+
+ bool setDelegate(const TaskDelegateSetter& setter);
+ bool submitRequest(TaskRequest& request);
+ bool canSubmitRequest() const;
+ void processOnCurrentThreadDirectly(TaskThread* thread);
+
+ void removeFromQueue();
+ void removeFromQueue2();
+
+ void cancel();
+ bool wait();
+ bool wait(const sead::TickSpan& span);
+
+ Status getStatus() const { return mStatus; }
+ bool isSuccess() const;
+ bool isInactive() const;
+
+ u8 getLaneId() const;
+ void* getUserData() const;
+ TaskQueueBase* getQueue() const { return mQueue; }
+
+protected:
+ friend class TaskQueueBase;
+ friend class TaskThread;
+
+ enum class Flag {
+ DeleteDelegate = 0x1,
+ DoNotDeleteDelegate = 0x2,
+ NeedsToSignalEvent = 0x4,
+ SynchronousRequest = 0x8,
+ Cancelled = 0x10,
+ };
+
+ static size_t getListNodeOffset() { return offsetof(Task, mListNode); }
+
+ virtual bool onSetDelegate_(const TaskDelegateSetter&) { return true; }
+ virtual void prepare_(TaskRequest* request);
+ virtual void run_();
+ virtual void onRunFinished_() {}
+ virtual void onFinish_() {}
+ virtual void onPostFinish_() {}
+ virtual void preRemove_() {}
+ virtual void postRemove_() {}
+
+ void setLaneId(u8 id);
+ void setThread(TaskThread* thread);
+ void setStatusPushed();
+ void setStatusFetched();
+ void run();
+ void onRunFinished();
+ void invokePostRunCallback(TaskPostRunResult* result);
+ void finish();
+ void onRemove();
+
+ void finalize_();
+ void deleteDelegate_();
+ void invokeRemoveCallback_();
+
+ void signalEvent() {
+ if (!mFlags.isOn(Flag::NeedsToSignalEvent))
+ return;
+
+ mFlags.reset(Flag::Cancelled);
+ mEvent.setSignal();
+ }
+
+ u8 mLaneId = 0;
+ sead::TypedBitFlag<Flag, u8> mFlags = Flag::DoNotDeleteDelegate;
+ bool mDelegateResult = false;
+ TaskDelegate* mDelegate = nullptr;
+ void* mUserData = nullptr;
+ TaskQueueBase* mQueue = nullptr;
+ TaskThread* mThread = nullptr;
+ TaskPostRunCallback* mPostRunCallback = nullptr;
+ TaskRemoveCallback* mRemoveCallback = nullptr;
+ Status mStatus = Status::Uninitialized;
+ sead::ListNode mListNode;
+ Event mEvent;
+ sead::SafeString mName;
+};
+KSYS_CHECK_SIZE_NX150(Task, 0xa8);
+
+inline void Task::prepare_(TaskRequest*) {}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueue.cpp b/src/KingSystem/Utils/Thread/TaskQueue.cpp
new file mode 100644
index 00000000..cf76c768
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueue.cpp
@@ -0,0 +1,7 @@
+#include "KingSystem/Utils/Thread/TaskQueue.h"
+
+namespace ksys::util {
+
+TaskQueue::TaskQueue(sead::Heap* heap) : TaskQueueBase(heap), mCS(heap) {}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueue.h b/src/KingSystem/Utils/Thread/TaskQueue.h
new file mode 100644
index 00000000..6792a783
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueue.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <thread/seadCriticalSection.h>
+#include "KingSystem/Utils/Thread/TaskQueueBase.h"
+
+namespace ksys::util {
+
+class TaskQueue : public TaskQueueBase {
+ SEAD_RTTI_OVERRIDE(TaskQueue, TaskQueueBase)
+public:
+ explicit TaskQueue(sead::Heap* heap);
+
+private:
+ void lock() const override { mCS.lock(); }
+ void unlock() const override { mCS.unlock(); }
+
+ mutable sead::CriticalSection mCS;
+};
+KSYS_CHECK_SIZE_NX150(TaskQueue, 0xd0);
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueueBase.cpp b/src/KingSystem/Utils/Thread/TaskQueueBase.cpp
new file mode 100644
index 00000000..430fdbf2
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueueBase.cpp
@@ -0,0 +1,491 @@
+#include "KingSystem/Utils/Thread/TaskQueueBase.h"
+#include <algorithm>
+#include <memory>
+#include <thread/seadThread.h>
+#include "KingSystem/Utils/Debug.h"
+#include "KingSystem/Utils/Thread/Task.h"
+#include "KingSystem/Utils/Thread/TaskQueueLock.h"
+#include "KingSystem/Utils/Thread/TaskThread.h"
+
+namespace ksys::util {
+
+static const auto cSleepSpan = sead::TickSpan::fromMicroSeconds(10);
+
+TaskQueueBase::TaskQueueBase(sead::Heap* heap) : mQueueEmptyEvent(heap) {
+ mActiveTasks.initOffset(Task::getListNodeOffset());
+}
+
+TaskQueueBase::~TaskQueueBase() {
+ clear();
+
+ for (auto& lane : mLanes) {
+ delete lane.lane_empty_event;
+ lane.lane_empty_event = nullptr;
+ }
+
+ mLanes.freeBuffer();
+ mThreads.freeBuffer();
+}
+
+void TaskQueueBase::clear() {
+ lock();
+
+ // Clear all tasks.
+ for (auto it = mActiveTasks.robustBegin(), end = mActiveTasks.robustEnd(); it != end; ++it) {
+ mActiveTasks.erase(std::addressof(*it));
+ it->onRemove();
+ }
+ mActiveTasks.clear();
+
+ for (auto& lane : mLanes)
+ lane.head_task = nullptr;
+
+ const bool is_any_thread_busy = isAnyThreadBusy();
+
+ signalEmptyEventsIfNeeded();
+ {
+ ConditionalScopedLock lock{this};
+ ScopedLock lock1{this};
+ for (auto& thread : mThreads) {
+ thread.cancelCurrentTask();
+ }
+ }
+
+ unlock();
+
+ if (is_any_thread_busy)
+ mQueueEmptyEvent.wait();
+}
+
+// NON_MATCHING: swapped operands for a csel. The arg.set_flag1 check looks suspicious.
+bool TaskQueueBase::init(const InitArg& arg) {
+ if (arg.max_num_threads == 0)
+ return false;
+
+ if (!mThreads.tryAllocBuffer(arg.max_num_threads, arg.heap))
+ return false;
+
+ if (!arg.enable_locks)
+ mFlags.reset(Flag::Lock);
+ else
+ mFlags.set(Flag::Lock);
+
+ if (arg.num_lanes <= 0 || arg.num_lanes > 0x100)
+ return false;
+
+ mLanes.allocBufferAssert(arg.num_lanes, arg.heap);
+ for (auto& lane : mLanes) {
+ lane.lane_empty_event = new (arg.heap) Event(arg.heap, true);
+ lane.lane_empty_event->setSignal();
+ }
+
+ mQueueEmptyEvent.initialize(true);
+ mQueueEmptyEvent.setSignal();
+ mTaskSelectionDelegate = arg.task_selection_delegate;
+
+ return true;
+}
+
+bool TaskQueueBase::addThread(TaskThread* thread) {
+ if (mFlags.isOn(Flag::PreventThreadPoolChanges))
+ return false;
+
+ lockIfNeeded();
+
+ if (mThreads.isFull()) {
+ unlockIfNeeded();
+ return false;
+ }
+
+ mThreads.pushBack(thread);
+ unlockIfNeeded();
+ return true;
+}
+
+void TaskQueueBase::removeThread(TaskThread* thread) {
+ if (mFlags.isOn(Flag::PreventThreadPoolChanges))
+ return;
+
+ ConditionalScopedLock lock{this};
+ mThreads.erase(mThreads.search(thread));
+}
+
+s32 TaskQueueBase::getNumActiveTasks() const {
+ return mActiveTasks.size();
+}
+
+s32 TaskQueueBase::countTasksInLane(u16 id) const {
+ lock();
+
+ if (!mLanes[id].head_task) {
+ unlock();
+ return 0;
+ }
+
+ s32 count = 0;
+ for (auto i = mActiveTasks.begin(mLanes[id].head_task), end = mActiveTasks.end(); i != end;
+ ++i) {
+ if (i->getLaneId() != id)
+ break;
+ ++count;
+ }
+ unlock();
+ return count;
+}
+
+bool TaskQueueBase::areNoThreadsBusy() const {
+ ScopedLock lock{this};
+
+ if (!mActiveTasks.isEmpty())
+ return false;
+ return !isAnyThreadBusy();
+}
+
+bool TaskQueueBase::isAnyThreadBusy() const {
+ ConditionalScopedLock lock{this};
+ return std::any_of(mThreads.begin(), mThreads.end(),
+ [](const TaskThread& thread) { return thread.isBusyProcessingTask(); });
+}
+
+bool TaskQueueBase::areAllThreadsPaused() const {
+ ConditionalScopedLock lock{this};
+ return std::all_of(mThreads.begin(), mThreads.end(),
+ [](const TaskThread& thread) { return thread.isPaused(); });
+}
+
+void TaskQueueBase::waitForQueueToEmpty() {
+ if (areAllThreadsPaused())
+ return;
+
+ for (const auto& lane : mLanes) {
+ if (lane.blocked && lane.head_task)
+ return;
+ }
+
+ mQueueEmptyEvent.wait();
+}
+
+void TaskQueueBase::waitForLaneToEmpty(u8 id) {
+ if (areAllThreadsPaused())
+ return;
+
+ const auto& lane = mLanes[id];
+ if (!lane.blocked || !lane.head_task)
+ lane.lane_empty_event->wait();
+}
+
+void TaskQueueBase::cancelTasks(u8 id) {
+ lock();
+
+ if (mLanes[id].head_task) {
+ for (auto it = mActiveTasks.robustBegin(mLanes[id].head_task),
+ end = mActiveTasks.robustEnd();
+ it != end; ++it) {
+ if (it->getLaneId() != id)
+ break;
+ mActiveTasks.erase(std::addressof(*it));
+ it->onRemove();
+ }
+ }
+ mLanes[id].head_task = nullptr;
+
+ const auto cancel_current_tasks_if_needed = [&] {
+ ConditionalScopedLock lock{this};
+ ScopedLock lock1{this};
+ for (auto it = mThreads.begin(), end = mThreads.end(); it != end; ++it) {
+ if (it->mTask && it->mTask->getLaneId() == id)
+ it->cancelCurrentTask();
+ }
+ };
+
+ if (isProcessingTask(id)) {
+ mLanes[id].lane_empty_event->resetSignal();
+ signalEmptyEventsIfNeeded();
+ cancel_current_tasks_if_needed();
+ unlock();
+ mLanes[id].lane_empty_event->wait();
+ } else {
+ signalEmptyEventsIfNeeded();
+ cancel_current_tasks_if_needed();
+ unlock();
+ }
+}
+
+bool TaskQueueBase::isProcessingTask(u8 id) const {
+ ConditionalScopedLock lock{this};
+ ScopedLock lock1{this};
+ return std::any_of(mThreads.begin(), mThreads.end(), [id](const TaskThread& thread) {
+ return thread.mTask && thread.mTask->getLaneId() == id;
+ });
+}
+
+void TaskQueueBase::signalEmptyEventsIfNeeded() {
+ ScopedLock lock{this};
+
+ const bool is_any_thread_busy = isAnyThreadBusy();
+ const bool has_no_tasks = mActiveTasks.isEmpty();
+ if (!is_any_thread_busy && has_no_tasks)
+ mQueueEmptyEvent.setSignal();
+
+ for (auto it = mLanes.begin(), end = mLanes.end(); it != end; ++it) {
+ if (!isProcessingTask(it.getIndex()) && it->head_task == nullptr)
+ it->lane_empty_event->setSignal();
+ }
+}
+
+void TaskQueueBase::blockTasks(u8 id) {
+ if (mLanes[id].blocked != 1)
+ mLanes[id].blocked = true;
+}
+
+// NON_MATCHING: the while (!areAllThreadsPaused()) loop generates weird code in the original
+void TaskQueueBase::blockTasksAndReloadThreads(u8 id) {
+ blockTasks(id);
+
+ {
+ ConditionalScopedLock lock{this};
+ for (auto& thread : mThreads)
+ thread.pause();
+ }
+
+ const auto sleep_duration = sead::TickSpan::fromMilliSeconds(1);
+
+ while (!areAllThreadsPaused())
+ sead::Thread::sleep(sleep_duration);
+
+ sead::Thread::sleep(sleep_duration);
+
+ {
+ ConditionalScopedLock lock{this};
+ for (auto& thread : mThreads)
+ thread.resume();
+ }
+}
+
+void TaskQueueBase::unblockTasks(u8 id) {
+ if (mLanes[id].blocked) {
+ mLanes[id].blocked = false;
+ notifyThreadsForNewTasks();
+ }
+}
+
+void TaskQueueBase::lock(TaskQueueLock* lock) {
+ lock->lock(this);
+}
+
+TaskThread* TaskQueueBase::getCurrentThread() const {
+ const sead::Thread* current_thread = sead::ThreadMgr::instance()->getCurrentThread();
+ lockIfNeeded();
+ for (auto it = mThreads.begin(), end = mThreads.end(); it != end; ++it) {
+ if (current_thread == std::addressof(*it)) {
+ unlockIfNeeded();
+ return std::addressof(*it);
+ }
+ }
+ unlockIfNeeded();
+ return nullptr;
+}
+
+sead::OffsetList<Task>::iterator TaskQueueBase::activeTasksBegin(TaskQueueLock* lock) {
+ lock->lock(this);
+ return mActiveTasks.begin();
+}
+
+sead::OffsetList<Task>::robustIterator TaskQueueBase::activeTasksRobustBegin(TaskQueueLock* lock) {
+ lock->lock(this);
+ return mActiveTasks.robustBegin();
+}
+
+sead::OffsetList<Task>::iterator TaskQueueBase::activeTasksEnd() const {
+ return mActiveTasks.end();
+}
+
+sead::OffsetList<Task>::robustIterator TaskQueueBase::activeTasksRobustEnd() const {
+ return mActiveTasks.robustEnd();
+}
+
+void TaskQueueBase::notifyThreadsForNewTasks() {
+ s32 retry_count = 0;
+ const sead::Thread* current_thread = sead::ThreadMgr::instance()->getCurrentThread();
+ sead::BitFlag32 mask = 0;
+
+ while (true) {
+ lockIfNeeded();
+ bool done = true;
+ s32 i = 0;
+ auto* data = mThreads.data();
+ for (auto& thread : mThreads) {
+ static_cast<void>(thread);
+ if (current_thread != data[i] && !data[i]->isLookingForTask() &&
+ !data[i]->receivedQueueUpdatedMsg() && !data[i]->receivedPauseMsg() &&
+ !data[i]->receivedResumeMsg() && !data[i]->receivedQuitMsg() && !mask.isOnBit(i)) {
+ const bool send_ok = data[i]->sendMessage(
+ TaskThread::cMessage_QueueUpdated, sead::MessageQueue::BlockType::NonBlocking);
+ if (send_ok)
+ mask.setBit(i);
+ done &= send_ok;
+ }
+ ++i;
+ }
+ unlockIfNeeded();
+
+ if (done)
+ break;
+
+ ++retry_count;
+ sead::Thread::sleep(cSleepSpan);
+ }
+
+ if (retry_count >= 2)
+ PrintDebug(sead::FormatFixedSafeString<128>("↓↓↓\nリトライ回数 %d 回\n↑↑↑\n", retry_count));
+}
+
+// NON_MATCHING: regalloc for max_idx
+bool TaskQueueBase::push(const PushArg& arg) {
+ lock();
+
+ if (!arg.task || mActiveTasks.isNodeLinked(arg.task)) {
+ unlock();
+ return false;
+ }
+
+ const auto num_tasks = mActiveTasks.size();
+ const u8 max_idx = mLanes.size() - 1;
+ const u8 id = arg.lane_id <= max_idx ? arg.lane_id : max_idx;
+ arg.task->setLaneId(id);
+
+ bool added = false;
+ for (u8 i = id - 1; i != 0xff; --i) {
+ if (!mLanes[i].head_task)
+ continue;
+
+ mActiveTasks.insertBefore(mLanes[i].head_task, arg.task);
+ added = true;
+ break;
+ }
+
+ if (!added) {
+ mActiveTasks.pushBack(arg.task);
+ mLanes[id].lane_empty_event->resetSignal();
+ }
+
+ if (!mLanes[id].head_task)
+ mLanes[id].head_task = arg.task;
+
+ arg.task->setStatusPushed();
+
+ if (num_tasks == 0)
+ mQueueEmptyEvent.resetSignal();
+
+ unlock();
+ notifyThreadsForNewTasks();
+ return true;
+}
+
+void TaskQueueBase::removeTask(Task* task, bool b) {
+ if (!task)
+ return;
+
+ lock();
+
+ if (!task->isInactive()) {
+ if (task->getStatus() == Task::Status::Pushed) {
+ const u8 id = task->getLaneId();
+ if (mLanes[id].head_task == task) {
+ auto* new_task = mActiveTasks.next(task);
+ if (new_task && task->getLaneId() == new_task->getLaneId())
+ mLanes[id].head_task = new_task;
+ else
+ mLanes[id].head_task = nullptr;
+ }
+
+ mActiveTasks.erase(task);
+ task->onRemove();
+ signalEmptyEventsIfNeeded();
+ } else if (b) {
+ task->cancel();
+ unlock();
+ task->wait();
+ return;
+ }
+ }
+
+ unlock();
+}
+
+// NON_MATCHING: regalloc inside the task lambda + reorderings for the loop counters.
+void TaskQueueBase::fetchTask(Task** out_task) {
+ lock();
+
+ const auto check_state = [&] {
+ if (!mActiveTasks.isEmpty() || isAnyThreadBusy())
+ return true;
+ mQueueEmptyEvent.setSignal();
+ *out_task = nullptr;
+ unlock();
+ return false;
+ };
+
+ if (!check_state())
+ return;
+
+ auto* task = [&]() -> Task* {
+ for (auto it = mLanes.rbegin(), end = mLanes.rend(); it != end; ++it) {
+ if (it->blocked)
+ continue;
+ if (it->head_task == nullptr)
+ continue;
+ if (!mTaskSelectionDelegate)
+ return it->head_task;
+
+ const auto it_begin = mActiveTasks.begin(it->head_task);
+ Task* end_ptr = nullptr;
+ for (auto it2 = it; it2 != mLanes.rbegin(0);) {
+ // XXX: This looks really weird.
+ auto* t = std::addressof(*it2)[-1].head_task;
+ ++it2;
+ if (t) {
+ if (it2->head_task)
+ end_ptr = it2->head_task;
+ break;
+ }
+ }
+ const auto it_end = end_ptr ? mActiveTasks.begin(end_ptr) : mActiveTasks.end();
+
+ TaskSelectionContext context;
+ context.lane_id = it->head_task->getLaneId();
+ context.it_begin = &it_begin;
+ context.it_end = &it_end;
+ Task* task = mTaskSelectionDelegate->invoke(context);
+ if (task)
+ return task;
+ }
+
+ return nullptr;
+ }();
+
+ if (!check_state())
+ return;
+
+ if (task) {
+ for (u8 id = mLanes.size() - 1; id != 0xff; --id) {
+ if (mLanes[id].head_task == task) {
+ auto* new_task = mActiveTasks.next(task);
+ if (new_task && task->getLaneId() == new_task->getLaneId())
+ mLanes[id].head_task = new_task;
+ else
+ mLanes[id].head_task = nullptr;
+ break;
+ }
+ }
+ mActiveTasks.erase(task);
+ task->setStatusFetched();
+ *out_task = task;
+ } else {
+ *out_task = nullptr;
+ }
+
+ unlock();
+}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueueBase.h b/src/KingSystem/Utils/Thread/TaskQueueBase.h
new file mode 100644
index 00000000..3d4b44cf
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueueBase.h
@@ -0,0 +1,157 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+#include <container/seadBuffer.h>
+#include <container/seadOffsetList.h>
+#include <container/seadPtrArray.h>
+#include <prim/seadDelegate.h>
+#include <prim/seadRuntimeTypeInfo.h>
+#include <prim/seadTypedBitFlag.h>
+#include <time/seadTickSpan.h>
+#include "KingSystem/Utils/Thread/Event.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::util {
+
+class Task;
+class TaskQueueLock;
+class TaskThread;
+
+struct TaskSelectionContext {
+ const auto& begin() const { return *it_begin; }
+ const auto& end() const { return *it_end; }
+ u8 lane_id;
+ const sead::OffsetList<Task>::iterator* it_begin;
+ const sead::OffsetList<Task>::iterator* it_end;
+};
+
+using TaskSelectionDelegate = sead::IDelegate1R<const TaskSelectionContext&, Task*>;
+
+class TaskQueueBase {
+ SEAD_RTTI_BASE(TaskQueueBase)
+public:
+ struct InitArg {
+ bool enable_locks;
+ /// Number of lanes.
+ u16 num_lanes;
+ /// Maximum number of threads that will be processing the queue.
+ u16 max_num_threads;
+ sead::Heap* heap;
+ TaskSelectionDelegate* task_selection_delegate;
+ };
+ KSYS_CHECK_SIZE_NX150(InitArg, 0x18);
+
+ struct PushArg {
+ u8 lane_id;
+ Task* task;
+ };
+ KSYS_CHECK_SIZE_NX150(PushArg, 0x10);
+
+ explicit TaskQueueBase(sead::Heap* heap);
+ virtual ~TaskQueueBase();
+
+ void clear();
+ bool init(const InitArg& arg);
+
+ bool addThread(TaskThread* thread);
+ void removeThread(TaskThread* thread);
+
+ s32 getNumActiveTasks() const;
+ s32 countTasksInLane(u16 id) const;
+ bool areNoThreadsBusy() const;
+ bool isAnyThreadBusy() const;
+ bool areAllThreadsPaused() const;
+
+ void waitForQueueToEmpty();
+ void waitForLaneToEmpty(u8 id);
+
+ void cancelTasks(u8 id);
+ bool isProcessingTask(u8 id) const;
+ void signalEmptyEventsIfNeeded();
+
+ void blockTasks(u8 id);
+ void blockTasksAndReloadThreads(u8 id);
+ void unblockTasks(u8 id);
+
+ void lock(TaskQueueLock* lock);
+
+ /// @returns the current thread if it is in the thread pool and nullptr otherwise.
+ TaskThread* getCurrentThread() const;
+
+ sead::OffsetList<Task>::iterator activeTasksBegin(TaskQueueLock* lock);
+ sead::OffsetList<Task>::robustIterator activeTasksRobustBegin(TaskQueueLock* lock);
+ sead::OffsetList<Task>::iterator activeTasksEnd() const;
+ sead::OffsetList<Task>::robustIterator activeTasksRobustEnd() const;
+
+ bool push(const PushArg& arg);
+ void removeTask(Task* task, bool b);
+ void fetchTask(Task** out_task);
+
+protected:
+ enum class Flag {
+ Lock = 0x1,
+ PreventThreadPoolChanges = 0x2,
+ };
+
+ struct Lane {
+ /// If true, tasks in this lane are not allowed to be fetched by any thread.
+ bool blocked = false;
+ /// First task in the lane. Tasks are also added to a linked list (mActiveTasks).
+ Task* head_task = nullptr;
+ Event* lane_empty_event = nullptr;
+ };
+ KSYS_CHECK_SIZE_NX150(Lane, 0x18);
+
+ class ScopedLock {
+ public:
+ explicit ScopedLock(const TaskQueueBase* queue) : mQueue(queue) { mQueue->lock(); }
+ ScopedLock(const ScopedLock&) = delete;
+ ~ScopedLock() { mQueue->unlock(); }
+ ScopedLock& operator=(const ScopedLock&) = delete;
+
+ private:
+ const TaskQueueBase* mQueue;
+ };
+
+ class ConditionalScopedLock {
+ public:
+ explicit ConditionalScopedLock(const TaskQueueBase* queue) : mQueue(queue) {
+ mQueue->lockIfNeeded();
+ }
+ ConditionalScopedLock(const ConditionalScopedLock&) = delete;
+ ~ConditionalScopedLock() { mQueue->unlockIfNeeded(); }
+ ConditionalScopedLock& operator=(const ConditionalScopedLock&) = delete;
+
+ private:
+ const TaskQueueBase* mQueue;
+ };
+
+ friend class TaskQueueLock;
+
+ virtual void lock() const {}
+ virtual void unlock() const {}
+
+ bool shouldLock() const { return mFlags.isOn(Flag::Lock); }
+
+ void lockIfNeeded() const {
+ if (shouldLock())
+ lock();
+ }
+
+ void unlockIfNeeded() const {
+ if (shouldLock())
+ unlock();
+ }
+
+ void notifyThreadsForNewTasks();
+
+ sead::TypedBitFlag<Flag, u8> mFlags;
+ sead::OffsetList<Task> mActiveTasks;
+ sead::Buffer<Lane> mLanes;
+ Event mQueueEmptyEvent;
+ sead::PtrArray<TaskThread> mThreads;
+ TaskSelectionDelegate* mTaskSelectionDelegate = nullptr;
+};
+KSYS_CHECK_SIZE_NX150(TaskQueueBase, 0x90);
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueueLock.cpp b/src/KingSystem/Utils/Thread/TaskQueueLock.cpp
new file mode 100644
index 00000000..be7b1c5e
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueueLock.cpp
@@ -0,0 +1,26 @@
+#include "KingSystem/Utils/Thread/TaskQueueLock.h"
+#include "KingSystem/Utils/Thread/TaskQueueBase.h"
+
+namespace ksys::util {
+
+TaskQueueLock::TaskQueueLock() = default;
+
+TaskQueueLock::~TaskQueueLock() {
+ unlock();
+}
+
+void TaskQueueLock::lock(TaskQueueBase* queue) {
+ if (!mQueue) {
+ mQueue = queue;
+ mQueue->lock();
+ }
+}
+
+void TaskQueueLock::unlock() {
+ if (mQueue) {
+ mQueue->unlock();
+ mQueue = nullptr;
+ }
+}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskQueueLock.h b/src/KingSystem/Utils/Thread/TaskQueueLock.h
new file mode 100644
index 00000000..2bc44035
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskQueueLock.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <prim/seadRuntimeTypeInfo.h>
+#include "KingSystem/Utils/Thread/TaskThread.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::util {
+
+class TaskQueueBase;
+
+class TaskQueueLock {
+ SEAD_RTTI_BASE(TaskQueueLock)
+public:
+ TaskQueueLock();
+ explicit TaskQueueLock(TaskThread* thread) : TaskQueueLock() { thread->lock(this); }
+ TaskQueueLock(const TaskQueueLock&) = delete;
+ virtual ~TaskQueueLock();
+ TaskQueueLock& operator=(const TaskQueueLock&) = delete;
+
+ void lock(TaskQueueBase* queue);
+ void unlock();
+
+private:
+ TaskQueueBase* mQueue = nullptr;
+};
+KSYS_CHECK_SIZE_NX150(TaskQueueLock, 0x10);
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskThread.cpp b/src/KingSystem/Utils/Thread/TaskThread.cpp
new file mode 100644
index 00000000..ff2e0f91
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskThread.cpp
@@ -0,0 +1,261 @@
+#include "KingSystem/Utils/Thread/TaskThread.h"
+#include <thread/seadThread.h>
+#include "KingSystem/Utils/Thread/Task.h"
+#include "KingSystem/Utils/Thread/TaskQueue.h"
+#include "KingSystem/Utils/Thread/TaskQueueLock.h"
+
+namespace ksys::util {
+
+TaskThread::TaskThread(const sead::SafeString& name, sead::Heap* heap, s32 priority,
+ sead::MessageQueue::BlockType block_type,
+ sead::MessageQueue::Element quit_msg, s32 stack_size, s32 message_queue_size)
+ : Thread(name, heap, priority, block_type, quit_msg, stack_size, message_queue_size),
+ mPauseResumeEvent(heap), mTaskProcessedEvent(heap) {}
+
+TaskThread::~TaskThread() {
+ if (!mTaskQueue)
+ return;
+
+ mTaskQueue->removeThread(this);
+ if (mFlags.isOff(Flag::DoesNotOwnTaskQueue) && mTaskQueue) {
+ delete mTaskQueue;
+ mTaskQueue = nullptr;
+ }
+}
+
+bool TaskThread::init(const TaskThread::InitArg& arg) {
+ if (arg.queue) {
+ mTaskQueue = arg.queue;
+ mFlags.set(Flag::DoesNotOwnTaskQueue);
+ } else {
+ mTaskQueue = new (arg.heap) TaskQueue(arg.heap);
+ TaskQueueBase::InitArg queue_arg;
+ queue_arg.enable_locks = false;
+ queue_arg.task_selection_delegate = nullptr;
+ queue_arg.heap = arg.heap;
+ queue_arg.num_lanes = arg.num_lanes;
+ queue_arg.max_num_threads = 1;
+ mTaskQueue->init(queue_arg);
+ }
+
+ mTaskQueue->addThread(this);
+
+ mPauseResumeEvent.initialize(true);
+ mPauseResumeEvent.setSignal();
+
+ mTaskProcessedEvent.initialize(true);
+ mTaskProcessedEvent.setSignal();
+
+ mBatchSize = arg.batch_size;
+
+ return true;
+}
+
+s32 TaskThread::getNumActiveTasks() const {
+ return mTaskQueue->getNumActiveTasks();
+}
+
+void TaskThread::waitForQueueToEmpty() {
+ mTaskQueue->waitForQueueToEmpty();
+}
+
+void TaskThread::cancelTasks(u8 id) {
+ mTaskQueue->cancelTasks(id);
+}
+
+void TaskThread::clearQueue() {
+ mTaskQueue->clear();
+}
+
+void TaskThread::lock(TaskQueueLock* lock) {
+ mTaskQueue->lock(lock);
+}
+
+bool TaskThread::isActiveAndReceivedQueueUpdateMsg() const {
+ if (mFlags.isOn(Flag::Paused))
+ return false;
+ if (mFlags.isOn(Flag::IsActive))
+ return true;
+ return receivedQueueUpdatedMsg();
+}
+
+bool TaskThread::isPaused() const {
+ return mFlags.isOn(Flag::Paused);
+}
+
+bool TaskThread::receivedQueueUpdatedMsg() const {
+ return mMessageQueue.peek(sead::MessageQueue::BlockType::NonBlocking) == cMessage_QueueUpdated;
+}
+
+void TaskThread::pause() {
+ if (sead::ThreadMgr::instance()->getCurrentThread() == this)
+ return;
+
+ if (!mPauseResumeMsg.compareExchange(cMessage_Resume, cMessage_Pause))
+ return;
+
+ mPauseResumeEvent.wait();
+ mPauseResumeEvent.resetSignal();
+ mMessageQueue.jam(cMessage_Pause, sead::MessageQueue::BlockType::Blocking);
+}
+
+void TaskThread::pauseAndWaitForAck() {
+ if (sead::ThreadMgr::instance()->getCurrentThread() == this)
+ return;
+
+ pause();
+ mPauseResumeEvent.wait();
+}
+
+void TaskThread::resume() {
+ if (sead::ThreadMgr::instance()->getCurrentThread() == this)
+ return;
+
+ if (!mPauseResumeMsg.compareExchange(cMessage_Pause, cMessage_Resume))
+ return;
+
+ mPauseResumeEvent.wait();
+ mPauseResumeEvent.resetSignal();
+ mMessageQueue.jam(cMessage_Resume, sead::MessageQueue::BlockType::Blocking);
+}
+
+void TaskThread::resumeAndWaitForAck() {
+ if (sead::ThreadMgr::instance()->getCurrentThread() == this)
+ return;
+
+ resume();
+ mPauseResumeEvent.wait();
+}
+
+bool TaskThread::isBusyProcessingTask() const {
+ return mFlags.isOn(Flag::IsBusyProcessingTask);
+}
+
+bool TaskThread::isLookingForTask() const {
+ return mFlags.isOn(Flag::IsLookingForTask);
+}
+
+// NON_MATCHING: branching for `if (mTaskQueue->getNumActiveTasks() == 0)`:
+// Clang got rid of the branch and merged the two mFlags writes
+void TaskThread::calc_(sead::MessageQueue::Element msg) {
+ if (mFlags.isOn(Flag::Paused)) {
+ if (msg != cMessage_Resume)
+ return;
+ mFlags.reset(Flag::Paused);
+ mPauseResumeEvent.setSignal();
+ }
+
+ if (msg == cMessage_Pause) {
+ mPauseResumeEvent.setSignal();
+ mFlags.set(Flag::Paused);
+ return;
+ }
+
+ if (mBatchSize >= 1)
+ mNumRemainingTasksInBatch = mBatchSize;
+
+ while (true) {
+ {
+ TaskQueueLock lock{this};
+ const auto latest_msg = mMessageQueue.peek(sead::MessageQueue::BlockType::NonBlocking);
+
+ if (latest_msg == mQuitMsg) {
+ mFlags.reset(Flag::IsActive);
+ mFlags.reset(Flag::IsLookingForTask);
+ break;
+ }
+
+ if (latest_msg == cMessage_Pause) {
+ mFlags.reset(Flag::IsActive);
+ mFlags.reset(Flag::IsLookingForTask);
+ break;
+ }
+
+ mFlags.set(Flag::IsActive);
+
+ mFlags.set(Flag::IsLookingForTask);
+ mTaskQueue->fetchTask(&mTask);
+ mFlags.reset(Flag::IsLookingForTask);
+
+ if (mTask == nullptr) {
+ mFlags.reset(Flag::IsActive);
+ mFlags.reset(Flag::IsLookingForTask);
+ mTaskQueue->signalEmptyEventsIfNeeded();
+ break;
+ }
+
+ mFlags.reset(Flag::IsLookingForTask);
+
+ mFlags.set(Flag::IsBusyProcessingTask);
+ mTask->setThread(this);
+ }
+
+ mTask->run();
+
+ Task* task;
+ {
+ TaskQueueLock lock{this};
+ mTask->onRunFinished();
+ task = mTask;
+ mTask = nullptr;
+ }
+
+ if (task) {
+ TaskPostRunResult result;
+ task->invokePostRunCallback(&result);
+
+ TaskQueueLock lock{this};
+ if (!result.getResult())
+ task->finish();
+
+ mFlags.reset(Flag::IsBusyProcessingTask);
+ mTaskProcessedEvent.setSignal();
+ mTaskQueue->signalEmptyEventsIfNeeded();
+
+ if (mTaskQueue->getNumActiveTasks() == 0) {
+ mFlags.reset(Flag::IsActive);
+#ifdef MATCHING_HACK_NX_CLANG
+ // To make it easier to see what this function is functionally equivalent.
+ // Does not fix the matching issue, but turns it into a 2-line reordering.
+ asm("" ::: "memory");
+#endif
+ break;
+ }
+
+ mFlags.set(Flag::IsLookingForTask);
+ }
+
+ if (mBatchSize <= 0)
+ continue;
+
+ --mNumRemainingTasksInBatch;
+ if (mNumRemainingTasksInBatch == 0) {
+ sead::Thread::yield();
+ mNumRemainingTasksInBatch = mBatchSize;
+ }
+ }
+
+ mFlags.reset(Flag::IsActive);
+ mFlags.reset(Flag::IsLookingForTask);
+}
+
+bool TaskThread::receivedPauseMsg() const {
+ return mMessageQueue.peek(sead::MessageQueue::BlockType::NonBlocking) == cMessage_Pause;
+}
+
+bool TaskThread::receivedResumeMsg() const {
+ return mMessageQueue.peek(sead::MessageQueue::BlockType::NonBlocking) == cMessage_Resume;
+}
+
+bool TaskThread::receivedQuitMsg() const {
+ const auto msg = static_cast<s32>(mQuitMsg);
+ return mMessageQueue.peek(sead::MessageQueue::BlockType::NonBlocking) == msg;
+}
+
+void TaskThread::cancelCurrentTask() {
+ TaskQueueLock lock{this};
+ if (mTask)
+ mTask->cancel();
+}
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Thread/TaskThread.h b/src/KingSystem/Utils/Thread/TaskThread.h
new file mode 100644
index 00000000..8d2cb559
--- /dev/null
+++ b/src/KingSystem/Utils/Thread/TaskThread.h
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <prim/seadRuntimeTypeInfo.h>
+#include <prim/seadTypedBitFlag.h>
+#include <thread/seadAtomic.h>
+#include <thread/seadThread.h>
+#include "KingSystem/Utils/Thread/Event.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::util {
+
+class Task;
+class TaskQueueBase;
+class TaskQueueLock;
+
+class TaskThread : public sead::Thread {
+ SEAD_RTTI_BASE(TaskThread)
+public:
+ enum Message {
+ cMessage_QueueUpdated = 1,
+ cMessage_Pause = 2,
+ cMessage_Resume = 3,
+ };
+
+ struct InitArg {
+ /// Number of lanes if a new queue is to be created.
+ /// Only used if queue is nullptr.
+ u16 num_lanes;
+ /// Number of tasks to process in a row before yielding. Can be zero to disable the limit.
+ u32 batch_size;
+ /// Heap that will be used to allocate a new queue if necessary.
+ /// Only used if queue is nullptr.
+ sead::Heap* heap;
+ /// Task queue. If null, a new queue will be created and owned by this thread.
+ TaskQueueBase* queue;
+ };
+ KSYS_CHECK_SIZE_NX150(InitArg, 0x18);
+
+ TaskThread(const sead::SafeString& name, sead::Heap* heap, s32 priority,
+ sead::MessageQueue::BlockType block_type, sead::MessageQueue::Element quit_msg,
+ s32 stack_size, s32 message_queue_size);
+ ~TaskThread() override;
+
+ bool init(const InitArg& arg);
+
+ s32 getNumActiveTasks() const;
+
+ void waitForQueueToEmpty();
+ void cancelTasks(u8 id);
+ void clearQueue();
+ void lock(TaskQueueLock* lock);
+
+ bool isActiveAndReceivedQueueUpdateMsg() const;
+ bool isPaused() const;
+ bool receivedQueueUpdatedMsg() const;
+
+ void pause();
+ void pauseAndWaitForAck();
+ void resume();
+ void resumeAndWaitForAck();
+
+ bool isBusyProcessingTask() const;
+ bool isLookingForTask() const;
+
+ bool receivedPauseMsg() const;
+ bool receivedResumeMsg() const;
+ bool receivedQuitMsg() const;
+
+ void cancelCurrentTask();
+
+ TaskQueueBase* getTaskQueue() const { return mTaskQueue; }
+
+protected:
+ friend class TaskQueueBase;
+
+ enum class Flag {
+ _1 = 0x1,
+ _2 = 0x2,
+ _4 = 0x4,
+ Paused = 0x8,
+ /// The task queue is *not* owned by this TaskThread.
+ DoesNotOwnTaskQueue = 0x10,
+ /// A batch is being processed.
+ IsActive = 0x20,
+ /// This thread is looking for a task to process.
+ IsLookingForTask = 0x40,
+ /// A task is being processed.
+ IsBusyProcessingTask = 0x80,
+ };
+
+ void calc_(sead::MessageQueue::Element msg) override;
+
+ sead::TypedBitFlag<Flag, u8> mFlags = [] {
+ decltype(mFlags) flags;
+ flags.set(Flag::_2);
+ flags.set(Flag::_4);
+ return flags;
+ }();
+ Task* mTask = nullptr;
+ s32 mBatchSize = 0;
+ s32 mNumRemainingTasksInBatch = 0;
+ sead::Atomic<Message> mPauseResumeMsg = cMessage_Resume;
+ Event mPauseResumeEvent;
+ Event mTaskProcessedEvent;
+ TaskQueueBase* mTaskQueue = nullptr;
+};
+KSYS_CHECK_SIZE_NX150(TaskThread, 0x1a0);
+
+} // namespace ksys::util