1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #pragma once |
4 | |
5 | #include "Prerequisites/BsPrerequisitesUtil.h" |
6 | #include "Utility/BsModule.h" |
7 | #include "Threading/BsThreadPool.h" |
8 | |
9 | namespace bs |
10 | { |
11 | /** @addtogroup Threading |
12 | * @{ |
13 | */ |
14 | class TaskScheduler; |
15 | |
16 | /** Task priority. Tasks with higher priority will get executed sooner. */ |
17 | enum class TaskPriority |
18 | { |
19 | VeryLow = 98, |
20 | Low = 99, |
21 | Normal = 100, |
22 | High = 101, |
23 | VeryHigh = 102 |
24 | }; |
25 | |
26 | /** |
27 | * Represents a single task that may be queued in the TaskScheduler. |
28 | * |
29 | * @note Thread safe. |
30 | */ |
31 | class BS_UTILITY_EXPORT Task |
32 | { |
33 | struct PrivatelyConstruct {}; |
34 | |
35 | public: |
36 | Task(const PrivatelyConstruct& dummy, const String& name, std::function<void()> taskWorker, |
37 | TaskPriority priority, SPtr<Task> dependency); |
38 | |
39 | /** |
40 | * Creates a new task. Task should be provided to TaskScheduler in order for it to start. |
41 | * |
42 | * @param[in] name Name you can use to more easily identify the task. |
43 | * @param[in] taskWorker Worker method that does all of the work in the task. |
44 | * @param[in] priority (optional) Higher priority means the tasks will be executed sooner. |
45 | * @param[in] dependency (optional) Task dependency if one exists. If provided the task will |
46 | * not be executed until its dependency is complete. |
47 | */ |
48 | static SPtr<Task> create(const String& name, std::function<void()> taskWorker, |
49 | TaskPriority priority = TaskPriority::Normal, SPtr<Task> dependency = nullptr); |
50 | |
51 | /** Returns true if the task has completed. */ |
52 | bool isComplete() const; |
53 | |
54 | /** Returns true if the task has been canceled. */ |
55 | bool isCanceled() const; |
56 | |
57 | /** Returns true if the task has started or completed execution. */ |
58 | bool hasStarted() const; |
59 | |
60 | /** |
61 | * Blocks the current thread until the task has completed. |
62 | * |
63 | * @note While waiting adds a new worker thread, so that the blocking threads core can be utilized. |
64 | */ |
65 | void wait(); |
66 | |
67 | /** Cancels the task and removes it from the TaskSchedulers queue. */ |
68 | void cancel(); |
69 | |
70 | private: |
71 | friend class TaskScheduler; |
72 | |
73 | String mName; |
74 | TaskPriority mPriority; |
75 | UINT32 mTaskId = 0; |
76 | std::function<void()> mTaskWorker; |
77 | SPtr<Task> mTaskDependency; |
78 | std::atomic<UINT32> mState{0}; /**< 0 - Inactive, 1 - In progress, 2 - Completed, 3 - Canceled */ |
79 | |
80 | TaskScheduler* mParent = nullptr; |
81 | }; |
82 | |
83 | /** |
84 | * Represents a group of tasks that may be queued in the TaskScheduler to be processed in parallel. |
85 | * |
86 | * @note Thread safe. |
87 | */ |
88 | class BS_UTILITY_EXPORT TaskGroup |
89 | { |
90 | struct PrivatelyConstruct {}; |
91 | |
92 | public: |
93 | TaskGroup(const PrivatelyConstruct& dummy, String name, std::function<void(UINT32)> taskWorker, UINT32 count, |
94 | TaskPriority priority, SPtr<Task> dependency); |
95 | |
96 | /** |
97 | * Creates a new task group. Task group should be provided to TaskScheduler in order for it to start. |
98 | * |
99 | * @param[in] name Name you can use to more easily identify the tasks in the group. |
100 | * @param[in] taskWorker Worker method that will get called for each item in the group. Each call will receive |
101 | * a sequential index of the item in the group. |
102 | * @param[in] count Number of items in the task group. Each item will be processed in a worker thread. |
103 | * @param[in] priority (optional) Higher priority means the tasks will be executed sooner. |
104 | * @param[in] dependency (optional) Task dependency if one exists. If provided the task will |
105 | * not be executed until its dependency is complete. |
106 | */ |
107 | static SPtr<TaskGroup> create(String name, std::function<void(UINT32)> taskWorker, UINT32 count, |
108 | TaskPriority priority = TaskPriority::Normal, SPtr<Task> dependency = nullptr); |
109 | |
110 | /** Returns true if all the tasks in the group have completed. */ |
111 | bool isComplete() const; |
112 | |
113 | /** |
114 | * Blocks the current thread until all tasks in the group have completed. |
115 | * |
116 | * @note While waiting adds a new worker thread, so that the blocking threads core can be utilized. |
117 | */ |
118 | void wait(); |
119 | |
120 | private: |
121 | friend class TaskScheduler; |
122 | |
123 | String mName; |
124 | UINT32 mCount; |
125 | TaskPriority mPriority; |
126 | std::function<void(UINT32)> mTaskWorker; |
127 | SPtr<Task> mTaskDependency; |
128 | std::atomic<UINT32> mNumRemainingTasks{mCount}; |
129 | |
130 | TaskScheduler* mParent = nullptr; |
131 | }; |
132 | |
133 | /** |
134 | * Represents a task scheduler running on multiple threads. You may queue tasks on it from any thread and they will be |
135 | * executed in user specified order on any available thread. |
136 | * |
137 | * @note |
138 | * Thread safe. |
139 | * @note |
140 | * This type of task scheduler uses a global queue and is best used for coarse granularity of tasks. (Number of tasks |
141 | * in the order of hundreds. Higher number of tasks might require different queuing and locking mechanism, potentially |
142 | * at the cost of flexibility.) |
143 | * @note |
144 | * By default the task scheduler will create as many threads as there are physical CPU cores. You may add or remove |
145 | * threads using addWorker()/removeWorker() methods. |
146 | */ |
147 | class BS_UTILITY_EXPORT TaskScheduler : public Module<TaskScheduler> |
148 | { |
149 | public: |
150 | TaskScheduler(); |
151 | ~TaskScheduler(); |
152 | |
153 | /** Queues a new task. */ |
154 | void addTask(SPtr<Task> task); |
155 | |
156 | /** Queues a new task group. */ |
157 | void addTaskGroup(const SPtr<TaskGroup>& taskGroup); |
158 | |
159 | /** Adds a new worker thread which will be used for executing queued tasks. */ |
160 | void addWorker(); |
161 | |
162 | /** Removes a worker thread (as soon as its current task is finished). */ |
163 | void removeWorker(); |
164 | |
165 | /** Returns the maximum available worker threads (maximum number of tasks that can be executed simultaneously). */ |
166 | UINT32 getNumWorkers() const { return mMaxActiveTasks; } |
167 | protected: |
168 | friend class Task; |
169 | friend class TaskGroup; |
170 | |
171 | /** Main task scheduler method that dispatches tasks to other threads. */ |
172 | void runMain(); |
173 | |
174 | /** Worker method that runs a single task. */ |
175 | void runTask(SPtr<Task> task); |
176 | |
177 | /** Blocks the calling thread until the specified task has completed. */ |
178 | void waitUntilComplete(const Task* task); |
179 | |
180 | /** Blocks the calling thread until all the tasks in the provided task group have completed. */ |
181 | void waitUntilComplete(const TaskGroup* taskGroup); |
182 | |
183 | /** Method used for sorting tasks. */ |
184 | static bool taskCompare(const SPtr<Task>& lhs, const SPtr<Task>& rhs); |
185 | |
186 | HThread mTaskSchedulerThread; |
187 | Set<SPtr<Task>, std::function<bool(const SPtr<Task>&, const SPtr<Task>&)>> mTaskQueue; |
188 | Vector<SPtr<Task>> mActiveTasks; |
189 | UINT32 mMaxActiveTasks = 0; |
190 | UINT32 mNextTaskId = 0; |
191 | bool mShutdown = false; |
192 | bool mCheckTasks = false; |
193 | |
194 | Mutex mReadyMutex; |
195 | Mutex mCompleteMutex; |
196 | Signal mTaskReadyCond; |
197 | Signal mTaskCompleteCond; |
198 | }; |
199 | |
200 | /** @} */ |
201 | } |
202 | |