feat(plugins): isolate device integrations
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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 { finiteMetric, pipelineLatency } from "./presentation";
|
||||
import { xgridsK1ObservationSources } from "./observationSources";
|
||||
import { useXgridsK1Runtime } from "./useXgridsK1Runtime";
|
||||
|
||||
export type XgridsK1Controller = ReturnType<typeof useXgridsK1Runtime>;
|
||||
|
||||
const XgridsK1RuntimeContext = createContext<XgridsK1Controller | null>(null);
|
||||
|
||||
function normalizeState(
|
||||
controller: XgridsK1Controller,
|
||||
activeModel: DeviceModelDefinition,
|
||||
): MissionRuntimeState | null {
|
||||
const state = controller.state;
|
||||
if (!state) return null;
|
||||
const metrics = state.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 ?? [],
|
||||
}
|
||||
: 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),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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 ? 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 (
|
||||
<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;
|
||||
}
|
||||
Reference in New Issue
Block a user