Skip to content

Commit 781f93f

Browse files
authored
feat: move remaining widgets to channels (#670)
1 parent cc4dbe3 commit 781f93f

75 files changed

Lines changed: 4016 additions & 2066 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.storybook/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export * from './telemetryDecorator';
22
export * from './channelSnapshotDecorator';
33
export * from './standingsSnapshot';
44
export * from './trackStateSnapshot';
5+
export * from './sessionBarSnapshot';
56
export * from './DynamicTelemetrySelector';
67
export * from './mockDashboardBridge';

.storybook/sessionBarSnapshot.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Session, SessionBarSnapshot, Telemetry } from '@irdashies/types';
2+
import { SessionBarProcessor } from '../src/app/processors/SessionBarProcessor';
3+
import sessionFixture from '../src/app/irsdk/node/utils/mock-data/session.json';
4+
import telemetryFixture from '../src/app/irsdk/node/utils/mock-data/telemetry.json';
5+
6+
const processor = new SessionBarProcessor();
7+
processor.init(sessionFixture as unknown as Session);
8+
processor.onFrame(telemetryFixture as unknown as Telemetry);
9+
10+
export const sessionBarStorySnapshot: SessionBarSnapshot = processor.snapshot();

.storybook/trackStateSnapshot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const trackStateStorySnapshot = {
1616
isReplayPlaying: false,
1717
sessionTime: 10,
1818
sessionState: 4,
19+
sessionFlags: 0,
1920
speed: 0,
2021
displayUnits: 1,
2122
pitSpeedLimiterToggle: false,

docs/IMPLEMENTATION_PLAN.md

Lines changed: 22 additions & 20 deletions
Large diffs are not rendered by default.

src/app/bridge/iracingSdk/iracingSdkBridge.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { SessionTimingRuntime } from '../../processors/sessionTimingRuntime';
2121
import { SessionBarRuntime } from '../../processors/sessionBarRuntime';
2222
import { DriverControlsRuntime } from '../../processors/driverControlsRuntime';
2323
import { TrackStateRuntime } from '../../processors/trackStateRuntime';
24+
import { LapLogRuntime } from '../../processors/lapLogRuntime';
2425

2526
// Keys consumed by the renderer. Anything outside this set is dropped before
2627
// the telemetry object crosses the IPC boundary — reducing structured-clone
@@ -231,6 +232,9 @@ export async function publishIRacingSDKEvents(
231232
const trackStateRuntime = channelBus
232233
? new TrackStateRuntime(channelBus, lifecycle, perfMetrics)
233234
: undefined;
235+
const lapLogRuntime = channelBus
236+
? new LapLogRuntime(channelBus, lifecycle, perfMetrics)
237+
: undefined;
234238

235239
let shouldStop = false;
236240
let lastRunningState: boolean | undefined = undefined;
@@ -339,6 +343,7 @@ export async function publishIRacingSDKEvents(
339343
sessionBarRuntime?.onFrame(telemetry);
340344
driverControlsRuntime?.onFrame(telemetry);
341345
trackStateRuntime?.onFrame(telemetry);
346+
lapLogRuntime?.onFrame(telemetry);
342347
if (
343348
perfTelemetryDeliveryEnabled &&
344349
overlayManager.hasLegacyStreamSubscribers('telemetry')
@@ -377,6 +382,7 @@ export async function publishIRacingSDKEvents(
377382
sessionBarRuntime?.onSession(session);
378383
driverControlsRuntime?.onSession(session);
379384
trackStateRuntime?.onSession(session);
385+
lapLogRuntime?.onSession(session);
380386
overlayManager.publishMessage('sessionData', session);
381387
sessionCallbacks.forEach((callback) => callback(session));
382388
perfMetrics.markEnd('sessionPublish');
@@ -444,6 +450,7 @@ export async function publishIRacingSDKEvents(
444450
sessionBarRuntime?.dispose();
445451
driverControlsRuntime?.dispose();
446452
trackStateRuntime?.dispose();
453+
lapLogRuntime?.dispose();
447454
referenceLapRuntime?.dispose();
448455
perfMetrics.stopReporting();
449456
},

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { LapTimesRuntime } from '../../../processors/lapTimesRuntime';
1414
import { SessionBarRuntime } from '../../../processors/sessionBarRuntime';
1515
import { DriverControlsRuntime } from '../../../processors/driverControlsRuntime';
1616
import { TrackStateRuntime } from '../../../processors/trackStateRuntime';
17+
import { LapLogRuntime } from '../../../processors/lapLogRuntime';
1718

1819
export async function publishIRacingSDKEvents(
1920
overlayManager: OverlayManager,
@@ -72,6 +73,9 @@ export async function publishIRacingSDKEvents(
7273
const trackStateRuntime = channelBus
7374
? new TrackStateRuntime(channelBus, lifecycle, perfMetrics)
7475
: undefined;
76+
const lapLogRuntime = channelBus
77+
? new LapLogRuntime(channelBus, lifecycle, perfMetrics)
78+
: undefined;
7579

7680
bridge.onSessionData((session) => {
7781
carSpeedsRuntime?.onSession(session);
@@ -83,6 +87,7 @@ export async function publishIRacingSDKEvents(
8387
sessionBarRuntime?.onSession(session);
8488
driverControlsRuntime?.onSession(session);
8589
trackStateRuntime?.onSession(session);
90+
lapLogRuntime?.onSession(session);
8691
overlayManager.publishMessage('sessionData', session);
8792
});
8893

@@ -99,6 +104,7 @@ export async function publishIRacingSDKEvents(
99104
sessionBarRuntime?.onFrame(telemetry);
100105
driverControlsRuntime?.onFrame(telemetry);
101106
trackStateRuntime?.onFrame(telemetry);
107+
lapLogRuntime?.onFrame(telemetry);
102108
perfMetrics.markStart('broadcast');
103109
overlayManager.publishMessage('telemetry', telemetry);
104110
perfMetrics.markEnd('broadcast');
@@ -124,6 +130,7 @@ export async function publishIRacingSDKEvents(
124130
sessionBarRuntime?.dispose();
125131
driverControlsRuntime?.dispose();
126132
trackStateRuntime?.dispose();
133+
lapLogRuntime?.dispose();
127134
referenceLapRuntime?.dispose();
128135
perfMetrics.stopReporting();
129136
originalStop();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from 'vitest';
2+
import type { Telemetry } from '@irdashies/types';
3+
import { LapLogProcessor } from './LapLogProcessor';
4+
5+
const frame = (values: Record<string, unknown[]>): Telemetry =>
6+
Object.fromEntries(
7+
Object.entries(values).map(([key, value]) => [key, { value }])
8+
) as Telemetry;
9+
10+
describe('LapLogProcessor', () => {
11+
it('projects lap log inputs without allocating a replacement history buffer', () => {
12+
const processor = new LapLogProcessor();
13+
processor.onFrame(
14+
frame({
15+
LapCompleted: [2],
16+
LapCurrentLapTime: [31.2],
17+
LapLastLapTime: [61.5],
18+
LapBestLapTime: [60.9],
19+
CarIdxBestLapTime: [60.9, 61.1],
20+
SessionNum: [1],
21+
SessionTime: [120.4],
22+
PlayerTrackSurface: [3],
23+
PlayerCarMyIncidentCount: [2],
24+
LapDistPct: [0.4],
25+
LapDeltaToSessionBestLap: [-0.2],
26+
LapDeltaToSessionBestLap_OK: [1],
27+
})
28+
);
29+
const snapshot = processor.snapshot();
30+
const bestLaps = snapshot.carIdxBestLapTime;
31+
expect(snapshot).toMatchObject({
32+
lapCompleted: 2,
33+
currentLapTime: 31.2,
34+
bestLapTime: 60.9,
35+
sessionNum: 1,
36+
incidentCount: 2,
37+
deltaToSessionBestLap: -0.2,
38+
deltaToSessionBestLapOk: true,
39+
});
40+
expect(bestLaps).toEqual([60.9, 61.1]);
41+
42+
processor.onFrame(frame({ CarIdxBestLapTime: [59.8] }));
43+
expect(processor.snapshot().carIdxBestLapTime).toBe(bestLaps);
44+
expect(bestLaps).toEqual([59.8]);
45+
});
46+
47+
it('resets session-derived state on lifecycle changes', () => {
48+
const processor = new LapLogProcessor();
49+
processor.onFrame(frame({ LapCompleted: [4], SessionNum: [2] }));
50+
processor.onLifecycle({ type: 'sessionNumChange' });
51+
expect(processor.snapshot()).toMatchObject({
52+
lapCompleted: 0,
53+
sessionNum: null,
54+
});
55+
});
56+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import type {
2+
LapLogSnapshot,
3+
Session,
4+
SessionLifecycleEvent,
5+
Telemetry,
6+
} from '@irdashies/types';
7+
import type { TelemetryProcessor } from './TelemetryProcessor';
8+
9+
const numberValue = (frame: Telemetry, key: keyof Telemetry): number => {
10+
const value = frame[key]?.value?.[0];
11+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
12+
};
13+
14+
const booleanValue = (frame: Telemetry, key: keyof Telemetry): boolean => {
15+
const value = frame[key]?.value?.[0];
16+
return value === true || value === 1;
17+
};
18+
19+
export class LapLogProcessor implements TelemetryProcessor<LapLogSnapshot> {
20+
readonly channel = 'lap-log.snapshot';
21+
readonly tickRateHz = 25;
22+
private readonly bestLaps: number[] = [];
23+
private readonly latest = this.empty();
24+
25+
init(session: Session): void {
26+
void session;
27+
}
28+
29+
onFrame(frame: Telemetry): void {
30+
const source = frame.CarIdxBestLapTime?.value;
31+
this.bestLaps.length = Array.isArray(source) ? source.length : 0;
32+
for (let index = 0; index < this.bestLaps.length; index += 1) {
33+
const value = source?.[index];
34+
this.bestLaps[index] =
35+
typeof value === 'number' && Number.isFinite(value) ? value : 0;
36+
}
37+
Object.assign(this.latest, {
38+
lapCompleted: numberValue(frame, 'LapCompleted'),
39+
currentLapTime: numberValue(frame, 'LapCurrentLapTime'),
40+
lastLapTime: numberValue(frame, 'LapLastLapTime'),
41+
bestLapTime: numberValue(frame, 'LapBestLapTime'),
42+
sessionNum: numberValue(frame, 'SessionNum'),
43+
sessionTime: numberValue(frame, 'SessionTime'),
44+
playerTrackSurface: numberValue(frame, 'PlayerTrackSurface'),
45+
incidentCount: numberValue(frame, 'PlayerCarMyIncidentCount'),
46+
lapDistPct: numberValue(frame, 'LapDistPct'),
47+
deltaToSessionLastLap: numberValue(frame, 'LapDeltaToSessionLastlLap'),
48+
deltaToSessionLastLapOk: booleanValue(
49+
frame,
50+
'LapDeltaToSessionLastlLap_OK'
51+
),
52+
deltaToSessionBestLap: numberValue(frame, 'LapDeltaToSessionBestLap'),
53+
deltaToSessionBestLapOk: booleanValue(
54+
frame,
55+
'LapDeltaToSessionBestLap_OK'
56+
),
57+
version: this.latest.version + 1,
58+
});
59+
}
60+
61+
onLifecycle(event: SessionLifecycleEvent): void {
62+
if (event.type === 'enter') return;
63+
Object.assign(this.latest, this.empty(), {
64+
version: this.latest.version + 1,
65+
});
66+
this.bestLaps.length = 0;
67+
}
68+
69+
snapshot(): LapLogSnapshot {
70+
return this.latest;
71+
}
72+
73+
private empty(): LapLogSnapshot {
74+
return {
75+
lapCompleted: 0,
76+
currentLapTime: 0,
77+
lastLapTime: 0,
78+
bestLapTime: 0,
79+
carIdxBestLapTime: this.bestLaps,
80+
sessionNum: null,
81+
sessionTime: 0,
82+
playerTrackSurface: 0,
83+
incidentCount: 0,
84+
lapDistPct: 0,
85+
deltaToSessionLastLap: 0,
86+
deltaToSessionLastLapOk: false,
87+
deltaToSessionBestLap: 0,
88+
deltaToSessionBestLapOk: false,
89+
version: 0,
90+
};
91+
}
92+
}

src/app/processors/SessionBarProcessor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class SessionBarProcessor implements TelemetryProcessor<SessionBarSnapsho
108108
?.IncidentWarningSubsequentLimit,
109109
trackWetness: n(frame, 'TrackWetness') ?? 0,
110110
precipitation: n(frame, 'Precipitation'),
111+
relativeHumidity: n(frame, 'RelativeHumidity'),
111112
airTemp: n(frame, 'AirTemp'),
112113
trackTemp: n(frame, 'TrackTempCrew'),
113114
windDirection: n(frame, 'WindDir'),
@@ -172,6 +173,7 @@ export class SessionBarProcessor implements TelemetryProcessor<SessionBarSnapsho
172173
incidentWarningSubsequentLimit: undefined,
173174
trackWetness: 0,
174175
precipitation: undefined,
176+
relativeHumidity: undefined,
175177
airTemp: undefined,
176178
trackTemp: undefined,
177179
windDirection: undefined,

src/app/processors/StandingsProcessor.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ export class StandingsProcessor implements TelemetryProcessor<StandingsSnapshot>
118118
this.latest.carIdxTrackSurface
119119
);
120120
copyNumberArray(frame, 'CarIdxF2Time', this.latest.carIdxF2Time);
121+
copyNumberArray(frame, 'CarIdxPosition', this.latest.carIdxPosition);
122+
copyNumberArray(
123+
frame,
124+
'CarIdxClassPosition',
125+
this.latest.carIdxClassPosition
126+
);
127+
copyNumberArray(frame, 'CarIdxBestLapTime', this.latest.carIdxBestLapTime);
128+
copyNumberArray(frame, 'CarIdxLastLapTime', this.latest.carIdxLastLapTime);
121129
copyNumberArray(frame, 'CarIdxEstTime', this.latest.carIdxEstTime);
122130
copyNumberArray(frame, 'CarIdxLapDistPct', this.latest.carIdxLapDistPct);
123131
copyNumberArray(
@@ -130,7 +138,12 @@ export class StandingsProcessor implements TelemetryProcessor<StandingsSnapshot>
130138
'CarIdxSessionFlags',
131139
this.latest.carIdxSessionFlags
132140
);
141+
copyBooleanArray(frame, 'CarIdxP2P_Status', this.latest.carIdxP2PStatus);
142+
copyNumberArray(frame, 'CarIdxP2P_Count', this.latest.carIdxP2PCount);
133143
const sessionState = numberValue(frame, 'SessionState') ?? 0;
144+
this.latest.sessionUniqueId = numberValue(frame, 'SessionUniqueID') ?? 0;
145+
this.latest.sessionTime = sessionTime;
146+
this.latest.sessionState = sessionState;
134147
this.updateLiveClassPositions(frame, sessionNum, sessionState);
135148
const carIdxOnPitRoad = this.latest.carIdxOnPitRoad;
136149
const carIdxLap = this.latest.carIdxLap;
@@ -198,13 +211,22 @@ export class StandingsProcessor implements TelemetryProcessor<StandingsSnapshot>
198211
this.latest.focusCarIdx = null;
199212
this.latest.sessionNum = sessionNum;
200213
this.latest.carIdxF2Time.length = 0;
214+
this.latest.carIdxPosition.length = 0;
215+
this.latest.carIdxClassPosition.length = 0;
216+
this.latest.carIdxBestLapTime.length = 0;
217+
this.latest.carIdxLastLapTime.length = 0;
201218
this.latest.carIdxEstTime.length = 0;
202219
this.latest.carIdxOnPitRoad.length = 0;
203220
this.latest.carIdxLap.length = 0;
204221
this.latest.carIdxLapDistPct.length = 0;
205222
this.latest.carIdxTrackSurface.length = 0;
206223
this.latest.carIdxTireCompound.length = 0;
207224
this.latest.carIdxSessionFlags.length = 0;
225+
this.latest.carIdxP2PStatus.length = 0;
226+
this.latest.carIdxP2PCount.length = 0;
227+
this.latest.sessionUniqueId = 0;
228+
this.latest.sessionTime = 0;
229+
this.latest.sessionState = 0;
208230
this.latest.lastPitLap.length = 0;
209231
this.latest.previousCarTrackSurface.length = 0;
210232
this.latest.liveClassPosition.length = 0;
@@ -216,13 +238,22 @@ export class StandingsProcessor implements TelemetryProcessor<StandingsSnapshot>
216238
focusCarIdx: null,
217239
sessionNum: null,
218240
carIdxF2Time: [],
241+
carIdxPosition: [],
242+
carIdxClassPosition: [],
243+
carIdxBestLapTime: [],
244+
carIdxLastLapTime: [],
219245
carIdxEstTime: [],
220246
carIdxOnPitRoad: [],
221247
carIdxLap: [],
222248
carIdxLapDistPct: [],
223249
carIdxTrackSurface: [],
224250
carIdxTireCompound: [],
225251
carIdxSessionFlags: [],
252+
carIdxP2PStatus: [],
253+
carIdxP2PCount: [],
254+
sessionUniqueId: 0,
255+
sessionTime: 0,
256+
sessionState: 0,
226257
lastPitLap: [],
227258
previousCarTrackSurface: [],
228259
liveClassPosition: [],

0 commit comments

Comments
 (0)