Browse Source

Added BlockUntilComplete to AsyncOp

Marko Pintera 11 years ago
parent
commit
d7d2e26f5e

+ 13 - 19
BansheeCore/Include/BsCommandQueue.h

@@ -64,36 +64,34 @@ namespace BansheeEngine
 	struct QueuedCommand
 	{
 #if BS_DEBUG_MODE
-		QueuedCommand(std::function<void(AsyncOp&)> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
-			:callbackWithReturnValue(_callback), debugId(_debugId), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(bs_new<AsyncOp>()), ownsData(true)
+		QueuedCommand(std::function<void(AsyncOp&)> _callback, UINT32 _debugId, const AsyncOpSyncDataPtr& asyncOpSyncData,
+			bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
+			:callbackWithReturnValue(_callback), debugId(_debugId), returnsValue(true), 
+			notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(asyncOpSyncData)
 		{ }
 
 		QueuedCommand(std::function<void()> _callback, UINT32 _debugId, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
-			:callback(_callback), debugId(_debugId), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(nullptr), ownsData(true)
+			:callback(_callback), debugId(_debugId), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(AsyncOpEmpty())
 		{ }
 
 		UINT32 debugId;
 #else
-		QueuedCommand(std::function<void(AsyncOp&)> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
-			:callbackWithReturnValue(_callback), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(bs_new<AsyncOp>()), ownsData(true)
+		QueuedCommand(std::function<void(AsyncOp&)> _callback, const AsyncOpSyncDataPtr& asyncOpSyncData, 
+			bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
+			:callbackWithReturnValue(_callback), returnsValue(true), notifyWhenComplete(_notifyWhenComplete), 
+			callbackId(_callbackId), asyncOp(asyncOpSyncData)
 		{ }
 
 		QueuedCommand(std::function<void()> _callback, bool _notifyWhenComplete = false, UINT32 _callbackId = 0)
-			:callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(nullptr), ownsData(true)
+			:callback(_callback), returnsValue(false), notifyWhenComplete(_notifyWhenComplete), callbackId(_callbackId), asyncOp(AsyncOpEmpty())
 		{ }
 #endif
 
 		~QueuedCommand()
-		{
-			if(ownsData && asyncOp != nullptr)
-				bs_delete(asyncOp);
-		}
+		{ }
 
 		QueuedCommand(const QueuedCommand& source)
 		{
-			ownsData = true;
-			source.ownsData = false;
-
 			callback = source.callback;
 			callbackWithReturnValue = source.callbackWithReturnValue;
 			asyncOp = source.asyncOp;
@@ -104,9 +102,6 @@ namespace BansheeEngine
 
 		QueuedCommand& operator=(const QueuedCommand& rhs)
 		{
-			ownsData = true;
-			rhs.ownsData = false;
-
 			callback = rhs.callback;
 			callbackWithReturnValue = rhs.callbackWithReturnValue;
 			asyncOp = rhs.asyncOp;
@@ -119,12 +114,10 @@ namespace BansheeEngine
 
 		std::function<void()> callback;
 		std::function<void(AsyncOp&)> callbackWithReturnValue;
-		AsyncOp* asyncOp;
+		AsyncOp asyncOp;
 		bool returnsValue;
 		UINT32 callbackId;
 		bool notifyWhenComplete;
-
-		mutable bool ownsData;
 	};
 
 	/**
@@ -266,6 +259,7 @@ namespace BansheeEngine
 
 		UINT32 mMaxDebugIdx;
 		UINT32 mCommandQueueIdx;
+		AsyncOpSyncDataPtr mAsyncOpSyncData;
 
 		static UINT32 MaxCommandQueueIdx;
 		static UnorderedSet<QueueBreakpoint, QueueBreakpoint::HashFunction, QueueBreakpoint::EqualFunction> SetBreakpoints;

+ 7 - 6
BansheeCore/Source/BsCommandQueue.cpp

@@ -10,6 +10,7 @@ namespace BansheeEngine
 	CommandQueueBase::CommandQueueBase(BS_THREAD_ID_TYPE threadId)
 		:mMyThreadId(threadId), mMaxDebugIdx(0)
 	{
+		mAsyncOpSyncData = bs_shared_ptr<AsyncOpSyncData>();
 		mCommands = bs_new<BansheeEngine::Queue<QueuedCommand>, PoolAlloc>();
 
 		{
@@ -43,9 +44,9 @@ namespace BansheeEngine
 #if BS_DEBUG_MODE
 		breakIfNeeded(mCommandQueueIdx, mMaxDebugIdx);
 
-		QueuedCommand newCommand(commandCallback, mMaxDebugIdx++, _notifyWhenComplete, _callbackId);
+		QueuedCommand newCommand(commandCallback, mMaxDebugIdx++, mAsyncOpSyncData, _notifyWhenComplete, _callbackId);
 #else
-		QueuedCommand newCommand(commandCallback, _notifyWhenComplete, _callbackId);
+		QueuedCommand newCommand(commandCallback, mAsyncOpSyncData, _notifyWhenComplete, _callbackId);
 #endif
 
 		mCommands->push(newCommand);
@@ -55,7 +56,7 @@ namespace BansheeEngine
 		playback(commands);
 #endif
 
-		return *newCommand.asyncOp;
+		return newCommand.asyncOp;
 	}
 
 	void CommandQueueBase::queue(std::function<void()> commandCallback, bool _notifyWhenComplete, UINT32 _callbackId)
@@ -106,14 +107,14 @@ namespace BansheeEngine
 
 			if(command.returnsValue)
 			{
-				AsyncOp& op = *command.asyncOp;
+				AsyncOp& op = command.asyncOp;
 				command.callbackWithReturnValue(op);
 
-				if(!command.asyncOp->hasCompleted())
+				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);
+					command.asyncOp._completeOperation(nullptr);
 				}
 			}
 			else

+ 1 - 2
BansheeEngine.sln

@@ -5,8 +5,7 @@ VisualStudioVersion = 12.0.30723.0
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Notes", "_Notes", "{1D081E5A-615A-4C06-B2DF-0D8D9390DE02}"
 	ProjectSection(SolutionItems) = preProject
-		Notes.txt = Notes.txt
-		SceneView.txt = SceneView.txt
+		TODO.txt = TODO.txt
 	EndProjectSection
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BansheeEngine", "BansheeEngine\BansheeEngine.vcxproj", "{07B0C186-5173-46F2-BE26-7E4148BD0CCA}"

+ 48 - 11
BansheeUtility/Include/BsAsyncOp.h

@@ -6,6 +6,23 @@
 
 namespace BansheeEngine
 {
+	/**
+	 * @brief	Thread synchronization primitives used by AsyncOps and their
+	 *			callers.
+	 */
+	class BS_UTILITY_EXPORT AsyncOpSyncData
+	{
+	public:
+		BS_MUTEX(mMutex)
+		BS_THREAD_SYNCHRONISER(mCondition)
+	};
+
+	/**
+	 * @brief	Flag used for creating async operations signaling that we want to create an empty
+	 *			AsyncOp with no internal memory storage.
+	 */
+	struct BS_UTILITY_EXPORT AsyncOpEmpty {};
+
 	/**
 	 * @brief	Object you may use to check on the results of an asynchronous operation. 
 	 *			Contains uninitialized data until "hasCompleted" returns true. 
@@ -27,35 +44,54 @@ namespace BansheeEngine
 			{ }
 
 			Any mReturnValue;
-			volatile bool mIsCompleted;
+			volatile std::atomic<bool> mIsCompleted;
 		};
 
 	public:
 		AsyncOp()
 			:mData(bs_shared_ptr<AsyncOpData, ScratchAlloc>())
-		{
-#if BS_ARCH_TYPE != BS_ARCHITECTURE_x86_32 && BS_ARCH_TYPE != BS_ARCHITECTURE_x86_64
-			// On some architectures it is not guaranteed that stores are executed in order, which means
-			// return value could be stored after mIsCompleted is set
-			static_assert(false, "You will likely need to add a memory barrier for mIsCompleted on architectures other than x86.");
-#endif
-		}
+		{ }
+
+		AsyncOp(AsyncOpEmpty empty)
+		{ }
+
+		AsyncOp(const AsyncOpSyncDataPtr& syncData)
+			:mData(bs_shared_ptr<AsyncOpData, ScratchAlloc>()), mSyncData(syncData)
+		{ }
+
+		AsyncOp(AsyncOpEmpty empty, const AsyncOpSyncDataPtr& syncData)
+			:mSyncData(syncData)
+		{ }
 
 		/**
 		 * @brief	True if the async operation has completed.
+		 * 
+		 * @note	Internal method.
 		 */
-		bool hasCompleted() const { return mData->mIsCompleted; }
+		bool hasCompleted() const;
 
 		/**
-		 * @brief	Internal method. Mark the async operation as completed.
+		 * @brief	Mark the async operation as completed.
+		 *
+		 * @note	Internal method.
 		 */
 		void _completeOperation(Any returnValue);
 
 		/**
-		 * @brief	Internal method. Mark the async operation as completed, without setting a return value.
+		 * @brief	Mark the async operation as completed, without setting a return value.
+		 *
+		 * @note	Internal method.
 		 */
 		void _completeOperation();
 
+		/**
+		 * @brief	Blocks the caller thread until the AsyncOp completes.
+		 *
+		 * @note	Do not call this on the thread that is completing the async op, as it
+		 *			will cause a deadlock.
+		 */
+		void blockUntilComplete() const;
+
 		/**
 		 * @brief	Retrieves the value returned by the async operation. Only valid
 		 *			if "hasCompleted" returns true.
@@ -74,5 +110,6 @@ namespace BansheeEngine
 
 	private:
 		std::shared_ptr<AsyncOpData> mData;
+		AsyncOpSyncDataPtr mSyncData;
 	};
 }

+ 2 - 0
BansheeUtility/Include/BsFwdDeclUtil.h

@@ -55,6 +55,7 @@ namespace BansheeEngine
 	class HThread;
 	class TestSuite;
 	class TestOutput;
+	class AsyncOpSyncData;
 	// Reflection
 	class IReflectable;
 	class RTTITypeBase;
@@ -72,6 +73,7 @@ namespace BansheeEngine
 	typedef std::shared_ptr<MemoryDataStream> MemoryDataStreamPtr;
 	typedef std::shared_ptr<Task> TaskPtr;
 	typedef std::shared_ptr<TestSuite> TestSuitePtr;
+	typedef std::shared_ptr<AsyncOpSyncData> AsyncOpSyncDataPtr;
 
 	typedef List<DataStreamPtr> DataStreamList;
 	typedef std::shared_ptr<DataStreamList> DataStreamListPtr;

+ 27 - 2
BansheeUtility/Source/BsAsyncOp.cpp

@@ -1,15 +1,40 @@
 #include "BsAsyncOp.h"
+#include "BsDebug.h"
 
 namespace BansheeEngine
 {
+	bool AsyncOp::hasCompleted() const 
+	{ 
+		return mData->mIsCompleted.load(std::memory_order_acquire);
+	}
+
 	void AsyncOp::_completeOperation(Any returnValue) 
 	{ 
 		mData->mReturnValue = returnValue; 
-		mData->mIsCompleted = true; 
+		mData->mIsCompleted.store(true, std::memory_order_release);
+
+		if (mSyncData != nullptr)
+			BS_THREAD_NOTIFY_ALL(mSyncData->mCondition);
 	}
 
 	void AsyncOp::_completeOperation() 
 	{ 
-		mData->mIsCompleted = true; 
+		mData->mIsCompleted.store(true, std::memory_order_release);
+
+		if (mSyncData != nullptr)
+			BS_THREAD_NOTIFY_ALL(mSyncData->mCondition);
+	}
+
+	void AsyncOp::blockUntilComplete() const
+	{
+		if (mSyncData == nullptr)
+		{
+			LOGERR("No sync data is available. Cannot block until AsyncOp is complete.");
+			return;
+		}
+
+		BS_LOCK_MUTEX_NAMED(mSyncData->mMutex, lock);
+		while (!hasCompleted())
+			BS_THREAD_WAIT(mSyncData->mCondition, mSyncData->mMutex, lock);
 	}
 }

+ 48 - 0
MBansheeEngine/AsyncOp.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace BansheeEngine
+{
+    public class AsyncOp : ScriptObject
+    {
+        internal AsyncOp()
+        {
+            Internal_CreateInstance(this);
+        }
+
+        public bool IsCompleted
+        {
+            get
+            {
+                bool value;
+                Internal_IsComplete(mCachedPtr, out value);
+                return value;
+            }
+        }
+
+        public T GetReturnValue<T>()
+        {
+            return (T)Internal_GetReturnValue(mCachedPtr);
+        }
+
+        public void BlockUntilComplete()
+        {
+            Internal_BlockUntilComplete(mCachedPtr);
+        }
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern void Internal_CreateInstance(AsyncOp managedInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern void Internal_IsComplete(IntPtr thisPtr, out bool value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern object Internal_GetReturnValue(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        internal static extern void Internal_BlockUntilComplete(IntPtr thisPtr);
+    }
+}

+ 1 - 0
MBansheeEngine/MBansheeEngine.csproj

@@ -42,6 +42,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="AsyncOp.cs" />
     <Compile Include="BuiltinResources.cs" />
     <Compile Include="Camera.cs" />
     <Compile Include="Cursor.cs" />

+ 28 - 0
SBansheeEngine/Include/BsScriptAsyncOp.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include "BsScriptEnginePrerequisites.h"
+#include "BsScriptObject.h"
+#include "BsAsyncOp.h"
+
+namespace BansheeEngine
+{
+	class BS_SCR_BE_EXPORT ScriptAsyncOp : public ScriptObject<ScriptAsyncOp>
+	{
+	public:
+		SCRIPT_OBJ(BansheeEngineAssemblyName, "BansheeEngine", "AsyncOp")
+
+		static MonoObject* create(const AsyncOp& op, std::function<MonoObject*(const AsyncOp&)> asyncOpToReturnValue);
+
+	private:
+		ScriptAsyncOp(MonoObject* instance);
+		void initialize(const AsyncOp& op, std::function<MonoObject*(const AsyncOp&)> asyncOpToReturnValue);
+
+		static void internal_createInstance(MonoObject* managedInstance);
+		static void internal_isComplete(ScriptAsyncOp* thisPtr, bool* value);
+		static MonoObject* internal_getReturnValue(ScriptAsyncOp* thisPtr);
+		static void internal_blockUntilComplete(ScriptAsyncOp* thisPtr);
+
+		AsyncOp mAsyncOp;
+		std::function<MonoObject*(const AsyncOp&)> mConvertCallback;
+	};
+}

+ 2 - 0
SBansheeEngine/SBansheeEngine.vcxproj

@@ -243,6 +243,7 @@
     <ClInclude Include="Include\BsManagedSerializableListRTTI.h" />
     <ClInclude Include="Include\BsManagedSerializableObject.h" />
     <ClInclude Include="Include\BsRuntimeScriptObjects.h" />
+    <ClInclude Include="Include\BsScriptAsyncOp.h" />
     <ClInclude Include="Include\BsScriptCamera.h" />
     <ClInclude Include="Include\BsScriptColor.h" />
     <ClInclude Include="Include\BsScriptComponent.h" />
@@ -303,6 +304,7 @@
     <ClCompile Include="Source\BsManagedResourceManager.cpp" />
     <ClCompile Include="Source\BsManagedResourceMetaData.cpp" />
     <ClCompile Include="Source\BsRuntimeScriptObjects.cpp" />
+    <ClCompile Include="Source\BsScriptAsyncOp.cpp" />
     <ClCompile Include="Source\BsScriptCamera.cpp" />
     <ClCompile Include="Source\BsScriptColor.cpp" />
     <ClCompile Include="Source\BsScriptComponent.cpp" />

+ 6 - 0
SBansheeEngine/SBansheeEngine.vcxproj.filters

@@ -243,6 +243,9 @@
     <ClInclude Include="Include\BsScriptColor.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsScriptAsyncOp.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptTexture2D.cpp">
@@ -419,5 +422,8 @@
     <ClCompile Include="Source\BsScriptColor.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsScriptAsyncOp.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 63 - 0
SBansheeEngine/Source/BsScriptAsyncOp.cpp

@@ -0,0 +1,63 @@
+#include "BsScriptAsyncOp.h"
+#include "BsMonoManager.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "BsCoreThread.h"
+
+namespace BansheeEngine
+{
+	ScriptAsyncOp::ScriptAsyncOp(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptAsyncOp::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptAsyncOp::internal_createInstance);
+		metaData.scriptClass->addInternalCall("Internal_IsComplete", &ScriptAsyncOp::internal_isComplete);
+		metaData.scriptClass->addInternalCall("Internal_GetReturnValue", &ScriptAsyncOp::internal_getReturnValue);
+		metaData.scriptClass->addInternalCall("Internal_BlockUntilComplete", &ScriptAsyncOp::internal_blockUntilComplete);
+	}
+
+	MonoObject* ScriptAsyncOp::create(const AsyncOp& op, 
+		std::function<MonoObject*(const AsyncOp&)> asyncOpToReturnValue)
+	{
+		MonoObject* managedInstance = metaData.scriptClass->createInstance();
+
+		ScriptAsyncOp* scriptAsyncOp = toNative(managedInstance);
+		scriptAsyncOp->initialize(op, asyncOpToReturnValue);
+
+		return managedInstance;
+	}
+
+	void ScriptAsyncOp::initialize(const AsyncOp& op, std::function<MonoObject*(const AsyncOp&)> asyncOpToReturnValue)
+	{
+		mAsyncOp = op;
+		mConvertCallback = asyncOpToReturnValue;
+	}
+
+	void ScriptAsyncOp::internal_createInstance(MonoObject* managedInstance)
+	{
+		ScriptAsyncOp* scriptAsyncOp = new (bs_alloc<ScriptAsyncOp>()) ScriptAsyncOp(managedInstance);
+	}
+
+	void ScriptAsyncOp::internal_isComplete(ScriptAsyncOp* thisPtr, bool* value)
+	{
+		*value = thisPtr->mAsyncOp.hasCompleted();
+	}
+
+	MonoObject* ScriptAsyncOp::internal_getReturnValue(ScriptAsyncOp* thisPtr)
+	{
+		if (!thisPtr->mAsyncOp.hasCompleted())
+			return nullptr;
+
+		if (thisPtr->mConvertCallback == nullptr)
+			return nullptr;
+
+		return thisPtr->mConvertCallback(thisPtr->mAsyncOp);
+	}
+
+	void ScriptAsyncOp::internal_blockUntilComplete(ScriptAsyncOp* thisPtr)
+	{
+		thisPtr->mAsyncOp.blockUntilComplete();
+	}
+}

+ 10 - 10
TODO.txt

@@ -1,22 +1,22 @@
-3. Figure out how to port AsyncOp to C#
-4. Write a C# interface for Texture, Texture2D, Texture3D, TextureCube
+--------- ALL LONG TERM TASKS / FIXES BELONG TO GOOGLE DOCS: ImplementationTODO OR PossibleImprovements ----------
+
+ Write a C# interface for Texture, Texture2D, Texture3D, TextureCube
   - Texture: width, height, format, numMipmaps, usage, sRGB, sampleCount
   - Texture2D: GetPixels(int mip), SetPixels(int mip), AsyncOp GetGPUPixels(int mip)
   - Texture3D: depth, GetPixels(int mip), SetPixels(int mip), AsyncOp GetGPUPixels(int mip)
   - TextureCube: GetPixels(CubeFace face, int mip), SetPixels(CubeFace face, int mip), AsyncOp GetGPUPixels(CubeFace face, int mip)
 
--------------
-
 Optionally port PixelUtility to compressing, converting, generating mipmaps, applying gamma to PixelData
  - Also getMaxMimaps and other methods for retrieving pixel format information
 
  C# AsyncOp
- - Internally holds actual AsyncOp
- - Direct accessor to isComplete
- - Accessor to returnValue
-   - returns null or empty value if not complete
-   - C++ wrapper accepts a callback that knows how to convert returnType into a C# type
-    - it should also increase ref count so the object isn't destroyed (if it's such object)
+ - blockUntilComplete doesn't actually wait for this command, but instead waits until entire queue is empty
+  - Modify CoreThreadAccesor so it uses playbackWithNotify instead of playback
+  - Internally keep a mutex + condition variable that can be referenced by every AsyncOp (they should be in a shared_ptr so that if CoreThreadAccesor maybe gets destroyed, they still remain)
+  - Every AsyncOp gets assigned an unique ID on creation (use an atomic counter?)
+  - Notify method in CoreThreadAccesor triggers the condition variable whenever a variable completes
+  - When notified AsyncOp checks its isCompleted variable
+  - This means I probably need to synchronize isCompleted (use atomic?)