Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions .storybook/telemetryDecorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
DashboardProvider,
RunningStateProvider,
SessionProvider,
TelemetryProvider,
StoryTelemetryProvider,
} from '@irdashies/context';
import { generateMockDataFromPath } from '../src/app/bridge/iracingSdk/mock-data/generateMockData';
import { mockDashboardBridge } from './mockDashboardBridge';
Expand All @@ -15,7 +15,7 @@ export const TelemetryDecorator: (path?: string) => Decorator = (path) => {
const DecoratorComponent = (Story: ComponentType) => (
<>
<SessionProvider bridge={generateMockDataFromPath(path)} />
<TelemetryProvider bridge={generateMockDataFromPath(path)} />
<StoryTelemetryProvider bridge={generateMockDataFromPath(path)} />
<DashboardProvider bridge={mockDashboardBridge}>
<RunningStateProvider bridge={generateMockDataFromPath(path)}>
<Story />
Expand Down Expand Up @@ -71,7 +71,7 @@ export const TelemetryDecoratorWithConfig: (
return (
<>
<SessionProvider bridge={generateMockDataFromPath(path)} />
<TelemetryProvider bridge={generateMockDataFromPath(path)} />
<StoryTelemetryProvider bridge={generateMockDataFromPath(path)} />
<DashboardProvider bridge={bridge}>
<RunningStateProvider bridge={generateMockDataFromPath(path)}>
<Story />
Expand Down
42 changes: 22 additions & 20 deletions docs/IMPLEMENTATION_PLAN.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions site/src/utils/mockSetup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ReactNode, useMemo } from 'react';
import {
SessionProvider,
TelemetryProvider,
StoryTelemetryProvider,
RunningStateProvider,
DashboardProvider,
} from '@irdashies/context';
Expand Down Expand Up @@ -131,7 +131,7 @@ export function LivePreviewProvider({
<DashboardProvider bridge={dashboardBridge}>
<RunningStateProvider bridge={bridge}>
<SessionProvider bridge={bridge} />
<TelemetryProvider bridge={bridge} />
<StoryTelemetryProvider bridge={bridge} />
{children}
</RunningStateProvider>
</DashboardProvider>
Expand Down
22 changes: 17 additions & 5 deletions src/app/bridge/iracingSdk/iracingSdkBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
getPerfRunConfig,
PERF_REPLAY_READY_LOG_MARKER,
} from '../../perfRunConfig';
import type { IrSdkBridge, Session, Telemetry } from '@irdashies/types';
import {
TELEMETRY_INSPECTOR_RATE_HZ,
type IrSdkSourceBridge,
type Session,
type Telemetry,
} from '@irdashies/types';
import logger from '../../logger';
import type { SessionLifecycle } from '../../sessionLifecycle';
import type { ChannelBus } from '../channelBridge';
Expand Down Expand Up @@ -148,7 +153,7 @@ export async function publishIRacingSDKEvents(
overlayManager: OverlayManager,
lifecycle?: SessionLifecycle,
channelBus?: ChannelBus
): Promise<IrSdkBridge> {
): Promise<IrSdkSourceBridge> {
logger.info('[iracingSdkBridge] Loading iRacing SDK bridge...');
const isTapeReplay = Boolean(process.env.IRDASHIES_TELEMETRY_REPLAY);
const sourceName = isTapeReplay ? 'telemetry replay' : 'iRacing';
Expand Down Expand Up @@ -259,7 +264,7 @@ export async function publishIRacingSDKEvents(
if (latestTelemetry && perfTelemetryDeliveryEnabled)
overlayManager.publishMessageToOverlay(
id,
'telemetry',
'telemetryInspector:telemetry',
telemetryForRenderer(latestTelemetry)
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (latestSession)
Expand Down Expand Up @@ -302,6 +307,7 @@ export async function publishIRacingSDKEvents(
(async () => {
while (!shouldStop) {
let lastSessionVersion = -1;
let lastInspectorTelemetryPublishTime = Number.NEGATIVE_INFINITY;
// Negative infinity makes the first tick fetch and publish immediately.
let lastSessionPublishTime = Number.NEGATIVE_INFINITY;
let lastSessionPollTime = Number.NEGATIVE_INFINITY;
Expand Down Expand Up @@ -346,13 +352,19 @@ export async function publishIRacingSDKEvents(
lapLogRuntime?.onFrame(telemetry);
if (
perfTelemetryDeliveryEnabled &&
overlayManager.hasLegacyStreamSubscribers('telemetry')
overlayManager.hasTelemetryInspectorSubscribers() &&
tickTime - lastInspectorTelemetryPublishTime >=
1000 / TELEMETRY_INSPECTOR_RATE_HZ
) {
lastInspectorTelemetryPublishTime = tickTime;
perfMetrics.markStart('telemetryProjection');
const rendererTelemetry = telemetryForRenderer(telemetry);
perfMetrics.markEnd('telemetryProjection');
perfMetrics.markStart('broadcast');
overlayManager.publishMessage('telemetry', rendererTelemetry);
overlayManager.publishMessage(
'telemetryInspector:telemetry',
rendererTelemetry
);
perfMetrics.markEnd('broadcast');
}
perfMetrics.markStart('telemetryCallbacks');
Expand Down
6 changes: 3 additions & 3 deletions src/app/bridge/iracingSdk/mock-data/generateMockData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { IrSdkBridge, Session, Telemetry } from '@irdashies/types';
import type { IrSdkSourceBridge, Session, Telemetry } from '@irdashies/types';
import mockSessionInfo from '../../../irsdk/node/utils/mock-data/session.json';
import mockTelemetry from '../../../irsdk/node/utils/mock-data/telemetry.json';

export async function generateMockDataFromPath(
path?: string
): Promise<IrSdkBridge> {
): Promise<IrSdkSourceBridge> {
if (!path) {
return generateMockData();
}
Expand All @@ -23,7 +23,7 @@ export async function generateMockDataFromPath(
export function generateMockData(sessionData?: {
telemetry: Telemetry | Telemetry[];
sessionInfo: Session | Session[];
}): IrSdkBridge {
}): IrSdkSourceBridge {
let telemetryInterval: NodeJS.Timeout | null = null;
let sessionInfoInterval: NodeJS.Timeout | null = null;
let runningStateInterval: NodeJS.Timeout | null = null;
Expand Down
55 changes: 49 additions & 6 deletions src/app/bridge/iracingSdk/mock-data/mockSdkBridge.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { IrSdkBridge, Session, Telemetry } from '@irdashies/types';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { IrSdkSourceBridge, Session, Telemetry } from '@irdashies/types';
import { ChannelBus } from '../../channelBridge';

const callbacks = vi.hoisted(() => ({
Expand All @@ -9,7 +9,7 @@ const callbacks = vi.hoisted(() => ({
const stop = vi.hoisted(() => vi.fn());

vi.mock('./generateMockData', () => ({
generateMockData: (): IrSdkBridge => ({
generateMockData: (): IrSdkSourceBridge => ({
onTelemetry(callback) {
callbacks.telemetry = callback;
return () => undefined;
Expand Down Expand Up @@ -51,6 +51,36 @@ describe('mockSdkBridge processor channels', () => {
stop.mockReset();
});

afterEach(() => vi.restoreAllMocks());

it('publishes subscribed Inspector telemetry at no more than 10 Hz', async () => {
const publishMessage = vi.fn();
const now = vi
.spyOn(performance, 'now')
.mockReturnValueOnce(0)
.mockReturnValueOnce(50)
.mockReturnValueOnce(100);
const bridge = await publishIRacingSDKEvents({
publishMessage,
hasTelemetryInspectorSubscribers: () => true,
} as never);

try {
callbacks.telemetry?.(telemetry(0.1, 1));
callbacks.telemetry?.(telemetry(0.11, 1.05));
callbacks.telemetry?.(telemetry(0.12, 1.1));
expect(now).toHaveBeenCalledTimes(3);
expect(publishMessage).toHaveBeenCalledTimes(2);
expect(publishMessage).toHaveBeenNthCalledWith(
1,
'telemetryInspector:telemetry',
expect.anything()
);
} finally {
bridge.stop();
}
});

it('feeds mock session and telemetry through the car-speed runtime', async () => {
const bus = new ChannelBus();
const publish = vi.spyOn(bus, 'publish');
Expand All @@ -61,7 +91,10 @@ describe('mockSdkBridge processor channels', () => {
send: vi.fn(),
};
bus.subscribe(target, 'car-speeds.snapshot');
const overlayManager = { publishMessage: vi.fn() };
const overlayManager = {
publishMessage: vi.fn(),
hasTelemetryInspectorSubscribers: () => false,
};
const bridge = await publishIRacingSDKEvents(
overlayManager as never,
undefined,
Expand All @@ -79,6 +112,10 @@ describe('mockSdkBridge processor channels', () => {
'car-speeds.snapshot',
expect.objectContaining({ carSpeeds: [360] })
);
expect(overlayManager.publishMessage).not.toHaveBeenCalledWith(
'telemetryInspector:telemetry',
expect.anything()
);
} finally {
bridge.stop();
}
Expand All @@ -101,7 +138,10 @@ describe('mockSdkBridge processor channels', () => {
'session-timing.snapshot'
);
const bridge = await publishIRacingSDKEvents(
{ publishMessage: vi.fn() } as never,
{
publishMessage: vi.fn(),
hasTelemetryInspectorSubscribers: () => false,
} as never,
undefined,
bus
);
Expand Down Expand Up @@ -148,7 +188,10 @@ describe('mockSdkBridge processor channels', () => {
'relative-gaps.snapshot'
);
const bridge = await publishIRacingSDKEvents(
{ publishMessage: vi.fn() } as never,
{
publishMessage: vi.fn(),
hasTelemetryInspectorSubscribers: () => false,
} as never,
undefined,
bus
);
Expand Down
16 changes: 13 additions & 3 deletions src/app/bridge/iracingSdk/mock-data/mockSdkBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { SessionBarRuntime } from '../../../processors/sessionBarRuntime';
import { DriverControlsRuntime } from '../../../processors/driverControlsRuntime';
import { TrackStateRuntime } from '../../../processors/trackStateRuntime';
import { LapLogRuntime } from '../../../processors/lapLogRuntime';
import { TELEMETRY_INSPECTOR_RATE_HZ } from '@irdashies/types';

export async function publishIRacingSDKEvents(
overlayManager: OverlayManager,
lifecycle?: SessionLifecycle,
channelBus?: ChannelBus
) {
const perfMetrics = new TelemetryPerfMetrics();
let lastInspectorTelemetryPublishTime = Number.NEGATIVE_INFINITY;
perfMetrics.startReporting();

const bridge = generateMockData();
Expand Down Expand Up @@ -105,9 +107,17 @@ export async function publishIRacingSDKEvents(
driverControlsRuntime?.onFrame(telemetry);
trackStateRuntime?.onFrame(telemetry);
lapLogRuntime?.onFrame(telemetry);
perfMetrics.markStart('broadcast');
overlayManager.publishMessage('telemetry', telemetry);
perfMetrics.markEnd('broadcast');
const tickTime = performance.now();
if (
overlayManager.hasTelemetryInspectorSubscribers() &&
tickTime - lastInspectorTelemetryPublishTime >=
1000 / TELEMETRY_INSPECTOR_RATE_HZ
) {
lastInspectorTelemetryPublishTime = tickTime;
perfMetrics.markStart('broadcast');
overlayManager.publishMessage('telemetryInspector:telemetry', telemetry);
perfMetrics.markEnd('broadcast');
}
perfMetrics.markEnd('processTelemetry');
perfMetrics.tick(telemetry);
});
Expand Down
10 changes: 5 additions & 5 deletions src/app/bridge/iracingSdk/setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OverlayManager } from '../../overlayManager';
import { ipcMain } from 'electron';
import type { IrSdkBridge } from '@irdashies/types';
import type { IrSdkSourceBridge } from '@irdashies/types';
import logger from '../../logger';
import {
createSessionLifecycle,
Expand All @@ -9,8 +9,8 @@ import {
import type { ChannelBus } from '../channelBridge';

let isDemoMode = false;
let currentBridge: IrSdkBridge | undefined;
const onBridgeChangedCallbacks = new Set<(bridge: IrSdkBridge) => void>();
let currentBridge: IrSdkSourceBridge | undefined;
const onBridgeChangedCallbacks = new Set<(bridge: IrSdkSourceBridge) => void>();

// Singleton lifecycle — created once; survives bridge restarts so subscribers
// registered before a demo-mode toggle are preserved.
Expand All @@ -23,15 +23,15 @@ export function getSessionLifecycle(): SessionLifecycle {
return sessionLifecycle;
}

export function getCurrentBridge(): IrSdkBridge | undefined {
export function getCurrentBridge(): IrSdkSourceBridge | undefined {
return currentBridge;
}

export function getIsDemoMode(): boolean {
return isDemoMode;
}

export function onBridgeChanged(callback: (bridge: IrSdkBridge) => void) {
export function onBridgeChanged(callback: (bridge: IrSdkSourceBridge) => void) {
onBridgeChangedCallbacks.add(callback);
return () => onBridgeChangedCallbacks.delete(callback);
}
Expand Down
16 changes: 0 additions & 16 deletions src/app/bridge/legacyRendererSubscriptions.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/app/bridge/rendererDataSubscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineRendererSubscriptionBridge } from './defineBridge';

export const RENDERER_DATA_SUBSCRIPTION_BRIDGE = 'renderer-data';

export type RendererDataStream = 'sessionData' | 'telemetryInspector';

export const isRendererDataStream = (
value: unknown
): value is RendererDataStream =>
value === 'sessionData' || value === 'telemetryInspector';

export const setupRendererDataSubscriptions = () =>
defineRendererSubscriptionBridge<RendererDataStream>({
name: RENDERER_DATA_SUBSCRIPTION_BRIDGE,
isValidKey: isRendererDataStream,
});
Loading