v0.87
RuntimeImport 非同期ロード¶
awaitCaller 引き数¶
public virtual async Task<RuntimeGltfInstance> LoadAsync(IAwaitCaller awaitCaller = null, ...)
awaitCaller によりロード時の挙動をカスタマイズできます。
1using System;
2using System.Threading.Tasks;
3
4namespace VRMShaders
5{
6 /// <summary>
7 /// ImporterContext の 非同期実行 LoadAsync を補助する。
8 /// この関数を経由して await すること。
9 /// そうしないと、同期実行 Load 時にデッドロックに陥るかもしれない。
10 /// (SynchronizationContext に Post された 継続が再開されない)
11 /// </summary>
12 public interface IAwaitCaller
13 {
14 /// <summary>
15 /// フレームレートを維持するために1フレーム待つ
16 /// </summary>
17 /// <returns></returns>
18 Task NextFrame();
19
20 /// <summary>
21 /// 非同期に実行して、終了を待つ
22 /// </summary>
23 /// <param name="action"></param>
24 /// <returns></returns>
25 Task Run(Action action);
26
27 /// <summary>
28 /// 非同期に実行して、終了を待つ
29 /// </summary>
30 /// <param name="action"></param>
31 /// <typeparam name="T"></typeparam>
32 /// <returns></returns>
33 Task<T> Run<T>(Func<T> action);
34
35 /// <summary>
36 /// 指定した時間が経過している場合のみ、NextFrame() を使って1フレーム待つ
37 /// </summary>
38 /// <returns>タイムアウト時はNextFrame()を呼び出す。そうではない場合、Task.CompletedTaskを返す</returns>
39 Task NextFrameIfTimedOut();
40 }
41}
NextFrame: 処理を中断して次のフレームで再開します。ロード処理が長い場合に長時間アプリケーションが固まることを防ぎます
Run: UnityEngine.Object にアクセスしない処理を別スレッドで実行します。タスクが終了したら await で Unity Script スレッドで続きを実行します。
ImmediateCaller¶
デフォルトでは、ImmediateCaller
が使われます。
ImmediateCaller
タスクを即時に実行するので、同期実行となります。
Play(Editor Play, build), Editor(not play), UnitTest でデッドロックしないための実装です。
1using System;
2using System.Threading.Tasks;
3
4namespace VRMShaders
5{
6 /// <summary>
7 /// 同期実行
8 /// </summary>
9 public sealed class ImmediateCaller : IAwaitCaller
10 {
11 public Task NextFrame()
12 {
13 return Task.FromResult<object>(null);
14 }
15
16 public Task Run(Action action)
17 {
18 action();
19 return Task.FromResult<object>(null);
20 }
21
22 public Task<T> Run<T>(Func<T> action)
23 {
24 return Task.FromResult(action());
25 }
26
27 public Task NextFrameIfTimedOut() => NextFrame();
28 }
29}
RuntimeOnlyAwaitCaller¶
Play(Editor Play, build) 時に非同期実行する実装です
VRM10_Samples/VRM10Viewer
に使用例があります。
1using System;
2using System.Threading.Tasks;
3
4namespace VRMShaders
5{
6 /// <summary>
7 /// Runtime (Build 後と、Editor Playing) での非同期ロードを実現する AwaitCaller.
8 /// NOTE: 簡便に実装されたものなので、最適化の余地はある.
9 /// </summary>
10 public sealed class RuntimeOnlyAwaitCaller : IAwaitCaller
11 {
12 private readonly NextFrameTaskScheduler _scheduler;
13 private readonly float _timeOutInSeconds;
14 private float _lastTimeoutBaseTime;
15
16 /// <summary>
17 /// タイムアウト指定可能なコンストラクタ
18 /// </summary>
19 /// <param name="timeOutInSeconds">NextFrameIfTimedOutがタイムアウトと見なす時間(秒単位)</param>
20 public RuntimeOnlyAwaitCaller(float timeOutInSeconds = 1f / 1000f)
21 {
22 _scheduler = new NextFrameTaskScheduler();
23 _timeOutInSeconds = timeOutInSeconds;
24 ResetLastTimeoutBaseTime();
25 }
26
27 public Task NextFrame()
28 {
29 ResetLastTimeoutBaseTime();
30 var tcs = new TaskCompletionSource<object>();
31 _scheduler.Enqueue(() => tcs.SetResult(default));
32 return tcs.Task;
33 }
34
35 public Task Run(Action action)
36 {
37 return Task.Run(action);
38 }
39
40 public Task<T> Run<T>(Func<T> action)
41 {
42 return Task.Run(action);
43 }
44
45 public Task NextFrameIfTimedOut() => CheckTimeout() ? NextFrame() : Task.CompletedTask;
46
47 private void ResetLastTimeoutBaseTime() => _lastTimeoutBaseTime = 0f;
48
49 private bool LastTimeoutBaseTimeNeedsReset => _lastTimeoutBaseTime == 0f;
50
51 private bool CheckTimeout()
52 {
53 float t = UnityEngine.Time.realtimeSinceStartup;
54 if (LastTimeoutBaseTimeNeedsReset)
55 {
56 _lastTimeoutBaseTime = t;
57 }
58 return (t - _lastTimeoutBaseTime) >= _timeOutInSeconds;
59 }
60 }
61}