From 6fdcf376b2764b5041ee96534dc4569de15aeaaa Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sat, 22 Nov 2025 00:13:19 +0000 Subject: [PATCH 01/11] i might kms --- .gitignore | 3 + main/pom.xml | 21 + main/src/main/java/sd/ExitNodeProcess.java | 108 +++-- .../src/main/java/sd/IntersectionProcess.java | 188 ++++++++- .../sd/coordinator/CoordinatorProcess.java | 84 ++++ .../sd/dashboard/DashboardClientHandler.java | 35 +- .../java/sd/dashboard/DashboardServer.java | 69 ++-- .../sd/dashboard/DashboardStatistics.java | 10 + .../main/java/sd/dashboard/DashboardUI.java | 378 ++++++++++++++++++ main/src/main/java/sd/model/Message.java | 18 +- main/src/main/java/sd/model/MessageType.java | 6 + main/src/main/java/sd/model/TrafficLight.java | 17 + .../java/sd/protocol/SocketConnection.java | 4 +- main/start.sh | 60 +++ 14 files changed, 929 insertions(+), 72 deletions(-) create mode 100644 main/src/main/java/sd/dashboard/DashboardUI.java create mode 100755 main/start.sh 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 "" From ce7f6422469eaf1e5ad3227763d39c057c89dd74 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sat, 22 Nov 2025 21:45:16 +0000 Subject: [PATCH 02/11] slight sim change and engine code fomat --- .../main/java/sd/engine/SimulationEngine.java | 303 +++++++++--------- main/src/main/resources/simulation.properties | 74 ++--- 2 files changed, 196 insertions(+), 181 deletions(-) diff --git a/main/src/main/java/sd/engine/SimulationEngine.java b/main/src/main/java/sd/engine/SimulationEngine.java index b69c7ef..f273cfd 100644 --- a/main/src/main/java/sd/engine/SimulationEngine.java +++ b/main/src/main/java/sd/engine/SimulationEngine.java @@ -28,49 +28,51 @@ import sd.util.VehicleGenerator; * - 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"). + * 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. + * 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. + * simulation parameters. */ public SimulationEngine(SimulationConfig config) { this.config = config; @@ -89,19 +91,19 @@ public class SimulationEngine { * @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; + 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. @@ -111,53 +113,53 @@ public class SimulationEngine { */ 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"}; + String[] intersectionIds = { "Cr1", "Cr2", "Cr3", "Cr4", "Cr5" }; // Note: "North" is commented out, so it won't be created. - String[] directions = {/*"North",*/ "South", "East", "West"}; - + 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 - ); - + 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 + * * 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." */ @@ -165,26 +167,26 @@ public class SimulationEngine { // 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("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("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. @@ -201,38 +203,40 @@ public class SimulationEngine { } } } - + /** * 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 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. + * @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}). + * @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. @@ -240,13 +244,13 @@ public class SimulationEngine { 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 @@ -260,46 +264,47 @@ public class SimulationEngine { 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}. + * 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}. @@ -308,17 +313,18 @@ public class SimulationEngine { * 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.) + * 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()); - + 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")) { @@ -327,13 +333,13 @@ public class SimulationEngine { 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. @@ -344,65 +350,67 @@ public class SimulationEngine { * 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. + * @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); - + 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 + + // 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 vehicle The vehicle to process. * @param intersection The intersection where the vehicle is. */ - private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { //FIXME + 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); - + .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... @@ -416,26 +424,26 @@ public class SimulationEngine { } } } - + /** * 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 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. @@ -447,23 +455,24 @@ public class SimulationEngine { 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); - + 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)}. + * 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. * @@ -472,24 +481,25 @@ public class SimulationEngine { 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); - + 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 + // 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); @@ -499,7 +509,7 @@ public class SimulationEngine { handleVehicleExit(vehicle); } } - + /** * Handles a vehicle exiting the simulation. * Records final statistics for the vehicle. @@ -508,18 +518,19 @@ public class SimulationEngine { */ 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)); - + 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)} + * 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. @@ -529,17 +540,17 @@ public class SimulationEngine { 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; - + 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); - + currentTime, light.getId(), newState); + // If changed to GREEN, process waiting vehicles if (newState == TrafficLightState.GREEN) { Intersection intersection = intersections.get(intersectionId); @@ -547,15 +558,15 @@ public class SimulationEngine { processGreenLight(light, intersection); } } - + // Schedule the *next* state change for this same light - double nextChangeDelay = (newState == TrafficLightState.GREEN) - ? light.getGreenTime() - : light.getRedTime(); - + 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, @@ -566,7 +577,7 @@ public class SimulationEngine { * processes the entire queue "instantaneously" at the moment * the light turns green. * - * @param light The {@link TrafficLight} that just turned 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) { @@ -579,7 +590,7 @@ public class SimulationEngine { } } } - + /** * Handles {@link EventType#STATISTICS_UPDATE}. * Calls the {@link StatisticsCollector} to print the current @@ -590,9 +601,10 @@ public class SimulationEngine { statisticsCollector.printCurrentStatistics(intersections, currentTime); System.out.println(); } - + /** - * Utility method to get the configured crossing time for a given {@link VehicleType}. + * 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. @@ -605,7 +617,7 @@ public class SimulationEngine { default -> 2.0; }; // Default fallback } - + /** * Prints the final summary of statistics at the end of the simulation. */ @@ -613,33 +625,36 @@ public class SimulationEngine { 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() { diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties index ffd421d..9613e78 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -46,54 +46,54 @@ 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.North.green=20.0 +trafficlight.Cr1.North.red=40.0 +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.North.green=12.0 +trafficlight.Cr2.North.red=36.0 +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 +# Intersection 3 (Path to exit - favor East) +trafficlight.Cr3.North.green=15.0 trafficlight.Cr3.North.red=30.0 -trafficlight.Cr3.South.green=30.0 +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 +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.North.green=15.0 trafficlight.Cr4.North.red=30.0 -trafficlight.Cr4.South.green=30.0 +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 +# Intersection 5 (Near exit - favor East) +trafficlight.Cr5.North.green=15.0 trafficlight.Cr5.North.red=30.0 -trafficlight.Cr5.South.green=30.0 +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 +118,4 @@ vehicle.travel.time.heavy.multiplier=2.0 # === STATISTICS === # Interval between dashboard updates (seconds) -statistics.update.interval=10.0 +statistics.update.interval=1.0 From d74517a27bf758e87dca9d1632c8d0d28406a524 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sat, 22 Nov 2025 22:52:01 +0000 Subject: [PATCH 03/11] starting the codebase cleanup for final delivery- single process prototype removal --- main/src/main/java/sd/Entry.java | 94 --- main/src/main/java/sd/ExitNodeProcess.java | 202 +++--- .../src/main/java/sd/IntersectionProcess.java | 166 ++--- .../main/java/sd/config/SimulationConfig.java | 192 ++++- .../main/java/sd/engine/SimulationEngine.java | 663 ------------------ main/src/main/java/sd/model/Intersection.java | 10 + .../java/sd/util/StatisticsCollector.java | 174 ++--- main/src/main/resources/network_config.json | 43 ++ main/src/main/resources/simulation.properties | 10 - main/src/test/java/SimulationTest.java | 76 +- 10 files changed, 482 insertions(+), 1148 deletions(-) delete mode 100644 main/src/main/java/sd/Entry.java delete mode 100644 main/src/main/java/sd/engine/SimulationEngine.java create mode 100644 main/src/main/resources/network_config.json 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 ec3dd07..44b1b88 100644 --- a/main/src/main/java/sd/ExitNodeProcess.java +++ b/main/src/main/java/sd/ExitNodeProcess.java @@ -18,12 +18,13 @@ 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 @@ -35,34 +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 * @@ -73,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); @@ -95,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. * @@ -136,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. * @@ -159,17 +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"); - + while (running) { try { Socket clientSocket = serverSocket.accept(); @@ -181,28 +188,29 @@ 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)) { - + 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()); - + 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(); @@ -210,20 +218,20 @@ public class ExitNodeProcess { } 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) { + 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("[Exit] Unknown message type: " + e.getMessage()); e.printStackTrace(); @@ -232,9 +240,9 @@ public class ExitNodeProcess { e.printStackTrace(); } } - + System.out.println("[Exit] Connection closed from " + clientAddress); - + } catch (IOException e) { if (running) { System.err.println("[Exit] Connection error from " + clientAddress + ": " + e.getMessage()); @@ -242,7 +250,7 @@ public class ExitNodeProcess { } } } - + /** * Processa um veículo que chegou ao nó de saída. * @@ -256,42 +264,30 @@ public class ExitNodeProcess { */ private synchronized void processExitingVehicle(Vehicle vehicle) { totalVehiclesReceived++; - + // 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, crossing=%.2fs)%n", - vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime); - + 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. * @@ -305,65 +301,69 @@ public class ExitNodeProcess { if (dashboardClient == null || !dashboardClient.isConnected()) { return; } - + try { // 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 - + 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()) { typeCounts.put(type, vehicleTypeCount.get(type)); - typeWaitTimes.put(type, (long)(vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms + typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms } - + payload.setVehicleTypeCounts(typeCounts); payload.setVehicleTypeWaitTimes(typeWaitTimes); - + // Send message Message message = new Message( - MessageType.STATS_UPDATE, - "ExitNode", - "Dashboard", - payload - ); - + 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, avgWait); - + 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(); @@ -371,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)) { @@ -380,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 * @@ -403,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); @@ -418,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 0c57807..b909bfc 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -4,6 +4,7 @@ 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; @@ -64,7 +65,6 @@ 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; @@ -140,19 +140,19 @@ public class IntersectionProcess { try { String dashboardHost = config.getDashboardHost(); int dashboardPort = config.getDashboardPort(); - - System.out.println("[" + intersectionId + "] Connecting to dashboard at " + - dashboardHost + ":" + dashboardPort + "..."); - + + 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 + "] Failed to connect to dashboard: " + + e.getMessage()); System.err.println("[" + intersectionId + "] Will continue without dashboard reporting."); dashboardClient = null; } @@ -167,23 +167,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) { @@ -202,36 +191,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."); @@ -247,7 +231,7 @@ public class IntersectionProcess { trafficCoordinationLock.lock(); currentGreenDirection = direction; } - + /** * Releases the green light permission, allowing another light to turn green. * @@ -259,7 +243,7 @@ public class IntersectionProcess { trafficCoordinationLock.unlock(); } } - + /** * Starts all traffic light threads. */ @@ -447,14 +431,13 @@ public class IntersectionProcess { // 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) { + if (message.getType() == MessageType.VEHICLE_TRANSFER || + message.getType() == MessageType.VEHICLE_SPAWN) { // Cast payload to Vehicle - handle Gson deserialization Vehicle vehicle; Object payload = message.getPayload(); @@ -478,7 +461,7 @@ public class IntersectionProcess { // Add vehicle to appropriate queue intersection.receiveVehicle(vehicle); - + // Record arrival for statistics recordVehicleArrival(); } @@ -601,12 +584,13 @@ public class IntersectionProcess { } /** - * Checks if it's time to send statistics to the dashboard and sends them if needed. + * 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(); @@ -625,68 +609,28 @@ public class IntersectionProcess { try { // Calculate current queue size int currentQueueSize = intersection.getTrafficLights().stream() - .mapToInt(TrafficLight::getQueueSize) - .sum(); + .mapToInt(TrafficLight::getQueueSize) + .sum(); StatsUpdatePayload payload = new StatsUpdatePayload() - .setIntersectionArrivals(totalArrivals) - .setIntersectionDepartures(totalDepartures) - .setIntersectionQueueSize(currentQueueSize); + .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 - ); - + 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); + 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 --- - - /** - * Implementation of MessageProtocol for vehicle transfers between processes. - */ - private static class VehicleTransferMessage implements MessageProtocol { - private static final long serialVersionUID = 1L; - - 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; - } - - @Override - public MessageType getType() { - return MessageType.VEHICLE_TRANSFER; - } - - @Override - public Object getPayload() { - return payload; - } - - @Override - public String getSourceNode() { - return sourceNode; - } - - @Override - public String getDestinationNode() { - return destinationNode; - } - } } diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java index 4c3d599..bc99159 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,6 +220,7 @@ public class SimulationConfig { /** * Gets the total duration of the simulation in virtual seconds. + * * @return The simulation duration. */ public double getSimulationDuration() { @@ -130,6 +229,7 @@ public class SimulationConfig { /** * Gets the vehicle arrival model ("POISSON" or "FIXED"). + * * @return The arrival model as a string. */ public String getArrivalModel() { @@ -139,6 +239,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 +248,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 +259,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 +271,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 +285,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 +294,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 +303,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 +312,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 +321,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 +330,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 +339,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 +349,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 +359,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 +370,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 +392,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/engine/SimulationEngine.java b/main/src/main/java/sd/engine/SimulationEngine.java deleted file mode 100644 index f273cfd..0000000 --- a/main/src/main/java/sd/engine/SimulationEngine.java +++ /dev/null @@ -1,663 +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/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/util/StatisticsCollector.java b/main/src/main/java/sd/util/StatisticsCollector.java index fa8f8bd..0b7851c 100644 --- a/main/src/main/java/sd/util/StatisticsCollector.java +++ b/main/src/main/java/sd/util/StatisticsCollector.java @@ -12,7 +12,8 @@ import sd.model.VehicleType; /** * Collects, manages, and reports statistics throughout the simulation. - * * This class acts as the central bookkeeper for simulation metrics. It tracks: + * * 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). @@ -21,69 +22,74 @@ import sd.model.VehicleType; * 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. + * 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. */ + + /** + * 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. */ + + /** + * 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). + * could be for configuration-dependent stats). */ public StatisticsCollector(SimulationConfig config) { this.vehicleArrivalTimes = new HashMap<>(); @@ -95,88 +101,88 @@ public class StatisticsCollector { 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} + * This is called by the vehicle generation component * during a {@code VEHICLE_GENERATION} event. * - * @param vehicle The {@link Vehicle} that was just created. + * @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} + * This is called by the vehicle generation component * during a {@code VEHICLE_ARRIVAL} event. * - * @param vehicle The {@link Vehicle} that arrived. + * @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. + * @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} + * This is called by the vehicle generation component * when a vehicle reaches destination "S". * - * @param vehicle The {@link Vehicle} that is exiting. + * @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 + * This is used by the intersection component to calculate * wait time just before the vehicle crosses. * * @param vehicle The {@link Vehicle} to check. @@ -185,45 +191,45 @@ public class StatisticsCollector { 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} + * This is called periodically by the simulation components * during a {@code STATISTICS_UPDATE} event. * * @param intersections A map of all intersections (to get queue data). - * @param currentTime The current simulation time. + * @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); - + 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()); + 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. + * @param currentTime The final simulation time. */ public void printFinalStatistics(Map intersections, double currentTime) { System.out.println("\n=== SIMULATION SUMMARY ==="); @@ -231,7 +237,7 @@ public class StatisticsCollector { 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"); @@ -239,7 +245,7 @@ public class StatisticsCollector { 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()) { @@ -249,46 +255,46 @@ public class StatisticsCollector { // 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; + double avgWait = vehicleTypeWaitTime.get(type) / count; System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n", - type, count, percentage, avgWait); + 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()); + 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(); + .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. @@ -302,7 +308,7 @@ public class StatisticsCollector { // 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. @@ -310,66 +316,62 @@ public class StatisticsCollector { 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. */ 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 9613e78..825476d 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -47,8 +47,6 @@ simulation.arrival.fixed.interval=2.0 # Format: trafficlight...= # Intersection 1 (Entry point - balanced) -trafficlight.Cr1.North.green=20.0 -trafficlight.Cr1.North.red=40.0 trafficlight.Cr1.South.green=20.0 trafficlight.Cr1.South.red=40.0 trafficlight.Cr1.East.green=20.0 @@ -57,8 +55,6 @@ trafficlight.Cr1.West.green=20.0 trafficlight.Cr1.West.red=40.0 # Intersection 2 (Main hub - shorter cycles, favor East-West) -trafficlight.Cr2.North.green=12.0 -trafficlight.Cr2.North.red=36.0 trafficlight.Cr2.South.green=12.0 trafficlight.Cr2.South.red=36.0 trafficlight.Cr2.East.green=18.0 @@ -67,8 +63,6 @@ trafficlight.Cr2.West.green=18.0 trafficlight.Cr2.West.red=30.0 # Intersection 3 (Path to exit - favor East) -trafficlight.Cr3.North.green=15.0 -trafficlight.Cr3.North.red=30.0 trafficlight.Cr3.South.green=15.0 trafficlight.Cr3.South.red=30.0 trafficlight.Cr3.East.green=20.0 @@ -77,8 +71,6 @@ trafficlight.Cr3.West.green=15.0 trafficlight.Cr3.West.red=30.0 # Intersection 4 (Favor East toward Cr5) -trafficlight.Cr4.North.green=15.0 -trafficlight.Cr4.North.red=30.0 trafficlight.Cr4.South.green=15.0 trafficlight.Cr4.South.red=30.0 trafficlight.Cr4.East.green=20.0 @@ -87,8 +79,6 @@ trafficlight.Cr4.West.green=15.0 trafficlight.Cr4.West.red=30.0 # Intersection 5 (Near exit - favor East) -trafficlight.Cr5.North.green=15.0 -trafficlight.Cr5.North.red=30.0 trafficlight.Cr5.South.green=15.0 trafficlight.Cr5.South.red=30.0 trafficlight.Cr5.East.green=22.0 diff --git a/main/src/test/java/SimulationTest.java b/main/src/test/java/SimulationTest.java index b3a49df..bbb3bff 100644 --- a/main/src/test/java/SimulationTest.java +++ b/main/src/test/java/SimulationTest.java @@ -6,7 +6,6 @@ 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; @@ -21,104 +20,91 @@ 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 - } - } - + + // Removed testSimulationEngineInitialization as SimulationEngine has been + // removed. + @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")); - + + 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()); } From 96c5680f41f6f715854eb7c6aac24bfff41a50fa Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sat, 22 Nov 2025 23:52:51 +0000 Subject: [PATCH 04/11] moved start to dashboard + fixed holding queue - looped sleep might be fine in this case + better customization via CSS file --- .../src/main/java/sd/IntersectionProcess.java | 34 +- .../sd/coordinator/CoordinatorProcess.java | 157 +-- .../main/java/sd/dashboard/DashboardUI.java | 412 ++++--- .../dashboard/SimulationProcessManager.java | 112 ++ .../java/sd/protocol/SocketConnection.java | 3 +- main/src/main/resources/dashboard.css | 142 +++ .../java/sd/TrafficLightCoordinationTest.java | 69 +- main/testing.txt | 1055 +++++++++++++++++ 8 files changed, 1681 insertions(+), 303 deletions(-) create mode 100644 main/src/main/java/sd/dashboard/SimulationProcessManager.java create mode 100644 main/src/main/resources/dashboard.css create mode 100644 main/testing.txt diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index b909bfc..6694aa8 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -8,6 +8,7 @@ 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; @@ -47,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. @@ -67,7 +70,6 @@ public class IntersectionProcess { private SocketClient dashboardClient; private volatile int totalArrivals = 0; private volatile int totalDepartures = 0; - private long lastStatsUpdateTime; /** * Constructs a new IntersectionProcess. @@ -83,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)); @@ -148,7 +151,6 @@ public class IntersectionProcess { dashboardClient.connect(); System.out.println("[" + intersectionId + "] Connected to dashboard."); - lastStatsUpdateTime = System.currentTimeMillis(); } catch (IOException e) { System.err.println("[" + intersectionId + "] Failed to connect to dashboard: " + @@ -365,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 @@ -523,6 +528,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 { @@ -532,6 +540,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(); } @@ -572,7 +583,6 @@ public class IntersectionProcess { */ public void recordVehicleArrival() { totalArrivals++; - checkAndSendStats(); } /** @@ -580,22 +590,6 @@ public class IntersectionProcess { */ 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; - } } /** diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java index 163dc5a..e7cb5b6 100644 --- a/main/src/main/java/sd/coordinator/CoordinatorProcess.java +++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java @@ -21,7 +21,7 @@ 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; @@ -30,28 +30,28 @@ public class CoordinatorProcess { 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); @@ -60,7 +60,7 @@ public class CoordinatorProcess { System.exit(1); } } - + public CoordinatorProcess(SimulationConfig config) { this.config = config; this.vehicleGenerator = new VehicleGenerator(config); @@ -69,131 +69,137 @@ 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); - } + } + + 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); } @@ -203,21 +209,21 @@ 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(); @@ -227,58 +233,55 @@ public class CoordinatorProcess { 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 - ); - + MessageType.STATS_UPDATE, + "COORDINATOR", + "Dashboard", + payload); + dashboardClient.send(message); - } catch (Exception e) { //This is fine - can add IOException if need be + } 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 - ); + 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 - ); + 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/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java index df544d7..cb4cfcf 100644 --- a/main/src/main/java/sd/dashboard/DashboardUI.java +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -12,10 +12,10 @@ 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.TitledPane; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; @@ -23,10 +23,7 @@ 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; @@ -36,10 +33,10 @@ import sd.model.VehicleType; * 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; @@ -47,287 +44,343 @@ public class DashboardUI extends Application { 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); - + 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;"); - + 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, 800); + 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 (IOException e) { + + } catch (Exception e) { showErrorAlert("Failed to start Dashboard Server", e.getMessage()); + e.printStackTrace(); 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.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(15); + VBox mainContent = new VBox(20); mainContent.setPadding(new Insets(20)); - + // Global Statistics Panel - TitledPane globalStatsPane = createGlobalStatisticsPanel(); - + VBox globalStatsCard = createGlobalStatisticsPanel(); + + // Tables Container + HBox tablesContainer = new HBox(20); + tablesContainer.setAlignment(Pos.TOP_CENTER); + // Vehicle Type Statistics Panel - TitledPane vehicleTypePane = createVehicleTypePanel(); - + VBox vehicleTypeCard = createVehicleTypePanel(); + HBox.setHgrow(vehicleTypeCard, Priority.ALWAYS); + // Intersection Statistics Panel - TitledPane intersectionPane = createIntersectionPanel(); - - mainContent.getChildren().addAll(globalStatsPane, vehicleTypePane, intersectionPane); - + VBox intersectionCard = createIntersectionPanel(); + HBox.setHgrow(intersectionCard, Priority.ALWAYS); + + tablesContainer.getChildren().addAll(vehicleTypeCard, intersectionCard); + + mainContent.getChildren().addAll(globalStatsCard, tablesContainer); + return mainContent; } - - private TitledPane createGlobalStatisticsPanel() { + + 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.setPadding(new Insets(15)); - grid.setHgap(20); + grid.getStyleClass().add("card-content"); + grid.setHgap(40); grid.setVgap(15); - grid.setStyle("-fx-background-color: white; -fx-border-radius: 5;"); - + grid.setAlignment(Pos.CENTER); + // Initialize labels - lblVehiclesGenerated = createStatLabel("0"); - lblVehiclesCompleted = createStatLabel("0"); - lblVehiclesInTransit = createStatLabel("0"); - lblAvgSystemTime = createStatLabel("0.00 ms"); - lblAvgWaitingTime = createStatLabel("0.00 ms"); - + 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, "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; + 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 TitledPane createVehicleTypePanel() { + + 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(200); - + vehicleTypeTable.setPrefHeight(300); + 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; + + card.getChildren().addAll(cardHeader, vehicleTypeTable); + return card; } - - private TitledPane createIntersectionPanel() { + + 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(250); - + intersectionTable.setPrefHeight(300); + 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; + + card.getChildren().addAll(cardHeader, intersectionTable); + return card; } - + private HBox createFooter() { HBox footer = new HBox(10); - footer.setPadding(new Insets(10, 20, 10, 20)); - footer.setStyle("-fx-background-color: #34495e;"); + footer.getStyleClass().add("footer"); footer.setAlignment(Pos.CENTER_LEFT); - + Label statusLabel = new Label("Status:"); - statusLabel.setTextFill(Color.WHITE); - statusLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12)); - + statusLabel.getStyleClass().add("footer-text"); + statusLabel.setStyle("-fx-font-weight: bold;"); + Circle statusIndicator = new Circle(6); - statusIndicator.setFill(Color.LIME); - + statusIndicator.setFill(javafx.scene.paint.Color.LIME); + Label statusText = new Label("Connected and Receiving Data"); - statusText.setTextFill(Color.WHITE); - statusText.setFont(Font.font("Arial", 12)); - + statusText.getStyleClass().add("footer-text"); + lblLastUpdate = new Label("Last Update: --:--:--"); - lblLastUpdate.setTextFill(Color.web("#ecf0f1")); - lblLastUpdate.setFont(Font.font("Arial", 12)); - + 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 createStatLabel(String initialValue) { + + private Label createStatValueLabel(String initialValue) { Label label = new Label(initialValue); - label.setFont(Font.font("Arial", FontWeight.BOLD, 20)); - label.setTextFill(Color.web("#2980b9")); + label.getStyleClass().add("stat-value"); return label; } - - private void addStatRow(GridPane grid, int row, String description, Label valueLabel) { + + 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.setFont(Font.font("Arial", FontWeight.NORMAL, 14)); - descLabel.setTextFill(Color.web("#34495e")); - - grid.add(descLabel, 0, row); - grid.add(valueLabel, 1, row); + 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 ms", statistics.getAverageSystemTime())); - lblAvgWaitingTime.setText(String.format("%.2f ms", statistics.getAverageWaitingTime())); + 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 ms", avgWait))); + type.toString(), count, String.format("%.2f s", avgWait / 1000.0))); } - + // Update intersection table intersectionTable.getItems().clear(); - Map intersectionStats = - statistics.getAllIntersectionStats(); + Map intersectionStats = statistics.getAllIntersectionStats(); for (DashboardStatistics.IntersectionStats stats : intersectionStats.values()) { intersectionTable.getItems().add(new IntersectionRow( - stats.getIntersectionId(), - stats.getTotalArrivals(), - stats.getTotalDepartures(), - stats.getCurrentQueueSize() - )); + 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); @@ -335,44 +388,63 @@ public class DashboardUI extends Application { 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 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; } + + 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/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java new file mode 100644 index 0000000..a1818f3 --- /dev/null +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -0,0 +1,112 @@ +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); + } + + // this is a linux thing - not sure about windows + String logName = className.substring(className.lastIndexOf('.') + 1) + (arg != null ? "-" + arg : "") + ".log"; + File logFile = new File("/tmp/" + logName); + builder.redirectOutput(logFile); + builder.redirectError(logFile); + + Process process = builder.start(); + runningProcesses.add(process); + System.out.println("Started " + className + (arg != null ? " " + arg : "")); + } + + public boolean isSimulationRunning() { + return !runningProcesses.isEmpty() && runningProcesses.stream().anyMatch(Process::isAlive); + } +} diff --git a/main/src/main/java/sd/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java index 12feecb..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"); } 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/test/java/sd/TrafficLightCoordinationTest.java b/main/src/test/java/sd/TrafficLightCoordinationTest.java index 347864d..80d5f09 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 60 seconds (enough time for all lights to cycle: 18+18+12 = 48s) + long endTime = System.currentTimeMillis() + 60000; + 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/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] ------------------------------------------------------------------------ From 13fa2f877deaa2e535de5718bda048c64aafc4b2 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 21:23:33 +0000 Subject: [PATCH 05/11] refactor: improve traffic light queue processing, add graceful intersection shutdown, and remove obsolete event and serialization classes. --- .../src/main/java/sd/IntersectionProcess.java | 9 + .../main/java/sd/config/SimulationConfig.java | 10 + .../sd/coordinator/CoordinatorProcess.java | 19 +- .../java/sd/engine/TrafficLightThread.java | 71 ++-- main/src/main/java/sd/model/Event.java | 131 ------ main/src/main/java/sd/model/EventType.java | 45 --- main/src/main/java/sd/model/Vehicle.java | 41 +- .../serialization/SerializationExample.java | 134 ------ .../java/sd/util/StatisticsCollector.java | 381 ------------------ main/src/test/java/SimulationTest.java | 29 -- 10 files changed, 96 insertions(+), 774 deletions(-) delete mode 100644 main/src/main/java/sd/model/Event.java delete mode 100644 main/src/main/java/sd/model/EventType.java delete mode 100644 main/src/main/java/sd/serialization/SerializationExample.java delete mode 100644 main/src/main/java/sd/util/StatisticsCollector.java diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index 6694aa8..bdcb74f 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -469,6 +469,12 @@ public class IntersectionProcess { // 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) { @@ -512,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 { diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java index bc99159..b974495 100644 --- a/main/src/main/java/sd/config/SimulationConfig.java +++ b/main/src/main/java/sd/config/SimulationConfig.java @@ -227,6 +227,16 @@ public class SimulationConfig { 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"). * diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java index e7cb5b6..2fb1423 100644 --- a/main/src/main/java/sd/coordinator/CoordinatorProcess.java +++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java @@ -119,10 +119,21 @@ public class CoordinatorProcess { 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 { 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/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/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 0b7851c..0000000 --- a/main/src/main/java/sd/util/StatisticsCollector.java +++ /dev/null @@ -1,381 +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 vehicle generation component - * 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 vehicle generation component - * 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 vehicle generation component - * 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 intersection component 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 simulation components - * 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 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/test/java/SimulationTest.java b/main/src/test/java/SimulationTest.java index bbb3bff..09df4bf 100644 --- a/main/src/test/java/SimulationTest.java +++ b/main/src/test/java/SimulationTest.java @@ -6,14 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; 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; /** @@ -45,16 +42,6 @@ class SimulationTest { 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"); @@ -92,20 +79,4 @@ class SimulationTest { // Removed testSimulationEngineInitialization as SimulationEngine has been // removed. - @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()); - } } From 19709f0d7a1f5f946e2b617c99e6adbe33280c93 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 21:29:38 +0000 Subject: [PATCH 06/11] feat: update main class to `sd.dashboard.DashboardUI` in `pom.xml` configurations. --- main/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index f2e1f63..1befe20 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -51,7 +51,7 @@ exec-maven-plugin 3.1.0 - sd.Entry + sd.dashboard.DashboardUI @@ -76,7 +76,7 @@ - sd.Entry + sd.dashboard.DashboardUI From 906e958729f9aa673b6ec076eb259d1292b57b68 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 21:53:52 +0000 Subject: [PATCH 07/11] feat: Introduce Launcher class as the application entry point and update pom.xml to use it. --- main/pom.xml | 6 +++--- main/src/main/java/sd/dashboard/Launcher.java | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 main/src/main/java/sd/dashboard/Launcher.java diff --git a/main/pom.xml b/main/pom.xml index 1befe20..3ecd199 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -51,7 +51,7 @@ exec-maven-plugin 3.1.0 - sd.dashboard.DashboardUI + sd.dashboard.Launcher @@ -60,7 +60,7 @@ javafx-maven-plugin 0.0.8 - sd.dashboard.DashboardUI + sd.dashboard.Launcher @@ -76,7 +76,7 @@ - sd.dashboard.DashboardUI + sd.dashboard.Launcher 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); + } +} From 0d85d010bfb12bdfa3c500d8262798d91334904f Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 22:14:10 +0000 Subject: [PATCH 08/11] Sync CI with main branch --- .github/workflows/maven.yml | 62 ++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 344c385..48035b2 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,88 @@ 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] + needs: [build, build-windows] if: startsWith(github.ref, 'refs/tags/') 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 + files: | + main/target/*.jar + windows-dist/*.zip From 46d148c9d5de498fe6465635271611aa99c75b9a Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 22:23:13 +0000 Subject: [PATCH 09/11] Allow manual trigger for publish-release job --- .github/workflows/maven.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 48035b2..0ae2296 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -77,7 +77,7 @@ jobs: publish-release: runs-on: ubuntu-latest needs: [build, build-windows] - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' permissions: contents: write steps: @@ -94,6 +94,11 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: + 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 From 52020324712c82dc9835ed8e50f3765f81f13c1f Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 23:03:07 +0000 Subject: [PATCH 10/11] feat: Dynamically set simulation log file path using OS temporary directory and remove `isSimulationRunning` method. --- .../sd/dashboard/SimulationProcessManager.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java index a1818f3..0658b2b 100644 --- a/main/src/main/java/sd/dashboard/SimulationProcessManager.java +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -95,18 +95,24 @@ public class SimulationProcessManager { builder = new ProcessBuilder(javaBin, "-cp", classpath, className); } - // this is a linux thing - not sure about windows + // 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"; - File logFile = new File("/tmp/" + logName); + + // 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 : "")); - } - - public boolean isSimulationRunning() { - return !runningProcesses.isEmpty() && runningProcesses.stream().anyMatch(Process::isAlive); + // print where the logs are actually going + System.out.println("Logs redirected to: " + logFile.getAbsolutePath()); } } From 173d9e54ce07185ad379fdc69b203d120fdf2596 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 23:06:08 +0000 Subject: [PATCH 11/11] test: Reduce traffic light coordination test monitoring duration from 60s to 10s --- main/src/test/java/sd/TrafficLightCoordinationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/test/java/sd/TrafficLightCoordinationTest.java b/main/src/test/java/sd/TrafficLightCoordinationTest.java index 80d5f09..cbc2d04 100644 --- a/main/src/test/java/sd/TrafficLightCoordinationTest.java +++ b/main/src/test/java/sd/TrafficLightCoordinationTest.java @@ -133,8 +133,8 @@ public class TrafficLightCoordinationTest { List lights = intersectionProcess.getIntersection().getTrafficLights(); boolean[] hasBeenGreen = new boolean[lights.size()]; - // Monitor for 60 seconds (enough time for all lights to cycle: 18+18+12 = 48s) - long endTime = System.currentTimeMillis() + 60000; + // 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++) {