From 52424f10c04e2312abf47f1eb982d15a8fec9be3 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 18 Mar 2025 16:21:16 -0700 Subject: [PATCH 1/3] move files and combine some code --- src/controller/index.ts | 66 +++++++++---------- .../{ => Simulator}/ClientSimulator.ts | 11 +++- src/simularium/{ => Simulator}/ISimulator.ts | 16 ++++- .../{ => Simulator}/LocalFileSimulator.ts | 18 ++++- .../{ => Simulator}/RemoteSimulator.ts | 9 ++- src/simularium/Simulator/types.ts | 27 ++++++++ src/simularium/index.ts | 2 +- src/simularium/types.ts | 24 ------- src/test/DummyRemoteSimulator.ts | 2 +- src/test/SimulariumController.test.ts | 10 +-- src/test/util.test.ts | 10 ++- src/util.ts | 14 ++-- 12 files changed, 118 insertions(+), 91 deletions(-) rename src/simularium/{ => Simulator}/ClientSimulator.ts (94%) rename src/simularium/{ => Simulator}/ISimulator.ts (74%) rename src/simularium/{ => Simulator}/LocalFileSimulator.ts (89%) rename src/simularium/{ => Simulator}/RemoteSimulator.ts (97%) create mode 100644 src/simularium/Simulator/types.ts diff --git a/src/controller/index.ts b/src/controller/index.ts index 1d619797..a709adbf 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -12,12 +12,13 @@ import { FILE_STATUS_SUCCESS, FILE_STATUS_FAIL, PlotConfig, - SimulatorParams, } from "../simularium/types.js"; -import { ClientSimulator } from "../simularium/ClientSimulator.js"; -import { ISimulator } from "../simularium/ISimulator.js"; -import { LocalFileSimulator } from "../simularium/LocalFileSimulator.js"; +import { + getClassFromParams, + ISimulator, +} from "../simularium/Simulator/ISimulator.js"; +import { LocalFileSimulator } from "../simularium/Simulator/LocalFileSimulator.js"; import { FrontEndError } from "../simularium/FrontEndError.js"; import type { ISimulariumFile } from "../simularium/ISimulariumFile.js"; import { WebsocketClient } from "../simularium/WebsocketClient.js"; @@ -25,10 +26,10 @@ import { TrajectoryType } from "../constants.js"; import { RemoteMetricsCalculator } from "../simularium/RemoteMetricsCalculator.js"; import { OctopusServicesClient } from "../simularium/OctopusClient.js"; import { - isLocalProceduralSimulatorParams, isLocalFileSimulatorParams, isRemoteSimulatorParams, } from "../util.js"; +import { SimulatorParams } from "../simularium/Simulator/types.js"; jsLogger.setHandler(jsLogger.createDefaultHandler()); @@ -75,32 +76,13 @@ export default class SimulariumController { this.cancelCurrentFile = this.cancelCurrentFile.bind(this); } - public buildSimulator(params: SimulatorParams): ISimulator { - if (isLocalProceduralSimulatorParams(params)) { - const simulator = new ClientSimulator(params.clientSimulatorImpl); - simulator.setTrajectoryDataHandler( - this.visData.parseAgentsFromNetData.bind(this.visData) - ); - return simulator; - } else if (isLocalFileSimulatorParams(params)) { - const simulator = new LocalFileSimulator( - params.fileName, - params.simulariumFile - ); - if ( - this.visGeometry && - params.geoAssets && - !isEmpty(params.geoAssets) - ) { - this.visGeometry.geometryStore.cacheLocalAssets( - params.geoAssets - ); - } - simulator.setTrajectoryDataHandler( - this.visData.parseAgentsFromFrameData.bind(this.visData) - ); - return simulator; - } else if (isRemoteSimulatorParams(params)) { + public buildSimulator(params: SimulatorParams) { + const SimulatorClass = getClassFromParams(params); + if (!SimulatorClass) { + throw new Error("Invalid simulator configuration"); + } + let simulator: ISimulator; + if (isRemoteSimulatorParams(params)) { if (this.needsNewNetworkConfig(params.netConnectionSettings)) { this.configureNetwork(params.netConnectionSettings); } @@ -108,17 +90,29 @@ export default class SimulariumController { throw new Error("Websocket client not configured"); } - const simulator = new RemoteSimulator( + simulator = new RemoteSimulator( this.remoteWebsocketClient, this.onError, params.requestJson ); - simulator.setTrajectoryDataHandler( - this.visData.parseAgentsFromNetData.bind(this.visData) - ); return simulator; + } else { + simulator = new SimulatorClass(params); + + if ( + this.visGeometry && + "geoAssets" in params && + !isEmpty(params.geoAssets) + ) { + this.visGeometry.geometryStore.cacheLocalAssets( + params.geoAssets + ); + } } - throw new Error("Invalid simulator configuration"); + simulator.setTrajectoryDataHandler( + this.visData.parseAgentsFromNetData.bind(this.visData) + ); + return simulator; } private createSimulatorConnection(params: SimulatorParams): void { diff --git a/src/simularium/ClientSimulator.ts b/src/simularium/Simulator/ClientSimulator.ts similarity index 94% rename from src/simularium/ClientSimulator.ts rename to src/simularium/Simulator/ClientSimulator.ts index e15d231f..700c95d7 100644 --- a/src/simularium/ClientSimulator.ts +++ b/src/simularium/Simulator/ClientSimulator.ts @@ -1,13 +1,14 @@ import jsLogger from "js-logger"; import { ILogger } from "js-logger"; -import { VisDataMessage, TrajectoryFileInfo } from "./types.js"; +import { VisDataMessage, TrajectoryFileInfo } from "../types.js"; import { ClientMessageEnum, ClientPlayBackType, IClientSimulatorImpl, -} from "./localSimulators/IClientSimulatorImpl.js"; +} from "../localSimulators/IClientSimulatorImpl.js"; import { ISimulator } from "./ISimulator.js"; +import { ClientSimulatorParams } from "./types.js"; // a ClientSimulator is a ISimulator that is expected to run purely in procedural javascript in the browser client, // with the procedural implementation in a IClientSimulatorImpl @@ -22,7 +23,11 @@ export class ClientSimulator implements ISimulator { public onTrajectoryDataArrive: (msg: VisDataMessage) => void; public handleError: (error: Error) => void; - public constructor(sim: IClientSimulatorImpl) { + public constructor(params: ClientSimulatorParams) { + const { clientSimulatorImpl } = params; + if (!clientSimulatorImpl) { + throw new Error("ClientSimulator requires a IClientSimulatorImpl"); + } this.logger = jsLogger.get("netconnection"); this.logger.setLevel(jsLogger.DEBUG); diff --git a/src/simularium/ISimulator.ts b/src/simularium/Simulator/ISimulator.ts similarity index 74% rename from src/simularium/ISimulator.ts rename to src/simularium/Simulator/ISimulator.ts index b6c2e179..cca7d1bd 100644 --- a/src/simularium/ISimulator.ts +++ b/src/simularium/Simulator/ISimulator.ts @@ -1,4 +1,8 @@ -import { VisDataMessage, TrajectoryFileInfo } from "./types.js"; +import { VisDataMessage, TrajectoryFileInfo } from "../types.js"; +import { ClientSimulator } from "./ClientSimulator.js"; +import { LocalFileSimulator } from "./LocalFileSimulator.js"; +import { RemoteSimulator } from "./RemoteSimulator.js"; +import { SimulatorParams } from "./types.js"; /** From the caller's perspective, this interface is a contract for a @@ -6,6 +10,16 @@ simulator that can be used to control set up, tear down, and streaming, and to subscribe to data events and error handling. */ +export const getClassFromParams = (params: SimulatorParams) => { + if ("netConnectionSettings" in params) { + return RemoteSimulator; + } else if ("clientSimulatorImpl" in params) { + return ClientSimulator; + } else if ("simulariumFile" in params) { + return LocalFileSimulator; + } +}; + export interface ISimulator { /** * Callbacks to subscribe front end implementations diff --git a/src/simularium/LocalFileSimulator.ts b/src/simularium/Simulator/LocalFileSimulator.ts similarity index 89% rename from src/simularium/LocalFileSimulator.ts rename to src/simularium/Simulator/LocalFileSimulator.ts index e10ecf11..6e00d0dc 100644 --- a/src/simularium/LocalFileSimulator.ts +++ b/src/simularium/Simulator/LocalFileSimulator.ts @@ -1,9 +1,14 @@ import jsLogger from "js-logger"; import { ILogger } from "js-logger"; -import { VisDataFrame, VisDataMessage, TrajectoryFileInfoV2 } from "./types.js"; +import { + VisDataFrame, + VisDataMessage, + TrajectoryFileInfoV2, +} from "../types.js"; import { ISimulator } from "./ISimulator.js"; -import type { ISimulariumFile } from "./ISimulariumFile.js"; +import type { ISimulariumFile } from "../ISimulariumFile.js"; +import { LocalFileSimulatorParams } from "./types.js"; // a LocalFileSimulator is a ISimulator that plays back the contents of // a drag-n-drop trajectory file (a ISimulariumFile object) @@ -18,7 +23,14 @@ export class LocalFileSimulator implements ISimulator { private playbackIntervalId = 0; private currentPlaybackFrameIndex = 0; - public constructor(fileName: string, simulariumFile: ISimulariumFile) { + public constructor(params: LocalFileSimulatorParams) { + const { fileName, simulariumFile } = params; + if (!simulariumFile) { + throw new Error("LocalFileSimulator requires a ISimulariumFile"); + } + if (!fileName) { + throw new Error("LocalFileSimulator requires a fileName"); + } this.fileName = fileName; this.simulariumFile = simulariumFile; this.logger = jsLogger.get("netconnection"); diff --git a/src/simularium/RemoteSimulator.ts b/src/simularium/Simulator/RemoteSimulator.ts similarity index 97% rename from src/simularium/RemoteSimulator.ts rename to src/simularium/Simulator/RemoteSimulator.ts index 21d8c641..aafabf28 100644 --- a/src/simularium/RemoteSimulator.ts +++ b/src/simularium/Simulator/RemoteSimulator.ts @@ -1,14 +1,14 @@ import jsLogger from "js-logger"; import { ILogger } from "js-logger"; -import { FrontEndError, ErrorLevel } from "./FrontEndError.js"; +import { FrontEndError, ErrorLevel } from "../FrontEndError.js"; import { WebsocketClient, NetMessageEnum, MessageEventLike, -} from "./WebsocketClient.js"; -import type { NetMessage, ErrorMessage } from "./WebsocketClient.js"; +} from "../WebsocketClient.js"; +import type { NetMessage, ErrorMessage } from "../WebsocketClient.js"; import { ISimulator } from "./ISimulator.js"; -import { TrajectoryFileInfoV2, VisDataMessage } from "./types.js"; +import { TrajectoryFileInfoV2, VisDataMessage } from "../types.js"; // a RemoteSimulator is a ISimulator that connects to the Octopus backend server // and plays back a trajectory specified in the NetConnectionParams @@ -20,7 +20,6 @@ export class RemoteSimulator implements ISimulator { public lastRequestedFile: string; public handleError: (error: FrontEndError) => void | (() => void); private jsonResponse: boolean; - public constructor( webSocketClient: WebsocketClient, errorHandler?: (error: FrontEndError) => void, diff --git a/src/simularium/Simulator/types.ts b/src/simularium/Simulator/types.ts new file mode 100644 index 00000000..4ab72390 --- /dev/null +++ b/src/simularium/Simulator/types.ts @@ -0,0 +1,27 @@ +import { ISimulariumFile } from "../ISimulariumFile"; +import { IClientSimulatorImpl } from "../localSimulators/IClientSimulatorImpl"; +import { NetConnectionParams } from "../WebsocketClient"; + +export interface BaseSimulatorParams { + fileName: string; +} + +export interface RemoteSimulatorParams extends BaseSimulatorParams { + netConnectionSettings: NetConnectionParams; + requestJson?: boolean; + prefetchFrames?: boolean; +} + +export interface ClientSimulatorParams extends BaseSimulatorParams { + clientSimulatorImpl: IClientSimulatorImpl; +} + +export interface LocalFileSimulatorParams extends BaseSimulatorParams { + simulariumFile: ISimulariumFile; + geoAssets?: { [key: string]: string }; +} + +export type SimulatorParams = + | RemoteSimulatorParams + | ClientSimulatorParams + | LocalFileSimulatorParams; diff --git a/src/simularium/index.ts b/src/simularium/index.ts index fc55efea..9f693b97 100644 --- a/src/simularium/index.ts +++ b/src/simularium/index.ts @@ -18,7 +18,7 @@ export type { } from "./SelectionInterface.js"; export { ErrorLevel, FrontEndError } from "./FrontEndError.js"; export { NetMessageEnum } from "./WebsocketClient.js"; -export { RemoteSimulator } from "./RemoteSimulator.js"; +export { RemoteSimulator } from "./Simulator/RemoteSimulator.js"; export { VisData } from "./VisData.js"; export { ThreadUtil } from "./ThreadUtil.js"; export { SelectionInterface } from "./SelectionInterface.js"; diff --git a/src/simularium/types.ts b/src/simularium/types.ts index 48e8a6d0..610018ff 100644 --- a/src/simularium/types.ts +++ b/src/simularium/types.ts @@ -212,27 +212,3 @@ export interface CacheNode { next: CacheNode | null; prev: CacheNode | null; } - -export interface BaseSimulatorParams { - fileName: string; -} - -export interface RemoteSimulatorParams extends BaseSimulatorParams { - netConnectionSettings: NetConnectionParams; - requestJson?: boolean; - prefetchFrames?: boolean; -} - -export interface LocalProceduralSimulatorParams extends BaseSimulatorParams { - clientSimulatorImpl: IClientSimulatorImpl; -} - -export interface LocalFileSimulatorParams extends BaseSimulatorParams { - simulariumFile: ISimulariumFile; - geoAssets?: { [key: string]: string }; -} - -export type SimulatorParams = - | RemoteSimulatorParams - | LocalProceduralSimulatorParams - | LocalFileSimulatorParams; diff --git a/src/test/DummyRemoteSimulator.ts b/src/test/DummyRemoteSimulator.ts index f257cd22..8251dc96 100644 --- a/src/test/DummyRemoteSimulator.ts +++ b/src/test/DummyRemoteSimulator.ts @@ -4,7 +4,7 @@ import { NetMessageEnum, WebsocketClient, } from "../simularium/WebsocketClient.js"; -import { RemoteSimulator } from "../simularium/RemoteSimulator.js"; +import { RemoteSimulator } from "../simularium/Simulator/RemoteSimulator.js"; import { VisDataFrame, VisDataMessage } from "../simularium/types.js"; // Mocks the simularium simulation back-end, w/ latency diff --git a/src/test/SimulariumController.test.ts b/src/test/SimulariumController.test.ts index f981937c..9e7c4331 100644 --- a/src/test/SimulariumController.test.ts +++ b/src/test/SimulariumController.test.ts @@ -1,8 +1,8 @@ import { describe, expect, beforeEach, vi } from "vitest"; import { WebsocketClient } from "../simularium/WebsocketClient"; -import { ClientSimulator } from "../simularium/ClientSimulator"; -import { LocalFileSimulator } from "../simularium/LocalFileSimulator"; +import { ClientSimulator } from "../simularium/Simulator/ClientSimulator"; +import { LocalFileSimulator } from "../simularium/Simulator/LocalFileSimulator"; import type { NetConnectionParams } from "../simularium/WebsocketClient"; import { IClientSimulatorImpl, RemoteSimulator } from "../simularium"; import { makeBinary, pad } from "./BinaryFile.test"; @@ -13,10 +13,10 @@ import { TrajectoryType } from "../constants"; import { DummyOctopusServicesClient } from "./DummyOctopusClient"; import SimulariumController from "../controller"; import { + ClientSimulatorParams, LocalFileSimulatorParams, - LocalProceduralSimulatorParams, RemoteSimulatorParams, -} from "../simularium/types"; +} from "../simularium/Simulator/types"; // build test binary local file, borrowed from `src/test/BinaryFile.test.ts` const buffer = makeBinary( @@ -45,7 +45,7 @@ export const LocalFileTestParams: LocalFileSimulatorParams = { simulariumFile: binarySimFile, }; -export const ProceduralSimTestParams: LocalProceduralSimulatorParams = { +export const ProceduralSimTestParams: ClientSimulatorParams = { fileName: "procedural", clientSimulatorImpl: new TestClientSimulatorImpl(), }; diff --git a/src/test/util.test.ts b/src/test/util.test.ts index 373138af..e9a66c53 100644 --- a/src/test/util.test.ts +++ b/src/test/util.test.ts @@ -1,12 +1,12 @@ import { FrontEndError } from "../simularium/index.js"; -import { SimulatorParams } from "../simularium/types.js"; +import { SimulatorParams } from "../simularium/Simulator/types.js"; import { checkAndSanitizePath, compareTimes, getAgentDataFromBuffer, getNextAgentOffset, isLocalFileSimulatorParams, - isLocalProceduralSimulatorParams, + isClientSimulatorParams, isRemoteSimulatorParams, } from "../util.js"; import { @@ -273,7 +273,7 @@ describe("util", () => { describe("isLocalProceduralSimulatorParams()", () => { it("returns true if 'clientSimulatorImpl' key exists", () => { expect( - isLocalProceduralSimulatorParams( + isClientSimulatorParams( ProceduralSimTestParams as SimulatorParams ) ).toBe(true); @@ -284,9 +284,7 @@ describe("util", () => { fileName: "dummy", }; expect( - isLocalProceduralSimulatorParams( - dummyParams as SimulatorParams - ) + isClientSimulatorParams(dummyParams as SimulatorParams) ).toBe(false); }); }); diff --git a/src/util.ts b/src/util.ts index e933c879..9c022dd9 100644 --- a/src/util.ts +++ b/src/util.ts @@ -5,13 +5,15 @@ import { AGENT_OBJECT_KEYS, AgentData, CachedFrame, - LocalFileSimulatorParams, - LocalProceduralSimulatorParams, - RemoteSimulatorParams, - SimulatorParams, } from "./simularium/types.js"; import { FrontEndError, RemoteSimulator } from "./simularium/index.js"; import VisGeometry from "./visGeometry/index.js"; +import { + SimulatorParams, + ClientSimulatorParams, + LocalFileSimulatorParams, + RemoteSimulatorParams, +} from "./simularium/Simulator/types.js"; export const compareTimes = ( time1: number, @@ -140,9 +142,9 @@ export const getNextAgentOffset = ( //// Type guards for SimulatorParams //// -export const isLocalProceduralSimulatorParams = ( +export const isClientSimulatorParams = ( params: SimulatorParams -): params is LocalProceduralSimulatorParams => { +): params is ClientSimulatorParams => { return "clientSimulatorImpl" in params; }; From 19df28c382d13c741bbcacb420b5796dc9c100dc Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 18 Mar 2025 17:08:35 -0700 Subject: [PATCH 2/3] merge more of the code and push netconnection down --- src/controller/index.ts | 123 ++++++------------ src/simularium/Simulator/ClientSimulator.ts | 7 +- src/simularium/Simulator/ISimulator.ts | 30 ++++- .../Simulator/LocalFileSimulator.ts | 5 + src/simularium/Simulator/RemoteSimulator.ts | 47 ++++++- src/simularium/Simulator/types.ts | 6 +- src/test/SimulariumController.test.ts | 11 +- src/util.ts | 2 +- 8 files changed, 127 insertions(+), 104 deletions(-) diff --git a/src/controller/index.ts b/src/controller/index.ts index a709adbf..d2335a32 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -25,18 +25,14 @@ import { WebsocketClient } from "../simularium/WebsocketClient.js"; import { TrajectoryType } from "../constants.js"; import { RemoteMetricsCalculator } from "../simularium/RemoteMetricsCalculator.js"; import { OctopusServicesClient } from "../simularium/OctopusClient.js"; -import { - isLocalFileSimulatorParams, - isRemoteSimulatorParams, -} from "../util.js"; +import { isLocalFileSimulatorParams } from "../util.js"; import { SimulatorParams } from "../simularium/Simulator/types.js"; jsLogger.setHandler(jsLogger.createDefaultHandler()); export default class SimulariumController { public simulator?: ISimulator; - public remoteWebsocketClient?: WebsocketClient; - public octopusClient?: OctopusServicesClient; + public _octopusClient?: OctopusServicesClient; public metricsCalculator?: RemoteMetricsCalculator; public visData: VisData; public visGeometry: VisGeometry | undefined; @@ -76,53 +72,32 @@ export default class SimulariumController { this.cancelCurrentFile = this.cancelCurrentFile.bind(this); } - public buildSimulator(params: SimulatorParams) { - const SimulatorClass = getClassFromParams(params); - if (!SimulatorClass) { + public initSimulator(params: SimulatorParams) { + const { simulatorClass, typedParams } = getClassFromParams(params); + if (!simulatorClass) { throw new Error("Invalid simulator configuration"); } - let simulator: ISimulator; - if (isRemoteSimulatorParams(params)) { - if (this.needsNewNetworkConfig(params.netConnectionSettings)) { - this.configureNetwork(params.netConnectionSettings); - } - if (!this.remoteWebsocketClient) { - throw new Error("Websocket client not configured"); - } - - simulator = new RemoteSimulator( - this.remoteWebsocketClient, - this.onError, - params.requestJson - ); - return simulator; - } else { - simulator = new SimulatorClass(params); - - if ( - this.visGeometry && - "geoAssets" in params && - !isEmpty(params.geoAssets) - ) { - this.visGeometry.geometryStore.cacheLocalAssets( - params.geoAssets - ); - } + if ( + this.visGeometry && + "geoAssets" in params && + !isEmpty(params.geoAssets) + ) { + this.visGeometry.geometryStore.cacheLocalAssets(params.geoAssets); } - simulator.setTrajectoryDataHandler( - this.visData.parseAgentsFromNetData.bind(this.visData) - ); - return simulator; + // will throw an error if the params are invalid + return new simulatorClass(typedParams, this.onError); } private createSimulatorConnection(params: SimulatorParams): void { try { - this.simulator = this.buildSimulator(params); + this.simulator = this.initSimulator(params); } catch (err) { console.error("createSimulatorConnection failed", err); throw err; } - + this.simulator.setTrajectoryDataHandler( + this.visData.parseAgentsFromNetData.bind(this.visData) + ); this.simulator.setTrajectoryFileInfoHandler( (trajFileInfo: TrajectoryFileInfo) => { this.handleTrajectoryInfo(trajFileInfo); @@ -131,22 +106,12 @@ export default class SimulariumController { this.playBackFile = params.fileName; } - public configureNetwork(config: NetConnectionParams): Promise { - if ( - !this.remoteWebsocketClient || - !this.remoteWebsocketClient.socketIsValid() - ) { - this.octopusClient = undefined; - this.remoteWebsocketClient = new WebsocketClient( - config, - this.onError - ); - } - if (!this.octopusClient) { - this.octopusClient = new OctopusServicesClient( - this.remoteWebsocketClient - ); - } + public configureOctopus(config: NetConnectionParams): Promise { + const webSocketClient = + this.remoteWebsocketClient || + new WebsocketClient(config, this.onError); + + this._octopusClient = new OctopusServicesClient(webSocketClient); return this.octopusClient.connectToRemoteServer(); } @@ -155,27 +120,17 @@ export default class SimulariumController { return !!this.simulator; } - public get ensureOctopusClient(): OctopusServicesClient { - if (!this.octopusClient || !this.octopusClient.socketIsValid()) { + public get octopusClient(): OctopusServicesClient { + if (!this._octopusClient || !this._octopusClient.socketIsValid()) { throw new Error( "Remote Octopus client is not configured or socket is invalid." ); } - return this.octopusClient; + return this._octopusClient; } public isRemoteOctopusClientConfigured(): boolean { - return !!(this.octopusClient && this.octopusClient?.socketIsValid()); - } - - private needsNewNetworkConfig( - netConnectionConfig: NetConnectionParams - ): boolean { - const expectedIp = `wss://${netConnectionConfig.serverIp}:${netConnectionConfig.serverPort}/`; - return ( - !this.remoteWebsocketClient || - this.remoteWebsocketClient.getIp() !== expectedIp - ); + return !!(this.octopusClient && this.octopusClient.socketIsValid()); } public get isChangingFile(): boolean { @@ -316,7 +271,7 @@ export default class SimulariumController { netConnectionSettings: netConnectionConfig, fileName, }); - this.ensureOctopusClient.setOnConversionCompleteHandler(() => { + this.octopusClient.setOnConversionCompleteHandler(() => { this.start(); }); } @@ -334,7 +289,7 @@ export default class SimulariumController { try { this.setupConversion(netConnectionConfig, fileName); - return this.ensureOctopusClient.convertTrajectory( + return this.octopusClient.convertTrajectory( dataToConvert, fileType, fileName @@ -355,10 +310,7 @@ export default class SimulariumController { ): Promise { try { this.setupConversion(netConnectionConfig, fileName); - return this.ensureOctopusClient.sendSmoldynData( - fileName, - smoldynInput - ); + return this.octopusClient.sendSmoldynData(fileName, smoldynInput); } catch (e) { return Promise.reject(e); } @@ -377,15 +329,22 @@ export default class SimulariumController { netConnectionConfig: NetConnectionParams ): void { if (!this.isRemoteOctopusClientConfigured()) { - this.configureNetwork(netConnectionConfig); + this.configureOctopus(netConnectionConfig); } - this.ensureOctopusClient.setHealthCheckHandler(handler); - this.ensureOctopusClient.checkServerHealth(); + this.octopusClient.setHealthCheckHandler(handler); + this.octopusClient.checkServerHealth(); } public cancelConversion(): void { - this.ensureOctopusClient.cancelConversion(); + this.octopusClient.cancelConversion(); + } + + private get remoteWebsocketClient(): WebsocketClient | null { + if (!this.simulator) { + return null; + } + return this.simulator?.getWebsocket(); } private setupMetricsCalculator( diff --git a/src/simularium/Simulator/ClientSimulator.ts b/src/simularium/Simulator/ClientSimulator.ts index 700c95d7..8f3ae3e1 100644 --- a/src/simularium/Simulator/ClientSimulator.ts +++ b/src/simularium/Simulator/ClientSimulator.ts @@ -9,6 +9,7 @@ import { } from "../localSimulators/IClientSimulatorImpl.js"; import { ISimulator } from "./ISimulator.js"; import { ClientSimulatorParams } from "./types.js"; +import { WebsocketClient } from "../WebsocketClient.js"; // a ClientSimulator is a ISimulator that is expected to run purely in procedural javascript in the browser client, // with the procedural implementation in a IClientSimulatorImpl @@ -40,7 +41,11 @@ export class ClientSimulator implements ISimulator { this.handleError = () => { /* do nothing */ }; - this.localSimulator = sim; + this.localSimulator = clientSimulatorImpl; + } + + public getWebsocket(): WebsocketClient | null { + return null; } public setTrajectoryFileInfoHandler( diff --git a/src/simularium/Simulator/ISimulator.ts b/src/simularium/Simulator/ISimulator.ts index cca7d1bd..254e985b 100644 --- a/src/simularium/Simulator/ISimulator.ts +++ b/src/simularium/Simulator/ISimulator.ts @@ -1,8 +1,14 @@ import { VisDataMessage, TrajectoryFileInfo } from "../types.js"; +import { WebsocketClient } from "../WebsocketClient.js"; import { ClientSimulator } from "./ClientSimulator.js"; import { LocalFileSimulator } from "./LocalFileSimulator.js"; import { RemoteSimulator } from "./RemoteSimulator.js"; -import { SimulatorParams } from "./types.js"; +import { + ClientSimulatorParams, + LocalFileSimulatorParams, + RemoteSimulatorParams, + SimulatorParams, +} from "./types.js"; /** From the caller's perspective, this interface is a contract for a @@ -12,11 +18,25 @@ and to subscribe to data events and error handling. export const getClassFromParams = (params: SimulatorParams) => { if ("netConnectionSettings" in params) { - return RemoteSimulator; + return { + simulatorClass: RemoteSimulator, + typedParams: params as RemoteSimulatorParams, + }; } else if ("clientSimulatorImpl" in params) { - return ClientSimulator; + return { + simulatorClass: ClientSimulator, + typedParams: params as ClientSimulatorParams, + }; } else if ("simulariumFile" in params) { - return LocalFileSimulator; + return { + simulatorClass: LocalFileSimulator, + typedParams: params as LocalFileSimulatorParams, + }; + } else { + return { + simulatorClass: null, + typedParams: null, + }; } }; @@ -60,4 +80,6 @@ export interface ISimulator { requestFrameByTime(time: number): void; /** request trajectory metadata */ requestTrajectoryFileInfo(fileName: string): void; + + getWebsocket(): WebsocketClient | null; } diff --git a/src/simularium/Simulator/LocalFileSimulator.ts b/src/simularium/Simulator/LocalFileSimulator.ts index 6e00d0dc..eeda16ae 100644 --- a/src/simularium/Simulator/LocalFileSimulator.ts +++ b/src/simularium/Simulator/LocalFileSimulator.ts @@ -9,6 +9,7 @@ import { import { ISimulator } from "./ISimulator.js"; import type { ISimulariumFile } from "../ISimulariumFile.js"; import { LocalFileSimulatorParams } from "./types.js"; +import { WebsocketClient } from "../WebsocketClient.js"; // a LocalFileSimulator is a ISimulator that plays back the contents of // a drag-n-drop trajectory file (a ISimulariumFile object) @@ -142,6 +143,10 @@ export class LocalFileSimulator implements ISimulator { } } + public getWebsocket(): WebsocketClient | null { + return null; + } + public getSimulariumFile(): ISimulariumFile { return this.simulariumFile; } diff --git a/src/simularium/Simulator/RemoteSimulator.ts b/src/simularium/Simulator/RemoteSimulator.ts index aafabf28..d29ca098 100644 --- a/src/simularium/Simulator/RemoteSimulator.ts +++ b/src/simularium/Simulator/RemoteSimulator.ts @@ -6,9 +6,14 @@ import { NetMessageEnum, MessageEventLike, } from "../WebsocketClient.js"; -import type { NetMessage, ErrorMessage } from "../WebsocketClient.js"; +import type { + NetMessage, + ErrorMessage, + NetConnectionParams, +} from "../WebsocketClient.js"; import { ISimulator } from "./ISimulator.js"; import { TrajectoryFileInfoV2, VisDataMessage } from "../types.js"; +import { RemoteSimulatorParams } from "./types.js"; // a RemoteSimulator is a ISimulator that connects to the Octopus backend server // and plays back a trajectory specified in the NetConnectionParams @@ -21,21 +26,25 @@ export class RemoteSimulator implements ISimulator { public handleError: (error: FrontEndError) => void | (() => void); private jsonResponse: boolean; public constructor( - webSocketClient: WebsocketClient, - errorHandler?: (error: FrontEndError) => void, - jsonResponse = false + params: RemoteSimulatorParams, + errorHandler?: (error: FrontEndError) => void ) { - this.webSocketClient = webSocketClient; + const { netConnectionSettings, requestJson } = params; + if (!netConnectionSettings) { + throw new Error("RemoteSimulator requires a NetConnectionParams"); + } + this.lastRequestedFile = ""; this.handleError = errorHandler || (() => { /* do nothing */ }); + this.webSocketClient = this.configureNetwork(netConnectionSettings); this.logger = jsLogger.get("netconnection"); this.logger.setLevel(jsLogger.DEBUG); - this.jsonResponse = jsonResponse; + this.jsonResponse = requestJson || false; this.registerBinaryMessageHandlers(); this.registerJsonMessageHandlers(); @@ -47,6 +56,10 @@ export class RemoteSimulator implements ISimulator { }; } + public getWebsocket(): WebsocketClient | null { + return this.webSocketClient; + } + public setTrajectoryFileInfoHandler( handler: (msg: TrajectoryFileInfoV2) => void ): void { @@ -186,6 +199,28 @@ export class RemoteSimulator implements ISimulator { /** * WebSocket Connect * */ + + private needsNewNetworkConfig( + netConnectionConfig: NetConnectionParams + ): boolean { + const expectedIp = `wss://${netConnectionConfig.serverIp}:${netConnectionConfig.serverPort}/`; + return ( + !this.webSocketClient || this.webSocketClient.getIp() !== expectedIp + ); + } + public configureNetwork(config: NetConnectionParams) { + if (!this.needsNewNetworkConfig(config)) { + return this.webSocketClient; + } + if (!this.webSocketClient || !this.webSocketClient.socketIsValid()) { + this.webSocketClient = new WebsocketClient( + config, + this.handleError + ); + } + return this.webSocketClient; + } + public disconnect(): void { this.webSocketClient.disconnect(); } diff --git a/src/simularium/Simulator/types.ts b/src/simularium/Simulator/types.ts index 4ab72390..8a68150d 100644 --- a/src/simularium/Simulator/types.ts +++ b/src/simularium/Simulator/types.ts @@ -7,17 +7,17 @@ export interface BaseSimulatorParams { } export interface RemoteSimulatorParams extends BaseSimulatorParams { - netConnectionSettings: NetConnectionParams; + netConnectionSettings?: NetConnectionParams; requestJson?: boolean; prefetchFrames?: boolean; } export interface ClientSimulatorParams extends BaseSimulatorParams { - clientSimulatorImpl: IClientSimulatorImpl; + clientSimulatorImpl?: IClientSimulatorImpl; } export interface LocalFileSimulatorParams extends BaseSimulatorParams { - simulariumFile: ISimulariumFile; + simulariumFile?: ISimulariumFile; geoAssets?: { [key: string]: string }; } diff --git a/src/test/SimulariumController.test.ts b/src/test/SimulariumController.test.ts index 9e7c4331..746005a8 100644 --- a/src/test/SimulariumController.test.ts +++ b/src/test/SimulariumController.test.ts @@ -14,7 +14,6 @@ import { DummyOctopusServicesClient } from "./DummyOctopusClient"; import SimulariumController from "../controller"; import { ClientSimulatorParams, - LocalFileSimulatorParams, RemoteSimulatorParams, } from "../simularium/Simulator/types"; @@ -40,7 +39,7 @@ const buffer = makeBinary( ); const binarySimFile = new BinaryFileReader(buffer); -export const LocalFileTestParams: LocalFileSimulatorParams = { +export const LocalFileTestParams: LocalSimulatorParams = { fileName: "local.simularium", simulariumFile: binarySimFile, }; @@ -87,21 +86,19 @@ describe("SimulariumController", () => { describe("buildSimulator()", () => { test("should configure a LocalFileSimulator if a local file params are provided", () => { - const simulator = controller.buildSimulator(LocalFileTestParams); + const simulator = controller.initSimulator(LocalFileTestParams); expect(simulator instanceof LocalFileSimulator).toBe(true); expect(simulator instanceof ClientSimulator).toBe(false); expect(simulator instanceof RemoteSimulator).toBe(false); }); test("should configure a ClientSimulator if a procedural params are provided", () => { - const simulator = controller.buildSimulator( - ProceduralSimTestParams - ); + const simulator = controller.initSimulator(ProceduralSimTestParams); expect(simulator instanceof ClientSimulator).toBe(true); expect(simulator instanceof LocalFileSimulator).toBe(false); expect(simulator instanceof RemoteSimulator).toBe(false); }); test("should configure a RemoteSimulator if a remote params are provided", () => { - const simulator = controller.buildSimulator(RemoteSimTestParams); + const simulator = controller.initSimulator(RemoteSimTestParams); expect(simulator instanceof RemoteSimulator).toBe(true); expect(simulator instanceof ClientSimulator).toBe(false); expect(simulator instanceof LocalFileSimulator).toBe(false); diff --git a/src/util.ts b/src/util.ts index 9c022dd9..eaa7424f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -11,8 +11,8 @@ import VisGeometry from "./visGeometry/index.js"; import { SimulatorParams, ClientSimulatorParams, - LocalFileSimulatorParams, RemoteSimulatorParams, + LocalFileSimulatorParams, } from "./simularium/Simulator/types.js"; export const compareTimes = ( From 0dc272d7106e3baa99f7aebe0d92a3080f4cec97 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 18 Mar 2025 17:26:43 -0700 Subject: [PATCH 3/3] better error handling --- src/controller/index.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/controller/index.ts b/src/controller/index.ts index d2335a32..181a261f 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -72,10 +72,25 @@ export default class SimulariumController { this.cancelCurrentFile = this.cancelCurrentFile.bind(this); } + private handleError(message: string): void { + if (this.onError) { + return this.onError(new FrontEndError(message)); + } else { + throw new Error(message); + } + } + public initSimulator(params: SimulatorParams) { + if (!params) { + this.handleError("Invalid simulator configuration"); + } + if (!params.fileName) { + this.handleError("Invalid simulator configuration: no file name"); + } const { simulatorClass, typedParams } = getClassFromParams(params); if (!simulatorClass) { - throw new Error("Invalid simulator configuration"); + this.handleError("Invalid simulator configuration"); + return; } if ( this.visGeometry && @@ -89,11 +104,9 @@ export default class SimulariumController { } private createSimulatorConnection(params: SimulatorParams): void { - try { - this.simulator = this.initSimulator(params); - } catch (err) { - console.error("createSimulatorConnection failed", err); - throw err; + this.simulator = this.initSimulator(params); + if (!this.simulator) { + return; } this.simulator.setTrajectoryDataHandler( this.visData.parseAgentsFromNetData.bind(this.visData)