From 9c4bab6e0a84d303cd289fb38ae1c815e36b7375 Mon Sep 17 00:00:00 2001 From: weicai995 Date: Fri, 4 Jul 2025 17:39:35 +0800 Subject: [PATCH] Add Manual Update --- .../Runtime/Internal/PlayerLoopRunner.cs | 6 +- .../UniTask/Runtime/PlayerLoopHelper.cs | 25 ++- .../Plugins/UniTask/Runtime/UniTask.Delay.cs | 153 +++++++++++++++++- 3 files changed, 171 insertions(+), 13 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs index 43625ab5..8c41d2cb 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs @@ -118,6 +118,9 @@ public void Run() case PlayerLoopTiming.LastPostLateUpdate: LastPostLateUpdate(); break; + case PlayerLoopTiming.ManualUpdate: + ManualUpdate(); + break; #if UNITY_2020_2_OR_NEWER case PlayerLoopTiming.TimeUpdate: TimeUpdate(); @@ -148,6 +151,7 @@ public void Run() void LastPreLateUpdate() => RunCore(); void PostLateUpdate() => RunCore(); void LastPostLateUpdate() => RunCore(); + void ManualUpdate() => RunCore(); #if UNITY_2020_2_OR_NEWER void TimeUpdate() => RunCore(); void LastTimeUpdate() => RunCore(); @@ -178,7 +182,7 @@ void RunCore() } else { - continue; // next i + continue; // next i } } catch (Exception ex) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopHelper.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopHelper.cs index b17375e7..1c994649 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopHelper.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/PlayerLoopHelper.cs @@ -96,6 +96,7 @@ public enum PlayerLoopTiming TimeUpdate = 14, LastTimeUpdate = 15, #endif + ManualUpdate = 16, } [Flags] @@ -192,6 +193,7 @@ public static class PlayerLoopHelper static SynchronizationContext unitySynchronizationContext; static ContinuationQueue[] yielders; static PlayerLoopRunner[] runners; + static PlayerLoopRunner ManualRunner; internal static bool IsEditorApplicationQuitting { get; private set; } static PlayerLoopSystem[] InsertRunner(PlayerLoopSystem loopSystem, bool injectOnFirst, @@ -251,6 +253,10 @@ static PlayerLoopSystem[] InsertRunner(PlayerLoopSystem loopSystem, return dest; } + public static void ManualUpdate() + { + ManualRunner.Run(); + } static PlayerLoopSystem[] RemoveRunner(PlayerLoopSystem loopSystem, Type loopRunnerYieldType, Type loopRunnerType) { @@ -302,7 +308,7 @@ static void Init() catch { } #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER - // When domain reload is disabled, re-initialization is required when entering play mode; + // When domain reload is disabled, re-initialization is required when entering play mode; // otherwise, pending tasks will leak between play mode sessions. var domainReloadDisabled = UnityEditor.EditorSettings.enterPlayModeOptionsEnabled && UnityEditor.EditorSettings.enterPlayModeOptions.HasFlag(UnityEditor.EnterPlayModeOptions.DisableDomainReload); @@ -405,6 +411,8 @@ public static void Initialize(ref PlayerLoopSystem playerLoop, InjectPlayerLoopT runners = new PlayerLoopRunner[14]; #endif + ManualRunner = new PlayerLoopRunner(PlayerLoopTiming.ManualUpdate); + var copyList = playerLoop.subSystemList.ToArray(); // Initialization @@ -491,6 +499,11 @@ public static void Initialize(ref PlayerLoopSystem playerLoop, InjectPlayerLoopT public static void AddAction(PlayerLoopTiming timing, IPlayerLoopItem action) { + if ((int)timing == (int)PlayerLoopTiming.ManualUpdate) + { + ManualRunner.AddAction(action); + return; + } var runner = runners[(int)timing]; if (runner == null) { @@ -528,8 +541,8 @@ public static void DumpCurrentPlayerLoop() { sb.AppendFormat("------{0}------", header.type.Name); sb.AppendLine(); - - if (header.subSystemList is null) + + if (header.subSystemList is null) { sb.AppendFormat("{0} has no subsystems!", header.ToString()); sb.AppendLine(); @@ -557,11 +570,11 @@ public static bool IsInjectedUniTaskPlayerLoop() foreach (var header in playerLoop.subSystemList) { - if (header.subSystemList is null) - { + if (header.subSystemList is null) + { continue; } - + foreach (var subSystem in header.subSystemList) { if (subSystem.type == typeof(UniTaskLoopRunners.UniTaskLoopRunnerInitialization)) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs index 4ff699dd..91197209 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs @@ -16,11 +16,14 @@ public enum DelayType /// Ignore timescale, use Time.unscaledDeltaTime. UnscaledDeltaTime, /// use Stopwatch.GetTimestamp(). - Realtime + Realtime, + ManualTime } public partial struct UniTask { + public static float deltaTime; + public static int frameCount; public static YieldAwaitable Yield() { // optimized for single continuation @@ -80,7 +83,7 @@ public static async UniTask WaitForEndOfFrame(CancellationToken cancellationToke { await Awaitable.EndOfFrameAsync(cancellationToken); } -#else +#else [Obsolete("Use WaitForEndOfFrame(MonoBehaviour) instead or UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate). Equivalent for coroutine's WaitForEndOfFrame requires MonoBehaviour(runner of Coroutine).")] public static YieldAwaitable WaitForEndOfFrame() { @@ -92,7 +95,7 @@ public static UniTask WaitForEndOfFrame(CancellationToken cancellationToken, boo { return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken, cancelImmediately); } -#endif +#endif public static UniTask WaitForEndOfFrame(MonoBehaviour coroutineRunner) { @@ -179,6 +182,10 @@ public static UniTask WaitForFixedUpdate(CancellationToken cancellationToken, bo switch (delayType) { + case DelayType.ManualTime: + { + return new UniTask(DelayManualPromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token); + } case DelayType.UnscaledDeltaTime: { return new UniTask(DelayIgnoreTimeScalePromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token); @@ -229,7 +236,7 @@ public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken c result.cancellationToken = cancellationToken; result.cancelImmediately = cancelImmediately; - + if (cancelImmediately && cancellationToken.CanBeCanceled) { result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => @@ -320,6 +327,7 @@ static NextFramePromise() CancellationToken cancellationToken; CancellationTokenRegistration cancellationTokenRegistration; bool cancelImmediately; + PlayerLoopTiming timing = PlayerLoopTiming.Update; NextFramePromise() { @@ -337,8 +345,9 @@ public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken c result = new NextFramePromise(); } - result.frameCount = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; + result.frameCount = PlayerLoopHelper.IsMainThread ?(timing == PlayerLoopTiming.ManualUpdate ? UniTask.frameCount : Time.frameCount) : -1; result.cancellationToken = cancellationToken; + result.timing = timing; result.cancelImmediately = cancelImmediately; if (cancelImmediately && cancellationToken.CanBeCanceled) @@ -400,7 +409,7 @@ public bool MoveNext() return false; } - if (frameCount == Time.frameCount) + if (frameCount == (timing == PlayerLoopTiming.ManualUpdate ? UniTask.frameCount : Time.frameCount)) { return true; } @@ -695,6 +704,138 @@ bool TryReturn() } } + sealed class DelayManualPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode + { + static TaskPool pool; + DelayManualPromise nextNode; + public ref DelayManualPromise NextNode => ref nextNode; + + static DelayManualPromise() + { + TaskPool.RegisterSizeGetter(typeof(DelayManualPromise), () => pool.Size); + } + + int initialFrame; + float delayTimeSpan; + float elapsed; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool cancelImmediately; + + UniTaskCompletionSourceCore core; + + DelayManualPromise() + { + } + + public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new DelayManualPromise(); + } + + result.elapsed = 0.0f; + result.delayTimeSpan = (float)delayTimeSpan.TotalSeconds; + result.cancellationToken = cancellationToken; + result.initialFrame = PlayerLoopHelper.IsMainThread ? frameCount : -1; + result.cancelImmediately = cancelImmediately; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (DelayManualPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(timing, result); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + if (!(cancelImmediately && cancellationToken.IsCancellationRequested)) + { + TryReturn(); + } + else + { + TaskTracker.RemoveTracking(this); + } + } + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + return false; + } + + if (elapsed == 0.0f) + { + if (initialFrame == Time.frameCount) + { + return true; + } + } + + elapsed += Time.deltaTime; + if (elapsed >= delayTimeSpan) + { + core.TrySetResult(null); + return false; + } + + return true; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + delayTimeSpan = default; + elapsed = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + cancelImmediately = default; + return pool.TryPush(this); + } + } + sealed class DelayPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool;