瀏覽代碼

Added the ability to add command breakpoints

Marko Pintera 12 年之前
父節點
當前提交
1b9ed00123
共有 4 個文件被更改,包括 145 次插入2 次删除
  1. 3 0
      CamelotClient/CamelotClient.cpp
  2. 65 2
      CamelotCore/Include/CmCommandQueue.h
  3. 73 0
      CamelotCore/Source/CmCommandQueue.cpp
  4. 4 0
      TODO.txt

+ 3 - 0
CamelotClient/CamelotClient.cpp

@@ -20,6 +20,7 @@
 #include "CmGpuProgInclude.h" // DEBUG ONLY
 #include "CmGpuProgramImportOptions.h"
 #include "CmFontImportOptions.h"
+#include "CmCommandQueue.h"
 
 #include "CmDebugCamera.h"
 #include "CmTestTextSprite.h"
@@ -108,6 +109,8 @@ int CALLBACK WinMain(
 	gApplication().startUp("CamelotGLRenderSystem", "CamelotForwardRenderer");
 #endif
 
+	//CommandQueue::addBreakpoint(1, 11);
+
 	RenderSystem* renderSystem = RenderSystem::instancePtr();
 	RenderWindowPtr renderWindow = gApplication().getPrimaryRenderWindow();
 

+ 65 - 2
CamelotCore/Include/CmCommandQueue.h

@@ -4,6 +4,7 @@
 #include "CmAsyncOp.h"
 #include "CmCommonEnums.h"
 #include "boost/function.hpp"
+#include <functional>
 
 namespace CamelotEngine
 {
@@ -11,11 +12,22 @@ namespace CamelotEngine
 	 * @brief	Contains a list of commands that can be queued by one thread,
 	 * 			and executed by another.
 	 */
-	class CommandQueue
+	class CM_EXPORT CommandQueue
 	{
 	public:
 		struct Command
 		{
+#ifdef CM_DEBUG_MODE
+			Command(boost::function<void(AsyncOp&)> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
+				:callbackWithReturnValue(_callback), debugId(_debugId), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
+			{ }
+
+			Command(boost::function<void()> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
+				:callback(_callback), debugId(_debugId), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
+			{ }
+
+			UINT32 debugId;
+#else
 			Command(boost::function<void(AsyncOp&)> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
 				:callbackWithReturnValue(_callback), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
 			{ }
@@ -23,6 +35,7 @@ namespace CamelotEngine
 			Command(boost::function<void()> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
 				:callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
 			{ }
+#endif
 
 			boost::function<void()> callback;
 			boost::function<void(AsyncOp&)> callbackWithReturnValue;
@@ -109,12 +122,62 @@ namespace CamelotEngine
 		 */
 		bool isEmpty();
 
+		/**
+		 * @brief	Allows you to set a breakpoint that will trigger when the specified command is executed.
+		 * 			
+		 * @note	This is helpful when you receive an error on the executing thread and you cannot tell from where was
+		 * 			the command that caused the error queued from. However you can make a note of the queue and command index
+		 * 			and set a breakpoint so that it gets triggered next time you run the program. At that point you can know 
+		 * 			exactly which part of code queued the command by examining the stack trace.
+		 *
+		 * @param	queueIdx  	Zero-based index of the queue the command was queued on.
+		 * @param	commandIdx	Zero-based index of the command.
+		 */
+		static void addBreakpoint(UINT32 queueIdx, UINT32 commandIdx);
+
 	private:
 		std::queue<Command>* mCommands;
 
 		bool mAllowAllThreads;
-
+		
 		CM_THREAD_ID_TYPE mMyThreadId;
 		CM_MUTEX(mCommandBufferMutex);
+
+		// Various variables that allow for easier debugging by allowing us to trigger breakpoints
+		// when a certain command was queued.
+#if CM_DEBUG_MODE
+		struct QueueBreakpoint
+		{
+			class HashFunction
+			{
+			public:
+				size_t operator()(const QueueBreakpoint &key) const;
+			};
+
+			class EqualFunction
+			{
+			public:
+				bool operator()(const QueueBreakpoint &a, const QueueBreakpoint &b) const;
+			};
+
+			QueueBreakpoint(UINT32 _queueIdx, UINT32 _commandIdx)
+				:queueIdx(_queueIdx), commandIdx(_commandIdx)
+			{ }
+
+			UINT32 queueIdx;
+			UINT32 commandIdx;
+
+			inline size_t operator()(const QueueBreakpoint& v) const;
+		};
+
+		UINT32 mMaxDebugIdx;
+		UINT32 mCommandQueueIdx;
+
+		static UINT32 MaxCommandQueueIdx;
+		static std::unordered_set<QueueBreakpoint, QueueBreakpoint::HashFunction, QueueBreakpoint::EqualFunction> SetBreakpoints;
+		CM_STATIC_MUTEX(CommandQueueBreakpointMutex);
+
+		static void breakIfNeeded(UINT32 queueIdx, UINT32 commandIdx);
+#endif
 	};
 }

+ 73 - 0
CamelotCore/Source/CmCommandQueue.cpp

@@ -2,14 +2,29 @@
 #include "CmException.h"
 #include "CmRenderSystem.h"
 #include "CmDebug.h"
+#include "CmUtil.h"
 
 namespace CamelotEngine
 {
+#if CM_DEBUG_MODE
+	CommandQueue::CommandQueue(CM_THREAD_ID_TYPE threadId, bool allowAllThreads)
+		:mMyThreadId(threadId), mAllowAllThreads(allowAllThreads), mMaxDebugIdx(0)
+	{
+		mCommands = new std::queue<Command>();
+
+		{
+			CM_LOCK_MUTEX(CommandQueueBreakpointMutex);
+
+			mCommandQueueIdx = MaxCommandQueueIdx++;
+		}
+	}
+#else
 	CommandQueue::CommandQueue(CM_THREAD_ID_TYPE threadId, bool allowAllThreads)
 		:mMyThreadId(threadId), mAllowAllThreads(allowAllThreads)
 	{
 		mCommands = new std::queue<Command>();
 	}
+#endif
 
 	CommandQueue::~CommandQueue()
 	{
@@ -37,7 +52,14 @@ namespace CamelotEngine
 #endif
 #endif
 
+#if CM_DEBUG_MODE
+		breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx);
+
+		Command newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId);
+#else
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
+#endif
+
 		mCommands->push(newCommand);
 
 #if CM_FORCE_SINGLETHREADED_RENDERING
@@ -59,7 +81,14 @@ namespace CamelotEngine
 #endif
 #endif
 
+#if CM_DEBUG_MODE
+		breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx);
+
+		Command newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId);
+#else
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
+#endif
+
 		mCommands->push(newCommand);
 
 #if CM_FORCE_SINGLETHREADED_RENDERING
@@ -138,4 +167,48 @@ namespace CamelotEngine
 
 		return true;
 	}
+
+#if CM_DEBUG_MODE
+	CM_STATIC_MUTEX_CLASS_INSTANCE(CommandQueueBreakpointMutex, CommandQueue);
+	UINT32 CommandQueue::MaxCommandQueueIdx = 0;
+	std::unordered_set<CommandQueue::QueueBreakpoint, CommandQueue::QueueBreakpoint::HashFunction, 
+		CommandQueue::QueueBreakpoint::EqualFunction> CommandQueue::SetBreakpoints;
+
+	inline size_t CommandQueue::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 CommandQueue::QueueBreakpoint::EqualFunction::operator()(const QueueBreakpoint &a, const QueueBreakpoint &b) const
+	{
+		return a.queueIdx == b.queueIdx && a.commandIdx == b.commandIdx;
+	}
+
+	void CommandQueue::addBreakpoint(UINT32 queueIdx, UINT32 commandIdx)
+	{
+		CM_LOCK_MUTEX(CommandQueueBreakpointMutex);
+
+		SetBreakpoints.insert(QueueBreakpoint(queueIdx, commandIdx));
+	}
+
+	void CommandQueue::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 CommandQueue::addBreakpoint(UINT32 queueIdx, UINT32 commandIdx)
+	{
+		// Do nothing, no breakpoints in release
+	}
+#endif
 }

+ 4 - 0
TODO.txt

@@ -21,6 +21,10 @@ Figure out how to store texture references in a font?
 
 Move Debug to CamelotCore and add SetFillMode
 
+Test:
+Better font (Terminal?)
+Shader with scene blending
+Test word wrap, implement clipping
 Add HLSL9 and GLSL text materials & shaders
 Add resolution to GUI shader
 Add 0.5 offset to GUI shader (depending on render system)