From 6d3cd7d157c4df27fa968ca664509cd145b91fb6 Mon Sep 17 00:00:00 2001 From: Dominic Bytes <305052051+dominicbytes@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:57:37 -0500 Subject: [PATCH 1/3] Fix WorldScape editor shutdown child cleanup --- modules/worldscape_3d/editor/menu.cpp | 6 --- modules/worldscape_3d/editor/menu.h | 1 - modules/worldscape_3d/editor/menu/baker.cpp | 9 ---- modules/worldscape_3d/editor/menu/baker.h | 2 - .../editor/menu/channel_packer.cpp | 32 ------------ .../editor/menu/channel_packer.h | 1 - .../editor/menu/directory_setup.cpp | 12 ----- .../editor/menu/directory_setup.h | 1 - .../editor/ui/worldscape_3d_asset_dock.cpp | 22 --------- .../editor/ui/worldscape_3d_asset_dock.h | 2 - .../editor/ui/worldscape_3d_tools.cpp | 10 ---- .../editor/ui/worldscape_3d_tools.h | 1 - .../editor/ui/worldscape_3d_ui.cpp | 3 -- .../editor/worldscape_3d_editor.cpp | 1 - tests/editor/test_shutdown.py | 49 +++++++++++++++++++ 15 files changed, 49 insertions(+), 103 deletions(-) create mode 100644 tests/editor/test_shutdown.py diff --git a/modules/worldscape_3d/editor/menu.cpp b/modules/worldscape_3d/editor/menu.cpp index 513656226b5..d86b49e35c5 100644 --- a/modules/worldscape_3d/editor/menu.cpp +++ b/modules/worldscape_3d/editor/menu.cpp @@ -66,12 +66,6 @@ WorldScape3DMenu::WorldScape3DMenu(WorldScape3DEditorPlugin *plugin) : p->connect("id_pressed", callable_mp(this, &WorldScape3DMenu::on_menu_entry)); } -WorldScape3DMenu::~WorldScape3DMenu() { - _baker->queue_free(); - _packer_dialog->queue_free(); - _dir_setup->queue_free(); -} - void WorldScape3DMenu::pressed() { on_menu_about_to_popup(); MenuButton::pressed(); diff --git a/modules/worldscape_3d/editor/menu.h b/modules/worldscape_3d/editor/menu.h index 58366856532..4c0b6af8dd6 100644 --- a/modules/worldscape_3d/editor/menu.h +++ b/modules/worldscape_3d/editor/menu.h @@ -63,7 +63,6 @@ class WorldScape3DMenu final : public MenuButton { }; explicit WorldScape3DMenu(WorldScape3DEditorPlugin *plugin); - ~WorldScape3DMenu() override; void pressed() override; diff --git a/modules/worldscape_3d/editor/menu/baker.cpp b/modules/worldscape_3d/editor/menu/baker.cpp index 051a273eb61..815fe510642 100644 --- a/modules/worldscape_3d/editor/menu/baker.cpp +++ b/modules/worldscape_3d/editor/menu/baker.cpp @@ -278,15 +278,6 @@ void BakerLODDialog::on_lod_box_value_changed(real_t) { _lod = static_cast(_lodbox->get_value()); } -BakerLODDialog::~BakerLODDialog() { - _description_label->queue_free(); - _lodbox->queue_free(); - _label->queue_free(); - _hbox->queue_free(); - _vbox->queue_free(); - _margin->queue_free(); -} - void BakerLODDialog::_notification(int what) { if (what == NOTIFICATION_POSTINITIALIZE) { init(); diff --git a/modules/worldscape_3d/editor/menu/baker.h b/modules/worldscape_3d/editor/menu/baker.h index bd59a4ab15f..c0893133678 100644 --- a/modules/worldscape_3d/editor/menu/baker.h +++ b/modules/worldscape_3d/editor/menu/baker.h @@ -64,8 +64,6 @@ class BakerLODDialog final : public ConfirmationDialog { void on_lod_box_value_changed(real_t value); public: - ~BakerLODDialog() override; - void set_description(const String &description) { _description = description; } int get_lod() const { return _lod; } diff --git a/modules/worldscape_3d/editor/menu/channel_packer.cpp b/modules/worldscape_3d/editor/menu/channel_packer.cpp index e7903eca07c..edd351d1e44 100644 --- a/modules/worldscape_3d/editor/menu/channel_packer.cpp +++ b/modules/worldscape_3d/editor/menu/channel_packer.cpp @@ -132,11 +132,6 @@ class ChannelSelector final : public HBoxContainer { add_child(_option); } - ~ChannelSelector() override { - _option->queue_free(); - _label->queue_free(); - } - void set_channels(int used_channels) { int channel_count = 4; switch (used_channels) { @@ -420,21 +415,6 @@ class ChannelPanel final : public PanelContainer { setup_layout(); } - ~ChannelPanel() override { - _height_label->queue_free(); - _width_label->queue_free(); - _wh_hbox->queue_free(); - _margin->queue_free(); - _texture_button->queue_free(); - _preview->queue_free(); - _preview_panel->queue_free(); - _pick_btn->queue_free(); - _path_edit->queue_free(); - _label->queue_free(); - _outer_hbox->queue_free(); - _vbox->queue_free(); - } - void add(Control *control) { if (_vbox2) { _vbox2->add_child(control); @@ -654,18 +634,6 @@ ChannelPackerDialog::ChannelPackerDialog() { init_file_dialog(); } -ChannelPackerDialog::~ChannelPackerDialog() { - _general_options_panel->queue_free(); - _roughness_panel->queue_free(); - _normal_panel->queue_free(); - _bottom_hbox->queue_free(); - _height_panel->queue_free(); - _albedo_panel->queue_free(); - _top_hbox->queue_free(); - _vbox->queue_free(); - _margin->queue_free(); -} - void ChannelPackerDialog::_notification(const int what) { if (what == NOTIFICATION_POSTINITIALIZE) { init(); diff --git a/modules/worldscape_3d/editor/menu/channel_packer.h b/modules/worldscape_3d/editor/menu/channel_packer.h index 57ee86f0034..ff65912a88b 100644 --- a/modules/worldscape_3d/editor/menu/channel_packer.h +++ b/modules/worldscape_3d/editor/menu/channel_packer.h @@ -123,7 +123,6 @@ class ChannelPackerDialog : public AcceptDialog { public: ChannelPackerDialog(); - ~ChannelPackerDialog() override; void _notification(int what); diff --git a/modules/worldscape_3d/editor/menu/directory_setup.cpp b/modules/worldscape_3d/editor/menu/directory_setup.cpp index 92bfbbc2dc8..f717b0de7d0 100644 --- a/modules/worldscape_3d/editor/menu/directory_setup.cpp +++ b/modules/worldscape_3d/editor/menu/directory_setup.cpp @@ -104,17 +104,6 @@ DirectorySetupDialog::DirectorySetupDialog() { ConfirmationDialog::set_visible(true); } -DirectorySetupDialog::~DirectorySetupDialog() { - _spacer->queue_free(); - _select_dir_btn->queue_free(); - _select_dir_le->queue_free(); - _dir_hbox->queue_free(); - _dir_label->queue_free(); - _instructions->queue_free(); - _vbox->queue_free(); - _margin->queue_free(); -} - void DirectorySetup::setup_layout() { _file_dialog->set_filters({ "*.res" }); _file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); @@ -144,7 +133,6 @@ DirectorySetup::DirectorySetup(WorldScape3DEditorPlugin *plugin) : } DirectorySetup::~DirectorySetup() { - _file_dialog->queue_free(); _dialog->queue_free(); } diff --git a/modules/worldscape_3d/editor/menu/directory_setup.h b/modules/worldscape_3d/editor/menu/directory_setup.h index 678aae4071f..e81e342b8ab 100644 --- a/modules/worldscape_3d/editor/menu/directory_setup.h +++ b/modules/worldscape_3d/editor/menu/directory_setup.h @@ -65,7 +65,6 @@ class DirectorySetupDialog final : public ConfirmationDialog { public: DirectorySetupDialog(); - ~DirectorySetupDialog() override; Button *get_dir_button() const { return _select_dir_btn; } LineEdit *get_dir_edit() const { return _select_dir_le; } diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp index bf4e40d43ae..f90fd421831 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp @@ -499,10 +499,6 @@ ListContainer::ListContainer(WorldScape3DEditorPlugin *plugin) : set_h_size_flags(SIZE_EXPAND_FILL); } -ListContainer::~ListContainer() { - clear(); -} - void ListContainer::clear() { for (auto *e : _entries) { e->queue_free(); @@ -950,24 +946,6 @@ WorldScape3DAssetDock::WorldScape3DAssetDock(WorldScape3DEditorPlugin *plugin) : _initialized = true; } -WorldScape3DAssetDock::~WorldScape3DAssetDock() { - _mesh_list->clear(); - _mesh_list->queue_free(); - _texture_list->clear(); - _texture_list->queue_free(); - - _placement_opt->queue_free(); - _floating_btn->queue_free(); - _pinned_btn->queue_free(); - _size_slider->queue_free(); - _box->queue_free(); - _buttons->queue_free(); - _textures_btn->queue_free(); - _meshes_btn->queue_free(); - _asset_container->queue_free(); - _confirm_dialog->queue_free(); -} - // Dock placement void WorldScape3DAssetDock::set_slot(const int slot) { diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h index 095da678a04..7cabc796886 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h @@ -148,7 +148,6 @@ class ListContainer : public Container { public: explicit ListContainer(WorldScape3DEditorPlugin *plugin); - ~ListContainer() override; void redraw(); void clear(); @@ -245,7 +244,6 @@ class WorldScape3DAssetDock final : public PanelContainer { public: explicit WorldScape3DAssetDock(WorldScape3DEditorPlugin *plugin); - ~WorldScape3DAssetDock() override; ListContainer *get_current_list() const { return _current_list; } ConfirmationDialog *get_confirmation_dialog() const { return _confirm_dialog; } diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp index 00fecf84e56..2274e9be240 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp @@ -101,16 +101,6 @@ constexpr int NO_SAVE = 0x20; // Don't save this in EditorSettings WorldScape3DToolSettings::WorldScape3DToolSettings(WorldScape3DEditorPlugin *plugin) : _plugin{ plugin } {} -WorldScape3DToolSettings::~WorldScape3DToolSettings() { - _select_brush_button->queue_free(); - _scale_list->queue_free(); - _color_list->queue_free(); - _rotation_list->queue_free(); - _height_list->queue_free(); - _advanced_list->queue_free(); - _main_list->queue_free(); -} - class ToolSubMenu : public PopupPanel { GDCLASS(ToolSubMenu, PopupPanel) diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.h b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.h index 047eb266510..737db2d600d 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.h +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.h @@ -81,7 +81,6 @@ class WorldScape3DToolSettings : public PanelContainer { }; explicit WorldScape3DToolSettings(WorldScape3DEditorPlugin *plugin); - ~WorldScape3DToolSettings() override; void on_show_submenu(bool toggled, Button *button); diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp index 201451c1000..3881522f912 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp @@ -309,9 +309,6 @@ WorldScape3DUI::~WorldScape3DUI() { _plugin->remove_control_from_container(CONTAINER_SPATIAL_EDITOR_BOTTOM, _tool_settings); _plugin->remove_control_from_container(CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, _toolbar); } - if (_editor_decal_timer) { - _editor_decal_timer->queue_free(); - } if (_toolbar) { _toolbar->queue_free(); } diff --git a/modules/worldscape_3d/editor/worldscape_3d_editor.cpp b/modules/worldscape_3d/editor/worldscape_3d_editor.cpp index 8b304d9d242..65879f678d2 100644 --- a/modules/worldscape_3d/editor/worldscape_3d_editor.cpp +++ b/modules/worldscape_3d/editor/worldscape_3d_editor.cpp @@ -927,7 +927,6 @@ WorldScape3DEditorPlugin::WorldScape3DEditorPlugin() : WorldScape3DEditorPlugin::~WorldScape3DEditorPlugin() { _asset_dock->queue_free(); - _ui->queue_free(); if (_rex_editor_window && _rex_editor_window->is_connected("focus_entered", callable_mp(this, &WorldScape3DEditorPlugin::on_focus_entered))) { _rex_editor_window->disconnect("focus_entered", callable_mp(this, &WorldScape3DEditorPlugin::on_focus_entered)); diff --git a/tests/editor/test_shutdown.py b/tests/editor/test_shutdown.py new file mode 100644 index 00000000000..ffff8edee98 --- /dev/null +++ b/tests/editor/test_shutdown.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Check editor shutdown using a built editor: python test_shutdown.py .""" + +import argparse +import subprocess +import tempfile +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("editor", type=Path) + args = parser.parse_args() + editor = args.editor.resolve(strict=True) + + with tempfile.TemporaryDirectory(prefix="redot editor shutdown ") as directory: + project = Path(directory) + (project / "project.godot").write_text( + 'config_version=5\n[application]\nconfig/name="Editor shutdown regression"\n', + encoding="utf-8", + ) + for frames in (10, 120, 600): + command = [ + str(editor), + "--headless", + "--editor", + "--path", + str(project), + "--quit-after", + str(frames), + "--max-fps", + "60", + ] + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=60) + except subprocess.TimeoutExpired: + print(f"FAIL: editor shutdown timed out after {frames} frames") + return 1 + if result.returncode != 0 or result.stderr.strip(): + print(f"FAIL: editor shutdown after {frames} frames (exit {result.returncode})") + print(result.stdout) + print(result.stderr) + return 1 + print(f"PASS: editor shutdown after {frames} frames") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 8400c06f16561bc213dd1fe9d8ff38f488888b61 Mon Sep 17 00:00:00 2001 From: Dominic Bytes <305052051+dominicbytes@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:39:40 -0500 Subject: [PATCH 2/3] Make shutdown test exit handling explicit --- tests/editor/test_shutdown.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/editor/test_shutdown.py b/tests/editor/test_shutdown.py index ffff8edee98..278c97fc0df 100644 --- a/tests/editor/test_shutdown.py +++ b/tests/editor/test_shutdown.py @@ -8,6 +8,7 @@ def main() -> int: + """Check clean headless editor exits at three frame limits in a fresh project.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("editor", type=Path) args = parser.parse_args() @@ -32,7 +33,7 @@ def main() -> int: "60", ] try: - result = subprocess.run(command, capture_output=True, text=True, timeout=60) + result = subprocess.run(command, capture_output=True, text=True, timeout=60, check=False) except subprocess.TimeoutExpired: print(f"FAIL: editor shutdown timed out after {frames} frames") return 1 From 01d462edf22705725972f2cb60c82ce9d6b451f6 Mon Sep 17 00:00:00 2001 From: Dominic Bytes <305052051+dominicbytes@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:54:43 -0500 Subject: [PATCH 3/3] Release WorldScape resources before node teardown --- modules/worldscape_3d/editor/menu.cpp | 9 ++ modules/worldscape_3d/editor/menu.h | 1 + modules/worldscape_3d/editor/menu/baker.cpp | 7 +- .../editor/menu/channel_packer.cpp | 15 +++ .../editor/menu/directory_setup.cpp | 8 +- .../editor/menu/directory_setup.h | 1 + .../editor/ui/worldscape_3d_asset_dock.cpp | 42 ++++--- .../editor/ui/worldscape_3d_asset_dock.h | 1 - .../editor/ui/worldscape_3d_tools.cpp | 3 +- .../editor/ui/worldscape_3d_ui.cpp | 13 +- .../editor/worldscape_3d_editor.cpp | 11 +- .../worldscape_3d/tests/test_editor_cleanup.h | 115 ++++++++++++++++++ 12 files changed, 199 insertions(+), 27 deletions(-) create mode 100644 modules/worldscape_3d/tests/test_editor_cleanup.h diff --git a/modules/worldscape_3d/editor/menu.cpp b/modules/worldscape_3d/editor/menu.cpp index d86b49e35c5..18e6d4795ae 100644 --- a/modules/worldscape_3d/editor/menu.cpp +++ b/modules/worldscape_3d/editor/menu.cpp @@ -66,6 +66,15 @@ WorldScape3DMenu::WorldScape3DMenu(WorldScape3DEditorPlugin *plugin) : p->connect("id_pressed", callable_mp(this, &WorldScape3DMenu::on_menu_entry)); } +void WorldScape3DMenu::_notification(int what) { + if (what == NOTIFICATION_PREDELETE) { + // Child pointers are still valid here, before Node's predelete notification. + memdelete(_baker); + memdelete(_packer_dialog); + memdelete(_dir_setup); + } +} + void WorldScape3DMenu::pressed() { on_menu_about_to_popup(); MenuButton::pressed(); diff --git a/modules/worldscape_3d/editor/menu.h b/modules/worldscape_3d/editor/menu.h index 4c0b6af8dd6..785edb42c7f 100644 --- a/modules/worldscape_3d/editor/menu.h +++ b/modules/worldscape_3d/editor/menu.h @@ -63,6 +63,7 @@ class WorldScape3DMenu final : public MenuButton { }; explicit WorldScape3DMenu(WorldScape3DEditorPlugin *plugin); + void _notification(int what); void pressed() override; diff --git a/modules/worldscape_3d/editor/menu/baker.cpp b/modules/worldscape_3d/editor/menu/baker.cpp index 815fe510642..b948814c1c4 100644 --- a/modules/worldscape_3d/editor/menu/baker.cpp +++ b/modules/worldscape_3d/editor/menu/baker.cpp @@ -281,6 +281,8 @@ void BakerLODDialog::on_lod_box_value_changed(real_t) { void BakerLODDialog::_notification(int what) { if (what == NOTIFICATION_POSTINITIALIZE) { init(); + } else if (what == NOTIFICATION_PREDELETE) { + memdelete(_margin); } } @@ -554,8 +556,9 @@ Baker::Baker(WorldScape3DEditorPlugin *plugin) : } Baker::~Baker() { - _confirm_dlg->queue_free(); - _bake_lod_dlg->queue_free(); + // These dialogs are unparented while hidden, so Node cannot release them. + memdelete(_confirm_dlg); + memdelete(_bake_lod_dlg); } Vector Baker::find_nav_region_terrains(NavigationRegion3D *nav_region) const { diff --git a/modules/worldscape_3d/editor/menu/channel_packer.cpp b/modules/worldscape_3d/editor/menu/channel_packer.cpp index edd351d1e44..62bd4ff617e 100644 --- a/modules/worldscape_3d/editor/menu/channel_packer.cpp +++ b/modules/worldscape_3d/editor/menu/channel_packer.cpp @@ -132,6 +132,13 @@ class ChannelSelector final : public HBoxContainer { add_child(_option); } + void _notification(int what) { + if (what == NOTIFICATION_PREDELETE) { + memdelete(_option); + memdelete(_label); + } + } + void set_channels(int used_channels) { int channel_count = 4; switch (used_channels) { @@ -495,6 +502,11 @@ class ChannelPanel final : public PanelContainer { void _notification(const int what) { if (what == NOTIFICATION_ENTER_TREE) { init(); + } else if (what == NOTIFICATION_PREDELETE) { + memdelete(_outer_margin); + if (_open_file_dialog) { + memdelete(_open_file_dialog); + } } } }; @@ -637,6 +649,9 @@ ChannelPackerDialog::ChannelPackerDialog() { void ChannelPackerDialog::_notification(const int what) { if (what == NOTIFICATION_POSTINITIALIZE) { init(); + } else if (what == NOTIFICATION_PREDELETE) { + memdelete(_vbox); + memdelete(_margin); } } diff --git a/modules/worldscape_3d/editor/menu/directory_setup.cpp b/modules/worldscape_3d/editor/menu/directory_setup.cpp index f717b0de7d0..c1c205481ee 100644 --- a/modules/worldscape_3d/editor/menu/directory_setup.cpp +++ b/modules/worldscape_3d/editor/menu/directory_setup.cpp @@ -104,6 +104,12 @@ DirectorySetupDialog::DirectorySetupDialog() { ConfirmationDialog::set_visible(true); } +void DirectorySetupDialog::_notification(int what) { + if (what == NOTIFICATION_PREDELETE) { + memdelete(_margin); + } +} + void DirectorySetup::setup_layout() { _file_dialog->set_filters({ "*.res" }); _file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); @@ -133,7 +139,7 @@ DirectorySetup::DirectorySetup(WorldScape3DEditorPlugin *plugin) : } DirectorySetup::~DirectorySetup() { - _dialog->queue_free(); + memdelete(_dialog); } void DirectorySetup::directory_setup_popup() { diff --git a/modules/worldscape_3d/editor/menu/directory_setup.h b/modules/worldscape_3d/editor/menu/directory_setup.h index e81e342b8ab..7efe6289488 100644 --- a/modules/worldscape_3d/editor/menu/directory_setup.h +++ b/modules/worldscape_3d/editor/menu/directory_setup.h @@ -65,6 +65,7 @@ class DirectorySetupDialog final : public ConfirmationDialog { public: DirectorySetupDialog(); + void _notification(int what); Button *get_dir_button() const { return _select_dir_btn; } LineEdit *get_dir_edit() const { return _select_dir_le; } diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp index f90fd421831..dcef482e7ef 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp @@ -110,22 +110,6 @@ ListEntry::ListEntry(const WorldScape3DAssets::AssetType type) { _button_enabled = memnew(TextureButton); } -ListEntry::~ListEntry() { - if (_count_label) { - _count_label->queue_free(); - } - if (_name_label) { - _name_label->queue_free(); - } - _button_enabled->queue_free(); - _spacer->queue_free(); - _button_edit->queue_free(); - _button_clear->queue_free(); - _button_row->queue_free(); - _margin->queue_free(); - _label_rows->queue_free(); -} - void ListEntry::_bind_methods() { ADD_SIGNAL(MethodInfo("hovered")); ADD_SIGNAL(MethodInfo("selected")); @@ -135,6 +119,22 @@ void ListEntry::_bind_methods() { void ListEntry::_notification(int what) { switch (what) { + case NOTIFICATION_PREDELETE: + // Also release controls that have not been parented by init(). + if (_count_label) { + memdelete(_count_label); + } + if (_name_label) { + memdelete(_name_label); + } + memdelete(_button_enabled); + memdelete(_spacer); + memdelete(_button_edit); + memdelete(_button_clear); + memdelete(_button_row); + memdelete(_margin); + memdelete(_label_rows); + break; case NOTIFICATION_POST_ENTER_TREE: init(); break; @@ -715,6 +715,11 @@ void ListContainer::redraw() { void ListContainer::_notification(int what) { if (what == NOTIFICATION_SORT_CHILDREN) { redraw(); + } else if (what == NOTIFICATION_PREDELETE) { + for (ListEntry *entry : _entries) { + memdelete(entry); + } + _entries.clear(); } } @@ -1307,6 +1312,11 @@ void WorldScape3DAssetDock::_bind_methods() { void WorldScape3DAssetDock::_notification(int what) { if (what == NOTIFICATION_POSTINITIALIZE) { init(); + } else if (what == NOTIFICATION_PREDELETE) { + memdelete(_box); + if (_confirm_dialog) { + memdelete(_confirm_dialog); + } } } diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h index 7cabc796886..ec9707e8586 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h @@ -97,7 +97,6 @@ class ListEntry : public MarginContainer { public: explicit ListEntry(WorldScape3DAssets::AssetType type); - ~ListEntry() override; void set_assets(Ref assets_list) { _asset_list = assets_list; diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp index 2274e9be240..2175c8d3c66 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp @@ -630,9 +630,10 @@ void WorldScape3DToolSettings::_bind_methods() { } void WorldScape3DToolSettings::_notification(int what) { - PanelContainer::_notification(what); if (what == NOTIFICATION_POST_ENTER_TREE) { init(); + } else if (what == NOTIFICATION_PREDELETE && _main_list) { + memdelete(_main_list); } } diff --git a/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp b/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp index 3881522f912..b8500f9d024 100644 --- a/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp +++ b/modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp @@ -310,10 +310,10 @@ WorldScape3DUI::~WorldScape3DUI() { _plugin->remove_control_from_container(CONTAINER_SPATIAL_EDITOR_SIDE_LEFT, _toolbar); } if (_toolbar) { - _toolbar->queue_free(); + memdelete(_toolbar); } if (_menu) { - _menu->queue_free(); + memdelete(_menu); } if (_tool_settings) { if (_tool_settings->is_connected("setting_changed", callable_mp(this, &WorldScape3DUI::on_setting_changed))) { @@ -322,7 +322,7 @@ WorldScape3DUI::~WorldScape3DUI() { if (_tool_settings->is_connected("picking", callable_mp(_tool_settings, &WorldScape3DToolSettings::on_pick))) { _tool_settings->disconnect("picking", callable_mp(_tool_settings, &WorldScape3DToolSettings::on_pick)); } - _tool_settings->queue_free(); + memdelete(_tool_settings); } } @@ -938,8 +938,13 @@ void WorldScape3DUI::_bind_methods() { } void WorldScape3DUI::_notification(int what) { - Node::_notification(what); switch (what) { + case NOTIFICATION_PREDELETE: { + if (_editor_decal_timer) { + memdelete(_editor_decal_timer); + } + break; + } case NOTIFICATION_ENTER_TREE: { on_tool_changed(WorldScape3DEditor::Tool::REGION, WorldScape3DEditor::Operation::ADD); break; diff --git a/modules/worldscape_3d/editor/worldscape_3d_editor.cpp b/modules/worldscape_3d/editor/worldscape_3d_editor.cpp index 65879f678d2..db18ad7f228 100644 --- a/modules/worldscape_3d/editor/worldscape_3d_editor.cpp +++ b/modules/worldscape_3d/editor/worldscape_3d_editor.cpp @@ -926,8 +926,6 @@ WorldScape3DEditorPlugin::WorldScape3DEditorPlugin() : } WorldScape3DEditorPlugin::~WorldScape3DEditorPlugin() { - _asset_dock->queue_free(); - if (_rex_editor_window && _rex_editor_window->is_connected("focus_entered", callable_mp(this, &WorldScape3DEditorPlugin::on_focus_entered))) { _rex_editor_window->disconnect("focus_entered", callable_mp(this, &WorldScape3DEditorPlugin::on_focus_entered)); } @@ -1100,6 +1098,15 @@ void WorldScape3DEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_POST_ENTER_TREE) { init(); set_input_event_forwarding_always_enabled(); + } else if (p_what == NOTIFICATION_PREDELETE) { + if (_ui) { + memdelete(_ui); + _ui = nullptr; + } + if (_asset_dock) { + memdelete(_asset_dock); + _asset_dock = nullptr; + } } } diff --git a/modules/worldscape_3d/tests/test_editor_cleanup.h b/modules/worldscape_3d/tests/test_editor_cleanup.h new file mode 100644 index 00000000000..fb6700c5226 --- /dev/null +++ b/modules/worldscape_3d/tests/test_editor_cleanup.h @@ -0,0 +1,115 @@ +/**************************************************************************/ +/* test_editor_cleanup.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#ifdef TOOLS_ENABLED + +#include "modules/worldscape_3d/editor/menu/baker.h" +#include "modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.h" +#include "modules/worldscape_3d/editor/ui/worldscape_3d_tools.h" +#include "scene/resources/image_texture.h" +#include "tests/test_macros.h" + +namespace TestWorldScape3DEditor { + +TEST_CASE("[SceneTree][WorldScape3D] Brush material and shader are released") { + WorldScape3DToolSettings *settings = memnew(WorldScape3DToolSettings(nullptr)); + Ref material = settings->get_brush_preview_material(); + const ObjectID material_id = material->get_instance_id(); + const ObjectID shader_id = material->get_shader()->get_instance_id(); + material.unref(); + CHECK(ObjectDB::get_instance(material_id) != nullptr); + memdelete(settings); + CHECK(ObjectDB::get_instance(material_id) == nullptr); + CHECK(ObjectDB::get_instance(shader_id) == nullptr); +} + +TEST_CASE("[SceneTree][WorldScape3D] Asset entry controls are released synchronously") { + for (WorldScape3DAssets::AssetType type : { WorldScape3DAssets::TYPE_TEXTURE, WorldScape3DAssets::TYPE_MESH }) { + const int before = ObjectDB::get_object_count(); + ListEntry *entry = memnew(ListEntry(type)); + CHECK(ObjectDB::get_object_count() > before); + memdelete(entry); + CHECK(ObjectDB::get_object_count() == before); + } +} + +TEST_CASE("[SceneTree][WorldScape3D] Detached baker dialogs are released synchronously") { + // Warm the shared font cache used when the dialog's text is first shaped. + Baker *warmup = memnew(Baker(nullptr)); + memdelete(warmup); + const int before = ObjectDB::get_object_count(); + Baker *baker = memnew(Baker(nullptr)); + CHECK(ObjectDB::get_object_count() > before); + memdelete(baker); + CHECK(ObjectDB::get_object_count() == before); +} + +TEST_CASE("[SceneTree][WorldScape3D] Populated asset entries release their resources") { + for (bool enter_tree : { false, true }) { + for (WorldScape3DAssets::AssetType type : { WorldScape3DAssets::TYPE_TEXTURE, WorldScape3DAssets::TYPE_MESH }) { + ListEntry *entry = memnew(ListEntry(type)); + Ref asset; + ObjectID texture_id; + if (type == WorldScape3DAssets::TYPE_TEXTURE) { + Ref texture_asset; + texture_asset.instantiate(); + Ref texture = ImageTexture::create_from_image(Image::create_empty(4, 4, true, Image::FORMAT_RGBA8)); + texture_id = texture->get_instance_id(); + texture_asset->set_albedo_texture(texture); + asset = texture_asset; + } else { + asset = memnew(WorldScape3DMeshAsset); + } + const ObjectID asset_id = asset->get_instance_id(); + entry->set_edited_resource(asset); + asset.unref(); + if (enter_tree) { + SceneTree::get_singleton()->get_root()->add_child(entry); + } + CHECK(ObjectDB::get_instance(asset_id) != nullptr); + if (texture_id.is_valid()) { + CHECK(ObjectDB::get_instance(texture_id) != nullptr); + } + memdelete(entry); + CHECK(ObjectDB::get_instance(asset_id) == nullptr); + if (texture_id.is_valid()) { + CHECK(ObjectDB::get_instance(texture_id) == nullptr); + } + } + } +} + +} // namespace TestWorldScape3DEditor + +#endif // TOOLS_ENABLED