Skip to content
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
43693cc
remove loadInitialData/postConnect flow and controller params
interim17 Mar 17, 2025
19fc1b7
fix placeholder test
interim17 Mar 17, 2025
aa0dcee
move plots and metrics API into ISimulator
interim17 Mar 21, 2025
58c9324
remove server health checks
interim17 Mar 21, 2025
5e409f4
extend BaseRemoteClient for classes that need websockets and handle o…
interim17 Mar 21, 2025
18f282a
update tests for remote simulator and add tests for base remote client
interim17 Mar 21, 2025
c3c1bde
make DummyRemoteSimulator properly extend RemoteSimulator
interim17 Mar 21, 2025
1571508
separate conversion client getter and connection check
interim17 Mar 21, 2025
b38f79a
remove code path for requesting remote metrics for local file
interim17 Mar 26, 2025
7a28fd2
implement callback pattern for plots and metrics
interim17 Mar 27, 2025
b8344b4
Merge branch 'feature/isimulator-plot-api' into feature/base-remote-c…
interim17 Mar 27, 2025
74ecc3c
Merge branch 'feature/controller-updates' of https://github.com/simul…
interim17 Mar 27, 2025
13adefa
make remote abstractions extend web socket client
interim17 Mar 28, 2025
8ce4a6b
get unused file name arg out of test
interim17 Mar 28, 2025
bf6f3d7
use file name from params
interim17 Mar 28, 2025
caee3d4
use composition over inheritance with websocket client
interim17 Mar 31, 2025
69f405a
fix tests for remote simulator to use websocket client as composed va…
interim17 Mar 31, 2025
5f05fa0
use old capitalization scheme on web socket client
interim17 Mar 31, 2025
8161f72
use old capitalization scheme on web socket client
interim17 Mar 31, 2025
b4a51f5
Merge branch 'feature/base-remote-client' of https://github.com/simul…
interim17 Mar 31, 2025
ac0c092
Merge branch 'feature/base-remote-client' of https://github.com/simul…
interim17 Mar 31, 2025
fb4dd2e
Merge branch 'feature/base-remote-client' of https://github.com/simul…
interim17 Mar 31, 2025
cdfe7cb
pass file name to conversion client when calling methods instead of i…
interim17 Mar 31, 2025
6f65909
reorg for new simulator params and controller testing
interim17 Apr 21, 2025
aa10bd1
move testing binary utils into separate file
interim17 Apr 21, 2025
1577a0a
update readme
interim17 Apr 21, 2025
6677ff3
Merge branch 'feature/controller-updates' of https://github.com/simul…
interim17 May 6, 2025
1aa7e30
dont unset simulator in call of stop
interim17 May 6, 2025
6e196ae
fix linting errors on controller tests for empty arrow functions
interim17 May 6, 2025
784019c
only mock the result of getSimulatorClassFromParams if its a remote sim
interim17 May 6, 2025
8780cf0
make new module SimulatorFactory
interim17 May 27, 2025
24b1330
deeper equality check on net connection configs
interim17 Jul 7, 2025
c6516c4
Update src/Simulator/ClientSimulator.ts
interim17 Jul 7, 2025
46bb76f
Update src/Simulator/LocalFileSimulator.ts
interim17 Jul 7, 2025
34fca1c
Merge branch 'feature/new-build-simulators' of https://github.com/sim…
interim17 Jul 7, 2025
591b000
remove basesimulatorparams class
interim17 Jul 7, 2025
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const netConnectionSettings = {
};

const simulariumController = new SimulariumController();
// todo show correct call of changeFile to load trajectory

class Viewer extends React.Component {

Expand All @@ -66,6 +65,16 @@ class Viewer extends React.Component {
}
}

public componentDidMount(): void {
// Load a trajectory by calling `changeFile`.
// You can do this here in a lifecycle method or call it later,
// whenever you’re ready to switch files.
simulariumController.changeFile({
fileName: "actin012_3.h5",
netConnectionSettings: netConnectionSettings,
});
}

handleTimeChange = (timeData) => {
console.log(timeData)
}
Expand All @@ -92,6 +101,7 @@ class Viewer extends React.Component {
showPaths={this.state.showPaths}
/>)
}
}
```

## Run an example app locally
Expand Down
1 change: 0 additions & 1 deletion examples/src/Components/ConversionForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ interface InputFormProps {
templateData: { [key: string]: any };
type: string;
submitFile: (data) => void;
onReturned: () => void;
}

class InputForm extends React.Component<InputFormProps> {
Expand Down
49 changes: 25 additions & 24 deletions examples/src/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ class Viewer extends React.Component<InputParams, ViewerState> {
this.handleJsonMeshData = this.handleJsonMeshData.bind(this);
this.handleTimeChange = this.handleTimeChange.bind(this);
this.loadFile = this.loadFile.bind(this);
this.clearPendingFile = this.clearPendingFile.bind(this);
this.convertFile = this.convertFile.bind(this);
this.handleResize = this.handleResize.bind(this);
this.state = initialState;
Expand Down Expand Up @@ -341,7 +340,6 @@ class Viewer extends React.Component<InputParams, ViewerState> {
fileName
)
.then(() => {
this.clearPendingFile();
this.setState({ awaitingConversion: true });
})
.catch((err) => {
Expand All @@ -360,7 +358,6 @@ class Viewer extends React.Component<InputParams, ViewerState> {
this.smoldynInput
)
.then(() => {
this.clearPendingFile();
this.setState({ awaitingConversion: true });
})
.catch((err) => {
Expand All @@ -369,17 +366,17 @@ class Viewer extends React.Component<InputParams, ViewerState> {
});
}

public clearPendingFile() {
this.setState({ filePending: null });
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick sanity check: this looks like something important, where the code wanted to know that there was a file in-flight? is it really ok to remove it and within scope of this PR? or is this dead code that was never used?

public loadFile(trajectoryFile, fileName, geoAssets?) {
const simulariumFile = fileName.includes(".simularium")
? trajectoryFile
: null;
this.setState({ initialPlay: true });
return simulariumController
.handleFileChange(simulariumFile, fileName, geoAssets)
.changeFile({
simulariumFile,
fileName,
geoAssets,
})
.catch(console.log);
}

Expand Down Expand Up @@ -597,13 +594,11 @@ class Viewer extends React.Component<InputParams, ViewerState> {
clientSimulator: true,
simulariumFile: { name: fileId, data: null },
});
simulariumController.changeFile(
{
netConnectionSettings: this.netConnectionSettings,
requestJson: true,
},
fileId
);
simulariumController.changeFile({
fileName: fileId,
netConnectionSettings: this.netConnectionSettings,
requestJson: true,
});
}

private handleFileSelect(file: string) {
Expand All @@ -626,8 +621,17 @@ class Viewer extends React.Component<InputParams, ViewerState> {
return;
}
if (fileId.simulatorType === SimulatorModes.localClientSimulator) {
const clientSim = this.configureLocalClientSimulator(fileId.id);
simulariumController.changeFile(clientSim, fileId.id);
const { clientSimulator } = this.configureLocalClientSimulator(
fileId.id
);
if (!clientSimulator) {
console.warn("No client simulator implementation found");
return;
}
simulariumController.changeFile({
fileName: fileId.id,
clientSimulatorImpl: clientSimulator,
});
this.setState({
clientSimulator: true,
});
Expand All @@ -640,12 +644,10 @@ class Viewer extends React.Component<InputParams, ViewerState> {
this.setState({
simulariumFile: { name: fileId.id, data: null },
});
simulariumController.changeFile(
{
netConnectionSettings: this.netConnectionSettings,
},
fileId.id
);
simulariumController.changeFile({
fileName: fileId.id,
netConnectionSettings: this.netConnectionSettings,
});
}
}

Expand Down Expand Up @@ -748,7 +750,6 @@ class Viewer extends React.Component<InputParams, ViewerState> {
<ConversionForm
{...this.state.filePending}
submitFile={(obj) => this.convertFile(obj, fileType)}
onReturned={this.clearPendingFile}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import jsLogger from "js-logger";
import { ILogger } from "js-logger";

import { ISimulator } from "../Simulator/ISimulator.js";
import {
IClientSimulatorImpl,
ClientMessageEnum,
} from "../simularium/index.js";
import {
VisDataMessage,
TrajectoryFileInfo,
PlotConfig,
Plot,
VisDataMessage,
Metrics,
} from "./types.js";
import {
ClientMessageEnum,
ClientPlayBackType,
IClientSimulatorImpl,
} from "./localSimulators/IClientSimulatorImpl.js";
import { ISimulator } from "./ISimulator.js";
Plot,
PlotConfig,
} from "../simularium/types.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
Expand All @@ -30,7 +30,11 @@ export class ClientSimulator implements ISimulator {
public onPlotDataArrive: (msg: Plot[]) => 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);

Expand All @@ -49,7 +53,7 @@ export class ClientSimulator implements ISimulator {
this.onPlotDataArrive = () => {
/* do nothing */
};
this.localSimulator = sim;
this.localSimulator = clientSimulatorImpl;
}

public setTrajectoryFileInfoHandler(
Expand Down Expand Up @@ -168,7 +172,6 @@ export class ClientSimulator implements ISimulator {
public initialize(fileName: string): Promise<void> {
const jsonData = {
msgType: ClientMessageEnum.ID_VIS_DATA_REQUEST,
mode: ClientPlayBackType.ID_TRAJECTORY_FILE_PLAYBACK,
fileName: fileName,
};

Expand Down Expand Up @@ -205,7 +208,6 @@ export class ClientSimulator implements ISimulator {
this.sendSimulationRequest(
{
msgType: ClientMessageEnum.ID_VIS_DATA_REQUEST,
mode: ClientPlayBackType.ID_TRAJECTORY_FILE_PLAYBACK,
frameNumber: startFrameNumber,
},
"Request Single Frame"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
PlotConfig,
Plot,
Metrics,
} from "./types.js";
} from "../simularium/types.js";

/**
From the caller's perspective, this interface is a contract for a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
PlotConfig,
Plot,
Metrics,
} from "./types.js";
} from "../simularium/types.js";
import { ISimulator } from "./ISimulator.js";
import type { ISimulariumFile } from "./ISimulariumFile.js";
import type { ISimulariumFile } from "../simularium/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)
Expand All @@ -27,8 +28,12 @@ export class LocalFileSimulator implements ISimulator {
private playbackIntervalId = 0;
private currentPlaybackFrameIndex = 0;

public constructor(simulariumFile: ISimulariumFile) {
this.fileName = "";
public constructor(params: LocalFileSimulatorParams) {
const { fileName, simulariumFile } = params;
if (!simulariumFile) {
throw new Error("LocalFileSimulator requires a ISimulariumFile");
}
this.fileName = fileName;
this.simulariumFile = simulariumFile;
this.logger = jsLogger.get("netconnection");
this.logger.setLevel(jsLogger.DEBUG);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import jsLogger from "js-logger";
import { ILogger } from "js-logger";
import { FrontEndError, ErrorLevel } from "./FrontEndError.js";
import { FrontEndError, ErrorLevel } from "../simularium/FrontEndError.js";
import {
NetMessageEnum,
MessageEventLike,
WebsocketClient,
} from "./WebsocketClient.js";
} from "../simularium/WebsocketClient.js";
import type {
NetMessage,
ErrorMessage,
NetConnectionParams,
} from "./WebsocketClient.js";
} from "../simularium/WebsocketClient.js";
import { ISimulator } from "./ISimulator.js";
import {
Metrics,
Plot,
PlotConfig,
TrajectoryFileInfoV2,
VisDataMessage,
} from "./types.js";
} from "../simularium/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
Expand All @@ -34,15 +34,20 @@ export class RemoteSimulator implements ISimulator {
private jsonResponse: boolean;

public constructor(
netConnectionSettings: NetConnectionParams,
errorHandler?: (error: FrontEndError) => void,
jsonResponse = false
params: RemoteSimulatorParams,
errorHandler?: (error: FrontEndError) => void
) {
const { netConnectionSettings, fileName, requestJson = false } = params;
if (!params.netConnectionSettings || !params.fileName) {
throw new FrontEndError(
"RemoteSimulator requires NetConnectionParams and file name."
);
}
this.webSocketClient = new WebsocketClient(
netConnectionSettings,
errorHandler
);
this.lastRequestedFile = "";
this.lastRequestedFile = fileName;
this.handleError =
errorHandler ||
(() => {
Expand All @@ -51,7 +56,7 @@ export class RemoteSimulator implements ISimulator {

this.logger = jsLogger.get("netconnection");
this.logger.setLevel(jsLogger.DEBUG);
this.jsonResponse = jsonResponse;
this.jsonResponse = requestJson;

this.onTrajectoryFileInfoArrive = () => {
/* do nothing */
Expand Down Expand Up @@ -87,6 +92,10 @@ export class RemoteSimulator implements ISimulator {
this.handleError = handler;
}

public isConnectedToRemoteServer(): boolean {
return this.webSocketClient.socketIsValid();
}

/**
* WebSocket Simulation Control
*/
Expand Down
33 changes: 33 additions & 0 deletions src/Simulator/SimulatorFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ClientSimulator } from "./ClientSimulator";
import { LocalFileSimulator } from "./LocalFileSimulator";
import { RemoteSimulator } from "./RemoteSimulator";
import {
SimulatorParams,
RemoteSimulatorParams,
ClientSimulatorParams,
LocalFileSimulatorParams,
} from "./types";

export const getSimulatorClassFromParams = (params?: SimulatorParams) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: just for the record, a true "factory" would actually CREATE and return instances of these classes. Just for clarity of terminology, as this is sort of a meta-factory which is just finding out what kind of class to create.

if (!params || !params.fileName) {
return { simulatorClass: null, typedParams: null };
}
if ("netConnectionSettings" in params) {
return {
simulatorClass: RemoteSimulator,
typedParams: params as RemoteSimulatorParams,
};
} else if ("clientSimulatorImpl" in params) {
return {
simulatorClass: ClientSimulator,
typedParams: params as ClientSimulatorParams,
};
} else if ("simulariumFile" in params) {
return {
simulatorClass: LocalFileSimulator,
typedParams: params as LocalFileSimulatorParams,
};
} else {
return { simulatorClass: null, typedParams: null };
}
};
26 changes: 26 additions & 0 deletions src/Simulator/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NetConnectionParams, IClientSimulatorImpl } from "../simularium";
import { ISimulariumFile } from "../simularium/ISimulariumFile";

export interface BaseSimulatorParams {
fileName: string;
}

export interface RemoteSimulatorParams extends BaseSimulatorParams {
netConnectionSettings?: NetConnectionParams;
requestJson?: boolean;
prefetchFrames?: boolean;
}

export interface ClientSimulatorParams extends BaseSimulatorParams {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious, why do these all extend BaseSimulatorParams? it feels like BaseSimulatorParams is never going to grow beyond fileName and ClientSimulatorParams doesn't even have a fileName..
Is BaseSimulatorParams used anywhere outside of this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm misunderstanding you, but ClientSimulatorParams does have a "file name" in a sense. If you look at the way the test bed loads client simulators we use strings in the ID field to serve as file name, and the downstream simulator factory method and initialize calls expect all incoming params to have a value for that field.

It's true that you currently can't actually load a client simulator file from your local machine (like via drag and drop, although that feature should exist imho) but even the "file name" for a remote trajectory is more of an ID. I think we could have something saved on octopus with a random string and as long as the server sent the right data back it's just a convention that the remote files have a .simularium extension.

I'd have to poke around but it seems like a pretty baked-in convention/assumption that everything that gets loaded in the viewer has a file name, would take some refactoring in a few places to take it out.

From test bed configs:

    {
        id: "blood-plasma-1.0.simularium",
        name: "Blood Plasma",
        simulatorType: SimulatorModes.remotePrecomputed,
    },
    {
        id: "TEST_POINTS",
        name: "Point Simulation",
        simulatorType: SimulatorModes.localClientSimulator,
    },
    ```
As for the base class we could just make each param interface have fileName rather than use a base class though, I don't remember why we did that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe trajectoryId or simulationId would be more general than filename then. I think my comment was partly motivated because it seemed odd to introduce a base class for just one single variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, pushed a quick change removing the base class but retaining fileName.

If you'd like to also see the move towards a different naming I'll work on that as well, but that naming convention is a bit more spread out so might be more involved.

clientSimulatorImpl?: IClientSimulatorImpl;
}

export interface LocalFileSimulatorParams extends BaseSimulatorParams {
simulariumFile?: ISimulariumFile;
geoAssets?: { [key: string]: string };
}

export type SimulatorParams =
| RemoteSimulatorParams
| ClientSimulatorParams
| LocalFileSimulatorParams;
Loading