BsCommandQueue.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. //__________________________ Banshee Project - A modern game development toolkit _________________________________//
  2. //_____________________________________ www.banshee-project.com __________________________________________________//
  3. //________________________ Copyright (c) 2014 Marko Pintera. All rights reserved. ________________________________//
  4. #pragma once
  5. #include "BsCorePrerequisites.h"
  6. #include "BsAsyncOp.h"
  7. #include <functional>
  8. namespace BansheeEngine
  9. {
  10. /**
  11. * @brief Command queue policy that provides no synchonization. Should be used
  12. * with command queues that are used on a single thread only.
  13. */
  14. class CommandQueueNoSync
  15. {
  16. public:
  17. CommandQueueNoSync() {}
  18. virtual ~CommandQueueNoSync() {}
  19. bool isValidThread(BS_THREAD_ID_TYPE ownerThread) const
  20. {
  21. return BS_THREAD_CURRENT_ID == ownerThread;
  22. }
  23. void lock() { };
  24. void unlock() { }
  25. };
  26. /**
  27. * @brief Command queue policy that provides synchonization. Should be used
  28. * with command queues that are used on multiple threads.
  29. */
  30. class CommandQueueSync
  31. {
  32. public:
  33. CommandQueueSync()
  34. :mLock(mCommandQueueMutex, BS_DEFER_LOCK)
  35. { }
  36. virtual ~CommandQueueSync() {}
  37. bool isValidThread(BS_THREAD_ID_TYPE ownerThread) const
  38. {
  39. return true;
  40. }
  41. void lock()
  42. {
  43. mLock.lock();
  44. };
  45. void unlock()
  46. {
  47. mLock.unlock();
  48. }
  49. private:
  50. BS_MUTEX(mCommandQueueMutex);
  51. BS_LOCK_TYPE mLock;
  52. };
  53. /**
  54. * @brief Represents a single queued command in the command list. Contains all the data for executing the command
  55. * and checking up on the command status.
  56. */
  57. struct QueuedCommand
  58. {
  59. #if BS_DEBUG_MODE
  60. QueuedCommand(std::function<void(AsyncOp&)> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  61. :callbackWithReturnValue(_callback), debugId(_debugId), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(bs_new<AsyncOp>()), ownsData(true)
  62. { }
  63. QueuedCommand(std::function<void()> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  64. :callback(_callback), debugId(_debugId), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(nullptr), ownsData(true)
  65. { }
  66. UINT32 debugId;
  67. #else
  68. QueuedCommand(std::function<void(AsyncOp&)> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  69. :callbackWithReturnValue(_callback), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(bs_new<AsyncOp>()), ownsData(true)
  70. { }
  71. QueuedCommand(std::function<void()> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  72. :callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(nullptr), ownsData(true)
  73. { }
  74. #endif
  75. ~QueuedCommand()
  76. {
  77. if(ownsData && asyncOp != nullptr)
  78. bs_delete(asyncOp);
  79. }
  80. QueuedCommand(const QueuedCommand& source)
  81. {
  82. ownsData = true;
  83. source.ownsData = false;
  84. callback = source.callback;
  85. callbackWithReturnValue = source.callbackWithReturnValue;
  86. asyncOp = source.asyncOp;
  87. returnsValue = source.returnsValue;
  88. callbackId = source.callbackId;
  89. notifyWhenComplete = source.notifyWhenComplete;
  90. }
  91. QueuedCommand& operator=(const QueuedCommand& rhs)
  92. {
  93. ownsData = true;
  94. rhs.ownsData = false;
  95. callback = rhs.callback;
  96. callbackWithReturnValue = rhs.callbackWithReturnValue;
  97. asyncOp = rhs.asyncOp;
  98. returnsValue = rhs.returnsValue;
  99. callbackId = rhs.callbackId;
  100. notifyWhenComplete = rhs.notifyWhenComplete;
  101. return *this;
  102. }
  103. std::function<void()> callback;
  104. std::function<void(AsyncOp&)> callbackWithReturnValue;
  105. AsyncOp* asyncOp;
  106. bool returnsValue;
  107. UINT32 callbackId;
  108. bool notifyWhenComplete;
  109. mutable bool ownsData;
  110. };
  111. /**
  112. * @brief Contains a list of commands you may queue for later execution on the core thread.
  113. */
  114. class BS_CORE_EXPORT CommandQueueBase
  115. {
  116. public:
  117. /**
  118. * @brief Constructor.
  119. *
  120. * @param threadId Identifier for the thread the command queue will be getting commands from.
  121. */
  122. CommandQueueBase(BS_THREAD_ID_TYPE threadId);
  123. virtual ~CommandQueueBase();
  124. /**
  125. * @brief Gets the thread identifier the command queue is used on.
  126. *
  127. * @note If the command queue is using a synchonized access policy generally this
  128. * is not relevant as it may be used on multiple threads.
  129. */
  130. BS_THREAD_ID_TYPE getThreadId() const { return mMyThreadId; }
  131. /**
  132. * @brief Executes all provided commands one by one in order. To get the commands you should call flush().
  133. *
  134. * @param notifyCallback Callback that will be called if a command that has "notifyOnComplete" flag set.
  135. * The callback will receive "callbackId" of the command.
  136. */
  137. void playbackWithNotify(Queue<QueuedCommand>* commands, std::function<void(UINT32)> notifyCallback);
  138. /**
  139. * @brief Executes all provided commands one by one in order. To get the commands you should call flush().
  140. */
  141. void playback(Queue<QueuedCommand>* commands);
  142. /**
  143. * @brief Allows you to set a breakpoint that will trigger when the specified command is executed.
  144. *
  145. * @note This is helpful when you receive an error on the executing thread and you cannot tell from where was
  146. * the command that caused the error queued from. However you can make a note of the queue and command index
  147. * and set a breakpoint so that it gets triggered next time you run the program. At that point you can know
  148. * exactly which part of code queued the command by examining the stack trace.
  149. *
  150. * @param queueIdx Zero-based index of the queue the command was queued on.
  151. * @param commandIdx Zero-based index of the command.
  152. */
  153. static void addBreakpoint(UINT32 queueIdx, UINT32 commandIdx);
  154. /**
  155. * @brief Queue up a new command to execute. Make sure the provided function has all of its
  156. * parameters properly bound. Last parameter must be unbound and of AsyncOp& type.
  157. * This is used to signal that the command is completed, and also for storing the return
  158. * value.
  159. *
  160. * @note Callback method also needs to call AsyncOp::markAsResolved once it is done
  161. * processing. (If it doesn't it will still be called automatically, but the return
  162. * value will default to nullptr)
  163. *
  164. * @param _notifyWhenComplete (optional) Call the notify method (provided in the call to CommandQueue::playback)
  165. * when the command is complete.
  166. * @param _callbackId (optional) Identifier for the callback so you can then later find it
  167. * if needed.
  168. *
  169. * @return Async operation object that you can continuously check until the command completes. After
  170. * it completes AsyncOp::isResolved will return true and return data will be valid (if
  171. * the callback provided any).
  172. */
  173. AsyncOp queueReturn(std::function<void(AsyncOp&)> commandCallback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0);
  174. /**
  175. * @brief Queue up a new command to execute. Make sure the provided function has all of its
  176. * parameters properly bound. Provided command is not expected to return a value. If you
  177. * wish to return a value from the callback use the queueReturn which accepts an AsyncOp parameter.
  178. *
  179. * @param _notifyWhenComplete (optional) Call the notify method (provided in the call to CommandQueue::playback)
  180. * when the command is complete.
  181. * @param _callbackId (optional) Identifier for the callback so you can then later find
  182. * it if needed.
  183. */
  184. void queue(std::function<void()> commandCallback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0);
  185. /**
  186. * @brief Returns a copy of all queued commands and makes room for new ones. Must be called from the thread
  187. * that created the command queue. Returned commands MUST be passed to "playback" method.
  188. */
  189. BansheeEngine::Queue<QueuedCommand>* flush();
  190. /**
  191. * @brief Cancels all currently queued commands.
  192. */
  193. void cancelAll();
  194. /**
  195. * @brief Returns true if no commands are queued.
  196. */
  197. bool isEmpty();
  198. protected:
  199. /**
  200. * @brief Helper method that throws an "Invalid thread" exception. Used primarily
  201. * so we can avoid including Exception include in this header.
  202. */
  203. void throwInvalidThreadException(const String& message) const;
  204. private:
  205. BansheeEngine::Queue<QueuedCommand>* mCommands;
  206. Stack<BansheeEngine::Queue<QueuedCommand>*> mEmptyCommandQueues; // List of empty queues for reuse
  207. BS_THREAD_ID_TYPE mMyThreadId;
  208. // Various variables that allow for easier debugging by allowing us to trigger breakpoints
  209. // when a certain command was queued.
  210. #if BS_DEBUG_MODE
  211. struct QueueBreakpoint
  212. {
  213. class HashFunction
  214. {
  215. public:
  216. size_t operator()(const QueueBreakpoint &key) const;
  217. };
  218. class EqualFunction
  219. {
  220. public:
  221. bool operator()(const QueueBreakpoint &a, const QueueBreakpoint &b) const;
  222. };
  223. QueueBreakpoint(UINT32 _queueIdx, UINT32 _commandIdx)
  224. :queueIdx(_queueIdx), commandIdx(_commandIdx)
  225. { }
  226. UINT32 queueIdx;
  227. UINT32 commandIdx;
  228. inline size_t operator()(const QueueBreakpoint& v) const;
  229. };
  230. UINT32 mMaxDebugIdx;
  231. UINT32 mCommandQueueIdx;
  232. static UINT32 MaxCommandQueueIdx;
  233. static UnorderedSet<QueueBreakpoint, QueueBreakpoint::HashFunction, QueueBreakpoint::EqualFunction> SetBreakpoints;
  234. BS_STATIC_MUTEX(CommandQueueBreakpointMutex);
  235. /**
  236. * @brief Checks if the specified command has a breakpoint and throw an assert if it does.
  237. */
  238. static void breakIfNeeded(UINT32 queueIdx, UINT32 commandIdx);
  239. #endif
  240. };
  241. /**
  242. * @copydoc CommandQueueBase
  243. *
  244. * @brief Use SyncPolicy to choose whether you want command queue be synchonized or not. Synchonized
  245. * command queues may be used across multiple threads and non-synchonized only on one.
  246. */
  247. template<class SyncPolicy = CommandQueueNoSync>
  248. class CommandQueue : public CommandQueueBase, public SyncPolicy
  249. {
  250. public:
  251. /**
  252. * @copydoc CommandQueueBase::CommandQueueBase
  253. */
  254. CommandQueue(BS_THREAD_ID_TYPE threadId)
  255. :CommandQueueBase(threadId)
  256. { }
  257. ~CommandQueue()
  258. {
  259. #if BS_DEBUG_MODE
  260. #if BS_THREAD_SUPPORT != 0
  261. if(!isValidThread(getThreadId()))
  262. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  263. #endif
  264. #endif
  265. }
  266. /**
  267. * @copydoc CommandQueueBase::queueReturn
  268. */
  269. AsyncOp queueReturn(std::function<void(AsyncOp&)> commandCallback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  270. {
  271. #if BS_DEBUG_MODE
  272. #if BS_THREAD_SUPPORT != 0
  273. if(!isValidThread(getThreadId()))
  274. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  275. #endif
  276. #endif
  277. lock();
  278. AsyncOp asyncOp = CommandQueueBase::queueReturn(commandCallback, _notifyWhenComplete, _callbackId);
  279. unlock();
  280. return asyncOp;
  281. }
  282. /**
  283. * @copydoc CommandQueueBase::queue
  284. */
  285. void queue(std::function<void()> commandCallback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
  286. {
  287. #if BS_DEBUG_MODE
  288. #if BS_THREAD_SUPPORT != 0
  289. if(!isValidThread(getThreadId()))
  290. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  291. #endif
  292. #endif
  293. lock();
  294. CommandQueueBase::queue(commandCallback, _notifyWhenComplete, _callbackId);
  295. unlock();
  296. }
  297. /**
  298. * @copydoc CommandQueueBase::flush
  299. */
  300. BansheeEngine::Queue<QueuedCommand>* flush()
  301. {
  302. #if BS_DEBUG_MODE
  303. #if BS_THREAD_SUPPORT != 0
  304. if(!isValidThread(getThreadId()))
  305. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  306. #endif
  307. #endif
  308. lock();
  309. BansheeEngine::Queue<QueuedCommand>* commands = CommandQueueBase::flush();
  310. unlock();
  311. return commands;
  312. }
  313. /**
  314. * @copydoc CommandQueueBase::cancelAll
  315. */
  316. void cancelAll()
  317. {
  318. #if BS_DEBUG_MODE
  319. #if BS_THREAD_SUPPORT != 0
  320. if(!isValidThread(getThreadId()))
  321. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  322. #endif
  323. #endif
  324. lock();
  325. CommandQueueBase::cancelAll();
  326. unlock();
  327. }
  328. /**
  329. * @copydoc CommandQueueBase::isEmpty
  330. */
  331. bool isEmpty()
  332. {
  333. #if BS_DEBUG_MODE
  334. #if BS_THREAD_SUPPORT != 0
  335. if(!isValidThread(getThreadId()))
  336. throwInvalidThreadException("Command queue accessed outside of its creation thread.");
  337. #endif
  338. #endif
  339. lock();
  340. bool empty = CommandQueueBase::isEmpty();
  341. unlock();
  342. return empty;
  343. }
  344. };
  345. }