RedMist.TimingCommon 1.3.9

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

Patch Class Generation System

This system provides automatic generation of patch variants for classes, enabling efficient partial updates over network protocols like SignalR with minimal payload sizes. The system now automatically generates both patch classes and Mapperly mappers.

Overview

The patch generation system consists of:

  1. [GeneratePatch] Attribute - Marks classes for patch generation
  2. PatchClassGenerator - Source generator that creates patch variants and mappers
  3. Auto-Generated Mapperly Mappers - Efficient patch application using compile-time mapping
  4. MessagePack Support - Optimized serialization for network transport

Basic Usage

1. Mark a Class for Patch Generation

[GeneratePatch]
[MessagePackObject]
public class SessionState
{
    [MessagePack.Key(0)]
    public int EventId { get; set; }
    
    [MessagePack.Key(1)]
    [MaxLength(512)]
    public string EventName { get; set; } = string.Empty;
    
    [MessagePack.Key(2)]
    public bool IsLive { get; set; }
    
    // ... more properties
}

2. Auto-Generated Patch Class

The generator automatically creates SessionStatePatch:

[MessagePackObject]
public class SessionStatePatch
{
    [MessagePack.Key(0)]
    public int? EventId { get; set; }
    
    [MessagePack.Key(1)]
    [MaxLength(512)]
    public string? EventName { get; set; }
    
    [MessagePack.Key(2)]
    public bool? IsLive { get; set; }
    
    // ... all properties become nullable
}

3. Auto-Generated Mapperly Mapper

The generator also creates SessionStateMapper with full functionality:

[Mapper]
public static partial class SessionStateMapper
{
    // Auto-generated by Mapperly
    public static partial void ApplyPatch(SessionStatePatch patch, SessionState target);
    
    // Auto-generated by Mapperly  
    public static partial SessionState PatchToEntity(SessionStatePatch patch);
    
    // Custom diff creation (generated by PatchClassGenerator)
    public static SessionStatePatch CreatePatch(SessionState original, SessionState updated);
    
    // Helper methods (generated by PatchClassGenerator)
    public static bool IsValidPatch(SessionStatePatch patch);
    public static string[] GetChangedProperties(SessionStatePatch patch);
}

4. Usage Examples

Creating and Applying Patches
// Original state
var sessionState = new SessionState
{
    EventId = 1,
    EventName = "Test Race",
    IsLive = false
};

// Create patch with only changed fields
var patch = new SessionStatePatch
{
    IsLive = true,  // Only this changed
    // EventId and EventName remain null = no change
};

// Apply patch efficiently using auto-generated mapper
SessionStateMapper.ApplyPatch(patch, sessionState);

// Validate and inspect patches
bool isValid = SessionStateMapper.IsValidPatch(patch);
string[] changedProps = SessionStateMapper.GetChangedProperties(patch);
SignalR Integration
// Server side - send minimal updates
public async Task SendSessionUpdate(SessionState newState)
{
    var patch = SessionStateMapper.CreatePatch(_previousState, newState);
    
    // Only send if there are actual changes
    if (SessionStateMapper.IsValidPatch(patch))
    {
        await Clients.All.SendAsync("SessionStateUpdate", patch);
    }
    
    _previousState = newState;
}

// Client side - apply updates
public async Task ReceiveSessionUpdate(SessionStatePatch patch)
{
    SessionStateMapper.ApplyPatch(patch, _localSessionState);
    
    // Optional: Log what changed
    var changedProps = SessionStateMapper.GetChangedProperties(patch);
    Console.WriteLine($"Updated properties: {string.Join(", ", changedProps)}");
    
    await UpdateUI();
}

Advanced Configuration

Full Configuration Options

[GeneratePatch(
    PatchClassName = "SessionDelta",
    PatchNamespace = "MyApp.Deltas",
    MapperClassName = "SessionDeltaMapper", 
    MapperNamespace = "MyApp.Deltas.Mappers",
    IncludeMessagePackAttributes = true,
    IncludeJsonAttributes = false,
    IncludeValidationAttributes = true,
    GenerateMapper = true
)]
public class CustomExample
{
    [MessagePack.Key(0)]
    public int Id { get; set; }
    
    [MessagePack.Key(1)]
    public string Name { get; set; } = string.Empty;
}

Attribute Options

  • PatchClassName - Custom name for generated patch class (default: {ClassName}Patch)
  • PatchNamespace - Custom namespace for patch class (default: same as source class)
  • MapperClassName - Custom name for generated mapper class (default: {ClassName}Mapper)
  • MapperNamespace - Custom namespace for mapper class (default: {SourceNamespace}.Mappers)
  • IncludeMessagePackAttributes - Include MessagePack serialization attributes (default: true)
  • IncludeJsonAttributes - Include System.Text.Json attributes (default: true)
  • IncludeValidationAttributes - Include validation attributes like [MaxLength] (default: true)
  • GenerateMapper - Whether to generate the Mapperly mapper class (default: true)

Patch-Only Generation

[GeneratePatch(GenerateMapper = false)]
public class PatchOnlyExample
{
    public int Value { get; set; }
    public string Description { get; set; } = string.Empty;
}
// Generates: PatchOnlyExamplePatch class only, no mapper

Generated Mapper Methods

Each auto-generated mapper includes:

Core Mapping Methods

  • ApplyPatch(patch, target) - Applies patch to existing object (generated by Mapperly)
  • PatchToEntity(patch) - Creates new object from patch (generated by Mapperly)

Utility Methods (Generated by PatchClassGenerator)

  • CreatePatch(original, updated) - Creates patch with only differences
  • IsValidPatch(patch) - Checks if patch has any changes
  • GetChangedProperties(patch) - Returns array of changed property names

Performance Benefits

Payload Size Reduction

public static void PerformanceComparison()
{
    var largeState = CreateLargeSessionState(); // 50 cars, full data
    var updatedState = largeState; // Copy
    updatedState.LapsToGo = largeState.LapsToGo - 1; // Small change
    
    // Full object serialization
    var fullBytes = MessagePackSerializer.Serialize(updatedState);
    Console.WriteLine($"Full object: {fullBytes.Length} bytes");
    
    // Patch serialization  
    var patch = SessionStateMapper.CreatePatch(largeState, updatedState);
    var patchBytes = MessagePackSerializer.Serialize(patch);
    Console.WriteLine($"Patch: {patchBytes.Length} bytes");
    
    // Typical reduction: 90%+ for small changes
    var reduction = (1.0 - (double)patchBytes.Length / fullBytes.Length) * 100;
    Console.WriteLine($"Size reduction: {reduction:F1}%");
}

Project Setup

1. Add Required Packages

<PackageReference Include="MessagePack" Version="2.5.140" />
<PackageReference Include="Riok.Mapperly" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />

2. Configure Source Generator

<ItemGroup>
  <Compile Remove="Generators\**" />
</ItemGroup>

<ItemGroup>
  <Analyzer Include="Generators\PatchClassGenerator.cs" />
</ItemGroup>

3. Build Project

After marking classes with [GeneratePatch], build the project to generate:

  • {ClassName}Patch.g.cs - The patch class
  • {ClassName}Mapper.g.cs - The Mapperly mapper (if GenerateMapper = true)

Benefits

1. Minimal Network Payload

  • Only changed fields are serialized and transmitted
  • Typically 80-95% reduction in payload size for small changes
  • Ideal for real-time applications with frequent updates

2. Zero Boilerplate

  • No manual patch class creation
  • No manual mapper implementation
  • Automatic synchronization with source class changes

3. Type Safety & Performance

  • Generated patch classes maintain full type safety
  • Mapperly generates optimized mapping code (no reflection)
  • MessagePack provides efficient binary serialization
  • IntelliSense support for all generated classes

4. Rich Functionality

  • Built-in validation (IsValidPatch)
  • Change tracking (GetChangedProperties)
  • Flexible configuration options
  • Seamless Mapperly integration

Best Practices

1. Use for Frequently Updated Objects

Ideal for objects that:

  • Change frequently (live race data, real-time metrics)
  • Are transmitted over networks regularly
  • Have many properties but few change at once
  • Need efficient bandwidth usage

2. MessagePack Key Management

[GeneratePatch]
[MessagePackObject]
public class EvolvingClass
{
    [MessagePack.Key(0)] public int Id { get; set; }
    [MessagePack.Key(1)] public string Name { get; set; } = "";
    // When adding new properties, use next available key
    [MessagePack.Key(2)] public DateTime CreatedAt { get; set; }
    // Never reuse or change existing key numbers for compatibility
}
// Good - batch related changes in one patch
var patch = new SessionStatePatch
{
    LapsToGo = 5,
    TimeToGo = "00:15:30", 
    CurrentFlag = Flags.Yellow
};

// Avoid - multiple individual patches for related changes

4. Validate Before Sending

public async Task SendUpdate<T>(T patch) where T : class
{
    // Only send if patch contains changes
    if (IsValidPatch(patch))
    {
        await hubContext.Clients.All.SendAsync("Update", patch);
    }
}

Integration with SignalR

1. Configure MessagePack Protocol

// Server
services.AddSignalR()
    .AddMessagePackProtocol();

// Client  
connection = new HubConnectionBuilder()
    .WithUrl("/timingHub")
    .AddMessagePackProtocol()
    .Build();

2. Hub Implementation

public class TimingHub : Hub
{
    public async Task SubscribeToSessionUpdates(int eventId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, $"Event_{eventId}");
    }
}

// Background service
public class TimingService
{
    private SessionState _lastState = new();
    
    public async Task BroadcastUpdate(SessionState newState, int eventId)
    {
        var patch = SessionStateMapper.CreatePatch(_lastState, newState);
        
        if (SessionStateMapper.IsValidPatch(patch))
        {
            await _hubContext.Clients.Group($"Event_{eventId}")
                .SendAsync("SessionStateUpdate", patch);
                
            // Log what changed for debugging
            var changes = SessionStateMapper.GetChangedProperties(patch);
            _logger.LogDebug("Updated properties: {Properties}", string.Join(", ", changes));
        }
        
        _lastState = newState;
    }
}

Troubleshooting

Common Issues

  1. Generator Not Running

    • Clean and rebuild the project
    • Ensure <Analyzer Include="..."> is correctly configured
    • Check Visual Studio output for generator errors
  2. Mapperly Compilation Errors

    • Ensure patch class exists before mapper generation
    • Check that all referenced types are accessible
    • Verify Mapperly package is installed
  3. MessagePack Serialization Issues

    • Verify all [MessagePack.Key] attributes have unique indices
    • Ensure referenced types are MessagePack-serializable
    • Check for circular references in object graphs
  4. Patch Not Generated

    • Verify [GeneratePatch] attribute is applied
    • Check that class is public and has public properties
    • Ensure class is not generic or abstract

Debug Generated Code

Generated files are available in your project under:

obj/Debug/net9.0/generated/RedMist.TimingCommon.Generators.PatchClassGenerator/
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.3.9 31 3/15/2026
1.3.8 37 3/14/2026
1.3.7 115 3/9/2026
1.3.6 325 2/20/2026
1.3.5 190 2/8/2026
1.3.4 213 2/6/2026
1.3.3 308 1/22/2026
1.3.2 170 1/3/2026
1.3.1 144 12/27/2025
1.3.0 189 12/25/2025
1.2.8 234 12/13/2025
1.2.7 257 11/30/2025
1.2.6 120 11/29/2025
1.2.5 298 11/11/2025
1.2.4 294 11/11/2025
1.2.3 146 11/7/2025
1.2.2 195 11/3/2025
1.2.1 199 11/2/2025
1.2.0 195 11/2/2025
1.1.6 308 10/24/2025
Loading failed