diff --git a/.example/Assets/csc.rsp b/.example/Assets/csc.rsp new file mode 100644 index 0000000..df842cd --- /dev/null +++ b/.example/Assets/csc.rsp @@ -0,0 +1,2 @@ +-nowarn:649 +-warnaserror+ diff --git a/.example/Assets/csc.rsp.meta b/.example/Assets/csc.rsp.meta new file mode 100644 index 0000000..e515ac4 --- /dev/null +++ b/.example/Assets/csc.rsp.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e9f4eafbf5d7b43b681fb51b68743d8a +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/ComponentExtensions.cs b/src/ComponentExtensions.cs index b924f58..99fe446 100644 --- a/src/ComponentExtensions.cs +++ b/src/ComponentExtensions.cs @@ -38,10 +38,14 @@ public static class ComponentExtensions /// /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func taskCreator) + public static Task StartTask( + this Component component, + Func taskCreator, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -52,7 +56,7 @@ public static Task StartTask(this Component component, Func taskCreator) UnityHelper.ThrowForNonUnityThreadOrEditMode(); // Start the task on a runner for the component. - return GetTaskRunnerUnchecked(component).StartTask(taskCreator); + return GetTaskRunnerUnchecked(component, options).StartTask(taskCreator); } /// @@ -81,10 +85,14 @@ public static Task StartTask(this Component component, Func taskCreator) /// /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func taskCreator) + public static Task StartTask( + this Component component, + Func taskCreator, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -95,7 +103,7 @@ public static Task StartTask(this Component component, Func @@ -122,10 +130,15 @@ public static Task StartTask(this Component component, Func /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func taskCreator, TIn data) + public static Task StartTask( + this Component component, + Func taskCreator, + TIn data, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -136,7 +149,7 @@ public static Task StartTask(this Component component, Func task UnityHelper.ThrowForNonUnityThreadOrEditMode(); // Start the task on a runner for the component. - return GetTaskRunnerUnchecked(component).StartTask(taskCreator, data); + return GetTaskRunnerUnchecked(component, options).StartTask(taskCreator, data); } /// @@ -165,10 +178,15 @@ public static Task StartTask(this Component component, Func task /// /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func taskCreator, TIn data) + public static Task StartTask( + this Component component, + Func taskCreator, + TIn data, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -179,7 +197,7 @@ public static Task StartTask(this Component component, Func @@ -206,10 +224,14 @@ public static Task StartTask(this Component component, Func /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func> taskCreator) + public static Task StartTask( + this Component component, + Func> taskCreator, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -220,7 +242,7 @@ public static Task StartTask(this Component component, Func @@ -249,10 +271,14 @@ public static Task StartTask(this Component component, Func /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func> taskCreator) + public static Task StartTask( + this Component component, + Func> taskCreator, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -263,7 +289,7 @@ public static Task StartTask(this Component component, Func @@ -290,10 +316,15 @@ public static Task StartTask(this Component component, Func /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func> taskCreator, TIn data) + public static Task StartTask( + this Component component, + Func> taskCreator, + TIn data, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -304,7 +335,7 @@ public static Task StartTask(this Component component, Func @@ -333,10 +364,15 @@ public static Task StartTask(this Component component, Func /// Component to run the task 'on'. /// Function for creating the task. + /// Options for configuring how the task is run. /// /// Task that completes when the original task completes or when the component gets destroyed. /// - public static Task StartTask(this Component component, Func> taskCreator, TIn data) + public static Task StartTask( + this Component component, + Func> taskCreator, + TIn data, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -347,7 +383,7 @@ public static Task StartTask(this Component component, Func @@ -366,8 +402,11 @@ public static Task StartTask(this Component component, Func /// Component to get the runner for. + /// Options for configuring how tasks are run on this runner. /// scoped to the given component. - public static ITaskRunner GetTaskRunner(this Component component) + public static ITaskRunner GetTaskRunner( + this Component component, + TaskRunOptions options = TaskRunOptions.Default) { // Validate params. UnityHelper.ThrowForInvalidObjectParam(component, nameof(component)); @@ -376,10 +415,10 @@ public static ITaskRunner GetTaskRunner(this Component component) UnityHelper.ThrowForNonUnityThreadOrEditMode(); // Start the task on a runner for the component. - return GetTaskRunnerUnchecked(component); + return GetTaskRunnerUnchecked(component, options); } - internal static ITaskRunner GetTaskRunnerUnchecked(this Component component) + internal static ITaskRunner GetTaskRunnerUnchecked(Component component, TaskRunOptions options) { var owner = component.gameObject; @@ -388,12 +427,13 @@ internal static ITaskRunner GetTaskRunnerUnchecked(this Component component) foreach (var runner in monoBehaviourRunners) { // If there is a runner for that component then return that. - if (runner.ComponentToFollow == component) + if (!runner.IsFinished && runner.RunOptions == options && runner.ComponentToFollow == component) return runner; } // Otherwise create a new runner for this component. var newRunner = owner.AddComponent(); + newRunner.RunOptions = options; newRunner.ComponentToFollow = component; newRunner.hideFlags = HideFlags.HideInInspector; return newRunner; diff --git a/src/ComponentTask/Internal/MonoBehaviourTaskRunner.cs b/src/ComponentTask/Internal/MonoBehaviourTaskRunner.cs index aafe3ac..c393336 100644 --- a/src/ComponentTask/Internal/MonoBehaviourTaskRunner.cs +++ b/src/ComponentTask/Internal/MonoBehaviourTaskRunner.cs @@ -1,10 +1,11 @@ using System; using System.Threading; using System.Threading.Tasks; +using UnityEngine; namespace ComponentTask.Internal { - internal sealed class MonoBehaviourTaskRunner : UnityEngine.MonoBehaviour, ITaskRunner, IExceptionHandler + internal sealed class MonoBehaviourTaskRunner : MonoBehaviour, ITaskRunner, IExceptionHandler { private readonly LocalTaskRunner taskRunner; @@ -13,7 +14,9 @@ public MonoBehaviourTaskRunner() this.taskRunner = new LocalTaskRunner(exceptionHandler: this); } - public UnityEngine.Component ComponentToFollow { get; set; } + public TaskRunOptions RunOptions { get; set; } + + public Component ComponentToFollow { get; set; } public bool IsFinished { get; private set; } @@ -86,7 +89,9 @@ private void LateUpdate() // If the component is a 'Behaviour' then we update when its enabled. if (ComponentToFollow is UnityEngine.Behaviour behaviour) { - if (behaviour.isActiveAndEnabled) + var updateWhileDisabled = + (this.RunOptions & TaskRunOptions.UpdateWhileComponentDisabled) == TaskRunOptions.UpdateWhileComponentDisabled; + if (updateWhileDisabled || behaviour.isActiveAndEnabled) this.Execute(); } else diff --git a/src/GameObjectExtensions.cs b/src/GameObjectExtensions.cs index c287faf..e9d86e6 100644 --- a/src/GameObjectExtensions.cs +++ b/src/GameObjectExtensions.cs @@ -35,6 +35,7 @@ public static ITaskRunner CreateTaskRunner(this GameObject gameObject) // Create runner. var runner = gameObject.AddComponent(); + runner.RunOptions = TaskRunOptions.Default; runner.hideFlags = HideFlags.HideInInspector; return runner; diff --git a/src/TaskRunOptions.cs b/src/TaskRunOptions.cs new file mode 100644 index 0000000..e7a89f5 --- /dev/null +++ b/src/TaskRunOptions.cs @@ -0,0 +1,24 @@ +using System; + +namespace UnityEngine +{ + /// + /// Flags for configuring how tasks are being run. + /// + [Flags] + public enum TaskRunOptions : int + { + /// + /// Default run options. + /// + /// + /// Tasks are updated in 'LateUpdate' when the component is enabled. + /// + Default = 0, + + /// + /// Tasks are updated even if the component is disabled. + /// + UpdateWhileComponentDisabled = 1 << 0, + } +} diff --git a/src/TaskRunOptions.cs.meta b/src/TaskRunOptions.cs.meta new file mode 100644 index 0000000..cd73320 --- /dev/null +++ b/src/TaskRunOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2fcdce5ac99a486ead26ebddc5b3d95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/playmode/ComponentExtensionsTests.cs b/tests/playmode/ComponentExtensionsTests.cs index 400e6c8..1e7d106 100644 --- a/tests/playmode/ComponentExtensionsTests.cs +++ b/tests/playmode/ComponentExtensionsTests.cs @@ -209,7 +209,42 @@ async Task IncrementCountAsync() } [UnityTest] - public IEnumerator SameRunnerIsReusedForTheSameComponent() + public IEnumerator TaskRunsWhileComponentIsDisabledWhenConfigured() + { + var count = 0; + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + comp.StartTask(IncrementCountAsync, TaskRunOptions.UpdateWhileComponentDisabled); + + // Assert task is running. + yield return null; + Assert.AreEqual(1, count); + + // Disable component. + comp.enabled = false; + + // Assert task is still running. + yield return null; + Assert.AreEqual(2, count); + yield return null; + Assert.AreEqual(3, count); + + // Cleanup. + Object.Destroy(go); + + async Task IncrementCountAsync() + { + await Task.Yield(); + count++; + await Task.Yield(); + count++; + await Task.Yield(); + count++; + } + } + + [UnityTest] + public IEnumerator SameRunnerIsReusedForTheSameComponentAndSameOptions() { var go = new GameObject("TestGameObject"); var comp = go.AddComponent(); @@ -222,6 +257,20 @@ public IEnumerator SameRunnerIsReusedForTheSameComponent() Object.Destroy(go); } + [UnityTest] + public IEnumerator DifferentRunnerIsUsedForSameComponentAndDifferentOptions() + { + var go = new GameObject("TestGameObject"); + var comp = go.AddComponent(); + + var runner1 = comp.GetTaskRunner(TaskRunOptions.Default); + var runner2 = comp.GetTaskRunner(TaskRunOptions.UpdateWhileComponentDisabled); + Assert.False(object.ReferenceEquals(runner1, runner2)); + + yield return null; + Object.Destroy(go); + } + [UnityTest] public IEnumerator RunnerDestroysItselfWhenComponentIsDestroyed() {