Files
sbox-public/engine/Sandbox.Engine/Systems/Networking/System/Channel/Connection.Response.cs
Lorenz Junglas 519ad5d815 Network: Broadcast and Chunking compression optimization (#4417)
**Broadcast**
now encodes the wire payload just once and sends the same bytes to
every recipient, before we did one redundant compression per connection.
This primarily reduces CPU load on the server/host.

**Chunking**
Large messages are now compressed before chunking rather than after.
Resulting in slightly smaller payloads.
The receiver now decompresses a single reassembled payload instead of
decompressing every chunk independently, significantly reducing CPU load
on receiving clients.

**Refactor**
Chunking and compression are now low-level wire concerns handled by
Connection rather than being mixed into the high-level message types.
The old `InternalMessageType.Chunk` enum is removed; chunk framing uses
a dedicated wire flag byte alongside `FlagRaw` and `FlagCompressed`.

**Results (Chunking changes)**

Synthetic data, results on real payloads may differ.

Benchmarks (1000 GOs / 2000 components, ~1MB payload, 500 iterations):

Wire size (chunk-first-then-compress):    275KB
Wire size (compress-first):               259KB  (5.7% smaller)

Send  chunk-first:                        0.85 ms/op  (old)
Send  compress-first:                     0.88 ms/op  (new)

Recv  chunk-first:                        1.16 ms/op  (old)
Recv  compress-first:                     0.34 ms/op  (new, 3.4x faster)
2026-03-31 11:56:17 +01:00

72 lines
1.4 KiB
C#

using Sandbox.Network;
namespace Sandbox;
public abstract partial class Connection
{
readonly Dictionary<Guid, Action<object>> _responseWaiters = new();
/// <summary>
/// Send a message to this connection, wait for a response
/// </summary>
public Task<object> SendRequest<T>( T t )
{
var requestGuid = Guid.NewGuid();
Assert.NotNull( System );
var msg = ByteStream.Create( 256 );
msg.Write( InternalMessageType.Request );
msg.Write( requestGuid );
msg.Write( InternalMessageType.Packed );
System.Serialize( t, ref msg );
SendStream( msg );
msg.Dispose();
var tcs = new TaskCompletionSource<object>();
_responseWaiters[requestGuid] = ( o ) => tcs.SetResult( o );
return tcs.Task;
}
/// <summary>
/// Send a response message to this connection.
/// </summary>
public void SendResponse<T>( Guid requestId, T t )
{
Assert.NotNull( System );
var msg = ByteStream.Create( 256 );
msg.Write( InternalMessageType.Response );
msg.Write( requestId );
msg.Write( InternalMessageType.Packed );
System.Serialize( t, ref msg );
SendStream( msg );
msg.Dispose();
}
/// <summary>
/// A response to a message has arrived, route it to the correct async function
/// </summary>
internal void OnResponse( Guid responseTo, object obj )
{
if ( !_responseWaiters.Remove( responseTo, out var waiter ) )
{
return;
}
try
{
waiter( obj );
}
catch ( Exception e )
{
Log.Warning( e );
}
}
}