Tool mode script for instancing new nodes crashes on save (export Array issue?)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By moniker

I’m relatively new to Godot so I’m hoping this is just a simple mistake. I’m using version 3.3 (Linux 64 build). I want a series of nodes which each maintain a list of ‘related’ nodes. To make this easier I have written an editor ‘tool’ script, which is attached to each node, and can create new related nodes when I click a ‘button’ (exported bool var). This script just adds a node to the scene/tree and performs push_back() on an array of related nodes for the node doing the adding.

The problem is that after adding a few nodes with the tool, Godot editor will crash when I try to save. I assume I’m trying to access something in a way which is incorrect. In particular, if the array of related nodes isn’t exported (viewable in the editor), it doesn’t crash. Or if I don’t add the newly instanced node to an array it doesn’t crash. It also doesn’t crash if the array isn’t ‘exported’, so I think it could be to do with how exported arrays are handled by the engine?

Minimal code example of the tool here:

tool
extends Sprite

export var relatedList = [] #No crash if the array isn't exposed
export (bool) var createNewTool = false setget create_new #editor 'button' to create new instance

var sceneResource = load("res://MySprite.tscn")

func create_new(value):
	if Engine.is_editor_hint():
		#create new instance and add to scene/tree
		var newSprite = sceneResource.instance()
		get_parent().add_child(newSprite)
		newSprite.set_owner(get_tree().get_edited_scene_root())
		
		#update list of related nodes
		relatedList.push_back(newSprite)
		newSprite.relatedList.push_back(self)
		
		#update exported array values
		property_list_changed_notify()
		newSprite.property_list_changed_notify()

To recreate: Create a new project. Add Node2d. Add a Sprite to the Node2d. Attach the above script and save sprite as “MySprite.tscn”. Click the ‘createNewTool’ checkbox in the editor to create a new instance (this works fine). Now click the newly created MySprite and click the ‘createNewTool’ checkbox on that. Try to save (ctrl+s) and editor will crash.

Editor log doesn’t provide anything useful as far as I can tell:

Godot Engine v3.3.stable.official - https://godotengine.org OpenGL ES
3.0 Renderer: GeForce GTX 1060/PCIe/SSE2 OpenGL ES Batching: ON
WARNING: Font oversampling only works with the stretch modes “Keep Width”, “Keep Height” and “Expand”, not “Ignore”. To remove this
warning, disable Rendering > Quality > Dynamic Fonts > Use
Oversampling in the Project Settings. At:
scene/main/scene_tree.cpp:1161:_update_root_rect() - Font oversampling
only works with the stretch modes “Keep Width”, “Keep Height” and
“Expand”, not “Ignore”. To remove this warning, disable Rendering >
Quality > Dynamic Fonts > Use Oversampling in the Project Settings.

Any help greatly appreciated! :slight_smile:

:bust_in_silhouette: Reply From: Lopy

The Engine was crashing on a stack overflow. I suspect it was because it was looping in the Nodes to save because they all exported one another.
You had an exported Array which is meant to be unique, but you set it directly, making it shared by default.

I changed your code to make Nodes keep a numeric id, and keep links using only those. To get the Node using the id, I used a shared Dictionary (const) named siblings where all instances register themselves. Also added a delete button.

tool
extends Sprite

export var relatedList: Array #saved, not shared (see _init, would be with relatedList = [])
export var id := 0 #saved, not shared (as it is not reference passed)
const max_id := [0] #shared, not saved, the value (max_id[0]) only needs to be unique on creation, not accurate
const siblings := {} #shared, not saved
export (bool) var createNewTool = false setget create_new #editor 'button' to create new instance
export (bool) var delete = false setget delete #editor 'button' to cleanly delete self

var sceneResource = load("res://MySprite.tscn")


func _ready() -> void:
    if Engine.is_editor_hint():
        if id > max_id[0]: #keep the max updated when loading
            max_id[0] = id+1
        
        if !relatedList: #not loaded, newly created
            relatedList = []
            max_id[0] += 1
            id = max_id[0]
        
        siblings[id] = self
        printt(name, self, id, max_id, siblings)

func create_new(value):
    if Engine.is_editor_hint() and value and is_instance_valid(get_parent()):
        #create new instance and add to scene/tree
        var newSprite = sceneResource.instance()
        get_parent().add_child(newSprite)
        newSprite.set_owner(get_tree().get_edited_scene_root())

        #update list of related nodes
        relatedList.push_back(newSprite.id)
        newSprite.relatedList.push_back(id)

        #update exported array values
        property_list_changed_notify()
        newSprite.property_list_changed_notify()


func delete(value):
    if Engine.is_editor_hint() and value and is_instance_valid(get_parent()):
        siblings.erase(id)
        print(siblings)
        for related_id in relatedList:
            var related = siblings.get(related_id, null)
            if is_instance_valid(related):
                var list: Array= related.relatedList
                list.erase(id)
        queue_free()

The Editor crashing is not normal, even if you tell it to do weird things. I recorded the crash under valgrind, here is the relevant section:

==62415== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==62415== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==62415== Can't extend stack to 0x1ffe801078 during signal delivery for thread 1:
==62415==   no stack segment
==62415== 
==62415== Process terminating with default action of signal 11 (SIGSEGV)
==62415==  Access not within mapped region at address 0x1FFE801078
==62415== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==62415==    at 0x77F1AF7: __printf_fp_l (printf_fp.c:933)
==62415==  If you believe this happened as a result of a stack
==62415==  overflow in your program's main thread (unlikely but
==62415==  possible), you can try to increase the size of the
==62415==  main thread stack using the --main-stacksize= flag.
==62415==  The main thread stack size used in this run was 8388608.
==62415== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==62415== 
==62415== Process terminating with default action of signal 11 (SIGSEGV)
==62415==  Access not within mapped region at address 0x1FFE801F40
==62415== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==62415==    at 0x71C2134: _vgnU_freeres (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_core-amd64-linux.so)
==62415==  If you believe this happened as a result of a stack
==62415==  overflow in your program's main thread (unlikely but
==62415==  possible), you can try to increase the size of the
==62415==  main thread stack using the --main-stacksize= flag.
==62415==  The main thread stack size used in this run was 8388608.

Run with valgrind ./Editor --editor --path ./debug using your original reproduction steps. Please consider filling a bug at Issues · godotengine/godot · GitHub
using your reproductions steps and the above error message.

Thanks for taking the time to tackle this. I obviously have a bit to learn about exported Arrays, but your idea of keeping an ID and registering with a dictionary is ideal, and it’s probably better in the long run to have a central place to access the nodes anyway. I’ll file a bug report for the editor crash later tonight.

moniker | 2021-05-05 18:08