From ba4934ccce66c32d3857efd13ce57910b332dc86 Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Tue, 17 Mar 2026 17:13:39 +0700 Subject: [PATCH] chore: Better handle Gamaless Current implementation wasn't handling ModelManager in the GAMALESS mode, leading to error where you can't run the application if the platform doesn't find any VU in its configuration... - Better handle GAMALESS mode in backend, with debug verbose and big warning message - Catch gameless behavior in frontend with front variable - Add big display of this GAMALESS mode to prevent any confusion Fix #130 --- src/api/core/Controller.ts | 43 +++++++++------ src/api/monitoring/MonitorServer.ts | 54 ++++++++++++++----- .../SelectorSimulations.tsx | 24 +++++++-- .../SimulationManager/SimulationManager.tsx | 12 +++-- .../WebSocketManager/WebSocketManager.tsx | 10 +++- 5 files changed, 106 insertions(+), 37 deletions(-) diff --git a/src/api/core/Controller.ts b/src/api/core/Controller.ts index 8725af1..00f9d52 100644 --- a/src/api/core/Controller.ts +++ b/src/api/core/Controller.ts @@ -11,7 +11,7 @@ import { getLogger } from "@logtape/logtape"; 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; @@ -22,14 +22,21 @@ export class Controller { 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); @@ -58,10 +65,10 @@ export class Controller { 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); } @@ -77,7 +84,12 @@ export class Controller { */ getSimulationInformations(): string { - return this.model_manager.getCatalogListJSON(); + if (!ENV_GAMALESS) { + return this.model_manager!.getCatalogListJSON(); + } else { + logger.debug("[getSimulationInformations] model_manager is not available in GAMALESS mode"); + return JSON.stringify([]); + } } /* @@ -110,7 +122,7 @@ export class Controller { 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 { @@ -119,6 +131,7 @@ export class Controller { // Remove from GAMA if (!ENV_GAMALESS) this.gama_connector!.removeInGamePlayer(id_player); + // Remove from connected list this.player_manager.removePlayer(id_player); @@ -135,14 +148,14 @@ export class Controller { 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() { @@ -158,7 +171,7 @@ export class Controller { 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() { @@ -168,21 +181,21 @@ export class Controller { 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..."); } } diff --git a/src/api/monitoring/MonitorServer.ts b/src/api/monitoring/MonitorServer.ts index 9308caa..7613a39 100644 --- a/src/api/monitoring/MonitorServer.ts +++ b/src/api/monitoring/MonitorServer.ts @@ -4,6 +4,7 @@ import { Controller } from "../core/Controller.ts"; 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"]); @@ -52,8 +53,7 @@ export class MonitorServer { 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": @@ -108,6 +108,10 @@ export class MonitorServer { 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, @@ -116,16 +120,20 @@ export class MonitorServer { 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({ @@ -141,10 +149,14 @@ export class MonitorServer { 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(); logger.debug("Selected simulation sent to gama: {json}", { json: selectedSimulation.getJsonSettings() }); this.sendMessageByWs({ type: "get_simulation_by_index", @@ -152,6 +164,13 @@ export class MonitorServer { }, 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}", @@ -163,9 +182,7 @@ export class MonitorServer { 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) { @@ -207,8 +224,19 @@ export class MonitorServer { * 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 = { @@ -228,7 +256,7 @@ export class MonitorServer { * 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(), }); diff --git a/src/components/SelectorSimulations/SelectorSimulations.tsx b/src/components/SelectorSimulations/SelectorSimulations.tsx index 7801ad9..e2be1ad 100644 --- a/src/components/SelectorSimulations/SelectorSimulations.tsx +++ b/src/components/SelectorSimulations/SelectorSimulations.tsx @@ -12,7 +12,7 @@ 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 = () => { - const { ws, isWsConnected, gama, simulationList } = useWebSocket(); + const { ws, isWsConnected, gamaless, gama, simulationList } = useWebSocket(); const [loading, setLoading] = useState(true); const [connectionStatus, setConnectionStatus] = useState('Waiting for connection ...'); const { t } = useTranslation(); @@ -138,8 +138,9 @@ const SelectorSimulations = () => { }; - // 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(() => { @@ -150,7 +151,7 @@ const SelectorSimulations = () => { return () => { clearInterval(interval); }; - }, [ws, gama.connected]); + }, [ws, gama.connected, gamaless]); // Display connexion status useEffect(() => { @@ -167,7 +168,22 @@ const SelectorSimulations = () => {
{/* ↑ prop to specify whether it should use the small version of the navigation bar */} - {loading ? ( + {gamaless ? ( +
+
+

GAMALESS Mode

+

Simulation features are disabled. No GAMA server is connected.

+

Headset management is still operational.

+
+ + + +
+ ) : loading ? (

{t('loading')}

diff --git a/src/components/SimulationManager/SimulationManager.tsx b/src/components/SimulationManager/SimulationManager.tsx index d6e2936..ecc306c 100644 --- a/src/components/SimulationManager/SimulationManager.tsx +++ b/src/components/SimulationManager/SimulationManager.tsx @@ -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(); @@ -88,10 +88,10 @@ const SimulationManager = () => { useEffect(() => { - if (!selectedSimulation) { + if (!gamaless && !selectedSimulation) { navigate('/'); } - }, [selectedSimulation, navigate]); + }, [gamaless, selectedSimulation, navigate]); // Handler for removing players @@ -149,6 +149,11 @@ const SimulationManager = () => { {/* Buttons Simulations : Play Button, Pause Button, Stop Button */} + {gamaless ? ( +
+ GAMALESS — simulation controls disabled +
+ ) : ( <>
{gama.experiment_state === 'NONE' || gama.experiment_state === 'NOTREADY' ? ( @@ -240,6 +245,7 @@ const SimulationManager = () => {
+ )} diff --git a/src/components/WebSocketManager/WebSocketManager.tsx b/src/components/WebSocketManager/WebSocketManager.tsx index b62d5e1..9f179a2 100644 --- a/src/components/WebSocketManager/WebSocketManager.tsx +++ b/src/components/WebSocketManager/WebSocketManager.tsx @@ -20,6 +20,7 @@ interface PlayerList { interface WebSocketContextType { ws: WebSocket | null; isWsConnected: boolean; + gamaless: boolean; gama: { connected: boolean; loading: 'hidden' | 'visible'; @@ -46,6 +47,7 @@ interface WebSocketManagerProps { const WebSocketManager = ({ children }: WebSocketManagerProps) => { const [ws, setWs] = useState(null); const [isWsConnected, setIsWsConnected] = useState(false); + const [gamaless, setGamaless] = useState(false); const [gama, setGama] = useState({ connected: false, loading: 'hidden' as 'hidden' | 'visible', @@ -101,7 +103,11 @@ const WebSocketManager = ({ children }: WebSocketManagerProps) => { switch (data.type) { // this case is launch too much time case 'json_state': - setGama(data.gama); + const isGamaless = Object.keys(data.gama).length === 0; + setGamaless(isGamaless); + if (!isGamaless) { + setGama(data.gama); + } setPlayerList(data.player); break; //Sets the selected simulation for the websocketManager's context @@ -137,7 +143,7 @@ const WebSocketManager = ({ children }: WebSocketManagerProps) => { return ( - + {children} );