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