diff --git a/src/ComponentTask/LocalTaskRunner.cs b/src/ComponentTask/LocalTaskRunner.cs index ead700e..e92a16a 100644 --- a/src/ComponentTask/LocalTaskRunner.cs +++ b/src/ComponentTask/LocalTaskRunner.cs @@ -174,19 +174,24 @@ public void Execute() if (this.isDisposed) throw new ObjectDisposedException(nameof(LocalTaskRunner)); - // Execute all the work that was scheduled on this runner. - using (var contextScope = ContextScope.WithContext(this.context)) + try { - this.context.Execute(); + // Execute all the work that was scheduled on this runner. + using (var contextScope = ContextScope.WithContext(this.context)) + { + this.context.Execute(); + } } - - // Remove any tasks that are now finished. - lock (this.runningTasksLock) + finally { - for (int i = this.runningTasks.Count - 1; i >= 0; i--) + // Remove any tasks that are now finished. + lock (this.runningTasksLock) { - if (this.runningTasks[i].IsFinished) - this.runningTasks.RemoveAt(i); + for (int i = this.runningTasks.Count - 1; i >= 0; i--) + { + if (this.runningTasks[i].IsFinished) + this.runningTasks.RemoveAt(i); + } } } } diff --git a/tests/editmode/LocalTaskRunner_MiscTests.cs b/tests/editmode/LocalTaskRunner_MiscTests.cs index 894fd64..a59c35c 100644 --- a/tests/editmode/LocalTaskRunner_MiscTests.cs +++ b/tests/editmode/LocalTaskRunner_MiscTests.cs @@ -126,6 +126,22 @@ public void ResetsPreviousSynchronizationContextWhenTaskCreatorThrows() Assert.True(SynchronizationContext.Current == syncContext); } + [Test] + public void ThrowWhenTaskChangesSyncronizationContextInSyncronousPart() + { + var exHandler = new MockExceptionHandler(); + using (var runner = new LocalTaskRunner(exHandler)) + { + Assert.Throws(() => runner.StartTask(TaskThatChangesSyncContext)); + } + + Task TaskThatChangesSyncContext() + { + SynchronizationContext.SetSynchronizationContext(new MockSynchronizationContext()); + return Task.CompletedTask; + } + } + [Test] public void StartingCompletedTaskIsReturnedDirectly() { diff --git a/tests/playmode/ComponentExtensionsTests.cs b/tests/playmode/ComponentExtensionsTests.cs index 869a649..bd3a632 100644 --- a/tests/playmode/ComponentExtensionsTests.cs +++ b/tests/playmode/ComponentExtensionsTests.cs @@ -23,7 +23,7 @@ public IEnumerator TaskStopsWhenComponentIsDestroyed() var count = 0; var go = new GameObject("TestGameObject"); var comp = go.AddComponent(); - var t = comp.GetTaskRunner().StartTask(IncrementCountAsync); + var t = comp.StartTask(IncrementCountAsync); // Assert that count is increment to 1. yield return null; @@ -54,6 +54,116 @@ async Task IncrementCountAsync() } } + [UnityTest] + public IEnumerator MultipleTasksCanRunInParallelOnSameComponent() + { + var count = 0; + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + comp.StartTask(IncrementCountAsync); + comp.StartTask(IncrementCountAsync); + comp.StartTask(IncrementCountAsync); + + yield return null; + Assert.AreEqual(3, count); + + yield return null; + Assert.AreEqual(6, count); + + yield return null; + Assert.AreEqual(9, count); + + // Cleanup. + Object.Destroy(go); + + async Task IncrementCountAsync() + { + await Task.Yield(); + count++; + await Task.Yield(); + count++; + await Task.Yield(); + count++; + } + } + + [UnityTest] + public IEnumerator MultipleTasksCanRunInSequenceOnSameComponent() + { + var count = 0; + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + comp.StartTask(IncrementCountAsync, 2); + + // Assert count is increased every frame for 9 frames. + for (int i = 0; i < 9; i++) + { + Assert.AreEqual(i, count); + yield return null; + } + + // Assert count stays at 9. + yield return null; + Assert.AreEqual(9, count); + + // Cleanup. + Object.Destroy(go); + + async Task IncrementCountAsync(int iters) + { + await Task.Yield(); + count++; + await Task.Yield(); + count++; + await Task.Yield(); + count++; + + if (iters > 0) + comp.StartTask(IncrementCountAsync, iters - 1).DontWait(); + } + } + + [UnityTest] + public IEnumerator MultipleTasksCanRunInSequenceWithPausesOnSameComponent() + { + var count = 0; + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + + // Run tasks. + comp.StartTask(IncrementCountAsync); + for (int i = 0; i < 3; i++) + { + Assert.AreEqual(i, count); + yield return null; + } + + // Idle for couple of frames. + yield return null; + yield return null; + + // Run another task. + comp.StartTask(IncrementCountAsync); + for (int i = 3; i < 6; i++) + { + Assert.AreEqual(i, count); + yield return null; + } + + // Cleanup. + Object.Destroy(go); + + async Task IncrementCountAsync() + { + await Task.Yield(); + count++; + await Task.Yield(); + count++; + await Task.Yield(); + count++; + } + } + [UnityTest] public IEnumerator SameRunnerIsReusedForTheSameComponent() { @@ -83,5 +193,27 @@ public IEnumerator ThrowsWhenCalledFromNonUnityThread() yield return null; Object.Destroy(go); } + + [UnityTest] + public IEnumerator NullComponentThrowsArgumentNullException() + { + Assert.Throws(() => ComponentExtensions.GetTaskRunner(null)); + Assert.Throws(() => ComponentExtensions.StartTask(null, () => Task.CompletedTask)); + yield break; + } + + [UnityTest] + public IEnumerator DestroyedComponentThrowsMissingReferenceException() + { + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + Object.DestroyImmediate(comp); + + Assert.Throws(() => ComponentExtensions.GetTaskRunner(comp)); + Assert.Throws(() => ComponentExtensions.StartTask(comp, () => Task.CompletedTask)); + + yield return null; + Object.Destroy(go); + } } } diff --git a/tests/playmode/GameObjectExtensionsTests.cs b/tests/playmode/GameObjectExtensionsTests.cs index 4d777c9..ae8409e 100644 --- a/tests/playmode/GameObjectExtensionsTests.cs +++ b/tests/playmode/GameObjectExtensionsTests.cs @@ -107,5 +107,22 @@ public IEnumerator CreateThrowsWhenCalledFromNonUnityThread() yield return null; Object.Destroy(go); } + + [UnityTest] + public IEnumerator NullGameObjectThrowsArgumentNullException() + { + Assert.Throws(() => GameObjectExtensions.CreateTaskRunner(null)); + yield break; + } + + [UnityTest] + public IEnumerator DestroyedGameObjectThrowsMissingReferenceException() + { + var go = new GameObject("TestGameObject"); + Object.DestroyImmediate(go); + + Assert.Throws(() => GameObjectExtensions.CreateTaskRunner(go)); + yield break; + } } }