ksys: Add Task utilities

This commit is contained in:
Léo Lam
2020-09-10 22:11:27 +02:00
parent c5ef0a1fd3
commit 8b7369dffb
13 changed files with 1707 additions and 125 deletions
+277
View File
@@ -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
+193
View File
@@ -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
@@ -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
+21
View File
@@ -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
@@ -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
+157
View File
@@ -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
@@ -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
@@ -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
+261
View File
@@ -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
+109
View File
@@ -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