import { createContext, useContext, useEffect, type ReactNode } from "react"; import { MissionRuntimeProvider, useMissionRuntime, type DeviceModelDefinition, type MissionRuntimeController, type MissionRuntimeState, } from "@mission-core/plugin-sdk"; import { confirmedRuntimeSourceMode, effectiveAcquisition, normalizeRuntimePhase, spatialSourceId, } from "./lifecycle"; 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; const XgridsK1RuntimeContext = createContext(null); function normalizeState( controller: XgridsK1Controller, activeModel: DeviceModelDefinition, ): MissionRuntimeState | null { const state = controller.state; if (!state) return null; const metrics = state.metrics; 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 ? { pluginId: xgridsK1Manifest.metadata.id, modelId: deviceRef.model_id || activeModel.id, displayName: activeModel.displayName, instanceId: deviceRef.device_id, endpointLabel: state.k1_ip, } : null, deviceSession: deviceSession ? { sessionId: deviceSession.device_session_id, deviceId: deviceSession.device_id, compatibilityProfileId: deviceSession.compatibility_profile_id, connectivity: deviceSession.connectivity, } : 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 ? { 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: confirmedRuntimeSourceMode(state), metrics: { latencyMs: pipelineLatency(metrics), frameRateHz: finiteMetric(metrics?.frame_rate ?? metrics?.frame_rate_hz), pointCount: finiteMetric(metrics?.point_count), droppedPreviewFrames: finiteMetric(metrics?.dropped_preview_frames), elapsedSeconds: telemetry.elapsedSeconds, routeDistanceMeters: telemetry.routeDistanceMeters, speedMetersPerSecond: telemetry.speedMetersPerSecond, }, }; } export function XgridsK1RuntimeProvider({ activeModel, registerDeactivation, children, }: { activeModel: DeviceModelDefinition | null; registerDeactivation: (handler: () => Promise) => () => void; children: ReactNode; }) { const active = activeModel !== null; const inheritedRuntime = useMissionRuntime(); const controller = useXgridsK1Runtime(active); const missionRuntime: MissionRuntimeController = { state: activeModel ? normalizeState(controller, activeModel) : null, backendStatus: controller.backendStatus, pendingAction: controller.pendingAction, refresh: controller.refresh, updateViewerSettings: controller.updateViewerSettings, setObservationSourceActive: controller.setObservationSourceActive, }; useEffect(() => { if (!activeModel) return; return registerDeactivation(async () => { if (controller.pendingAction !== null) return false; // Always ask the backend to stop. The browser snapshot may be stale or not // loaded yet, while a previous local capture is still alive. return controller.stop(); }); }, [ activeModel, controller.pendingAction, controller.stop, registerDeactivation, ]); return ( {children} ); } export function useXgridsK1Controller(): XgridsK1Controller { const controller = useContext(XgridsK1RuntimeContext); if (!controller) { throw new Error("XGRIDS K1 runtime используется вне собственного plugin provider."); } return controller; }