refactor: cleanup UI service method namespace
[e-mobility-charging-stations-simulator.git] / src / charging-station / Bootstrap.ts
index 4a6bd75c2d7a2ebbce2e6ebb24c3ceab9019dba6..802a944525cc4c824e6a282aa048c20ab6a0e9cc 100644 (file)
@@ -1,5 +1,6 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
+import { EventEmitter } from 'node:events';
 import path from 'node:path';
 import { fileURLToPath } from 'node:url';
 import { type Worker, isMainThread } from 'node:worker_threads';
@@ -31,7 +32,7 @@ enum exitCodes {
   noChargingStationTemplates = 2,
 }
 
-export class Bootstrap {
+export class Bootstrap extends EventEmitter {
   private static instance: Bootstrap | null = null;
   public numberOfChargingStations!: number;
   public numberOfChargingStationTemplates!: number;
@@ -45,10 +46,9 @@ export class Bootstrap {
   private readonly workerScript: string;
 
   private constructor() {
+    super();
     for (const signal of ['SIGINT', 'SIGQUIT', 'SIGTERM']) {
-      process.on(signal, () => {
-        this.gracefulShutdown().catch(Constants.EMPTY_FUNCTION);
-      });
+      process.on(signal, this.gracefulShutdown);
     }
     // Enable unconditionally for now
     ErrorUtils.handleUnhandledRejection();
@@ -129,11 +129,14 @@ export class Bootstrap {
 
   public async stop(): Promise<void> {
     if (isMainThread && this.started === true) {
-      await this.uiServer?.sendBroadcastChannelRequest(
-        Utils.generateUUID(),
-        ProcedureName.STOP_CHARGING_STATION,
-        Constants.EMPTY_FREEZED_OBJECT
+      await this.uiServer?.sendInternalRequest(
+        this.uiServer.buildProtocolRequest(
+          Utils.generateUUID(),
+          ProcedureName.STOP_CHARGING_STATION,
+          Constants.EMPTY_FREEZED_OBJECT
+        )
       );
+      await this.waitForChargingStationsStopped();
       await this.workerImplementation?.stop();
       this.workerImplementation = null;
       this.uiServer?.stop();
@@ -183,15 +186,22 @@ export class Bootstrap {
       switch (msg.id) {
         case ChargingStationWorkerMessageEvents.started:
           this.workerEventStarted(msg.data as ChargingStationData);
+          this.emit(ChargingStationWorkerMessageEvents.started, msg.data as ChargingStationData);
           break;
         case ChargingStationWorkerMessageEvents.stopped:
           this.workerEventStopped(msg.data as ChargingStationData);
+          this.emit(ChargingStationWorkerMessageEvents.stopped, msg.data as ChargingStationData);
           break;
         case ChargingStationWorkerMessageEvents.updated:
           this.workerEventUpdated(msg.data as ChargingStationData);
+          this.emit(ChargingStationWorkerMessageEvents.updated, msg.data as ChargingStationData);
           break;
         case ChargingStationWorkerMessageEvents.performanceStatistics:
           this.workerEventPerformanceStatistics(msg.data as Statistics);
+          this.emit(
+            ChargingStationWorkerMessageEvents.performanceStatistics,
+            msg.data as Statistics
+          );
           break;
         default:
           throw new BaseError(
@@ -282,14 +292,33 @@ export class Bootstrap {
     });
   }
 
-  private gracefulShutdown = async (): Promise<void> => {
+  private gracefulShutdown = (): void => {
     console.info(`${chalk.green('Graceful shutdown')}`);
-    try {
-      await this.stop();
-      process.exit(0);
-    } catch (error) {
-      process.exit(1);
-    }
+    this.stop()
+      .then(() => {
+        process.exit(0);
+      })
+      .catch((error) => {
+        console.error(chalk.red('Error while shutdowning charging stations simulator: '), error);
+        process.exit(1);
+      });
+  };
+
+  private waitForChargingStationsStopped = async (
+    stoppedEventsToWait = this.numberOfStartedChargingStations
+  ): Promise<number> => {
+    return new Promise((resolve) => {
+      let stoppedEvents = 0;
+      if (stoppedEventsToWait === 0) {
+        resolve(stoppedEvents);
+      }
+      this.on(ChargingStationWorkerMessageEvents.stopped, () => {
+        ++stoppedEvents;
+        if (stoppedEvents === stoppedEventsToWait) {
+          resolve(stoppedEvents);
+        }
+      });
+    });
   };
 
   private logPrefix = (): string => {