@gama-platform/gama-client 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gama_client.cjs +560 -0
- package/dist/gama_client.d.cts +166 -0
- package/dist/gama_client.d.ts +166 -0
- package/dist/gama_client.js +532 -0
- package/package.json +41 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __export = (target, all) => {
|
|
26
|
+
for (var name in all)
|
|
27
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
28
|
+
};
|
|
29
|
+
var __copyProps = (to, from, except, desc) => {
|
|
30
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
31
|
+
for (let key of __getOwnPropNames(from))
|
|
32
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
33
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
38
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
39
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
40
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
41
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
42
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
43
|
+
mod
|
|
44
|
+
));
|
|
45
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
+
|
|
47
|
+
// src/gama_client.ts
|
|
48
|
+
var gama_client_exports = {};
|
|
49
|
+
__export(gama_client_exports, {
|
|
50
|
+
default: () => GamaClient
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(gama_client_exports);
|
|
53
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
54
|
+
|
|
55
|
+
// src/constants.ts
|
|
56
|
+
var GAMA_ERROR_MESSAGES = [
|
|
57
|
+
"SimulationStatusError",
|
|
58
|
+
"SimulationErrorDialog",
|
|
59
|
+
"SimulationError",
|
|
60
|
+
"RuntimeError",
|
|
61
|
+
"GamaServerError",
|
|
62
|
+
"MalformedRequest",
|
|
63
|
+
"UnableToExecuteRequest"
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// src/gama_client.ts
|
|
67
|
+
var import_logtape = require("@logtape/logtape");
|
|
68
|
+
var logger = (0, import_logtape.getLogger)(["GAMA-library", "GAMA-client"]);
|
|
69
|
+
var GamaClient = class {
|
|
70
|
+
//websocket of the client. needs to be initialized by using the asynchronous connectGama() to be used
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param port port of gama server you want to reach
|
|
74
|
+
* @param host host of the gama server you want to reach
|
|
75
|
+
*/
|
|
76
|
+
constructor(port, host) {
|
|
77
|
+
// json object detailing the state of the gama server, including: if connected, experiment details, errors from the server, and loading status
|
|
78
|
+
this.port = 1e3;
|
|
79
|
+
//default port number pointing to the gama server. can be redefined when using the constructor
|
|
80
|
+
this.host = "localhost";
|
|
81
|
+
this.jsonGamaState = {
|
|
82
|
+
connected: false,
|
|
83
|
+
model_path: "",
|
|
84
|
+
experiment_state: "NONE",
|
|
85
|
+
loading: false,
|
|
86
|
+
content_error: "",
|
|
87
|
+
experiment_id: "",
|
|
88
|
+
experiment_name: ""
|
|
89
|
+
};
|
|
90
|
+
this.port = port || 1e3;
|
|
91
|
+
this.host = host || "localhost";
|
|
92
|
+
}
|
|
93
|
+
//? GETTERS
|
|
94
|
+
isConnected() {
|
|
95
|
+
return this.jsonGamaState.connected;
|
|
96
|
+
}
|
|
97
|
+
getExperimentState() {
|
|
98
|
+
return this.jsonGamaState.experiment_state;
|
|
99
|
+
}
|
|
100
|
+
isLoading() {
|
|
101
|
+
return this.jsonGamaState.loading;
|
|
102
|
+
}
|
|
103
|
+
getContentError() {
|
|
104
|
+
return this.jsonGamaState.content_error;
|
|
105
|
+
}
|
|
106
|
+
getExperimentId() {
|
|
107
|
+
return this.jsonGamaState.experiment_id;
|
|
108
|
+
}
|
|
109
|
+
getModelPath() {
|
|
110
|
+
return this.jsonGamaState.model_path;
|
|
111
|
+
}
|
|
112
|
+
getExperimentName() {
|
|
113
|
+
return this.jsonGamaState.experiment_name;
|
|
114
|
+
}
|
|
115
|
+
getReadyState() {
|
|
116
|
+
return this.gama_socket.readyState;
|
|
117
|
+
}
|
|
118
|
+
getPort() {
|
|
119
|
+
return this.port;
|
|
120
|
+
}
|
|
121
|
+
getHost() {
|
|
122
|
+
return this.host;
|
|
123
|
+
}
|
|
124
|
+
getSocket() {
|
|
125
|
+
return this.gama_socket;
|
|
126
|
+
}
|
|
127
|
+
//? SETTERS --------------------------------------------------------------------------------------------------------------------------------------
|
|
128
|
+
setConnected(connected) {
|
|
129
|
+
this.jsonGamaState.connected = connected;
|
|
130
|
+
}
|
|
131
|
+
setExperimentState(state) {
|
|
132
|
+
this.jsonGamaState.experiment_state = state;
|
|
133
|
+
}
|
|
134
|
+
setLoading(loading) {
|
|
135
|
+
this.jsonGamaState.loading = loading;
|
|
136
|
+
}
|
|
137
|
+
setContentError(content_error) {
|
|
138
|
+
this.jsonGamaState.content_error = content_error;
|
|
139
|
+
}
|
|
140
|
+
setExperimentId(experiment_id) {
|
|
141
|
+
this.jsonGamaState.experiment_id = experiment_id;
|
|
142
|
+
}
|
|
143
|
+
setExperimentName(experiment_name) {
|
|
144
|
+
this.jsonGamaState.experiment_name = experiment_name;
|
|
145
|
+
}
|
|
146
|
+
setModelPath(model_path) {
|
|
147
|
+
this.jsonGamaState.model_path = model_path;
|
|
148
|
+
}
|
|
149
|
+
//? INTERNAL UTILITIES ---------------------------------------------------------------------------------------------------------------------------
|
|
150
|
+
/**
|
|
151
|
+
* internal function to avoid unecessary boilerplate code,
|
|
152
|
+
* checks if gamasocket exists, and if it's ready to accept a new message
|
|
153
|
+
*/
|
|
154
|
+
socketCheck() {
|
|
155
|
+
if (!this.gama_socket) {
|
|
156
|
+
throw new Error("No socket connected to GAMA Server found");
|
|
157
|
+
} else if (!this.jsonGamaState.connected) {
|
|
158
|
+
throw new Error("Gama is not connected");
|
|
159
|
+
} else if (!(this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING)) {
|
|
160
|
+
throw new Error("socket not in the OPEN state");
|
|
161
|
+
} else {
|
|
162
|
+
logger.trace("Websocket is connected and open");
|
|
163
|
+
this.setConnected(true);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* internal function that contains a simple try catch and stringifies a json payload to send it to the websocket
|
|
168
|
+
* @param payload json payload to be sent
|
|
169
|
+
*/
|
|
170
|
+
sendPayload(payload) {
|
|
171
|
+
try {
|
|
172
|
+
this.gama_socket.send(JSON.stringify(payload));
|
|
173
|
+
logger.debug("sent message to websocket:{payload}", { payload });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(`couldn't send the message to the websocket:${error}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* internal function that returns the string of an experiment to run
|
|
180
|
+
* it represents the last used experiment or the new one if any specified
|
|
181
|
+
* @param new_exp_id id of the experiment passed by the user in parameter. used by default, sets the current experience to itself
|
|
182
|
+
* @returns the string of the Id of the last used experiment. Used if no new_exp_id is given
|
|
183
|
+
*/
|
|
184
|
+
getId(new_exp_id) {
|
|
185
|
+
if (new_exp_id) {
|
|
186
|
+
this.setExperimentId(new_exp_id);
|
|
187
|
+
return new_exp_id;
|
|
188
|
+
} else {
|
|
189
|
+
if (this.getExperimentId() === "") throw new Error("no current experiment to be called");
|
|
190
|
+
return this.getExperimentId();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* async function that closes the websocket connection, and runs the callback function passed in parameter if any.
|
|
195
|
+
* When called, creates a promise that either rejects after 15 seconds to avoid timeout lockdowns,
|
|
196
|
+
* or resolves after the close internalListener fires.
|
|
197
|
+
* @param optional callback Function to be called after the websocket's connection is closed
|
|
198
|
+
*/
|
|
199
|
+
async closeConnection(callback) {
|
|
200
|
+
if (!this.gama_socket || this.getReadyState() === import_ws.default.CLOSED) {
|
|
201
|
+
logger.warn("Websocket already closed, running the callback function");
|
|
202
|
+
if (callback) callback();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING) {
|
|
206
|
+
await new Promise((resolve, reject) => {
|
|
207
|
+
const timer = setTimeout(() => {
|
|
208
|
+
this.gama_socket.removeEventListener("close", internalListener);
|
|
209
|
+
reject(new Error("Websocket timed out"));
|
|
210
|
+
}, 15e3);
|
|
211
|
+
const internalListener = () => {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
this.gama_socket.removeEventListener("close", internalListener);
|
|
214
|
+
resolve();
|
|
215
|
+
};
|
|
216
|
+
this.gama_socket.addEventListener("close", internalListener);
|
|
217
|
+
this.gama_socket.close();
|
|
218
|
+
});
|
|
219
|
+
if (callback) callback();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Connects the websocket client with gama server and manage the messages received
|
|
224
|
+
* this function is asynchronous, it needs to be called with await. This is because
|
|
225
|
+
* other functions need the websocket to be created and in the state "OPEN" to start
|
|
226
|
+
* sending messages, which is not done when the function has finished it's execution
|
|
227
|
+
* @returns WebSocket properly initialised at the end of the asynchronous execution
|
|
228
|
+
*/
|
|
229
|
+
async connectGama() {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
if (this.gama_socket && (this.getReadyState() === import_ws.default.OPEN || this.getReadyState() === import_ws.default.CONNECTING)) {
|
|
232
|
+
this.setConnected(true);
|
|
233
|
+
logger.info("Already connected or connecting. Skipping. status:{status}", { status: this.getReadyState() });
|
|
234
|
+
return resolve();
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
this.gama_socket = new import_ws.default(`ws://${this.host}:${this.port}`);
|
|
238
|
+
this.gama_socket.onopen = () => {
|
|
239
|
+
this.setConnected(true);
|
|
240
|
+
logger.info("created new connection to {host}:{port}", { host: this.host, port: this.port });
|
|
241
|
+
this.gama_socket.onclose = () => {
|
|
242
|
+
this.setConnected(false);
|
|
243
|
+
this.setExperimentState("NONE");
|
|
244
|
+
logger.info("successfully closed the websocket.");
|
|
245
|
+
};
|
|
246
|
+
const simulationStatus = (event) => {
|
|
247
|
+
const message = JSON.parse(event.data);
|
|
248
|
+
if (message.type === "SimulationStatus") {
|
|
249
|
+
this.setExperimentState(message.content);
|
|
250
|
+
this.setExperimentId(message.exp_id);
|
|
251
|
+
}
|
|
252
|
+
logger.info("JsonGamaState:{state}", { state: this.getExperimentName() });
|
|
253
|
+
};
|
|
254
|
+
this.gama_socket.addEventListener("message", simulationStatus);
|
|
255
|
+
return resolve();
|
|
256
|
+
};
|
|
257
|
+
this.gama_socket.onerror = (error) => {
|
|
258
|
+
this.setConnected(false);
|
|
259
|
+
if (error.error.code == "ECONNREFUSED") {
|
|
260
|
+
logger.trace(`full stack trace for Error CONNREFUSED {error}`, { error });
|
|
261
|
+
logger.error("The platform can't connect to GAMA at address {host}:{port}", { host: this.host, port: this.port });
|
|
262
|
+
reject(new Error(`Failed to connect to GAMA at ${this.host}:${this.port} with error code ECONNREFUSED`));
|
|
263
|
+
} else {
|
|
264
|
+
logger.error(`An error happened within the Gama Server WebSocket
|
|
265
|
+
{error}`, { error });
|
|
266
|
+
reject(new Error(`Failed to connect to GAMA at ${this.host}:${this.port}`));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
} catch (e) {
|
|
270
|
+
const err = e;
|
|
271
|
+
logger.error("Synchronous error when creating the websocket:{error}", { error: err.message });
|
|
272
|
+
reject(e);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* This function is used to watch on messages stream and look for a response to the command initiated.
|
|
278
|
+
* it resolves if the message received is of the same type specified in the parameter
|
|
279
|
+
* and throws an error if it's of any type specified in the GAMA_ERROR_MESSAGES specified in the constants file
|
|
280
|
+
* @returns returns a promise containing the response's message's content
|
|
281
|
+
*/
|
|
282
|
+
async success(successMessage) {
|
|
283
|
+
return new Promise((resolve, reject) => {
|
|
284
|
+
const onMessage = (event) => {
|
|
285
|
+
const message = JSON.parse(event.data);
|
|
286
|
+
const type = message.type;
|
|
287
|
+
if (type === successMessage) {
|
|
288
|
+
this.gama_socket.removeEventListener("message", onMessage);
|
|
289
|
+
resolve(message);
|
|
290
|
+
} else if (GAMA_ERROR_MESSAGES.includes(type)) {
|
|
291
|
+
this.gama_socket.removeEventListener("message", onMessage);
|
|
292
|
+
this.setContentError(message.content);
|
|
293
|
+
reject(`Couldn't execute command on the Gama Server. ${type}: ${JSON.stringify(message.content)}`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
this.gama_socket.addEventListener("message", onMessage);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* function used to check for a specific message on the websocket.
|
|
301
|
+
* returns a resolved boolean promise once the provided message is found
|
|
302
|
+
* @param messageType the basic type of the message you want to analyse
|
|
303
|
+
* @param field what part of the message to analyse
|
|
304
|
+
* @param expectedValue what you expect the field value to be
|
|
305
|
+
* @returns a resolved promise containing a boolean
|
|
306
|
+
*/
|
|
307
|
+
//Voir pour retourner le message au lieu de juste retourner un booléen ?
|
|
308
|
+
async listenFor(messageType, field, expectedValue) {
|
|
309
|
+
if (!this.gama_socket) {
|
|
310
|
+
throw new Error("couldn't find an active gama socket when creating a listener:");
|
|
311
|
+
}
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
const listener = (event) => {
|
|
314
|
+
const message = JSON.parse(event.data);
|
|
315
|
+
logger.debug("message: {message}", { message });
|
|
316
|
+
const type = message.type;
|
|
317
|
+
if (type === messageType && message[field] === expectedValue) {
|
|
318
|
+
clearTimeout(timer);
|
|
319
|
+
resolve(true);
|
|
320
|
+
this.gama_socket.removeEventListener("message", listener);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const timer = setTimeout(() => {
|
|
324
|
+
this.gama_socket.removeEventListener("message", listener);
|
|
325
|
+
reject(new Error("Websocket timed out"));
|
|
326
|
+
}, 15e3);
|
|
327
|
+
this.gama_socket.addEventListener("message", listener);
|
|
328
|
+
logger.debug("added an event listener to the gama_socket");
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async readyCheck() {
|
|
332
|
+
const isReady = new Promise(async (resolve, reject) => {
|
|
333
|
+
if (this.getExperimentState() === "PAUSED" || "RUNNING") {
|
|
334
|
+
resolve(true);
|
|
335
|
+
} else {
|
|
336
|
+
return await this.listenFor("SimulationStatus", "content", "PAUSED");
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
//? GAMA FUNCTIONS ---------------------------------------------------------------------------------------------------------------------------
|
|
341
|
+
/**
|
|
342
|
+
* loads and launches an experiment using the absolute path of it's model and
|
|
343
|
+
* the identifier of the experiment. Resolves when the server answers with a SimulationStatus of type "PAUSED"
|
|
344
|
+
* @param model_path absolute path pointing to the model cointaining the experiment
|
|
345
|
+
* @param experiment id of the experiment to load
|
|
346
|
+
*/
|
|
347
|
+
async loadExperiment(model_path, experiment) {
|
|
348
|
+
this.socketCheck();
|
|
349
|
+
const payload = {
|
|
350
|
+
"type": "load",
|
|
351
|
+
"model": model_path,
|
|
352
|
+
"experiment": experiment
|
|
353
|
+
};
|
|
354
|
+
this.sendPayload(payload);
|
|
355
|
+
this.setModelPath(model_path);
|
|
356
|
+
this.setExperimentName(experiment);
|
|
357
|
+
this.setExperimentId(experiment);
|
|
358
|
+
await this.success("CommandExecutedSuccessfully");
|
|
359
|
+
return await this.readyCheck();
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Starts or resumes the experiment specified.
|
|
363
|
+
* @param exp_id string name of the experiment to pause or resume
|
|
364
|
+
* @param sync boolean used if an end condition was specified when loading a simulation. the command will return only the SimulationEnded message if true, and both a response and a SimulationEnded message if false
|
|
365
|
+
* when starting the experiment
|
|
366
|
+
*/
|
|
367
|
+
async play(exp_id, sync) {
|
|
368
|
+
this.socketCheck();
|
|
369
|
+
if (this.getExperimentState() === "NOTREADY") {
|
|
370
|
+
logger.warn("Simulation not ready yet, waiting for PAUSED simulationstatus");
|
|
371
|
+
await this.listenFor("Simulationstatus", "content", "PAUSED");
|
|
372
|
+
} else if (this.getExperimentState() === "PAUSED") {
|
|
373
|
+
const payload = __spreadValues({
|
|
374
|
+
"type": "play",
|
|
375
|
+
"exp_id": this.getId()
|
|
376
|
+
}, sync && { "sync": sync });
|
|
377
|
+
this.sendPayload(payload);
|
|
378
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
379
|
+
} else if (this.getExperimentState() === "RUNNING") {
|
|
380
|
+
logger.warn("cannot unpause a running simulation");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Pauses the experiment specified.
|
|
385
|
+
* @param exp_id optionnal parameter, will default to last used experiment
|
|
386
|
+
*/
|
|
387
|
+
async pause(exp_id) {
|
|
388
|
+
this.socketCheck();
|
|
389
|
+
const payload = {
|
|
390
|
+
"type": "pause",
|
|
391
|
+
"exp_id": this.getId(exp_id)
|
|
392
|
+
};
|
|
393
|
+
this.sendPayload(payload);
|
|
394
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
395
|
+
}
|
|
396
|
+
async reload(exp_id, parameters, until) {
|
|
397
|
+
this.socketCheck();
|
|
398
|
+
if (this.getExperimentState() === "NOTREADY") {
|
|
399
|
+
await this.listenFor("Simulationstatus", "content", "PAUSED");
|
|
400
|
+
}
|
|
401
|
+
const payload = __spreadValues(__spreadValues({
|
|
402
|
+
"type": "reload",
|
|
403
|
+
"exp_id": this.getId(exp_id)
|
|
404
|
+
}, parameters && { "parameters": parameters }), until && { "until": until });
|
|
405
|
+
this.sendPayload(payload);
|
|
406
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Sends a message to gama to order it to process a specified number of steps.
|
|
410
|
+
* Can only be used after the simulation has already been loaded
|
|
411
|
+
* @param exp_id the name of the experiment you want to step to. if not used, then the last used experiment Id will be used
|
|
412
|
+
* @param nb_step the number of steps you want to simulate. if none is specified, it will default to one step
|
|
413
|
+
*/
|
|
414
|
+
async step(nb_step, sync, exp_id) {
|
|
415
|
+
this.socketCheck();
|
|
416
|
+
if (this.getExperimentState() === "NOTREADY") {
|
|
417
|
+
logger.warn("The experiment is not yet ready:{state}", { state: this.getExperimentState() });
|
|
418
|
+
await this.listenFor("Simulationstatus", "content", "PAUSED");
|
|
419
|
+
}
|
|
420
|
+
const exp_id_payload = exp_id ? exp_id : this.getExperimentId();
|
|
421
|
+
if (exp_id_payload === "") throw new Error("no experience_id specified, and no experiment in the jsongamastate");
|
|
422
|
+
const payload = __spreadProps(__spreadValues({
|
|
423
|
+
"type": "step",
|
|
424
|
+
"exp_id": exp_id_payload
|
|
425
|
+
}, nb_step && { "nb_step": nb_step }), {
|
|
426
|
+
"sync": true
|
|
427
|
+
});
|
|
428
|
+
this.sendPayload(payload);
|
|
429
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* ! you must be sure that the type of the experiment is compatible (record) before using this
|
|
433
|
+
* This command is used to rollback a specific amount of steps.
|
|
434
|
+
* Can only be used if the experiment is of type "record"
|
|
435
|
+
* @param exp_id the name of the experiment you want to step to. if not used, then the last used experiment Id will be used
|
|
436
|
+
* @param nb_step the number of steps you want to simulate. if none is specified, it will default to one step
|
|
437
|
+
*/
|
|
438
|
+
async stepback(nb_step, exp_id) {
|
|
439
|
+
this.socketCheck();
|
|
440
|
+
const payload = __spreadProps(__spreadValues({
|
|
441
|
+
"type": "stepBack",
|
|
442
|
+
"exp_id": this.getId(exp_id)
|
|
443
|
+
}, nb_step && { "nb_step": nb_step }), {
|
|
444
|
+
"sync": true
|
|
445
|
+
});
|
|
446
|
+
this.sendPayload(payload);
|
|
447
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* stops the specified experiment or the current experiment if not specified
|
|
451
|
+
* @param exp_id optionnal parameter, leave empty to use the last used exp_id
|
|
452
|
+
*/
|
|
453
|
+
async stop(exp_id) {
|
|
454
|
+
this.socketCheck();
|
|
455
|
+
if (this.getExperimentState() !== "NONE") {
|
|
456
|
+
try {
|
|
457
|
+
const payload = {
|
|
458
|
+
"type": "stop",
|
|
459
|
+
"exp_id": this.getId(exp_id)
|
|
460
|
+
};
|
|
461
|
+
this.sendPayload(payload);
|
|
462
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
463
|
+
} catch (error) {
|
|
464
|
+
throw new Error(`couldn't stop the experiment:${error}`);
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
logger.warn(`couldn't stop the experiment, no experiment running`);
|
|
468
|
+
return new Promise((resolve) => {
|
|
469
|
+
resolve("couldn't stop experiment");
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* used to specify a fonction to be called on any message received by the websocket from the gama server
|
|
475
|
+
* you can only have one onMessage per client.
|
|
476
|
+
* @param callback the function you want to call upon receiving data through the javascript client
|
|
477
|
+
*/
|
|
478
|
+
onMessage(callback) {
|
|
479
|
+
if (!this.gama_socket) {
|
|
480
|
+
throw new Error("WebSocket is not initialized");
|
|
481
|
+
}
|
|
482
|
+
this.gama_socket.on("message", (data) => {
|
|
483
|
+
try {
|
|
484
|
+
const parsed = JSON.parse(data.toString());
|
|
485
|
+
callback(parsed);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
logger.warn("Received non-JSON message:{data}", { data });
|
|
488
|
+
callback(data);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* kills the gama server.
|
|
494
|
+
* used to exit the gama server, closes the websocket connection and closes the gama instance
|
|
495
|
+
*/
|
|
496
|
+
async killGamaServer() {
|
|
497
|
+
this.socketCheck();
|
|
498
|
+
const payload = { "type": "exit" };
|
|
499
|
+
this.sendPayload(payload);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* used to run execute an action defined in an agent in an experiment.
|
|
503
|
+
* @param action gaml code to be run from an agent
|
|
504
|
+
* @param args arguments of the action
|
|
505
|
+
* @param agent what agent this code applies to
|
|
506
|
+
* @param escaped optional parameter, if true will escape the action and args before sending them to gama
|
|
507
|
+
* @param exp_id optionnal parameter to specify the experiment. if none is given it will instead default to the last used experiment
|
|
508
|
+
* @returns a stringified response containing the result of the execution of the command
|
|
509
|
+
*/
|
|
510
|
+
async ask(action, args, agent, escaped, exp_id) {
|
|
511
|
+
this.socketCheck();
|
|
512
|
+
var payload = __spreadValues({
|
|
513
|
+
"type": "ask",
|
|
514
|
+
"exp_id": this.getId(exp_id),
|
|
515
|
+
"action": action,
|
|
516
|
+
"args": args,
|
|
517
|
+
"agent": agent
|
|
518
|
+
}, escaped && { "escaped": escaped });
|
|
519
|
+
this.sendPayload(payload);
|
|
520
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Compiles the code given in parameter and returns a message if any errors are detected.
|
|
524
|
+
* @param expr gaml expression to test
|
|
525
|
+
* @param syntax optionnal boolean, if true will only check the syntax. false will check for both syntactical and semantic errors
|
|
526
|
+
* @param escaped optionnal boolean, dictates if the expression is escaped or not
|
|
527
|
+
* @returns stringified json containing errors in the code if any
|
|
528
|
+
*/
|
|
529
|
+
async validate(expr, syntax, escaped) {
|
|
530
|
+
this.socketCheck();
|
|
531
|
+
const payload = __spreadValues(__spreadValues({
|
|
532
|
+
"type": "validate",
|
|
533
|
+
"expr": expr
|
|
534
|
+
}, syntax && { "syntax": syntax }), escaped && { "escaped": escaped });
|
|
535
|
+
this.sendPayload(payload);
|
|
536
|
+
try {
|
|
537
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
538
|
+
} catch (err) {
|
|
539
|
+
throw err;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* This command is used to ask the server more information on a given model. When received,
|
|
544
|
+
* the server will compile the model and return the different components found, depending on the option picked by the client.
|
|
545
|
+
* @param model_path path to the model to evaluate
|
|
546
|
+
* @param experimentsNames optional boolean that returns the name of all the experiments of the model
|
|
547
|
+
* @param speciesNames optional boolean that returns all of the species' names
|
|
548
|
+
* @param speciesVariables optional boolean that returns all variables of the species
|
|
549
|
+
* @param speciesActions optional boolean that returns all actions in the species
|
|
550
|
+
*/
|
|
551
|
+
async describe(model_path, experimentsNames, speciesNames, speciesVariables, speciesActions) {
|
|
552
|
+
this.socketCheck();
|
|
553
|
+
const payload = __spreadValues(__spreadValues(__spreadValues(__spreadValues({
|
|
554
|
+
"type": "describe",
|
|
555
|
+
"model": model_path
|
|
556
|
+
}, experimentsNames && { "experiments": experimentsNames }), speciesNames && { "speciesNames": speciesNames }), speciesActions && { "speciesActions": speciesActions }), speciesVariables && { "speciesVariables": speciesVariables });
|
|
557
|
+
this.sendPayload(payload);
|
|
558
|
+
return await this.success("CommandExecutedSuccessfully");
|
|
559
|
+
}
|
|
560
|
+
};
|