From 13fa2f877deaa2e535de5718bda048c64aafc4b2 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 23 Nov 2025 21:23:33 +0000 Subject: [PATCH] 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()); - } }