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