Skip to content

Commit c9b4199

Browse files
committed
feat: move session timing to channel
1 parent e9b7d99 commit c9b4199

23 files changed

Lines changed: 1277 additions & 55 deletions

docs/IMPLEMENTATION_PLAN.md

Lines changed: 23 additions & 19 deletions
Large diffs are not rendered by default.

src/app/bridge/iracingSdk/iracingSdkBridge.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { RelativeGapRuntime } from '../../processors/relativeGapRuntime';
1717
import { SectorTimingRuntime } from '../../processors/sectorTimingRuntime';
1818
import { StandingsRuntime } from '../../processors/standingsRuntime';
1919
import { RadioRuntime } from '../../processors/radioRuntime';
20+
import { SessionTimingRuntime } from '../../processors/sessionTimingRuntime';
2021

2122
// Keys consumed by the renderer. Anything outside this set is dropped before
2223
// the telemetry object crosses the IPC boundary — reducing structured-clone
@@ -208,6 +209,16 @@ export async function publishIRacingSDKEvents(
208209
lifecycle && channelBus
209210
? new RadioRuntime(channelBus, lifecycle, perfMetrics, isTapeReplay)
210211
: undefined;
212+
const sessionTimingRuntime =
213+
lifecycle && channelBus && lapTimesRuntime
214+
? new SessionTimingRuntime(
215+
channelBus,
216+
lifecycle,
217+
perfMetrics,
218+
lapTimesRuntime,
219+
isTapeReplay
220+
)
221+
: undefined;
211222

212223
let shouldStop = false;
213224
let lastRunningState: boolean | undefined = undefined;
@@ -312,6 +323,7 @@ export async function publishIRacingSDKEvents(
312323
sectorTimingRuntime?.onFrame(telemetry);
313324
standingsRuntime?.onFrame(telemetry);
314325
radioRuntime?.onFrame(telemetry);
326+
sessionTimingRuntime?.onFrame(telemetry);
315327
if (
316328
perfTelemetryDeliveryEnabled &&
317329
overlayManager.hasLegacyStreamSubscribers('telemetry')
@@ -346,6 +358,7 @@ export async function publishIRacingSDKEvents(
346358
relativeGapRuntime?.onSession(session);
347359
sectorTimingRuntime?.onSession(session);
348360
standingsRuntime?.onSession(session);
361+
sessionTimingRuntime?.onSession(session);
349362
overlayManager.publishMessage('sessionData', session);
350363
sessionCallbacks.forEach((callback) => callback(session));
351364
perfMetrics.markEnd('sessionPublish');
@@ -409,6 +422,7 @@ export async function publishIRacingSDKEvents(
409422
sectorTimingRuntime?.dispose();
410423
standingsRuntime?.dispose();
411424
radioRuntime?.dispose();
425+
sessionTimingRuntime?.dispose();
412426
referenceLapRuntime?.dispose();
413427
perfMetrics.stopReporting();
414428
},

src/app/bridge/iracingSdk/mock-data/mockSdkBridge.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { RelativeGapRuntime } from '../../../processors/relativeGapRuntime';
99
import { SectorTimingRuntime } from '../../../processors/sectorTimingRuntime';
1010
import { StandingsRuntime } from '../../../processors/standingsRuntime';
1111
import { RadioRuntime } from '../../../processors/radioRuntime';
12+
import { SessionTimingRuntime } from '../../../processors/sessionTimingRuntime';
13+
import { LapTimesRuntime } from '../../../processors/lapTimesRuntime';
1214

1315
export async function publishIRacingSDKEvents(
1416
overlayManager: OverlayManager,
@@ -19,6 +21,10 @@ export async function publishIRacingSDKEvents(
1921
perfMetrics.startReporting();
2022

2123
const bridge = generateMockData();
24+
const lapTimesRuntime =
25+
channelBus && lifecycle
26+
? new LapTimesRuntime(channelBus, lifecycle, perfMetrics)
27+
: undefined;
2228
const carSpeedsRuntime = channelBus
2329
? new CarSpeedsRuntime(channelBus, lifecycle, perfMetrics)
2430
: undefined;
@@ -46,24 +52,36 @@ export async function publishIRacingSDKEvents(
4652
const radioRuntime = channelBus
4753
? new RadioRuntime(channelBus, lifecycle, perfMetrics)
4854
: undefined;
55+
const sessionTimingRuntime =
56+
channelBus && lapTimesRuntime
57+
? new SessionTimingRuntime(
58+
channelBus,
59+
lifecycle,
60+
perfMetrics,
61+
lapTimesRuntime
62+
)
63+
: undefined;
4964

5065
bridge.onSessionData((session) => {
5166
carSpeedsRuntime?.onSession(session);
5267
referenceLapRuntime?.onSession(session);
5368
relativeGapRuntime?.onSession(session);
5469
sectorTimingRuntime?.onSession(session);
5570
standingsRuntime?.onSession(session);
71+
sessionTimingRuntime?.onSession(session);
5672
overlayManager.publishMessage('sessionData', session);
5773
});
5874

5975
bridge.onTelemetry((telemetry) => {
6076
perfMetrics.markStart('processTelemetry');
77+
lapTimesRuntime?.onFrame(telemetry);
6178
carSpeedsRuntime?.onFrame(telemetry);
6279
referenceLapRuntime?.onFrame(telemetry);
6380
relativeGapRuntime?.onFrame(telemetry);
6481
sectorTimingRuntime?.onFrame(telemetry);
6582
standingsRuntime?.onFrame(telemetry);
6683
radioRuntime?.onFrame(telemetry);
84+
sessionTimingRuntime?.onFrame(telemetry);
6785
perfMetrics.markStart('broadcast');
6886
overlayManager.publishMessage('telemetry', telemetry);
6987
perfMetrics.markEnd('broadcast');
@@ -80,10 +98,12 @@ export async function publishIRacingSDKEvents(
8098
...bridge,
8199
stop: () => {
82100
carSpeedsRuntime?.dispose();
101+
lapTimesRuntime?.dispose();
83102
relativeGapRuntime?.dispose();
84103
sectorTimingRuntime?.dispose();
85104
standingsRuntime?.dispose();
86105
radioRuntime?.dispose();
106+
sessionTimingRuntime?.dispose();
87107
referenceLapRuntime?.dispose();
88108
perfMetrics.stopReporting();
89109
originalStop();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { describe, expect, it } from 'vitest';
2+
import type { Session, Telemetry } from '@irdashies/types';
3+
import { SessionState } from '@irdashies/types';
4+
import { SessionTimingProcessor } from './SessionTimingProcessor';
5+
6+
const session = {
7+
DriverInfo: {
8+
DriverCarIdx: 1,
9+
Drivers: [{ CarIdx: 0, CarClassEstLapTime: 60 }, { CarIdx: 1 }],
10+
},
11+
SessionInfo: {
12+
Sessions: [{ SessionNum: 0, SessionType: 'Race', SessionLaps: 10 }],
13+
},
14+
} as unknown as Session;
15+
16+
const frame = (overrides: Record<string, unknown> = {}) =>
17+
Object.fromEntries(
18+
Object.entries({
19+
SessionTime: 20,
20+
SessionNum: 0,
21+
SessionState: SessionState.Racing,
22+
SessionTimeTotal: 600,
23+
SessionTimeRemain: 604800,
24+
CamCarIdx: 1,
25+
LapDistPct: 0.25,
26+
CarIdxLap: [2, 2],
27+
CarIdxPosition: [1, 2],
28+
CarIdxLapDistPct: [0.5, 0.25],
29+
CarIdxBestLapTime: [60, 61],
30+
CarIdxLapCompleted: [1, 1],
31+
CarIdxLastLapTime: [60, 61],
32+
...overrides,
33+
}).map(([key, value]) => [
34+
key,
35+
{ value: Array.isArray(value) ? value : [value] },
36+
])
37+
) as unknown as Telemetry;
38+
39+
describe('SessionTimingProcessor', () => {
40+
it('projects fixed-lap timing for the focused car', () => {
41+
const processor = new SessionTimingProcessor(() => [60, 61]);
42+
processor.init(session);
43+
processor.onFrame(frame());
44+
expect(processor.snapshot()).toMatchObject({
45+
sessionType: 'Race',
46+
state: SessionState.Racing,
47+
currentLap: 2,
48+
totalLaps: 10,
49+
isFixedLapRace: true,
50+
totalRaceLaps: 10,
51+
totalRaceTime: 600,
52+
adjustedRaceTime: 600,
53+
sessionNum: 0,
54+
});
55+
});
56+
57+
it('captures the green transition and freezes the lap at checkered', () => {
58+
const processor = new SessionTimingProcessor();
59+
processor.init(session);
60+
processor.onFrame(
61+
frame({ SessionTime: 10, SessionState: SessionState.Warmup })
62+
);
63+
processor.onFrame(
64+
frame({ SessionTime: 11, SessionState: SessionState.Racing })
65+
);
66+
expect(processor.snapshot().greenFlagTimestamp).toBe(11);
67+
processor.onFrame(
68+
frame({
69+
SessionTime: 12,
70+
SessionState: SessionState.Checkered,
71+
CarIdxLap: [3, 3],
72+
})
73+
);
74+
processor.onFrame(
75+
frame({
76+
SessionTime: 13,
77+
SessionState: SessionState.Checkered,
78+
CarIdxLap: [4, 4],
79+
})
80+
);
81+
expect(processor.snapshot().currentLap).toBe(3);
82+
});
83+
84+
it('resets on session changes and ignores replay scrubbing', () => {
85+
const processor = new SessionTimingProcessor();
86+
processor.init(session);
87+
processor.onFrame(frame());
88+
processor.onLifecycle({ type: 'sessionNumChange' });
89+
expect(processor.snapshot()).toMatchObject({
90+
sessionNum: null,
91+
currentLap: 0,
92+
});
93+
processor.onLifecycle({ type: 'enter', replay: true });
94+
processor.onFrame(frame({ SessionTime: 30 }));
95+
expect(processor.snapshot().currentLap).toBe(0);
96+
});
97+
});

0 commit comments

Comments
 (0)