[SOLVED] Game get stuck after closing a Control popup. How to debug?

Godot Version

4.4.1

Question

In my game, i have a UI popup that i set as visible = true to interact with it on specific events, then i hide it with visible = false when done.

It happens that the game get stuck after hiding it.

Now, my question is not much why it get stuck, rather how to debug the issue. If i go to the Godot editor and tap the “pause” button in the debug panel, nothing happens. It just change from whiteish to cyanish color, i was expecting at least a clal stack of some indication of what Godot is doing internally?

How to debug such a condition?

(there is also network activity in the background, so i am not sure it’s linked to the visible toggling or not, but i always happens when the popup become invisible or shortly after)

After Pausing the execution, you can click Step Into, which will go to the next line of code that it wanted to go. This will give you the callstack.

But I don’t think it’ll be of any use in your specific example.

There is no one good answer to that, that’s the challenge of debugging.
You haven’t gave us any clear explanation what happens when the game “gets stuck”. Is it completely stopped? Is there an error? Is the input not working? Each of these has to be treated separately.
When you have issues with UI nodes, especially when not reacting to input, I suggest doing what I suggested in another post a couple minutes ago.

The game window get stuck, doesn’t accept any input anywhere. The Node2D tree seems also stuck as there are no updates on the game area itself.

I can go to the editor and restart or stop.

And the stack trace button is not enabled even if i tap on the pause button….

seems like maybe a control node is eating up the mouse input.is it a mouse only game?

Yes it’s mouse only, and in the misc debugger windows it shows “clicked control” on the button i clicked to hide the control node (which has been hidden)

Actually the physics_process gets called properly, it’s the UI to be stuck.

I have another control popup that is closed the same way, but does’nt block the UI, weird

can u try setting every control nodes mouse property to this

set them to Mouse => Ignore

I have two buttons: save, and abort.

Abort set visible = false

Save emits a signal, then set visible = false

Abort works fine, save blocks.

I have stepped torugh debugger in the signal emit, and it worked fine, back to the visible = false, as well works fine, but after function return i have the UI bocked

can you share your script?

it’s hard to guess whats happening

After some more debug, it feels like i have some kind of race condition with the networking code?

The “save” button send some data to the server. The server periodically send a game data update back to Godot client, and it will update the node list.

If i put a breakpoint in the update code, the UI doesn’t hang. If i don’t break, it will hang. Back to good old prints in the code to avoid debugger “fixing” the bug?

The code is actually open source, but i havent published it yet.

I feel like the script would not be of much help, since it’s interleaved with too much else.

Here is the script anyway:

extends PanelContainer
class_name ShipyardControl

@onready var _module = get_node("VBoxContainer/modules_grid/module")
@onready var _modules_grid = get_node("VBoxContainer/modules_grid")

@onready var _summary_mass_volume: Label = get_node("VBoxContainer/summary_grid/summary_mass_volume")
@onready var _summary_fuel_tank: Label = get_node("VBoxContainer/summary_grid/summary_fuel_tank")
@onready var _summary_thrust_consumption: Label = get_node("VBoxContainer/summary_grid/summary_thrust_consumption")
@onready var _summary_cargo_hold: Label = get_node("VBoxContainer/summary_grid/summary_cargo_hold")
@onready var _summary_hydro_hold: Label = get_node("VBoxContainer/summary_grid/summary_hydro_hold")

signal closed
signal loadout_changed( star_id: int, ship_id: int, modules: Array[String] )

# Keep track of all the newly created moduels boxes
var _modules: Dictionary = {}
var _ship: DumpedScene = null
var _module_textures: Dictionary = {}

func _ready():
	_module.visible = false	

func _clean_stuff():
	for k in _modules:
		_modules[ k ][ "node" ].queue_free()
	_modules.clear()


func _on_cancel_btn_pressed() -> void:
	_clean_stuff()
	visible = false
	closed.emit()


func _replace_module( id: int, n: int ):
	var rule_module: DumpedData = Game.game_rules.getChild( id, "module")
	if rule_module != null:
		_modules[ n ][ "data" ] = rule_module
	else:
		_modules[ n ][ "data" ] = null
	_setup_module( n )
	_recalculate_summary()

func _recalculate_summary():
	var summary_mass: float = 0.0
	var summary_volume: float = 0.0
	var summary_fuel_capacity: float = 0.0
	var summary_thrust: float = 0.0
	var summary_consumption: float = 0.0
	var summary_cargo_capacity: float = 0.0
	var summary_hydro_capacity: float = 0.0
	for n in _modules:
		var module_data: DumpedData = _modules[ n ][ "data" ]
		if module_data != null:
			summary_mass += module_data.getItem( "mass" )
			summary_volume += module_data.getItem( "volume" )
			var tmp = module_data.getItem( "thrust", true )
			if tmp != null:
				summary_thrust += tmp
			tmp = module_data.getItem( "fuel_consumption", true )
			if tmp != null:
				summary_consumption += tmp
			tmp = module_data.getItem( "cargo_capacity", true )
			if tmp != null:
				summary_cargo_capacity += tmp
			tmp = module_data.getItem( "hydro_capacity", true )
			if tmp != null:
				summary_hydro_capacity += tmp
			tmp = module_data.getItem( "fuel_capacity", true )
			if tmp != null:
				summary_fuel_capacity += tmp
	_summary_mass_volume.text = str("%0.1ft" % summary_mass) + " / " + str("%0.1fmq" % summary_volume )
	_summary_fuel_tank.text = str("%0.1ft" % summary_fuel_capacity)
	_summary_thrust_consumption.text = str("%0.1fn" % summary_thrust) + " / " + str("%0.1fts" % summary_consumption )
	_summary_cargo_hold.text = str("%0.1ft" % summary_cargo_capacity )
	_summary_hydro_hold.text = str("%0.1ft" % summary_hydro_capacity )


func setupPopup( ship: DumpedScene ) -> void:
	var rules_modules: Array[DumpedData] = OpenAstra.game_rules.getChildren("module")
	if _module_textures.is_empty():
		for m in rules_modules:
			_module_textures[ m.tag ] = load("res://icons/" + m.tag + "_module.png")
	_ship = ship
	if visible:
		_clean_stuff()
	var module_manager: DumpedData = ship.module_manager
	if module_manager == null:
		return
	var num_modules: int = module_manager.getItem("num_modules")
	for n in num_modules:
		var new_module_block = _module.duplicate()
		new_module_block.visible = true
		var module_data: DumpedData = module_manager.getChild( n, "module" )
		new_module_block.get_node("vbox/title_row/module_no").text = str(n)
		var replace_menu: PopupMenu = new_module_block.get_node("vbox/title_row/replace_btn").get_popup()
		replace_menu.id_pressed.connect( _replace_module.bind(n) )
		replace_menu.add_item( "empty/remove", 0 )
		for m in rules_modules:
			replace_menu.add_item( m.tag, m.id )
		_modules_grid.add_child( new_module_block )
		_modules[ n ] = {
			"node" = new_module_block,
			"data" = module_data
		}
		_setup_module( n )
	_recalculate_summary()


func _setup_module( n: int ):
	var module_data: DumpedData = _modules[ n ][ "data" ]
	var node = _modules[ n ][ "node" ]
	if module_data != null:
		node.get_node("vbox/title_row/module_type").text = module_data.tag
		node.get_node("vbox/data_row/sprite").texture = _module_textures[ module_data.tag ]
	else:
		node.get_node("vbox/title_row/module_type").text = _module.get_node("vbox/title_row/module_type").text
		node.get_node("vbox/data_row/sprite").texture = _module.get_node("vbox/data_row/sprite").texture
	

func _on_save_btn_pressed() -> void:
	var loadout: Array[ String ]
	for n in _modules:
		var tag: String = ""
		if _modules[ n ][ "data" ] != null:
			tag = _modules[ n ][ "data" ].tag
		loadout.push_back( tag )
	loadout_changed.emit(  Game.starsystem.data.id, _ship.data.id, loadout )
	_clean_stuff()
	visible = false
	closed.emit()
	

func _on_reset_btn_pressed() -> void:
	setupPopup( _ship )


I am just learning Godot, i am an old fashioned C/C++ coder, please offer any suggestion to improve.

It’s an in-game module editor to change the setup of a ship. The user select different modules and the setup get sent to the server to be validated and applied. The server in the next cycle send back the new ship configuration. No graphics (yet) changes on the Node2D tree for this change.

The DumpedData stuff is how the server send the data to the Godot client, it’s bascally a dinamyc set of data values.

I have found the issue and fixed it.

While it seemed to be an UI issue, actually the

func _physics_process(_delta: float) -> void:

was hanging due to an error in the reading of incoming TCP data.

What sent me in the wrong direction was two things:

  • print_debug being somehow “stopped” in the editor, i could not see all the output. Luckly, on the command line where i started Godot, i could see everything. It feels like print_debug() is not immediate but relies on the Godot loop to actually print out.
  • Unable to actually stop and trace where Godot was stuck, in this case i would have identified the networking code sooner.

Thank you guys for your replies.