#include "CmCommandQueue.h" #include "CmException.h" #include "CmCoreThread.h" #include "CmDebug.h" #include "CmUtil.h" namespace CamelotFramework { #if CM_DEBUG_MODE CommandQueueBase::CommandQueueBase(CM_THREAD_ID_TYPE threadId) :mMyThreadId(threadId), mMaxDebugIdx(0) { mCommands = cm_new::type, PoolAlloc>(); { CM_LOCK_MUTEX(CommandQueueBreakpointMutex); mCommandQueueIdx = MaxCommandQueueIdx++; } } #else CommandQueueBase::CommandQueueBase(CM_THREAD_ID_TYPE threadId) :mMyThreadId(threadId) { mCommands = cm_new::type, PoolAlloc>(); } #endif CommandQueueBase::~CommandQueueBase() { if(mCommands != nullptr) cm_delete(mCommands); while(!mEmptyCommandQueues.empty()) { cm_delete(mEmptyCommandQueues.top()); mEmptyCommandQueues.pop(); } } AsyncOp CommandQueueBase::queueReturn(boost::function commandCallback, bool _notifyWhenComplete, UINT32 _callbackId) { #if CM_DEBUG_MODE breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx); QueuedCommand newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId); #else QueuedCommand newCommand(commandCallback, _notifyWhenComplete, _callbackId); #endif mCommands->push(newCommand); #if CM_FORCE_SINGLETHREADED_RENDERING queue::type* commands = flush(); playback(commands); #endif return *newCommand.asyncOp; } void CommandQueueBase::queue(boost::function commandCallback, bool _notifyWhenComplete, UINT32 _callbackId) { #if CM_DEBUG_MODE breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx); QueuedCommand newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId); #else QueuedCommand newCommand(commandCallback, _notifyWhenComplete, _callbackId); #endif mCommands->push(newCommand); #if CM_FORCE_SINGLETHREADED_RENDERING queue::type* commands = flush(); playback(commands); #endif } CamelotFramework::Queue::type* CommandQueueBase::flush() { CamelotFramework::Queue::type* oldCommands = mCommands; if(!mEmptyCommandQueues.empty()) { mCommands = mEmptyCommandQueues.top(); mEmptyCommandQueues.pop(); } else { mCommands = cm_new::type, PoolAlloc>(); } return oldCommands; } void CommandQueueBase::playback(CamelotFramework::Queue::type* commands, boost::function notifyCallback) { THROW_IF_NOT_CORE_THREAD; if(commands == nullptr) return; while(!commands->empty()) { QueuedCommand& command = commands->front(); if(command.returnsValue) { command.callbackWithReturnValue(*command.asyncOp); if(!command.asyncOp->hasCompleted()) { LOGDBG("Async operation return value wasn't resolved properly. Resolving automatically to nullptr. " \ "Make sure to complete the operation before returning from the command callback method."); command.asyncOp->completeOperation(nullptr); } } else { command.callback(); } if(command.notifyWhenComplete && !notifyCallback.empty()) { notifyCallback(command.callbackId); } commands->pop(); } mEmptyCommandQueues.push(commands); } void CommandQueueBase::playback(CamelotFramework::Queue::type* commands) { playback(commands, boost::function()); } void CommandQueueBase::cancelAll() { CamelotFramework::Queue::type* commands = flush(); while(!commands->empty()) commands->pop(); mEmptyCommandQueues.push(commands); } bool CommandQueueBase::isEmpty() { if(mCommands != nullptr && mCommands->size() > 0) return false; return true; } void CommandQueueBase::throwInvalidThreadException(const String& message) const { CM_EXCEPT(InternalErrorException, message); } #if CM_DEBUG_MODE CM_STATIC_MUTEX_CLASS_INSTANCE(CommandQueueBreakpointMutex, CommandQueueBase); UINT32 CommandQueueBase::MaxCommandQueueIdx = 0; UnorderedSet::type CommandQueueBase::SetBreakpoints; inline size_t CommandQueueBase::QueueBreakpoint::HashFunction::operator()(const QueueBreakpoint& v) const { size_t seed = 0; hash_combine(seed, v.queueIdx); hash_combine(seed, v.commandIdx); return seed; } inline bool CommandQueueBase::QueueBreakpoint::EqualFunction::operator()(const QueueBreakpoint &a, const QueueBreakpoint &b) const { return a.queueIdx == b.queueIdx && a.commandIdx == b.commandIdx; } void CommandQueueBase::addBreakpoint(UINT32 queueIdx, UINT32 commandIdx) { CM_LOCK_MUTEX(CommandQueueBreakpointMutex); SetBreakpoints.insert(QueueBreakpoint(queueIdx, commandIdx)); } void CommandQueueBase::breakIfNeeded(UINT32 queueIdx, UINT32 commandIdx) { // I purposely don't use a mutex here, as this gets called very often. Generally breakpoints // will only be added at the start of the application, so race conditions should not occur. auto iterFind = SetBreakpoints.find(QueueBreakpoint(queueIdx, commandIdx)); if(iterFind != SetBreakpoints.end()) { assert(false && "Command queue breakpoint triggered!"); } } #else void CommandQueueBase::addBreakpoint(UINT32 queueIdx, UINT32 commandIdx) { // Do nothing, no breakpoints in release } #endif }