multimodalsim-viewer 0.1.1.0__py3-none-any.whl → 0.1.3.0__py3-none-any.whl

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.
@@ -1,8 +1,11 @@
1
+ import dataclasses
1
2
  import inspect
2
3
  import logging
3
4
  import multiprocessing
5
+ import time
6
+ from threading import Lock, Thread
4
7
 
5
- from flask_socketio import emit
8
+ from flask_socketio import SocketIO
6
9
 
7
10
  from multimodalsim_viewer.common.utils import (
8
11
  CLIENT_ROOM,
@@ -69,15 +72,203 @@ class SimulationHandler: # pylint: disable=too-many-instance-attributes, too-fe
69
72
  self.polylines_version = None
70
73
 
71
74
 
75
+ @dataclasses.dataclass
76
+ class ScheduledTask:
77
+ task: Thread | None
78
+ last_run: float | None = None
79
+
80
+
81
+ class SimulationNotFoundError(Exception):
82
+ def __init__(self, simulation_id: str):
83
+ super().__init__(f"Simulation {simulation_id} not found")
84
+
85
+
86
+ class MultipleSimulationsMatchingSocketIdError(Exception):
87
+ def __init__(self, socket_id: str, simulation_ids: list[str]):
88
+ super().__init__(f"Multiple simulations matching socket id {socket_id} : {', '.join(simulation_ids)}")
89
+
90
+
91
+ # MARK: SimulationManager
72
92
  class SimulationManager:
73
- simulations: dict[str, SimulationHandler]
93
+ def __init__(self, socketio: SocketIO):
94
+ self.socketio = socketio
74
95
 
75
- def __init__(self):
76
96
  self.simulations = {}
77
97
 
78
- def start_simulation(
79
- self, name: str, data: str, response_event: str, max_duration: float | None
80
- ) -> SimulationHandler:
98
+ self.task_by_simulation_id: dict[str, ScheduledTask] = {}
99
+
100
+ self.lock = Lock()
101
+
102
+ # MARK: +- Multi-threading
103
+ def __add_simulation_handler(self, simulation_handler: SimulationHandler) -> None:
104
+ with self.lock:
105
+ self.simulations[simulation_handler.simulation_id] = simulation_handler
106
+
107
+ def __set_socket_id(self, simulation_id: str, socket_id: str) -> None:
108
+ with self.lock:
109
+ if simulation_id not in self.simulations:
110
+ raise SimulationNotFoundError(simulation_id)
111
+
112
+ self.simulations[simulation_id].socket_id = socket_id
113
+
114
+ def __set_status(self, simulation_id: str, status: SimulationStatus) -> None:
115
+ with self.lock:
116
+ if simulation_id not in self.simulations:
117
+ raise SimulationNotFoundError(simulation_id)
118
+
119
+ self.simulations[simulation_id].status = status
120
+
121
+ def __set_start_time(self, simulation_id: str, start_time: float) -> None:
122
+ with self.lock:
123
+ if simulation_id not in self.simulations:
124
+ raise SimulationNotFoundError(simulation_id)
125
+
126
+ self.simulations[simulation_id].start_time = start_time
127
+
128
+ def __set_max_duration(self, simulation_id: str, max_duration: float | None) -> None:
129
+ with self.lock:
130
+ if simulation_id not in self.simulations:
131
+ raise SimulationNotFoundError(simulation_id)
132
+
133
+ self.simulations[simulation_id].max_duration = max_duration
134
+
135
+ def __set_simulation_time(self, simulation_id: str, simulation_time: float | None) -> None:
136
+ with self.lock:
137
+ if simulation_id not in self.simulations:
138
+ raise SimulationNotFoundError(simulation_id)
139
+
140
+ self.simulations[simulation_id].simulation_time = simulation_time
141
+
142
+ def __set_simulation_estimated_end_time(
143
+ self, simulation_id: str, simulation_estimated_end_time: float | None
144
+ ) -> None:
145
+ with self.lock:
146
+ if simulation_id not in self.simulations:
147
+ raise SimulationNotFoundError(simulation_id)
148
+
149
+ self.simulations[simulation_id].simulation_estimated_end_time = simulation_estimated_end_time
150
+
151
+ def __set_polylines_version(self, simulation_id: str, polylines_version: int | None) -> None:
152
+ with self.lock:
153
+ if simulation_id not in self.simulations:
154
+ raise SimulationNotFoundError(simulation_id)
155
+
156
+ self.simulations[simulation_id].polylines_version = polylines_version
157
+
158
+ def __set_name(self, simulation_id: str, name: str) -> None:
159
+ with self.lock:
160
+ if simulation_id not in self.simulations:
161
+ raise SimulationNotFoundError(simulation_id)
162
+
163
+ self.simulations[simulation_id].name = name
164
+
165
+ def __set_data(self, simulation_id: str, data: str) -> None:
166
+ with self.lock:
167
+ if simulation_id not in self.simulations:
168
+ raise SimulationNotFoundError(simulation_id)
169
+
170
+ self.simulations[simulation_id].data = data
171
+
172
+ def __set_simulation_start_time(self, simulation_id: str, simulation_start_time: float) -> None:
173
+ with self.lock:
174
+ if simulation_id not in self.simulations:
175
+ raise SimulationNotFoundError(simulation_id)
176
+
177
+ self.simulations[simulation_id].simulation_start_time = simulation_start_time
178
+
179
+ def __set_size(self, simulation_id: str, size: int) -> None:
180
+ with self.lock:
181
+ if simulation_id not in self.simulations:
182
+ raise SimulationNotFoundError(simulation_id)
183
+
184
+ self.simulations[simulation_id].size = size
185
+
186
+ def __get_socket_id(self, simulation_id: str) -> str | None:
187
+ with self.lock:
188
+ if simulation_id in self.simulations:
189
+ return self.simulations[simulation_id].socket_id
190
+ raise SimulationNotFoundError(simulation_id)
191
+
192
+ def __get_status(self, simulation_id: str) -> SimulationStatus:
193
+ with self.lock:
194
+ if simulation_id in self.simulations:
195
+ return self.simulations[simulation_id].status
196
+ raise SimulationNotFoundError(simulation_id)
197
+
198
+ def __get_status_if_exists(self, simulation_id: str) -> SimulationStatus | None:
199
+ with self.lock:
200
+ if simulation_id in self.simulations:
201
+ return self.simulations[simulation_id].status
202
+ return None
203
+
204
+ def __get_matching_simulation_id_by_socket_id(self, socket_id: str) -> str | None:
205
+ with self.lock:
206
+ matching_simulation_ids = [
207
+ simulation_id
208
+ for simulation_id, simulation in self.simulations.items()
209
+ if simulation.socket_id == socket_id
210
+ ]
211
+
212
+ if len(matching_simulation_ids) == 1:
213
+ return matching_simulation_ids[0]
214
+
215
+ if len(matching_simulation_ids) > 1:
216
+ raise MultipleSimulationsMatchingSocketIdError(socket_id, matching_simulation_ids)
217
+ return None
218
+
219
+ def __get_all_simulation_ids(self) -> list[str]:
220
+ with self.lock:
221
+ return list(self.simulations.keys())
222
+
223
+ def __get_serialized_simulation(self, simulation_id: str) -> dict:
224
+ with self.lock:
225
+ if simulation_id not in self.simulations:
226
+ raise SimulationNotFoundError(simulation_id)
227
+
228
+ simulation = self.simulations[simulation_id]
229
+
230
+ serialized_simulation = {
231
+ "id": simulation_id,
232
+ "name": simulation.name,
233
+ "status": simulation.status.value,
234
+ "startTime": simulation.start_time,
235
+ "data": simulation.data,
236
+ }
237
+
238
+ if simulation.simulation_start_time is not None:
239
+ serialized_simulation["simulationStartTime"] = simulation.simulation_start_time
240
+
241
+ if simulation.simulation_end_time is not None:
242
+ serialized_simulation["simulationEndTime"] = simulation.simulation_end_time
243
+
244
+ if simulation.simulation_time is not None:
245
+ serialized_simulation["simulationTime"] = simulation.simulation_time
246
+
247
+ if simulation.simulation_estimated_end_time is not None:
248
+ serialized_simulation["simulationEstimatedEndTime"] = simulation.simulation_estimated_end_time
249
+
250
+ if simulation.max_duration is not None:
251
+ serialized_simulation["configuration"] = {"maxDuration": simulation.max_duration}
252
+
253
+ if simulation.polylines_version is not None:
254
+ serialized_simulation["polylinesVersion"] = simulation.polylines_version
255
+
256
+ if simulation.size is not None:
257
+ serialized_simulation["size"] = simulation.size
258
+
259
+ return serialized_simulation
260
+
261
+ def __does_simulation_exist(self, simulation_id: str) -> bool:
262
+ with self.lock:
263
+ return simulation_id in self.simulations
264
+
265
+ def __delete_simulation_handler_if_exists(self, simulation_id: str) -> None:
266
+ with self.lock:
267
+ if simulation_id in self.simulations:
268
+ del self.simulations[simulation_id]
269
+
270
+ # MARK: +- Simulation control
271
+ def start_simulation(self, name: str, data: str, response_event: str, max_duration: float | None):
81
272
  simulation_id, start_time = build_simulation_id(name)
82
273
 
83
274
  simulation_process = multiprocessing.Process(
@@ -96,199 +287,194 @@ class SimulationManager:
96
287
  simulation_process,
97
288
  )
98
289
 
99
- self.simulations[simulation_id] = simulation_handler
290
+ self.__add_simulation_handler(simulation_handler)
100
291
 
101
292
  simulation_process.start()
102
293
 
103
- self.emit_simulations()
104
-
105
294
  log(f'Emitting response event "{response_event}"', "server")
106
- emit(response_event, simulation_id, to=CLIENT_ROOM)
107
295
 
108
- return simulation_handler
296
+ self.socketio.emit(response_event, simulation_id, to=CLIENT_ROOM)
109
297
 
110
- def on_simulation_start(self, simulation_id, socket_id, simulation_start_time):
111
- if simulation_id not in self.simulations:
112
- log(
113
- f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
114
- "server",
115
- logging.ERROR,
116
- )
117
- return
298
+ log(f"Started simulation {simulation_id}", "server")
118
299
 
119
- simulation = self.simulations[simulation_id]
300
+ self.emit_simulation(simulation_id)
301
+
302
+ def stop_simulation(self, simulation_id):
303
+ try:
304
+ self.__set_status(simulation_id, SimulationStatus.STOPPING)
120
305
 
121
- simulation.socket_id = socket_id
122
- simulation.status = SimulationStatus.RUNNING
123
- simulation.simulation_start_time = simulation_start_time
306
+ self.socketio.emit("stop-simulation", to=self.__get_socket_id(simulation_id))
124
307
 
125
- self.emit_simulations()
308
+ log(f"Stopping simulation {simulation_id}", "server")
126
309
 
127
- def stop_simulation(self, simulation_id):
128
- if simulation_id not in self.simulations:
310
+ self.emit_simulation(simulation_id)
311
+ except SimulationNotFoundError:
129
312
  log(
130
313
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
131
314
  "server",
132
315
  logging.ERROR,
133
316
  )
134
- return
135
-
136
- simulation = self.simulations[simulation_id]
137
- simulation.status = SimulationStatus.STOPPING
138
-
139
- emit("stop-simulation", to=simulation.socket_id)
140
317
 
141
318
  def pause_simulation(self, simulation_id):
142
- if simulation_id not in self.simulations:
319
+ try:
320
+ self.socketio.emit("pause-simulation", to=self.__get_socket_id(simulation_id))
321
+
322
+ log(f"Pausing simulation {simulation_id}", "server")
323
+ except SimulationNotFoundError:
143
324
  log(
144
325
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
145
326
  "server",
146
327
  logging.ERROR,
147
328
  )
148
- return
149
-
150
- simulation = self.simulations[simulation_id]
151
329
 
152
- emit("pause-simulation", to=simulation.socket_id)
330
+ def resume_simulation(self, simulation_id):
331
+ try:
332
+ self.socketio.emit("resume-simulation", to=self.__get_socket_id(simulation_id))
153
333
 
154
- def on_simulation_pause(self, simulation_id):
155
- if simulation_id not in self.simulations:
334
+ log(f"Resuming simulation {simulation_id}", "server")
335
+ except SimulationNotFoundError:
156
336
  log(
157
337
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
158
338
  "server",
159
339
  logging.ERROR,
160
340
  )
161
- return
162
341
 
163
- simulation = self.simulations[simulation_id]
342
+ def edit_simulation_configuration(self, simulation_id: str, max_duration: float | None) -> None:
343
+ try:
344
+ self.__set_max_duration(simulation_id, max_duration)
164
345
 
165
- simulation.status = SimulationStatus.PAUSED
346
+ self.socketio.emit("edit-simulation-configuration", (max_duration,), to=self.__get_socket_id(simulation_id))
166
347
 
167
- self.emit_simulations()
348
+ log(f"Edited simulation {simulation_id} configuration", "server")
168
349
 
169
- def resume_simulation(self, simulation_id):
170
- if simulation_id not in self.simulations:
350
+ self.emit_simulation(simulation_id)
351
+ except SimulationNotFoundError:
171
352
  log(
172
353
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
173
354
  "server",
174
355
  logging.ERROR,
175
356
  )
176
- return
177
357
 
178
- simulation = self.simulations[simulation_id]
358
+ # MARK: +- Simulation events
359
+ def on_simulation_start(self, simulation_id, socket_id, simulation_start_time):
360
+ try:
361
+ self.__set_socket_id(simulation_id, socket_id)
362
+ self.__set_status(simulation_id, SimulationStatus.RUNNING)
363
+ self.__set_simulation_start_time(simulation_id, simulation_start_time)
179
364
 
180
- emit("resume-simulation", to=simulation.socket_id)
365
+ log(f"Simulation {simulation_id} started", "server")
181
366
 
182
- def on_simulation_resume(self, simulation_id):
183
- if simulation_id not in self.simulations:
367
+ self.emit_simulation(simulation_id)
368
+ except SimulationNotFoundError:
184
369
  log(
185
370
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
186
371
  "server",
187
372
  logging.ERROR,
188
373
  )
189
- return
190
-
191
- simulation = self.simulations[simulation_id]
192
374
 
193
- simulation.status = SimulationStatus.RUNNING
375
+ def on_simulation_pause(self, simulation_id):
376
+ try:
377
+ self.__set_status(simulation_id, SimulationStatus.PAUSED)
194
378
 
195
- self.emit_simulations()
379
+ log(f"Simulation {simulation_id} paused", "server")
196
380
 
197
- def edit_simulation_configuration(self, simulation_id: str, max_duration: float | None) -> None:
198
- if simulation_id not in self.simulations:
381
+ self.emit_simulation(simulation_id)
382
+ except SimulationNotFoundError:
199
383
  log(
200
384
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
201
385
  "server",
202
386
  logging.ERROR,
203
387
  )
204
- return
205
388
 
206
- simulation = self.simulations[simulation_id]
207
-
208
- simulation.max_duration = max_duration
209
-
210
- emit("edit-simulation-configuration", (max_duration,), to=simulation.socket_id)
389
+ def on_simulation_resume(self, simulation_id):
390
+ try:
391
+ self.__set_status(simulation_id, SimulationStatus.RUNNING)
211
392
 
212
- self.emit_simulations()
393
+ log(f"Simulation {simulation_id} resumed", "server")
213
394
 
214
- log(
215
- f"Emitted simulations with new max duration {max_duration} for simulation {simulation_id}",
216
- "server",
217
- logging.WARN,
218
- )
395
+ self.emit_simulation(simulation_id)
396
+ except SimulationNotFoundError:
397
+ log(
398
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
399
+ "server",
400
+ logging.ERROR,
401
+ )
219
402
 
220
403
  def on_simulation_disconnect(self, socket_id):
221
- matching_simulation_ids = [
222
- simulation_id for simulation_id, simulation in self.simulations.items() if simulation.socket_id == socket_id
223
- ]
224
-
225
- if len(matching_simulation_ids) != 1:
226
- # The simulation has already been disconnected properly
227
- return
404
+ try:
405
+ simulation_id = self.__get_matching_simulation_id_by_socket_id(socket_id)
228
406
 
229
- simulation_id = matching_simulation_ids[0]
407
+ if simulation_id is None:
408
+ # Simulation already disconnected properly
409
+ return
230
410
 
231
- # Get the simulation information from the save file
232
- simulation_information = SimulationVisualizationDataManager.get_simulation_information(simulation_id)
411
+ status = self.__get_status(simulation_id)
233
412
 
234
- simulation = self.simulations[simulation_id]
413
+ simulation_information = SimulationVisualizationDataManager.get_simulation_information(simulation_id)
235
414
 
236
- if simulation.status in RUNNING_SIMULATION_STATUSES:
237
- if simulation_information.simulation_end_time is None:
238
- # The simulation has been lost
239
- simulation.status = SimulationStatus.LOST
240
- else:
241
- # The simulation has been completed
242
- simulation.status = SimulationStatus.COMPLETED
415
+ if status in RUNNING_SIMULATION_STATUSES:
416
+ if simulation_information.simulation_end_time is None:
417
+ # The simulation has been lost
418
+ self.__set_status(simulation_id, SimulationStatus.LOST)
419
+ else:
420
+ # The simulation has been completed
421
+ self.__set_status(simulation_id, SimulationStatus.COMPLETED)
243
422
 
244
- simulation.socket_id = None
423
+ self.__set_socket_id(simulation_id, None)
245
424
 
246
- self.emit_simulations()
425
+ log(f"Simulation {simulation_id} disconnected", "server")
247
426
 
248
- def on_simulation_update_time(self, simulation_id, timestamp):
249
- if simulation_id not in self.simulations:
427
+ self.emit_simulation(simulation_id)
428
+ except SimulationNotFoundError:
250
429
  log(
251
- f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
430
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation with socket ID {socket_id} not found",
252
431
  "server",
253
432
  logging.ERROR,
254
433
  )
255
- return
256
434
 
257
- simulation = self.simulations[simulation_id]
258
-
259
- simulation.simulation_time = timestamp
435
+ def on_simulation_update_time(self, simulation_id, timestamp):
436
+ try:
437
+ self.__set_simulation_time(simulation_id, timestamp)
260
438
 
261
- self.emit_simulations()
439
+ log(f"Simulation {simulation_id} time updated to {timestamp}", "server")
262
440
 
263
- def on_simulation_update_estimated_end_time(self, simulation_id, estimated_end_time):
264
- if simulation_id not in self.simulations:
441
+ self.emit_simulation(simulation_id)
442
+ except SimulationNotFoundError:
265
443
  log(
266
444
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
267
445
  "server",
268
446
  logging.ERROR,
269
447
  )
270
- return
271
-
272
- simulation = self.simulations[simulation_id]
273
448
 
274
- simulation.simulation_estimated_end_time = estimated_end_time
449
+ def on_simulation_update_estimated_end_time(self, simulation_id, estimated_end_time):
450
+ try:
451
+ self.__set_simulation_estimated_end_time(simulation_id, estimated_end_time)
275
452
 
276
- self.emit_simulations()
453
+ log(f"Simulation {simulation_id} estimated end time updated to {estimated_end_time}", "server")
277
454
 
278
- def on_simulation_update_polylines_version(self, simulation_id):
279
- if simulation_id not in self.simulations:
455
+ self.emit_simulation(simulation_id)
456
+ except SimulationNotFoundError:
280
457
  log(
281
458
  f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
282
459
  "server",
283
460
  logging.ERROR,
284
461
  )
285
- return
286
462
 
287
- simulation = self.simulations[simulation_id]
463
+ def on_simulation_update_polylines_version(self, simulation_id):
464
+ try:
465
+ self.__set_polylines_version(
466
+ simulation_id, SimulationVisualizationDataManager.get_polylines_version_with_lock(simulation_id)
467
+ )
288
468
 
289
- simulation.polylines_version = SimulationVisualizationDataManager.get_polylines_version_with_lock(simulation_id)
469
+ log(f"Simulation {simulation_id} polylines version updated", "server")
290
470
 
291
- self.emit_simulations()
471
+ self.emit_simulation(simulation_id)
472
+ except SimulationNotFoundError:
473
+ log(
474
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
475
+ "server",
476
+ logging.ERROR,
477
+ )
292
478
 
293
479
  def on_simulation_identification( # pylint: disable=too-many-arguments, too-many-positional-arguments
294
480
  self,
@@ -301,89 +487,66 @@ class SimulationManager:
301
487
  status,
302
488
  socket_id,
303
489
  ):
304
-
305
- log(
306
- f"Identifying simulation {simulation_id}",
307
- "simulation",
308
- )
309
-
310
- start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
311
-
312
- if simulation_id in self.simulations:
313
- simulation = self.simulations[simulation_id]
314
- else:
315
- start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
316
-
317
- simulation = SimulationHandler(
318
- simulation_id,
319
- name,
320
- start_time,
321
- data,
322
- SimulationStatus(status),
323
- max_duration,
324
- None,
490
+ try:
491
+ log(
492
+ f"Identifying simulation {simulation_id}",
493
+ "simulation",
325
494
  )
326
495
 
327
- self.simulations[simulation_id] = simulation
328
-
329
- simulation.name = name
330
- simulation.start_time = start_time
331
- simulation.data = data
332
- simulation.simulation_start_time = simulation_start_time
333
- simulation.simulation_time = simulation_time
334
- simulation.simulation_estimated_end_time = simulation_estimated_end_time
335
- simulation.max_duration = max_duration
336
- simulation.status = SimulationStatus(status)
337
- simulation.socket_id = socket_id
338
-
339
- simulation.polylines_version = SimulationVisualizationDataManager.get_polylines_version_with_lock(simulation_id)
340
-
341
- self.emit_simulations()
342
-
343
- def emit_simulations(self):
344
- self.query_simulations()
345
-
346
- serialized_simulations = []
347
-
348
- for simulation_id, simulation in self.simulations.items():
349
- serialized_simulation = {
350
- "id": simulation_id,
351
- "name": simulation.name,
352
- "status": simulation.status.value,
353
- "startTime": simulation.start_time,
354
- "data": simulation.data,
355
- }
356
-
357
- if simulation.simulation_start_time is not None:
358
- serialized_simulation["simulationStartTime"] = simulation.simulation_start_time
359
-
360
- if simulation.simulation_end_time is not None:
361
- serialized_simulation["simulationEndTime"] = simulation.simulation_end_time
496
+ start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
362
497
 
363
- if simulation.simulation_time is not None:
364
- serialized_simulation["simulationTime"] = simulation.simulation_time
498
+ if not self.__does_simulation_exist(simulation_id):
499
+ start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
365
500
 
366
- if simulation.simulation_estimated_end_time is not None:
367
- serialized_simulation["simulationEstimatedEndTime"] = simulation.simulation_estimated_end_time
501
+ simulation = SimulationHandler(
502
+ simulation_id,
503
+ name,
504
+ start_time,
505
+ data,
506
+ SimulationStatus(status),
507
+ max_duration,
508
+ None,
509
+ )
368
510
 
369
- if simulation.max_duration is not None:
370
- serialized_simulation["configuration"] = {"maxDuration": simulation.max_duration}
511
+ self.__add_simulation_handler(simulation)
512
+
513
+ self.__set_name(simulation_id, name)
514
+ self.__set_start_time(simulation_id, start_time)
515
+ self.__set_data(simulation_id, data)
516
+ self.__set_simulation_start_time(simulation_id, simulation_start_time)
517
+ self.__set_simulation_time(simulation_id, simulation_time)
518
+ self.__set_simulation_estimated_end_time(simulation_id, simulation_estimated_end_time)
519
+ self.__set_max_duration(simulation_id, max_duration)
520
+ self.__set_status(simulation_id, SimulationStatus(status))
521
+ self.__set_socket_id(simulation_id, socket_id)
522
+ self.__set_polylines_version(
523
+ simulation_id, SimulationVisualizationDataManager.get_polylines_version_with_lock(simulation_id)
524
+ )
371
525
 
372
- if simulation.polylines_version is not None:
373
- serialized_simulation["polylinesVersion"] = simulation.polylines_version
526
+ self.emit_simulation(simulation_id)
527
+ except SimulationNotFoundError as error:
528
+ print(error)
529
+ log(
530
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
531
+ "server",
532
+ logging.ERROR,
533
+ )
374
534
 
375
- if simulation.size is not None:
376
- serialized_simulation["size"] = simulation.size
535
+ # MARK: +- Visualization
536
+ def emit_simulation_polylines(self, simulation_id):
537
+ if not self.__does_simulation_exist(simulation_id):
538
+ log(
539
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
540
+ "server",
541
+ logging.ERROR,
542
+ )
543
+ return
377
544
 
378
- serialized_simulations.append(serialized_simulation)
545
+ polylines, version = SimulationVisualizationDataManager.get_polylines(simulation_id)
379
546
 
380
- emit(
381
- "simulations",
382
- serialized_simulations,
383
- to=CLIENT_ROOM,
384
- )
547
+ self.socketio.emit(f"polylines-{simulation_id}", (polylines, version), to=CLIENT_ROOM)
385
548
 
386
- log("Emitting simulations", "server")
549
+ log(f"Emitted polylines for simulation {simulation_id}", "server")
387
550
 
388
551
  def emit_missing_simulation_states(
389
552
  self,
@@ -391,30 +554,28 @@ class SimulationManager:
391
554
  visualization_time: float,
392
555
  complete_state_update_indexes: list[int],
393
556
  ) -> None:
394
- if simulation_id not in self.simulations:
395
- log(
396
- f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
397
- "server",
398
- logging.ERROR,
399
- )
400
- return
401
-
402
- simulation = self.simulations[simulation_id]
403
557
 
404
558
  try:
405
559
  (missing_states, missing_updates, has_all_states) = SimulationVisualizationDataManager.get_missing_states(
406
560
  simulation_id,
407
561
  visualization_time,
408
562
  complete_state_update_indexes,
409
- simulation.status not in RUNNING_SIMULATION_STATUSES,
563
+ self.__get_status(simulation_id) not in RUNNING_SIMULATION_STATUSES,
410
564
  )
411
565
 
412
- emit(
566
+ self.socketio.emit(
413
567
  "missing-simulation-states",
414
568
  (missing_states, missing_updates, has_all_states),
415
569
  to=get_session_id(),
416
570
  )
417
571
 
572
+ except SimulationNotFoundError:
573
+ log(
574
+ f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
575
+ "server",
576
+ logging.ERROR,
577
+ )
578
+ return
418
579
  except Exception as e: # pylint: disable=broad-exception-caught
419
580
  log(
420
581
  f"Error while emitting missing simulation states for {simulation_id}: {e}",
@@ -427,52 +588,120 @@ class SimulationManager:
427
588
  logging.ERROR,
428
589
  )
429
590
 
430
- simulation.status = SimulationStatus.CORRUPTED
591
+ self.__set_status(simulation_id, SimulationStatus.CORRUPTED)
431
592
 
432
593
  SimulationVisualizationDataManager.mark_simulation_as_corrupted(simulation_id)
433
594
 
434
- self.emit_simulations()
595
+ self.emit_simulation(simulation_id)
435
596
 
436
- def emit_simulation_polylines(self, simulation_id):
437
- if simulation_id not in self.simulations:
597
+ # MARK: +- Simulation list
598
+ def on_simulation_delete(self, simulation_id: str) -> None:
599
+ self.__delete_simulation_handler_if_exists(simulation_id)
600
+
601
+ self.socketio.emit("delete-simulation", simulation_id, to=CLIENT_ROOM)
602
+
603
+ log(f"Deleted simulation {simulation_id} from simulation manager", "server")
604
+
605
+ def emit_simulations(self, loaded_simulations_ids: list[str]):
606
+ try:
607
+ all_simulation_ids = SimulationVisualizationDataManager.get_all_saved_simulation_ids()
608
+
609
+ log("Emitting simulations", "server")
610
+
611
+ simulation_ids_to_delete: set[str] = set()
612
+
613
+ for simulation_id in self.__get_all_simulation_ids():
614
+ if simulation_id not in all_simulation_ids and self.__get_status(simulation_id) not in [
615
+ SimulationStatus.RUNNING,
616
+ SimulationStatus.PAUSED,
617
+ SimulationStatus.STOPPING,
618
+ SimulationStatus.STARTING,
619
+ SimulationStatus.LOST,
620
+ ]:
621
+ simulation_ids_to_delete.add(simulation_id)
622
+
623
+ for loaded_simulation_id in loaded_simulations_ids:
624
+ if not loaded_simulation_id in all_simulation_ids:
625
+ simulation_ids_to_delete.add(loaded_simulation_id)
626
+
627
+ for simulation_id in simulation_ids_to_delete:
628
+ self.on_simulation_delete(simulation_id)
629
+
630
+ for simulation_id in all_simulation_ids:
631
+ self.emit_simulation(simulation_id)
632
+
633
+ log("Emitted simulations", "server")
634
+ except SimulationNotFoundError:
438
635
  log(
439
- f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
636
+ f"{__file__} {inspect.currentframe().f_lineno}: One or more simulations not found",
440
637
  "server",
441
638
  logging.ERROR,
442
639
  )
443
- return
444
640
 
445
- polylines, version = SimulationVisualizationDataManager.get_polylines(simulation_id)
641
+ def emit_simulation(self, simulation_id: str) -> None:
642
+ scheduled_task = self.task_by_simulation_id.get(simulation_id, None)
643
+ if scheduled_task is None:
644
+ scheduled_task = ScheduledTask(None)
645
+ self.task_by_simulation_id[simulation_id] = scheduled_task
646
+
647
+ minimum_debounce_time = 1
648
+ actual_debounce_time = 0
649
+ now = time.monotonic()
650
+
651
+ if scheduled_task.last_run is not None and scheduled_task.last_run + minimum_debounce_time > now:
652
+ actual_debounce_time = scheduled_task.last_run + minimum_debounce_time - now
446
653
 
447
- emit(f"polylines-{simulation_id}", (polylines, version), to=CLIENT_ROOM)
654
+ def action():
655
+ self.socketio.sleep(actual_debounce_time)
656
+
657
+ try:
658
+ self.query_simulation(simulation_id)
448
659
 
449
- def query_simulations(self):
450
- all_simulation_ids = SimulationVisualizationDataManager.get_all_saved_simulation_ids()
660
+ log(f"Emitting simulation {simulation_id}", "server")
451
661
 
452
- for simulation_id, _ in list(self.simulations.items()):
453
- if simulation_id not in all_simulation_ids and self.simulations[simulation_id].status not in [
662
+ serialized_simulation = self.__get_serialized_simulation(simulation_id)
663
+
664
+ self.socketio.emit("simulation", serialized_simulation, to=CLIENT_ROOM)
665
+
666
+ log(f"Emitted simulation {simulation_id}", "server")
667
+
668
+ except Exception as e: # pylint: disable=broad-exception-caught
669
+ log(
670
+ f"Error while emitting simulation {simulation_id}: {e}",
671
+ "server",
672
+ logging.ERROR,
673
+ )
674
+ finally:
675
+ scheduled_task.last_run = time.monotonic()
676
+ scheduled_task.task = None
677
+
678
+ if scheduled_task.task is None:
679
+ if actual_debounce_time > 0:
680
+ scheduled_task.task = self.socketio.start_background_task(action)
681
+ log(f"Scheduled emit of simulation {simulation_id} in {actual_debounce_time}s", "server")
682
+ else:
683
+ action()
684
+ log(f"Emitted simulation {simulation_id} immediately", "server")
685
+ else:
686
+ log(f"Simulation {simulation_id} is already scheduled", "server")
687
+
688
+ def query_simulation(self, simulation_id) -> None:
689
+ log(f"Querying simulation {simulation_id}", "server")
690
+
691
+ try:
692
+ if self.__get_status_if_exists(simulation_id) in [
454
693
  SimulationStatus.RUNNING,
455
694
  SimulationStatus.PAUSED,
456
695
  SimulationStatus.STOPPING,
457
696
  SimulationStatus.STARTING,
458
697
  SimulationStatus.LOST,
459
698
  ]:
460
- del self.simulations[simulation_id]
461
-
462
- for simulation_id in all_simulation_ids:
463
- # Non valid save files might throw an exception
464
- self.query_simulation(simulation_id)
465
-
466
- def query_simulation(self, simulation_id) -> None:
467
- if simulation_id in self.simulations and self.simulations[simulation_id].status in [
468
- SimulationStatus.RUNNING,
469
- SimulationStatus.PAUSED,
470
- SimulationStatus.STOPPING,
471
- SimulationStatus.STARTING,
472
- SimulationStatus.LOST,
473
- ]:
474
- simulation = self.simulations[simulation_id]
475
- simulation.size = SimulationVisualizationDataManager.get_saved_simulation_size(simulation_id)
699
+ self.__set_size(
700
+ simulation_id, SimulationVisualizationDataManager.get_saved_simulation_size(simulation_id)
701
+ )
702
+ return
703
+ except SimulationNotFoundError:
704
+ log(f"Simulation {simulation_id} not found", "server", logging.ERROR)
476
705
  return
477
706
 
478
707
  is_corrupted = SimulationVisualizationDataManager.is_simulation_corrupted(simulation_id)
@@ -499,13 +728,11 @@ class SimulationManager:
499
728
  log(
500
729
  f"Simulation {simulation_id} version is outdated",
501
730
  "server",
502
- logging.DEBUG,
503
731
  )
504
732
  if status == SimulationStatus.FUTURE:
505
733
  log(
506
734
  f"Simulation {simulation_id} version is future",
507
735
  "server",
508
- logging.DEBUG,
509
736
  )
510
737
 
511
738
  simulation = SimulationHandler(
@@ -529,13 +756,13 @@ class SimulationManager:
529
756
  # The simulation is not running but the end time is not set
530
757
  raise Exception("Simulation is corrupted") # pylint: disable=broad-exception-raised
531
758
 
532
- self.simulations[simulation_id] = simulation
759
+ self.__add_simulation_handler(simulation)
533
760
 
534
761
  except Exception: # pylint: disable=broad-exception-caught
535
762
  is_corrupted = True
536
763
 
537
764
  if is_corrupted:
538
- log(f"Simulation {simulation_id} is corrupted", "server", logging.DEBUG)
765
+ log(f"Simulation {simulation_id} is corrupted", "server")
539
766
 
540
767
  simulation = SimulationHandler(
541
768
  simulation_id,
@@ -547,23 +774,8 @@ class SimulationManager:
547
774
  None,
548
775
  )
549
776
 
550
- self.simulations[simulation_id] = simulation
777
+ self.__add_simulation_handler(simulation)
551
778
 
552
779
  SimulationVisualizationDataManager.mark_simulation_as_corrupted(simulation_id)
553
-
554
- def get_all_simulation_states(self, simulation_id: str) -> None:
555
- if simulation_id not in self.simulations:
556
- log(
557
- f"{__file__} {inspect.currentframe().f_lineno}: Simulation {simulation_id} not found",
558
- "server",
559
- logging.ERROR,
560
- )
561
- return
562
-
563
- states, updates = SimulationVisualizationDataManager.get_all_simulation_states(simulation_id)
564
-
565
- emit(
566
- "all-simulation-states",
567
- (states, updates),
568
- to=CLIENT_ROOM,
569
- )
780
+ else:
781
+ log(f"Simulation {simulation_id} queried successfully", "server")