summaryrefslogtreecommitdiff
path: root/Runtime/Mono/Coroutine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Mono/Coroutine.cpp')
-rw-r--r--Runtime/Mono/Coroutine.cpp379
1 files changed, 379 insertions, 0 deletions
diff --git a/Runtime/Mono/Coroutine.cpp b/Runtime/Mono/Coroutine.cpp
new file mode 100644
index 0000000..887eafc
--- /dev/null
+++ b/Runtime/Mono/Coroutine.cpp
@@ -0,0 +1,379 @@
+#include "UnityPrefix.h"
+
+#include "Coroutine.h"
+
+#if ENABLE_SCRIPTING
+
+#include "Runtime/Misc/AsyncOperation.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Mono/MonoIncludes.h"
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/GameCode/CallDelayed.h"
+#include "Runtime/Export/WWW.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/Backend/ScriptingBackendApi.h"
+#include "Runtime/Scripting/ScriptingObjectWithIntPtrField.h"
+
+#if UNITY_IPHONE_API
+# include "Runtime/Input/OnScreenKeyboard.h"
+#endif
+
+#include "Runtime/Scripting/Backend/ScriptingBackendApi.h"
+
+// copied from MonoBehaviour.cpp
+// if they should be synced - blame the author not me ;-)
+#define DEBUG_COROUTINE 0
+#define DEBUG_COROUTINE_LEAK 0
+
+Coroutine::Coroutine()
+ : m_DoneRunning(false)
+{
+ #if DEBUG_COROUTINE
+ static int coroutineCounter = 0;
+ coroutineCounter++;
+ printf_console("Allocating coroutine %d --- %d : 0x%x\n", this, coroutineCounter, &m_RefCount);
+ #endif
+}
+
+Coroutine::~Coroutine()
+{
+ Assert(m_CoroutineEnumeratorGCHandle == 0);
+
+ #if DEBUG_COROUTINE
+ printf_console("Deconstructor coroutine %d\n", this);
+ #endif
+}
+
+void Coroutine::SetMoveNextMethod(ScriptingMethodPtr method)
+{
+ m_MoveNext = method;
+}
+
+void Coroutine::SetCurrentMethod(ScriptingMethodPtr method)
+{
+ m_Current = method;
+}
+
+void Coroutine::ContinueCoroutine (Object* o, void* userData)
+{
+ Coroutine* coroutine = (Coroutine*)userData;
+ Assert(coroutine->m_RefCount > 0 && coroutine->m_RefCount < 1000000);
+
+ if ((Object*)coroutine->m_Behaviour != o)
+ {
+ ErrorString("Coroutine continue failure");
+ #if DEBUG_COROUTINE
+ if ((Object*)coroutine->m_Behaviour != o)
+ {
+ printf_console ("continue Coroutine fuckup %d refcount: %d behaviour: %d \n", coroutine, coroutine->m_RefCount, coroutine->m_Behaviour);
+ printf_console ("continue Coroutine fuckup name: %s methodname\n", ((MonoBehaviour*)(o))->GetScript()->GetName());
+ if (coroutine->m_CoroutineMethod)
+ printf_console ("continue Coroutine methodname: %s\n", scripting_method_get_name(coroutine->m_CoroutineMethod));
+ }
+ #endif
+ return;
+ }
+
+ coroutine->Run ();
+}
+
+void Coroutine::CleanupCoroutine (void* userData)
+{
+ Coroutine* coroutine = (Coroutine*)userData;
+ AssertIf(coroutine->m_RefCount <= 0);
+ AssertIf(coroutine->m_RefCount > 1000000);
+ coroutine->m_RefCount--;
+
+ #if DEBUG_COROUTINE
+ printf_console("decrease refcount %d - active: %d \n", coroutine, coroutine->m_RefCount);
+ #endif
+
+ if (coroutine->m_RefCount > 0)
+ return;
+
+ coroutine->m_DoneRunning = true;
+
+ #if DEBUG_COROUTINE
+ printf_console ("CleanupCoroutine %d\n", coroutine);
+ if (coroutine->m_Behaviour && GetDelayedCallManager().HasDelayedCall(coroutine->m_Behaviour, Coroutine::ContinueCoroutine, CompareCoroutineMethodName, coroutine))
+ {
+ printf_console ("FUCKUP is still in delayed call manager%d!\n", coroutine->m_Behaviour);
+ }
+ #endif
+
+ if (coroutine->m_ContinueWhenFinished)
+ {
+ CleanupCoroutine (coroutine->m_ContinueWhenFinished);
+ coroutine->m_ContinueWhenFinished = NULL;
+ }
+
+ if (coroutine->m_WaitingFor)
+ coroutine->m_WaitingFor->m_ContinueWhenFinished = NULL;
+
+ coroutine->RemoveFromList();
+
+ if (coroutine->m_AsyncOperation)
+ {
+ coroutine->m_AsyncOperation->SetCoroutineCallback(NULL, NULL, NULL, NULL);
+ coroutine->m_AsyncOperation->Release();
+ coroutine->m_AsyncOperation = NULL;
+ }
+
+ Assert(coroutine->m_CoroutineEnumeratorGCHandle != 0);
+
+ scripting_gchandle_free (coroutine->m_CoroutineEnumeratorGCHandle);
+
+ coroutine->m_CoroutineEnumeratorGCHandle = 0;
+
+ if (!coroutine->m_IsReferencedByMono)
+ {
+ delete coroutine;
+
+ #if DEBUG_COROUTINE_LEAK
+ gCoroutineCounter--;
+ #endif
+ }
+}
+
+void Coroutine::CleanupCoroutineGC (void* userData)
+{
+ Coroutine* coroutine = (Coroutine*)userData;
+ if (!coroutine->m_IsReferencedByMono)
+ return;
+
+ if (coroutine->m_RefCount != 0)
+ {
+ coroutine->m_IsReferencedByMono = false;
+ return;
+ }
+
+ ErrorIf(coroutine->IsInList());
+
+ delete coroutine;
+
+ #if DEBUG_COROUTINE
+ printf_console ("GC free coroutine: %d\n", coroutine);
+ #endif
+
+ #if DEBUG_COROUTINE_LEAK
+ gCoroutineCounter--;
+ #endif
+}
+
+
+bool Coroutine::CompareCoroutineMethodName (void* callBackUserData, void* cancelUserdata)
+{
+ Coroutine* coroutine = (Coroutine*)callBackUserData;
+ if (!coroutine->m_CoroutineMethod)
+ return false;
+
+ return strcmp(scripting_method_get_name(coroutine->m_CoroutineMethod), (const char*)cancelUserdata) == 0;
+}
+
+bool Coroutine::InvokeMoveNext(ScriptingExceptionPtr* exception)
+{
+ ScriptingInvocation invocation(m_MoveNext);
+ invocation.object = m_CoroutineEnumerator;
+ invocation.classContextForProfiler = m_Behaviour->GetClass();
+ invocation.objectInstanceIDContextForException = m_Behaviour->GetInstanceID();
+ bool result = invocation.Invoke<bool>(exception);
+ return result && *exception==NULL;
+}
+
+
+void Coroutine::Run ()
+{
+ Assert(m_RefCount != 0);
+ Assert(m_Behaviour != NULL);
+
+ #if DEBUG_COROUTINE
+ AssertIf(GetDelayedCallManager().HasDelayedCall(m_Behaviour, Coroutine::ContinueCoroutine, CompareCoroutineMethodName, this));
+ if (m_Behaviour == NULL)
+ {
+ printf_console ("Coroutine fuckup %d refcount: %d behaviour%d\n", this, m_RefCount, m_Behaviour);
+ }
+ #endif
+
+ // - Call MoveNext (This processes the function until the next yield!)
+ // - Call Current (This returns condition when to continue the coroutine next.)
+ // -> Queue it based on the continue condition
+
+ // Temporarily increase refcount so the object will not get destroyed during the m_MoveNext call
+ m_RefCount++;
+ ScriptingExceptionPtr exception = NULL;
+ bool keepLooping = InvokeMoveNext(&exception);
+ AssertIf(m_RefCount <= 0 || m_RefCount > 10000000);
+
+ bool coroutineWasDestroyedDuringMoveNext = m_RefCount == 1;
+ // Decrease temporary refcount so the object will not get destroyed during the m_MoveNext call
+ CleanupCoroutine(this);
+
+ // The coroutine has been destroyed in the mean time, probably due to a call to StopAllCoroutines, stop executing further
+ if (coroutineWasDestroyedDuringMoveNext)
+ {
+ Assert(m_ContinueWhenFinished == NULL);
+ return;
+ }
+
+ if (exception != NULL)
+ return;
+
+ // Are we done with this coroutine?
+ if (!keepLooping)
+ {
+ // If there is a coroutine waiting for this one to finish Run it!
+ if (m_ContinueWhenFinished)
+ {
+ AssertIf (this != m_ContinueWhenFinished->m_WaitingFor);
+ Coroutine* continueWhenFinished = m_ContinueWhenFinished;
+ m_ContinueWhenFinished->m_WaitingFor = NULL;
+ m_ContinueWhenFinished = NULL;
+ // The coroutine might have been stopped inside of the last coroutine invokation
+ if (continueWhenFinished->m_Behaviour)
+ continueWhenFinished->Run ();
+ CleanupCoroutine (continueWhenFinished);
+ }
+
+ return;
+ }
+
+ if (m_Behaviour == NULL)
+ return;
+
+ ProcessCoroutineCurrent();
+}
+
+void Coroutine::ProcessCoroutineCurrent()
+{
+ ScriptingExceptionPtr exception = NULL;
+#if !UNITY_FLASH
+ ScriptingInvocation invocation(m_Current);
+ invocation.object = m_CoroutineEnumerator;
+ invocation.objectInstanceIDContextForException = m_Behaviour->GetInstanceID();
+ invocation.classContextForProfiler = m_Behaviour->GetClass();
+ ScriptingObjectPtr monoWait = invocation.Invoke(&exception);
+#else
+ ScriptingObjectPtr monoWait = Ext_Flash_getProperty(m_CoroutineEnumerator,"IEnumerator_Current");
+#endif
+
+ AssertIf(m_RefCount <= 0 || m_RefCount > 10000000);
+
+ if (exception != NULL)
+ return;
+
+ if (monoWait == SCRIPTING_NULL)
+ {
+ m_RefCount++;
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
+ return;
+ }
+
+ HandleIEnumerableCurrentReturnValue(monoWait);
+}
+
+void Coroutine::HandleIEnumerableCurrentReturnValue(ScriptingObjectPtr monoWait)
+{
+ AsyncOperation* async = NULL;
+ ScriptingClassPtr waitClass = scripting_object_get_class (monoWait, GetScriptingTypeRegistry());
+ const CommonScriptingClasses& classes = GetMonoManager ().GetCommonClasses ();
+
+ // Continue the coroutine in 'wait' seconds
+ if (scripting_class_is_subclass_of (waitClass, classes.waitForSeconds))
+ {
+ m_RefCount++;
+
+ float wait;
+ MarshallManagedStructIntoNative(monoWait,&wait);
+ CallDelayed (ContinueCoroutine, m_Behaviour, wait, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
+ return;
+ }
+
+ // Continue the coroutine on the next fixed update
+ if (scripting_class_is_subclass_of (waitClass, classes.waitForFixedUpdate))
+ {
+ m_RefCount++;
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunFixedFrameRate);
+ return;
+ }
+
+ // Continue the coroutine at the end of frame
+ if (scripting_class_is_subclass_of (waitClass, classes.waitForEndOfFrame))
+ {
+ m_RefCount++;
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kEndOfFrame);
+ return;
+ }
+
+ // Continue after another coroutine is finished
+ if (scripting_class_is_subclass_of (waitClass, classes.coroutine))
+ {
+ Coroutine* waitForCoroutine;
+ MarshallManagedStructIntoNative(monoWait,&waitForCoroutine);
+ if (waitForCoroutine->m_DoneRunning)
+ {
+ // continue executing.
+ ContinueCoroutine(m_Behaviour, this);
+ return;
+ }
+
+ if (waitForCoroutine->m_ContinueWhenFinished != NULL)
+ {
+ LogStringObject ("Another coroutine is already waiting for this coroutine!\nCurrently only one coroutine can wait for another coroutine!", m_Behaviour);
+ return;
+ }
+
+ m_RefCount++;
+ waitForCoroutine->m_ContinueWhenFinished = this;
+ m_WaitingFor = waitForCoroutine;
+ return;
+ }
+
+#if ENABLE_WWW
+ // Continue after fetching an www object is done
+
+ if (classes.www && scripting_class_is_subclass_of (waitClass, classes.www ))
+ {
+ WWW* wwwptr;
+ MarshallManagedStructIntoNative(monoWait,&wwwptr);
+ if(wwwptr != NULL)
+ {
+ m_RefCount++;
+ wwwptr->CallWhenDone (ContinueCoroutine, m_Behaviour, this, CleanupCoroutine);
+ }
+ return;
+ }
+#endif
+ // Continue after fetching an www object is done
+ if ((scripting_class_is_subclass_of (waitClass, classes.asyncOperation)) && (async = ScriptingObjectWithIntPtrField<AsyncOperation> (monoWait).GetPtr()) != NULL)
+ {
+ m_RefCount++;
+
+ if (async->IsDone())
+ {
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
+ return;
+ }
+
+ // Use AysncOperation ContinueCoroutine - default path
+ if (async->HasCoroutineCallback ())
+ {
+ ////@TODO: Throw exception?
+ ErrorString("This asynchronous operation is already being yielded from another coroutine. An asynchronous operation can only be yielded once.");
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
+ return;
+ }
+
+ async->SetCoroutineCallback(ContinueCoroutine, m_Behaviour, this, CleanupCoroutine);
+ m_AsyncOperation = async;
+ m_AsyncOperation->Retain();
+
+ return;
+ }
+
+ // Continue the coroutine on the next dynamic frame update
+ m_RefCount++;
+ CallDelayed (ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
+ //Ext_MarshalMap_Release_ScriptingObject(monoWait);//RH TODO : RELEASE THE MONOWAIT OBJECTS SOMEWHERE
+}
+#endif