Browse Source

Added the ability to add command breakpoints

Marko Pintera 12 years ago
parent
commit
1b9ed00123

+ 3 - 0
CamelotClient/CamelotClient.cpp

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

+ 65 - 2
CamelotCore/Include/CmCommandQueue.h

@@ -4,6 +4,7 @@
 #include "CmAsyncOp.h"
 #include "CmAsyncOp.h"
 #include "CmCommonEnums.h"
 #include "CmCommonEnums.h"
 #include "boost/function.hpp"
 #include "boost/function.hpp"
+#include <functional>
 
 
 namespace CamelotEngine
 namespace CamelotEngine
 {
 {
@@ -11,11 +12,22 @@ namespace CamelotEngine
 	 * @brief	Contains a list of commands that can be queued by one thread,
 	 * @brief	Contains a list of commands that can be queued by one thread,
 	 * 			and executed by another.
 	 * 			and executed by another.
 	 */
 	 */
-	class CommandQueue
+	class CM_EXPORT CommandQueue
 	{
 	{
 	public:
 	public:
 		struct Command
 		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)
 			Command(boost::function<void(AsyncOp&)> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
 				:callbackWithReturnValue(_callback), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
 				: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)
 			Command(boost::function<void()> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
 				:callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
 				:callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId)
 			{ }
 			{ }
+#endif
 
 
 			boost::function<void()> callback;
 			boost::function<void()> callback;
 			boost::function<void(AsyncOp&)> callbackWithReturnValue;
 			boost::function<void(AsyncOp&)> callbackWithReturnValue;
@@ -109,12 +122,62 @@ namespace CamelotEngine
 		 */
 		 */
 		bool isEmpty();
 		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:
 	private:
 		std::queue<Command>* mCommands;
 		std::queue<Command>* mCommands;
 
 
 		bool mAllowAllThreads;
 		bool mAllowAllThreads;
-
+		
 		CM_THREAD_ID_TYPE mMyThreadId;
 		CM_THREAD_ID_TYPE mMyThreadId;
 		CM_MUTEX(mCommandBufferMutex);
 		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 "CmException.h"
 #include "CmRenderSystem.h"
 #include "CmRenderSystem.h"
 #include "CmDebug.h"
 #include "CmDebug.h"
+#include "CmUtil.h"
 
 
 namespace CamelotEngine
 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)
 	CommandQueue::CommandQueue(CM_THREAD_ID_TYPE threadId, bool allowAllThreads)
 		:mMyThreadId(threadId), mAllowAllThreads(allowAllThreads)
 		:mMyThreadId(threadId), mAllowAllThreads(allowAllThreads)
 	{
 	{
 		mCommands = new std::queue<Command>();
 		mCommands = new std::queue<Command>();
 	}
 	}
+#endif
 
 
 	CommandQueue::~CommandQueue()
 	CommandQueue::~CommandQueue()
 	{
 	{
@@ -37,7 +52,14 @@ namespace CamelotEngine
 #endif
 #endif
 #endif
 #endif
 
 
+#if CM_DEBUG_MODE
+		breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx);
+
+		Command newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId);
+#else
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
+#endif
+
 		mCommands->push(newCommand);
 		mCommands->push(newCommand);
 
 
 #if CM_FORCE_SINGLETHREADED_RENDERING
 #if CM_FORCE_SINGLETHREADED_RENDERING
@@ -59,7 +81,14 @@ namespace CamelotEngine
 #endif
 #endif
 #endif
 #endif
 
 
+#if CM_DEBUG_MODE
+		breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx);
+
+		Command newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId);
+#else
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
 		Command newCommand(commandCallback, _notifyWhenComplete, _callbackId);
+#endif
+
 		mCommands->push(newCommand);
 		mCommands->push(newCommand);
 
 
 #if CM_FORCE_SINGLETHREADED_RENDERING
 #if CM_FORCE_SINGLETHREADED_RENDERING
@@ -138,4 +167,48 @@ namespace CamelotEngine
 
 
 		return true;
 		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
 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 HLSL9 and GLSL text materials & shaders
 Add resolution to GUI shader
 Add resolution to GUI shader
 Add 0.5 offset to GUI shader (depending on render system)
 Add 0.5 offset to GUI shader (depending on render system)