From 935afcb80dce5043985a126b8e9ad0c2fc68e7d9 Mon Sep 17 00:00:00 2001 From: Takayuki Matsuoka Date: Wed, 7 Sep 2022 17:51:10 +0900 Subject: [PATCH 1/2] Add new pseudo method NextFrameIfTimedOut to RuntimeOnlyAwaitCaller This change adds (pseudo) method RuntimeOnlyAwaitCaller.NextFrameIfTimedOut() which invokes NextFrame() if given time is elapsed. Otherwise, it does nothing. We'll be able to utilize this (pseudo) method to ease possible cause of spikes. For example: ---- public class VRMImporterContext : ImporterContext { protected override async Task OnLoadHierarchy(IAwaitCaller awaitCaller, ...) { ... var blendShapeList = VRM.blendShapeMaster.blendShapeGroups; if (blendShapeList != null && blendShapeList.Count > 0) { foreach (var x in blendShapeList) { await awaitCaller.NextFrameIfTimeout(); BlendShapeAvatar.Clips.Add(await LoadBlendShapeBind(awaitCaller, x, transformMeshTable)); } } ... } } ---- Note that this change doesn't add NextFrameIfTimeout() to IAwaitCaller to avoid breaking change. This limitation introduces extra (maybe unnecessarily) complexity. --- .../AwaitCaller/RuntimeOnlyAwaitCaller.cs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs index 23cc7b592..83665d7a0 100644 --- a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs @@ -32,5 +32,51 @@ namespace VRMShaders { return Task.Run(action); } + + /// + /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ + /// + /// タイムアウト時間(ミリ秒単位) + /// タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す + public Task NextFrameIfTimedOut_(float timeOutInMilliseconds = 1f) + { + if (!CheckTimeOut(timeOutInMilliseconds)) + { + return Task.CompletedTask; + } + _lastBaseTime = 0f; + return NextFrame(); + } + + private bool CheckTimeOut(float timeOutInMilliseconds) + { + float t = UnityEngine.Time.realtimeSinceStartup; + if (_lastBaseTime == 0f) + { + // Reset base time + _lastBaseTime = t; + } + return (t - _lastBaseTime) >= timeOutInMilliseconds * (1f / 1000f); + } + + private float _lastBaseTime; } -} \ No newline at end of file + + internal static class RuntimeOnlyAwaitCallerHelper + { + /// + /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ + /// + /// IAwaitCallerのインスタンス + /// タイムアウト時間(ミリ秒単位) + /// タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す + internal static Task NextFrameIfTimedOut(this IAwaitCaller iAwaitCaller, float timeOutInMilliseconds) + { + if (iAwaitCaller is RuntimeOnlyAwaitCaller runtimeOnlyAwaitCaller) + { + return runtimeOnlyAwaitCaller.NextFrameIfTimedOut_(timeOutInMilliseconds); + } + return Task.CompletedTask; + } + } +} From a68d0122ced4db76a3eaa909ceed006a920d4c6e Mon Sep 17 00:00:00 2001 From: Takayuki Matsuoka Date: Thu, 8 Sep 2022 17:39:05 +0900 Subject: [PATCH 2/2] BREAKING CHANGE: Introduce NextFrameIfTimedOut as a new interface method of IAwaitCaller This is a breaking change since it introduces new interface method to IAwaitCaller. ---- NextFrameIfTimedOut() is added to IAwaitCaller. The following inheritors also introduce the method. - ImmediateCaller.NextFrameIfTimedOut() just invokes NextFrame(). - RuntimeOnlyAwaitCaller.NextFrameIfTimedOut() invokes NextFrame() if timed out. Otherwise, do nothing. - RuntimeOnlyNoThreadAwaitCaller.NextFrameIfTimedOut() works same as RuntimeOnlyAwaitCaller. Constructor of RuntimeOnlyAwaitCaller and RuntimeOnlyNoThreadAwaitCaller now take argument of timeout in seconds. The default value is 1/1000 (1 milliseconds). See also: https://github.com/vrm-c/UniVRM/pull/1781#pullrequestreview-1100131840 --- .../IO/Runtime/AwaitCaller/IAwaitCaller.cs | 6 ++ .../IO/Runtime/AwaitCaller/ImmediateCaller.cs | 2 + .../AwaitCaller/RuntimeOnlyAwaitCaller.cs | 59 ++++++------------- .../RuntimeOnlyNoThreadAwaitCaller.cs | 27 ++++++++- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/IAwaitCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/IAwaitCaller.cs index c97e4d0ce..877ae8ae0 100644 --- a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/IAwaitCaller.cs +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/IAwaitCaller.cs @@ -31,5 +31,11 @@ namespace VRMShaders /// /// Task Run(Func action); + + /// + /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ + /// + /// タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す + Task NextFrameIfTimedOut(); } } diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/ImmediateCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/ImmediateCaller.cs index f3e6d56a0..db71fe818 100644 --- a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/ImmediateCaller.cs +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/ImmediateCaller.cs @@ -23,5 +23,7 @@ namespace VRMShaders { return Task.FromResult(action()); } + + public Task NextFrameIfTimedOut() => NextFrame(); } } \ No newline at end of file diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs index 83665d7a0..b583465ff 100644 --- a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyAwaitCaller.cs @@ -10,14 +10,23 @@ namespace VRMShaders public sealed class RuntimeOnlyAwaitCaller : IAwaitCaller { private readonly NextFrameTaskScheduler _scheduler; + private readonly float _timeOutInSeconds; + private float _lastTimeoutBaseTime; - public RuntimeOnlyAwaitCaller() + /// + /// タイムアウト指定可能なコンストラクタ + /// + /// NextFrameIfTimedOutがタイムアウトと見なす時間(秒単位) + public RuntimeOnlyAwaitCaller(float timeOutInSeconds = 1f / 1000f) { _scheduler = new NextFrameTaskScheduler(); + _timeOutInSeconds = timeOutInSeconds; + ResetLastTimeoutBaseTime(); } public Task NextFrame() { + ResetLastTimeoutBaseTime(); var tcs = new TaskCompletionSource(); _scheduler.Enqueue(() => tcs.SetResult(default)); return tcs.Task; @@ -33,50 +42,20 @@ namespace VRMShaders return Task.Run(action); } - /// - /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ - /// - /// タイムアウト時間(ミリ秒単位) - /// タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す - public Task NextFrameIfTimedOut_(float timeOutInMilliseconds = 1f) - { - if (!CheckTimeOut(timeOutInMilliseconds)) - { - return Task.CompletedTask; - } - _lastBaseTime = 0f; - return NextFrame(); - } + public Task NextFrameIfTimedOut() => CheckTimeout() ? NextFrame() : Task.CompletedTask; - private bool CheckTimeOut(float timeOutInMilliseconds) + private void ResetLastTimeoutBaseTime() => _lastTimeoutBaseTime = 0f; + + private bool LastTimeoutBaseTimeNeedsReset => _lastTimeoutBaseTime == 0f; + + private bool CheckTimeout() { float t = UnityEngine.Time.realtimeSinceStartup; - if (_lastBaseTime == 0f) + if (LastTimeoutBaseTimeNeedsReset) { - // Reset base time - _lastBaseTime = t; + _lastTimeoutBaseTime = t; } - return (t - _lastBaseTime) >= timeOutInMilliseconds * (1f / 1000f); - } - - private float _lastBaseTime; - } - - internal static class RuntimeOnlyAwaitCallerHelper - { - /// - /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ - /// - /// IAwaitCallerのインスタンス - /// タイムアウト時間(ミリ秒単位) - /// タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す - internal static Task NextFrameIfTimedOut(this IAwaitCaller iAwaitCaller, float timeOutInMilliseconds) - { - if (iAwaitCaller is RuntimeOnlyAwaitCaller runtimeOnlyAwaitCaller) - { - return runtimeOnlyAwaitCaller.NextFrameIfTimedOut_(timeOutInMilliseconds); - } - return Task.CompletedTask; + return (t - _lastTimeoutBaseTime) >= _timeOutInSeconds; } } } diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs index e3b53c9a8..97e8597fd 100644 --- a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs @@ -10,14 +10,23 @@ namespace VRMShaders public sealed class RuntimeOnlyNoThreadAwaitCaller : IAwaitCaller { private readonly NextFrameTaskScheduler _scheduler; + private readonly float _timeoutInSeconds; + private float _lastTimeoutBaseTime; - public RuntimeOnlyNoThreadAwaitCaller() + /// + /// タイムアウト指定可能なコンストラクタ + /// + /// NextFrameIfTimedOutがタイムアウトと見なす時間(秒単位) + public RuntimeOnlyNoThreadAwaitCaller(float timeoutInSeconds = 1f / 1000f) { _scheduler = new NextFrameTaskScheduler(); + _timeoutInSeconds = timeoutInSeconds; + ResetLastTimeoutBaseTime(); } public Task NextFrame() { + ResetLastTimeoutBaseTime(); var tcs = new TaskCompletionSource(); _scheduler.Enqueue(() => tcs.SetResult(default)); return tcs.Task; @@ -47,5 +56,21 @@ namespace VRMShaders return Task.FromException(ex); } } + + public Task NextFrameIfTimedOut() => CheckTimeout() ? NextFrame() : Task.CompletedTask; + + private void ResetLastTimeoutBaseTime() => _lastTimeoutBaseTime = 0f; + + private bool LastTimeoutBaseTimeNeedsReset => _lastTimeoutBaseTime == 0f; + + private bool CheckTimeout() + { + float t = UnityEngine.Time.realtimeSinceStartup; + if (LastTimeoutBaseTimeNeedsReset) + { + _lastTimeoutBaseTime = t; + } + return (t - _lastTimeoutBaseTime) >= _timeoutInSeconds; + } } } \ No newline at end of file