summaryrefslogtreecommitdiff
path: root/src/KingSystem/Utils/Thread/TaskQueueBase.h
blob: e90d4a24e2e4ca2b70cd4ee716aea0ef213a63e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#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*>;
template <typename T>
using TaskSelectionDelegateT = sead::Delegate1R<T, 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 : u8 {
        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> 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