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..f2e1f63 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
+
@@ -42,6 +54,15 @@
sd.Entry
+
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.8
+
+ sd.dashboard.DashboardUI
+
+
org.apache.maven.plugins
maven-shade-plugin
diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java
index c2a4f2d..ec3dd07 100644
--- a/main/src/main/java/sd/ExitNodeProcess.java
+++ b/main/src/main/java/sd/ExitNodeProcess.java
@@ -11,6 +11,7 @@ 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;
@@ -38,6 +39,9 @@ public class ExitNodeProcess {
/** 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;
@@ -161,9 +165,10 @@ public class ExitNodeProcess {
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 {
@@ -186,28 +191,54 @@ public class ExitNodeProcess {
* @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();
+ System.out.println("[Exit] Received message type: " + message.getType() +
+ " from " + message.getSourceNode());
- if (message.getType() == MessageType.VEHICLE_TRANSFER) {
- Vehicle vehicle = (Vehicle) message.getPayload();
+ 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();
}
}
}
@@ -226,10 +257,14 @@ 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;
@@ -238,12 +273,11 @@ public class ExitNodeProcess {
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);
+ System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n",
+ vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime);
- if (totalVehiclesReceived % 10 == 0) {
- sendStatsToDashboard();
- }
+ // Send stats after every vehicle to ensure dashboard updates quickly
+ sendStatsToDashboard();
}
/**
@@ -273,32 +307,42 @@ public class ExitNodeProcess {
}
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);
+ // 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 vehicle type stats
+ Map typeCounts = new HashMap<>();
+ Map typeWaitTimes = new HashMap<>();
- Map typeCounts = new HashMap<>();
- Map typeAvgWait = 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);
+ totalVehiclesReceived, avgWait);
- } catch (SerializationException | IOException e) {
- System.err.println("Failed to send stats to dashboard: " + e.getMessage());
+ } catch (Exception e) {
+ System.err.println("[Exit] Failed to send stats to dashboard: " + e.getMessage());
}
}
diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java
index 57c658f..0c57807 100644
--- a/main/src/main/java/sd/IntersectionProcess.java
+++ b/main/src/main/java/sd/IntersectionProcess.java
@@ -12,13 +12,17 @@ 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.
@@ -59,6 +63,12 @@ public class IntersectionProcess {
*/
private volatile String currentGreenDirection;
+ private SocketClient dashboardClient;
+ private long simulationStartMillis;
+ private volatile int totalArrivals = 0;
+ private volatile int totalDepartures = 0;
+ private long lastStatsUpdateTime;
+
/**
* Constructs a new IntersectionProcess.
*
@@ -82,6 +92,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 +128,36 @@ 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.");
+ lastStatsUpdateTime = System.currentTimeMillis();
+
+ } 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.
@@ -222,17 +288,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) {
@@ -374,14 +445,42 @@ 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) {
+ simulationStartMillis = ((Number) message.getPayload()).longValue();
+ 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();
}
} catch (java.net.SocketTimeoutException e) {
@@ -394,6 +493,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
}
}
@@ -459,6 +565,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,6 +584,73 @@ public class IntersectionProcess {
return intersection;
}
+ /**
+ * Records that a vehicle has arrived at this intersection.
+ */
+ public void recordVehicleArrival() {
+ totalArrivals++;
+ checkAndSendStats();
+ }
+
+ /**
+ * Records that a vehicle has departed from this intersection.
+ */
+ public void recordVehicleDeparture() {
+ totalDepartures++;
+ checkAndSendStats();
+ }
+
+ /**
+ * Checks if it's time to send statistics to the dashboard and sends them if needed.
+ */
+ private void checkAndSendStats() {
+ long now = System.currentTimeMillis();
+ long elapsed = now - lastStatsUpdateTime;
+
+ // Send stats every 5 seconds
+ if (elapsed >= 5000) {
+ sendStatsToDashboard();
+ lastStatsUpdateTime = now;
+ }
+ }
+
+ /**
+ * Sends current statistics to the dashboard server.
+ */
+ private void sendStatsToDashboard() {
+ if (dashboardClient == null || !dashboardClient.isConnected()) {
+ return;
+ }
+
+ try {
+ // Calculate current queue size
+ int currentQueueSize = intersection.getTrafficLights().stream()
+ .mapToInt(TrafficLight::getQueueSize)
+ .sum();
+
+ StatsUpdatePayload payload = new StatsUpdatePayload()
+ .setIntersectionArrivals(totalArrivals)
+ .setIntersectionDepartures(totalDepartures)
+ .setIntersectionQueueSize(currentQueueSize);
+
+ // Send StatsUpdatePayload directly as the message payload
+ sd.model.Message message = new sd.model.Message(
+ MessageType.STATS_UPDATE,
+ intersectionId,
+ "Dashboard",
+ payload
+ );
+
+ 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());
+ }
+ }
+
// --- Inner class for Vehicle Transfer Messages ---
/**
diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java
index 9d25ee0..163dc5a 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;
@@ -24,6 +25,7 @@ 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;
@@ -75,6 +77,9 @@ public class CoordinatorProcess {
}
public void initialize() {
+ // Connect to dashboard first
+ connectToDashboard();
+
System.out.println("Connecting to intersection processes...");
String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"};
@@ -108,6 +113,9 @@ public class CoordinatorProcess {
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;
@@ -132,6 +140,9 @@ public class CoordinatorProcess {
System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n",
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;
@@ -201,4 +212,77 @@ public class CoordinatorProcess {
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..df544d7
--- /dev/null
+++ b/main/src/main/java/sd/dashboard/DashboardUI.java
@@ -0,0 +1,378 @@
+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.Label;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TitledPane;
+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.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+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.setStyle("-fx-background-color: #f5f5f5;");
+
+ // 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, 800);
+ 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 (IOException e) {
+ showErrorAlert("Failed to start Dashboard Server", e.getMessage());
+ Platform.exit();
+ }
+ }
+
+ private VBox createHeader() {
+ VBox header = new VBox(10);
+ header.setPadding(new Insets(20));
+ header.setStyle("-fx-background-color: linear-gradient(to right, #2c3e50, #3498db);");
+
+ Label title = new Label("DISTRIBUTED TRAFFIC SIMULATION DASHBOARD");
+ title.setFont(Font.font("Arial", FontWeight.BOLD, 28));
+ title.setTextFill(Color.WHITE);
+
+ Label subtitle = new Label("Real-time Statistics and Monitoring");
+ subtitle.setFont(Font.font("Arial", FontWeight.NORMAL, 16));
+ subtitle.setTextFill(Color.web("#ecf0f1"));
+
+ header.getChildren().addAll(title, subtitle);
+ header.setAlignment(Pos.CENTER);
+
+ return header;
+ }
+
+ private VBox createMainContent() {
+ VBox mainContent = new VBox(15);
+ mainContent.setPadding(new Insets(20));
+
+ // Global Statistics Panel
+ TitledPane globalStatsPane = createGlobalStatisticsPanel();
+
+ // Vehicle Type Statistics Panel
+ TitledPane vehicleTypePane = createVehicleTypePanel();
+
+ // Intersection Statistics Panel
+ TitledPane intersectionPane = createIntersectionPanel();
+
+ mainContent.getChildren().addAll(globalStatsPane, vehicleTypePane, intersectionPane);
+
+ return mainContent;
+ }
+
+ private TitledPane createGlobalStatisticsPanel() {
+ GridPane grid = new GridPane();
+ grid.setPadding(new Insets(15));
+ grid.setHgap(20);
+ grid.setVgap(15);
+ grid.setStyle("-fx-background-color: white; -fx-border-radius: 5;");
+
+ // Initialize labels
+ lblVehiclesGenerated = createStatLabel("0");
+ lblVehiclesCompleted = createStatLabel("0");
+ lblVehiclesInTransit = createStatLabel("0");
+ lblAvgSystemTime = createStatLabel("0.00 ms");
+ lblAvgWaitingTime = createStatLabel("0.00 ms");
+
+ // Add labels with descriptions
+ addStatRow(grid, 0, "Total Vehicles Generated:", lblVehiclesGenerated);
+ addStatRow(grid, 1, "Total Vehicles Completed:", lblVehiclesCompleted);
+ addStatRow(grid, 2, "Vehicles In Transit:", lblVehiclesInTransit);
+ addStatRow(grid, 3, "Average System Time:", lblAvgSystemTime);
+ addStatRow(grid, 4, "Average Waiting Time:", lblAvgWaitingTime);
+
+ TitledPane pane = new TitledPane("Global Statistics", grid);
+ pane.setCollapsible(false);
+ pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+
+ return pane;
+ }
+
+ private TitledPane createVehicleTypePanel() {
+ vehicleTypeTable = new TableView<>();
+ vehicleTypeTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ vehicleTypeTable.setPrefHeight(200);
+
+ TableColumn typeCol = new TableColumn<>("Vehicle Type");
+ typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType"));
+ typeCol.setPrefWidth(200);
+
+ TableColumn countCol = new TableColumn<>("Count");
+ countCol.setCellValueFactory(new PropertyValueFactory<>("count"));
+ countCol.setPrefWidth(150);
+
+ TableColumn avgWaitCol = new TableColumn<>("Avg Wait Time");
+ avgWaitCol.setCellValueFactory(new PropertyValueFactory<>("avgWaitTime"));
+ avgWaitCol.setPrefWidth(150);
+
+ vehicleTypeTable.getColumns().addAll(typeCol, countCol, avgWaitCol);
+
+ TitledPane pane = new TitledPane("Vehicle Type Statistics", vehicleTypeTable);
+ pane.setCollapsible(false);
+ pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+
+ return pane;
+ }
+
+ private TitledPane createIntersectionPanel() {
+ intersectionTable = new TableView<>();
+ intersectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ intersectionTable.setPrefHeight(250);
+
+ TableColumn idCol = new TableColumn<>("Intersection ID");
+ idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId"));
+ idCol.setPrefWidth(200);
+
+ TableColumn arrivalsCol = new TableColumn<>("Total Arrivals");
+ arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals"));
+ arrivalsCol.setPrefWidth(150);
+
+ TableColumn departuresCol = new TableColumn<>("Total Departures");
+ departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures"));
+ departuresCol.setPrefWidth(150);
+
+ TableColumn queueCol = new TableColumn<>("Current Queue");
+ queueCol.setCellValueFactory(new PropertyValueFactory<>("queueSize"));
+ queueCol.setPrefWidth(150);
+
+ intersectionTable.getColumns().addAll(idCol, arrivalsCol, departuresCol, queueCol);
+
+ TitledPane pane = new TitledPane("Intersection Statistics", intersectionTable);
+ pane.setCollapsible(false);
+ pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
+
+ return pane;
+ }
+
+ private HBox createFooter() {
+ HBox footer = new HBox(10);
+ footer.setPadding(new Insets(10, 20, 10, 20));
+ footer.setStyle("-fx-background-color: #34495e;");
+ footer.setAlignment(Pos.CENTER_LEFT);
+
+ Label statusLabel = new Label("Status:");
+ statusLabel.setTextFill(Color.WHITE);
+ statusLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
+
+ Circle statusIndicator = new Circle(6);
+ statusIndicator.setFill(Color.LIME);
+
+ Label statusText = new Label("Connected and Receiving Data");
+ statusText.setTextFill(Color.WHITE);
+ statusText.setFont(Font.font("Arial", 12));
+
+ lblLastUpdate = new Label("Last Update: --:--:--");
+ lblLastUpdate.setTextFill(Color.web("#ecf0f1"));
+ lblLastUpdate.setFont(Font.font("Arial", 12));
+
+ Region spacer = new Region();
+ HBox.setHgrow(spacer, Priority.ALWAYS);
+
+ footer.getChildren().addAll(statusLabel, statusIndicator, statusText, spacer, lblLastUpdate);
+
+ return footer;
+ }
+
+ private Label createStatLabel(String initialValue) {
+ Label label = new Label(initialValue);
+ label.setFont(Font.font("Arial", FontWeight.BOLD, 20));
+ label.setTextFill(Color.web("#2980b9"));
+ return label;
+ }
+
+ private void addStatRow(GridPane grid, int row, String description, Label valueLabel) {
+ Label descLabel = new Label(description);
+ descLabel.setFont(Font.font("Arial", FontWeight.NORMAL, 14));
+ descLabel.setTextFill(Color.web("#34495e"));
+
+ grid.add(descLabel, 0, row);
+ grid.add(valueLabel, 1, 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 ms", statistics.getAverageSystemTime()));
+ lblAvgWaitingTime.setText(String.format("%.2f ms", statistics.getAverageWaitingTime()));
+ 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 ms", avgWait)));
+ }
+
+ // 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/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/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java
index 446281c..12feecb 100644
--- a/main/src/main/java/sd/protocol/SocketConnection.java
+++ b/main/src/main/java/sd/protocol/SocketConnection.java
@@ -172,8 +172,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/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 ""