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 "BsCorePrerequisites.h" |
6 | #include "Utility/BsModule.h" |
7 | #include "CoreThread/BsCommandQueue.h" |
8 | #include "Threading/BsThreadPool.h" |
9 | |
10 | namespace bs |
11 | { |
12 | /** @addtogroup CoreThread-Internal |
13 | * @{ |
14 | */ |
15 | |
16 | /** Flags that control how is a command submitted to the command queue. */ |
17 | enum CoreThreadQueueFlag |
18 | { |
19 | /** |
20 | * Default flag, meaning the commands will be added to the per-thread queue and only begin executing after |
21 | * submit() has been called. |
22 | */ |
23 | CTQF_Default = 0, |
24 | /** |
25 | * Specifies that the queued command should be executed on the internal queue. Internal queue doesn't require |
26 | * a separate CoreThread::submit() call, and the queued command is instead immediately visible to the core thread. |
27 | * The downside is that the queue requires additional synchronization and is slower than the normal queue. |
28 | */ |
29 | CTQF_InternalQueue = 1 << 0, |
30 | /** |
31 | * If true, the method will block until the command finishes executing on the core thread. Only relevant for the |
32 | * internal queue commands since contents of the normal queue won't be submitted to the core thread until the |
33 | * CoreThread::submit() call. |
34 | */ |
35 | CTQF_BlockUntilComplete = 1 << 1 |
36 | }; |
37 | |
38 | typedef Flags<CoreThreadQueueFlag> CoreThreadQueueFlags; |
39 | BS_FLAGS_OPERATORS(CoreThreadQueueFlag) |
40 | |
41 | /** |
42 | * Manager for the core thread. Takes care of starting, running, queuing commands and shutting down the core thread. |
43 | * |
44 | * How threading works: |
45 | * - Commands from various threads can be queued for execution on the core thread by calling queueCommand() or |
46 | * queueReturnCommand(). |
47 | * - Internally each thread maintains its own separate queue of commands, so you cannot interleave commands from |
48 | * different threads. |
49 | * - There is also the internal command queue, which is the only queue directly visible from the core thread. |
50 | * - Core thread continually polls the internal command queue for new commands, and executes them in order they were |
51 | * submitted. |
52 | * - Commands queued on the per-thread queues are submitted to the internal command queue by calling submit(), at |
53 | * which point they are made visible to the core thread, and will begin executing. |
54 | * - Commands can also be submitted directly to the internal command queue (via a special flag), but with a |
55 | * performance cost due to extra synchronization required. |
56 | */ |
57 | class BS_CORE_EXPORT CoreThread : public Module<CoreThread> |
58 | { |
59 | /** Contains data about an queue for a specific thread. */ |
60 | struct ThreadQueueContainer |
61 | { |
62 | SPtr<CommandQueue<CommandQueueSync>> queue; |
63 | bool isMain; |
64 | }; |
65 | |
66 | /** Wrapper for the thread-local variable because MSVC can't deal with a thread-local variable marked with dllimport or dllexport, |
67 | * and we cannot use per-member dllimport/dllexport specifiers because Module's members will then not be exported and its static |
68 | * members will not have external linkage. */ |
69 | struct QueueData |
70 | { |
71 | static BS_THREADLOCAL ThreadQueueContainer* current; |
72 | }; |
73 | |
74 | public: |
75 | CoreThread(); |
76 | ~CoreThread(); |
77 | |
78 | /** Returns the id of the core thread. */ |
79 | ThreadId getCoreThreadId() { return mCoreThreadId; } |
80 | |
81 | /** Submits the commands from all queues and starts executing them on the core thread. */ |
82 | void submitAll(bool blockUntilComplete = false); |
83 | |
84 | /** Submits the commands from the current thread's queue and starts executing them on the core thread. */ |
85 | void submit(bool blockUntilComplete = false); |
86 | |
87 | /** |
88 | * Queues a new command that will be added to the command queue. Command returns a value. |
89 | * |
90 | * @param[in] commandCallback Command to queue. |
91 | * @param[in] flags Flags that further control command submission. |
92 | * @return Structure that can be used to check if the command completed execution, |
93 | * and to retrieve the return value once it has. |
94 | * |
95 | * @see CommandQueue::queueReturn() |
96 | * @note Thread safe |
97 | */ |
98 | AsyncOp queueReturnCommand(std::function<void(AsyncOp&)> commandCallback, CoreThreadQueueFlags flags = CTQF_Default); |
99 | |
100 | /** |
101 | * Queues a new command that will be added to the global command queue. |
102 | * |
103 | * @param[in] commandCallback Command to queue. |
104 | * @param[in] flags Flags that further control command submission. |
105 | * |
106 | * @see CommandQueue::queue() |
107 | * @note Thread safe |
108 | */ |
109 | void queueCommand(std::function<void()> commandCallback, CoreThreadQueueFlags flags = CTQF_Default); |
110 | |
111 | /** |
112 | * Called once every frame. |
113 | * |
114 | * @note Must be called before sim thread schedules any core thread operations for the frame. |
115 | */ |
116 | void update(); |
117 | |
118 | /** |
119 | * Returns a frame allocator that should be used for allocating temporary data being passed to the core thread. As the |
120 | * name implies the data only lasts one frame, so you need to be careful not to use it for longer than that. |
121 | * |
122 | * @note Sim thread only. |
123 | */ |
124 | FrameAlloc* getFrameAlloc() const; |
125 | |
126 | /** |
127 | * Returns number of buffers needed to sync data between core and sim thread. Currently the sim thread can be one frame |
128 | * ahead of the core thread, meaning we need two buffers. If this situation changes increase this number. |
129 | * |
130 | * For example: |
131 | * - Sim thread frame starts, it writes some data to buffer 0. |
132 | * - Core thread frame starts, it reads some data from buffer 0. |
133 | * - Sim thread frame finishes |
134 | * - New sim thread frame starts, it writes some data to buffer 1. |
135 | * - Core thread still working, reading from buffer 0. (If we were using just one buffer at this point core thread |
136 | * would be reading wrong data). |
137 | * - Sim thread waiting for core thread (application defined that it cannot go ahead more than one frame) |
138 | * - Core thread frame finishes. |
139 | * - New core thread frame starts, it reads some data from buffer 1. |
140 | * - ... |
141 | */ |
142 | static const int NUM_SYNC_BUFFERS = 2; |
143 | private: |
144 | /** |
145 | * Double buffered frame allocators. Means sim thread cannot be more than 1 frame ahead of core thread (If that changes |
146 | * you should be able to easily add more). |
147 | */ |
148 | FrameAlloc* mFrameAllocs[NUM_SYNC_BUFFERS]; |
149 | UINT32 mActiveFrameAlloc; |
150 | |
151 | static QueueData mPerThreadQueue; |
152 | Vector<ThreadQueueContainer*> mAllQueues; |
153 | |
154 | volatile bool mCoreThreadShutdown; |
155 | |
156 | HThread mCoreThread; |
157 | bool mCoreThreadStarted; |
158 | ThreadId mSimThreadId; |
159 | ThreadId mCoreThreadId; |
160 | Mutex mCommandQueueMutex; |
161 | Mutex mSubmitMutex; |
162 | Signal mCommandReadyCondition; |
163 | Mutex mCommandNotifyMutex; |
164 | Signal mCommandCompleteCondition; |
165 | Mutex mThreadStartedMutex; |
166 | Signal mCoreThreadStartedCondition; |
167 | |
168 | CommandQueue<CommandQueueSync>* mCommandQueue; |
169 | |
170 | UINT32 mMaxCommandNotifyId; /**< ID that will be assigned to the next command with a notifier callback. */ |
171 | Vector<UINT32> mCommandsCompleted; /**< Completed commands that have notifier callbacks set up */ |
172 | |
173 | /** Starts the core thread worker method. Should only be called once. */ |
174 | void initCoreThread(); |
175 | |
176 | /** Main worker method of the core thread. Called once thread is started. */ |
177 | void runCoreThread(); |
178 | |
179 | /** Shutdowns the core thread. It will complete all ready commands before shutdown. */ |
180 | void shutdownCoreThread(); |
181 | |
182 | /** Creates or retrieves a queue for the calling thread. */ |
183 | SPtr<CommandQueue<CommandQueueSync>> getQueue(); |
184 | |
185 | /** |
186 | * Submits all the commands from the provided command queue to the internal command queue. Optionally blocks the |
187 | * calling thread until all the submitted commands have done executing. |
188 | */ |
189 | void submitCommandQueue(CommandQueue<CommandQueueSync>& queue, bool blockUntilComplete); |
190 | |
191 | /** |
192 | * Blocks the calling thread until the command with the specified ID completes. Make sure that the specified ID |
193 | * actually exists, otherwise this will block forever. |
194 | */ |
195 | void blockUntilCommandCompleted(UINT32 commandId); |
196 | |
197 | /** |
198 | * Callback called by the command list when a specific command finishes executing. This is only called on commands that |
199 | * have a special notify on complete flag set. |
200 | * |
201 | * @param[in] commandId Identifier for the command. |
202 | */ |
203 | void commandCompletedNotify(UINT32 commandId); |
204 | }; |
205 | |
206 | /** |
207 | * Returns the core thread manager used for dealing with the core thread from external threads. |
208 | * |
209 | * @see CoreThread |
210 | */ |
211 | BS_CORE_EXPORT CoreThread& gCoreThread(); |
212 | |
213 | /** Throws an exception if current thread isn't the core thread. */ |
214 | BS_CORE_EXPORT void throwIfNotCoreThread(); |
215 | |
216 | /** Throws an exception if current thread is the core thread. */ |
217 | BS_CORE_EXPORT void throwIfCoreThread(); |
218 | |
219 | #if BS_DEBUG_MODE |
220 | #define THROW_IF_NOT_CORE_THREAD throwIfNotCoreThread(); |
221 | #define THROW_IF_CORE_THREAD throwIfCoreThread(); |
222 | #else |
223 | #define THROW_IF_NOT_CORE_THREAD |
224 | #define THROW_IF_CORE_THREAD |
225 | #endif |
226 | |
227 | /** @} */ |
228 | |
229 | /** @addtogroup CoreThread |
230 | * @{ |
231 | */ |
232 | |
233 | /** @} */ |
234 | } |
235 | |
236 | |