Disconnection while changing scenes in multiplayer

Godot Version

4.6.1

Question

I’ve tried many ways to handle the game loading process once all peers have been connected and its time to pass from the menu scene to the game scene, but it always ends desynchronizing and losing connection when the previous scene gets deleted. Currently I am using MultiplayerSpawner to instantiate the game scene onto its parent node which is also its spawn_path, after freeing the menu scene which were in a separate branch of the tree. I have a scene structure like this:

  • Main (main scene of the game)
    • MenuContainer (Node)
      • Main menu
    • GameContainer (Node)
      • SceneSpawner
      • (here should be spawned Game)

The scripts that handle all the process are more or less as follows:

# Main menu.gd
func _ready():
	$game_start_button.pressed.connect(func():
		if multiplayer.is_server(): load_game())

func load_game():
	if multiplayer.is_server():
		main.change_to_game(player_data.duplicate())
# Notice that I handle creating and assigning multiplayer peer in a
# Network autoload, to make sure that the node that created multiplayer
# peer is never freed. When load_game gets called all peers have joined
# main.gd
func _ready() -> void:
	%ScreenSpawner.spawn_path = %GameContainer.get_path()
	%ScreenSpawner.spawn_function = _screen_spawner
	menu_scene = main_menu_scene.instantiate()
	menu_scene.main = self
	%MenuContainer.add_child(menu_scene)

@rpc("authority", "call_local")
func change_to_game(data):
	menu_scene.queue_free()
	if multiplayer.is_server():
		%ScreenSpawner.spawn(data)

func _screen_spawner(data):
	var game = game_scene.instantiate()
	game.main = self
	game.configure(data)
	return game
# game.gd
var ready_player_count = 0

func _enter_tree() -> void:
	tree_exiting.connect(func():
		print(multiplayer.get_unique_id(), ": game tree exiting")
	)

func configure(data):
	player_data = data

func _ready():
	# game setup code here...
	print(multiplayer.get_unique_id(), ": path: ", get_path(), ", peers: ", multiplayer.get_peers())
	await get_tree().process_frame
	rpc_id.call_deferred(1, "on_player_loaded", multiplayer.get_unique_id())

@rpc("any_peer", "call_local")
func on_player_loaded(peer_id):
	print(multiplayer.get_unique_id(), ": remote: ", multiplayer.get_remote_sender_id(), ": on_player_loaded, peer_id: ", peer_id, ", peers: ", multiplayer.get_peers())
	if multiplayer.is_server():
		ready_player_count += 1
		print(multiplayer.get_unique_id(), ", ready players: ", ready_player_count, ", player_data.size: ", player_data.size())
		if ready_player_count == player_data.size():
			server_initialize()

The idea is to wait until all players have loaded their game scenes to initialize the game. I added some prints in different moments of the execution to view whats happening, and it seems to be that, in the client, the game._ready func runs but on_player_loaded is never called and instead tree_exiting is emitted, indicating that the Spawner first instantiated the game and then deleted it when a disconnection occured. I dont know why that disconnection happens, but if I comment the line menu_scene.queue_free() in main.change_to_game, then it all goes as it should be. However, that scene needs to be deleted, because its not in use anymore. I thought that setting the Spawner’s spawn_path to a sub-branch where that deletion didnt happened would make it unaffected by that, but apparently its not the case