Files
NODEDC_MISSION_CORE/plugins/xgrids-k1/frontend/src/runtimeContext.tsx
T
DCCONSTRUCTIONS 0ca7316a24 wip(k1): checkpoint connection recovery rewrite
Capture the current unreleased K1 connection, recovery, lifecycle, viewer, and test work as a single known-bad baseline for subsequent fixes.
2026-08-14 14:57:50 +03:00

209 lines
7.8 KiB
TypeScript

import { createContext, useContext, useEffect, type ReactNode } from "react";
import {
MissionRuntimeProvider,
useMissionRuntime,
type DeviceModelDefinition,
type MissionRuntimeController,
type MissionRuntimeState,
} from "@mission-core/plugin-sdk";
import {
activeConnectionEndpointLabel,
canonicalDeviceConnectivity,
confirmedRuntimeSourceMode,
effectiveAcquisition,
hasAuthoritativeData,
hasControlAuthority,
normalizeRuntimePhase,
spatialSourceId,
} from "./lifecycle";
import {
activeStreamRecoveryOwnsPresentationDecision,
activeStreamRecoveryPresentationAuthority,
} from "./activeStreamRecovery";
import { localizeRuntimeMessage } from "./messages";
import { xgridsK1Manifest } from "./manifest";
import { deviceTelemetry, finiteMetric, pipelineLatency } from "./presentation";
import { xgridsK1ObservationSources } from "./observationSources";
import { useXgridsK1Runtime } from "./useXgridsK1Runtime";
export type XgridsK1Controller = ReturnType<typeof useXgridsK1Runtime>;
const XgridsK1RuntimeContext = createContext<XgridsK1Controller | null>(null);
export function normalizeXgridsK1MissionState(
controller: Pick<XgridsK1Controller, "state">,
activeModel: DeviceModelDefinition,
): MissionRuntimeState | null {
const state = controller.state;
if (!state) return null;
const controlAuthoritative = hasControlAuthority(state);
const recoveryPresentationAuthority = activeStreamRecoveryPresentationAuthority(state);
const recoveryOwnsPresentation = activeStreamRecoveryOwnsPresentationDecision(state);
const dataAuthoritative = hasAuthoritativeData(state) && !recoveryOwnsPresentation;
const recoveryPresentationAuthoritative = recoveryPresentationAuthority !== null;
const replayAuthoritative = state.source_mode === "replay";
const metrics = replayAuthoritative || dataAuthoritative
? state.metrics
: undefined;
const telemetry = deviceTelemetry(metrics);
const deviceRef = state.device_ref;
const deviceSession = state.device_session;
const acquisition = effectiveAcquisition(state);
const sourceUrl = state.rerun_grpc_url?.trim() ?? "";
const resolvedSpatialSourceId =
state.source_mode === "replay"
? spatialSourceId(state, sourceUrl)
: acquisition?.acquisition_id ?? spatialSourceId(state, sourceUrl);
return {
phase: normalizeRuntimePhase(state),
message: localizeRuntimeMessage(state.message),
activeDevice: deviceRef && controlAuthoritative
? {
pluginId: xgridsK1Manifest.metadata.id,
modelId: deviceRef.model_id || activeModel.id,
displayName: activeModel.displayName,
instanceId: deviceRef.device_id,
endpointLabel: activeConnectionEndpointLabel(state),
}
: null,
deviceSession: deviceSession
? {
sessionId: deviceSession.device_session_id,
deviceId: deviceSession.device_id,
compatibilityProfileId: deviceSession.compatibility_profile_id,
connectivity: canonicalDeviceConnectivity(state),
}
: null,
acquisition: acquisition
? {
acquisitionId: acquisition.acquisition_id,
deviceId: acquisition.device_id,
deviceSessionId: acquisition.device_session_id,
compatibilityProfileId: acquisition.compatibility_profile_id,
controlMode: acquisition.control_mode,
state: acquisition.state,
stateRevision: acquisition.state_revision,
operatorInstructions: acquisition.operator_instructions ?? [],
cleanupPending: acquisition.cleanup_pending === true,
}
: null,
operations: (state.operations ?? []).map((operation) => ({
operationId: operation.operation_id,
action: operation.action,
status: operation.status,
stageCode: operation.stage_code,
messageCode: operation.message_code,
})),
spatialSource: sourceUrl
&& resolvedSpatialSourceId
&& (replayAuthoritative || dataAuthoritative || recoveryPresentationAuthoritative)
? {
id: resolvedSpatialSourceId,
url: sourceUrl,
label:
state.source_mode === "replay"
? "Повтор пространственной записи"
: "Локальный пространственный поток",
kind: "rerun-grpc",
}
: null,
observationSources: xgridsK1ObservationSources(state, activeModel),
observationTimeline: {
// This is the shared host timeline. A replayable Rerun source alone does
// not make camera and point-cloud time jointly seekable.
mode: "live-only",
seekable: false,
sessionRecording: false,
synchronization: "host-arrival-best-effort",
range: null,
},
viewerSettings: state.viewer_settings,
sourceMode: recoveryPresentationAuthoritative
? "live"
: confirmedRuntimeSourceMode(state),
metrics: {
publishedFrameCount: (
Number.isSafeInteger(metrics?.pcl_frames) &&
Number.isSafeInteger(metrics?.pose_frames) &&
(metrics?.pcl_frames ?? -1) >= 0 &&
(metrics?.pose_frames ?? -1) >= 0
)
? (metrics?.pcl_frames ?? 0) + (metrics?.pose_frames ?? 0)
: null,
latencyMs: pipelineLatency(metrics),
frameRateHz: finiteMetric(metrics?.frame_rate ?? metrics?.frame_rate_hz),
pointCount: finiteMetric(metrics?.point_count),
droppedPreviewFrames: finiteMetric(metrics?.dropped_preview_frames),
aiLatencyMs: finiteMetric(metrics?.ai_end_to_end_ms),
aiFrameRateHz: finiteMetric(metrics?.ai_frame_rate_hz),
aiDroppedFrames: finiteMetric(metrics?.ai_dropped_frames),
aiStaleMs: finiteMetric(metrics?.ai_stale_ms),
elapsedSeconds: telemetry.elapsedSeconds,
routeDistanceMeters: telemetry.routeDistanceMeters,
speedMetersPerSecond: telemetry.speedMetersPerSecond,
},
};
}
export function XgridsK1RuntimeProvider({
activeModel,
registerDeactivation,
children,
}: {
activeModel: DeviceModelDefinition | null;
registerDeactivation: (handler: () => Promise<boolean>) => () => void;
children: ReactNode;
}) {
const active = activeModel !== null;
const inheritedRuntime = useMissionRuntime();
const controller = useXgridsK1Runtime(active);
const missionRuntime: MissionRuntimeController = {
state: activeModel
? normalizeXgridsK1MissionState(controller, activeModel)
: null,
backendStatus: controller.backendStatus,
pendingAction: controller.pendingAction,
refresh: () => controller.refresh().then(() => undefined),
resetConnectionScenario: controller.resetConnectionScenario,
updateViewerSettings: controller.updateViewerSettings,
setObservationSourceActive: controller.setObservationSourceActive,
};
useEffect(() => {
if (!activeModel) return;
return registerDeactivation(async () => {
if (controller.pendingAction !== null) return false;
// Never translate navigation/model switching into a K1 control command.
// The operator must explicitly finish or close the canonical dialogue.
if (controller.state?.application_control_session?.control_socket_open) {
return false;
}
return controller.stop();
});
}, [
activeModel,
controller.pendingAction,
controller.state?.application_control_session?.control_socket_open,
controller.stop,
registerDeactivation,
]);
return (
<XgridsK1RuntimeContext.Provider value={active ? controller : null}>
<MissionRuntimeProvider value={active ? missionRuntime : inheritedRuntime}>
{children}
</MissionRuntimeProvider>
</XgridsK1RuntimeContext.Provider>
);
}
export function useXgridsK1Controller(): XgridsK1Controller {
const controller = useContext(XgridsK1RuntimeContext);
if (!controller) {
throw new Error("XGRIDS K1 runtime используется вне собственного plugin provider.");
}
return controller;
}