Search code examples
pythonmemorywidgetpyside6unreal-engine5

Unreal Pyside6 widgets wont get removed by garbage collector


I am unable to remove PySide6 widgets in Unreal 5 python. Will describe the code first:

I have my ArtToolsUI class inheriting from QMainWindow. In this class I set some basic stuff like groupboxes, layouts etc.:

class ArtToolsUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self._app = self._get_qt_app()
        self.resize(600,400)
        self.editor_tools_buttons = [] #for showcase this has no widgets in it
        self._main_widget = self._set_layouts()


    def _get_qt_app(self):
        qt_app = QApplication.instance()
        qt_app.setQuitOnLastWindowClosed(True)
        return qt_app


    def get_v_layout(widgets):
        widgets = make_list(widgets)
        layout = QVBoxLayout()
        layout.setObjectName("vert_layout")
        for widget in widgets:
            layout.addWidget(widget)
    
        return layout

    
    def _set_layouts(self):
        default_grpbox_names = {"tools":self.editor_tools_buttons}
        all_grp_boxes = []
                
        for key in default_grpbox_names:
            layout = get_v_layout(default_grpbox_names[key]) # right now this just returns empty QVBoxLayout
            new_grp_box = QGroupBox(title=key)
            new_grp_box.setAttribute(Qt.WA_DeleteOnClose, True)
            new_grp_box.setLayout(layout)
            all_grp_boxes.append(new_grp_box)
        
        main_layout = get_v_layout(all_grp_boxes)

        main_widget = QWidget()
        main_widget.setObjectName("tat_main_widget")
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)
        
        return main_widget

I also implemented function for removing all widgets in this unreal python session. This method is called from different module.

def _remove_widgets():
    qt_app = QApplication.instance()
    
    if qt_app != None:
        all_qt_widgets = qt_app.allWidgets()
    
        for widget in all_qt_widgets:
            widget.setParent(None)
            widget.close()
            widget.deleteLater()
            

This _remove_widgets() method goes through all existing widgets, which should be destroyed, but for some reason only my ArtToolsUI main window is getting destroyed and all other widgets are still hanging around in the memory. On the other side when I manually click on the X button to close the window it closes everything.

Does anyone know what might be the problem?


Solution

  • Ok I think I figured it out.

    When running PySide6 on unreal you actualy cannot start your own QT event loop ( if you do, Unreal Editor will freeze and wait till you end your window). QT somehow finds unreal GUI event loop, which is responsible for everything .. except answering deleteLater().

    Only two ways ( I found) for deleting UI are:

    1. When user clicks on X button to end the Window -> This automatically ends and removes all widgets in that window.
    2. When calling QApplication.closeAllWindows() -> This calls all windows, but definitelly better than if they stay in memory.

    Both ways seem to bypass deleteLater() functionality, as documentation for deleteLater() says:

    The object will be deleted when control returns to the event loop.

    At the end I ended up with this functionality:

    1. I am not deleting dialogs, I just hide them on close().
    2. Then when creating them I ask if there are dialogs with my specific object name (object name is class property at every window I have)

    This will prevent spawning multiple widgets ( 10k widgets is aprox. 1.2GB RAM)

    ADD: There is also possibility to use self.setAttribute(Qt.WA_DeleteOnClose, True) on closing. This will delete dialog from memory as well.