Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ For consumer-app usage patterns, read `docs/ai/SDK_USAGE.md` first and `docs/ai/
```powershell
dotnet build .\Sdk\SVappsLAB.iRacingTelemetrySDK.slnx
dotnet build .\Samples\Samples.slnx
dotnet test .\Sdk\tests\UnitTests\UnitTests.csproj --no-build
dotnet test --project .\Sdk\tests\UnitTests\UnitTests.csproj --no-build
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=ibt
dotnet pack .\Sdk\SVappsLAB.iRacingTelemetrySDK\SVappsLAB.iRacingTelemetrySDK.csproj
```

Run `Category=live` smoke tests only with iRacing running in an active Windows session. Run `Category=manual` tests only when the local developer setup matches the test requirements.

`global.json` pins `test.runner` to `Microsoft.Testing.Platform`, so `dotnet test` needs `--project`/`--solution` (a bare positional path is rejected) and does not understand VSTest-style `--filter`. Use `dotnet run --project`, not `dotnet test`, for `SmokeTests` - its `launchSettings.json` injects a default `--filter-class` that `dotnet test` combines with (rather than overrides with) any `--filter-trait` you add, silently yielding "zero tests ran". See `Sdk/tests/README.md` for details.

## Style

- Target .NET 8 for the SDK.
Expand Down
19 changes: 16 additions & 3 deletions NUGET.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
# iRacing Telemetry SDK for C# .NET
# iRacing SDK for C# .NET

High-performance .NET SDK for accessing **live telemetry data** from iRacing simulator and **IBT file playback**. Features compile-time code generation for strongly-typed telemetry access with lock-free performance optimizations.
High-performance .NET SDK for accessing **live telemetry data** from iRacing simulator, **IBT file playback**, and **simulator control**. Features compile-time code generation for strongly-typed telemetry access with lock-free performance optimizations, plus a command API for driving cameras, replay, pit service, and more.

## Why Use This SDK?

- **Type Safety**: Enum-based telemetry variables with IntelliSense/Copilot support and compile-time validation
- **High Performance**: Processes 600,000+ telemetry records/second with lock-free data streaming architecture
- **Background Processing**: Dedicated threads for telemetry collection and processing - your app's processing speed never blocks the streaming telemetry data
- **Modern Async API**: Async data streams with async/await patterns and automatic backpressure handling
- **Modern Async API**: Async data streams with async/await patterns, bounded buffering, and automatic overload handling
- **Dynamic Variable Lookup**: Look up any telemetry variable by name at runtime with `GetValue(string)` - no compile-time struct required
- **Live Telemetry**: Real-time access to speed, RPM, tire data, and 200+ variables during iRacing sessions
- **IBT File Support**: Cross-platform playback of historical telemetry using the same strongly-typed API
- **Robust Buffering**: 60-sample ring buffer (1 second at 60Hz) ensures reliable data delivery even when your app can't keep pace
- **Simulator Control**: Send commands *to* the simulator - cameras, replay, pit service, chat, telemetry recording, and more

## How It Compares

Most .NET iRacing libraries are thin wrappers over the simulator's shared-memory layout — variables looked up by string name at runtime, data delivered through events on the caller's thread. This SDK treats telemetry as a **typed, high-throughput data stream** instead:

- **Compile-time generated structs** instead of runtime string/dictionary lookups — typos are build errors, and only the variables you declare are ever decoded
- **Non-blocking async data streams with bounded backpressure** instead of callbacks that block further frame processing until your handler returns — your processing speed never stalls data collection
- **The same strongly-typed API for live and IBT playback** — most alternatives are live-only, or treat file playback as a separate path
- **Built-in `System.Diagnostics.Metrics`** integration (rates, dropped records, histograms), so telemetry health plugs directly into your existing observability stack

Full comparison and design rationale: [Architecture and Design](https://github.com/SVappsLAB/iRacingTelemetrySDK/blob/main/docs/ARCHITECTURE.md)

## Architecture Benefits

**Production-Ready Data Streaming:**
Expand Down Expand Up @@ -142,6 +154,7 @@ See the [SimControl sample](https://github.com/SVappsLAB/iRacingTelemetrySDK/tre

- **[Getting Started Guide](https://github.com/SVappsLAB/iRacingTelemetrySDK#readme)** - Setup and basic usage
- **[Sample Projects](https://github.com/SVappsLAB/iRacingTelemetrySDK/tree/main/Samples)** - Basic monitoring, data export, track analysis
- **[Architecture and Design](https://github.com/SVappsLAB/iRacingTelemetrySDK/blob/main/docs/ARCHITECTURE.md)** - Threading model, buffering, performance, and metrics
- **[Advanced Usage](https://github.com/SVappsLAB/iRacingTelemetrySDK/blob/main/docs/ADVANCED.md)** - Direct stream access, multiple consumers, and cancellation behavior
- **[Migration Guide](https://github.com/SVappsLAB/iRacingTelemetrySDK/blob/main/docs/MIGRATION_GUIDE.md)** - Upgrading from previous versions
- **[GitHub Repository](https://github.com/SVappsLAB/iRacingTelemetrySDK)** - Source code and releases
Expand Down
221 changes: 74 additions & 147 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ Perfect for building **real-time dashboards**, **data analysis tools**, **race e
## Table of Contents

- [Features](#features)
- [How It Compares](#how-it-compares)
- [Requirements](#requirements)
- [Quick Example](#quick-example)
- [Getting Started](#getting-started)
- [Understanding Telemetry Variables](#understanding-telemetry-variables)
- [Controlling the Simulator](#controlling-the-simulator)
- [Samples](#samples)
- [Advanced Usage](./docs/ADVANCED.md)
- [Architecture and Design](./docs/ARCHITECTURE.md)
- [Documentation](#documentation)
- [AI-Assisted Development](#ai-assisted-development)
- [Performance and Design](#performance-and-design)
- [Performance Monitoring](#performance-monitoring)
- [Building from Source](#building-from-source)
- [License](#license)

Expand All @@ -31,15 +31,45 @@ Perfect for building **real-time dashboards**, **data analysis tools**, **race e
- **Background Processing**: Dedicated threads for telemetry collection and processing - your app's processing speed never blocks the streaming telemetry data
- **Live Telemetry**: Real-time access to 200+ variables including speed, RPM, tire data during iRacing sessions
- **IBT File Playback**: Analyze historical telemetry using the same API as live data
- **Modern Async API**: Async data streams with automatic backpressure handling
- **Modern Async API**: Async data streams with bounded buffering and automatic overload handling
- **Dynamic Variable Lookup**: Look up any telemetry variable by name at runtime via `GetValue(string)` - useful when the variable set isn't known at compile time
- **Built-in Metrics**: Integrated performance monitoring via System.Diagnostics.Metrics
- **Pause and Resume**: Control data flow while background processing continues
- **Sim Control**: Remotely control the simulator (pit commands, replay, cameras, chat, and more)

## How It Compares

There are several .NET libraries for reading iRacing telemetry.
Most are thin wrappers over the iRacing shared-memory layout: variables are done by string name at runtime, receive data through events on the caller's thread.

This SDK takes a different approach. It treats telemetry as a **typed, high-throughput data stream** rather than a bag of named values.

| | **iRacingTelemetrySDK** | **Typical .NET iRacing libraries** |
|---|---|---|
| **Variable access** | Compile-time generated `TelemetryData` struct — only the variables you declare | Runtime lookup by string name or dictionary indexing |
| **Type safety** | Enum-based selection, validated at build time, full IntelliSense | Strings resolved at runtime; typos surface as runtime errors or nulls |
| **API model** | Async data streams with `async`/`await`, bounded buffering, and automatic overload handling | Blocking event handlers or manual polling loops |
| **Threading** | Dedicated background tasks for collection and YAML parsing — your handler never blocks the data source | Callbacks commonly block further frame processing until they return |
| **Backpressure** | Bounded 60-sample ring buffer with drop-oldest; slow consumers cost bounded data, never memory | Not supported |
| **Live + IBT parity** | Identical strongly-typed API for both | Frequently separate code paths, or live-only |
| **Throughput** | 600,000+ records/sec on IBT playback | Unknown |
| **Observability** | Built-in `System.Diagnostics.Metrics` counters and histograms | Not supported |
| **Simulator control** | Pit, replay, camera, chat, and broadcast commands included | Varies |
| **AI agent support** | Dedicated agent-facing usage and reference docs | Rare |


**Why it's fast:**

- **Source-generated structs** eliminate runtime reflection and string lookups from the hot path — only the variables you asked for are ever decoded
- **Lock-free bounded streams** keep telemetry current under load instead of queueing unboundedly behind a slow consumer
- **Minimal-allocation reads** — each sample is copied once into a reused buffer (guarding against iRacing overwriting it mid-read), then decoded field-by-field via `ReadOnlySpan<T>` with no further allocations, keeping steady-state GC pressure near zero
- **Independent background tasks** mean CPU-intensive session-info YAML parsing never stalls the 60Hz telemetry path

Full details, including the threading model, buffering semantics, and built-in metrics: **[Architecture and Design](./docs/ARCHITECTURE.md)**

## Requirements

- **.NET 8.0+**
- **Windows** for live iRacing telemetry

## Quick Example

Expand Down Expand Up @@ -212,20 +242,51 @@ All telemetry properties are nullable (`float?`, `int?`, `bool?`) to accurately
**Recommended patterns for handling null values:**

```csharp
// ✅ Null-conditional formatting
// Null-conditional formatting
var speedDisplay = $"Speed: {data.Speed?.ToString("F1") ?? "N/A"}";

// ✅ Direct arithmetic (preserves null semantics)
// Direct arithmetic (preserves null semantics)
var speedMph = data.Speed * 2.23694f; // Result is null if Speed is null

// ✅ Explicit null handling
// Explicit null handling
var speed = data.Speed ?? 0f;
var hasValue = data.Speed.HasValue;

// ✅ Boolean checks
// Boolean checks
if (data.IsOnTrackCar == true) { /* ... */ }
```

### Dynamic Variable Lookup

For scenarios where the telemetry variables you need aren't known until runtime, like a dashboard that lets end users pick which values to display, or an integration (such as a game engine) that prefers a string-keyed lookup over a compile-time generated struct - use `GetValue(string)` instead of (or alongside) the strongly-typed `T`:

```csharp
// no RequiredTelemetryVars needed - use the DynamicTelemetryData placeholder
// GetValue requires Synchronous delivery mode - see below
var clientOptions = new ClientOptions { DeliveryMode = TelemetryDeliveryMode.Synchronous };
await using var client = TelemetryClient<DynamicTelemetryData>.Create(logger, ibtOptions: null, clientOptions);

var handlers = new TelemetryHandlers<DynamicTelemetryData>
{
OnTelemetryUpdate = _ =>
{
if (client.GetValue("Speed") is float speed)
{
Console.WriteLine($"Speed: {speed}");
}
return Task.CompletedTask;
}
};

await client.Monitor(handlers, cts.Token);
```

`GetValue` matches names case-insensitively and works for any variable reported by `GetTelemetryVariables()`. It returns an 'object' or a 'null' if the variable is unknown.

The SDK only works in one delivery mode at a time, you can't create the client in one mode and read data in the other. `GetValue` is only usable when the client is created with `new ClientOptions { DeliveryMode = TelemetryDeliveryMode.Synchronous }`.

Note: in `Synchronous` mode, each sample is blocking and awaited directly by your handler before the next record is read.

## Controlling the Simulator

In addition to reading telemetry, the SDK can send commands *to* the simulator. Commands are grouped by feature
Expand Down Expand Up @@ -264,6 +325,7 @@ See [Samples Directory](./Samples/README.md) for ready-to-run example projects i

## Documentation

- **[Architecture and Design](./docs/ARCHITECTURE.md)** - Threading model, data streaming and buffering, performance characteristics, and built-in metrics
- **[Advanced Usage](./docs/ADVANCED.md)** - Direct stream access, multiple consumers, and cancellation behavior
- **[Migration Guide](./docs/MIGRATION_GUIDE.md)** - Upgrading from early pre-1.0 releases

Expand All @@ -282,140 +344,6 @@ https://raw.githubusercontent.com/SVappsLAB/iRacingTelemetrySDK/main/docs/ai/SDK
and https://raw.githubusercontent.com/SVappsLAB/iRacingTelemetrySDK/main/docs/ai/SDK_REFERENCE.md for advanced patterns.
```

## Performance and Design

The SDK is designed for high performance with bounded async data streaming that keeps telemetry current under load.

### Data Streaming Benefits

**Data Safety:**
- **60-sample ring buffer** with FIFO (First-In-First-Out) and destructive-read semantics
- Provides **up to 1 second** of buffering at iRacing's 60Hz update rate
- When the buffer fills, **oldest unread samples are automatically dropped** to make room for new data
- **Drop-oldest strategy** ensures the SDK never blocks and prioritizes the most recent telemetry

**Performance:**
- **Non-blocking bounded streams** reduce contention and keep telemetry current under load
- **Asynchronous processing** keeps your main thread responsive
- **~2x performance improvement** over traditional event-based approaches
- **600,000+ records/sec** processing capability for IBT files

**Flexibility:**
- **Two consumption patterns**: Handler callbacks (`Monitor(handlers, ct)`) or direct stream access
- **Independent streams**: Consume only the data types you need
- **Cancellation support**: Graceful shutdown with `CancellationToken`

### Architecture Overview

**Key Design Principles:**

- **Strongly Typed Telemetry Data**: Use strong type checking throughout
- **Non-blocking Streams**: All real-time data flows through asynchronous data streams
- **Separation of Concerns**: Telemetry data (high-frequency, time-critical) and Session Info processing (low frequency, CPU-intensive) run independently
- **Background Processing**: CPU-intensive YAML parsing runs in an independent async processing task so telemetry data handling stays responsive.

```mermaid
graph TB
subgraph "Data Sources"
iRacing[iRacing Simulator<br/>60Hz Live Data]
IBT[IBT File<br/>Historical Data]
end

subgraph "Task1 - Telemetry Data"
MainTask[Telemetry Data Stream]
end

subgraph "Task2 - Session Info"
SessionTask[Session Info Processing<br/>YAML Parsing]
end

subgraph "High Performance Data Streams"
TelemetryStream[TelemetryData]
SessionStream[SessionData]
RawStream[SessionDataYaml]
end

iRacing --> MainTask
IBT --> MainTask

MainTask --> TelemetryStream
MainTask --> SessionTask
SessionTask --> SessionStream
SessionTask --> RawStream

classDef dataSource fill:#e1f5fe
classDef processing fill:#f3e5f5
classDef stream fill:#e8f5e8

class iRacing,IBT dataSource
class MainTask,SessionTask processing
class TelemetryStream,SessionStream,RawStream stream
```


### Performance Monitoring

The SDK includes built-in support for System.Diagnostics.Metrics to help monitor performance and diagnose issues.

#### Available Metrics

The SDK exposes several metrics automatically:

- **telemetry_records_processed_total**: Counter of processed telemetry records
- **telemetry_records_dropped_total**: Counter of dropped records (when channels are full)
- **telemetry_processing_duration_microseconds**: Histogram of telemetry processing time
- **sessioninfo_records_processed_total**: Counter of session info updates processed
- **sessioninfo_processing_duration_milliseconds**: Histogram of session info processing time

#### Enabling Metrics with Dependency Injection

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

[RequiredTelemetryVars([TelemetryVar.Speed, TelemetryVar.RPM])]
public class Program
{
public static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddMetrics(); // Enable metrics support
services.AddLogging(logging => logging.AddConsole());
})
.Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
var meterFactory = host.Services.GetRequiredService<IMeterFactory>();

// Create client with metrics support
var clientOptions = new ClientOptions { MeterFactory = meterFactory };
await using var client = TelemetryClient<TelemetryData>.Create(logger, null, clientOptions);

// Your telemetry code here...
}
}
```

#### Monitoring with dotnet-counters

Use any monitoring tool that supports System.Diagnostics.Metrics, like OpenTelemetry or the free Microsoft provided `dotnet-counters` tool to monitor SDK performance in real-time:

```bash
# Monitor all SDK metrics for a running application named "YourApp"
dotnet-counters monitor --name "YourApp" --counters SVappsLAB.iRacingTelemetrySDK

# Sample output:
# [SVappsLAB.iRacingTelemetrySDK]
# telemetry_records_processed_total 45,231
# telemetry_records_dropped_total 0
# sessioninfo_records_processed_total 12
```

This helps identify performance bottlenecks, monitor processing rates, and detect if records are being dropped due to slow consumption.

## Building from Source

If you've cloned or forked the repository, you can build, test, and package the SDK with the standard .NET CLI from the repository root.
Expand All @@ -437,7 +365,7 @@ Tests are split into categories so you can run subsets based on your environment

```bash
# unit tests only
dotnet test .\Sdk\tests\UnitTests\UnitTests.csproj
dotnet test --project .\Sdk\tests\UnitTests\UnitTests.csproj

# repeatable offline smoke tests using bundled IBT files
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=ibt
Expand All @@ -446,19 +374,18 @@ dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=live

# all test projects, including tests that may require live/manual setup
dotnet test .\Sdk\SVappsLAB.iRacingTelemetrySDK.slnx
dotnet test --solution .\Sdk\SVappsLAB.iRacingTelemetrySDK.slnx
```

See [Sdk/tests/README.md](./Sdk/tests/README.md) for manual test commands and filtering notes.

### Running the samples

```bash
# live iRacing data
dotnet run --project .\Samples\MinimalExample\MinimalExample.csproj
dotnet run --project .\Samples\MinimalExampleAsync\MinimalExampleAsync.csproj

# IBT file playback
dotnet run --project .\Samples\MinimalExample\MinimalExample.csproj -- path\to\file.ibt
dotnet run --project .\Samples\MinimalExampleAsync\MinimalExampleAsync.csproj -- path\to\file.ibt
```

See the [Samples](./Samples/README.md) directory for the individual example projects.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
<PackageReference Include="SVappsLAB.iRacingTelemetrySDK" Version="2.2.0" />
<PackageReference Include="SVappsLAB.iRacingTelemetrySDK" Version="2.3.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion Samples/LocationAndWarnings/LocationAndWarnings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
<PackageReference Include="SVappsLAB.iRacingTelemetrySDK" Version="2.2.0" />
<PackageReference Include="SVappsLAB.iRacingTelemetrySDK" Version="2.3.0" />
</ItemGroup>

</Project>
Loading
Loading