Search Unity

Callback for destroy/dispose?

Discussion in 'UI Toolkit' started by Foxaphantsum, Mar 30, 2020.

  1. Foxaphantsum

    Foxaphantsum

    Joined:
    Jul 5, 2013
    Posts:
    139
    Is there a callback or a function we can overwrite if a UIElement is destroyed?

    A use case for this would be wrapping a PreviewRenderUtility and IMGUIContainer together into a single element called PreviewRenderElement.

    I've gotten this working mostly fine however within UI Builder I get issues with unity needing me to Cleanup the render Utility when the elements preview is destroyed.

    If I could access some sorta dispose callback/override I could clean up this issue.
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Register for the
    DetachFromPanelEvent
    . It will be called when an element is removed from the Panel's hierarchy (which is how elements are "destroyed").
     
  3. Foxaphantsum

    Foxaphantsum

    Joined:
    Jul 5, 2013
    Posts:
    139
    I've had no issues until recently with this. Or at least I think its an issue related to this.

    I've created a Custom C# UIElement called PreviewUtilElement which inherits from IMGUIContainer, I then use that to render a PreviewRenderUtility within the onGUIHandler event, and dispose of it in DetachFromPanelEvent (as well as within my editor window in OnDisable)

    This works 100% perfectly fine... until I add another PreviewUtilElement. Then on occasion it creates the error telling me.

    A PreviewRenderUtility was not clean up properly before assembly reloading which lead to leaking this scene in the Editor. This can be caused by not calling Cleanup() during the OnDisable of an Editor or an EditorWindow.
    UnityEditor.PreviewRenderUtility:Finalize()


    This only displays after closing my window, but after a delay. I feel like some sort of delayed call is happening. Its especially strange that this error only applies once despite having two of these PreviewUtils up, as if its only happening on the second window.

    There are two PreviewUtils so it should be calling twice as they both use the same way of Cleaning up after DetachFromPanelEvent, except only 1 of them seems to be getting called correctly.


    Edit*

    Even trying it from the root doesn't seem to work if there is more then 1 (I was doing it per IMGUIContainer before)


    Code (CSharp):
    1.        root.RegisterCallback<DetachFromPanelEvent>((evt)=>
    2.             {
    3.                 _meshPreviewElement.PreviewUtilty.Cleanup();
    4.                 _texturePreviewElement.PreviewUtilty.Cleanup();
    5.             });
    Edit again*

    Well now it doesn't seem to be working correctly period..even with just 1... hmmm
     
    Last edited: Apr 2, 2020
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    There are known issues with reusing the IMGUI Previews. They don't like to be kept around or have multiple of them open. But this starts to be more of an Editor core or IMGUI issue than a UI Toolkit (formally UIElements) question. I would recommend trying to get them to work outside UI Toolkit - simulating the way they are created/destroyed. If you can repro the problems without UI Toolkit, you can try the IMGUI forum.
     
  5. Foxaphantsum

    Foxaphantsum

    Joined:
    Jul 5, 2013
    Posts:
    139
    So I think it has to do with UIToolkits callback order or something.

    I say this because I experimented manually adding a RenderPreviewUtlity to a IMGContainer, and calling Cleanup in the OnDisable.

    The result was no errors at all, only when I'd try to wrap it with my Custom C# UI Element


    I did this for two different IMGContainers as demonstrated here for this editor window.

    Code (CSharp):
    1. public class TestWindow: EditorWindow
    2.     {
    3.  
    4.         private IMGUIContainer _imguiContainer;
    5.         private IMGUIContainer _imguiContainer2;
    6.  
    7.         private PreviewRenderUtility _previewUtil;
    8.         private PreviewRenderUtility _previewUtil2;
    9.  
    10.         private Rect _rect = new Rect(){width = 100, height = 100};
    11.  
    12.         [MenuItem("Window/TestWindow")]
    13.         static void OpenWindow()
    14.         {
    15.             GetWindow().Show();
    16.         }
    17.      
    18.         private static TestWindow GetWindow()
    19.         {
    20.             var window = GetWindow<TestWindow>();
    21.             window.titleContent = new GUIContent("Test Painter");
    22.             window.minSize = new Vector2(300,200);
    23.             return window;
    24.         }
    25.      
    26.  
    27.         private void OnEnable()
    28.         {
    29.             SetupElements();
    30.         }
    31.  
    32.         private void SetupElements()
    33.         {
    34.             var root = rootVisualElement;
    35.             var visualTree = Resources.Load<VisualTreeAsset>("UI/Test/TestUI");
    36.             if (visualTree == null) return;
    37.             visualTree.CloneTree(root);
    38.          
    39.             _imguiContainer = root.Q<IMGUIContainer>("renderPreview");//.StartRender();
    40.             _imguiContainer.RegisterCallback<GeometryChangedEvent>(OnGeometryChangedEvent);
    41.             _imguiContainer.onGUIHandler = DrawMeshPreviewUtility;
    42.          
    43.             _imguiContainer2 = root.Q<IMGUIContainer>("renderPreview2");//.StartRender();
    44.             _imguiContainer2.RegisterCallback<GeometryChangedEvent>(OnGeometryChangedEvent);
    45.             _imguiContainer2.onGUIHandler = DrawMeshPreviewUtility2;
    46.  
    47.         }
    48.      
    49.         void OnGeometryChangedEvent(GeometryChangedEvent evt)
    50.         {
    51.             var rect = evt.newRect;
    52.             rect.x = 0;
    53.             rect.y = 0;
    54.             _rect = new Rect(rect);
    55.         }
    56.      
    57.         private void DrawMeshPreviewUtility()
    58.         {
    59.             if (_rect.width <= 0 || _rect.height <= 0) return;
    60.             EditorGUI.BeginChangeCheck();
    61.             if (_previewUtil == null) {
    62.                 _previewUtil = new PreviewRenderUtility();
    63.                 _previewUtil.camera.clearFlags = CameraClearFlags.Skybox;
    64.                 //_meshPreviewUtil.camera.rect = new Rect(new Vector2(0,0), new Vector2(0.5f,0.5f));
    65.                 //_meshPreviewUtility.camera.cullingMask = PreviewCameraCullingMask;
    66.                 _previewUtil.camera.nearClipPlane = 0.01f;
    67.                 _previewUtil.camera.farClipPlane  = 10000.0f;
    68.                 _previewUtil.camera.fieldOfView   = 60.0f;
    69.                 _previewUtil.camera.transform.position = new Vector3(0,0,-10);
    70.              
    71.                 _previewUtil.lights[0].intensity = 1.1f;
    72.                 _previewUtil.lights[0].transform.rotation = Quaternion.Euler(41.687f, -25.491f ,6.266f);
    73.                 _previewUtil.lights[0].color=new Color(0.914f,0.906f,0.78f );
    74.                 _previewUtil.lights[1].intensity = 0;
    75.  
    76.                 _previewUtil.ambientColor = new Color(.45f, .37f, .76f);
    77.              
    78.                 var skybox = _previewUtil.camera.gameObject.AddComponent<Skybox>();
    79.                 skybox.material = RenderSettings.skybox;
    80.  
    81.  
    82.                 var mesh = GameObject.CreatePrimitive(PrimitiveType.Quad);
    83.                 mesh.transform.position = new Vector3(0,0, 0);
    84.                 _previewUtil.AddSingleGO(mesh);
    85.              
    86.              
    87.                 // _meshPreviewUtil.
    88.             }
    89.             _previewUtil.BeginPreview(_rect, GUIStyle.none);
    90.             if (EditorGUI.EndChangeCheck()) {
    91.                 SceneView.RepaintAll();
    92.             }
    93.             _previewUtil.Render();
    94.             _previewUtil.EndAndDrawPreview(_rect);
    95.  
    96.         }
    97.      
    98.         private void DrawMeshPreviewUtility2()
    99.         {
    100.             if (_rect.width <= 0 || _rect.height <= 0) return;
    101.             EditorGUI.BeginChangeCheck();
    102.             if (_previewUtil2 == null) {
    103.                 _previewUtil2 = new PreviewRenderUtility();
    104.                 _previewUtil2.camera.clearFlags = CameraClearFlags.Nothing;
    105.                 //_meshPreviewUtil.camera.rect = new Rect(new Vector2(0,0), new Vector2(0.5f,0.5f));
    106.                 //_meshPreviewUtility.camera.cullingMask = PreviewCameraCullingMask;
    107.                 _previewUtil2.camera.nearClipPlane = 0.01f;
    108.                 _previewUtil2.camera.farClipPlane  = 10000.0f;
    109.                 _previewUtil2.camera.fieldOfView   = 60.0f;
    110.                 _previewUtil2.camera.transform.position = new Vector3(0,0,-10);
    111.              
    112.                 _previewUtil2.lights[0].intensity = 1.1f;
    113.                 _previewUtil2.lights[0].transform.rotation = Quaternion.Euler(41.687f, -25.491f ,6.266f);
    114.                 _previewUtil2.lights[0].color=new Color(0.914f,0.906f,0.78f );
    115.                 _previewUtil2.lights[1].intensity = 0;
    116.  
    117.                 _previewUtil2.ambientColor = new Color(.45f, .37f, .76f);
    118.              
    119.                 var skybox = _previewUtil2.camera.gameObject.AddComponent<Skybox>();
    120.                 skybox.material = RenderSettings.skybox;
    121.              
    122.              
    123.                 var mesh = GameObject.CreatePrimitive(PrimitiveType.Quad);
    124.                 mesh.transform.position = new Vector3(0,0, 0);
    125.                 _previewUtil2.AddSingleGO(mesh);
    126.                 // _meshPreviewUtil.
    127.             }
    128.             _previewUtil2.BeginPreview(_rect, GUIStyle.none);
    129.             if (EditorGUI.EndChangeCheck()) {
    130.                 SceneView.RepaintAll();
    131.             }
    132.             _previewUtil2.Render();
    133.             _previewUtil2.EndAndDrawPreview(_rect);
    134.  
    135.         }
    136.  
    137.         private void Update()
    138.         {
    139.             _imguiContainer?.MarkDirtyRepaint();
    140.             _imguiContainer2?.MarkDirtyRepaint();
    141.  
    142.         }
    143.  
    144.  
    145.         private void OnDisable()
    146.         {
    147.             _previewUtil?.Cleanup();
    148.             _previewUtil2?.Cleanup();
    149.  
    150.         }
    151.     }

    However when I'd try to combine the IMGUIContainer and the PreviewUtility into 1 Custom C# Element like this


    Code (CSharp):
    1.  public class PreviewUtil: IMGUIContainer
    2.     {
    3.         public new class UxmlFactory : UxmlFactory<PreviewUtil>{}
    4.      
    5.         public Scene Scene
    6.         {
    7.             get
    8.             {
    9.                 if (_previewUtil != null) {
    10.                     _scene = _previewUtil.camera.scene;
    11.                 }
    12.                 return _scene;
    13.             }
    14.         }
    15.  
    16.         public PreviewRenderUtility PreviewUtilty
    17.         {
    18.             get => _previewUtil;
    19.             set => _previewUtil = value;
    20.         }
    21.  
    22.         private Scene _scene;
    23.         private PreviewRenderUtility _previewUtil;
    24.         private Rect _rect = new Rect(){width = 100, height = 100};
    25.         private float _zoom = 10f;
    26.  
    27.         private PreviewUtilManipulator _previewUtilManipulator;
    28.      
    29.          public PreviewUtil()
    30.          {
    31.    
    32.  
    33.              if (_previewUtil == null) {
    34.              
    35.                  _previewUtil = new PreviewRenderUtility();
    36.                  _previewUtil.camera.clearFlags = CameraClearFlags.Skybox;
    37.  
    38.                  _previewUtil.camera.nearClipPlane = 0.01f;
    39.                  _previewUtil.camera.farClipPlane  = 10000.0f;
    40.                  _previewUtil.camera.fieldOfView   = 60.0f;
    41.                  _previewUtil.camera.transform.position = new Vector3(0,0,-_zoom);
    42.              
    43.                  _previewUtil.lights[0].intensity = 1.1f;
    44.                  _previewUtil.lights[0].transform.rotation = Quaternion.Euler(41.687f, -25.491f ,6.266f);
    45.                  _previewUtil.lights[0].color=new Color(0.914f,0.906f,0.78f );
    46.                  _previewUtil.lights[1].intensity = 0;
    47.  
    48.                  _previewUtil.ambientColor = new Color(.45f, .37f, .76f);
    49.              
    50.                  var skybox = _previewUtil.camera.gameObject.AddComponent<Skybox>();
    51.                  skybox.material = RenderSettings.skybox;
    52.              }
    53.          
    54.              RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
    55.  
    56.              onGUIHandler = DrawMeshPreviewUtility;;
    57.  
    58.          }
    59.          void OnGeometryChangedEvent(GeometryChangedEvent evt)
    60.          {
    61.              var rect = evt.newRect;
    62.              rect.x = 0;
    63.              rect.y = 0;
    64.              _rect = new Rect(rect);
    65.          }
    66.  
    67.          private void DrawMeshPreviewUtility()
    68.          {
    69.              if (_rect.width <= 0 || _rect.height <= 0) return;
    70.             EditorGUI.BeginChangeCheck();
    71.             _previewUtil.BeginPreview(_rect, GUIStyle.none);
    72.             if (EditorGUI.EndChangeCheck()) {
    73.                 SceneView.RepaintAll();
    74.             }
    75.             _previewUtil.Render();
    76.             _previewUtil.EndAndDrawPreview(_rect);
    77.  
    78.         }
    79.      
    80.      
    81.          public void Cleanup()
    82.          {
    83.              _previewUtil?.Cleanup();
    84.          }
    85.  
    86.          private void OnDetachFromPanel(DetachFromPanelEvent evt)
    87.          {
    88.                  Cleanup();
    89.          }
    90.     }
    I get errors.

    I'm not exactly sure why doing it this way is causing issues.

    I noticed that IMGUIContainer has a dispose method, I'm unsure if thats possibly related somehow hmmm
     
  6. Foxaphantsum

    Foxaphantsum

    Joined:
    Jul 5, 2013
    Posts:
    139
    Edit*

    After further testing it seems inconsistent. I was able to get the error to stop happening in a branch of my project after messing around with re-making the uxml from scratch again in builder. I feel like custom C# UIElements don't have changes applied all the time so you need to delete and re-add them for some changes to happen.

    This comes back to my previous point that I don't get this issue when I bind directly to imguicontainers rather then inherit them in a custom c# element

    I'm curious if the root of the issue is there.

    Also I'd like to point out that the detach event doesn't seem to call when previewing an uxml in the inspector so it calls the error for the PreviewRenderUtility. Also the text in the preview looks off

     
    Last edited: Apr 4, 2020