OatIM.DeltaCompression 1.1.1

dotnet add package OatIM.DeltaCompression --version 1.1.1
                    
NuGet\Install-Package OatIM.DeltaCompression -Version 1.1.1
                    
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="OatIM.DeltaCompression" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OatIM.DeltaCompression" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="OatIM.DeltaCompression" />
                    
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 OatIM.DeltaCompression --version 1.1.1
                    
#r "nuget: OatIM.DeltaCompression, 1.1.1"
                    
#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 OatIM.DeltaCompression@1.1.1
                    
#: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=OatIM.DeltaCompression&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=OatIM.DeltaCompression&version=1.1.1
                    
Install as a Cake Tool

OatIM.DeltaCompression · v1.1.1

<sub>Fast, allocation-free delta compression for .NET 8+</sub>


0 TL;DR

var compressor = new DeltaCompressor<ShipState, GlobalTickContext>(maxPlayers);
compressor.SetInitialState(snapshot0);          // key-frame
await compressor.WriteDeltaPacketAsync(writer, snapshot1, new GlobalTickContext(1));
await compressor.ApplyDeltaPacketAsync(reader); // on the receiving side

1 What’s new in 1.1.x

Area Highlights
Reliability 100 % line and branch coverage. <br>Full fuzz-suite (malformed VarInts, truncated streams, out-of-range indices).
Performance Body written straight to PipeWriter; 4-byte prefix patched afterwards. <br>SwapBuffers() removes sender-side O(n) copy.
API Static-interface members:<br>• IDeltaContext.Size<br>• IDeltaSerializable.GetDeltaSize  → compile-time constants. <br>New AdvanceBaseline() helper for relay servers.
Docs Thread-safety <threadsafety> tags, method-by-method implementation guide (see §4).

2 Why delta compression?

Sending the whole snapshot every tick is wasteful. Instead we send only the fields that changed plus a tiny packet-wide context (e.g. the current tick). Typical savings for fast-moving game objects: 10×–100× smaller packets.


3 Installing

dotnet add package OatIM.DeltaCompression --version 1.1.1

Target framework(s): net8.0, net9.0.


4 Implementation guide (method-by-method)

4.1 Create your packet context IDeltaContext

Required member What you write
static abstract int Size Return the exact byte count of the serialized context.
void Write(ref PipeWriter w) Write exactly Size bytes (little-endian).
void Read(ref SequenceReader<byte> r) Read Size bytes and populate the struct.
public readonly struct GlobalTickContext : IDeltaContext
{
    public GlobalTickContext(ulong tick) => Tick = tick;
    public ulong Tick { get; }

    public static int Size => sizeof(ulong);

    public void Write(ref PipeWriter w)
    {
        var span = w.GetSpan(Size);
        BinaryPrimitives.WriteUInt64LittleEndian(span, Tick);
        w.Advance(Size);
    }

    public void Read(ref SequenceReader<byte> r)
    {
        r.TryReadLittleEndian(out ulong t);
        this = new GlobalTickContext(t);
    }
}

4.2 Create your state struct IDeltaSerializable<T,TContext>

Implement five methods:

Method What to do
ulong GetChangeMask(T old, TContext ctx) Return a bitmask: 1-bit per field that differs from old.
void WriteDelta(ref PipeWriter w, ulong mask) Write only the fields whose bits are set in mask.
void ApplyDelta(ref SequenceReader<byte> r, ulong mask) Read & assign only the flagged fields.
void ApplyContext(TContext ctx) Apply packet-wide context (e.g. copy the tick).
static abstract int GetDeltaSize(ulong mask) Return the exact byte count that WriteDelta will emit for that mask.

Example:

public struct ShipState : IDeltaSerializable<ShipState, GlobalTickContext>
{
    [Flags] private enum Bits : ulong
    {
        PosX = 1 << 0, PosY = 1 << 1, Yaw  = 1 << 2, Vel = 1 << 3
    }

    public int PosX, PosY;
    public ushort Yaw, Vel;
    public ulong Tick;

    /* 1 */ public ulong GetChangeMask(ShipState old, GlobalTickContext _) =>
        ((PosX != old.PosX) ? Bits.PosX : 0) |
        ((PosY != old.PosY) ? Bits.PosY : 0) |
        ((Yaw  != old.Yaw ) ? Bits.Yaw  : 0) |
        ((Vel  != old.Vel ) ? Bits.Vel  : 0);

    /* 2 */ public void WriteDelta(ref PipeWriter w, ulong m)
    {
        if ((m & (ulong)Bits.PosX) != 0) w.WriteIntLE(PosX);
        if ((m & (ulong)Bits.PosY) != 0) w.WriteIntLE(PosY);
        if ((m & (ulong)Bits.Yaw ) != 0) w.WriteUShortLE(Yaw);
        if ((m & (ulong)Bits.Vel ) != 0) w.WriteUShortLE(Vel);
    }

    /* 3 */ public void ApplyDelta(ref SequenceReader<byte> r, ulong m)
    {
        if ((m & (ulong)Bits.PosX) != 0) r.TryReadLittleEndian(out PosX);
        if ((m & (ulong)Bits.PosY) != 0) r.TryReadLittleEndian(out PosY);
        if ((m & (ulong)Bits.Yaw ) != 0) r.TryReadLittleEndian(out ushort yaw); Vel = yaw;
        if ((m & (ulong)Bits.Vel ) != 0) r.TryReadLittleEndian(out ushort vel); Yaw = vel;
    }

    /* 4 */ public void ApplyContext(GlobalTickContext ctx) => Tick = ctx.Tick;

    /* 5 */ public static int GetDeltaSize(ulong m) =>
        ((m & (ulong)Bits.PosX) != 0 ? 4 : 0) +
        ((m & (ulong)Bits.PosY) != 0 ? 4 : 0) +
        ((m & (ulong)Bits.Yaw ) != 0 ? 2 : 0) +
        ((m & (ulong)Bits.Vel ) != 0 ? 2 : 0);
}

Helper extensions for brevity:

internal static class PipeWriterExt
{
    public static void WriteIntLE(this ref PipeWriter w, int v)
    { var s = w.GetSpan(4); BinaryPrimitives.WriteInt32LittleEndian(s, v); w.Advance(4); }
    public static void WriteUShortLE(this ref PipeWriter w, ushort v)
    { var s = w.GetSpan(2); BinaryPrimitives.WriteUInt16LittleEndian(s, v); w.Advance(2); }
}

5 Using DeltaCompressor

// construction
var server = new DeltaCompressor<ShipState, GlobalTickContext>(maxPlayers);
var client = new DeltaCompressor<ShipState, GlobalTickContext>(maxPlayers);

// baseline sync (key-frame)
server.SetInitialState(initial); client.SetInitialState(initial);

// each tick on the server
await server.WriteDeltaPacketAsync(pipe.Writer, newSnapshot,
                                   new GlobalTickContext(tick));

// each tick on the client
await client.ApplyDeltaPacketAsync(pipe.Reader);

5.1 Relay / proxy

After the client decodes a packet and plans to re-encode it:

client.AdvanceBaseline();   // move last-sent-state → current-state

6 Thread-safety

<threadsafety>
  <static>All public static members are thread-safe.</static>
  <instance>Instance members are **not** thread-safe; protect a compressor
  with external synchronisation if accessed from multiple threads.</instance>
</threadsafety>

7 Building, testing & coverage

dotnet build -c Release
dotnet test                # 100 % coverage • 100 % branch • fuzz suite

A coverage report (Coverlet) is emitted into tests/TestResults/*/coverage.cobertura.xml.


9 License

MIT — © Oat Interactive Media 2025.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.1 108 7/31/2025
1.1.0 100 7/31/2025
1.0.0 98 7/31/2025