How to get detailed debugging on RPC calls?

Godot Version

4.2.1

Question

I have a question about the best ways to debug RPC calls. I have a client that calls a server and I can see where the rpc_id() call is making a call back to my client, but I never see the response call ON the client and I don’t see any way to triage where it might be failing.

Can you see anything here?


I am not yet familiar with Godot RPC / Network, for other projects I mostly use the application “Wireshark”

Is the method defined on both the client and server? This must be done when using RPCs in Godot 4 (for optimization reasons), even if the method is empty on one side.

Yep. It is defined on both sides. I definitely went back and made sure that was the case. I’m starting to suspect that it may have something to do with the class being an Autoload. It appears that one of the nodes is at /root/Server from the Autoload, even though it is referencing the path “systems/network/Server” in both the client and the server. I can’t see what it is on the client, even though I have the debug and capture active.

One issue that I’m definitely seeing is that while I can see the /root/Server node on the server and see the Incoming RPC and Outgoing RPC, I don’t see ANYTHING in the debugger on the client side even though the server is definitely receiving the request.

Also doesn’t appear to be a mechanism to see the actual RPC data.

This is what it looks like on the server…

@rpc("any_peer", "call_remote", "reliable")	
func return_login_response( player_id, result ):
	print("Sending login response to client %d" % player_id )
	rpc_id(player_id, "return_login_response", player_id, result )
	network.disconnect_peer(player_id)

This is what it looks like on the client:

@rpc("any_peer", "call_remote", "reliable")	
func return_login_response( player_id, result ):
	print("Results received")
	
	if result == true:
		print("Need to join the server")
		GameServer.join_server("foo")
	else:
		print("Authentication failed")

	gateway_api.connected_to_server.disconnect(_on_connection_succeeded )	
	gateway_api.connection_failed.disconnect(_on_connection_failed)

There are two errors here - one appears to be a defect. The first one is that rpc_id does not have time to complete before the network.disconnect_peer() call disconnects the player. Removing the network.disconnect_peer() call will allow the rpc_id to work properly.

The second error appears to be some weirdness with call_remote vs call_local.

If you perform an rpc_id() directed to a client and the client RPC signature is not “call_local”, the call does not happen on the client even though it is being called from a remote authority.

  • "call_remote": The function will not be called on the local peer.
  • "call_local": The function can be called on the local peer. Useful when the server is also a player.

In my scenario, the function is being called from the server TO the local peer. The difference between these two in practice is about as clear as mud because it appears that these semantics were designed around a single executable being both a client and a server. If that’s not your scenario - none of this syntax makes sense.

Setting my client and server to “call_local” allowed the client to actually receive the rpc call from the server.

you cant call the same function with two definitions on the same node.

which points out the immediate problem. you have two different nodes and are trying to call the other nodes function. You are calling the same function of the same node on the remote end. When you intended to call the other nodes function of the same name.

The host and client need to have the exact same scene tree in order for rpc messages to hit their target. To resolve your issue you need to call:

@rpc("any_peer", "call_remote", "reliable")	
func return_login_response( player_id, result ):
	print("Sending login response to client %d" % player_id )

    # you need to call the other nodes remote function "return_login_response"
	other_node.rpc_id(player_id, "return_login_response", player_id, result )

	network.disconnect_peer(player_id)

The syntax may be wrong but that should fix your issue.

I will dig into that in the AM. What I did find was that the network.disconnect_peer call ended up being the root cause.

@rpc("any_peer", "call_remote", "reliable")	
func return_login_response( player_id, result ):
	print("Sending login response to client %d" % player_id )
	rpc_id(player_id, "return_login_response", player_id, result )
	
	await get_tree().create_timer(1).timeout 
	
	network.disconnect_peer(player_id)

This ended up resolving it.

The other node’s function is actually called return_login_response in this particular case. The scene trees are the same in terms of location, naming, and function signature.

I’m not sure where I will get a reference to the other_node in this particular instance.

1 Like

Okay, I wondered if that was possible to trick the api like that.

Another option to waiting is a deferred call. That will wait at least a frame.

1 Like

Awesome. I will definitely go look at the docs on deferred calls. Much appreciated.

A lot of the docs are written assuming that the client and the server are the same executable, so when you step outside that and have a real client/server topology - some of the docs didn’t work - or at least I’ll say, there is some REALLY undocumented stuff in the way you use get_tree().set_multiplayer(…). This particular patch is a client calling a gateway server which is then itself calling an authserver to the gateway is both a client and a server… so I’m sure that is ALSO causing some weirdness.

Okay, i had to think about how that would look for a second.

I have seen tutorials utilizing multiple scene trees and instantiating multiple network objects, within the same executable. Not really your objective but i think logically similar.

I had seen a bunch of them for Godot 3, but they didn’t work properly for Godot 4 since the API changed. If you have any, would love to watch/read them!

Finding out how to add SceneMultiplayer and where to add it in the tree (and that you have to poll for it) was all stuff we kinda figured out along the way.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.