How to use ENet's built-in variables to track ping and packet loss percentage?

Godot Version

4.6

Question

I’m currently working on server reconciliation for multiplayer hit detection in Monkanics. However, the server needs to know the ping and packet loss percentage for all clients to perform calculations.

I just haven’t implemented that yet.

Then I remembered that ENet, the backend library for multiplayer in Godot 4, has built-in functions to track these stats.

However, I don’t know how to implement these in code. Seriously, I get thrown an error whenever I try to reference PeerStatistic.

I also asked this same question a year ago, but I opted to get ping with my own function. But I literally cannot remember any of it.

Can anyone provide a code example of using these functions.

If you are using high level multiplayer you are probably using an ENetMultiPlayerPeer. To get a ENetPacketPeer from that you can use .get_peer(player_id) for each player, or .host.get_peers() for an array, which may be very difficult to map to which player is which.

func get_statistics_for_player(player_id: int, stats_label: Label) -> void:
    var enet_multiplayer: ENetMultiplayerPeer = multiplayer.multiplayer_peer
    var enet_peer: ENetPacketPeer = enet_multiplayer.get_peer(player_id)

    var ping: float = enet_peer.get_statistic(ENetPacketPeer.PEER_LAST_ROUND_TRIP_TIME)
    var loss: float = enet_peer.get_statistic(ENetPacketPeer.PEER_PACKET_LOSS)
    stats_label.text = """
Ping: %f
Loss Ratio: %f
""" % [ping, loss]

Try this out, I cannot test this code (lol) so it’s very likely wrong in some way

1 Like

Ok, there was LOT of nuance when I was testing this, but by-and-large, this method works.

Firstly, the PEER_LAST_ROUND_TRIP_TIME is very accurate, but slow to update. I figured out the slowness is actually an intended feature from my research on Enet. So that’s good.

Secondly, the PEER_PACKET_LOSS is a bit strange. It’s accurate, but it’s SUPER slow to update (10~ second interval) and it takes a long time to reduce packet loss percentage when it does go up.

Also, when I test artificial packet loss with Clumsy, the percentage shoots up (2.0 packet loss on Clumsy = 20% Enet Packet Loss). That last one could be a misunderstanding with Clumsy though.

Here’s the functions I made for the server to track ping and packet loss:

(They had to be separate in order for me to return their values)
(And if anything but the server calls these, it returns a default value of -1)

func get_client_ping(Client_ID:int) -> float:
	
	if not multiplayer.is_server(): return -1
	
	var Enet_Multiplayer : ENetMultiplayerPeer = multiplayer.multiplayer_peer
	var Enet_Peer : ENetPacketPeer = Enet_Multiplayer.get_peer(Client_ID)
	
	var Client_Ping : float = Enet_Peer.get_statistic(ENetPacketPeer.PEER_LAST_ROUND_TRIP_TIME)
	
	return Client_Ping
func get_client_packet_loss(Client_ID:int) -> float:
	
	if not multiplayer.is_server(): return -1
	
	var Enet_Multiplayer : ENetMultiplayerPeer = multiplayer.multiplayer_peer
	var Enet_Peer : ENetPacketPeer = Enet_Multiplayer.get_peer(Client_ID)
	
	var Client_Packet_Loss_Percentage : float = Enet_Peer.get_statistic(ENetPacketPeer.PEER_PACKET_LOSS)
	
	return Client_Packet_Loss_Percentage

Thanks, @gertkeno for helping me figure this out. :+1:

Last thing, having to set a variable to set another variable is such a bad design. No wonder I couldn’t figure it out before, it was extremely unintuitive.

1 Like

Is this your design and/or working around a quirk in ENet? Makes sense if only the server could tell ping to each player and each player could only tell ping to the server, since peers don’t talk directly to each other.

1 Like

It’s my design. I have these if not multiplayer.is_server(): return checks in place in most server functions.

However, since the ping and packet loss getter functions I created return a float and Monkanics’ project enforces static typing, I just return -1 as a placeholder value that isn’t 0.

1 Like