mirror of
https://github.com/zeldaret/botw
synced 2026-07-28 07:08:23 -04:00
ksys: Add Task utilities
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user