SetNet.Congestion 1.1.0

dotnet add package SetNet.Congestion --version 1.1.0
                    
NuGet\Install-Package SetNet.Congestion -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SetNet.Congestion" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SetNet.Congestion" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="SetNet.Congestion" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SetNet.Congestion --version 1.1.0
                    
#r "nuget: SetNet.Congestion, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SetNet.Congestion@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SetNet.Congestion&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=SetNet.Congestion&version=1.1.0
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/Povstalez/SetNet/master/assets/icon.png" alt="SetNet" width="96"> </p>

SetNet.Congestion

AIMD congestion controller for SetNet.

A CongestionController maintains a target send rate in bytes per second using additive-increase / multiplicative-decrease — the same control family TCP uses. You feed it your own delivery and loss signals; each acknowledged round nudges the rate up a little, each detected loss cuts it back sharply. Ask it for a per-tick byte budget and hand that to a SetNet.Priority sender so the connection sheds low-priority traffic under congestion instead of bloating queues.

It is signal-driven and transport-agnostic — the controller never touches a socket. You decide what "delivered" and "lost" mean (reliable-UDP acks, app-level acks, a StateSync snapshot-ack gap), which makes it usable over any transport.

Install

dotnet add package SetNet
dotnet add package SetNet.Congestion

Usage

var cc = new CongestionController(startRate: 128_000);   // 128 KB/s

// from your ack/loss tracking:
cc.OnDelivered();   // additive increase: rate += increaseBytesPerAck
cc.OnLoss();        // multiplicative decrease: rate *= decreaseFactor (default ×0.7)

// current rate, if you want to inspect it:
double bps = cc.RateBytesPerSecond;

// each tick, turn the rate into a byte budget for a priority flush:
const double tickRate = 30.0;                        // ticks/sec
int budget = cc.BudgetForInterval(1.0 / tickRate);   // bytes allowed this tick
await prioritySender.FlushAsync(budget);

Constructor & API

new CongestionController(
    double startRate           = 64_000,     // initial target rate, bytes/sec
    double minRate             = 8_000,      // floor the rate never drops below
    double maxRate             = 8_000_000,  // ceiling the rate never rises above
    double increaseBytesPerAck = 2_000,      // additive step per OnDelivered()
    double decreaseFactor      = 0.7);       // multiplier per OnLoss() (clamped to 0.1..0.99)
Member Purpose
void OnDelivered() signal a successful delivery/ack — additive increase
void OnLoss() signal a detected loss/timeout — multiplicative decrease (× decreaseFactor)
double RateBytesPerSecond the current target send rate
int BudgetForInterval(double seconds) payload bytes allowed in an interval of the given length — feed to PrioritySender.FlushAsync

The rate is always clamped to [minRate, maxRate], and decreaseFactor is clamped to 0.1..0.99 so a bad argument can't stall or disable the controller.

End-to-end: adaptive, prioritized sending

Congestion decides how much to send; SetNet.Priority decides what to send first. Together they let a connection stay responsive under load — important messages keep flowing while low-priority ones defer.

var sender = new PrioritySender(client);
var cc     = new CongestionController(startRate: 128_000);

// wire your ack/loss detection into the controller:
void OnAck()  => cc.OnDelivered();
void OnDrop() => cc.OnLoss();

const double tickRate = 30.0;

// each tick:
sender.Enqueue((ushort)MsgType.Hit,      SetNetSerializer.Serialize(hit), priority: 100, DeliveryMethod.Reliable);
sender.Enqueue((ushort)MsgType.Position, SetNetSerializer.Serialize(pos), priority: 10,  DeliveryMethod.Unreliable);

int budget = cc.BudgetForInterval(1.0 / tickRate);
await sender.FlushAsync(budget);   // high-priority first; low-priority backs up when the rate drops

When losses appear, OnLoss() shrinks the rate, BudgetForInterval returns fewer bytes, and the priority queue sheds its low-priority tail (watch sender.QueuedBytes). As acks recover, OnDelivered() grows the rate back up.

Notes

  • You define the signals. The controller has no idea what a "loss" is — call OnLoss() from reliable-UDP retransmit detection, an app-level ack timeout, or a StateSync snapshot gap, whatever fits your protocol.
  • Thread-safe. All rate mutations and reads are internally locked, so ack/loss callbacks and the tick loop can run on different threads.
  • AIMD, not a full stack. This is intentionally a small controller — a single adaptive rate. It doesn't do RTT estimation, pacing between packets, or fairness across connections; layer those on top if you need them.
  • Bytes, not packets. The budget is a byte count meant to feed PrioritySender.FlushAsync, so keep your enqueued payloads sized in bytes too.

Documentation & source

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 162 7/2/2026