"Program crashed with signal 11" implementing multiplayer level switching

4.3

Hi all, I’m trying to implement dynamic and indpedent level switching for clients in multiplayer but I encounter the signal 11 error on all attempts. Server is not a player in this project. The first level will be called ‘overworld’, the second ‘shop’. From what I understand all level switching should occur on the server for multiplayer levels.

So far I can:

  • Host a local network server and dynamically add clients
  • Clients can join and interact with the world in their own instance
  • Change levels by first removing the client player from the scene tree, removing the overworld from the scene tree and adding the shop to the scene tree before adding the client player to the shop. I use remove child and not queue free for the levels because I would like to return to the same level later

However when trying to change from the shop back to the overworld I remove the client player, request_ready() the overworld and its children before removing the shop and adding the overworld to the scene tree. Whenever I attempt this I get the signal 11 error detailed below. It always happens around the point where I try to add_child(overworld).

================================================================
CrashHandlerException: Program crashed with signal 11
Engine version: Godot Engine v4.3.stable.official (77dcf97d82cbfe4e4615475fa52ca03da645dbd8)
Dumping the backtrace. Please include this when reporting the bug to the project developer.
[1] error(-1): no debug info in PE/COFF executable
[2] error(-1): no debug info in PE/COFF executable
[3] error(-1): no debug info in PE/COFF executable
[4] error(-1): no debug info in PE/COFF executable
[5] error(-1): no debug info in PE/COFF executable
[6] error(-1): no debug info in PE/COFF executable
[7] error(-1): no debug info in PE/COFF executable
[8] error(-1): no debug info in PE/COFF executable
[9] error(-1): no debug info in PE/COFF executable
[10] error(-1): no debug info in PE/COFF executable
[11] error(-1): no debug info in PE/COFF executable
[12] error(-1): no debug info in PE/COFF executable
[13] error(-1): no debug info in PE/COFF executable
[14] error(-1): no debug info in PE/COFF executable
[15] error(-1): no debug info in PE/COFF executable
[16] error(-1): no debug info in PE/COFF executable
[17] error(-1): no debug info in PE/COFF executable
[18] error(-1): no debug info in PE/COFF executable
[19] error(-1): no debug info in PE/COFF executable
[20] error(-1): no debug info in PE/COFF executable
[21] error(-1): no debug info in PE/COFF executable
[22] error(-1): no debug info in PE/COFF executable
[23] error(-1): no debug info in PE/COFF executable
[24] error(-1): no debug info in PE/COFF executable
[25] error(-1): no debug info in PE/COFF executable
[26] error(-1): no debug info in PE/COFF executable
[27] error(-1): no debug info in PE/COFF executable
[28] error(-1): no debug info in PE/COFF executable
[29] error(-1): no debug info in PE/COFF executable
[30] error(-1): no debug info in PE/COFF executable
[31] error(-1): no debug info in PE/COFF executable
[32] error(-1): no debug info in PE/COFF executable
[33] error(-1): no debug info in PE/COFF executable
[34] error(-1): no debug info in PE/COFF executable
[35] error(-1): no debug info in PE/COFF executable
[36] error(-1): no debug info in PE/COFF executable
[37] error(-1): no debug info in PE/COFF executable
[38] error(-1): no debug info in PE/COFF executable
[39] error(-1): no debug info in PE/COFF executable
-- END OF BACKTRACE --
================================================================

Below is relevant code and viewports

This code initialises the server and players

var network
var max_players = 10
@onready var player_name: LineEdit = $Main_Menu/Name
@onready var ip: LineEdit = $Main_Menu/IP
@onready var port: LineEdit = $Main_Menu/Port
@onready var overworld = preload("res://Scenes/game.tscn").instantiate()
@onready var character3d = preload("res://Scenes/3D Player.tscn").instantiate()
@onready var shop_interior = preload("res://Scenes/shop_interior.tscn").instantiate()
@onready var crab_shop_interior = preload("res://Scenes/crab_shop_interior.tscn").instantiate()
func _on_host_pressed() -> void:
	network = ENetMultiplayerPeer.new()
	network.create_server(port.text.to_int(), max_players)
	multiplayer.multiplayer_peer = network
	network.peer_connected.connect(peer_connected)
	network.peer_disconnected.connect(func(id): remove_player_character(id))
	$Main_Menu.visible = false
	add_child(overworld)
func _on_join_pressed() -> void:
	network = ENetMultiplayerPeer.new()
	network.create_client(ip.text, port.text.to_int())
	multiplayer.multiplayer_peer = network
	#$Main_Menu.visible = false
func _on_spawn_pressed() -> void:
	load_player.rpc(multiplayer.get_unique_id(), sell)
	$Main_Menu.visible = false
@rpc("any_peer")
func load_player(id, sell):
	if is_multiplayer_authority():
		overworld.add_player_character(sell, id)

func add_player_character(sell, id):
	overworld.add_player_character(sell, id)
func remove_player_character(id):
	overworld.remove_player_character(id)

This button changes levels from overworld to shop

func _on_button_pressed() -> void:
	var level = overworld
	var c = level.get_children()
	var id = c[-1].name
	level.remove_player_character(id)
	print("player removed")
	remove_child(level)
	add_shop_interior(id)

func add_shop_interior(id):
	print("adding shop")
	add_child(shop_interior)
	shop_interior.add_player_character(id)

This button changes levels from shop to overworld

func _on_button_2_pressed() -> void:
	print("ASFF")
	var level = shop_interior
	var c = level.get_children()
	var id = c[-1].name
	level.remove_player_character(id)
	print("player removed")
	remove_child(level)
	spawn_overworld(id)

func spawn_overworld(id):
	var level = overworld
	print(level)
	level.request_ready()
	var c = level.get_child_count()
	for i in c:
		level.get_child(i).request_ready()
	add_child(level) 
        #the code breaks here and the screen freezes before crashing
	level.add_player_character(id)
	print("adding")


all needed levels are in the auto spawn list

Any help is appreciated.

1 Like

If you enable verbose output is any other info given?

Project Settings: debug/settings/stdout/verbose_stdout

no discernable difference

Looking at your code I did come across a similar engine-level failure due to @onready preloads. The error I faced caused me to implement this scenemanager autoload in my projects now:

var sample_scene: PackedScene = null

func _ready() -> void:
    sample_scene.preload("res://path/to/sample_scene.tcsn")

And then later on in my project I call my scenemanager to do scenemanager.sample_scene.instantiate()

I couldn’t identify a specific root cause, but my issue went away once I split up my preload() logic. Does your error go away if you switch from preload(0 to load()? If so could be a similar issue with @onready preload(). My sample code above was for a simple GUI project for some automation tooling hence just preload()-ing everything at launch, but I’d still try either just load() or splitting the preload() to see how it behaves after.

I also recommend commenting out the request_ready calls and see if it crashes. If it doesn’t crash maybe look into the ready functions of the overworld and its children.

1 Like

Try running the project using a debug build. To see the backtraces better.
Download the debug builds here if you don’t want to build it yourself. Let’s see what it says after it crashes again.

1 Like

I’ve discovered the issue can be replicated much easier. Simply add_child(node), remove_child(node), add_child(node) will cause the crash regardless of request_ready() or declaring the node to a separate variable.

Naturally you should instantiate() a new version of the desired node and then add it as a child but this doesn’t seem appropriate for an instance where another client is currently using said node (let’s say a particular level in the game) and you may want to rejoin that same instance.

I’m going to restructure my logic trying to follow the advice provided here: Switching between multiple scenes in multiplayer with multiple clients.

Hey so I managed to solve it myself by restructuring my project.

Here is my new scene tree, each level I intended players to access has been split into their own Nodes with an respective Multiplayer Spawner and whatever script I was using to process the level logic. These empty nodes help to physically separated the levels in the world so they’re not overlapping when new players join.

In my world script I instantiate the network and joining logic as normal. GN = GameNode or the overworld which is the first default level

func _on_host_pressed() -> void:
	network = ENetMultiplayerPeer.new()
	network.create_server(port.text.to_int(), max_players)
	multiplayer.multiplayer_peer = network
	network.peer_connected.connect(peer_connected)
	network.peer_disconnected.connect(func(id): remove_player_character(id))
	$Main_Menu.visible = false
	GN.add_game()
	
func peer_connected(id):
	print("Peer connected ", id)
	
func _on_join_pressed() -> void:
	network = ENetMultiplayerPeer.new()
	network.create_client(ip.text, port.text.to_int())
	multiplayer.multiplayer_peer = network
	$Main_Menu/Join.visible = false
	$Main_Menu/Spawn.visible = true
	#$Main_Menu.visible = false
func _on_spawn_pressed() -> void:
	load_player.rpc(multiplayer.get_unique_id(), sell)
	$Main_Menu.visible = false

func add_player_character(id):
	GN.add_player_character(sell, id)
func remove_player_character(id):
	GN.remove_player_character(id)

@rpc("any_peer")
func load_player(id, sell):
	if is_multiplayer_authority():
		GN.add_player_character(sell, id)

inside each "level"Node script is logic for adding and removing players and connecting player input signals to the script

func add_player_character(sell, id=1):
	var buyer = preload("res://Scenes/player.tscn").instantiate()
	var seller = preload("res://Scenes/seller.tscn").instantiate()
	print(sell)
	if sell == false:
		buyer.name = str(id)
		add_child(buyer, true)
		print(buyer.get_parent())
		buyer.interact.connect(_on_player_interact)
	if sell == true:
		seller.name = str(id)
		add_child(seller, true)
		seller.interact.connect(_on_player_interact)
	player_count += 1
	
	
func remove_player_character(peer_id):
	# Find and despawn the character that peer is controlling
	var player_character = get_node_or_null(str(peer_id))
	remove_child(player_character)
	if player_character:
		print("free")
		player_character.queue_free()
	player_count -= 1
		
func _on_player_interact(node) -> void:
	player_node = node
	print($Game/Shop.enable_enter)
	print(multiplayer.get_unique_id())
	if $Game/Shop.enable_enter == true:
		print("node got")
		player_node.warp(0)
	if $Game/Crab_Shop.enable_enter == true:
		print("warp crab shop")
		player_node.warp(2)

In my player script I have a warp function which under the right conditions allows a player to change level, normally level switching should only occur on the server, 1, so we call an rpc_id(1) to make sure this occurs safely while passing the level number and the player that wants to warp as parameters.

func warp(int) -> void:
	print("warp")
	print(int)
	get_parent().get_parent().warp.rpc_id(1, int, name)

The parent, parent is the world which calls this warp function

@rpc("any_peer","call_local")
func warp(level, id):
	print(id)
	if level == 0:
		if $ShopNode.player_count == 0:
			$ShopNode.setup_shop()
		$GameNode.remove_player_character(id)
		$ShopNode.add_player_character(id)
		$ShopNode.setup_signals.rpc()

For each level I check to see if the level already exists and instantiate it

func setup_shop():
	if shop_interior == null:
		shop_interior = shop_interior_scene.instantiate()
		add_child(shop_interior)
	elif shop_interior.is_inside_tree() == true:
		print("Shop already spawned")
	else:
		print("shop is already spawned")

If all the players leave the level I run a check before deleting the scene

func close_shop():
	if shop_interior == null:
		print("shop is not spawned yet")	
	elif shop_interior.is_inside_tree() == true:
		remove_child(shop_interior)
		shop_interior.queue_free()
	else:
		print("shop_interior not in tree")

There’s a few debug errors here and there about missing nodes and missing rpc calls because of player transfering but it doesn’t affect performance and I’m pretty sure it’s negligible.

Hope this helps someone looking to implement something similar in the future.