From a156d95589993f7a8fad12738af1899c880bdf5d Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Thu, 12 Feb 2026 22:24:33 +0700 Subject: [PATCH 1/5] chore: Properly store constants in Constants core file --- src/api/core/Constants.ts | 41 ++++++++++++++++++++++++++++++ src/api/simulation/ModelManager.ts | 16 ------------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/api/core/Constants.ts b/src/api/core/Constants.ts index 2102a79..5e3c500 100644 --- a/src/api/core/Constants.ts +++ b/src/api/core/Constants.ts @@ -53,6 +53,47 @@ export interface Simulation { selected_monitoring: string } +// Learning packages ============================================== + +/** + * Inteface to make manipulation of the json file easier + * these are incomplete and do not represent the full structure of the json file + * but contain what is necessary to parse them + */ +export interface VU_MODEL_SETTING_JSON { + type: "json_settings"; + name: string; + splashscreen: string; + model_file_path: string; + experiment_name: string; + minimal_players: string; + maximal_players: string; + selected_monitoring?: 'gama_screen'; +} + +export interface VU_CATALOG_SETTING_JSON { + type: "catalog"; + name: string; + splashscreen?: string; + entries: VU_MODEL_SETTING_JSON[] | VU_CATALOG_SETTING_JSON[]; +} + +// Simplier version used to send useful information only to Monitor clients +export interface MIN_VU_MODEL_SETTING_JSON { + type: string; + name: string; + splashscreen: string; + model_index: number; +} + +// Simplier version used to send useful information only to Monitor clients +export interface MIN_VU_CATALOG_SETTING_JSON { + type: string; + name: string; + splashscreen?: string; + entries: MIN_VU_MODEL_SETTING_JSON[]|MIN_VU_CATALOG_SETTING_JSON; +} + // Internal message exchange ============================================== export interface PlayerState { diff --git a/src/api/simulation/ModelManager.ts b/src/api/simulation/ModelManager.ts index 7cd0a12..7562f0b 100644 --- a/src/api/simulation/ModelManager.ts +++ b/src/api/simulation/ModelManager.ts @@ -6,22 +6,6 @@ import Model from './Model.ts'; import Controller from "../core/Controller.ts"; import {getLogger} from "@logtape/logtape"; -/** - * Inteface to make manipulation of the json file easier - * these are incomplete and do not represent the full structure of the json file - * but contain what is necessary to parse them - */ -interface Settings { - type: "json_settings"; - model_file_path: string; - name: string; -} -interface Catalog { - type: "catalog"; - name: string; - entries: Settings[] | Catalog[]; -} - // Override the log function const logger= getLogger(["simulation", "ModelManager"]); From 344ee5444d657e4944e71639796e38d2d6bcd861 Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Thu, 12 Feb 2026 22:25:47 +0700 Subject: [PATCH 2/5] fix: Switch back `sendMessageByWs` taking `any` message At the moment, nowhere using this function is giving it a string - Creates a lot of bug --- src/api/monitoring/MonitorServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/monitoring/MonitorServer.ts b/src/api/monitoring/MonitorServer.ts index cd0e0ce..7be917f 100644 --- a/src/api/monitoring/MonitorServer.ts +++ b/src/api/monitoring/MonitorServer.ts @@ -245,7 +245,7 @@ export class MonitorServer { * @param clientWsId (optional) WS to send the message to * @return void */ - sendMessageByWs(message: string, clientWsId?: any): void { + sendMessageByWs(message: any, clientWsId?: any): void { if (this.wsClients !== undefined) { this.wsClients.forEach((client) => { if (clientWsId == undefined || clientWsId == client) { From c99be8e16dfccbffc2c3dbcdf8c0664471c3f7be Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Thu, 12 Feb 2026 22:29:49 +0700 Subject: [PATCH 3/5] fix: Model class use absolute gaml file path - Slim refactoring of the Model class - Actually make gaml file path correction/verification used - Was stored in a private variable, but never used when getting json infos - Clean-up, log and document each parts --- src/api/simulation/Model.ts | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/api/simulation/Model.ts b/src/api/simulation/Model.ts index 7dbb78c..aca63b7 100644 --- a/src/api/simulation/Model.ts +++ b/src/api/simulation/Model.ts @@ -1,30 +1,30 @@ import path from 'path'; -import { JsonSettings } from "../core/Constants.ts"; +import {VU_MODEL_SETTING_JSON} from "../core/Constants.ts"; import { Logger, getLogger } from '@logtape/logtape'; +import fs from "fs"; -const logger: Logger = getLogger(["api", "simulation"]); +const logger: Logger = getLogger(["simulation", "Model"]); class Model { - readonly #jsonSettings: SETTINGS_FILE_JSON; - readonly #modelFilePath: string; + readonly #jsonSettings: VU_MODEL_SETTING_JSON; /** * Creates a Model object based on VU founded by the ModelManager * @param {string} settingsPath - Path to the settings file - * @param {string} jsonSettings - Json content _Stringyfied_ of the settings file + * @param {VU_MODEL_SETTING_JSON} jsonSettings - Json content _Stringyfied_ of the settings file */ - constructor(settingsPath: string, jsonSettings: string) { - this.#jsonSettings = JSON.parse(jsonSettings); + constructor(settingsPath: string, jsonSettings: VU_MODEL_SETTING_JSON) { + this.#jsonSettings = jsonSettings; - logger.debug("Parsing {json}", { json: this.#jsonSettings }); + logger.debug("Parsing new model {json}", { json: this.#jsonSettings.name }); //if the path is relative, we rebuild it using the path of the settings.json it is found in - const modelFilePath = this.#jsonSettings.model_file_path; - if (path.isAbsolute(modelFilePath)) { - this.#modelFilePath = modelFilePath; - } else { - this.#modelFilePath = path.join(path.dirname(settingsPath), this.#jsonSettings.model_file_path); - } + const absoluteModelFilePath = path.isAbsolute(this.#jsonSettings.model_file_path) ? this.#jsonSettings.model_file_path : path.join(path.dirname(settingsPath), this.#jsonSettings.model_file_path); + + if (!fs.existsSync(absoluteModelFilePath)) + logger.error(`GAML model for VU ${this.#jsonSettings.name} can't be found at ${absoluteModelFilePath}. Please check the path in the settings.json file.`) + + this.#jsonSettings.model_file_path = absoluteModelFilePath; } // Getters @@ -34,7 +34,7 @@ class Model { * @returns {string} - The path to the model file */ getModelFilePath(): string { - return this.#modelFilePath; + return this.#jsonSettings.model_file_path; } /** @@ -47,9 +47,9 @@ class Model { /** * Gets the JSON settings - * @returns {SETTINGS_FILE_JSON} - The JSON settings + * @returns {VU_MODEL_SETTING_JSON} - The JSON settings */ - getJsonSettings(): SETTINGS_FILE_JSON { + getJsonSettings(): VU_MODEL_SETTING_JSON { return this.#jsonSettings; } @@ -63,12 +63,12 @@ class Model { return { type: "json_simulation_list", jsonSettings: this.#jsonSettings, - modelFilePath: this.#modelFilePath + modelFilePath: this.#jsonSettings.model_file_path }; } toString() { - return this.#modelFilePath; + return this.#jsonSettings.model_file_path; } From 8a63a365c9f70a4a16b7d47034619327081393a6 Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Thu, 12 Feb 2026 22:41:26 +0700 Subject: [PATCH 4/5] fix: Refactoring of the ModelManager - Was trash, fully rework to be : 1. Readable code - Comments - Logically ordered - Semi-column 2. Understandable logs (proper sentences, set at proper level) 3. Rely on other file verification and work 4. Use the Model objects, as well as properly typing elements 5. Logs and code clean-up - Probably not finished, but already removed a lot - Finished to replace `console.log` by logger - Stop stringify everything, especially if it's to parse it back right after 6. Refactoring class to avoid code smells and remove dead code 7. Kept the outside logic (requesting JSON stringified with nested catalog) - This commit should have been splitted, but since I changed everything, this comment is the log explaining it --- src/api/simulation/ModelManager.ts | 200 +++++++++++++++++------------ 1 file changed, 117 insertions(+), 83 deletions(-) diff --git a/src/api/simulation/ModelManager.ts b/src/api/simulation/ModelManager.ts index 7562f0b..5077ff6 100644 --- a/src/api/simulation/ModelManager.ts +++ b/src/api/simulation/ModelManager.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; import { isAbsolute } from 'path'; -import { ANSI_COLORS as color } from '../core/Constants.ts'; +import { VU_MODEL_SETTING_JSON, VU_CATALOG_SETTING_JSON, MIN_VU_MODEL_SETTING_JSON, MIN_VU_CATALOG_SETTING_JSON } from '../core/Constants.ts'; import Model from './Model.ts'; import Controller from "../core/Controller.ts"; import {getLogger} from "@logtape/logtape"; @@ -13,7 +13,7 @@ class ModelManager { controller: Controller; models: Model[]; activeModel: Model | undefined; - jsonList: string[] = []; // List of all the models as written in each settings.json file, useful to keep the structure of subprojects + monitorNestedModels: any[] = []; /** * Creates the model manager @@ -21,21 +21,18 @@ class ModelManager { */ constructor(controller: Controller) { this.controller = controller; - this.models = this.#initModelsList(); + this.models = [] + this.#initModelsList(); } - - /** * Initialize the models list by scanning the learning packages * checks for the type of settings * if it is a single model, it is directly added to the modelList - * if it is a catalog, it will parse it and it's sub objects + * if it is a catalog, it will parse it, and it's sub objects * if it is an array, it will parse the array and create a model for each object, and read catalogs if any - * @returns {Model[]} - List of models */ - #initModelsList(): Model[] { - let modelList: Model[] = []; + #initModelsList(): void { const directoriesWithProjects: string[] = []; directoriesWithProjects.push(isAbsolute(process.env.LEARNING_PACKAGE_PATH!) ? process.env.LEARNING_PACKAGE_PATH! : path.join(process.cwd(), process.env.LEARNING_PACKAGE_PATH!)); @@ -47,7 +44,7 @@ class ModelManager { directoriesWithProjects.forEach((packageRootDir) => { const packageFolder: string[] = ["."].concat(fs.readdirSync(packageRootDir)); - // Browse in learning package folder to find available packages + // Browse in the learning package folder to find available packages packageFolder.forEach((file) => { const folderPath = path.join(packageRootDir, file); @@ -57,59 +54,130 @@ class ModelManager { if (fs.existsSync(settingsPath)) { logger.debug(`Append new package to ModelManager: ${folderPath}`); - const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); - this.jsonList.push(settings); // add the settings file to the list of json files - - logger.trace(`{jsonList}`, {jsonList: this.jsonList}); - logger.debug(`Found settings file in ${folderPath}`); - - if (settings.type === "catalog") { //it's a catalog, i.e it contains a subset of catalogs and models - logger.debug(`Found catalog in ${folderPath}`); - this.parseCatalog(settings, modelList, settingsPath); - } else if (Array.isArray(settings)) { - logger.debug(`Found array in ${color.cyan}${folderPath}${color.reset},iterating through`); - - for (const item of settings) { - logger.debug(`\titem: ${item.type}`); - this.parseCatalog(item, modelList, settingsPath); - } - - } else if (settings.type === "json_settings") { - logger.debug("{settings}", {settings}); - - modelList = modelList.concat( - new Model(settingsPath, JSON.stringify(settings)) - ); + const settings: VU_MODEL_SETTING_JSON|VU_CATALOG_SETTING_JSON = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + + switch (settings.type) { + //it's a catalog, i.e it contains a subset of catalogs and models + case "catalog": + logger.debug(`Found catalog in ${folderPath}`); + + // Save final recursion in variable + this.monitorNestedModels.push({ + "type": "catalog", + "name": settings.name, + "entries": this.parseCatalog(settings, settingsPath), + ...(settings.splashscreen !== undefined && { "splashscreen": settings.splashscreen }) + }); + + break; + + case "json_settings": + logger.debug(`Found single game settings in ${folderPath}`); + + // Directly save new models not in catalog + this.monitorNestedModels.push( + this.saveNewModel(settingsPath, settings) + ); + + break; + + default: + // TODO: Remove ? + if (Array.isArray(settings)) { + logger.debug(`Found array in ${folderPath},iterating through`); + // @ts-expect-error I don't know what this code is supposed to catch + // Will probably remove it soon... + for (const item of settings) { + logger.debug(`\titem: ${item.type}`); + this.parseCatalog(item, settingsPath); + } + } else { + logger.error(`Can't identify setting's type from ${settingsPath}`); + logger.error(`{settings}`, {settings}); + } } - logger.trace(modelList.toString()); } else { logger.warn(`Couldn't find settings file in folder ${folderPath}`); } } }); - }) + }); + } + + /** + * recursively parse a Json catalog passed in parameter + * adds the list of model to a list provided in parameter. + * declared as a separate function to be used recursively + * @param catalog a json catalog object containing catalogs or settings + * @param settingsPath the path of the current settings being parsed, used for creating models in the constructor + */ + parseCatalog(catalog: VU_CATALOG_SETTING_JSON, settingsPath: string) { + const catalogName: string = catalog.name; + + logger.debug(`Start parsing catalog: ${catalogName}`); + logger.trace(`{catalog}`,{catalog}); + + let cleanedEntry: MIN_VU_MODEL_SETTING_JSON[] = []; + let cleanedCatalog: MIN_VU_CATALOG_SETTING_JSON[] = []; + + for (const entry of catalog.entries) { + logger.info(`[${catalogName}] Parsing entry found: {entry}`, {entry: entry.name}); + switch (entry.type) { + case "catalog": + logger.debug(`[${catalogName}] Found catalog, parsing it recursively`) + cleanedCatalog.push({ + "type": "catalog", + "name": entry.name, + // @ts-expect-error Can't properly set what are entries since it can be a list of any MIN_VU + "entries": this.parseCatalog(entry, settingsPath), + ...(entry.splashscreen !== undefined && { "splashscreen": entry.splashscreen }) + }) + break; + // @ts-expect-error If unknown, trying to parse it as a legacy entry... + default: + logger.warn(`[${catalogName}] Unknown type for this entry: {entry}`, {entry}); + logger.warn(`[${catalogName}] Trying to parse it as a legacy entry...`); + case "json_settings": + try { + logger.debug(`[${catalogName}] Parsing json_settings entry`); + + cleanedEntry.push( + this.saveNewModel(settingsPath, entry) + ); + } catch (e) { + logger.error(`[${catalogName}] Couldn't parse catalog entry: {entry}, error: {e}`, {entry, e}); + } + } + } - return modelList; + return [...cleanedEntry, ...cleanedCatalog]; } // ------------------- - setActiveModelByIndex(index: number) { - this.activeModel = this.models[index]; + saveNewModel(settingsPath: string, settings: VU_MODEL_SETTING_JSON): MIN_VU_MODEL_SETTING_JSON { + logger.debug(`Saving new model: ${settings.name}`); + logger.trace(`{settings}`,{settings}); + + // TODO Check that settings if of type VU_MODEL_SETTING_JSON + const newModel: Model = new Model(settingsPath, settings); + const cleanedJson: VU_MODEL_SETTING_JSON = newModel.getJsonSettings(); + + return { + type: "json_settings", + name: cleanedJson.name, + splashscreen: cleanedJson.splashscreen, + // -1 as `push` return the new array size, not the index + // Add full Model object for GAMA + model_index: (this.models.push(newModel) - 1) + } } // ------------------- - /** - *returns the model with the model_file_path specified - * @filepath the path of the model, specified in the settings.json of the model - * @returns {Model} sets the active model to the model found - */ - setActiveModelByFilePath(filePath: string) { - logger.debug("trying to set active model using file path: {filepath}",{filepath: filePath}) - const modelFound = this.models.find(model => model.getJsonSettings().model_file_path === filePath); - logger.debug("found model: {model}",{model: modelFound?.toString()}) - return this.activeModel = modelFound + setActiveModelByIndex(index: number) { + logger.debug(`Setting active model to index ${index}`); + this.activeModel = this.models[index]; } getActiveModel() { @@ -118,20 +186,12 @@ class ModelManager { // ------------------- - /** - * Converts the model list to JSON format - * @returns {string} - JSON string of models - */ - getModelListJSON(): string { - const jsonSafeModels = this.models.map(model => model.toJSON()); - return JSON.stringify(jsonSafeModels); - } /** * used to send the models structure to the front end for proper display * @returns {string} - JSON string of the list of models as written in each settings.json file */ getCatalogListJSON(): string { - return JSON.stringify(this.jsonList); + return JSON.stringify(this.monitorNestedModels); } /** @@ -141,32 +201,6 @@ class ModelManager { getModelList(): Model[] { return this.models; } - /** - * recursively parse a Json catalog passed in parameter - * adds the list of model to a list provided in parameter. - * declared as a separate function to be used recursively - * @param catalog a json catalog object containing catalogs or settings - * @param list the list of models containing all parsed models throughout all the settings files - * @param settingsPath the path of the current settings being parsed, used for creating models in the constructor - */ - parseCatalog(catalog: Catalog, list: Model[], settingsPath: string) { - for (const entry of catalog.entries) { - if ('type' in entry) { - logger.info(`entry found: ${entry}`) - if (entry.type === "json_settings") { - logger.info(`${entry.name}`) - - const model = new Model(settingsPath, JSON.stringify(entry)); - logger.debug(model.toString()); - list.push(model); - } else if (entry.type === "catalog") { - this.parseCatalog(entry, list, settingsPath); - } - } - } - } } - - export default ModelManager; From dde36d40f1d9a41e1eb812b0aa75a4554d8d84c7 Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Thu, 12 Feb 2026 22:43:20 +0700 Subject: [PATCH 5/5] fix: Set active model from monitor by index Revert to old method which is way more reliable (since the index is managed by the backend) using the newly added `model_index` attribute in each VU_MODEL_SETTING_JSON (generated and managed by the backend, so no possible out of bound or anything) --- src/api/monitoring/MonitorServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/monitoring/MonitorServer.ts b/src/api/monitoring/MonitorServer.ts index 7be917f..50bd55a 100644 --- a/src/api/monitoring/MonitorServer.ts +++ b/src/api/monitoring/MonitorServer.ts @@ -144,7 +144,7 @@ export class MonitorServer { logger.trace("Sending simulation"); const simulationFromStream = JSON.parse(Buffer.from(message).toString()); - this.controller.model_manager.setActiveModelByFilePath(simulationFromStream.simulation.model_file_path); + 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({