bdunderscore / ndmf

MIT License
72 stars 22 forks source link

[PreviewSystem] `IRenderFilter.Instantiate` などがランダムでPlayerLoopの内外で呼び出される #409

Closed anatawa12 closed 3 weeks ago

anatawa12 commented 3 weeks ago

e70e43bbf3086e4e3305ab9a313a0ed969a341e1 で確認しました。

IRenderFilter.Instantiate などがランダムでPlayerLoopの内外で呼び出されてしまいます。

PlayerLoopの中で呼び出されるとRead/Write offなメッシュのアクセスがエラーになってしまうので、これが発生するか否かがランダムだとデバッグ時に微妙に困ります。

PlayerLoopのなかかどうかは_Z18IsInsidePlayerLoopvを呼び出せば確認できますがDllImportでは呼び出せないため様々なハックが必要となってしまいます。(正攻法は不明)

一応EditorApplication.updateなどは外であることが確定しています。

unity 2022.3.22f1 arm64でのみ動く確認用コードはありますが他環境だと確実に壊れますため汎用性がないです。 https://gist.github.com/anatawa12/f730f08940ef965cadcf25388c0afb1c

bdunderscore commented 3 weeks ago

こちらの環境では再現できてません(ProxyPipelineなどの中にMeshを参照するテストコードを追加などしてみてもだめっぽい)

Mac特有の事情の可能性もあるので、とりあえず発生時点でのスタックトレースなどをお願いします。

anatawa12 commented 3 weeks ago

スタックトレース的には変化がありません

少し今試した感じ、エディタをいじってるだけでは発生確率が低いですが、シーンウィンドウの視点移動をしていたりすると(PlayerLoopをトリガさせていると?)よく発生するように思えます

Instantiate IsPlayerLoop: True
UnityEngine.Debug:Log (object)
Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>:MoveNext () (at ./Packages/AvatarOptimizer/Editor/EditModePreview/AAORenderFilterBase.cs:64)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.IRenderFilterNode>:Start<Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>> (Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>&)
Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1<Anatawa12.AvatarOptimizer.RemoveMeshInBox>:Instantiate (nadena.dev.ndmf.preview.RenderGroup,System.Collections.Generic.IEnumerable`1<System.ValueTuple`2<UnityEngine.Renderer, UnityEngine.Renderer>>,nadena.dev.ndmf.preview.ComputeContext)
nadena.dev.ndmf.preview.NodeController/<Create>d__23:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/NodeController.cs:121)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.NodeController/<Create>d__23> (nadena.dev.ndmf.preview.NodeController/<Create>d__23&)
nadena.dev.ndmf.preview.NodeController:Create (nadena.dev.ndmf.preview.IRenderFilter,nadena.dev.ndmf.preview.RenderGroup,nadena.dev.ndmf.ObjectRegistry,System.Collections.Generic.List`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>>,string)
nadena.dev.ndmf.preview.NodeController/<Refresh>d__24:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/NodeController.cs:190)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.NodeController/<Refresh>d__24> (nadena.dev.ndmf.preview.NodeController/<Refresh>d__24&)
nadena.dev.ndmf.preview.NodeController:Refresh (System.Collections.Generic.List`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>>,nadena.dev.ndmf.preview.RenderAspects,string)
nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/ProxyPipeline.cs:234)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d> (nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d&)
nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2:<Build>b__5 (System.Threading.Tasks.Task`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>[]>)
UnityEngine.UnitySynchronizationContext:ExecuteTasks () (at /Users/bokken/build/output/unity/unity/Runtime/Export/Scripting/UnitySynchronizationContext.cs:107)
Instantiate IsPlayerLoop: False
UnityEngine.Debug:Log (object)
Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>:MoveNext () (at ./Packages/AvatarOptimizer/Editor/EditModePreview/AAORenderFilterBase.cs:64)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.IRenderFilterNode>:Start<Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>> (Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1/<Instantiate>d__5<Anatawa12.AvatarOptimizer.RemoveMeshInBox>&)
Anatawa12.AvatarOptimizer.EditModePreview.AAORenderFilterBase`1<Anatawa12.AvatarOptimizer.RemoveMeshInBox>:Instantiate (nadena.dev.ndmf.preview.RenderGroup,System.Collections.Generic.IEnumerable`1<System.ValueTuple`2<UnityEngine.Renderer, UnityEngine.Renderer>>,nadena.dev.ndmf.preview.ComputeContext)
nadena.dev.ndmf.preview.NodeController/<Create>d__23:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/NodeController.cs:121)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.NodeController/<Create>d__23> (nadena.dev.ndmf.preview.NodeController/<Create>d__23&)
nadena.dev.ndmf.preview.NodeController:Create (nadena.dev.ndmf.preview.IRenderFilter,nadena.dev.ndmf.preview.RenderGroup,nadena.dev.ndmf.ObjectRegistry,System.Collections.Generic.List`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>>,string)
nadena.dev.ndmf.preview.NodeController/<Refresh>d__24:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/NodeController.cs:190)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.NodeController/<Refresh>d__24> (nadena.dev.ndmf.preview.NodeController/<Refresh>d__24&)
nadena.dev.ndmf.preview.NodeController:Refresh (System.Collections.Generic.List`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>>,nadena.dev.ndmf.preview.RenderAspects,string)
nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d:MoveNext () (at ./Packages/ndmf/Editor/PreviewSystem/Rendering/ProxyPipeline.cs:234)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<nadena.dev.ndmf.preview.NodeController>:Start<nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d> (nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2/<<Build>b__5>d&)
nadena.dev.ndmf.preview.ProxyPipeline/<>c__DisplayClass22_2:<Build>b__5 (System.Threading.Tasks.Task`1<System.ValueTuple`3<UnityEngine.Renderer, nadena.dev.ndmf.preview.ProxyObjectController, nadena.dev.ndmf.ObjectRegistry>[]>)
UnityEngine.UnitySynchronizationContext:ExecuteTasks () (at /Users/bokken/build/output/unity/unity/Runtime/Export/Scripting/UnitySynchronizationContext.cs:107)
anatawa12 commented 3 weeks ago

今試したところ、windowsでも再現しました。

context.Observeでextractを指定していないIRenderFilterがある必要がありそうです

Windowsで確認するために使ったコードです。GUIDとfileIDをRead Write mesh offなメッシュにしてください。(この例ではあのんちゃんになってます)

static class IsInPlayerLoop
{
    private static Mesh? _mesh;
    private static System.Reflection.PropertyInfo? _propertyInfo;
    public static bool IsPlayerLoop() {
        if (_mesh == null)
        {
            // GUID and fileID of a read write mesh off mesh here
            var guid = "b90ae694f8009d949934a1419170452e";
            var fileID = 13727694889372365607;
            UnityEditor.GlobalObjectId.TryParse($"GlobalObjectId_V1-1-{guid}-{fileID}-0", out var globalObjectId);
            _mesh = (Mesh)UnityEditor.GlobalObjectId.GlobalObjectIdentifierToObjectSlow(globalObjectId);
        }
        if (_propertyInfo == null)
        {
            _propertyInfo = typeof(Mesh).GetProperty("canAccess", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
        }
        var canAccess = (bool)_propertyInfo.GetValue(_mesh)!;
        return !canAccess;
    }
}
anatawa12 commented 3 weeks ago

context.Observeでextractを指定していないIRenderFilterがある必要がありそうです

こちらはなくても、シーン上の動作を下にしたIRenderFilterの更新がある場合に発生することがありそうです(on macos with RemoveMesh in Boxのpreviewで確認)

bdunderscore commented 3 weeks ago

再現できました。どうやらUnityのデフォルトSynchronizationContextがPlayerLoop扱いになるため、TaskThrottleが起動するとPlayerLoopに移ってしまうようです。NDMF専用のSynchronizationContextで対応します。