diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 344c385..0ae2296 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,8 +1,9 @@
name: Java CI with Maven
on:
+ workflow_dispatch:
push:
- branches: [ "main" ]
+ branches: [ "dev", "cleanup" ]
tags:
- 'v*.*.*'
pull_request:
@@ -11,51 +12,93 @@ on:
jobs:
build:
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
-
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
-
- name: Build with Maven
run: mvn -B package
working-directory: main
-
- name: Upload built JAR
uses: actions/upload-artifact@v4
with:
name: package
path: main/target/*.jar
-
- name: Generate dependency graph
run: mvn -B -f main/pom.xml com.github.ferstl:depgraph-maven-plugin:4.0.1:graph
-
- name: Upload dependency graph artifact
uses: actions/upload-artifact@v4
with:
name: dependency-graph
path: main/target/**
+ build-windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven
+ - name: Build with Maven (Skip Tests)
+ run: mvn -B package -DskipTests
+ working-directory: main
+ - name: Create JPackage App Image
+ shell: pwsh
+ run: |
+ New-Item -ItemType Directory -Force -Path "dist"
+ jpackage --name "DTSS" `
+ --input main/target `
+ --main-jar main-1.0-SNAPSHOT.jar `
+ --dest dist `
+ --type app-image `
+ --win-console
+ - name: Inject java.exe
+ shell: pwsh
+ run: |
+ $javaPath = (Get-Command java).Source
+ Copy-Item -Path $javaPath -Destination "dist/DTSS/runtime/bin/"
+ - name: Zip Windows Release
+ shell: pwsh
+ run: |
+ Compress-Archive -Path "dist/DTSS" -DestinationPath "dist/DTSS-Windows.zip"
+ - name: Upload Windows Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: windows-package
+ path: dist/DTSS-Windows.zip
+
publish-release:
runs-on: ubuntu-latest
- needs: [build]
- if: startsWith(github.ref, 'refs/tags/')
+ needs: [build, build-windows]
+ if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
permissions:
contents: write
-
steps:
- - name: Download built JAR
+ - name: Download Linux JAR
uses: actions/download-artifact@v4
with:
name: package
path: main/target/
-
+ - name: Download Windows Zip
+ uses: actions/download-artifact@v4
+ with:
+ name: windows-package
+ path: windows-dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
- files: main/target/*.jar
+ tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'snapshot-build' }}
+ name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'Manual Snapshot Build' }}
+ draft: false
+ prerelease: true
+ make_latest: false
+ files: |
+ main/target/*.jar
+ windows-dist/*.zip
diff --git a/.gitignore b/.gitignore
index 6fa7db9..5c91afc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,6 @@ build/
# Other
*.swp
*.pdf
+
+# JAR built pom file
+dependency-reduced-pom.xml
\ No newline at end of file
diff --git a/main/pom.xml b/main/pom.xml
index 56ce74f..3ecd199 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -29,6 +29,18 @@
gson
2.10.1
+
+
+
+ org.openjfx
+ javafx-controls
+ 17.0.2
+
+
+ org.openjfx
+ javafx-fxml
+ 17.0.2
+
@@ -39,7 +51,16 @@
exec-maven-plugin
3.1.0
- sd.Entry
+ sd.dashboard.Launcher
+
+
+
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.8
+
+ sd.dashboard.Launcher
@@ -55,7 +76,7 @@
- sd.Entry
+ sd.dashboard.Launcher
diff --git a/main/src/main/java/sd/Entry.java b/main/src/main/java/sd/Entry.java
deleted file mode 100644
index 323ce66..0000000
--- a/main/src/main/java/sd/Entry.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package sd;
-
-import java.io.IOException;
-
-import sd.config.SimulationConfig;
-import sd.engine.SimulationEngine;
-
-/**
- * Main entry point for the traffic simulation.
- * * This class is responsible for loading the simulation configuration,
- * initializing the {@link SimulationEngine}, and starting the simulation run.
- * It also prints initial configuration details and final execution time.
- */
-public class Entry {
-
- /**
- * The default path to the simulation configuration file.
- * This is used if no command-line arguments are provided.
- */
- private static final String DEFAULT_CONFIG_FILE = "src/main/resources/simulation.properties";
-
- /**
- * The main method to start the simulation.
- * * @param args Command-line arguments. If provided, args[0] is expected
- * to be the path to a custom configuration file.
- */
- public static void main(String[] args) {
- System.out.println("=".repeat(60));
- System.out.println("TRAFFIC SIMULATION - DISCRETE EVENT SIMULATOR");
- System.out.println("=".repeat(60));
-
- try {
- // 1. Load configuration
- String configFile = args.length > 0 ? args[0] : DEFAULT_CONFIG_FILE;
- System.out.println("Loading configuration from: " + configFile);
-
- SimulationConfig config = new SimulationConfig(configFile);
-
- // 2. Display configuration
- displayConfiguration(config);
-
- // 3. Create and initialize simulation engine
- SimulationEngine engine = new SimulationEngine(config);
- engine.initialize();
-
- System.out.println("\n" + "=".repeat(60));
-
- // 4. Run simulation
- long startTime = System.currentTimeMillis();
- engine.run();
- long endTime = System.currentTimeMillis();
-
- // 5. Display execution time
- double executionTime = (endTime - startTime) / 1000.0;
- System.out.println("\nExecution time: " + String.format("%.2f", executionTime) + " seconds");
- System.out.println("=".repeat(60));
-
- } catch (IOException e) {
- System.err.println("Error loading configuration: " + e.getMessage());
- e.printStackTrace();
- } catch (Exception e) {
- System.err.println("Error during simulation: " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- /**
- * Displays the main configuration parameters to the console.
- * This provides a summary of the simulation settings before it starts.
- *
- * @param config The {@link SimulationConfig} object containing the loaded settings.
- */
- private static void displayConfiguration(SimulationConfig config) {
- System.out.println("\nSIMULATION CONFIGURATION:");
- System.out.println(" Duration: " + config.getSimulationDuration() + " seconds");
- System.out.println(" Arrival Model: " + config.getArrivalModel());
-
- if ("POISSON".equalsIgnoreCase(config.getArrivalModel())) {
- System.out.println(" Arrival Rate (λ): " + config.getArrivalRate() + " vehicles/second");
- } else {
- System.out.println(" Fixed Interval: " + config.getFixedArrivalInterval() + " seconds");
- }
-
- System.out.println(" Statistics Update Interval: " + config.getStatisticsUpdateInterval() + " seconds");
-
- System.out.println("\nVEHICLE TYPES:");
- System.out.println(" Bike: " + (config.getBikeVehicleProbability() * 100) + "% " +
- "(crossing time: " + config.getBikeVehicleCrossingTime() + "s)");
- System.out.println(" Light: " + (config.getLightVehicleProbability() * 100) + "% " +
- "(crossing time: " + config.getLightVehicleCrossingTime() + "s)");
- System.out.println(" Heavy: " + (config.getHeavyVehicleProbability() * 100) + "% " +
- "(crossing time: " + config.getHeavyVehicleCrossingTime() + "s)");
- }
-}
\ No newline at end of file
diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java
index c2a4f2d..44b1b88 100644
--- a/main/src/main/java/sd/ExitNodeProcess.java
+++ b/main/src/main/java/sd/ExitNodeProcess.java
@@ -11,18 +11,20 @@ import java.util.concurrent.TimeUnit;
import sd.config.SimulationConfig;
import sd.coordinator.SocketClient;
+import sd.dashboard.StatsUpdatePayload;
import sd.model.Message;
import sd.model.MessageType;
import sd.model.Vehicle;
import sd.model.VehicleType;
import sd.protocol.MessageProtocol;
import sd.protocol.SocketConnection;
-import sd.serialization.SerializationException;
/**
- * Processo responsável pelo nó de saída do sistema de simulação de tráfego distribuído.
+ * Processo responsável pelo nó de saída do sistema de simulação de tráfego
+ * distribuído.
*
- * Este processo representa o ponto final ("S") onde os veículos completam as suas rotas.
+ * Este processo representa o ponto final ("S") onde os veículos completam as
+ * suas rotas.
* As suas principais responsabilidades são:
* - Receber veículos que terminam a sua rota vindos das interseções
* - Calcular e agregar estatísticas finais dos veículos
@@ -34,31 +36,37 @@ public class ExitNodeProcess {
private final SimulationConfig config;
private ServerSocket serverSocket;
private final ExecutorService connectionHandlerPool;
-
- /** Flag para controlar a execução do processo (volatile para visibilidade entre threads) */
+
+ /**
+ * Flag para controlar a execução do processo (volatile para visibilidade entre
+ * threads)
+ */
private volatile boolean running;
-
+
+ /** Simulation start time (milliseconds) to calculate relative times */
+ private long simulationStartMillis;
+
/** Counter de veículos que completaram a rota */
private int totalVehiclesReceived;
-
+
/** Soma dos tempos no sistema de todos os veículos */
private double totalSystemTime;
-
+
/** Soma dos tempos de espera de todos os veículos */
private double totalWaitingTime;
-
+
/** Soma dos tempos de travessia de todos os veículos */
private double totalCrossingTime;
-
+
/** Contagem de veículos por tipo */
private final Map vehicleTypeCount;
-
+
/** Tempo total de espera acumulado por tipo de veículo */
private final Map vehicleTypeWaitTime;
-
+
/** Socket para comunicação com o dashboard */
private SocketClient dashboardClient;
-
+
/**
* Método para iniciar o processo
*
@@ -69,20 +77,20 @@ public class ExitNodeProcess {
System.out.println("=".repeat(60));
System.out.println("EXIT NODE PROCESS");
System.out.println("=".repeat(60));
-
+
try {
String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties";
System.out.println("Loading configuration from: " + configFile);
-
+
SimulationConfig config = new SimulationConfig(configFile);
ExitNodeProcess exitNode = new ExitNodeProcess(config);
-
+
System.out.println("\n" + "=".repeat(60));
exitNode.initialize();
-
+
System.out.println("\n" + "=".repeat(60));
exitNode.start();
-
+
} catch (IOException e) {
System.err.println("Failed to start exit node: " + e.getMessage());
System.exit(1);
@@ -91,38 +99,40 @@ public class ExitNodeProcess {
System.exit(1);
}
}
-
+
/**
* Constrói um novo processo de nó de saída.
*
- * Inicializa todas as estruturas de dados necessárias para recolher estatísticas
+ * Inicializa todas as estruturas de dados necessárias para recolher
+ * estatísticas
* e configura o pool de threads para processar as ligações concorrentes.
*
- * @param config Configuração da simulação contendo portas e endereços dos serviços
+ * @param config Configuração da simulação contendo portas e endereços dos
+ * serviços
*/
public ExitNodeProcess(SimulationConfig config) {
this.config = config;
this.connectionHandlerPool = Executors.newCachedThreadPool();
this.running = false;
-
+
this.totalVehiclesReceived = 0;
this.totalSystemTime = 0.0;
this.totalWaitingTime = 0.0;
this.totalCrossingTime = 0.0;
this.vehicleTypeCount = new HashMap<>();
this.vehicleTypeWaitTime = new HashMap<>();
-
+
// Inicializa os counters para cada tipo de veículo
for (VehicleType type : VehicleType.values()) {
vehicleTypeCount.put(type, 0);
vehicleTypeWaitTime.put(type, 0.0);
}
-
+
System.out.println("Exit node initialized");
System.out.println(" - Exit port: " + config.getExitPort());
System.out.println(" - Dashboard: " + config.getDashboardHost() + ":" + config.getDashboardPort());
}
-
+
/**
* Inicializa o processo de ligação ao dashboard.
*
@@ -132,21 +142,21 @@ public class ExitNodeProcess {
*/
public void initialize() {
System.out.println("Connecting to dashboard...");
-
+
try {
String host = config.getDashboardHost();
int port = config.getDashboardPort();
-
+
dashboardClient = new SocketClient("Dashboard", host, port);
dashboardClient.connect();
-
+
System.out.println("Successfully connected to dashboard");
} catch (IOException e) {
System.err.println("WARNING: Failed to connect to dashboard: " + e.getMessage());
System.err.println("Exit node will continue without dashboard connection");
}
}
-
+
/**
* Inicia o socket e começa a aceitar ligações.
*
@@ -155,16 +165,18 @@ public class ExitNodeProcess {
* 2. Aguarda pelas ligações das interseções
* 3. Delega cada ligação a uma thread da pool para processamento assíncrono
*
- * @throws IOException Se o socket não puder ser criado ou houver erro na aceitação
+ * @throws IOException Se o socket não puder ser criado ou houver erro na
+ * aceitação
*/
public void start() throws IOException {
int port = config.getExitPort();
serverSocket = new ServerSocket(port);
running = true;
-
+ simulationStartMillis = System.currentTimeMillis();
+
System.out.println("Exit node started on port " + port);
- System.out.println("Waiting for vehicles...\n");
-
+ System.out.println("Waiting for vehicles...\\n");
+
while (running) {
try {
Socket clientSocket = serverSocket.accept();
@@ -176,42 +188,69 @@ public class ExitNodeProcess {
}
}
}
-
+
/**
* Processa uma ligação recebida de uma interseção.
*
* Mantém a ligação aberta e processa continuamente mensagens do tipo
- * VEHICLE_TRANSFER. Cada mensagem representa um veículo que chegou ao nó de saída.
+ * VEHICLE_TRANSFER. Cada mensagem representa um veículo que chegou ao nó de
+ * saída.
*
* @param clientSocket Socket da ligação estabelecida com a interseção
*/
private void handleIncomingConnection(Socket clientSocket) {
+ String clientAddress = clientSocket.getInetAddress().getHostAddress();
+ System.out.println("New connection accepted from " + clientAddress);
+
try (SocketConnection connection = new SocketConnection(clientSocket)) {
-
- System.out.println("New connection accepted from " +
- clientSocket.getInetAddress().getHostAddress());
-
+
while (running && connection.isConnected()) {
try {
+ System.out.println("[Exit] Waiting for message from " + clientAddress);
MessageProtocol message = connection.receiveMessage();
-
- if (message.getType() == MessageType.VEHICLE_TRANSFER) {
- Vehicle vehicle = (Vehicle) message.getPayload();
+ System.out.println("[Exit] Received message type: " + message.getType() +
+ " from " + message.getSourceNode());
+
+ if (message.getType() == MessageType.SIMULATION_START) {
+ // Coordinator sends start time - use it instead of our local start
+ simulationStartMillis = ((Number) message.getPayload()).longValue();
+ System.out.println("[Exit] Simulation start time synchronized");
+ } else if (message.getType() == MessageType.VEHICLE_TRANSFER) {
+ Object payload = message.getPayload();
+ System.out.println("[Exit] Payload type: " + payload.getClass().getName());
+
+ // Handle Gson LinkedHashMap
+ Vehicle vehicle;
+ if (payload instanceof com.google.gson.internal.LinkedTreeMap ||
+ payload instanceof java.util.LinkedHashMap) {
+ String json = new com.google.gson.Gson().toJson(payload);
+ vehicle = new com.google.gson.Gson().fromJson(json, Vehicle.class);
+ } else {
+ vehicle = (Vehicle) payload;
+ }
+
processExitingVehicle(vehicle);
}
-
+
} catch (ClassNotFoundException e) {
- System.err.println("Unknown message type received: " + e.getMessage());
+ System.err.println("[Exit] Unknown message type: " + e.getMessage());
+ e.printStackTrace();
+ } catch (Exception e) {
+ System.err.println("[Exit] Error processing message: " + e.getMessage());
+ e.printStackTrace();
}
}
-
+
+ System.out.println("[Exit] Connection closed from " + clientAddress);
+
} catch (IOException e) {
if (running) {
- System.err.println("Connection error: " + e.getMessage());
+ System.err.println("[Exit] Connection error from " + clientAddress + ": " + e.getMessage());
+ e.printStackTrace();
}
}
}
-
+
/**
* Processa um veículo que chegou ao nó de saída.
*
@@ -225,39 +264,30 @@ public class ExitNodeProcess {
*/
private synchronized void processExitingVehicle(Vehicle vehicle) {
totalVehiclesReceived++;
-
- double systemTime = vehicle.getTotalTravelTime(getCurrentTime());
+
+ // Calculate relative simulation time (seconds since simulation start)
+ double currentSimTime = (System.currentTimeMillis() - simulationStartMillis) / 1000.0;
+ // System time = time vehicle spent in system (current time - entry time)
+ double systemTime = currentSimTime - vehicle.getEntryTime();
double waitTime = vehicle.getTotalWaitingTime();
double crossingTime = vehicle.getTotalCrossingTime();
-
+
+ // Store times in seconds, will be converted to ms when sending to dashboard
totalSystemTime += systemTime;
totalWaitingTime += waitTime;
totalCrossingTime += crossingTime;
-
+
VehicleType type = vehicle.getType();
vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1);
vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime);
-
- System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs)%n",
- vehicle.getId(), vehicle.getType(), systemTime, waitTime);
-
- if (totalVehiclesReceived % 10 == 0) {
- sendStatsToDashboard();
- }
+
+ System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n",
+ vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime);
+
+ // Send stats after every vehicle to ensure dashboard updates quickly
+ sendStatsToDashboard();
}
-
- /**
- * Obtém o tempo atual da simulação em segundos.
- *
- * @return Tempo atual em segundos desde "epoch"
- *
- * "Epoch" é um ponto de referência temporal Unix (1 de janeiro de 1970).
- * Este método retorna os segundos decorridos desde esse momento.
- */
- private double getCurrentTime() {
- return System.currentTimeMillis() / 1000.0;
- }
-
+
/**
* Envia as estatísticas para o dashboard.
*
@@ -271,55 +301,69 @@ public class ExitNodeProcess {
if (dashboardClient == null || !dashboardClient.isConnected()) {
return;
}
-
+
try {
- Map stats = new HashMap<>();
- stats.put("totalVehicles", totalVehiclesReceived);
- stats.put("avgSystemTime", totalVehiclesReceived > 0 ? totalSystemTime / totalVehiclesReceived : 0.0);
- stats.put("avgWaitingTime", totalVehiclesReceived > 0 ? totalWaitingTime / totalVehiclesReceived : 0.0);
- stats.put("avgCrossingTime", totalVehiclesReceived > 0 ? totalCrossingTime / totalVehiclesReceived : 0.0);
-
- Map typeCounts = new HashMap<>();
- Map typeAvgWait = new HashMap<>();
+ // Create stats payload
+ StatsUpdatePayload payload = new StatsUpdatePayload();
+
+ // Set global stats - convert seconds to milliseconds
+ payload.setTotalVehiclesCompleted(totalVehiclesReceived);
+ payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); // s -> ms
+ payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); // s -> ms
+
+ // Set intersection-like stats so it shows up correctly in the dashboard table
+ payload.setIntersectionArrivals(totalVehiclesReceived);
+ payload.setIntersectionDepartures(totalVehiclesReceived);
+ payload.setIntersectionQueueSize(0);
+
+ // Set vehicle type stats
+ Map typeCounts = new HashMap<>();
+ Map typeWaitTimes = new HashMap<>();
+
for (VehicleType type : VehicleType.values()) {
- int count = vehicleTypeCount.get(type);
- typeCounts.put(type.name(), count);
- if (count > 0) {
- typeAvgWait.put(type.name(), vehicleTypeWaitTime.get(type) / count);
- }
+ typeCounts.put(type, vehicleTypeCount.get(type));
+ typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms
}
- stats.put("vehicleTypeCounts", typeCounts);
- stats.put("vehicleTypeAvgWait", typeAvgWait);
-
- Message message = new Message(MessageType.STATS_UPDATE, "ExitNode", "Dashboard", stats);
+
+ payload.setVehicleTypeCounts(typeCounts);
+ payload.setVehicleTypeWaitTimes(typeWaitTimes);
+
+ // Send message
+ Message message = new Message(
+ MessageType.STATS_UPDATE,
+ "ExitNode",
+ "Dashboard",
+ payload);
+
dashboardClient.send(message);
-
+
+ double avgWait = totalVehiclesReceived > 0 ? totalWaitingTime / totalVehiclesReceived : 0.0;
System.out.printf("[Exit] Sent stats to dashboard (total=%d, avg_wait=%.2fs)%n",
- totalVehiclesReceived, totalWaitingTime / totalVehiclesReceived);
-
- } catch (SerializationException | IOException e) {
- System.err.println("Failed to send stats to dashboard: " + e.getMessage());
+ totalVehiclesReceived, avgWait);
+
+ } catch (Exception e) {
+ System.err.println("[Exit] Failed to send stats to dashboard: " + e.getMessage());
}
}
-
+
/**
* Termina o processo
*
* Executa a seguinte sequência:
- * Imprime as estatísticas finais no terminal;
- * Envia a última atualização de estatísticas ao dashboard;
- * Fecha o socket;
- * Aguarda pela finalização das threads;
- * Fecha a ligação com o dashboard;
+ * Imprime as estatísticas finais no terminal;
+ * Envia a última atualização de estatísticas ao dashboard;
+ * Fecha o socket;
+ * Aguarda pela finalização das threads;
+ * Fecha a ligação com o dashboard;
*/
public void shutdown() {
System.out.println("\n[Exit] Shutting down...");
running = false;
-
+
printFinalStatistics();
-
+
sendStatsToDashboard();
-
+
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
@@ -327,7 +371,7 @@ public class ExitNodeProcess {
} catch (IOException e) {
System.err.println("Error closing server socket: " + e.getMessage());
}
-
+
connectionHandlerPool.shutdown();
try {
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) {
@@ -336,15 +380,15 @@ public class ExitNodeProcess {
} catch (InterruptedException e) {
connectionHandlerPool.shutdownNow();
}
-
+
if (dashboardClient != null) {
dashboardClient.close();
}
-
+
System.out.println("[Exit] Shutdown complete.");
System.out.println("=".repeat(60));
}
-
+
/**
* Imprime as estatísticas finais detalhadas no terminal
*
@@ -359,14 +403,14 @@ public class ExitNodeProcess {
private void printFinalStatistics() {
System.out.println("\n=== EXIT NODE STATISTICS ===");
System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesReceived);
-
+
if (totalVehiclesReceived > 0) {
System.out.printf("%nAVERAGE METRICS:%n");
System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesReceived);
System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesReceived);
System.out.printf(" Crossing Time: %.2f seconds%n", totalCrossingTime / totalVehiclesReceived);
}
-
+
System.out.println("\nVEHICLE TYPE DISTRIBUTION:");
for (VehicleType type : VehicleType.values()) {
int count = vehicleTypeCount.get(type);
@@ -374,9 +418,9 @@ public class ExitNodeProcess {
double percentage = (count * 100.0) / totalVehiclesReceived;
double avgWait = vehicleTypeWaitTime.get(type) / count;
System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n",
- type, count, percentage, avgWait);
+ type, count, percentage, avgWait);
}
}
}
-
+
}
diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java
index 57c658f..bdcb74f 100644
--- a/main/src/main/java/sd/IntersectionProcess.java
+++ b/main/src/main/java/sd/IntersectionProcess.java
@@ -4,21 +4,27 @@ import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import sd.config.SimulationConfig;
+import sd.coordinator.SocketClient;
+import sd.dashboard.StatsUpdatePayload;
import sd.engine.TrafficLightThread;
import sd.model.Intersection;
+import sd.model.Message;
import sd.model.MessageType;
import sd.model.TrafficLight;
import sd.model.Vehicle;
import sd.protocol.MessageProtocol;
import sd.protocol.SocketConnection;
+import sd.serialization.SerializationException;
/**
* Main class for an Intersection Process in the distributed traffic simulation.
@@ -42,6 +48,8 @@ public class IntersectionProcess {
private final ExecutorService trafficLightPool;
+ private ScheduledExecutorService statsExecutor;
+
private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras
// threads veem a mudança imediatamente.
@@ -59,6 +67,10 @@ public class IntersectionProcess {
*/
private volatile String currentGreenDirection;
+ private SocketClient dashboardClient;
+ private volatile int totalArrivals = 0;
+ private volatile int totalDepartures = 0;
+
/**
* Constructs a new IntersectionProcess.
*
@@ -73,8 +85,9 @@ public class IntersectionProcess {
this.outgoingConnections = new HashMap<>();
this.connectionHandlerPool = Executors.newCachedThreadPool();
this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions
+ this.statsExecutor = Executors.newSingleThreadScheduledExecutor();
this.running = false;
- this.trafficCoordinationLock = new ReentrantLock();
+ this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation
this.currentGreenDirection = null;
System.out.println("=".repeat(60));
@@ -82,6 +95,35 @@ public class IntersectionProcess {
System.out.println("=".repeat(60));
}
+ // Main entry point for running an intersection process
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Usage: java IntersectionProcess [configFile]");
+ System.err.println("Example: java IntersectionProcess Cr1");
+ System.exit(1);
+ }
+
+ String intersectionId = args[0];
+ String configFile = args.length > 1 ? args[1] : "src/main/resources/simulation.properties";
+
+ try {
+ IntersectionProcess process = new IntersectionProcess(intersectionId, configFile);
+ process.initialize();
+ process.start();
+
+ // Add shutdown hook
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ System.out.println("\nShutdown signal received...");
+ process.shutdown();
+ }));
+
+ } catch (IOException e) {
+ System.err.println("Failed to start intersection process: " + e.getMessage());
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
public void initialize() {
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
@@ -89,9 +131,35 @@ public class IntersectionProcess {
configureRouting();
+ connectToDashboard();
+
System.out.println("[" + intersectionId + "] Initialization complete.");
}
+ /**
+ * Establishes connection to the dashboard server for statistics reporting.
+ */
+ private void connectToDashboard() {
+ try {
+ String dashboardHost = config.getDashboardHost();
+ int dashboardPort = config.getDashboardPort();
+
+ System.out.println("[" + intersectionId + "] Connecting to dashboard at " +
+ dashboardHost + ":" + dashboardPort + "...");
+
+ dashboardClient = new SocketClient(intersectionId, dashboardHost, dashboardPort);
+ dashboardClient.connect();
+
+ System.out.println("[" + intersectionId + "] Connected to dashboard.");
+
+ } catch (IOException e) {
+ System.err.println("[" + intersectionId + "] Failed to connect to dashboard: " +
+ e.getMessage());
+ System.err.println("[" + intersectionId + "] Will continue without dashboard reporting.");
+ dashboardClient = null;
+ }
+ }
+
/**
* Creates traffic lights for this intersection based on its physical
* connections.
@@ -101,23 +169,12 @@ public class IntersectionProcess {
private void createTrafficLights() {
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
- String[] directions = new String[0];
- switch (intersectionId) {
- case "Cr1":
- directions = new String[] { "East", "South" };
- break;
- case "Cr2":
- directions = new String[] { "West", "East", "South" };
- break;
- case "Cr3":
- directions = new String[] { "West", "South" };
- break;
- case "Cr4":
- directions = new String[] { "East" };
- break;
- case "Cr5":
- directions = new String[] { "East" };
- break;
+ SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig();
+ List directions = intersectionConfig.getLights();
+
+ if (directions == null || directions.isEmpty()) {
+ System.err.println(" Warning: No traffic lights configured for " + intersectionId);
+ return;
}
for (String direction : directions) {
@@ -136,36 +193,31 @@ public class IntersectionProcess {
}
}
+ private SimulationConfig.IntersectionConfig getIntersectionConfig() {
+ if (config.getNetworkConfig() == null || config.getNetworkConfig().getIntersections() == null) {
+ throw new RuntimeException("Network configuration not loaded or empty.");
+ }
+ return config.getNetworkConfig().getIntersections().stream()
+ .filter(i -> i.getId().equals(intersectionId))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("Intersection config not found for " + intersectionId));
+ }
+
private void configureRouting() {
System.out.println("\n[" + intersectionId + "] Configuring routing...");
- switch (intersectionId) {
- case "Cr1":
- intersection.configureRoute("Cr2", "East");
- intersection.configureRoute("Cr4", "South");
- break;
+ SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig();
+ Map routes = intersectionConfig.getRoutes();
- case "Cr2":
- intersection.configureRoute("Cr1", "West");
- intersection.configureRoute("Cr3", "East");
- intersection.configureRoute("Cr5", "South");
- break;
-
- case "Cr3":
- intersection.configureRoute("Cr2", "West");
- intersection.configureRoute("S", "South");
- break;
-
- case "Cr4":
- intersection.configureRoute("Cr5", "East");
- break;
-
- case "Cr5":
- intersection.configureRoute("S", "East");
- break;
-
- default:
- System.err.println(" Error: unknown intersection ID: " + intersectionId);
+ if (routes != null) {
+ for (Map.Entry entry : routes.entrySet()) {
+ String destination = entry.getKey();
+ String direction = entry.getValue();
+ intersection.configureRoute(destination, direction);
+ System.out.println(" Route configured: To " + destination + " -> Use " + direction);
+ }
+ } else {
+ System.out.println(" No routes configured.");
}
System.out.println(" Routing configured.");
@@ -181,7 +233,7 @@ public class IntersectionProcess {
trafficCoordinationLock.lock();
currentGreenDirection = direction;
}
-
+
/**
* Releases the green light permission, allowing another light to turn green.
*
@@ -193,7 +245,7 @@ public class IntersectionProcess {
trafficCoordinationLock.unlock();
}
}
-
+
/**
* Starts all traffic light threads.
*/
@@ -222,17 +274,22 @@ public class IntersectionProcess {
// Get or create connection to next destination
SocketConnection connection = getOrCreateConnection(nextDestination);
- // Create and send message
- MessageProtocol message = new VehicleTransferMessage(
+ // Create and send message using Message class
+ MessageProtocol message = new Message(
+ MessageType.VEHICLE_TRANSFER,
intersectionId,
nextDestination,
- vehicle);
+ vehicle,
+ System.currentTimeMillis());
connection.sendMessage(message);
System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
" to " + nextDestination);
+ // Record departure for statistics
+ recordVehicleDeparture();
+
// Note: vehicle route is advanced when it arrives at the next intersection
} catch (IOException | InterruptedException e) {
@@ -310,6 +367,9 @@ public class IntersectionProcess {
// Start traffic light threads when running is true
startTrafficLights();
+ // Start stats updater
+ statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS);
+
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
// Main accept loop
@@ -374,14 +434,47 @@ public class IntersectionProcess {
try {
MessageProtocol message = connection.receiveMessage();
- if (message.getType() == MessageType.VEHICLE_TRANSFER) {
- Vehicle vehicle = (Vehicle) message.getPayload();
+ // Handle simulation start time synchronization
+ if (message.getType() == MessageType.SIMULATION_START) {
+ System.out.println("[" + intersectionId + "] Simulation start time synchronized");
+ continue;
+ }
+
+ // Accept both VEHICLE_TRANSFER and VEHICLE_SPAWN (from coordinator)
+ if (message.getType() == MessageType.VEHICLE_TRANSFER ||
+ message.getType() == MessageType.VEHICLE_SPAWN) {
+ // Cast payload to Vehicle - handle Gson deserialization
+ Vehicle vehicle;
+ Object payload = message.getPayload();
+ if (payload instanceof Vehicle) {
+ vehicle = (Vehicle) payload;
+ } else if (payload instanceof java.util.Map) {
+ // Gson deserialized as LinkedHashMap - re-serialize and deserialize as Vehicle
+ com.google.gson.Gson gson = new com.google.gson.Gson();
+ String json = gson.toJson(payload);
+ vehicle = gson.fromJson(json, Vehicle.class);
+ } else {
+ System.err.println("[" + intersectionId + "] Unknown payload type: " + payload.getClass());
+ continue;
+ }
System.out.println("[" + intersectionId + "] Received vehicle: " +
vehicle.getId() + " from " + message.getSourceNode());
+ // Advance vehicle to next destination in its route
+ vehicle.advanceRoute();
+
// Add vehicle to appropriate queue
intersection.receiveVehicle(vehicle);
+
+ // Record arrival for statistics
+ recordVehicleArrival();
+ } else if (message.getType() == MessageType.SHUTDOWN) {
+ System.out.println(
+ "[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode());
+ running = false;
+ // Close this specific connection
+ break;
}
} catch (java.net.SocketTimeoutException e) {
@@ -394,6 +487,13 @@ public class IntersectionProcess {
System.err.println("[" + intersectionId + "] Unknown message type received: " +
e.getMessage());
break; // Invalid message, close connection
+ } catch (IOException e) {
+ if (running) {
+ System.err.println("[" + intersectionId + "] Failed to deserialize message: " +
+ e.getMessage());
+ e.printStackTrace(); // For debugging - maybe change//remove later
+ }
+ break; // Connection error, close connection
}
}
@@ -418,6 +518,9 @@ public class IntersectionProcess {
System.out.println("\n[" + intersectionId + "] Shutting down...");
running = false;
+ // Send final stats before closing connections
+ sendStatsToDashboard();
+
// 1. Close ServerSocket first
if (serverSocket != null && !serverSocket.isClosed()) {
try {
@@ -434,6 +537,9 @@ public class IntersectionProcess {
if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) {
connectionHandlerPool.shutdownNow();
}
+ if (statsExecutor != null && !statsExecutor.isShutdown()) {
+ statsExecutor.shutdownNow();
+ }
// 3. Wait briefly for termination (don't block forever)
try {
@@ -443,6 +549,9 @@ public class IntersectionProcess {
if (connectionHandlerPool != null) {
connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS);
}
+ if (statsExecutor != null) {
+ statsExecutor.awaitTermination(1, TimeUnit.SECONDS);
+ }
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -459,6 +568,11 @@ public class IntersectionProcess {
outgoingConnections.clear();
}
+ // 5. Close dashboard connection
+ if (dashboardClient != null) {
+ dashboardClient.close();
+ }
+
System.out.println("[" + intersectionId + "] Shutdown complete.");
System.out.println("============================================================\n");
}
@@ -473,42 +587,53 @@ public class IntersectionProcess {
return intersection;
}
- // --- Inner class for Vehicle Transfer Messages ---
+ /**
+ * Records that a vehicle has arrived at this intersection.
+ */
+ public void recordVehicleArrival() {
+ totalArrivals++;
+ }
/**
- * Implementation of MessageProtocol for vehicle transfers between processes.
+ * Records that a vehicle has departed from this intersection.
*/
- private static class VehicleTransferMessage implements MessageProtocol {
- private static final long serialVersionUID = 1L;
+ public void recordVehicleDeparture() {
+ totalDepartures++;
+ }
- private final String sourceNode;
- private final String destinationNode;
- private final Vehicle payload;
-
- public VehicleTransferMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
- this.sourceNode = sourceNode;
- this.destinationNode = destinationNode;
- this.payload = vehicle;
+ /**
+ * Sends current statistics to the dashboard server.
+ */
+ private void sendStatsToDashboard() {
+ if (dashboardClient == null || !dashboardClient.isConnected()) {
+ return;
}
- @Override
- public MessageType getType() {
- return MessageType.VEHICLE_TRANSFER;
- }
+ try {
+ // Calculate current queue size
+ int currentQueueSize = intersection.getTrafficLights().stream()
+ .mapToInt(TrafficLight::getQueueSize)
+ .sum();
- @Override
- public Object getPayload() {
- return payload;
- }
+ StatsUpdatePayload payload = new StatsUpdatePayload()
+ .setIntersectionArrivals(totalArrivals)
+ .setIntersectionDepartures(totalDepartures)
+ .setIntersectionQueueSize(currentQueueSize);
- @Override
- public String getSourceNode() {
- return sourceNode;
- }
+ // Send StatsUpdatePayload directly as the message payload
+ sd.model.Message message = new sd.model.Message(
+ MessageType.STATS_UPDATE,
+ intersectionId,
+ "Dashboard",
+ payload);
- @Override
- public String getDestinationNode() {
- return destinationNode;
+ dashboardClient.send(message);
+
+ System.out.printf("[%s] Sent stats to dashboard (arrivals=%d, departures=%d, queue=%d)%n",
+ intersectionId, totalArrivals, totalDepartures, currentQueueSize);
+
+ } catch (SerializationException | IOException e) {
+ System.err.println("[" + intersectionId + "] Failed to send stats to dashboard: " + e.getMessage());
}
}
}
diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java
index 4c3d599..b974495 100644
--- a/main/src/main/java/sd/config/SimulationConfig.java
+++ b/main/src/main/java/sd/config/SimulationConfig.java
@@ -3,8 +3,16 @@ package sd.config;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
+import com.google.gson.Gson;
+
/**
* Class to load and manage simulation configurations.
* Configurations are read from a .properties file. This class provides
@@ -12,64 +20,149 @@ import java.util.Properties;
* with default values to ensure robustness.
*/
public class SimulationConfig {
-
+
/**
* Holds all properties loaded from the file.
*/
private final Properties properties;
+ private NetworkConfig networkConfig;
+
+ public static class NetworkConfig {
+ private List intersections;
+
+ public List getIntersections() {
+ return intersections;
+ }
+ }
+
+ public static class IntersectionConfig {
+ private String id;
+ private List lights;
+ private Map routes;
+
+ public String getId() {
+ return id;
+ }
+
+ public List getLights() {
+ return lights;
+ }
+
+ public Map getRoutes() {
+ return routes;
+ }
+ }
/**
* Constructs a new SimulationConfig object by loading properties
* from the specified file path.
+ *
+ * This constructor attempts to load the configuration file using multiple
+ * strategies:
+ * 1. Direct file system path
+ * 2. Classpath resource (with automatic path normalization)
+ * 3. Classpath resource with leading slash
*
- * @param filePath The path to the .properties file (e.g., "src/main/resources/simulation.properties").
- * @throws IOException If the file cannot be found or read.
+ * @param filePath The path to the .properties file (e.g.,
+ * "src/main/resources/simulation.properties").
+ * @throws IOException If the file cannot be found or read from any location.
*/
public SimulationConfig(String filePath) throws IOException {
properties = new Properties();
- /**Tenta carregar diretamente a partir do sistema de ficheiros, se o ficheiro não existir
- * (por exemplo quando executado a partir do classpath/jar),
- * faz fallback para carregar a partir do classpath usando o ClassLoader.
- */
- IOException lastException = null; //FIXME: melhorar esta parte para reportar erros de forma mais clara
- try {
- try (InputStream input = new FileInputStream(filePath)) {
- properties.load(input);
- return; // carregado com sucesso a partir do caminho fornecido
- }
+ // List to track all attempted paths for better error reporting
+ List attemptedPaths = new ArrayList<>();
+ IOException fileSystemException = null;
+
+ // Strategy 1: Try to load directly from file system
+ try (InputStream input = new FileInputStream(filePath)) {
+ properties.load(input);
+ loadNetworkConfig();
+ return; // Successfully loaded from file system
} catch (IOException e) {
- lastException = e;
- //tenta carregar a partir do classpath sem prefixos comuns
- String resourcePath = filePath;
- //Remove prefixos que apontam para src/main/resources quando presentes
- resourcePath = resourcePath.replace("src/main/resources/", "").replace("src\\main\\resources\\", "");
- //Remove prefixo classpath: se fornecido
- if (resourcePath.startsWith("classpath:")) {
- resourcePath = resourcePath.substring("classpath:".length());
- if (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1);
- }
+ fileSystemException = e;
+ attemptedPaths.add("File system: " + filePath);
+ }
- InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath);
- if (resourceStream == null) {
- //como último recurso, tentar com um leading slash
- resourceStream = SimulationConfig.class.getResourceAsStream('/' + resourcePath);
- }
+ // Strategy 2: Try to load from classpath with path normalization
+ String resourcePath = filePath;
- if (resourceStream != null) {
- try (InputStream input = resourceStream) {
- properties.load(input);
- return;
- }
+ // Remove common src/main/resources prefixes
+ resourcePath = resourcePath.replace("src/main/resources/", "").replace("src\\main\\resources\\", "");
+
+ // Remove classpath: prefix if provided
+ if (resourcePath.startsWith("classpath:")) {
+ resourcePath = resourcePath.substring("classpath:".length());
+ if (resourcePath.startsWith("/")) {
+ resourcePath = resourcePath.substring(1);
}
}
- if (lastException != null) throw lastException;
+
+ // Try loading from classpath using thread context class loader
+ InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath);
+ attemptedPaths.add("Classpath (context): " + resourcePath);
+
+ if (resourceStream == null) {
+ // Strategy 3: Try with leading slash
+ String slashPath = "/" + resourcePath;
+ resourceStream = SimulationConfig.class.getResourceAsStream(slashPath);
+ attemptedPaths.add("Classpath (class): " + slashPath);
+ }
+
+ if (resourceStream != null) {
+ try (InputStream input = resourceStream) {
+ properties.load(input);
+ loadNetworkConfig();
+ return; // Successfully loaded from classpath
+ } catch (IOException e) {
+ // Failed to read from classpath resource
+ throw new IOException(
+ String.format("Failed to read properties from classpath resource '%s': %s",
+ resourcePath, e.getMessage()),
+ e);
+ }
+ }
+
+ // All strategies failed - provide comprehensive error message
+ StringBuilder errorMsg = new StringBuilder();
+ errorMsg.append("Configuration file '").append(filePath).append("' could not be found.\n");
+ errorMsg.append("Attempted locations:\n");
+ for (String path : attemptedPaths) {
+ errorMsg.append(" - ").append(path).append("\n");
+ }
+
+ if (fileSystemException != null) {
+ errorMsg.append("\nOriginal error: ").append(fileSystemException.getMessage());
+ }
+
+ throw new IOException(errorMsg.toString(), fileSystemException);
+ }
+
+ private void loadNetworkConfig() {
+ try (InputStream is = getClass().getClassLoader().getResourceAsStream("network_config.json")) {
+ if (is == null) {
+ System.err.println("Warning: network_config.json not found in classpath. Using defaults/empty.");
+ return;
+ }
+ try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
+ Gson gson = new Gson();
+ this.networkConfig = gson.fromJson(reader, NetworkConfig.class);
+ }
+ } catch (IOException e) {
+ System.err.println("Failed to load network_config.json: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ public NetworkConfig getNetworkConfig() {
+ return networkConfig;
}
// --- Network configurations ---
/**
* Gets the host address for a specific intersection.
+ *
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @return The host (e.g., "localhost").
*/
@@ -79,6 +172,7 @@ public class SimulationConfig {
/**
* Gets the port number for a specific intersection.
+ *
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @return The port number.
*/
@@ -88,6 +182,7 @@ public class SimulationConfig {
/**
* Gets the host address for the dashboard server.
+ *
* @return The dashboard host.
*/
public String getDashboardHost() {
@@ -96,6 +191,7 @@ public class SimulationConfig {
/**
* Gets the port number for the dashboard server.
+ *
* @return The dashboard port.
*/
public int getDashboardPort() {
@@ -104,6 +200,7 @@ public class SimulationConfig {
/**
* Gets the host address for the exit node.
+ *
* @return The exit node host.
*/
public String getExitHost() {
@@ -112,6 +209,7 @@ public class SimulationConfig {
/**
* Gets the port number for the exit node.
+ *
* @return The exit node port.
*/
public int getExitPort() {
@@ -122,14 +220,26 @@ public class SimulationConfig {
/**
* Gets the total duration of the simulation in virtual seconds.
+ *
* @return The simulation duration.
*/
public double getSimulationDuration() {
return Double.parseDouble(properties.getProperty("simulation.duration", "3600.0"));
}
+ /**
+ * Gets the drain time (in virtual seconds) to allow vehicles to exit after
+ * generation stops.
+ *
+ * @return The drain time.
+ */
+ public double getDrainTime() {
+ return Double.parseDouble(properties.getProperty("simulation.drain.time", "60.0"));
+ }
+
/**
* Gets the vehicle arrival model ("POISSON" or "FIXED").
+ *
* @return The arrival model as a string.
*/
public String getArrivalModel() {
@@ -139,6 +249,7 @@ public class SimulationConfig {
/**
* Gets the average arrival rate (lambda) for the POISSON model.
* This represents the average number of vehicles arriving per second.
+ *
* @return The arrival rate.
*/
public double getArrivalRate() {
@@ -147,6 +258,7 @@ public class SimulationConfig {
/**
* Gets the fixed time interval between vehicle arrivals for the FIXED model.
+ *
* @return The fixed interval in seconds.
*/
public double getFixedArrivalInterval() {
@@ -157,8 +269,9 @@ public class SimulationConfig {
/**
* Gets the duration of the GREEN light state for a specific traffic light.
+ *
* @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @param direction The direction of the light (e.g., "North").
+ * @param direction The direction of the light (e.g., "North").
* @return The green light time in seconds.
*/
public double getTrafficLightGreenTime(String intersectionId, String direction) {
@@ -168,8 +281,9 @@ public class SimulationConfig {
/**
* Gets the duration of the RED light state for a specific traffic light.
+ *
* @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @param direction The direction of the light (e.g., "North").
+ * @param direction The direction of the light (e.g., "North").
* @return The red light time in seconds.
*/
public double getTrafficLightRedTime(String intersectionId, String direction) {
@@ -181,6 +295,7 @@ public class SimulationConfig {
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type LIGHT.
+ *
* @return The probability for LIGHT vehicles.
*/
public double getLightVehicleProbability() {
@@ -189,6 +304,7 @@ public class SimulationConfig {
/**
* Gets the average time it takes a LIGHT vehicle to cross an intersection.
+ *
* @return The crossing time in seconds.
*/
public double getLightVehicleCrossingTime() {
@@ -197,6 +313,7 @@ public class SimulationConfig {
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type BIKE.
+ *
* @return The probability for BIKE vehicles.
*/
public double getBikeVehicleProbability() {
@@ -205,6 +322,7 @@ public class SimulationConfig {
/**
* Gets the average time it takes a BIKE vehicle to cross an intersection.
+ *
* @return The crossing time in seconds.
*/
public double getBikeVehicleCrossingTime() {
@@ -213,6 +331,7 @@ public class SimulationConfig {
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type HEAVY.
+ *
* @return The probability for HEAVY vehicles.
*/
public double getHeavyVehicleProbability() {
@@ -221,6 +340,7 @@ public class SimulationConfig {
/**
* Gets the average time it takes a HEAVY vehicle to cross an intersection.
+ *
* @return The crossing time in seconds.
*/
public double getHeavyVehicleCrossingTime() {
@@ -229,6 +349,7 @@ public class SimulationConfig {
/**
* Gets the base travel time between intersections for light vehicles.
+ *
* @return The base travel time in seconds.
*/
public double getBaseTravelTime() {
@@ -238,6 +359,7 @@ public class SimulationConfig {
/**
* Gets the travel time multiplier for bike vehicles.
* Bike travel time = base time × this multiplier.
+ *
* @return The multiplier for bike travel time.
*/
public double getBikeTravelTimeMultiplier() {
@@ -247,6 +369,7 @@ public class SimulationConfig {
/**
* Gets the travel time multiplier for heavy vehicles.
* Heavy vehicle travel time = base time × this multiplier.
+ *
* @return The multiplier for heavy vehicle travel time.
*/
public double getHeavyTravelTimeMultiplier() {
@@ -257,17 +380,19 @@ public class SimulationConfig {
/**
* Gets the interval (in virtual seconds) between periodic statistics updates.
+ *
* @return The statistics update interval.
*/
public double getStatisticsUpdateInterval() {
- return Double.parseDouble(properties.getProperty("statistics.update.interval", "10.0"));
+ return Double.parseDouble(properties.getProperty("statistics.update.interval", "1.0"));
}
// --- Generic getters ---
/**
* Generic method to get any property as a string, with a default value.
- * @param key The property key.
+ *
+ * @param key The property key.
* @param defaultValue The value to return if the key is not found.
* @return The property value or the default.
*/
@@ -277,6 +402,7 @@ public class SimulationConfig {
/**
* Generic method to get any property as a string.
+ *
* @param key The property key.
* @return The property value, or null if not found.
*/
diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java
index 9d25ee0..2fb1423 100644
--- a/main/src/main/java/sd/coordinator/CoordinatorProcess.java
+++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java
@@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.Map;
import sd.config.SimulationConfig;
+import sd.dashboard.StatsUpdatePayload;
import sd.model.Message;
import sd.model.MessageType;
import sd.model.Vehicle;
@@ -20,36 +21,37 @@ import sd.util.VehicleGenerator;
* This is the main entry point for the distributed simulation architecture.
*/
public class CoordinatorProcess {
-
+
private final SimulationConfig config;
private final VehicleGenerator vehicleGenerator;
private final Map intersectionClients;
+ private SocketClient dashboardClient;
private double currentTime;
private int vehicleCounter;
private boolean running;
private double nextGenerationTime;
-
+
public static void main(String[] args) {
System.out.println("=".repeat(60));
System.out.println("COORDINATOR PROCESS - DISTRIBUTED TRAFFIC SIMULATION");
System.out.println("=".repeat(60));
-
+
try {
// 1. Load configuration
String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties";
System.out.println("Loading configuration from: " + configFile);
-
+
SimulationConfig config = new SimulationConfig(configFile);
CoordinatorProcess coordinator = new CoordinatorProcess(config);
-
+
// 2. Connect to intersection processes
System.out.println("\n" + "=".repeat(60));
coordinator.initialize();
-
+
// 3. Run the sim
System.out.println("\n" + "=".repeat(60));
coordinator.run();
-
+
} catch (IOException e) {
System.err.println("Failed to load configuration: " + e.getMessage());
System.exit(1);
@@ -58,7 +60,7 @@ public class CoordinatorProcess {
System.exit(1);
}
}
-
+
public CoordinatorProcess(SimulationConfig config) {
this.config = config;
this.vehicleGenerator = new VehicleGenerator(config);
@@ -67,122 +69,148 @@ public class CoordinatorProcess {
this.vehicleCounter = 0;
this.running = false;
this.nextGenerationTime = 0.0;
-
+
System.out.println("Coordinator initialized with configuration:");
System.out.println(" - Simulation duration: " + config.getSimulationDuration() + "s");
System.out.println(" - Arrival model: " + config.getArrivalModel());
System.out.println(" - Arrival rate: " + config.getArrivalRate() + " vehicles/s");
}
-
+
public void initialize() {
+ // Connect to dashboard first
+ connectToDashboard();
+
System.out.println("Connecting to intersection processes...");
-
- String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"};
-
+
+ String[] intersectionIds = { "Cr1", "Cr2", "Cr3", "Cr4", "Cr5" };
+
for (String intersectionId : intersectionIds) {
try {
String host = config.getIntersectionHost(intersectionId);
int port = config.getIntersectionPort(intersectionId);
-
+
SocketClient client = new SocketClient(intersectionId, host, port);
client.connect();
intersectionClients.put(intersectionId, client);
-
+
} catch (IOException e) {
System.err.println("Failed to connect to " + intersectionId + ": " + e.getMessage());
}
}
-
+
System.out.println("Successfully connected to " + intersectionClients.size() + " intersection(s)");
-
+
if (intersectionClients.isEmpty()) {
System.err.println("WARNING: No intersections connected. Simulation cannot proceed.");
}
}
-
+
public void run() {
double duration = config.getSimulationDuration();
running = true;
-
+
System.out.println("Starting vehicle generation simulation...");
System.out.println("Duration: " + duration + " seconds");
System.out.println();
-
+
+ // Send simulation start time to all processes for synchronization
+ sendSimulationStartTime();
+
nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime);
final double TIME_STEP = 0.1;
-
- while (running && currentTime < duration) {
- if (currentTime >= nextGenerationTime) {
- generateAndSendVehicle();
- nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime);
- }
+
+ double drainTime = config.getDrainTime();
+ double totalDuration = duration + drainTime;
+ boolean draining = false;
+
+ while (running && currentTime < totalDuration) {
+ // Only generate vehicles during the main duration
+ if (currentTime < duration) {
+ if (currentTime >= nextGenerationTime) {
+ generateAndSendVehicle();
+ nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime);
+ }
+ } else if (!draining) {
+ draining = true;
+ System.out.println("\n[t=" + String.format("%.2f", currentTime)
+ + "] Generation complete. Entering DRAIN MODE for " + drainTime + "s...");
+ }
+
+ try {
+ Thread.sleep((long) (TIME_STEP * 1000));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+
currentTime += TIME_STEP;
}
-
+
System.out.println();
System.out.println("Simulation complete at t=" + String.format("%.2f", currentTime) + "s");
System.out.println("Total vehicles generated: " + vehicleCounter);
-
+
shutdown();
}
-
+
private void generateAndSendVehicle() {
Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime);
-
+
System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n",
- currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute());
-
+ currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute());
+
+ // Send generation count to dashboard
+ sendGenerationStatsToDashboard();
+
if (vehicle.getRoute().isEmpty()) {
System.err.println("ERROR: Vehicle " + vehicle.getId() + " has empty route!");
return;
}
-
+
String entryIntersection = vehicle.getRoute().get(0);
sendVehicleToIntersection(vehicle, entryIntersection);
}
-
+
private void sendVehicleToIntersection(Vehicle vehicle, String intersectionId) {
SocketClient client = intersectionClients.get(intersectionId);
-
+
if (client == null || !client.isConnected()) {
System.err.println("ERROR: No connection to " + intersectionId + " for vehicle " + vehicle.getId());
return;
}
-
+
try {
Message message = new Message(
- MessageType.VEHICLE_SPAWN,
- "COORDINATOR",
- intersectionId,
- vehicle
- );
-
+ MessageType.VEHICLE_SPAWN,
+ "COORDINATOR",
+ intersectionId,
+ vehicle);
+
client.send(message);
System.out.printf("->Sent to %s%n", intersectionId);
-
+
} catch (SerializationException | IOException e) {
System.err.println("ERROR: Failed to send vehicle " + vehicle.getId() + " to " + intersectionId);
System.err.println("Reason: " + e.getMessage());
}
}
-
+
public void shutdown() {
System.out.println();
System.out.println("=".repeat(60));
System.out.println("Shutting down coordinator...");
-
+
for (Map.Entry entry : intersectionClients.entrySet()) {
String intersectionId = entry.getKey();
SocketClient client = entry.getValue();
-
+
try {
if (client.isConnected()) {
Message personalizedShutdown = new Message(
- MessageType.SHUTDOWN,
- "COORDINATOR",
- intersectionId,
- "Simulation complete"
- );
+ MessageType.SHUTDOWN,
+ "COORDINATOR",
+ intersectionId,
+ "Simulation complete");
client.send(personalizedShutdown);
System.out.println("Sent shutdown message to " + intersectionId);
}
@@ -192,13 +220,83 @@ public class CoordinatorProcess {
client.close();
}
}
-
+
System.out.println("Coordinator shutdown complete");
System.out.println("=".repeat(60));
}
-
+
public void stop() {
System.out.println("\nStop signal received...");
running = false;
}
+
+ private void connectToDashboard() {
+ try {
+ String host = config.getDashboardHost();
+ int port = config.getDashboardPort();
+
+ System.out.println("Connecting to dashboard at " + host + ":" + port);
+ dashboardClient = new SocketClient("Dashboard", host, port);
+ dashboardClient.connect();
+ System.out.println("Successfully connected to dashboard\n");
+ } catch (IOException e) {
+ System.err.println("WARNING: Failed to connect to dashboard: " + e.getMessage());
+ System.err.println("Coordinator will continue without dashboard connection\n");
+ }
+ }
+
+ private void sendGenerationStatsToDashboard() {
+ if (dashboardClient == null || !dashboardClient.isConnected()) {
+ return;
+ }
+
+ try {
+ // Create stats payload with vehicle generation count
+ StatsUpdatePayload payload = new StatsUpdatePayload();
+ payload.setTotalVehiclesGenerated(vehicleCounter);
+
+ Message message = new Message(
+ MessageType.STATS_UPDATE,
+ "COORDINATOR",
+ "Dashboard",
+ payload);
+
+ dashboardClient.send(message);
+ } catch (Exception e) { // This is fine - can add IOException if need be
+ // Don't crash if dashboard update fails
+ System.err.println("Failed to send stats to dashboard: " + e.getMessage());
+ }
+ }
+
+ private void sendSimulationStartTime() {
+ long startTimeMillis = System.currentTimeMillis();
+
+ // Send to all intersections
+ for (Map.Entry entry : intersectionClients.entrySet()) {
+ try {
+ Message message = new Message(
+ MessageType.SIMULATION_START,
+ "COORDINATOR",
+ entry.getKey(),
+ startTimeMillis);
+ entry.getValue().send(message);
+ } catch (Exception e) { // Same thing here
+ System.err.println("Failed to send start time to " + entry.getKey() + ": " + e.getMessage());
+ }
+ }
+
+ // Send to dashboard
+ if (dashboardClient != null && dashboardClient.isConnected()) {
+ try {
+ Message message = new Message(
+ MessageType.SIMULATION_START,
+ "COORDINATOR",
+ "Dashboard",
+ startTimeMillis);
+ dashboardClient.send(message);
+ } catch (Exception e) { // And here
+ // Don't crash
+ }
+ }
+ }
}
diff --git a/main/src/main/java/sd/dashboard/DashboardClientHandler.java b/main/src/main/java/sd/dashboard/DashboardClientHandler.java
index 11ccb17..68a52db 100644
--- a/main/src/main/java/sd/dashboard/DashboardClientHandler.java
+++ b/main/src/main/java/sd/dashboard/DashboardClientHandler.java
@@ -2,6 +2,7 @@ package sd.dashboard;
import java.io.IOException;
import java.net.Socket;
+import java.util.Map;
import sd.model.MessageType;
import sd.protocol.MessageProtocol;
@@ -71,12 +72,22 @@ public class DashboardClientHandler implements Runnable {
System.out.println("[Handler] Received STATS_UPDATE from: " + senderId);
- if (payload instanceof StatsUpdatePayload stats) {
- updateStatistics(senderId, stats);
+ // Handle both direct StatsUpdatePayload and Gson-deserialized Map
+ StatsUpdatePayload stats;
+ if (payload instanceof StatsUpdatePayload) {
+ stats = (StatsUpdatePayload) payload;
+ } else if (payload instanceof java.util.Map) {
+ // Gson deserialized as LinkedHashMap - re-serialize and deserialize properly
+ com.google.gson.Gson gson = new com.google.gson.Gson();
+ String json = gson.toJson(payload);
+ stats = gson.fromJson(json, StatsUpdatePayload.class);
} else {
System.err.println("[Handler] Unknown payload type: " +
(payload != null ? payload.getClass().getName() : "null"));
+ return;
}
+
+ updateStatistics(senderId, stats);
}
private void updateStatistics(String senderId, StatsUpdatePayload stats) {
@@ -88,14 +99,30 @@ public class DashboardClientHandler implements Runnable {
statistics.updateVehiclesCompleted(stats.getTotalVehiclesCompleted());
}
+ // Exit Node sends cumulative totals, so we SET rather than ADD
if (stats.getTotalSystemTime() >= 0) {
- statistics.addSystemTime(stats.getTotalSystemTime());
+ statistics.setTotalSystemTime(stats.getTotalSystemTime());
}
if (stats.getTotalWaitingTime() >= 0) {
- statistics.addWaitingTime(stats.getTotalWaitingTime());
+ statistics.setTotalWaitingTime(stats.getTotalWaitingTime());
}
+ // Process vehicle type statistics (from Exit Node)
+ if (stats.getVehicleTypeCounts() != null && !stats.getVehicleTypeCounts().isEmpty()) {
+ Map counts = stats.getVehicleTypeCounts();
+ Map waitTimes = stats.getVehicleTypeWaitTimes();
+
+ for (var entry : counts.entrySet()) {
+ sd.model.VehicleType type = entry.getKey();
+ int count = entry.getValue();
+ long waitTime = (waitTimes != null && waitTimes.containsKey(type))
+ ? waitTimes.get(type) : 0L;
+ statistics.updateVehicleTypeStats(type, count, waitTime);
+ }
+ }
+
+ // Process intersection statistics (from Intersection processes)
if (senderId.startsWith("Cr") || senderId.startsWith("E")) {
statistics.updateIntersectionStats(
senderId,
diff --git a/main/src/main/java/sd/dashboard/DashboardServer.java b/main/src/main/java/sd/dashboard/DashboardServer.java
index 9299d0a..cca71b0 100644
--- a/main/src/main/java/sd/dashboard/DashboardServer.java
+++ b/main/src/main/java/sd/dashboard/DashboardServer.java
@@ -22,34 +22,51 @@ public class DashboardServer {
private ServerSocket serverSocket;
public static void main(String[] args) {
- System.out.println("=".repeat(60));
- System.out.println("DASHBOARD SERVER - DISTRIBUTED TRAFFIC SIMULATION");
- System.out.println("=".repeat(60));
+ // Check if GUI mode is requested
+ boolean useGUI = false;
+ String configFile = "src/main/resources/simulation.properties";
- try {
- // Load configuration
- String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties";
- System.out.println("Loading configuration from: " + configFile);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--gui") || args[i].equals("-g")) {
+ useGUI = true;
+ } else {
+ configFile = args[i];
+ }
+ }
+
+ if (useGUI) {
+ // Launch JavaFX UI
+ System.out.println("Launching Dashboard with JavaFX GUI...");
+ DashboardUI.main(args);
+ } else {
+ // Traditional terminal mode
+ System.out.println("=".repeat(60));
+ System.out.println("DASHBOARD SERVER - DISTRIBUTED TRAFFIC SIMULATION");
+ System.out.println("=".repeat(60));
- SimulationConfig config = new SimulationConfig(configFile);
- DashboardServer server = new DashboardServer(config);
-
- // Start the server
- System.out.println("\n" + "=".repeat(60));
- server.start();
-
- // Keep running until interrupted
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- System.out.println("\n\nShutdown signal received...");
- server.stop();
- }));
-
- // Display statistics periodically
- server.displayLoop();
-
- } catch (IOException e) {
- System.err.println("Failed to start Dashboard Server: " + e.getMessage());
- System.exit(1);
+ try {
+ System.out.println("Loading configuration from: " + configFile);
+
+ SimulationConfig config = new SimulationConfig(configFile);
+ DashboardServer server = new DashboardServer(config);
+
+ // Start the server
+ System.out.println("\n" + "=".repeat(60));
+ server.start();
+
+ // Keep running until interrupted
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ System.out.println("\n\nShutdown signal received...");
+ server.stop();
+ }));
+
+ // Display statistics periodically
+ server.displayLoop();
+
+ } catch (IOException e) {
+ System.err.println("Failed to start Dashboard Server: " + e.getMessage());
+ System.exit(1);
+ }
}
}
diff --git a/main/src/main/java/sd/dashboard/DashboardStatistics.java b/main/src/main/java/sd/dashboard/DashboardStatistics.java
index 56f227c..da6f097 100644
--- a/main/src/main/java/sd/dashboard/DashboardStatistics.java
+++ b/main/src/main/java/sd/dashboard/DashboardStatistics.java
@@ -68,11 +68,21 @@ public class DashboardStatistics {
updateTimestamp();
}
+ public void setTotalSystemTime(long timeMs) {
+ totalSystemTime.set(timeMs);
+ updateTimestamp();
+ }
+
public void addWaitingTime(long timeMs) {
totalWaitingTime.addAndGet(timeMs);
updateTimestamp();
}
+ public void setTotalWaitingTime(long timeMs) {
+ totalWaitingTime.set(timeMs);
+ updateTimestamp();
+ }
+
public void updateVehicleTypeStats(VehicleType type, int count, long waitTimeMs) {
vehicleTypeCount.get(type).set(count);
vehicleTypeWaitTime.get(type).set(waitTimeMs);
diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java
new file mode 100644
index 0000000..cb4cfcf
--- /dev/null
+++ b/main/src/main/java/sd/dashboard/DashboardUI.java
@@ -0,0 +1,450 @@
+package sd.dashboard;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.scene.shape.Circle;
+import javafx.stage.Stage;
+import sd.config.SimulationConfig;
+import sd.model.VehicleType;
+
+/**
+ * JavaFX-based Dashboard UI for displaying real-time simulation statistics.
+ * Provides a graphical interface with auto-updating statistics panels.
+ */
+public class DashboardUI extends Application {
+
+ private DashboardServer server;
+ private DashboardStatistics statistics;
+
+ // Global Statistics Labels
+ private Label lblVehiclesGenerated;
+ private Label lblVehiclesCompleted;
+ private Label lblVehiclesInTransit;
+ private Label lblAvgSystemTime;
+ private Label lblAvgWaitingTime;
+ private Label lblLastUpdate;
+
+ // Vehicle Type Table
+ private TableView vehicleTypeTable;
+
+ // Intersection Table
+ private TableView intersectionTable;
+
+ // Update scheduler
+ private ScheduledExecutorService updateScheduler;
+
+ @Override
+ public void start(Stage primaryStage) {
+ try {
+ // Initialize server
+ String configFile = getParameters().getRaw().isEmpty()
+ ? "src/main/resources/simulation.properties"
+ : getParameters().getRaw().get(0);
+
+ SimulationConfig config = new SimulationConfig(configFile);
+ server = new DashboardServer(config);
+ statistics = server.getStatistics();
+
+ // Start the dashboard server
+ server.start();
+
+ // Build UI
+ BorderPane root = new BorderPane();
+ root.getStyleClass().add("root");
+
+ // Header
+ VBox header = createHeader();
+ root.setTop(header);
+
+ // Main content
+ VBox mainContent = createMainContent();
+ root.setCenter(mainContent);
+
+ // Footer
+ HBox footer = createFooter();
+ root.setBottom(footer);
+
+ // Create scene
+ Scene scene = new Scene(root, 1200, 850);
+
+ // Load CSS
+ String cssUrl = getClass().getResource("/dashboard.css").toExternalForm();
+ scene.getStylesheets().add(cssUrl);
+
+ primaryStage.setTitle("Traffic Simulation Dashboard - Real-time Statistics");
+ primaryStage.setScene(scene);
+ primaryStage.show();
+
+ // Start periodic updates
+ startPeriodicUpdates();
+
+ // Handle window close
+ primaryStage.setOnCloseRequest(event -> {
+ shutdown();
+ });
+
+ } catch (Exception e) {
+ showErrorAlert("Failed to start Dashboard Server", e.getMessage());
+ e.printStackTrace();
+ Platform.exit();
+ }
+ }
+
+ private VBox createHeader() {
+ VBox header = new VBox(10);
+ header.getStyleClass().add("header");
+ header.setAlignment(Pos.CENTER);
+
+ Label title = new Label("DISTRIBUTED TRAFFIC SIMULATION DASHBOARD");
+ title.getStyleClass().add("header-title");
+
+ Label subtitle = new Label("Real-time Statistics and Monitoring");
+ subtitle.getStyleClass().add("header-subtitle");
+
+ // Control Buttons
+ HBox controls = new HBox(15);
+ controls.setAlignment(Pos.CENTER);
+
+ Button btnStart = new Button("START SIMULATION");
+ btnStart.getStyleClass().add("button-start");
+
+ Button btnStop = new Button("STOP SIMULATION");
+ btnStop.getStyleClass().add("button-stop");
+ btnStop.setDisable(true);
+
+ SimulationProcessManager processManager = new SimulationProcessManager();
+
+ btnStart.setOnAction(e -> {
+ try {
+ processManager.startSimulation();
+ btnStart.setDisable(true);
+ btnStop.setDisable(false);
+ } catch (IOException ex) {
+ showErrorAlert("Start Failed", "Could not start simulation processes: " + ex.getMessage());
+ }
+ });
+
+ btnStop.setOnAction(e -> {
+ processManager.stopSimulation();
+ btnStart.setDisable(false);
+ btnStop.setDisable(true);
+ });
+
+ controls.getChildren().addAll(btnStart, btnStop);
+
+ header.getChildren().addAll(title, subtitle, controls);
+
+ return header;
+ }
+
+ private VBox createMainContent() {
+ VBox mainContent = new VBox(20);
+ mainContent.setPadding(new Insets(20));
+
+ // Global Statistics Panel
+ VBox globalStatsCard = createGlobalStatisticsPanel();
+
+ // Tables Container
+ HBox tablesContainer = new HBox(20);
+ tablesContainer.setAlignment(Pos.TOP_CENTER);
+
+ // Vehicle Type Statistics Panel
+ VBox vehicleTypeCard = createVehicleTypePanel();
+ HBox.setHgrow(vehicleTypeCard, Priority.ALWAYS);
+
+ // Intersection Statistics Panel
+ VBox intersectionCard = createIntersectionPanel();
+ HBox.setHgrow(intersectionCard, Priority.ALWAYS);
+
+ tablesContainer.getChildren().addAll(vehicleTypeCard, intersectionCard);
+
+ mainContent.getChildren().addAll(globalStatsCard, tablesContainer);
+
+ return mainContent;
+ }
+
+ private VBox createGlobalStatisticsPanel() {
+ VBox card = new VBox();
+ card.getStyleClass().add("card");
+
+ // Card Header
+ HBox cardHeader = new HBox();
+ cardHeader.getStyleClass().add("card-header");
+ Label cardTitle = new Label("Global Statistics");
+ cardTitle.getStyleClass().add("card-title");
+ cardHeader.getChildren().add(cardTitle);
+
+ // Card Content
+ GridPane grid = new GridPane();
+ grid.getStyleClass().add("card-content");
+ grid.setHgap(40);
+ grid.setVgap(15);
+ grid.setAlignment(Pos.CENTER);
+
+ // Initialize labels
+ lblVehiclesGenerated = createStatValueLabel("0");
+ lblVehiclesCompleted = createStatValueLabel("0");
+ lblVehiclesInTransit = createStatValueLabel("0");
+ lblAvgSystemTime = createStatValueLabel("0.00 s");
+ lblAvgWaitingTime = createStatValueLabel("0.00 s");
+
+ // Add labels with descriptions
+ addStatRow(grid, 0, 0, "Total Vehicles Generated", lblVehiclesGenerated);
+ addStatRow(grid, 1, 0, "Total Vehicles Completed", lblVehiclesCompleted);
+ addStatRow(grid, 2, 0, "Vehicles In Transit", lblVehiclesInTransit);
+ addStatRow(grid, 0, 1, "Average System Time", lblAvgSystemTime);
+ addStatRow(grid, 1, 1, "Average Waiting Time", lblAvgWaitingTime);
+
+ card.getChildren().addAll(cardHeader, grid);
+ return card;
+ }
+
+ private VBox createVehicleTypePanel() {
+ VBox card = new VBox();
+ card.getStyleClass().add("card");
+
+ // Card Header
+ HBox cardHeader = new HBox();
+ cardHeader.getStyleClass().add("card-header");
+ Label cardTitle = new Label("Vehicle Type Statistics");
+ cardTitle.getStyleClass().add("card-title");
+ cardHeader.getChildren().add(cardTitle);
+
+ // Table
+ vehicleTypeTable = new TableView<>();
+ vehicleTypeTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ vehicleTypeTable.setPrefHeight(300);
+
+ TableColumn typeCol = new TableColumn<>("Vehicle Type");
+ typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType"));
+
+ TableColumn countCol = new TableColumn<>("Count");
+ countCol.setCellValueFactory(new PropertyValueFactory<>("count"));
+
+ TableColumn avgWaitCol = new TableColumn<>("Avg Wait Time");
+ avgWaitCol.setCellValueFactory(new PropertyValueFactory<>("avgWaitTime"));
+
+ vehicleTypeTable.getColumns().addAll(typeCol, countCol, avgWaitCol);
+
+ card.getChildren().addAll(cardHeader, vehicleTypeTable);
+ return card;
+ }
+
+ private VBox createIntersectionPanel() {
+ VBox card = new VBox();
+ card.getStyleClass().add("card");
+
+ // Card Header
+ HBox cardHeader = new HBox();
+ cardHeader.getStyleClass().add("card-header");
+ Label cardTitle = new Label("Intersection Statistics");
+ cardTitle.getStyleClass().add("card-title");
+ cardHeader.getChildren().add(cardTitle);
+
+ // Table
+ intersectionTable = new TableView<>();
+ intersectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ intersectionTable.setPrefHeight(300);
+
+ TableColumn idCol = new TableColumn<>("Intersection ID");
+ idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId"));
+
+ TableColumn arrivalsCol = new TableColumn<>("Total Arrivals");
+ arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals"));
+
+ TableColumn departuresCol = new TableColumn<>("Total Departures");
+ departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures"));
+
+ TableColumn queueCol = new TableColumn<>("Current Queue");
+ queueCol.setCellValueFactory(new PropertyValueFactory<>("queueSize"));
+
+ intersectionTable.getColumns().addAll(idCol, arrivalsCol, departuresCol, queueCol);
+
+ card.getChildren().addAll(cardHeader, intersectionTable);
+ return card;
+ }
+
+ private HBox createFooter() {
+ HBox footer = new HBox(10);
+ footer.getStyleClass().add("footer");
+ footer.setAlignment(Pos.CENTER_LEFT);
+
+ Label statusLabel = new Label("Status:");
+ statusLabel.getStyleClass().add("footer-text");
+ statusLabel.setStyle("-fx-font-weight: bold;");
+
+ Circle statusIndicator = new Circle(6);
+ statusIndicator.setFill(javafx.scene.paint.Color.LIME);
+
+ Label statusText = new Label("Connected and Receiving Data");
+ statusText.getStyleClass().add("footer-text");
+
+ lblLastUpdate = new Label("Last Update: --:--:--");
+ lblLastUpdate.getStyleClass().add("footer-text");
+
+ Region spacer = new Region();
+ HBox.setHgrow(spacer, Priority.ALWAYS);
+
+ footer.getChildren().addAll(statusLabel, statusIndicator, statusText, spacer, lblLastUpdate);
+
+ return footer;
+ }
+
+ private Label createStatValueLabel(String initialValue) {
+ Label label = new Label(initialValue);
+ label.getStyleClass().add("stat-value");
+ return label;
+ }
+
+ private void addStatRow(GridPane grid, int row, int colGroup, String description, Label valueLabel) {
+ VBox container = new VBox(5);
+ container.setAlignment(Pos.CENTER_LEFT);
+
+ Label descLabel = new Label(description);
+ descLabel.getStyleClass().add("stat-label");
+
+ container.getChildren().addAll(descLabel, valueLabel);
+
+ grid.add(container, colGroup, row);
+ }
+
+ private void startPeriodicUpdates() {
+ updateScheduler = Executors.newSingleThreadScheduledExecutor();
+ updateScheduler.scheduleAtFixedRate(() -> {
+ Platform.runLater(this::updateUI);
+ }, 0, 5, TimeUnit.SECONDS);
+ }
+
+ private void updateUI() {
+ // Update global statistics
+ lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated()));
+ lblVehiclesCompleted.setText(String.valueOf(statistics.getTotalVehiclesCompleted()));
+ lblVehiclesInTransit.setText(String.valueOf(
+ statistics.getTotalVehiclesGenerated() - statistics.getTotalVehiclesCompleted()));
+ lblAvgSystemTime.setText(String.format("%.2f s", statistics.getAverageSystemTime() / 1000.0));
+ lblAvgWaitingTime.setText(String.format("%.2f s", statistics.getAverageWaitingTime() / 1000.0));
+ lblLastUpdate.setText(String.format("Last Update: %tT", statistics.getLastUpdateTime()));
+
+ // Update vehicle type table
+ vehicleTypeTable.getItems().clear();
+ for (VehicleType type : VehicleType.values()) {
+ int count = statistics.getVehicleTypeCount(type);
+ double avgWait = statistics.getAverageWaitingTimeByType(type);
+ vehicleTypeTable.getItems().add(new VehicleTypeRow(
+ type.toString(), count, String.format("%.2f s", avgWait / 1000.0)));
+ }
+
+ // Update intersection table
+ intersectionTable.getItems().clear();
+ Map intersectionStats = statistics.getAllIntersectionStats();
+ for (DashboardStatistics.IntersectionStats stats : intersectionStats.values()) {
+ intersectionTable.getItems().add(new IntersectionRow(
+ stats.getIntersectionId(),
+ stats.getTotalArrivals(),
+ stats.getTotalDepartures(),
+ stats.getCurrentQueueSize()));
+ }
+ }
+
+ private void shutdown() {
+ System.out.println("Shutting down Dashboard UI...");
+
+ if (updateScheduler != null && !updateScheduler.isShutdown()) {
+ updateScheduler.shutdownNow();
+ }
+
+ if (server != null) {
+ server.stop();
+ }
+
+ Platform.exit();
+ }
+
+ private void showErrorAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.ERROR);
+ alert.setTitle(title);
+ alert.setHeaderText(null);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ // Inner classes for TableView data models
+ public static class VehicleTypeRow {
+ private final String vehicleType;
+ private final int count;
+ private final String avgWaitTime;
+
+ public VehicleTypeRow(String vehicleType, int count, String avgWaitTime) {
+ this.vehicleType = vehicleType;
+ this.count = count;
+ this.avgWaitTime = avgWaitTime;
+ }
+
+ public String getVehicleType() {
+ return vehicleType;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public String getAvgWaitTime() {
+ return avgWaitTime;
+ }
+ }
+
+ public static class IntersectionRow {
+ private final String intersectionId;
+ private final int arrivals;
+ private final int departures;
+ private final int queueSize;
+
+ public IntersectionRow(String intersectionId, int arrivals, int departures, int queueSize) {
+ this.intersectionId = intersectionId;
+ this.arrivals = arrivals;
+ this.departures = departures;
+ this.queueSize = queueSize;
+ }
+
+ public String getIntersectionId() {
+ return intersectionId;
+ }
+
+ public int getArrivals() {
+ return arrivals;
+ }
+
+ public int getDepartures() {
+ return departures;
+ }
+
+ public int getQueueSize() {
+ return queueSize;
+ }
+ }
+}
diff --git a/main/src/main/java/sd/dashboard/Launcher.java b/main/src/main/java/sd/dashboard/Launcher.java
new file mode 100644
index 0000000..0a45c3d
--- /dev/null
+++ b/main/src/main/java/sd/dashboard/Launcher.java
@@ -0,0 +1,7 @@
+package sd.dashboard;
+
+public class Launcher {
+ public static void main(String[] args) {
+ DashboardUI.main(args);
+ }
+}
diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java
new file mode 100644
index 0000000..0658b2b
--- /dev/null
+++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java
@@ -0,0 +1,118 @@
+package sd.dashboard;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the lifecycle of simulation processes (Intersections, Exit Node,
+ * Coordinator).
+ * Allows starting and stopping the distributed simulation from within the Java
+ * application.
+ */
+public class SimulationProcessManager {
+
+ private final List runningProcesses;
+ private final String classpath;
+
+ public SimulationProcessManager() {
+ this.runningProcesses = new ArrayList<>();
+ this.classpath = System.getProperty("java.class.path");
+ }
+
+ /**
+ * Starts the full simulation: 5 Intersections, 1 Exit Node, and 1 Coordinator.
+ *
+ * @throws IOException If a process fails to start.
+ */
+ public void startSimulation() throws IOException {
+ if (!runningProcesses.isEmpty()) {
+ stopSimulation();
+ }
+
+ System.out.println("Starting simulation processes...");
+
+ // 1. Start Intersections (Cr1 - Cr5)
+ String[] intersectionIds = { "Cr1", "Cr2", "Cr3", "Cr4", "Cr5" };
+ for (String id : intersectionIds) {
+ startProcess("sd.IntersectionProcess", id);
+ }
+
+ // 2. Start Exit Node
+ startProcess("sd.ExitNodeProcess", null);
+
+ // 3. Start Coordinator (Wait a bit for others to initialize)
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ startProcess("sd.coordinator.CoordinatorProcess", null);
+
+ System.out.println("All simulation processes started.");
+ }
+
+ /**
+ * Stops all running simulation processes.
+ */
+ public void stopSimulation() {
+ System.out.println("Stopping simulation processes...");
+
+ for (Process process : runningProcesses) {
+ if (process.isAlive()) {
+ process.destroy(); // Try graceful termination first
+ }
+ }
+
+ // Wait a bit and force kill if necessary
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ for (Process process : runningProcesses) {
+ if (process.isAlive()) {
+ process.destroyForcibly();
+ }
+ }
+
+ runningProcesses.clear();
+ System.out.println("All simulation processes stopped.");
+ }
+
+ /**
+ * Helper to start a single Java process.
+ */
+ private void startProcess(String className, String arg) throws IOException {
+ String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
+
+ ProcessBuilder builder;
+ if (arg != null) {
+ builder = new ProcessBuilder(javaBin, "-cp", classpath, className, arg);
+ } else {
+ builder = new ProcessBuilder(javaBin, "-cp", classpath, className);
+ }
+
+ // get the OS temp folder
+ // Linux: /tmp/
+ // Windows: %AppData%\Local\Temp\
+ String tempDir = System.getProperty("java.io.tmpdir");
+
+ String logName = className.substring(className.lastIndexOf('.') + 1) + (arg != null ? "-" + arg : "") + ".log";
+
+ // use the (File parent, String child) constructor to handle slash/backslash
+ // automatically
+ File logFile = new File(tempDir, logName);
+
+ builder.redirectOutput(logFile);
+ builder.redirectError(logFile);
+
+ Process process = builder.start();
+ runningProcesses.add(process);
+ System.out.println("Started " + className + (arg != null ? " " + arg : ""));
+ // print where the logs are actually going
+ System.out.println("Logs redirected to: " + logFile.getAbsolutePath());
+ }
+}
diff --git a/main/src/main/java/sd/engine/SimulationEngine.java b/main/src/main/java/sd/engine/SimulationEngine.java
deleted file mode 100644
index b69c7ef..0000000
--- a/main/src/main/java/sd/engine/SimulationEngine.java
+++ /dev/null
@@ -1,648 +0,0 @@
-package sd.engine;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.PriorityQueue;
-
-import sd.config.SimulationConfig;
-import sd.model.Event;
-import sd.model.EventType;
-import sd.model.Intersection;
-import sd.model.TrafficLight;
-import sd.model.TrafficLightState;
-import sd.model.Vehicle;
-import sd.model.VehicleType;
-import sd.util.StatisticsCollector;
-import sd.util.VehicleGenerator;
-
-/**
- * Core simulation engine using discrete event simulation (DES).
- * * This class orchestrates the entire simulation. It maintains a
- * {@link PriorityQueue} of {@link Event} objects, representing all
- * scheduled future actions. The engine processes events in strict
- * chronological order (based on their timestamp).
- * * It manages the simulation's state, including:
- * - The current simulation time ({@code currentTime}).
- * - The collection of all {@link Intersection} objects.
- * - The {@link VehicleGenerator} for creating new vehicles.
- * - The {@link StatisticsCollector} for tracking metrics.
- */
-public class SimulationEngine {
-
- /**
- * Holds all simulation parameters loaded from the properties file.
- */
- private final SimulationConfig config;
-
- /**
- * The core of the discrete event simulation. Events are pulled from this
- * queue in order of their timestamp.
- */
- private final PriorityQueue eventQueue;
-
- /**
- * A map storing all intersections in the simulation, keyed by their ID (e.g., "Cr1").
- */
- private final Map intersections;
-
- /**
- * Responsible for creating new vehicles according to the configured arrival model.
- */
- private final VehicleGenerator vehicleGenerator;
-
- /**
- * Collects and calculates statistics throughout the simulation.
- */
- private final StatisticsCollector statisticsCollector;
-
- /**
- * The current time in the simulation (in virtual seconds).
- * This time advances based on the timestamp of the event being processed.
- */
- private double currentTime;
-
- /**
- * A simple counter to generate unique IDs for vehicles.
- */
- private int vehicleCounter;
-
- /**
- * Constructs a new SimulationEngine.
- *
- * @param config The {@link SimulationConfig} object containing all
- * simulation parameters.
- */
- public SimulationEngine(SimulationConfig config) {
- this.config = config;
- this.eventQueue = new PriorityQueue<>();
- this.intersections = new HashMap<>();
- this.vehicleGenerator = new VehicleGenerator(config);
- this.statisticsCollector = new StatisticsCollector(config);
- this.currentTime = 0.0;
- this.vehicleCounter = 0;
- }
-
- /**
- * Calculates the travel time between intersections based on vehicle type.
- *
- * @param vehicleType The type of the vehicle.
- * @return The travel time in seconds.
- */
- private double calculateTravelTime(VehicleType vehicleType) {
- double baseTime = config.getBaseTravelTime();
-
- switch (vehicleType) {
- case BIKE:
- return baseTime * config.getBikeTravelTimeMultiplier();
- case HEAVY:
- return baseTime * config.getHeavyTravelTimeMultiplier();
- case LIGHT:
- default:
- return baseTime;
- }
-}
-
- /**
- * Initializes the simulation. This involves:
- * 1. Creating all {@link Intersection} and {@link TrafficLight} objects.
- * 2. Configuring the routing logic between intersections.
- * 3. Scheduling the initial events (first traffic light changes,
- * first vehicle generation, and periodic statistics updates).
- */
- public void initialize() {
- System.out.println("Initializing simulation...");
-
- setupIntersections();
- setupRouting();
-
- // Schedule initial events to "bootstrap" the simulation
- scheduleTrafficLightEvents();
- scheduleNextVehicleGeneration(0.0);
- scheduleStatisticsUpdates();
-
- System.out.println("Simulation initialized with " + intersections.size() + " intersections");
- }
-
- /**
- * Creates all intersections defined in the configuration
- * and adds their corresponding traffic lights.
- */
- private void setupIntersections() {
- String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"};
- // Note: "North" is commented out, so it won't be created.
- String[] directions = {/*"North",*/ "South", "East", "West"};
-
- for (String id : intersectionIds) {
- Intersection intersection = new Intersection(id);
-
- // Add traffic lights for each configured direction
- for (String direction : directions) {
- double greenTime = config.getTrafficLightGreenTime(id, direction);
- double redTime = config.getTrafficLightRedTime(id, direction);
-
- TrafficLight light = new TrafficLight(
- id + "-" + direction,
- direction,
- greenTime,
- redTime
- );
-
- intersection.addTrafficLight(light);
- }
-
- intersections.put(id, intersection);
- }
- }
-
- /**
- * Configures how vehicles should be routed between intersections.
- * This hardcoded logic defines the "map" of the city.
- * * For example, `intersections.get("Cr1").configureRoute("Cr2", "East");` means
- * "at intersection Cr1, any vehicle whose *next* destination is Cr2
- * should be sent to the 'East' traffic light queue."
- */
- private void setupRouting() {
- // Cr1 routing
- intersections.get("Cr1").configureRoute("Cr2", "East");
- intersections.get("Cr1").configureRoute("Cr4", "South");
-
- // Cr2 routing
- intersections.get("Cr2").configureRoute("Cr1", "West");
- intersections.get("Cr2").configureRoute("Cr3", "East");
- intersections.get("Cr2").configureRoute("Cr5", "South");
-
- // Cr3 routing
- intersections.get("Cr3").configureRoute("Cr2", "West");
- intersections.get("Cr3").configureRoute("S", "South"); // "S" is the exit
-
- // Cr4 routing
- //intersections.get("Cr4").configureRoute("Cr1", "North");
- intersections.get("Cr4").configureRoute("Cr5", "East");
-
- // Cr5 routing
- //intersections.get("Cr5").configureRoute("Cr2", "North");
- //intersections.get("Cr5").configureRoute("Cr4", "West");
- intersections.get("Cr5").configureRoute("S", "East"); // "S" is the exit
- }
-
- /**
- * Schedules the initial {@link EventType#TRAFFIC_LIGHT_CHANGE} event
- * for every traffic light in the simulation.
- * A small random delay is added to "stagger" the lights, preventing
- * all of them from changing at the exact same time at t=0.
- */
- private void scheduleTrafficLightEvents() {
- for (Intersection intersection : intersections.values()) {
- for (TrafficLight light : intersection.getTrafficLights()) {
- // Start with lights in RED state, schedule first GREEN change
- // Stagger the start times slightly to avoid all lights changing at once
- double staggerDelay = Math.random() * 1.5;
- scheduleTrafficLightChange(light, intersection.getId(), staggerDelay);
- }
- }
- }
-
- /**
- * Creates and schedules a new {@link EventType#TRAFFIC_LIGHT_CHANGE} event.
- * The event is scheduled to occur at {@code currentTime + delay}.
- *
- * @param light The {@link TrafficLight} that will change state.
- * @param intersectionId The ID of the intersection where the light is located.
- * @param delay The time (in seconds) from {@code currentTime} when the change should occur.
- */
- private void scheduleTrafficLightChange(TrafficLight light, String intersectionId, double delay) {
- double changeTime = currentTime + delay;
- Event event = new Event(changeTime, EventType.TRAFFIC_LIGHT_CHANGE, light, intersectionId);
- eventQueue.offer(event);
- }
-
- /**
- * Schedules the next {@link EventType#VEHICLE_GENERATION} event.
- * The time of the next arrival is determined by the {@link VehicleGenerator}.
- *
- * @param baseTime The time from which to calculate the next arrival (usually {@code currentTime}).
- */
- private void scheduleNextVehicleGeneration(double baseTime) {
- // Get the absolute time for the next arrival.
- double nextArrivalTime = vehicleGenerator.getNextArrivalTime(baseTime);
-
- // Only schedule the event if it's within the simulation's total duration.
- if (nextArrivalTime < config.getSimulationDuration()) {
- Event event = new Event(nextArrivalTime, EventType.VEHICLE_GENERATION, null, null);
- eventQueue.offer(event);
- }
- }
-
- /**
- * Schedules all periodic {@link EventType#STATISTICS_UPDATE} events
- * for the entire duration of the simulation.
- */
- private void scheduleStatisticsUpdates() {
- double interval = config.getStatisticsUpdateInterval();
- double duration = config.getSimulationDuration();
-
- for (double time = interval; time < duration; time += interval) {
- Event event = new Event(time, EventType.STATISTICS_UPDATE, null, null);
- eventQueue.offer(event);
- }
- }
-
- /**
- * Runs the main simulation loop.
- * The loop continues as long as there are events in the queue and
- * the {@code currentTime} is less than the total simulation duration.
- * * In each iteration, it:
- * 1. Polls the next event from the {@link #eventQueue}.
- * 2. Advances {@link #currentTime} to the event's timestamp.
- * 3. Calls {@link #processEvent(Event)} to handle the event.
- * * After the loop, it prints the final statistics.
- */
- public void run() {
- System.out.println("Starting simulation...");
- double duration = config.getSimulationDuration();
-
- while (!eventQueue.isEmpty() && currentTime < duration) {
- // Get the next event in chronological order
- Event event = eventQueue.poll();
-
- // Advance simulation time to this event's time
- currentTime = event.getTimestamp();
-
- // Process the event
- processEvent(event);
- }
-
- System.out.println("\nSimulation completed at t=" + String.format("%.2f", currentTime) + "s");
- printFinalStatistics();
- }
-
- /**
- * Main event processing logic.
- * Delegates the event to the appropriate handler method based on its {@link EventType}.
- *
- * @param event The {@link Event} to be processed.
- */
- private void processEvent(Event event) {
- switch (event.getType()) {
- case VEHICLE_GENERATION -> handleVehicleGeneration();
-
- case VEHICLE_ARRIVAL -> handleVehicleArrival(event);
-
- case TRAFFIC_LIGHT_CHANGE -> handleTrafficLightChange(event);
-
- case CROSSING_START -> handleCrossingStart(event);
-
- case CROSSING_END -> handleCrossingEnd(event);
-
- case STATISTICS_UPDATE -> handleStatisticsUpdate();
-
- default -> System.err.println("Unknown event type: " + event.getType());
- }
- }
-
- /**
- * Handles {@link EventType#VEHICLE_GENERATION}.
- * 1. Creates a new {@link Vehicle} using the {@link #vehicleGenerator}.
- * 2. Records the generation event with the {@link #statisticsCollector}.
- * 3. Schedules a {@link EventType#VEHICLE_ARRIVAL} event for the vehicle
- * at its first destination intersection.
- * 4. Schedules the *next* {@link EventType#VEHICLE_GENERATION} event.
- * (Note: This line is commented out in the original, which might be a bug,
- * as it implies only one vehicle is ever generated. It should likely be active.)
- */
- private void handleVehicleGeneration() {
- Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime);
-
- System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n",
- currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute());
-
- // Register with statistics collector
- statisticsCollector.recordVehicleGeneration(vehicle, currentTime);
-
- // Schedule arrival at first intersection
- String firstIntersection = vehicle.getCurrentDestination();
- if (firstIntersection != null && !firstIntersection.equals("S")) {
- double travelTime = calculateTravelTime(vehicle.getType());
- double arrivalTime = currentTime + travelTime;
- Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, firstIntersection);
- eventQueue.offer(arrivalEvent);
- }
-
- // Schedule next vehicle generation
- // This was commented out in the original file.
- // For a continuous simulation, it should be enabled:
- scheduleNextVehicleGeneration(currentTime);
- }
-
- /**
- * Handles {@link EventType#VEHICLE_ARRIVAL} at an intersection.
- * 1. Records the arrival for statistics.
- * 2. Advances the vehicle's internal route planner to its *next* destination.
- * 3. If the next destination is the exit ("S") or null,
- * the vehicle exits the system via {@link #handleVehicleExit(Vehicle)}.
- * 4. Otherwise, the vehicle is placed in the correct queue at the
- * current intersection using {@link Intersection#receiveVehicle(Vehicle)}.
- * 5. Attempts to process the vehicle immediately if its light is green.
- *
- * @param event The arrival event, containing the {@link Vehicle} and intersection ID.
- */
- private void handleVehicleArrival(Event event) {
- Vehicle vehicle = (Vehicle) event.getData();
- String intersectionId = event.getLocation();
-
- Intersection intersection = intersections.get(intersectionId);
- if (intersection == null) {
- System.err.println("Unknown intersection: " + intersectionId);
- return;
- }
-
- System.out.printf("[t=%.2f] Vehicle %s arrived at %s%n",
- currentTime, vehicle.getId(), intersectionId);
-
- // Record arrival time (used to calculate waiting time later)
- statisticsCollector.recordVehicleArrival(vehicle, intersectionId, currentTime);
-
- // Advance the vehicle's route to the *next* stop
- // (it has now arrived at its *current* destination)
- boolean hasNext = vehicle.advanceRoute();
-
- if (!hasNext) {
- // This was the last stop
- handleVehicleExit(vehicle);
- return;
- }
-
- String nextDestination = vehicle.getCurrentDestination();
- if (nextDestination == null || "S".equals(nextDestination)) {
- // Next stop is the exit
- handleVehicleExit(vehicle);
- return;
- }
-
- // Add vehicle to the appropriate traffic light queue based on its next destination
- intersection.receiveVehicle(vehicle);
-
- // Try to process the vehicle immediately if its light is already green
- tryProcessVehicle(vehicle, intersection);
- }
-
- /**
- * Checks if a newly arrived vehicle (or a vehicle in a queue
- * that just turned green) can start crossing.
- *
- * @param vehicle The vehicle to process.
- * @param intersection The intersection where the vehicle is.
- */
- private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { //FIXME
- // Find the direction (and light) this vehicle is queued at
- // This logic is a bit flawed: it just finds the *first* non-empty queue
- // A better approach would be to get the light from the vehicle's route
- String direction = intersection.getTrafficLights().stream()
- .filter(tl -> tl.getQueueSize() > 0)
- .map(TrafficLight::getDirection)
- .findFirst()
- .orElse(null);
-
- if (direction != null) {
- TrafficLight light = intersection.getTrafficLight(direction);
- // If the light is green and it's the correct one...
- if (light != null && light.getState() == TrafficLightState.GREEN) {
- // ...remove the vehicle from the queue (if it's at the front)
- Vehicle v = light.removeVehicle();
- if (v != null) {
- // ...and schedule its crossing.
- scheduleCrossing(v, intersection);
- }
- }
- }
- }
-
- /**
- * Schedules the crossing for a vehicle that has just been dequeued
- * from a green light.
- * 1. Calculates and records the vehicle's waiting time.
- * 2. Schedules an immediate {@link EventType#CROSSING_START} event.
- *
- * @param vehicle The {@link Vehicle} that is crossing.
- * @param intersection The {@link Intersection} it is crossing.
- */
- private void scheduleCrossing(Vehicle vehicle, Intersection intersection) {
- // Calculate time spent waiting at the red light
- double waitTime = currentTime - statisticsCollector.getArrivalTime(vehicle);
- vehicle.addWaitingTime(waitTime);
-
- // Schedule crossing start event *now*
- Event crossingStart = new Event(currentTime, EventType.CROSSING_START, vehicle, intersection.getId());
- processEvent(crossingStart); // Process immediately
- }
-
- /**
- * Handles {@link EventType#CROSSING_START}.
- * 1. Determines the crossing time based on vehicle type.
- * 2. Schedules a {@link EventType#CROSSING_END} event to occur
- * at {@code currentTime + crossingTime}.
- *
- * @param event The crossing start event.
- */
- private void handleCrossingStart(Event event) {
- Vehicle vehicle = (Vehicle) event.getData();
- String intersectionId = event.getLocation();
-
- double crossingTime = getCrossingTime(vehicle.getType());
-
- System.out.printf("[t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n",
- currentTime, vehicle.getId(), intersectionId, crossingTime);
-
- // Schedule the *end* of the crossing
- double endTime = currentTime + crossingTime;
- Event crossingEnd = new Event(endTime, EventType.CROSSING_END, vehicle, intersectionId);
- eventQueue.offer(crossingEnd);
- }
-
- /**
- * Handles {@link EventType#CROSSING_END}.
- * 1. Updates intersection and vehicle statistics.
- * 2. Checks the vehicle's *next* destination.
- * 3. If the next destination is the exit ("S"), call {@link #handleVehicleExit(Vehicle)}.
- * 4. Otherwise, schedule a {@link EventType#VEHICLE_ARRIVAL} event at the
- * *next* intersection, after some travel time.
- *
- * @param event The crossing end event.
- */
- private void handleCrossingEnd(Event event) {
- Vehicle vehicle = (Vehicle) event.getData();
- String intersectionId = event.getLocation();
-
- // Update stats
- Intersection intersection = intersections.get(intersectionId);
- if (intersection != null) {
- intersection.incrementVehiclesSent();
- }
-
- double crossingTime = getCrossingTime(vehicle.getType());
- vehicle.addCrossingTime(crossingTime);
-
- System.out.printf("[t=%.2f] Vehicle %s finished crossing at %s%n",
- currentTime, vehicle.getId(), intersectionId);
-
- // Decide what to do next
- String nextDest = vehicle.getCurrentDestination();
- if (nextDest != null && !nextDest.equals("S")) {
- // Route to the *next* intersection
- // Travel time varies by vehicle type: tmoto = 0.5 × tcarro, tcaminhão = 4 × tmoto
- double travelTime = calculateTravelTime(vehicle.getType());
- double arrivalTime = currentTime + travelTime;
- Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, nextDest);
- eventQueue.offer(arrivalEvent);
- } else {
- // Reached the exit
- handleVehicleExit(vehicle);
- }
- }
-
- /**
- * Handles a vehicle exiting the simulation.
- * Records final statistics for the vehicle.
- *
- * @param vehicle The {@link Vehicle} that has completed its route.
- */
- private void handleVehicleExit(Vehicle vehicle) {
- System.out.printf("[t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n",
- currentTime, vehicle.getId(),
- vehicle.getTotalWaitingTime(),
- vehicle.getTotalTravelTime(currentTime));
-
- // Record the exit for final statistics calculation
- statisticsCollector.recordVehicleExit(vehicle, currentTime);
- }
-
- /**
- * Handles {@link EventType#TRAFFIC_LIGHT_CHANGE}.
- * 1. Toggles the light's state (RED to GREEN or GREEN to RED).
- * 2. If the light just turned GREEN, call {@link #processGreenLight(TrafficLight, Intersection)}
- * to process any waiting vehicles.
- * 3. Schedules the *next* state change for this light based on its
- * green/red time duration.
- *
- * @param event The light change event.
- */
- private void handleTrafficLightChange(Event event) {
- TrafficLight light = (TrafficLight) event.getData();
- String intersectionId = event.getLocation();
-
- // Toggle state
- TrafficLightState newState = (light.getState() == TrafficLightState.RED)
- ? TrafficLightState.GREEN
- : TrafficLightState.RED;
-
- light.changeState(newState);
-
- System.out.printf("[t=%.2f] Traffic light %s changed to %s%n",
- currentTime, light.getId(), newState);
-
- // If changed to GREEN, process waiting vehicles
- if (newState == TrafficLightState.GREEN) {
- Intersection intersection = intersections.get(intersectionId);
- if (intersection != null) {
- processGreenLight(light, intersection);
- }
- }
-
- // Schedule the *next* state change for this same light
- double nextChangeDelay = (newState == TrafficLightState.GREEN)
- ? light.getGreenTime()
- : light.getRedTime();
-
- scheduleTrafficLightChange(light, intersectionId, nextChangeDelay);
- }
-
- /**
- * Processes vehicles when a light turns green.
- * It loops as long as the light is green and there are vehicles in the queue,
- * dequeuing one vehicle at a time and scheduling its crossing.
- * * *Note*: This is a simplified model. A real simulation would
- * account for the *time* it takes each vehicle to cross, processing
- * one vehicle every {@code crossingTime} seconds. This implementation
- * processes the entire queue "instantaneously" at the moment
- * the light turns green.
- *
- * @param light The {@link TrafficLight} that just turned green.
- * @param intersection The {@link Intersection} where the light is.
- */
- private void processGreenLight(TrafficLight light, Intersection intersection) {
- // While the light is green and vehicles are waiting...
- while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
- Vehicle vehicle = light.removeVehicle();
- if (vehicle != null) {
- // Dequeue one vehicle and schedule its crossing
- scheduleCrossing(vehicle, intersection);
- }
- }
- }
-
- /**
- * Handles {@link EventType#STATISTICS_UPDATE}.
- * Calls the {@link StatisticsCollector} to print the current
- * state of the simulation (queue sizes, averages, etc.).
- */
- private void handleStatisticsUpdate() {
- System.out.printf("\n=== Statistics at t=%.2f ===%n", currentTime);
- statisticsCollector.printCurrentStatistics(intersections, currentTime);
- System.out.println();
- }
-
- /**
- * Utility method to get the configured crossing time for a given {@link VehicleType}.
- *
- * @param type The type of vehicle.
- * @return The crossing time in seconds.
- */
- private double getCrossingTime(VehicleType type) {
- return switch (type) {
- case BIKE -> config.getBikeVehicleCrossingTime();
- case LIGHT -> config.getLightVehicleCrossingTime();
- case HEAVY -> config.getHeavyVehicleCrossingTime();
- default -> 2.0;
- }; // Default fallback
- }
-
- /**
- * Prints the final summary of statistics at the end of the simulation.
- */
- private void printFinalStatistics() {
- System.out.println("\n" + "=".repeat(60));
- System.out.println("FINAL SIMULATION STATISTICS");
- System.out.println("=".repeat(60));
-
- statisticsCollector.printFinalStatistics(intersections, currentTime);
-
- System.out.println("=".repeat(60));
- }
-
- // --- Public Getters ---
-
- /**
- * Gets the current simulation time.
- * @return The time in virtual seconds.
- */
- public double getCurrentTime() {
- return currentTime;
- }
-
- /**
- * Gets a map of all intersections in the simulation.
- * Returns a copy to prevent external modification.
- * @return A {@link Map} of intersection IDs to {@link Intersection} objects.
- */
- public Map getIntersections() {
- return new HashMap<>(intersections);
- }
-
- /**
- * Gets the statistics collector instance.
- * @return The {@link StatisticsCollector}.
- */
- public StatisticsCollector getStatisticsCollector() {
- return statisticsCollector;
- }
-}
\ No newline at end of file
diff --git a/main/src/main/java/sd/engine/TrafficLightThread.java b/main/src/main/java/sd/engine/TrafficLightThread.java
index 1f1c197..8a0c3a5 100644
--- a/main/src/main/java/sd/engine/TrafficLightThread.java
+++ b/main/src/main/java/sd/engine/TrafficLightThread.java
@@ -4,7 +4,7 @@ import sd.IntersectionProcess;
import sd.config.SimulationConfig;
import sd.model.TrafficLight;
import sd.model.TrafficLightState;
-import sd.model.Vehicle;
+import sd.model.Vehicle;
/**
* Implements the control logic for a single TrafficLight
@@ -16,7 +16,7 @@ public class TrafficLightThread implements Runnable {
private final IntersectionProcess process;
private final SimulationConfig config;
private volatile boolean running;
-
+
// Store the thread reference for proper interruption
private Thread currentThread;
@@ -35,33 +35,31 @@ public class TrafficLightThread implements Runnable {
try {
while (running && !Thread.currentThread().isInterrupted()) {
-
+
// Request permission to turn green (blocks until granted)
process.requestGreenLight(light.getDirection());
-
+
try {
// --- GREEN Phase ---
light.changeState(TrafficLightState.GREEN);
System.out.println("[" + light.getId() + "] State: GREEN");
-
- processGreenLightQueue();
-
- if (!running || Thread.currentThread().isInterrupted()) break;
-
- // Wait for green duration
- Thread.sleep((long) (light.getGreenTime() * 1000));
-
- if (!running || Thread.currentThread().isInterrupted()) break;
+
+ // Process queue for the duration of the green light
+ long greenDurationMs = (long) (light.getGreenTime() * 1000);
+ processGreenLightQueue(greenDurationMs);
+
+ if (!running || Thread.currentThread().isInterrupted())
+ break;
// --- RED Phase ---
light.changeState(TrafficLightState.RED);
System.out.println("[" + light.getId() + "] State: RED");
-
+
} finally {
// Always release the green light permission
process.releaseGreenLight(light.getDirection());
}
-
+
// Wait for red duration
Thread.sleep((long) (light.getRedTime() * 1000));
}
@@ -74,21 +72,34 @@ public class TrafficLightThread implements Runnable {
}
}
- private void processGreenLightQueue() throws InterruptedException {
- while (running && !Thread.currentThread().isInterrupted()
- && light.getState() == TrafficLightState.GREEN
- && light.getQueueSize() > 0) {
-
- Vehicle vehicle = light.removeVehicle();
-
- if (vehicle != null) {
- double crossingTime = getCrossingTimeForVehicle(vehicle);
-
- Thread.sleep((long) (crossingTime * 1000));
-
- vehicle.addCrossingTime(crossingTime);
- process.getIntersection().incrementVehiclesSent();
- process.sendVehicleToNextDestination(vehicle);
+ private void processGreenLightQueue(long greenDurationMs) throws InterruptedException {
+ long startTime = System.currentTimeMillis();
+
+ while (running && !Thread.currentThread().isInterrupted()
+ && light.getState() == TrafficLightState.GREEN) {
+
+ // Check if green time has expired
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed >= greenDurationMs) {
+ break;
+ }
+
+ if (light.getQueueSize() > 0) {
+ Vehicle vehicle = light.removeVehicle();
+
+ if (vehicle != null) {
+ double crossingTime = getCrossingTimeForVehicle(vehicle);
+ long crossingTimeMs = (long) (crossingTime * 1000);
+
+ Thread.sleep(crossingTimeMs);
+
+ vehicle.addCrossingTime(crossingTime);
+ process.getIntersection().incrementVehiclesSent();
+ process.sendVehicleToNextDestination(vehicle);
+ }
+ } else {
+ // Queue is empty, wait briefly for new vehicles or until time expires
+ Thread.sleep(50);
}
}
}
diff --git a/main/src/main/java/sd/model/Event.java b/main/src/main/java/sd/model/Event.java
deleted file mode 100644
index c25d734..0000000
--- a/main/src/main/java/sd/model/Event.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package sd.model;
-
-import java.io.Serializable;
-
-/**
- * Represents a single event in the discrete event simulation.
- * * An Event is the fundamental unit of action in the simulation. It contains:
- * - A {@code timestamp} (when the event should occur).
- * - A {@link EventType} (what kind of event it is).
- * - Associated {@code data} (e.g., the {@link Vehicle} or {@link TrafficLight} involved).
- * - An optional {@code location} (e.g., the ID of the {@link Intersection}).
- * * Events are {@link Comparable}, allowing them to be sorted in a
- * {@link java.util.PriorityQueue}. The primary sorting key is the
- * {@code timestamp}. If timestamps are equal, {@code EventType} is used
- * as a tie-breaker to ensure a consistent, deterministic order.
- * * Implements {@link Serializable} so events could (in theory) be sent
- * across a network in a distributed simulation.
- */
-public class Event implements Comparable, Serializable {
- private static final long serialVersionUID = 1L;
-
- /**
- * The simulation time (in seconds) when this event is scheduled to occur.
- */
- private final double timestamp;
-
- /**
- * The type of event (e.g., VEHICLE_ARRIVAL, TRAFFIC_LIGHT_CHANGE).
- */
- private final EventType type;
-
- /**
- * The data payload associated with this event.
- * This could be a {@link Vehicle}, {@link TrafficLight}, or null.
- */
- private final Object data;
-
- /**
- * The ID of the location where the event occurs (e.g., "Cr1").
- * Can be null if the event is not location-specific (like VEHICLE_GENERATION).
- */
- private final String location;
-
- /**
- * Constructs a new Event.
- *
- * @param timestamp The simulation time when the event occurs.
- * @param type The {@link EventType} of the event.
- * @param data The associated data (e.g., a Vehicle object).
- * @param location The ID of the location (e.g., an Intersection ID).
- */
- public Event(double timestamp, EventType type, Object data, String location) {
- this.timestamp = timestamp;
- this.type = type;
- this.data = data;
- this.location = location;
- }
-
- /**
- * Convenience constructor for an Event without a specific location.
- *
- * @param timestamp The simulation time when the event occurs.
- * @param type The {@link EventType} of the event.
- * @param data The associated data (e.g., a Vehicle object).
- */
- public Event(double timestamp, EventType type, Object data) {
- this(timestamp, type, data, null);
- }
-
- /**
- * Compares this event to another event for ordering.
- * * Events are ordered primarily by {@link #timestamp} (ascending).
- * If timestamps are identical, they are ordered by {@link #type} (alphabetical)
- * to provide a stable, deterministic tie-breaking mechanism.
- *
- * @param other The other Event to compare against.
- * @return A negative integer if this event comes before {@code other},
- * zero if they are "equal" in sorting (though this is rare),
- * or a positive integer if this event comes after {@code other}.
- */
- @Override
- public int compareTo(Event other) {
- // Primary sort: timestamp (earlier events come first)
- int cmp = Double.compare(this.timestamp, other.timestamp);
- if (cmp == 0) {
- // Tie-breaker: event type (ensures deterministic order)
- return this.type.compareTo(other.type);
- }
- return cmp;
- }
-
- // --- Getters ---
-
- /**
- * @return The simulation time when the event occurs.
- */
- public double getTimestamp() {
- return timestamp;
- }
-
- /**
- * @return The {@link EventType} of the event.
- */
- public EventType getType() {
- return type;
- }
-
- /**
- * @return The data payload (e.g., {@link Vehicle}, {@link TrafficLight}).
- * The caller must cast this to the expected type.
- */
- public Object getData() {
- return data;
- }
-
- /**
- * @return The location ID (e.g., "Cr1"), or null if not applicable.
- */
- public String getLocation() {
- return location;
- }
-
- /**
- * @return A string representation of the event for logging.
- */
- @Override
- public String toString() {
- return String.format("Event{t=%.2f, type=%s, loc=%s}",
- timestamp, type, location);
- }
-}
\ No newline at end of file
diff --git a/main/src/main/java/sd/model/EventType.java b/main/src/main/java/sd/model/EventType.java
deleted file mode 100644
index 5e4d9ee..0000000
--- a/main/src/main/java/sd/model/EventType.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package sd.model;
-
-/**
- * Enumeration representing all possible event types in the discrete event simulation.
- * These types are used by the {@link sd.engine.SimulationEngine} to determine
- * how to process a given {@link Event}.
- */
-public enum EventType {
-
- /**
- * Fired when a {@link Vehicle} arrives at an {@link Intersection}.
- * Data: {@link Vehicle}, Location: Intersection ID
- */
- VEHICLE_ARRIVAL,
-
- /**
- * Fired when a {@link TrafficLight} is scheduled to change its state.
- * Data: {@link TrafficLight}, Location: Intersection ID
- */
- TRAFFIC_LIGHT_CHANGE,
-
- /**
- * Fired when a {@link Vehicle} begins to cross an {@link Intersection}.
- * Data: {@link Vehicle}, Location: Intersection ID
- */
- CROSSING_START,
-
- /**
- * Fired when a {@link Vehicle} finishes crossing an {@link Intersection}.
- * Data: {@link Vehicle}, Location: Intersection ID
- */
- CROSSING_END,
-
- /**
- * Fired when a new {@link Vehicle} should be created and added to the system.
- * Data: null, Location: null
- */
- VEHICLE_GENERATION,
-
- /**
- * Fired periodically to trigger the printing or sending of simulation statistics.
- * Data: null, Location: null
- */
- STATISTICS_UPDATE
-}
\ No newline at end of file
diff --git a/main/src/main/java/sd/model/Intersection.java b/main/src/main/java/sd/model/Intersection.java
index bc8dea7..7d6ff32 100644
--- a/main/src/main/java/sd/model/Intersection.java
+++ b/main/src/main/java/sd/model/Intersection.java
@@ -140,6 +140,16 @@ public class Intersection {
}
}
+ /**
+ * Returns the direction a vehicle should take to reach a given destination.
+ *
+ * @param destination The next destination (e.g., "Cr3", "S").
+ * @return The direction (e.g., "East"), or null if no route is configured.
+ */
+ public String getDirectionForDestination(String destination) {
+ return routing.get(destination);
+ }
+
/**
* Returns the traffic light controlling the given direction.
*
diff --git a/main/src/main/java/sd/model/Message.java b/main/src/main/java/sd/model/Message.java
index 0217070..87e1200 100644
--- a/main/src/main/java/sd/model/Message.java
+++ b/main/src/main/java/sd/model/Message.java
@@ -1,14 +1,15 @@
package sd.model;
-import java.io.Serializable;
import java.util.UUID;
+import sd.protocol.MessageProtocol;
+
/**
* Represents a message exchanged between processes in the distributed simulation.
* Each message has a unique ID, a type, a sender, a destination, and a payload.
- * This class implements {@link Serializable} to allow transmission over the network.
+ * This class implements {@link MessageProtocol} which extends Serializable for network transmission.
*/
-public class Message implements Serializable {
+public class Message implements MessageProtocol {
private static final long serialVersionUID = 1L;
@@ -132,6 +133,17 @@ public class Message implements Serializable {
return (T) payload;
}
+ // Impl MessageProtocol interface
+ @Override
+ public String getSourceNode() {
+ return senderId;
+ }
+
+ @Override
+ public String getDestinationNode() {
+ return destinationId;
+ }
+
@Override
public String toString() {
return String.format("Message[id=%s, type=%s, from=%s, to=%s, timestamp=%d]",
diff --git a/main/src/main/java/sd/model/MessageType.java b/main/src/main/java/sd/model/MessageType.java
index 76cb067..a9f4794 100644
--- a/main/src/main/java/sd/model/MessageType.java
+++ b/main/src/main/java/sd/model/MessageType.java
@@ -19,6 +19,12 @@ public enum MessageType {
*/
STATS_UPDATE,
+ /**
+ * Message to synchronize simulation start time across all processes.
+ * Payload: Start timestamp (long milliseconds)
+ */
+ SIMULATION_START,
+
/**
* Message to synchronize traffic light states between processes.
* Payload: TrafficLight state and timing information
diff --git a/main/src/main/java/sd/model/TrafficLight.java b/main/src/main/java/sd/model/TrafficLight.java
index 1007c03..7cfd393 100644
--- a/main/src/main/java/sd/model/TrafficLight.java
+++ b/main/src/main/java/sd/model/TrafficLight.java
@@ -1,6 +1,8 @@
package sd.model;
+import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Map;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@@ -93,6 +95,12 @@ public class TrafficLight {
* been dequeued (processed) by this light.
*/
private int totalVehiclesProcessed;
+
+ /**
+ * Track when vehicles arrive at this light for wait time calculation.
+ * Maps vehicle ID to arrival timestamp (milliseconds).
+ */
+ private final Map vehicleArrivalTimes;
/**
* Constructs a new TrafficLight.
@@ -115,6 +123,7 @@ public class TrafficLight {
this.greenTime = greenTime;
this.redTime = redTime;
+ this.vehicleArrivalTimes = new HashMap<>();
this.totalVehiclesProcessed = 0;
}
@@ -128,6 +137,7 @@ public class TrafficLight {
lock.lock(); // Acquire the lock
try {
queue.offer(vehicle); // Add vehicle to queue
+ vehicleArrivalTimes.put(vehicle.getId(), System.currentTimeMillis());
vehicleAdded.signalAll(); // Signal (for concurrent models)
} finally {
lock.unlock(); // Always release the lock
@@ -152,6 +162,13 @@ public class TrafficLight {
Vehicle vehicle = queue.poll(); // Remove vehicle from queue
if (vehicle != null) {
totalVehiclesProcessed++;
+
+ // Calculate wait time (time spent in queue)
+ Long arrivalTime = vehicleArrivalTimes.remove(vehicle.getId());
+ if (arrivalTime != null) {
+ double waitTimeSeconds = (System.currentTimeMillis() - arrivalTime) / 1000.0;
+ vehicle.addWaitingTime(waitTimeSeconds);
+ }
}
return vehicle;
}
diff --git a/main/src/main/java/sd/model/Vehicle.java b/main/src/main/java/sd/model/Vehicle.java
index dcf860a..2ee7d23 100644
--- a/main/src/main/java/sd/model/Vehicle.java
+++ b/main/src/main/java/sd/model/Vehicle.java
@@ -12,7 +12,7 @@ import java.util.List;
* - Its complete, pre-determined {@code route} (a list of intersection IDs).
* - Its current position in the route ({@code currentRouteIndex}).
* - Metrics for total time spent waiting at red lights and time spent crossing.
- * * This object is passed around the simulation, primarily inside {@link Event}
+ * * This object is passed around the simulation, primarily inside message
* payloads and stored in {@link TrafficLight} queues.
* * Implements {@link Serializable} so it can be sent between processes
* or nodes (e.g., over a socket in a distributed version of the simulation).
@@ -21,28 +21,28 @@ public class Vehicle implements Serializable {
private static final long serialVersionUID = 1L;
// --- Identity and configuration ---
-
+
/**
* Unique identifier for the vehicle (e.g., "V1", "V2").
*/
private final String id;
-
+
/**
* The type of vehicle (BIKE, LIGHT, HEAVY).
*/
private final VehicleType type;
-
+
/**
* The simulation time (in seconds) when the vehicle was generated.
*/
private final double entryTime;
-
+
/**
* The complete, ordered list of destinations (intersection IDs and the
* final exit "S"). Example: ["Cr1", "Cr3", "S"].
*/
private final List route;
-
+
/**
* An index that tracks the vehicle's progress along its {@link #route}.
* {@code route.get(currentRouteIndex)} is the vehicle's *current*
@@ -51,13 +51,13 @@ public class Vehicle implements Serializable {
private int currentRouteIndex;
// --- Metrics ---
-
+
/**
* The total accumulated time (in seconds) this vehicle has spent
* waiting at red lights.
*/
private double totalWaitingTime;
-
+
/**
* The total accumulated time (in seconds) this vehicle has spent
* actively crossing intersections.
@@ -67,10 +67,11 @@ public class Vehicle implements Serializable {
/**
* Constructs a new Vehicle.
*
- * @param id The unique ID for the vehicle.
- * @param type The {@link VehicleType}.
+ * @param id The unique ID for the vehicle.
+ * @param type The {@link VehicleType}.
* @param entryTime The simulation time when the vehicle is created.
- * @param route The complete list of destination IDs (e.t., ["Cr1", "Cr2", "S"]).
+ * @param route The complete list of destination IDs (e.t., ["Cr1", "Cr2",
+ * "S"]).
*/
public Vehicle(String id, VehicleType type, double entryTime, List route) {
this.id = id;
@@ -90,8 +91,8 @@ public class Vehicle implements Serializable {
* to set its *next* destination before it is queued.
*
* @return {@code true} if there is still at least one more destination
- * in the route, {@code false} if the vehicle has passed its
- * final destination.
+ * in the route, {@code false} if the vehicle has passed its
+ * final destination.
*/
public boolean advanceRoute() {
currentRouteIndex++;
@@ -103,7 +104,7 @@ public class Vehicle implements Serializable {
* the vehicle is heading towards.
*
* @return The ID of the current destination (e.g., "Cr1"), or
- * {@code null} if the route is complete.
+ * {@code null} if the route is complete.
*/
public String getCurrentDestination() {
return (currentRouteIndex < route.size()) ? route.get(currentRouteIndex) : null;
@@ -113,7 +114,7 @@ public class Vehicle implements Serializable {
* Checks if the vehicle has completed its entire route.
*
* @return {@code true} if the route index is at or past the end
- * of the route list, {@code false} otherwise.
+ * of the route list, {@code false} otherwise.
*/
public boolean hasReachedEnd() {
return currentRouteIndex >= route.size();
@@ -151,7 +152,8 @@ public class Vehicle implements Serializable {
}
/**
- * @return The current index pointing to the vehicle's destination in its route list.
+ * @return The current index pointing to the vehicle's destination in its route
+ * list.
*/
public int getCurrentRouteIndex() {
return currentRouteIndex;
@@ -199,7 +201,7 @@ public class Vehicle implements Serializable {
*
* @param currentTime The current simulation time.
* @return The total elapsed time (in seconds) since the vehicle
- * was generated ({@code currentTime - entryTime}).
+ * was generated ({@code currentTime - entryTime}).
*/
public double getTotalTravelTime(double currentTime) {
return currentTime - entryTime;
@@ -211,8 +213,7 @@ public class Vehicle implements Serializable {
@Override
public String toString() {
return String.format(
- "Vehicle{id='%s', type=%s, next='%s', route=%s}",
- id, type, getCurrentDestination(), route
- );
+ "Vehicle{id='%s', type=%s, next='%s', route=%s}",
+ id, type, getCurrentDestination(), route);
}
}
\ No newline at end of file
diff --git a/main/src/main/java/sd/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java
index 446281c..c65680b 100644
--- a/main/src/main/java/sd/protocol/SocketConnection.java
+++ b/main/src/main/java/sd/protocol/SocketConnection.java
@@ -4,7 +4,6 @@ import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
@@ -127,7 +126,7 @@ public class SocketConnection implements Closeable {
* @param message The "envelope" (which contains the Vehicle) to be sent.
* @throws IOException If writing to the stream fails or socket is not connected.
*/
- public void sendMessage(MessageProtocol message) throws IOException {
+ public synchronized void sendMessage(MessageProtocol message) throws IOException {
if (socket == null || !socket.isConnected()) {
throw new IOException("Socket is not connected");
}
@@ -172,8 +171,8 @@ public class SocketConnection implements Closeable {
byte[] data = new byte[length];
dataIn.readFully(data);
- // Deserialize do JSON
- return serializer.deserialize(data, MessageProtocol.class);
+ // Deserialize do JSON - use concrete Message class, not interface
+ return serializer.deserialize(data, sd.model.Message.class);
} catch (SerializationException e) {
throw new IOException("Failed to deserialize message", e);
diff --git a/main/src/main/java/sd/serialization/SerializationExample.java b/main/src/main/java/sd/serialization/SerializationExample.java
deleted file mode 100644
index f6bd817..0000000
--- a/main/src/main/java/sd/serialization/SerializationExample.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package sd.serialization;
-
-import sd.model.Message;
-import sd.model.MessageType;
-import sd.model.Vehicle;
-import sd.model.VehicleType;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Demonstration of JSON serialization usage in the traffic simulation system.
- *
- * This class shows practical examples of how to use JSON (Gson) serialization
- * for network communication between simulation processes.
- */
-public class SerializationExample {
-
- public static void main(String[] args) {
- System.out.println("=== JSON Serialization Example ===\n");
-
- // Create a sample vehicle
- List route = Arrays.asList("Cr1", "Cr2", "Cr5", "S");
- Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5, route);
- vehicle.addWaitingTime(2.3);
- vehicle.addCrossingTime(1.2);
-
- // Create a message containing the vehicle
- Message message = new Message(
- MessageType.VEHICLE_TRANSFER,
- "Cr1",
- "Cr2",
- vehicle
- );
-
- // ===== JSON Serialization =====
- demonstrateJsonSerialization(message);
-
- // ===== Factory Usage =====
- demonstrateFactoryUsage(message);
-
- // ===== Performance Test =====
- performanceTest(message);
- }
-
- private static void demonstrateJsonSerialization(Message message) {
- System.out.println("--- JSON Serialization ---");
-
- try {
- // Create JSON serializer with pretty printing for readability
- MessageSerializer serializer = new JsonMessageSerializer(true);
-
- // Serialize to bytes
- byte[] data = serializer.serialize(message);
-
- // Display the JSON
- String json = new String(data);
- System.out.println("Serialized JSON (" + data.length + " bytes):");
- System.out.println(json);
-
- // Deserialize back
- Message deserialized = serializer.deserialize(data, Message.class);
- System.out.println("\nDeserialized: " + deserialized);
- System.out.println("✓ JSON serialization successful\n");
-
- } catch (SerializationException e) {
- System.err.println("❌ JSON serialization failed: " + e.getMessage());
- }
- }
-
- private static void demonstrateFactoryUsage(Message message) {
- System.out.println("--- Using SerializerFactory ---");
-
- try {
- // Get default serializer (JSON)
- MessageSerializer serializer = SerializerFactory.createDefault();
- System.out.println("Default serializer: " + serializer.getName());
-
- // Use it
- byte[] data = serializer.serialize(message);
- Message deserialized = serializer.deserialize(data, Message.class);
-
- System.out.println("Message type: " + deserialized.getType());
- System.out.println("From: " + deserialized.getSenderId() +
- " → To: " + deserialized.getDestinationId());
- System.out.println("✓ Factory usage successful\n");
-
- } catch (SerializationException e) {
- System.err.println("❌ Factory usage failed: " + e.getMessage());
- }
- }
-
- private static void performanceTest(Message message) {
- System.out.println("--- Performance Test ---");
-
- int iterations = 1000;
-
- try {
- MessageSerializer compactSerializer = new JsonMessageSerializer(false);
- MessageSerializer prettySerializer = new JsonMessageSerializer(true);
-
- // Warm up
- for (int i = 0; i < 100; i++) {
- compactSerializer.serialize(message);
- }
-
- // Test compact JSON
- long compactStart = System.nanoTime();
- byte[] compactData = null;
- for (int i = 0; i < iterations; i++) {
- compactData = compactSerializer.serialize(message);
- }
- long compactTime = System.nanoTime() - compactStart;
-
- // Test pretty JSON
- byte[] prettyData = prettySerializer.serialize(message);
-
- // Results
- System.out.println("Iterations: " + iterations);
- System.out.println("\nJSON Compact:");
- System.out.println(" Size: " + compactData.length + " bytes");
- System.out.println(" Time: " + (compactTime / 1_000_000.0) + " ms total");
- System.out.println(" Avg: " + (compactTime / iterations / 1_000.0) + " μs/operation");
-
- System.out.println("\nJSON Pretty-Print:");
- System.out.println(" Size: " + prettyData.length + " bytes");
- System.out.println(" Size increase: " +
- String.format("%.1f%%", ((double)prettyData.length / compactData.length - 1) * 100));
-
- } catch (SerializationException e) {
- System.err.println("❌ Performance test failed: " + e.getMessage());
- }
- }
-}
diff --git a/main/src/main/java/sd/util/StatisticsCollector.java b/main/src/main/java/sd/util/StatisticsCollector.java
deleted file mode 100644
index fa8f8bd..0000000
--- a/main/src/main/java/sd/util/StatisticsCollector.java
+++ /dev/null
@@ -1,379 +0,0 @@
-package sd.util;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import sd.config.SimulationConfig;
-import sd.model.Intersection;
-import sd.model.Vehicle;
-import sd.model.VehicleType;
-
-/**
- * Collects, manages, and reports statistics throughout the simulation.
- * * This class acts as the central bookkeeper for simulation metrics. It tracks:
- * - Overall system statistics (total vehicles, completion time, wait time).
- * - Per-vehicle-type statistics (counts, average wait time by type).
- * - Per-intersection statistics (arrivals, departures).
- * * It also maintains "in-flight" data, such as the arrival time of a
- * vehicle at its *current* intersection, which is necessary to
- * calculate waiting time when the vehicle later departs.
- */
-public class StatisticsCollector {
-
- // --- Vehicle tracking (for in-flight vehicles) ---
-
- /**
- * Tracks the simulation time when a vehicle arrives at its *current* intersection.
- * This is used later to calculate waiting time (Depart_Time - Arrive_Time).
- * Key: Vehicle ID (String)
- * Value: Arrival Time (Double)
- */
- private final Map vehicleArrivalTimes;
-
- /**
- * Tracks the sequence of intersections a vehicle has visited.
- * Key: Vehicle ID (String)
- * Value: List of Intersection IDs (String)
- */
- private final Map> vehicleIntersectionHistory;
-
- // --- Overall system statistics ---
-
- /** Total number of vehicles created by the {@link VehicleGenerator}. */
- private int totalVehiclesGenerated;
-
- /** Total number of vehicles that have reached their final destination ("S"). */
- private int totalVehiclesCompleted;
-
- /** The sum of all *completed* vehicles' total travel times. Used for averaging. */
- private double totalSystemTime;
-
- /** The sum of all *completed* vehicles' total waiting times. Used for averaging. */
- private double totalWaitingTime;
-
- // --- Per-vehicle-type statistics ---
-
- /**
- * Tracks the total number of vehicles generated, broken down by type.
- * Key: {@link VehicleType}
- * Value: Count (Integer)
- */
- private final Map vehicleTypeCount;
-
- /**
- * Tracks the total waiting time, broken down by vehicle type.
- * Key: {@link VehicleType}
- * Value: Total Wait Time (Double)
- */
- private final Map vehicleTypeWaitTime;
-
- // --- Per-intersection statistics ---
-
- /**
- * A map to hold statistics objects for each intersection.
- * Key: Intersection ID (String)
- * Value: {@link IntersectionStats} object
- */
- private final Map intersectionStats;
-
- /**
- * Constructs a new StatisticsCollector.
- * Initializes all maps and counters.
- *
- * @param config The {@link SimulationConfig} (not currently used, but
- * could be for configuration-dependent stats).
- */
- public StatisticsCollector(SimulationConfig config) {
- this.vehicleArrivalTimes = new HashMap<>();
- this.vehicleIntersectionHistory = new HashMap<>();
- this.totalVehiclesGenerated = 0;
- this.totalVehiclesCompleted = 0;
- this.totalSystemTime = 0.0;
- this.totalWaitingTime = 0.0;
- this.vehicleTypeCount = new HashMap<>();
- this.vehicleTypeWaitTime = new HashMap<>();
- this.intersectionStats = new HashMap<>();
-
- // Initialize vehicle type counters to 0
- for (VehicleType type : VehicleType.values()) {
- vehicleTypeCount.put(type, 0);
- vehicleTypeWaitTime.put(type, 0.0);
- }
- }
-
- /**
- * Records that a new vehicle has been generated.
- * This is called by the {@link sd.engine.SimulationEngine}
- * during a {@code VEHICLE_GENERATION} event.
- *
- * @param vehicle The {@link Vehicle} that was just created.
- * @param currentTime The simulation time of the event.
- */
- public void recordVehicleGeneration(Vehicle vehicle, double currentTime) {
- totalVehiclesGenerated++;
-
- // Track by vehicle type
- VehicleType type = vehicle.getType();
- vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1);
-
- // Initialize history tracking for this vehicle
- vehicleIntersectionHistory.put(vehicle.getId(), new ArrayList<>());
- }
-
- /**
- * Records that a vehicle has arrived at an intersection queue.
- * This is called by the {@link sd.engine.SimulationEngine}
- * during a {@code VEHICLE_ARRIVAL} event.
- *
- * @param vehicle The {@link Vehicle} that arrived.
- * @param intersectionId The ID of the intersection it arrived at.
- * @param currentTime The simulation time of the arrival.
- */
- public void recordVehicleArrival(Vehicle vehicle, String intersectionId, double currentTime) {
- // Store arrival time - this is the "start waiting" time
- vehicleArrivalTimes.put(vehicle.getId(), currentTime);
-
- // Track intersection history
- List history = vehicleIntersectionHistory.get(vehicle.getId());
- if (history != null) {
- history.add(intersectionId);
- }
-
- // Update per-intersection statistics
- getOrCreateIntersectionStats(intersectionId).recordArrival();
- }
-
- /**
- * Records that a vehicle has completed its route and exited the system.
- * This is where final metrics for the vehicle are aggregated.
- * This is called by the {@link sd.engine.SimulationEngine}
- * when a vehicle reaches destination "S".
- *
- * @param vehicle The {@link Vehicle} that is exiting.
- * @param currentTime The simulation time of the exit.
- */
- public void recordVehicleExit(Vehicle vehicle, double currentTime) {
- totalVehiclesCompleted++;
-
- // Calculate and aggregate total system time
- double systemTime = vehicle.getTotalTravelTime(currentTime);
- totalSystemTime += systemTime;
-
- // Aggregate waiting time
- double waitTime = vehicle.getTotalWaitingTime();
- totalWaitingTime += waitTime;
-
- // Aggregate waiting time by vehicle type
- VehicleType type = vehicle.getType();
- vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime);
-
- // Clean up tracking maps to save memory
- vehicleArrivalTimes.remove(vehicle.getId());
- vehicleIntersectionHistory.remove(vehicle.getId());
- }
-
- /**
- * Gets the time a vehicle arrived at its *current* intersection.
- * This is used by the {@link sd.engine.SimulationEngine} to calculate
- * wait time just before the vehicle crosses.
- *
- * @param vehicle The {@link Vehicle} to check.
- * @return The arrival time, or 0.0 if not found.
- */
- public double getArrivalTime(Vehicle vehicle) {
- return vehicleArrivalTimes.getOrDefault(vehicle.getId(), 0.0);
- }
-
- /**
- * Prints a "snapshot" of the current simulation statistics.
- * This is called periodically by the {@link sd.engine.SimulationEngine}
- * during a {@code STATISTICS_UPDATE} event.
- *
- * @param intersections A map of all intersections (to get queue data).
- * @param currentTime The current simulation time.
- */
- public void printCurrentStatistics(Map intersections, double currentTime) {
- System.out.printf("--- Statistics at t=%.2f ---%n", currentTime);
- System.out.printf("Vehicles: Generated=%d, Completed=%d, In-System=%d%n",
- totalVehiclesGenerated,
- totalVehiclesCompleted,
- totalVehiclesGenerated - totalVehiclesCompleted);
-
- if (totalVehiclesCompleted > 0) {
- System.out.printf("Average System Time (so far): %.2fs%n", totalSystemTime / totalVehiclesCompleted);
- System.out.printf("Average Waiting Time (so far): %.2fs%n", totalWaitingTime / totalVehiclesCompleted);
- }
-
- // Print per-intersection queue sizes
- System.out.println("\nIntersection Queues:");
- for (Map.Entry entry : intersections.entrySet()) {
- String id = entry.getKey();
- Intersection intersection = entry.getValue();
- System.out.printf(" %s: Queue=%d, Received=%d, Sent=%d%n",
- id,
- intersection.getTotalQueueSize(),
- intersection.getTotalVehiclesReceived(),
- intersection.getTotalVehiclesSent());
- }
- }
-
- /**
- * Prints the final simulation summary statistics at the end of the run.
- *
- * @param intersections A map of all intersections.
- * @param currentTime The final simulation time.
- */
- public void printFinalStatistics(Map intersections, double currentTime) {
- System.out.println("\n=== SIMULATION SUMMARY ===");
- System.out.printf("Duration: %.2f seconds%n", currentTime);
- System.out.printf("Total Vehicles Generated: %d%n", totalVehiclesGenerated);
- System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesCompleted);
- System.out.printf("Vehicles Still in System: %d%n", totalVehiclesGenerated - totalVehiclesCompleted);
-
- // Overall averages
- if (totalVehiclesCompleted > 0) {
- System.out.printf("%nAVERAGE METRICS (for completed vehicles):%n");
- System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesCompleted);
- System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesCompleted);
- System.out.printf(" Throughput: %.2f vehicles/second%n", totalVehiclesCompleted / currentTime);
- }
-
- // Vehicle type breakdown
- System.out.println("\nVEHICLE TYPE DISTRIBUTION:");
- for (VehicleType type : VehicleType.values()) {
- int count = vehicleTypeCount.get(type);
- if (count > 0) {
- double percentage = (count * 100.0) / totalVehiclesGenerated;
- // Calculate avg wait *only* for this type
- // This assumes all generated vehicles of this type *completed*
- // A more accurate way would be to track completed vehicle types
- double avgWait = vehicleTypeWaitTime.get(type) / count;
- System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n",
- type, count, percentage, avgWait);
- }
- }
-
- // Per-intersection statistics
- System.out.println("\nINTERSECTION STATISTICS:");
- for (Map.Entry entry : intersections.entrySet()) {
- String id = entry.getKey();
- Intersection intersection = entry.getValue();
-
- System.out.printf(" %s:%n", id);
- System.out.printf(" Vehicles Received: %d%n", intersection.getTotalVehiclesReceived());
- System.out.printf(" Vehicles Sent: %d%n", intersection.getTotalVehiclesSent());
- System.out.printf(" Final Queue Size: %d%n", intersection.getTotalQueueSize());
-
- // Traffic light details
- intersection.getTrafficLights().forEach(light -> {
- System.out.printf(" Light %s: State=%s, Queue=%d, Processed=%d%n",
- light.getDirection(),
- light.getState(),
- light.getQueueSize(),
- light.getTotalVehiclesProcessed());
- });
- }
-
- // System health indicators
- System.out.println("\nSYSTEM HEALTH:");
- int totalQueuedVehicles = intersections.values().stream()
- .mapToInt(Intersection::getTotalQueueSize)
- .sum();
- System.out.printf(" Total Queued Vehicles (at end): %d%n", totalQueuedVehicles);
-
- if (totalVehiclesGenerated > 0) {
- double completionRate = (totalVehiclesCompleted * 100.0) / totalVehiclesGenerated;
- System.out.printf(" Completion Rate: %.1f%%%n", completionRate);
- }
- }
-
- /**
- * Gets or creates the statistics object for a given intersection.
- * Uses {@code computeIfAbsent} for efficient, thread-safe-like instantiation.
- *
- * @param intersectionId The ID of the intersection.
- * @return The {@link IntersectionStats} object for that ID.
- */
- private IntersectionStats getOrCreateIntersectionStats(String intersectionId) {
- // If 'intersectionId' is not in the map, create a new IntersectionStats()
- // and put it in the map, then return it.
- // Otherwise, just return the one that's already there.
- return intersectionStats.computeIfAbsent(intersectionId, k -> new IntersectionStats());
- }
-
- /**
- * Inner class to track per-intersection statistics.
- * This is a simple data holder.
- */
- private static class IntersectionStats {
- private int totalArrivals;
- private int totalDepartures;
-
- public IntersectionStats() {
- this.totalArrivals = 0;
- this.totalDepartures = 0;
- }
-
- public void recordArrival() {
- totalArrivals++;
- }
-
- public void recordDeparture() {
- totalDepartures++;
- }
-
- public int getTotalArrivals() {
- return totalArrivals;
- }
-
- public int getTotalDepartures() {
- return totalDepartures;
- }
- }
-
- // --- Public Getters for Final Statistics ---
-
- /**
- * @return Total vehicles generated during the simulation.
- */
- public int getTotalVehiclesGenerated() {
- return totalVehiclesGenerated;
- }
-
- /**
- * @return Total vehicles that completed their route.
- */
- public int getTotalVehiclesCompleted() {
- return totalVehiclesCompleted;
- }
-
- /**
- * @return The sum of all travel times for *completed* vehicles.
- */
- public double getTotalSystemTime() {
- return totalSystemTime;
- }
-
- /**
- * @return The sum of all waiting times for *completed* vehicles.
- */
- public double getTotalWaitingTime() {
- return totalWaitingTime;
- }
-
- /**
- * @return The average travel time for *completed* vehicles.
- */
- public double getAverageSystemTime() {
- return totalVehiclesCompleted > 0 ? totalSystemTime / totalVehiclesCompleted : 0.0;
- }
-
- /**
- * @return The average waiting time for *completed* vehicles.
- */
- public double getAverageWaitingTime() {
- return totalVehiclesCompleted > 0 ? totalWaitingTime / totalVehiclesCompleted : 0.0;
- }
-}
\ No newline at end of file
diff --git a/main/src/main/resources/dashboard.css b/main/src/main/resources/dashboard.css
new file mode 100644
index 0000000..5cd7e57
--- /dev/null
+++ b/main/src/main/resources/dashboard.css
@@ -0,0 +1,142 @@
+/* Global Styles */
+.root {
+ -fx-background-color: #f4f7f6;
+ -fx-font-family: 'Segoe UI', sans-serif;
+}
+
+/* Header */
+.header {
+ -fx-background-color: linear-gradient(to right, #2c3e50, #4ca1af);
+ -fx-padding: 20;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 10, 0, 0, 5);
+}
+
+.header-title {
+ -fx-font-size: 28px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.header-subtitle {
+ -fx-font-size: 16px;
+ -fx-text-fill: #ecf0f1;
+}
+
+/* Buttons */
+.button-start {
+ -fx-background-color: #2ecc71;
+ -fx-text-fill: white;
+ -fx-font-weight: bold;
+ -fx-padding: 10 20;
+ -fx-background-radius: 5;
+ -fx-cursor: hand;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
+}
+
+.button-start:hover {
+ -fx-background-color: #27ae60;
+}
+
+.button-start:disabled {
+ -fx-background-color: #95a5a6;
+ -fx-opacity: 0.7;
+}
+
+.button-stop {
+ -fx-background-color: #e74c3c;
+ -fx-text-fill: white;
+ -fx-font-weight: bold;
+ -fx-padding: 10 20;
+ -fx-background-radius: 5;
+ -fx-cursor: hand;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
+}
+
+.button-stop:hover {
+ -fx-background-color: #c0392b;
+}
+
+.button-stop:disabled {
+ -fx-background-color: #95a5a6;
+ -fx-opacity: 0.7;
+}
+
+/* Cards / Panels */
+.card {
+ -fx-background-color: white;
+ -fx-background-radius: 8;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.05), 10, 0, 0, 2);
+ -fx-padding: 0;
+}
+
+.card-header {
+ -fx-background-color: #ecf0f1;
+ -fx-background-radius: 8 8 0 0;
+ -fx-padding: 10 15;
+ -fx-border-color: #bdc3c7;
+ -fx-border-width: 0 0 1 0;
+}
+
+.card-title {
+ -fx-font-size: 16px;
+ -fx-font-weight: bold;
+ -fx-text-fill: #2c3e50;
+}
+
+.card-content {
+ -fx-padding: 15;
+}
+
+/* Statistics Grid */
+.stat-label {
+ -fx-font-size: 14px;
+ -fx-text-fill: #7f8c8d;
+}
+
+.stat-value {
+ -fx-font-size: 20px;
+ -fx-font-weight: bold;
+ -fx-text-fill: #2980b9;
+}
+
+/* Tables */
+.table-view {
+ -fx-background-color: transparent;
+ -fx-border-color: transparent;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: #ecf0f1;
+ -fx-border-color: #bdc3c7;
+ -fx-border-width: 0 0 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-text-fill: #2c3e50;
+ -fx-font-weight: bold;
+}
+
+.table-row-cell {
+ -fx-background-color: white;
+ -fx-border-color: transparent;
+}
+
+.table-row-cell:odd {
+ -fx-background-color: #f9f9f9;
+}
+
+.table-row-cell:selected {
+ -fx-background-color: #3498db;
+ -fx-text-fill: white;
+}
+
+/* Footer */
+.footer {
+ -fx-background-color: #34495e;
+ -fx-padding: 10 20;
+}
+
+.footer-text {
+ -fx-text-fill: #ecf0f1;
+ -fx-font-size: 12px;
+}
diff --git a/main/src/main/resources/network_config.json b/main/src/main/resources/network_config.json
new file mode 100644
index 0000000..8bbd50a
--- /dev/null
+++ b/main/src/main/resources/network_config.json
@@ -0,0 +1,43 @@
+{
+ "intersections": [
+ {
+ "id": "Cr1",
+ "lights": ["East", "South"],
+ "routes": {
+ "Cr2": "East",
+ "Cr4": "South"
+ }
+ },
+ {
+ "id": "Cr2",
+ "lights": ["West", "East", "South"],
+ "routes": {
+ "Cr1": "West",
+ "Cr3": "East",
+ "Cr5": "South"
+ }
+ },
+ {
+ "id": "Cr3",
+ "lights": ["West", "South"],
+ "routes": {
+ "Cr2": "West",
+ "S": "South"
+ }
+ },
+ {
+ "id": "Cr4",
+ "lights": ["East"],
+ "routes": {
+ "Cr5": "East"
+ }
+ },
+ {
+ "id": "Cr5",
+ "lights": ["East"],
+ "routes": {
+ "S": "East"
+ }
+ }
+ ]
+}
diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties
index ffd421d..825476d 100644
--- a/main/src/main/resources/simulation.properties
+++ b/main/src/main/resources/simulation.properties
@@ -46,54 +46,44 @@ simulation.arrival.fixed.interval=2.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight...=
-# Intersection 1
-trafficlight.Cr1.North.green=30.0
-trafficlight.Cr1.North.red=30.0
-trafficlight.Cr1.South.green=30.0
-trafficlight.Cr1.South.red=30.0
-trafficlight.Cr1.East.green=30.0
-trafficlight.Cr1.East.red=30.0
-trafficlight.Cr1.West.green=30.0
-trafficlight.Cr1.West.red=30.0
+# Intersection 1 (Entry point - balanced)
+trafficlight.Cr1.South.green=20.0
+trafficlight.Cr1.South.red=40.0
+trafficlight.Cr1.East.green=20.0
+trafficlight.Cr1.East.red=40.0
+trafficlight.Cr1.West.green=20.0
+trafficlight.Cr1.West.red=40.0
-# Intersection 2
-trafficlight.Cr2.North.green=25.0
-trafficlight.Cr2.North.red=35.0
-trafficlight.Cr2.South.green=25.0
-trafficlight.Cr2.South.red=35.0
-trafficlight.Cr2.East.green=35.0
-trafficlight.Cr2.East.red=25.0
-trafficlight.Cr2.West.green=35.0
-trafficlight.Cr2.West.red=25.0
+# Intersection 2 (Main hub - shorter cycles, favor East-West)
+trafficlight.Cr2.South.green=12.0
+trafficlight.Cr2.South.red=36.0
+trafficlight.Cr2.East.green=18.0
+trafficlight.Cr2.East.red=30.0
+trafficlight.Cr2.West.green=18.0
+trafficlight.Cr2.West.red=30.0
-# Intersection 3
-trafficlight.Cr3.North.green=30.0
-trafficlight.Cr3.North.red=30.0
-trafficlight.Cr3.South.green=30.0
+# Intersection 3 (Path to exit - favor East)
+trafficlight.Cr3.South.green=15.0
trafficlight.Cr3.South.red=30.0
-trafficlight.Cr3.East.green=30.0
-trafficlight.Cr3.East.red=30.0
-trafficlight.Cr3.West.green=30.0
+trafficlight.Cr3.East.green=20.0
+trafficlight.Cr3.East.red=25.0
+trafficlight.Cr3.West.green=15.0
trafficlight.Cr3.West.red=30.0
-# Intersection 4
-trafficlight.Cr4.North.green=30.0
-trafficlight.Cr4.North.red=30.0
-trafficlight.Cr4.South.green=30.0
+# Intersection 4 (Favor East toward Cr5)
+trafficlight.Cr4.South.green=15.0
trafficlight.Cr4.South.red=30.0
-trafficlight.Cr4.East.green=30.0
-trafficlight.Cr4.East.red=30.0
-trafficlight.Cr4.West.green=30.0
+trafficlight.Cr4.East.green=20.0
+trafficlight.Cr4.East.red=25.0
+trafficlight.Cr4.West.green=15.0
trafficlight.Cr4.West.red=30.0
-# Intersection 5
-trafficlight.Cr5.North.green=30.0
-trafficlight.Cr5.North.red=30.0
-trafficlight.Cr5.South.green=30.0
+# Intersection 5 (Near exit - favor East)
+trafficlight.Cr5.South.green=15.0
trafficlight.Cr5.South.red=30.0
-trafficlight.Cr5.East.green=30.0
-trafficlight.Cr5.East.red=30.0
-trafficlight.Cr5.West.green=30.0
+trafficlight.Cr5.East.green=22.0
+trafficlight.Cr5.East.red=23.0
+trafficlight.Cr5.West.green=15.0
trafficlight.Cr5.West.red=30.0
# === VEHICLE CONFIGURATION ===
@@ -118,4 +108,4 @@ vehicle.travel.time.heavy.multiplier=2.0
# === STATISTICS ===
# Interval between dashboard updates (seconds)
-statistics.update.interval=10.0
+statistics.update.interval=1.0
diff --git a/main/src/test/java/SimulationTest.java b/main/src/test/java/SimulationTest.java
index b3a49df..09df4bf 100644
--- a/main/src/test/java/SimulationTest.java
+++ b/main/src/test/java/SimulationTest.java
@@ -6,120 +6,77 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import sd.config.SimulationConfig;
-import sd.engine.SimulationEngine;
-import sd.model.Event;
-import sd.model.EventType;
import sd.model.Intersection;
import sd.model.TrafficLight;
import sd.model.TrafficLightState;
import sd.model.Vehicle;
import sd.model.VehicleType;
-import sd.util.StatisticsCollector;
import sd.util.VehicleGenerator;
/**
* Basic tests for the simulation components.
*/
class SimulationTest {
-
+
@Test
void testConfigurationLoading() throws IOException {
SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
-
+
assertEquals(60.0, config.getSimulationDuration());
assertEquals("POISSON", config.getArrivalModel());
assertEquals(0.5, config.getArrivalRate());
- assertEquals(10.0, config.getStatisticsUpdateInterval());
+ assertEquals(1.0, config.getStatisticsUpdateInterval());
}
-
+
@Test
void testVehicleGeneration() throws IOException {
SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
VehicleGenerator generator = new VehicleGenerator(config);
-
+
Vehicle vehicle = generator.generateVehicle("TEST1", 0.0);
-
+
assertNotNull(vehicle);
assertEquals("TEST1", vehicle.getId());
assertNotNull(vehicle.getType());
assertNotNull(vehicle.getRoute());
assertTrue(!vehicle.getRoute().isEmpty());
}
-
- @Test
- void testEventOrdering() {
- Event e1 = new Event(5.0, EventType.VEHICLE_ARRIVAL, null, "Cr1");
- Event e2 = new Event(3.0, EventType.VEHICLE_ARRIVAL, null, "Cr2");
- Event e3 = new Event(7.0, EventType.TRAFFIC_LIGHT_CHANGE, null, "Cr1");
-
- assertTrue(e2.compareTo(e1) < 0); // e2 should come before e1
- assertTrue(e1.compareTo(e3) < 0); // e1 should come before e3
- }
-
+
@Test
void testIntersectionVehicleQueue() {
Intersection intersection = new Intersection("TestCr");
TrafficLight light = new TrafficLight("TestCr-N", "North", 30.0, 30.0);
-
+
intersection.addTrafficLight(light);
-
- Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0,
- java.util.Arrays.asList("TestCr", "S"));
-
+
+ Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0,
+ java.util.Arrays.asList("TestCr", "S"));
+
intersection.configureRoute("S", "North");
-
+
// Advance route to next destination
v1.advanceRoute();
-
+
intersection.receiveVehicle(v1);
-
+
assertEquals(1, intersection.getTotalQueueSize());
assertEquals(1, intersection.getTotalVehiclesReceived());
}
-
+
@Test
void testTrafficLightStateChange() {
TrafficLight light = new TrafficLight("Test-Light", "North", 30.0, 30.0);
-
+
assertEquals(TrafficLightState.RED, light.getState());
-
+
light.changeState(TrafficLightState.GREEN);
assertEquals(TrafficLightState.GREEN, light.getState());
-
+
light.changeState(TrafficLightState.RED);
assertEquals(TrafficLightState.RED, light.getState());
}
-
- @Test
- void testSimulationEngineInitialization() throws IOException {
- SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
- SimulationEngine engine = new SimulationEngine(config);
-
- engine.initialize();
-
- assertNotNull(engine.getIntersections());
- assertEquals(5, engine.getIntersections().size());
-
- // Check that intersections have traffic lights
- for (Intersection intersection : engine.getIntersections().values()) {
- assertEquals(3, intersection.getTrafficLights().size()); // North, South, East, West
- }
- }
-
- @Test
- void testStatisticsCollector() throws IOException {
- SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
- StatisticsCollector collector = new StatisticsCollector(config);
-
- Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0,
- java.util.Arrays.asList("Cr1", "Cr2", "S"));
-
- collector.recordVehicleGeneration(v1, 0.0);
- assertEquals(1, collector.getTotalVehiclesGenerated());
-
- collector.recordVehicleArrival(v1, "Cr1", 1.0);
-
- collector.recordVehicleExit(v1, 10.0);
- assertEquals(1, collector.getTotalVehiclesCompleted());
- }
+
+ // Removed testSimulationEngineInitialization as SimulationEngine has been
+ // removed.
+
}
diff --git a/main/src/test/java/sd/TrafficLightCoordinationTest.java b/main/src/test/java/sd/TrafficLightCoordinationTest.java
index 347864d..cbc2d04 100644
--- a/main/src/test/java/sd/TrafficLightCoordinationTest.java
+++ b/main/src/test/java/sd/TrafficLightCoordinationTest.java
@@ -44,7 +44,7 @@ public class TrafficLightCoordinationTest {
@Test
public void testOnlyOneGreenLightAtATime() throws InterruptedException {
System.out.println("\n=== Testing Traffic Light Mutual Exclusion ===");
-
+
// Start the intersection
Thread intersectionThread = new Thread(() -> {
try {
@@ -59,55 +59,55 @@ public class TrafficLightCoordinationTest {
AtomicInteger maxGreenSimultaneously = new AtomicInteger(0);
AtomicInteger violationCount = new AtomicInteger(0);
List violations = new ArrayList<>();
-
+
// Monitor for 10 seconds
long endTime = System.currentTimeMillis() + 10000;
-
+
while (System.currentTimeMillis() < endTime) {
int greenCount = 0;
StringBuilder currentState = new StringBuilder("States: ");
-
+
for (TrafficLight light : intersectionProcess.getIntersection().getTrafficLights()) {
TrafficLightState state = light.getState();
currentState.append(light.getDirection()).append("=").append(state).append(" ");
-
+
if (state == TrafficLightState.GREEN) {
greenCount++;
}
}
-
+
// Update maximum simultaneous green lights
if (greenCount > maxGreenSimultaneously.get()) {
maxGreenSimultaneously.set(greenCount);
}
-
+
// Check for violations (more than one green)
if (greenCount > 1) {
violationCount.incrementAndGet();
- String violation = String.format("[VIOLATION] %d lights GREEN simultaneously: %s",
- greenCount, currentState.toString());
+ String violation = String.format("[VIOLATION] %d lights GREEN simultaneously: %s",
+ greenCount, currentState.toString());
violations.add(violation);
System.err.println(violation);
}
-
+
Thread.sleep(50); // Check every 50ms
}
-
+
System.out.println("\n=== Test Results ===");
System.out.println("Maximum simultaneous GREEN lights: " + maxGreenSimultaneously.get());
System.out.println("Total violations detected: " + violationCount.get());
-
+
if (!violations.isEmpty()) {
System.err.println("\nViolation details:");
violations.forEach(System.err::println);
}
-
+
// Assert that we never had more than one green light
- assertEquals(0, violationCount.get(),
- "Traffic light coordination violated! Multiple lights were GREEN simultaneously.");
- assertTrue(maxGreenSimultaneously.get() <= 1,
- "At most ONE light should be GREEN at any time. Found: " + maxGreenSimultaneously.get());
-
+ assertEquals(0, violationCount.get(),
+ "Traffic light coordination violated! Multiple lights were GREEN simultaneously.");
+ assertTrue(maxGreenSimultaneously.get() <= 1,
+ "At most ONE light should be GREEN at any time. Found: " + maxGreenSimultaneously.get());
+
System.out.println("\nTraffic light coordination working correctly!");
}
@@ -118,7 +118,7 @@ public class TrafficLightCoordinationTest {
@Test
public void testAllLightsGetGreenTime() throws InterruptedException {
System.out.println("\n=== Testing Traffic Light Fairness ===");
-
+
// Start the intersection
Thread intersectionThread = new Thread(() -> {
try {
@@ -132,10 +132,10 @@ public class TrafficLightCoordinationTest {
// Track which lights have been green
List lights = intersectionProcess.getIntersection().getTrafficLights();
boolean[] hasBeenGreen = new boolean[lights.size()];
-
- // Monitor for 15 seconds (enough time for all lights to cycle)
- long endTime = System.currentTimeMillis() + 15000;
-
+
+ // Monitor for 10 seconds (enough time for all lights to cycle: 18+18+12 = 48s)
+ long endTime = System.currentTimeMillis() + 10000;
+
while (System.currentTimeMillis() < endTime) {
for (int i = 0; i < lights.size(); i++) {
if (lights.get(i).getState() == TrafficLightState.GREEN) {
@@ -145,16 +145,17 @@ public class TrafficLightCoordinationTest {
}
Thread.sleep(100);
}
-
+
// Check if all lights got green time
int greenCount = 0;
System.out.println("\n=== Fairness Results ===");
for (int i = 0; i < lights.size(); i++) {
String status = hasBeenGreen[i] ? "✓ YES" : "✗ NO";
System.out.println(lights.get(i).getDirection() + " got GREEN time: " + status);
- if (hasBeenGreen[i]) greenCount++;
+ if (hasBeenGreen[i])
+ greenCount++;
}
-
+
assertTrue(greenCount > 0, "At least one light should have been GREEN during the test");
System.out.println("\n" + greenCount + "/" + lights.size() + " lights were GREEN during test period");
}
@@ -165,7 +166,7 @@ public class TrafficLightCoordinationTest {
@Test
public void testStateTransitionsAreConsistent() throws InterruptedException {
System.out.println("\n=== Testing State Transition Consistency ===");
-
+
Thread intersectionThread = new Thread(() -> {
try {
intersectionProcess.start();
@@ -177,29 +178,29 @@ public class TrafficLightCoordinationTest {
List lights = intersectionProcess.getIntersection().getTrafficLights();
TrafficLightState[] previousStates = new TrafficLightState[lights.size()];
-
+
// Initialize previous states
for (int i = 0; i < lights.size(); i++) {
previousStates[i] = lights.get(i).getState();
}
-
+
int transitionCount = 0;
long endTime = System.currentTimeMillis() + 8000;
-
+
while (System.currentTimeMillis() < endTime) {
for (int i = 0; i < lights.size(); i++) {
TrafficLightState currentState = lights.get(i).getState();
-
+
if (currentState != previousStates[i]) {
transitionCount++;
- System.out.println(lights.get(i).getDirection() + " transitioned: " +
- previousStates[i] + " → " + currentState);
+ System.out.println(lights.get(i).getDirection() + " transitioned: " +
+ previousStates[i] + " → " + currentState);
previousStates[i] = currentState;
}
}
Thread.sleep(100);
}
-
+
System.out.println("\nTotal state transitions observed: " + transitionCount);
assertTrue(transitionCount > 0, "There should be state transitions during the test period");
}
diff --git a/main/start.sh b/main/start.sh
new file mode 100755
index 0000000..d710bd7
--- /dev/null
+++ b/main/start.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# Distributed Traffic Simulation Startup Script
+
+# kill java
+echo "-> Cleaning up existing processes..."
+pkill -9 java 2>/dev/null
+sleep 2
+
+# build
+echo "-> Building project..."
+cd "$(dirname "$0")"
+mvn package -DskipTests -q
+if [ $? -ne 0 ]; then
+ echo "XXX Build failed! XXX"
+ exit 1
+fi
+echo "-> Build complete"
+echo ""
+
+# start gui
+echo "-> Starting JavaFX Dashboard..."
+mvn javafx:run &
+DASHBOARD_PID=$!
+sleep 3
+
+# acho que é assim idk
+echo "-> Starting 5 Intersection processes..."
+for id in Cr1 Cr2 Cr3 Cr4 Cr5; do
+ java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.IntersectionProcess $id > /tmp/$(echo $id | tr '[:upper:]' '[:lower:]').log 2>&1 &
+ echo "[SUCCESS] Started $id"
+done
+sleep 2
+
+# exit
+echo "-> Starting Exit Node..."
+java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.ExitNodeProcess > /tmp/exit.log 2>&1 &
+sleep 1
+
+# coordinator
+echo "-> Starting Coordinator..."
+java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.coordinator.CoordinatorProcess > /tmp/coordinator.log 2>&1 &
+sleep 1
+
+echo ""
+echo "-> All processes started!"
+echo ""
+echo "-> System Status:"
+ps aux | grep "java.*sd\." | grep -v grep | wc -l | xargs -I {} echo " {} Java processes running"
+echo ""
+echo " IMPORTANT: Keep the JavaFX Dashboard window OPEN for 60+ seconds"
+echo " to see live updates! The simulation runs for 60 seconds."
+echo ""
+echo "-> Logs available at:"
+echo " Dashboard: Check JavaFX window (live updates)"
+echo " Intersections: /tmp/cr*.log"
+echo " Exit Node: /tmp/exit.log"
+echo " Coordinator: /tmp/coordinator.log"
+echo ""
+echo "-> To stop all processes: pkill -9 java"
+echo ""
diff --git a/main/testing.txt b/main/testing.txt
new file mode 100644
index 0000000..6636918
--- /dev/null
+++ b/main/testing.txt
@@ -0,0 +1,1055 @@
+[INFO] Scanning for projects...
+[INFO]
+[INFO] ------------------------------< sd:main >-------------------------------
+[INFO] Building main 1.0-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[WARNING] 6 problems were encountered while building the effective model for org.openjfx:javafx-controls:jar:17.0.2 during dependency collection step for project (use -X to see details)
+[INFO]
+[INFO] --- resources:3.3.1:resources (default-resources) @ main ---
+[INFO] Copying 2 resources from src/main/resources to target/classes
+[INFO]
+[INFO] --- compiler:3.13.0:compile (default-compile) @ main ---
+[INFO] Nothing to compile - all classes are up to date.
+[INFO]
+[INFO] --- resources:3.3.1:testResources (default-testResources) @ main ---
+[INFO] skip non existing resourceDirectory /home/leo/uni/SD/Trabalho-Pratico-SD/main/src/test/resources
+[INFO]
+[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ main ---
+[INFO] Nothing to compile - all classes are up to date.
+[INFO]
+[INFO] --- surefire:3.2.5:test (default-test) @ main ---
+[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
+[INFO]
+[INFO] -------------------------------------------------------
+[INFO] T E S T S
+[INFO] -------------------------------------------------------
+[INFO] Running sd.coordinator.CoordinatorIntegrationTest
+Mock Cr1 listening on port 9001
+Connected to Cr1 at localhost:9001
+Mock Cr1 received: VEHICLE_SPAWN
+Mock Cr1 stopped
+Mock Cr1 listening on port 9001
+Connected to Cr1 at localhost:9001
+Mock Cr1 stopped
+Mock Cr1 listening on port 9001
+Connected to Cr1 at localhost:9001
+Mock Cr1 received: VEHICLE_SPAWN
+Mock Cr1 stopped
+Mock Cr1 listening on port 9001
+Mock Cr2 listening on port 9002
+Mock Cr3 listening on port 9003
+Connected to Cr1 at localhost:9001
+Connected to Cr2 at localhost:9002
+Connected to Cr3 at localhost:9003
+Mock Cr1 received: SHUTDOWN
+Mock Cr2 received: SHUTDOWN
+Mock Cr3 received: SHUTDOWN
+Mock Cr1 stopped
+Mock Cr2 stopped
+Mock Cr3 stopped
+[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.265 s -- in sd.coordinator.CoordinatorIntegrationTest
+[INFO] Running sd.coordinator.CoordinatorProcessTest
+Coordinator initialized with configuration:
+ - Simulation duration: 60.0s
+ - Arrival model: POISSON
+ - Arrival rate: 0.5 vehicles/s
+[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.052 s -- in sd.coordinator.CoordinatorProcessTest
+[INFO] Running sd.dashboard.DashboardTest
+[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.020 s -- in sd.dashboard.DashboardTest
+[INFO] Running sd.serialization.SerializationTest
+JSON Vehicle:
+{"id":"V001","type":"LIGHT","entryTime":10.5,"route":["Cr1","Cr2","Cr5","S"],"currentRouteIndex":0,"totalWaitingTime":0.0,"totalCrossingTime":0.0}
+
+JSON Message:
+{"messageId":"2ee10daa-34c4-4629-9613-bfc4fbd03e46","type":"VEHICLE_TRANSFER","senderId":"Cr1","destinationId":"Cr2","payload":{"id":"V001","type":"LIGHT","entryTime":10.5,"route":["Cr1","Cr2","Cr5","S"],"currentRouteIndex":0,"totalWaitingTime":0.0,"totalCrossingTime":0.0},"timestamp":1763852220055}
+[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s -- in sd.serialization.SerializationTest
+[INFO] Running sd.ExitNodeProcessTest
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+Exit node started on port 19001
+Waiting for vehicles...\n
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+Exit node started on port 19001
+Waiting for vehicles...\n
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+Exit node started on port 19001
+Waiting for vehicles...\n
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+Exit node started on port 19001
+Waiting for vehicles...\n
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+New connection accepted from 127.0.0.1
+[Exit] Connection closed from 127.0.0.1
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+Exit node started on port 19001
+Waiting for vehicles...\n
+New connection accepted from 127.0.0.1
+[Exit] Waiting for message from 127.0.0.1
+New connection accepted from 127.0.0.1
+[Exit] Waiting for message from 127.0.0.1
+New connection accepted from 127.0.0.1
+[Exit] Waiting for message from 127.0.0.1
+[Exit] Waiting for message from 127.0.0.1
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: [Exit] Connection closed from 127.0.0.1
+0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Connection closed from 127.0.0.1
+[Exit] Connection closed from 127.0.0.1
+[Exit] Shutdown complete.
+============================================================
+Exit node initialized
+ - Exit port: 19001
+ - Dashboard: localhost:19000
+Connecting to dashboard...
+
+[Exit] Shutting down...
+
+=== EXIT NODE STATISTICS ===
+Total Vehicles Completed: 0
+
+VEHICLE TYPE DISTRIBUTION:
+[Exit] Shutdown complete.
+============================================================
+[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.273 s -- in sd.ExitNodeProcessTest
+[INFO] Running sd.TrafficLightCoordinationTest
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 18.0s, Red: 30.0s)
+ Created traffic light: East (Green: 18.0s, Red: 30.0s)
+ Created traffic light: South (Green: 12.0s, Red: 36.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:9000...
+[Cr2] Initialization complete.
+
+=== Testing Traffic Light Fairness ===
+
+[Cr2] Server started on port 8002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: West
+ Started thread for: South
+[Cr2-West] Traffic light thread started.
+ Started thread for: East
+[Cr2-South] Traffic light thread started.
+[Cr2-West] State: GREEN
+[Cr2-East] Traffic light thread started.
+[Cr2] Waiting for incoming connections...
+
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+✓ West has been GREEN
+
+=== Fairness Results ===
+West got GREEN time: ✓ YES
+South got GREEN time: ✗ NO
+East got GREEN time: ✗ NO
+
+1/3 lights were GREEN during test period
+
+[Cr2] Shutting down...
+[Cr2-South] State: GREEN
+[Cr2-East] State: GREEN
+[Cr2-West] Traffic light thread interrupted.
+[Cr2-South] Traffic light thread stopped.
+[Cr2-East] Traffic light thread stopped.
+[Cr2-West] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 18.0s, Red: 30.0s)
+ Created traffic light: East (Green: 18.0s, Red: 30.0s)
+ Created traffic light: South (Green: 12.0s, Red: 36.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:9000...
+[Cr2] Initialization complete.
+
+=== Testing Traffic Light Mutual Exclusion ===
+
+[Cr2] Server started on port 8002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: West
+ Started thread for: South
+[Cr2-West] Traffic light thread started.
+[Cr2-West] State: GREEN
+ Started thread for: East
+[Cr2-South] Traffic light thread started.
+[Cr2] Waiting for incoming connections...
+
+[Cr2-East] Traffic light thread started.
+
+=== Test Results ===
+Maximum simultaneous GREEN lights: 1
+Total violations detected: 0
+
+Traffic light coordination working correctly!
+
+[Cr2] Shutting down...
+[Cr2-West] Traffic light thread interrupted.
+[Cr2-South] State: GREEN
+[Cr2-West] Traffic light thread stopped.
+[Cr2-East] State: GREEN
+[Cr2-South] Traffic light thread stopped.
+[Cr2-East] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 18.0s, Red: 30.0s)
+ Created traffic light: East (Green: 18.0s, Red: 30.0s)
+ Created traffic light: South (Green: 12.0s, Red: 36.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:9000...
+[Cr2] Initialization complete.
+
+=== Testing State Transition Consistency ===
+
+[Cr2] Server started on port 8002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: West
+ Started thread for: South
+[Cr2-South] Traffic light thread started.
+[Cr2-South] State: GREEN
+[Cr2-West] Traffic light thread started.
+ Started thread for: East
+[Cr2] Waiting for incoming connections...
+
+[Cr2-East] Traffic light thread started.
+South transitioned: RED → GREEN
+
+Total state transitions observed: 1
+
+[Cr2] Shutting down...
+[Cr2-South] Traffic light thread interrupted.
+[Cr2-South] Traffic light thread stopped.
+[Cr2-West] State: GREEN
+[Cr2-West] Traffic light thread stopped.
+[Cr2-East] State: GREEN
+[Cr2-East] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 33.14 s -- in sd.TrafficLightCoordinationTest
+[INFO] Running IntersectionProcessTest
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+ Started thread for: South
+ Started thread for: East
+[Cr1-South] Traffic light thread started.
+[Cr1] Waiting for incoming connections...
+
+[Cr1-South] State: GREEN
+[Cr1-East] Traffic light thread started.
+
+[Cr1] Shutting down...
+[Cr1] New connection accepted from 127.0.0.1
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1-South] Traffic light thread stopped.
+[Cr1] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+ Started thread for: South
+ Started thread for: East
+[Cr1-South] Traffic light thread started.
+[Cr1] Waiting for incoming connections...
+
+[Cr1-South] State: GREEN
+[Cr1-East] Traffic light thread started.
+
+[Cr1] Shutting down...
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-South] Traffic light thread stopped.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:18100...
+[Cr2] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+
+[Cr2] Server started on port 18002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: South
+ Started thread for: West
+[Cr1-South] Traffic light thread started.
+[Cr1-South] State: GREEN
+ Started thread for: East
+[Cr1] Waiting for incoming connections...
+
+[Cr1-East] Traffic light thread started.
+[Cr2-West] Traffic light thread started.
+ Started thread for: South
+[Cr2-West] State: GREEN
+[Cr2-South] Traffic light thread started.
+ Started thread for: East
+[Cr2] Waiting for incoming connections...
+
+[Cr2-East] Traffic light thread started.
+[Cr1] New connection accepted from 127.0.0.1
+[Cr1] New connection accepted from 127.0.0.1
+
+[Cr1] Shutting down...
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-South] Traffic light thread stopped.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1] Shutdown complete.
+============================================================
+
+
+[Cr2] Shutting down...
+[Cr2-West] Traffic light thread interrupted.
+[Cr2-South] State: GREEN
+[Cr2-West] Traffic light thread stopped.
+[Cr2-South] Traffic light thread stopped.
+[Cr2-East] State: GREEN
+[Cr2-East] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+ Started thread for: South
+[Cr1-South] Traffic light thread started.
+ Started thread for: East
+[Cr1-South] State: GREEN
+[Cr1] Waiting for incoming connections...
+
+[Cr1-East] Traffic light thread started.
+
+[Cr1] Shutting down...
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-South] Traffic light thread stopped.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr4
+============================================================
+
+[Cr4] Initializing intersection...
+
+[Cr4] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+
+[Cr4] Configuring routing...
+ Route configured: To Cr5 -> Use East
+ Routing configured.
+[Cr4] Connecting to dashboard at localhost:18100...
+[Cr4] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:18100...
+[Cr2] Initialization complete.
+
+[Cr2] Server started on port 18002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: West
+ Started thread for: South
+[Cr2-West] Traffic light thread started.
+ Started thread for: East
+[Cr2-West] State: GREEN
+[Cr2] Waiting for incoming connections...
+
+[Cr2-South] Traffic light thread started.
+[Cr2-East] Traffic light thread started.
+[Cr2] New connection accepted from 127.0.0.1
+[Cr2] New connection accepted from 127.0.0.1
+
+[Cr2] Shutting down...
+[Cr2-South] State: GREEN
+[Cr2-South] Traffic light thread stopped.
+[Cr2-West] Traffic light thread interrupted.
+[Cr2-West] Traffic light thread stopped.
+[Cr2-East] State: GREEN
+[Cr2-East] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr5
+============================================================
+
+[Cr5] Initializing intersection...
+
+[Cr5] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+
+[Cr5] Configuring routing...
+ Route configured: To S -> Use East
+ Routing configured.
+[Cr5] Connecting to dashboard at localhost:18100...
+[Cr5] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+ Started thread for: South
+ Started thread for: East
+[Cr1] Waiting for incoming connections...
+
+[Cr1-South] Traffic light thread started.
+[Cr1-South] State: GREEN
+[Cr1-East] Traffic light thread started.
+
+[Cr1] Shutting down...
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-South] Traffic light thread stopped.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1] Shutdown complete.
+============================================================
+
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr3
+============================================================
+
+[Cr3] Initializing intersection...
+
+[Cr3] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr3] Configuring routing...
+ Route configured: To Cr2 -> Use West
+ Route configured: To S -> Use South
+ Routing configured.
+[Cr3] Connecting to dashboard at localhost:18100...
+[Cr3] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:18100...
+[Cr2] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr3
+============================================================
+
+[Cr3] Initializing intersection...
+
+[Cr3] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr3] Configuring routing...
+ Route configured: To Cr2 -> Use West
+ Route configured: To S -> Use South
+ Routing configured.
+[Cr3] Connecting to dashboard at localhost:18100...
+[Cr3] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr4
+============================================================
+
+[Cr4] Initializing intersection...
+
+[Cr4] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+
+[Cr4] Configuring routing...
+ Route configured: To Cr5 -> Use East
+ Routing configured.
+[Cr4] Connecting to dashboard at localhost:18100...
+[Cr4] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr5
+============================================================
+
+[Cr5] Initializing intersection...
+
+[Cr5] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+
+[Cr5] Configuring routing...
+ Route configured: To S -> Use East
+ Routing configured.
+[Cr5] Connecting to dashboard at localhost:18100...
+[Cr5] Initialization complete.
+============================================================
+INTERSECTION PROCESS: Cr1
+============================================================
+============================================================
+INTERSECTION PROCESS: Cr2
+============================================================
+
+[Cr1] Initializing intersection...
+
+[Cr1] Creating traffic lights...
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr1] Configuring routing...
+ Route configured: To Cr2 -> Use East
+ Route configured: To Cr4 -> Use South
+ Routing configured.
+[Cr1] Connecting to dashboard at localhost:18100...
+[Cr1] Initialization complete.
+
+[Cr2] Initializing intersection...
+
+[Cr2] Creating traffic lights...
+ Created traffic light: West (Green: 30.0s, Red: 30.0s)
+ Created traffic light: East (Green: 30.0s, Red: 30.0s)
+ Created traffic light: South (Green: 30.0s, Red: 30.0s)
+
+[Cr2] Configuring routing...
+ Route configured: To Cr1 -> Use West
+ Route configured: To Cr3 -> Use East
+ Route configured: To Cr5 -> Use South
+ Routing configured.
+[Cr2] Connecting to dashboard at localhost:18100...
+[Cr2] Initialization complete.
+
+[Cr1] Server started on port 18001
+
+[Cr1] Starting traffic light threads...
+
+[Cr2] Server started on port 18002
+
+[Cr2] Starting traffic light threads...
+ Started thread for: South
+ Started thread for: West
+[Cr1-South] Traffic light thread started.
+[Cr1-South] State: GREEN
+ Started thread for: East
+[Cr1] Waiting for incoming connections...
+
+[Cr2-West] Traffic light thread started.
+[Cr2-West] State: GREEN
+[Cr1-East] Traffic light thread started.
+ Started thread for: South
+[Cr2-South] Traffic light thread started.
+ Started thread for: East
+[Cr2] Waiting for incoming connections...
+
+[Cr2-East] Traffic light thread started.
+[Cr1] New connection accepted from 127.0.0.1
+[Cr2] New connection accepted from 127.0.0.1
+
+[Cr1] Shutting down...
+[Cr1-South] Traffic light thread interrupted.
+[Cr1-South] Traffic light thread stopped.
+[Cr1-East] State: GREEN
+[Cr1-East] Traffic light thread stopped.
+[Cr1] New connection accepted from 127.0.0.1
+[Cr2] New connection accepted from 127.0.0.1
+[Cr1] Shutdown complete.
+============================================================
+
+
+[Cr2] Shutting down...
+[Cr2-West] Traffic light thread interrupted.
+[Cr2-West] Traffic light thread stopped.
+[Cr2-South] State: GREEN
+[Cr2-South] Traffic light thread stopped.
+[Cr2-East] State: GREEN
+[Cr2-East] Traffic light thread stopped.
+[Cr2] Shutdown complete.
+============================================================
+
+[INFO] Tests run: 20, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.406 s -- in IntersectionProcessTest
+[INFO] Running SimulationTest
+[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.014 s -- in SimulationTest
+[INFO]
+[INFO] Results:
+[INFO]
+[INFO] Tests run: 66, Failures: 0, Errors: 0, Skipped: 0
+[INFO]
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 44.392 s
+[INFO] Finished at: 2025-11-22T22:57:41Z
+[INFO] ------------------------------------------------------------------------