Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 28 additions & 15 deletions src/api/core/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const logger = getLogger(["core", "Controller"]);

export class Controller {
model_manager: ModelManager;
model_manager: ModelManager | undefined;
monitor_server: MonitorServer;
player_manager: PlayerManager;
gama_connector: GamaConnector | undefined;
Expand All @@ -22,14 +22,21 @@

constructor(useAdb: boolean) {
// this.mDnsService = new mDnsService(process.env.WEB_HOSTNAME);
this.model_manager = new ModelManager(this);
this.monitor_server = new MonitorServer(this);
this.player_manager = new PlayerManager(this);
if (ENV_GAMALESS) {
logger.info("Web platform launched in 'Gamaless' mode")
const border = "=".repeat(58);
logger.warn(border);
logger.warn("= =");
logger.warn("= !! GAMALESS MODE ACTIVE — NO GAMA, NO MODEL MANAGER =");
logger.warn("= Simulation features are fully disabled. =");
logger.warn("= Only headset/player management is operational. =");
logger.warn("= =");
logger.warn(border);
} else {
this.model_manager = new ModelManager(this);
this.gama_connector = new GamaConnector(this);
}
this.monitor_server = new MonitorServer(this);
this.player_manager = new PlayerManager(this);

if (useAdb) {
this.adb_manager = new AdbManager(this);
Expand Down Expand Up @@ -58,10 +65,10 @@
this.player_manager = new PlayerManager(this);
this.monitor_server = new MonitorServer(this);


if (ENV_GAMALESS) {
logger.trace("skipped restarting the gama connector, application in gamaless mode...")
logger.trace("Skipped restarting the gama connector and model manager, application in gamaless mode...")
} else {
this.model_manager = new ModelManager(this);
this.gama_connector = new GamaConnector(this);
}

Expand All @@ -77,7 +84,12 @@
*/

getSimulationInformations(): string {
return this.model_manager.getCatalogListJSON();
if (!ENV_GAMALESS) {

Check warning on line 87 in src/api/core/Controller.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Bt0pnyHFRBIWnW&open=AZz7S4Bt0pnyHFRBIWnW&pullRequest=131
return this.model_manager!.getCatalogListJSON();
} else {
logger.debug("[getSimulationInformations] model_manager is not available in GAMALESS mode");
return JSON.stringify([]);
}
}

/*
Expand Down Expand Up @@ -110,7 +122,7 @@
if (!ENV_GAMALESS)
this.gama_connector!.addInGamePlayer(id_player);
else
logger.warn("[addInGamePlayer] Message received to add player in GAMA, but the webplatform is in GAMALESS mode...");
logger.debug("[addInGamePlayer] Message received to add player in GAMA, but the webplatform is in GAMALESS mode...");
}

purgePlayer(id_player: string): void {
Expand All @@ -119,6 +131,7 @@
// Remove from GAMA
if (!ENV_GAMALESS)
this.gama_connector!.removeInGamePlayer(id_player);

// Remove from connected list
this.player_manager.removePlayer(id_player);

Expand All @@ -135,14 +148,14 @@
if (!ENV_GAMALESS)
this.gama_connector!.sendExpression(id_player, expr);
else
logger.warn("[sendExpression] Message received to send to GAMA, but the webplatform is in GAMALESS mode...");
logger.debug("[sendExpression] Message received to send to GAMA, but the webplatform is in GAMALESS mode...");
}

sendAsk(json: JsonPlayerAsk) {
if (!ENV_GAMALESS)
this.gama_connector!.sendAsk(json);
else
logger.warn("[sendAsk] Message received to send to GAMA, but the webplatform is in GAMALESS mode...");
logger.debug("[sendAsk] Message received to send to GAMA, but the webplatform is in GAMALESS mode...");
}

launchExperiment() {
Expand All @@ -158,7 +171,7 @@
this.notifyMonitor();
}, 100);
} else
logger.warn("[launchExperiment] Message received to load an experiment in GAMA, but the webplatform is in GAMALESS mode...");
logger.debug("[launchExperiment] Message received to load an experiment in GAMA, but the webplatform is in GAMALESS mode...");
}

stopExperiment() {
Expand All @@ -168,21 +181,21 @@

this.notifyMonitor();
} else
logger.warn("[stopExperiment] Message received to close current GAMA simulation, but the webplatform is in GAMALESS mode...");
logger.debug("[stopExperiment] Message received to close current GAMA simulation, but the webplatform is in GAMALESS mode...");
}

pauseExperiment(callback?: () => void) {
if (!ENV_GAMALESS)
this.gama_connector!.pauseExperiment(callback);
else
logger.warn("[pauseExperiment] Message received to pause current GAMA simulation, but the webplatform is in GAMALESS mode...");
logger.debug("[pauseExperiment] Message received to pause current GAMA simulation, but the webplatform is in GAMALESS mode...");
}

resumeExperiment() {
if (!ENV_GAMALESS)
this.gama_connector!.resumeExperiment();
else
logger.warn("[resumeExperiment] Message received to resume current GAMA simulation, but the webplatform is in GAMALESS mode...");
logger.debug("[resumeExperiment] Message received to resume current GAMA simulation, but the webplatform is in GAMALESS mode...");
}
}

Expand Down
54 changes: 41 additions & 13 deletions src/api/monitoring/MonitorServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { JsonMonitor } from "../core/Constants.ts";
import { getLogger } from "@logtape/logtape";
import Model from "../simulation/Model.ts";
import { ENV_GAMALESS } from "../index.ts";

const logger = getLogger(["monitor", "MonitorServer"]);

Expand Down Expand Up @@ -47,13 +48,12 @@
this.sendMonitorGamaState();
this.sendMonitorJsonSettings();
},
message: (ws, message) => {

Check failure on line 51 in src/api/monitoring/MonitorServer.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Ch0pnyHFRBIWnX&open=AZz7S4Ch0pnyHFRBIWnX&pullRequest=131
const jsonMonitor: JsonMonitor = JSON.parse(
Buffer.from(message).toString(),
);
const type = jsonMonitor.type;
(logger.trace("Received message on monitor server : {jsonMonitor}"),
{ jsonMonitor });
logger.trace("Received message on monitor server : {jsonMonitor}", { jsonMonitor });

switch (type) {
case "launch_experiment":
Expand Down Expand Up @@ -108,6 +108,10 @@

case "get_simulation_informations":
logger.trace("Requesting and sending back simulation information");
if (ENV_GAMALESS) {
logger.debug("[get_simulation_informations] model_manager unavailable in GAMALESS mode");
break;
}
this.sendMessageByWs(
this.controller.getSimulationInformations(),
ws,
Expand All @@ -116,16 +120,20 @@

case "get_simulation_by_index":
logger.trace("Requesting and sending back simulation by index");
if (ENV_GAMALESS) {
logger.debug("[get_simulation_by_index] model_manager unavailable in GAMALESS mode");
break;
}
const index: number|undefined = jsonMonitor.simulationIndex;

if (index !== undefined && index >= 0 && index < this.controller.model_manager.getModelList().length) {
if (index !== undefined && index >= 0 && index < this.controller.model_manager!.getModelList().length) {
// Retrieve the simulation based on the index
this.controller.model_manager.setActiveModelByIndex(index);
this.controller.model_manager!.setActiveModelByIndex(index);
logger.debug("set active model to {modelName}", {
modelName: this.controller.model_manager.getActiveModel().toString()
modelName: this.controller.model_manager!.getActiveModel().toString()
});

const selectedSimulation = this.controller.model_manager.getActiveModel();
const selectedSimulation = this.controller.model_manager!.getActiveModel();

logger.trace("Sending back");
this.sendMessageByWs({
Expand All @@ -141,17 +149,28 @@

case "send_simulation":
logger.trace("Sending simulation");
if (ENV_GAMALESS) {
logger.debug("[send_simulation] model_manager unavailable in GAMALESS mode");
break;
}
const simulationFromStream = JSON.parse(Buffer.from(message).toString());

this.controller.model_manager.setActiveModelByIndex(simulationFromStream.simulation.model_index);
const selectedSimulation: Model = this.controller.model_manager.getActiveModel();
this.controller.model_manager!.setActiveModelByIndex(simulationFromStream.simulation.model_index);
const selectedSimulation: Model = this.controller.model_manager!.getActiveModel();

Check warning on line 159 in src/api/monitoring/MonitorServer.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Ch0pnyHFRBIWnY&open=AZz7S4Ch0pnyHFRBIWnY&pullRequest=131
logger.debug("Selected simulation sent to gama: {json}", { json: selectedSimulation.getJsonSettings() });
this.sendMessageByWs({
type: "get_simulation_by_index",
simulation: selectedSimulation.getJsonSettings()
}, ws);
break;

case "try_connection":
if (ENV_GAMALESS) {
logger.debug("[try_connection] GAMA is not active (GAMALESS mode) — ignoring connection attempt");
}
// In normal mode: silently ignore, GamaConnector manages its own connection
break;

default:
logger.warn(
"The last message received from the monitor had an unknown type.\n{jsonMonitor}",
Expand All @@ -163,9 +182,7 @@
close: (ws, code: number, message) => {
try {
this.wsClients.delete(ws);
logger.debug(
`Connection closed. Code: ${code}, Reason: ${Buffer.from(message).toString()}`,
);
logger.debug(`Connection closed. Code: ${code}, Reason: ${Buffer.from(message).toString()}`);

// Handle specific close codes
switch (code) {
Expand Down Expand Up @@ -207,8 +224,19 @@
* Sends the json_state to the monitor
*/
sendMonitorGamaState(): void {
if (ENV_GAMALESS) {
const messageToSend = {
type: "json_state",
gama: {},
player: this.controller.player_manager.getArrayPlayerList(),
};
logger.trace("Sending monitor gama state (GAMALESS):\n{messageToSend}", { messageToSend });
this.sendMessageByWs(messageToSend);
return;
}

if (
this.controller.model_manager.getActiveModel() !== undefined &&
this.controller.model_manager?.getActiveModel() !== undefined &&
this.controller.gama_connector !== undefined
) {
const messageToSend = {
Expand All @@ -228,7 +256,7 @@
* Send the json_setting to the monitor
*/
sendMonitorJsonSettings(): void {
if (this.controller.model_manager.getActiveModel() !== undefined) {
if (this.controller.model_manager?.getActiveModel() !== undefined) {
logger.trace("Sending monitor json settings:\n{json}", {
json: this.controller.model_manager.getActiveModel().getJsonSettings(),
});
Expand Down
24 changes: 20 additions & 4 deletions src/components/SelectorSimulations/SelectorSimulations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import { getLogger } from '@logtape/logtape';
import { VU_CATALOG_SETTING_JSON, VU_MODEL_SETTING_JSON } from '../../api/core/Constants';
import visibility from '/src/svg_logos/visibility.svg';
const SelectorSimulations = () => {

Check failure on line 14 in src/components/SelectorSimulations/SelectorSimulations.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Aq0pnyHFRBIWnT&open=AZz7S4Aq0pnyHFRBIWnT&pullRequest=131
const { ws, isWsConnected, gama, simulationList } = useWebSocket();
const { ws, isWsConnected, gamaless, gama, simulationList } = useWebSocket();
const [loading, setLoading] = useState<boolean>(true);
const [connectionStatus, setConnectionStatus] = useState<string>('Waiting for connection ...');
const { t } = useTranslation();
Expand Down Expand Up @@ -138,8 +138,9 @@

};

// Loop which tries to connect to Gama
// Loop which tries to connect to Gama (skipped in GAMALESS mode)
useEffect(() => {
if (gamaless) return;
let interval: NodeJS.Timeout;
if (ws && !gama.connected) {
interval = setInterval(() => {
Expand All @@ -150,7 +151,7 @@
return () => {
clearInterval(interval);
};
}, [ws, gama.connected]);
}, [ws, gama.connected, gamaless]);

// Display connexion status
useEffect(() => {
Expand All @@ -167,49 +168,64 @@
<Header needsMiniNav />
{/* ↑ prop to specify whether it should use the small version of the navigation bar */}

{loading ? (
{gamaless ? (
<div className="flex flex-col items-center justify-center w-5/6 h-2/3 rounded-md" style={{ backgroundColor: "#A1D2FF" }}>
<div className="bg-yellow-100 border-4 border-yellow-500 rounded-xl px-8 py-6 text-center max-w-lg">
<h2 className="text-2xl font-bold text-yellow-700 mb-2">GAMALESS Mode</h2>
<p className="text-yellow-800">Simulation features are disabled. No GAMA server is connected.</p>
<p className="text-yellow-700 mt-2 text-sm">Headset management is still operational.</p>
</div>
<Link to={"../streamPlayerScreen"} className='bg-white rounded-lg mt-4' target='_blank'>
<Button bgColor='bg-purple-500'
text="VR screens"
icon={<img src={visibility} />}

Check warning on line 181 in src/components/SelectorSimulations/SelectorSimulations.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Aq0pnyHFRBIWnU&open=AZz7S4Aq0pnyHFRBIWnU&pullRequest=131
className='flex w-15'
></Button>
</Link>
</div>
) : loading ? (
<div className="text-center">
<div className="animate-pulse ease-linear rounded-full border-8 border-t-8 border-gray-200 h-24 w-24 mb-4 -z-50"></div>
<h2 className="text-gray-700">{t('loading')}</h2>
</div>
) : (
<div className="flex flex-col items-center justify-center w-5/6 h-2/3 rounded-md relative" style={{ "backgroundColor": "#A1D2FF" }}>
{
//? Shows the back button if in a nested folder
path.length >= 1 &&
<div
className={`shadow-lg rounded-xl flex flex-col items-center justify-center size-14 cursor-pointer bg-white absolute top-10 left-10`}
onClick={() => back()}
>
<img src={selectedSplashscreen ? selectedSplashscreen : "/images/simple_logo.png"} alt="background image" className='absolute z-0'/>
<img src={arrow_back} className='rounded-full bg-slate-700 size-8 z-10 opacity-70' />
</div>
}

{subProjectsList ? subProjectsList.length > 0 ? <h2 className='font-medium'>{t('select_subproject')}</h2> : <h2>{t('select_simulation')} </h2> : null}

{/* //TODO add translation for Thai language */}

<div className="flex items-center justify-between">
<div className="flex mt-5 mb-8" style={{ gap: '55px' }}>
<SimulationList list={subProjectsList} handleSimulation={handleSimulation} gama={gama} />
</div>
</div>
{/* Display the status, ask for the user to connect to Gama if still not */}
<div className='flex gap-2 mt-6'>
<span className={gama.connected ? 'text-green-500' : 'text-red-500'}>
{gama.connected ? '' : connectionStatus}
</span>
</div>
<Link to={"../streamPlayerScreen"} className='bg-white rounded-lg' target='_blank'>
<Button bgColor='bg-purple-500'
text="VR screens"
icon={<img src={visibility} />}
className='flex w-15'
></Button>
</Link>
</div>
)}

Check warning on line 228 in src/components/SelectorSimulations/SelectorSimulations.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S4Aq0pnyHFRBIWnV&open=AZz7S4Aq0pnyHFRBIWnV&pullRequest=131
<Footer />
</div>
);
Expand Down
12 changes: 9 additions & 3 deletions src/components/SimulationManager/SimulationManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface Player {
const SimulationManager = () => {

const logger = getLogger(["simulationManager", "SimulationManager"]);
const { ws, gama, playerList, selectedSimulation } = useWebSocket(); // `removePlayer` is now available
const { ws, gamaless, gama, playerList, selectedSimulation } = useWebSocket(); // `removePlayer` is now available
const navigate = useNavigate();
const [simulationStarted, setSimulationStarted] = useState(false);
const { t } = useTranslation();
Expand Down Expand Up @@ -88,10 +88,10 @@ const SimulationManager = () => {


useEffect(() => {
if (!selectedSimulation) {
if (!gamaless && !selectedSimulation) {
navigate('/');
}
}, [selectedSimulation, navigate]);
}, [gamaless, selectedSimulation, navigate]);

// Handler for removing players

Expand Down Expand Up @@ -149,6 +149,11 @@ const SimulationManager = () => {

{/* Buttons Simulations : Play Button, Pause Button, Stop Button */}

{gamaless ? (
<div className="mt-4 px-4 py-2 bg-yellow-100 border-2 border-yellow-400 rounded-lg text-yellow-800 text-sm text-center">
GAMALESS — simulation controls disabled
</div>
) : (
<>
<div>
{gama.experiment_state === 'NONE' || gama.experiment_state === 'NOTREADY' ? (
Expand Down Expand Up @@ -240,6 +245,7 @@ const SimulationManager = () => {

</div>
</>
)}



Expand Down
10 changes: 8 additions & 2 deletions src/components/WebSocketManager/WebSocketManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
interface WebSocketContextType {
ws: WebSocket | null;
isWsConnected: boolean;
gamaless: boolean;
gama: {
connected: boolean;
loading: 'hidden' | 'visible';
Expand All @@ -46,6 +47,7 @@
const WebSocketManager = ({ children }: WebSocketManagerProps) => {
const [ws, setWs] = useState<WebSocket | null>(null);
const [isWsConnected, setIsWsConnected] = useState<boolean>(false);
const [gamaless, setGamaless] = useState<boolean>(false);
const [gama, setGama] = useState({
connected: false,
loading: 'hidden' as 'hidden' | 'visible',
Expand Down Expand Up @@ -101,7 +103,11 @@
switch (data.type) {
// this case is launch too much time
case 'json_state':
setGama(data.gama);
const isGamaless = Object.keys(data.gama).length === 0;

Check warning on line 106 in src/components/WebSocketManager/WebSocketManager.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected lexical declaration in case block.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S38f0pnyHFRBIWnR&open=AZz7S38f0pnyHFRBIWnR&pullRequest=131
setGamaless(isGamaless);
if (!isGamaless) {
setGama(data.gama);
}
setPlayerList(data.player);
break;
//Sets the selected simulation for the websocketManager's context
Expand Down Expand Up @@ -137,7 +143,7 @@


return (
<WebSocketContext.Provider value={{ ws, isWsConnected, gama, playerList, simulationList, selectedSimulation, removePlayer }}>
<WebSocketContext.Provider value={{ ws, isWsConnected, gamaless, gama, playerList, simulationList, selectedSimulation, removePlayer }}>

Check warning on line 146 in src/components/WebSocketManager/WebSocketManager.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The object passed as the value prop to the Context provider changes every render. To fix this consider wrapping it in a useMemo hook.

See more on https://sonarcloud.io/project/issues?id=project-SIMPLE_simple.webplatform&issues=AZz7S38f0pnyHFRBIWnS&open=AZz7S38f0pnyHFRBIWnS&pullRequest=131
{children}
</WebSocketContext.Provider>
);
Expand Down
Loading