Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1b5fc85
Added F1 2025 packets
codegefluester Feb 8, 2026
05c82b9
Added F1 2024 types
codegefluester Feb 9, 2026
06cbfb8
Added F1 2023 structs
codegefluester Feb 9, 2026
d267c63
Added F1 packets and fixtures
codegefluester Feb 10, 2026
7e59998
Added F1 2025 packets
codegefluester Feb 8, 2026
2d0610e
Added F1 2024 types
codegefluester Feb 9, 2026
33773f4
Added F1 2023 structs
codegefluester Feb 9, 2026
03c50c9
Added F1 packets and fixtures
codegefluester Feb 10, 2026
6cc4471
Remove legacy F1 implementation workflow file
codegefluester Feb 10, 2026
4d548e7
Merge branch 'feat/udp-base-source' of https://github.com/codegeflues…
codegefluester Feb 10, 2026
b91ad7c
Remove debug tooling for F1
codegefluester Feb 10, 2026
8157ec0
Add F1 fixture files to test project
codegefluester Feb 10, 2026
56a7f24
Add refactoring plan for telemetry source interface
codegefluester Feb 10, 2026
c2941a3
Remove debug logging
codegefluester Feb 10, 2026
3831a8d
Added test cases for F1 packet parsing (super basic)
codegefluester Feb 10, 2026
c94d0f3
Added tests for event details parsing in F1 2025
codegefluester Feb 10, 2026
2781cda
Tested F1 recording and reading of session recording
codegefluester Feb 10, 2026
2eccdde
Update GamesDat.Demo.Wpf/Resources/Styles.xaml
codegefluester Feb 10, 2026
9e1e0cb
Update GamesDat.Demo.Wpf/ViewModels/IRealtimeSource.cs
codegefluester Feb 10, 2026
5e25ff0
[WIP] WIP Address feedback on adding source for F1 games (#5)
Copilot Feb 10, 2026
007b7a3
Apply BufferSize to UDP socket ReceiveBufferSize (#6)
Copilot Feb 10, 2026
f238435
Refactor UdpSourceBase to use proper Dispose pattern (#7)
Copilot Feb 10, 2026
d100533
Make LiveryColour struct fields public for deserialization access (#8)
Copilot Feb 10, 2026
1b732a9
Remove unused BytesToStruct method from F1RealtimeTelemetrySource (#9)
Copilot Feb 10, 2026
d23cb84
[WIP] WIP address feedback from review on adding source for F1 games …
Copilot Feb 10, 2026
5519f4f
Fix unsafe fixed buffer pointer handling in F1 telemetry extensions (…
Copilot Feb 11, 2026
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
409 changes: 409 additions & 0 deletions .claude/skills/f1-add-game/SKILL.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,6 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd

.claude/settings.local.json
**/.claude/settings.local.json

DebugTooling/**/*
9 changes: 0 additions & 9 deletions GamesDat.Demo.Wpf/.claude/settings.local.json

This file was deleted.

2 changes: 1 addition & 1 deletion GamesDat.Demo.Wpf/GamesDat.Demo.Wpf.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
5 changes: 4 additions & 1 deletion GamesDat.Demo.Wpf/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"profiles": {
"GamesDat.Demo.Wpf": {
"commandName": "Project"
"commandName": "Project",
"environmentVariables": {
"DEBUG": "1"
}
}
}
}
166 changes: 166 additions & 0 deletions GamesDat.Demo.Wpf/ViewModels/F1RealtimeSourceViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GamesDat.Core;
using GamesDat.Core.Telemetry.Sources;
using GamesDat.Core.Telemetry.Sources.Formula1;
using GamesDat.Core.Writer;
using System.Windows.Input;

namespace GamesDat.Demo.Wpf.ViewModels;

public partial class F1RealtimeSourceViewModel : ViewModelBase, IRealtimeSource
{
private GameSession? _gameSession;
private CancellationTokenSource? _cts;

[ObservableProperty]
private string _sourceName = "Formula 1";

[ObservableProperty]
private bool _isRunning;

[ObservableProperty]
private string _statusMessage = "Stopped";

[ObservableProperty]
private int _totalPacketsReceived;

[ObservableProperty]
private string? _sessionOutputPath;

[ObservableProperty]
private int _port = 20777;

// Explicit interface implementations for command covariance
ICommand IRealtimeSource.StartCommand => StartCommand;
ICommand IRealtimeSource.StopCommand => StopCommand;
ICommand IRealtimeSource.ClearDataCommand => ClearDataCommand;

[RelayCommand(CanExecute = nameof(CanStart))]
private async Task StartAsync()
{
if (IsRunning) return;

try
{
StatusMessage = "Starting...";

// Create F1 realtime telemetry source
var options = new UdpSourceOptions
{
Port = Port
};

// Set output path for session recording
SessionOutputPath = $"./sessions/f1_session_{DateTime.UtcNow:yyyy-MM-dd_HH-mm-ss}.dat";

F1RealtimeTelemetrySource f1Source;
try
{
f1Source = new F1RealtimeTelemetrySource(options);
f1Source.OutputTo(SessionOutputPath).UseWriter(new BinarySessionWriter());
}
catch (System.Net.Sockets.SocketException ex)
{
StatusMessage = $"Failed to bind to port {Port}: {ex.Message}. Another application may be using this port.";
IsRunning = false;
return;
}

// Create game session
_gameSession = new GameSession(defaultOutputDirectory: "./sessions")
.AddSource(f1Source);

// Simple callback to count packets
_gameSession.OnData<F1TelemetryFrame>(frame =>
{
Interlocked.Increment(ref _totalPacketsReceived);

// Update status message periodically
var count = _totalPacketsReceived;
if (count == 1)
{
StatusMessage = $"Receiving data on UDP port {Port}";
}
else if (count % 100 == 0)
{
StatusMessage = $"Running... ({count} packets)";
}
});

// Start the session
_cts = new CancellationTokenSource();
IsRunning = true;

// Start game session in background
_ = Task.Run(async () =>
{
try
{
await _gameSession.StartAsync(_cts.Token);
}
catch (OperationCanceledException)
{
// Normal cancellation
}
catch (Exception ex)
{
StatusMessage = $"Session Error: {ex.Message}";
IsRunning = false;
}
});

// Give it a moment to start
await Task.Delay(500);

StatusMessage = $"Listening on UDP port {Port} (Waiting for F1 game data...)";
}
catch (Exception ex)
{
StatusMessage = $"Startup Error: {ex.Message}";
IsRunning = false;
}
}

private bool CanStart() => !IsRunning;

[RelayCommand(CanExecute = nameof(CanStop))]
private async Task StopAsync()
{
_cts?.Cancel();

if (_gameSession != null)
{
await _gameSession.StopAsync();
await _gameSession.DisposeAsync();
_gameSession = null;
}

IsRunning = false;
StatusMessage = "Stopped";
}

private bool CanStop() => IsRunning;

[RelayCommand]
private void ClearData()
{
TotalPacketsReceived = 0;
}

partial void OnIsRunningChanged(bool value)
{
StartCommand.NotifyCanExecuteChanged();
StopCommand.NotifyCanExecuteChanged();
}

public void Dispose()
{
_cts?.Cancel();
_gameSession?.StopAsync().GetAwaiter().GetResult();
_gameSession?.DisposeAsync().AsTask().GetAwaiter().GetResult();
_gameSession = null;
_cts?.Dispose();
GC.SuppressFinalize(this);
}
}
13 changes: 13 additions & 0 deletions GamesDat.Demo.Wpf/ViewModels/IRealtimeSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Windows.Input;

namespace GamesDat.Demo.Wpf.ViewModels;

public interface IRealtimeSource : IDisposable
{
string SourceName { get; }
bool IsRunning { get; }
string StatusMessage { get; }
ICommand StartCommand { get; }
ICommand StopCommand { get; }
ICommand ClearDataCommand { get; }
}
8 changes: 7 additions & 1 deletion GamesDat.Demo.Wpf/ViewModels/RealtimeSourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Windows.Input;

namespace GamesDat.Demo.Wpf.ViewModels;

public partial class RealtimeSourceViewModel : ViewModelBase, IDisposable
public partial class RealtimeSourceViewModel : ViewModelBase, IRealtimeSource
{
private readonly Func<GameSession>? _sessionFactory;
private GameSession? _session;
Expand Down Expand Up @@ -41,6 +42,11 @@ public partial class RealtimeSourceViewModel : ViewModelBase, IDisposable

public ObservableCollection<TelemetryDataPoint> DataPoints { get; } = [];

// Explicit interface implementations for command covariance
ICommand IRealtimeSource.StartCommand => StartCommand;
ICommand IRealtimeSource.StopCommand => StopCommand;
ICommand IRealtimeSource.ClearDataCommand => ClearDataCommand;

public RealtimeSourceViewModel(
string sourceName,
Func<GameSession>? sessionFactory)
Expand Down
8 changes: 6 additions & 2 deletions GamesDat.Demo.Wpf/ViewModels/RealtimeTabViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace GamesDat.Demo.Wpf.ViewModels;

public partial class RealtimeTabViewModel : ViewModelBase, IDisposable
{
public ObservableCollection<RealtimeSourceViewModel> Sources { get; } = [];
public ObservableCollection<IRealtimeSource> Sources { get; } = [];

[ObservableProperty]
private RealtimeSourceViewModel? _selectedSource;
private IRealtimeSource? _selectedSource;

public RealtimeTabViewModel()
{
Expand Down Expand Up @@ -72,6 +72,10 @@ private void InitializeBuiltInSources()

trackmaniaSourceRef = trackmaniaSource;
Sources.Add(trackmaniaSource);

// Add F1 source
var f1Source = new F1RealtimeSourceViewModel();
Sources.Add(f1Source);
}

[RelayCommand]
Expand Down
Loading
Loading