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()); }