How i can simulate a network latency and packet loss between client and server (peers) ?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By megalike

Require something like net_packetextralag && net_packetlossrate like in CryEngine, the same from Unreal is PktLag && PktLoss, the same from Source Engine is net_fakelag && net_fakeloss.
Thanks.

:bust_in_silhouette: Reply From: kraybit

One solution, is to implement a simple UDP relay server in Godot. That would work with ENetMuliplayerPeer, since ENet is UDP-only (at least, I believe so?).

That way, you can get network latency in Godot, without external tools and stuff.

The Idea
Let’s say your “true” server listens at port 9000. Then the relay server listens at port 9001, and just redirects packets to port 9000, and vice-versa. Then it’s simple to add latency, packet drop, etc. The Client would then connect to port 9001 to get the latency stuff.

Here’s a simple GDScript implementation that works in Godot v4.0.beta17.mono.official [c40020513]

# UDP Relay server with latency
# Seems to work in Godot v4.0.beta17.mono.official [c40020513]
# Not thoroughly tested..

extends Node
class_name DebugUDPLagger

@export var virtual_server_port : int = 8001
@export var true_server_port : int = 8000
@export_category("Degradation")
@export_range(0, 5000) var fake_latency_ms : int = 0
@export_range(0, 0.5) var fake_loss : float = 0

# Mental picture :
#
#  (True) Server  <-->  Virtual Client -[Laggy bridge]- Virtual Server  <-->  (True) Client
#

var vserver_peer : PacketPeerUDP
var vserver_has_dest_address : bool = false
var vserver_first_client_port : int = -1
var vclient_peer : PacketPeerUDP

var rng = RandomNumberGenerator.new()

class QueEntry :
	var byte_array : PackedByteArray
	var qued_at : int
	
	func _init(packet:PackedByteArray, time_now:int) :
		self.byte_array = packet
		self.qued_at = time_now


var client_to_server_que : Array[QueEntry]
var server_to_client_que : Array[QueEntry]

func _enter_tree() -> void:
	print("Setting up DebugUDPLagger")

	vserver_peer = PacketPeerUDP.new()
	vserver_peer.bind(virtual_server_port, "127.0.0.1")
	
	vclient_peer = PacketPeerUDP.new()
	vclient_peer.set_dest_address("127.0.0.1", true_server_port)

	if not OS.is_debug_build() :	
		push_warning("DebugUDPLagger is active, but it is not a debug build!!! This will consume some 	performance....")
		fake_latency_ms = 0
		fake_loss = 0


func _process(_delta : float) -> void :
	var now : int = Time.get_ticks_msec()
	var send_at_ms = now - fake_latency_ms
	
	# Handle packets Client -> Server
	while vserver_peer.get_available_packet_count() > 0 :
		var packet = vserver_peer.get_packet()
		var err = vserver_peer.get_packet_error()
		if err != OK :
			push_error("DebugUDPLagger : Incoming packet error : ", err)
			continue
			
		var from_port = vserver_peer.get_packet_port()
		
		if not vserver_has_dest_address : 
			# Assume the first who send a packet to us is the True Client
			vserver_peer.set_dest_address("127.0.0.1", from_port)
			vserver_first_client_port = from_port
			vserver_has_dest_address = true
		elif vserver_first_client_port != from_port :
			push_warning("DebugUDPLagger : VServer got packet from unknown port, ignored.")
			continue
		
		client_to_server_que.push_back(QueEntry.new(packet, now))
	
	_process_que(client_to_server_que, vclient_peer, send_at_ms)
	
	# If the true client hasn't sent any packets to us yet, then
	#  we don't check for any incoming packets from the true server,
	#  because they can't possibly know what port vclient is sending from
	#  anyway, because we never forwarded any packets to the true server then.
	if not vserver_has_dest_address :
		return
	
	# Handle packets Server -> Client
	while vclient_peer.get_available_packet_count() > 0 :
		var packet = vclient_peer.get_packet()
		var err = vclient_peer.get_packet_error()
		if err != OK :
			push_error("DebugUDPLagger : Incoming packet error : ", err)
			continue
		
		var from_port = vclient_peer.get_packet_port()
		if from_port != true_server_port :
			push_warning("DebugUDPLagger : VClient got packet from unknown port, ignored.")
			continue
		
		server_to_client_que.push_back(QueEntry.new(packet, now))

	_process_que(server_to_client_que, vserver_peer, send_at_ms)
	
	
func _process_que(que : Array[QueEntry], to_peer : PacketPeerUDP, send_at_ms : int) :
	while not que.is_empty() :
		var front = que.front()
		if send_at_ms >= front.qued_at :
			# Send it, or drop it?
			# Note : We check for dropping here, so that you can change fake_loss
			#  while packets are in the queue. Eg. the que is kept "intact"
			#  for as long as possible.
			if fake_loss <= 0  ||  rng.randf() >= fake_loss:
				to_peer.put_packet(front.byte_array)
			# Remove from que
			que.pop_front()
		else :
			break
	

Place it in the scene tree, and make sure your Server is listening to the true_server_port, and your client is set to connect to the virtual_server_port.

Cheers!