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
9namespace 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