From 90db380f61323e5482b13452ce7eed77d1ab3d3b Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Fri, 5 Dec 2025 02:29:33 +0000 Subject: [PATCH] Dash editor and DES impl --- main/src/main/java/sd/ExitNodeProcess.java | 291 +++++++--- .../src/main/java/sd/IntersectionProcess.java | 545 +++++++++++++++--- .../java/sd/aggregator/AggregatorClient.java | 0 .../java/sd/aggregator/AggregatorServer.java | 0 .../java/sd/analysis/MultiRunAnalyzer.java | 223 +++++++ .../sd/analysis/SimulationBatchRunner.java | 172 ++++++ .../java/sd/analysis/SimulationRunResult.java | 143 +++++ .../java/sd/analysis/StatisticalAnalysis.java | 160 +++++ .../main/java/sd/config/SimulationConfig.java | 41 +- .../sd/coordinator/CoordinatorProcess.java | 182 ++++-- .../java/sd/coordinator/SocketClient.java | 30 +- .../sd/dashboard/ConfigurationDialog.java | 167 ++++++ .../java/sd/dashboard/DashboardServer.java | 4 +- .../sd/dashboard/DashboardStatistics.java | 4 +- .../main/java/sd/dashboard/DashboardUI.java | 95 ++- .../dashboard/SimulationProcessManager.java | 27 +- .../java/sd/dashboard/StatsUpdatePayload.java | 4 +- main/src/main/java/sd/des/DESEventType.java | 39 ++ main/src/main/java/sd/des/EventQueue.java | 137 +++++ .../src/main/java/sd/des/SimulationClock.java | 67 +++ .../src/main/java/sd/des/SimulationEvent.java | 98 ++++ .../main/java/sd/des/TrafficLightEvent.java | 36 ++ .../java/sd/engine/TrafficLightThread.java | 126 ---- .../src/main/java/sd/logging/EventLogger.java | 213 +++++++ main/src/main/java/sd/logging/EventType.java | 47 ++ .../main/java/sd/logging/VehicleTracer.java | 331 +++++++++++ main/src/main/java/sd/model/Intersection.java | 161 +++--- main/src/main/java/sd/model/Message.java | 61 +- main/src/main/java/sd/model/MessageType.java | 88 +-- main/src/main/java/sd/model/TrafficLight.java | 259 ++++----- .../main/java/sd/model/TrafficLightState.java | 10 +- main/src/main/java/sd/model/Vehicle.java | 157 ++--- main/src/main/java/sd/model/VehicleType.java | 22 +- .../java/sd/protocol/MessageProtocol.java | 36 +- .../java/sd/protocol/SocketConnection.java | 24 +- .../main/java/sd/util/RandomGenerator.java | 95 ++- .../main/java/sd/util/VehicleGenerator.java | 117 ++-- main/src/main/resources/network_config.json | 7 +- .../main/resources/simulation-high.properties | 117 ++++ .../main/resources/simulation-low.properties | 111 ++++ .../resources/simulation-medium.properties | 112 ++++ main/src/main/resources/simulation.properties | 6 +- .../test/java/sd/des/DESComponentsTest.java | 174 ++++++ 43 files changed, 3737 insertions(+), 1002 deletions(-) create mode 100644 main/src/main/java/sd/aggregator/AggregatorClient.java create mode 100644 main/src/main/java/sd/aggregator/AggregatorServer.java create mode 100644 main/src/main/java/sd/analysis/MultiRunAnalyzer.java create mode 100644 main/src/main/java/sd/analysis/SimulationBatchRunner.java create mode 100644 main/src/main/java/sd/analysis/SimulationRunResult.java create mode 100644 main/src/main/java/sd/analysis/StatisticalAnalysis.java create mode 100644 main/src/main/java/sd/dashboard/ConfigurationDialog.java create mode 100644 main/src/main/java/sd/des/DESEventType.java create mode 100644 main/src/main/java/sd/des/EventQueue.java create mode 100644 main/src/main/java/sd/des/SimulationClock.java create mode 100644 main/src/main/java/sd/des/SimulationEvent.java create mode 100644 main/src/main/java/sd/des/TrafficLightEvent.java delete mode 100644 main/src/main/java/sd/engine/TrafficLightThread.java create mode 100644 main/src/main/java/sd/logging/EventLogger.java create mode 100644 main/src/main/java/sd/logging/EventType.java create mode 100644 main/src/main/java/sd/logging/VehicleTracer.java create mode 100644 main/src/main/resources/simulation-high.properties create mode 100644 main/src/main/resources/simulation-low.properties create mode 100644 main/src/main/resources/simulation-medium.properties create mode 100644 main/src/test/java/sd/des/DESComponentsTest.java diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java index 44b1b88..725fd44 100644 --- a/main/src/main/java/sd/ExitNodeProcess.java +++ b/main/src/main/java/sd/ExitNodeProcess.java @@ -12,6 +12,13 @@ import java.util.concurrent.TimeUnit; import sd.config.SimulationConfig; import sd.coordinator.SocketClient; import sd.dashboard.StatsUpdatePayload; +import sd.des.DESEventType; +import sd.des.EventQueue; +import sd.des.SimulationClock; +import sd.des.SimulationEvent; +import sd.logging.EventLogger; +import sd.logging.EventType; +import sd.logging.VehicleTracer; import sd.model.Message; import sd.model.MessageType; import sd.model.Vehicle; @@ -20,16 +27,17 @@ import sd.protocol.MessageProtocol; import sd.protocol.SocketConnection; /** - * Processo responsável pelo nó de saída do sistema de simulação de tráfego - * distribuído. + * Destino final de todos os veículos da simulação (nó de saída S). * - * 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 - * - Enviar estatísticas periódicas para o dashboard - * - Gerar relatórios finais ao terminar a simulação + *

Opera como sumidouro da rede: + *

    + *
  1. Recebe veículos que completaram a viagem + *
  2. Regista estatísticas finais (tempo total, espera, travessia) + *
  3. Envia métricas ao dashboard em tempo real + *
+ * + *

Participa no DES rastreando eventos, mas opera principalmente + * de forma reativa, aguardando chegadas via socket. */ public class ExitNodeProcess { @@ -37,41 +45,43 @@ public class ExitNodeProcess { private ServerSocket serverSocket; private final ExecutorService connectionHandlerPool; - /** - * Flag para controlar a execução do processo (volatile para visibilidade entre - * threads) - */ + // DES components + private final SimulationClock clock; + private final EventQueue eventQueue; + private final EventLogger eventLogger; + private Thread eventProcessorThread; + + /** Flag de controlo (volatile para visibilidade entre threads) */ private volatile boolean running; - /** Simulation start time (milliseconds) to calculate relative times */ + /** Instante de início da simulação (milissegundos) */ private long simulationStartMillis; - /** Counter de veículos que completaram a rota */ + /** Contador de veículos que completaram a rota */ private int totalVehiclesReceived; - /** Soma dos tempos no sistema de todos os veículos */ + /** Tempo acumulado no sistema de todos os veículos */ private double totalSystemTime; - /** Soma dos tempos de espera de todos os veículos */ + /** Tempo acumulado em espera de todos os veículos */ private double totalWaitingTime; - /** Soma dos tempos de travessia de todos os veículos */ + /** Tempo acumulado em 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 */ + /** Tempo de espera acumulado por tipo de veículo */ private final Map vehicleTypeWaitTime; - /** Socket para comunicação com o dashboard */ + /** Cliente socket para envio de estatísticas ao dashboard */ private SocketClient dashboardClient; /** - * Método para iniciar o processo + * Ponto de entrada do processo. * - * @param args Argumentos da linha de comandos. Se fornecido, args[0] deve ser - * o caminho para um ficheiro de configuração personalizado. + * @param args args[0] (opcional) = caminho do ficheiro de configuração */ public static void main(String[] args) { System.out.println("=".repeat(60)); @@ -79,6 +89,8 @@ public class ExitNodeProcess { System.out.println("=".repeat(60)); try { + EventLogger.getInstance().log(EventType.PROCESS_STARTED, "ExitNode", "Exit node process started"); + String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties"; System.out.println("Loading configuration from: " + configFile); @@ -93,22 +105,25 @@ public class ExitNodeProcess { } catch (IOException e) { System.err.println("Failed to start exit node: " + e.getMessage()); + EventLogger.getInstance().logError("ExitNode", "Failed to start", e); System.exit(1); } catch (Exception e) { System.err.println("Exit node error: " + e.getMessage()); + EventLogger.getInstance().logError("ExitNode", "Exit node error", e); System.exit(1); + } finally { + EventLogger.getInstance().log(EventType.PROCESS_STOPPED, "ExitNode", "Exit node process stopped"); } } /** - * Constrói um novo processo de nó de saída. + * Configura o Nó de Saída. * - * Inicializa todas as estruturas de dados necessárias para recolher - * estatísticas - * e configura o pool de threads para processar as ligações concorrentes. + * Inicializamos os nossos contadores, preparamos a pool de threads para tratar + * das ligações de veículos recebidas, + * e configuramos os componentes DES para rastreio de eventos. * - * @param config Configuração da simulação contendo portas e endereços dos - * serviços + * @param config A configuração da simulação. */ public ExitNodeProcess(SimulationConfig config) { this.config = config; @@ -128,17 +143,23 @@ public class ExitNodeProcess { vehicleTypeWaitTime.put(type, 0.0); } - System.out.println("Exit node initialized"); + // Initialize DES components + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); // Track history + this.eventLogger = EventLogger.getInstance(); + + eventLogger.log(EventType.PROCESS_STARTED, "ExitNode", + "Exit node initialized with DES architecture"); + + System.out.println("Exit node initialized (DES Mode)"); System.out.println(" - Exit port: " + config.getExitPort()); System.out.println(" - Dashboard: " + config.getDashboardHost() + ":" + config.getDashboardPort()); } /** - * Inicializa o processo de ligação ao dashboard. - * - * Tenta conectar-se ao dashboard. Se a ligação falhar, o processo - * continua a funcionar normalmente, mas sem enviar estatísticas. - * + * Tenta estabelecer uma ligação ao dashboard. + * Se for bem-sucedido, poderemos enviar estatísticas em tempo real. Se não, + * apenas registamos localmente. */ public void initialize() { System.out.println("Connecting to dashboard..."); @@ -158,23 +179,141 @@ public class ExitNodeProcess { } /** - * Inicia o socket e começa a aceitar ligações. + * Starts the DES event processing thread. + * Currently, ExitNode is primarily reactive (receives vehicles via network), + * but maintains event queue for potential scheduled events and history + * tracking. + */ + private void startEventProcessor() { + eventProcessorThread = new Thread(() -> { + eventLogger.log(EventType.SIMULATION_STARTED, "ExitNode", + "Event processor thread started"); + + // Keep running while process is active + while (running) { + SimulationEvent event = eventQueue.poll(); + if (event == null) { + // No events currently, wait before checking again + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + continue; + } + + // Advance clock to event time + clock.advanceTo(event.getTimestamp()); + + // Process the event + processEvent(event); + } + + eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode", + String.format("Event processor thread terminated at time %.2f", clock.getCurrentTime())); + }, "EventProcessor-ExitNode"); + + eventProcessorThread.start(); + } + + /** + * Processes a discrete event based on its type. + * Currently supports VEHICLE_EXIT and SIMULATION_END events. + */ + private void processEvent(SimulationEvent event) { + try { + switch (event.getType()) { + case VEHICLE_EXIT: + // Vehicle exits are handled via network messages in real-time + // This event type can be used for scheduled vehicle processing + break; + + case SIMULATION_END: + handleSimulationEndEvent(event); + break; + + default: + System.err.println("[ExitNode] Unknown event type: " + event.getType()); + } + } catch (Exception e) { + System.err.println("[ExitNode] Error processing event " + event.getType() + + " at time " + event.getTimestamp() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Handles simulation end event. + */ + private void handleSimulationEndEvent(SimulationEvent event) { + eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode", + String.format("Simulation ended at time %.2f", event.getTimestamp())); + running = false; + + // Print final statistics + printFinalStatistics(); + } + + /** + * Exports the complete event history for the exit node. + * This satisfies the spec requirement: "Deve ser possível verificar a lista + * completa de eventos" + */ + public void exportEventHistory(String outputPath) { + String history = eventQueue.exportEventHistory(); + try (java.io.PrintWriter writer = new java.io.PrintWriter(outputPath)) { + writer.println(history); + System.out.println("[ExitNode] Event history exported to: " + outputPath); + } catch (java.io.FileNotFoundException e) { + System.err.println("[ExitNode] Failed to export event history: " + e.getMessage()); + } + } + + /** + * Schedules a simulation end event at the specified time. * - * Este é o loop principal do processo que: - * 1. Cria um socket na porta definida - * 2. Aguarda pelas ligações das interseções - * 3. Delega cada ligação a uma thread da pool para processamento assíncrono + * @param endTime The simulation time when the simulation should end + */ + public void scheduleSimulationEnd(double endTime) { + SimulationEvent endEvent = new SimulationEvent( + endTime, + DESEventType.SIMULATION_END, + null); + eventQueue.schedule(endEvent); + System.out.println("[ExitNode] Simulation end scheduled at time " + endTime); + } + + /** + * Abre o socket do servidor e começa a escutar por veículos. * - * @throws IOException Se o socket não puder ser criado ou houver erro na - * aceitação + * Este é o loop principal. Aceitamos ligações das interseções (de onde vêm os + * veículos) + * e passamo-las para a nossa pool de threads para processamento. + * + * @throws IOException Se não conseguirmos fazer bind à porta. */ public void start() throws IOException { + start(true); // Default to DES mode + } + + /** + * Starts the exit node process. + * + * @param useDES If true, starts event processor for DES mode tracking + */ + public void start(boolean useDES) throws IOException { int port = config.getExitPort(); serverSocket = new ServerSocket(port); running = true; simulationStartMillis = System.currentTimeMillis(); System.out.println("Exit node started on port " + port); + if (useDES) { + // Note: ExitNode is primarily reactive (network-driven), but maintains + // event queue for simulation end events and history tracking + System.out.println("Running in DES mode (event history tracking enabled)"); + } System.out.println("Waiting for vehicles...\\n"); while (running) { @@ -190,13 +329,12 @@ public class ExitNodeProcess { } /** - * Processa uma ligação recebida de uma interseção. + * Trata uma ligação 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. + * Mantemos a ligação aberta e escutamos por mensagens `VEHICLE_TRANSFER`. + * Cada mensagem contém um veículo que acabou de terminar a sua viagem. * - * @param clientSocket Socket da ligação estabelecida com a interseção + * @param clientSocket O socket ligado à interseção. */ private void handleIncomingConnection(Socket clientSocket) { String clientAddress = clientSocket.getInetAddress().getHostAddress(); @@ -252,25 +390,24 @@ public class ExitNodeProcess { } /** - * Processa um veículo que chegou ao nó de saída. + * Processa um veículo que acabou de sair do sistema. * - * Método sincronizado para garantir thread-safety ao atualizar as estatísticas. - * Calcula as métricas finais do veículo e atualiza: - * - Counters globais; - * - Estatísticas por tipo de veículo; - * - Faz update ao dashboard a cada 10 veículos. + * Calculamos quanto tempo demorou, atualizamos as nossas estatísticas globais e + * notificamos o dashboard. + * Este método é sincronizado porque múltiplos veículos podem chegar ao mesmo + * tempo. * - * @param vehicle Veículo que completou a sua rota + * @param vehicle O veículo que completou a sua rota. */ 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(); + // Use simulation time instead of wall-clock time + // System time = total time vehicle spent in system (wait + crossing times) + // This represents the actual simulation time elapsed, not real-time double waitTime = vehicle.getTotalWaitingTime(); double crossingTime = vehicle.getTotalCrossingTime(); + double systemTime = waitTime + crossingTime; // Store times in seconds, will be converted to ms when sending to dashboard totalSystemTime += systemTime; @@ -284,18 +421,23 @@ public class ExitNodeProcess { System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n", vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime); + // Log vehicle exit + EventLogger.getInstance().logVehicle(EventType.VEHICLE_EXITED, "ExitNode", vehicle.getId(), + String.format("Completed - System: %.2fs, Wait: %.2fs, Crossing: %.2fs", systemTime, waitTime, + crossingTime)); + + // Complete vehicle trace if tracking + VehicleTracer.getInstance().logExit(vehicle, systemTime); + // Send stats after every vehicle to ensure dashboard updates quickly sendStatsToDashboard(); } /** - * Envia as estatísticas para o dashboard. - * - * Prepara e envia uma mensagem STATS_UPDATE com: - * - O total de veículos processados; - * - A média dos tempos (sistema, espera, travessia); - * - As contagens e médias por cada tipo de veículo. + * Envia as estatísticas mais recentes para o dashboard. * + * Empacotamos as contagens totais e os tempos médios num `StatsUpdatePayload` + * e enviamo-lo. */ private void sendStatsToDashboard() { if (dashboardClient == null || !dashboardClient.isConnected()) { @@ -347,14 +489,9 @@ public class ExitNodeProcess { } /** - * Termina o processo + * Encerra graciosamente 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; + * Imprimimos as estatísticas finais, fechamos ligações e limpamos threads. */ public void shutdown() { System.out.println("\n[Exit] Shutting down..."); @@ -390,15 +527,9 @@ public class ExitNodeProcess { } /** - * Imprime as estatísticas finais detalhadas no terminal - * - * Gera um relatório com: - * Total de veículos que completaram a rota; - * Médias de tempo no sistema, espera e travessia; - * Distribuição e médias pelo tipo de veículo (BIKE, LIGHT, HEAVY); - * - * Este método é chamado durante o shutdown para fornecer um resumo - * da simulação antes de terminar o processo. + * Imprime um resumo dos resultados da simulação na consola. + * Isto dá-nos uma visão rápida de como a simulação correu (médias, contagens de + * veículos, etc.). */ private void printFinalStatistics() { System.out.println("\n=== EXIT NODE STATISTICS ==="); diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index 437b962..dc2dc5b 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -16,21 +16,36 @@ import java.util.concurrent.locks.ReentrantLock; import sd.config.SimulationConfig; import sd.coordinator.SocketClient; import sd.dashboard.StatsUpdatePayload; -import sd.engine.TrafficLightThread; +import sd.des.DESEventType; +import sd.des.EventQueue; +import sd.des.SimulationClock; +import sd.des.SimulationEvent; +import sd.des.TrafficLightEvent; +import sd.logging.EventLogger; import sd.model.Intersection; import sd.model.Message; import sd.model.MessageType; import sd.model.TrafficLight; +import sd.model.TrafficLightState; import sd.model.Vehicle; import sd.protocol.MessageProtocol; import sd.protocol.SocketConnection; import sd.serialization.SerializationException; /** - * Main class for an Intersection Process in the distributed traffic simulation. - * * Each IntersectionProcess runs as an independent Java application (JVM - * instance) - * representing one of the five intersections (Cr1-Cr5) in the network. + * Representa uma única interseção na nossa simulação de tráfego distribuída. + * + * Esta classe opera como um processo independente (uma aplicação Java autónoma) + * e é responsável por: + * 1. Gerir os semáforos e a sua temporização. + * 2. Processar as chegadas e partidas de veículos. + * 3. Comunicar com outras interseções e com o dashboard. + * + * Utiliza uma abordagem de Simulação de Eventos Discretos (DES), onde as + * mudanças de estado (como semáforos a mudar para verde) + * são agendadas como eventos numa fila de prioridade, em vez de depender de + * loops contínuos ou threads em espera. + * Isto garante uma temporização precisa e uma execução eficiente. */ public class IntersectionProcess { @@ -46,25 +61,31 @@ public class IntersectionProcess { private final ExecutorService connectionHandlerPool; - private final ExecutorService trafficLightPool; - private ScheduledExecutorService statsExecutor; private ScheduledExecutorService departureExecutor; - private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras - // threads veem a mudança imediatamente. + private volatile boolean running; + /** Escala temporal para visualização: tempo_real = tempo_simulado * escala */ + private double timeScale; + + /** Relógio central da simulação */ + private final SimulationClock clock; + /** Fila de eventos discretos agendados */ + private final EventQueue eventQueue; + /** Sistema de registo de eventos */ + private final EventLogger eventLogger; + /** Thread dedicada ao processamento sequencial de eventos DES */ + private Thread eventProcessorThread; - // Traffic Light Coordination /** - * Lock to ensure mutual exclusion between traffic lights. - * Only one traffic light can be green at any given time within this - * intersection. + * Lock para exclusão mútua entre semáforos. + * Garante que apenas um semáforo pode estar verde de cada vez nesta interseção. */ private final Lock trafficCoordinationLock; /** - * Tracks which direction currently has the green light. - * null means no direction is currently green (all are red). + * Regista qual direção tem atualmente o sinal verde. + * {@code null} significa que todos os semáforos estão vermelhos. */ private volatile String currentGreenDirection; @@ -73,11 +94,11 @@ public class IntersectionProcess { private volatile int totalDepartures = 0; /** - * Constructs a new IntersectionProcess. - * - * @param intersectionId The ID of this intersection (e.g., "Cr1"). - * @param configFilePath Path to the simulation.properties file. - * @throws IOException If configuration cannot be loaded. + * Inicializa o processo da interseção. + * + * @param intersectionId O identificador único para esta interseção (ex: "Cr1"). + * @param configFilePath O caminho para o ficheiro de configuração. + * @throws IOException Se houver algum problema ao ler a configuração. */ public IntersectionProcess(String intersectionId, String configFilePath) throws IOException { this.intersectionId = intersectionId; @@ -85,18 +106,327 @@ public class IntersectionProcess { this.intersection = new Intersection(intersectionId); this.outgoingConnections = new HashMap<>(); this.connectionHandlerPool = Executors.newCachedThreadPool(); - this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions this.statsExecutor = Executors.newSingleThreadScheduledExecutor(); this.departureExecutor = Executors.newScheduledThreadPool(4); this.running = false; this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation this.currentGreenDirection = null; + this.timeScale = config.getTimeScale(); + + // Initialize DES components + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); // Track history for debugging + this.eventLogger = EventLogger.getInstance(); + + eventLogger.log(sd.logging.EventType.PROCESS_STARTED, intersectionId, + "Intersection process initialized with DES architecture"); System.out.println("=".repeat(60)); - System.out.println("INTERSECTION PROCESS: " + intersectionId); + System.out.println("INTERSECTION PROCESS: " + intersectionId + " (DES Mode)"); System.out.println("=".repeat(60)); } + /** + * Inicia o ciclo de processamento de eventos. + * + * Esta thread é o coração do modelo DES para esta interseção. Retira eventos da + * fila + * e executa-os por ordem cronológica. Enquanto a thread principal trata das + * operações de I/O de rede (receção de veículos), + * esta thread trata da lógica da simulação (semáforos, travessias de veículos). + */ + private void startEventProcessor() { + eventProcessorThread = new Thread(() -> { + eventLogger.log(sd.logging.EventType.SIMULATION_STARTED, intersectionId, + "Event processor thread started"); + + // Keep running while the process is active + double lastTime = 0.0; + while (running) { + SimulationEvent event = eventQueue.poll(); + if (event == null) { + // No events currently, wait a bit before checking again + try { + Thread.sleep(50); // Short sleep to avoid busy-waiting + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + continue; + } + + // Apply time scaling for visualization + if (timeScale > 0) { + double simTimeDelta = event.getTimestamp() - lastTime; + long realDelayMs = (long) (simTimeDelta * timeScale * 1000); + if (realDelayMs > 0) { + try { + Thread.sleep(realDelayMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + lastTime = event.getTimestamp(); + } + + // Advance clock to event time + clock.advanceTo(event.getTimestamp()); + + // Process the event + processEvent(event); + } + + eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId, + String.format("Event processor thread terminated at time %.2f", clock.getCurrentTime())); + }, "EventProcessor-" + intersectionId); + + eventProcessorThread.start(); + } + + /** + * Processa um evento da fila de simulação. + * Cada tipo de evento é encaminhado para o seu tratador específico. + * + * @param event o evento a processar + */ + private void processEvent(SimulationEvent event) { + try { + switch (event.getType()) { + case TRAFFIC_LIGHT_CHANGE: + handleTrafficLightChangeEvent(event); + break; + + case VEHICLE_ARRIVAL: + // Vehicle arrivals are still handled via network messages + // This event type is for internal scheduling if needed + break; + + case VEHICLE_CROSSING_START: + handleVehicleCrossingStartEvent(event); + break; + + case VEHICLE_CROSSING_END: + handleVehicleCrossingEndEvent(event); + break; + + case SIMULATION_END: + handleSimulationEndEvent(event); + break; + + default: + System.err.println("[" + intersectionId + "] Unknown event type: " + event.getType()); + } + } catch (Exception e) { + System.err.println("[" + intersectionId + "] Error processing event " + event.getType() + + " at time " + event.getTimestamp() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Trata da mudança dos semáforos. + * + * Quando um semáforo muda de estado, registamos o evento, atualizamos o modelo + * e, se tiver mudado para VERDE, + * verificamos imediatamente se há veículos à espera para atravessar. + * Também agendamos aqui o *próximo* evento de mudança, mantendo o ciclo ativo. + */ + private void handleTrafficLightChangeEvent(SimulationEvent event) { + TrafficLightEvent tlEvent = (TrafficLightEvent) event.getPayload(); + TrafficLight light = tlEvent.getLight(); + String direction = tlEvent.getDirection(); + + // Toggle state + TrafficLightState oldState = light.getState(); + TrafficLightState newState = (oldState == TrafficLightState.GREEN) ? TrafficLightState.RED + : TrafficLightState.GREEN; + + light.changeState(newState); + + sd.logging.EventType logEventType = (newState == TrafficLightState.GREEN) + ? sd.logging.EventType.LIGHT_CHANGED_GREEN + : sd.logging.EventType.LIGHT_CHANGED_RED; + + eventLogger.log(logEventType, intersectionId, + String.format("Direction %s changed to %s at time %.2f", + direction, newState, event.getTimestamp())); + + // If light turned GREEN, process queued vehicles + if (newState == TrafficLightState.GREEN) { + processQueuedVehiclesForLight(light, event.getTimestamp()); + } + + // Schedule next state change + double nextChangeTime = event.getTimestamp() + + (newState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime()); + + SimulationEvent nextEvent = new SimulationEvent( + nextChangeTime, + DESEventType.TRAFFIC_LIGHT_CHANGE, + tlEvent); + eventQueue.schedule(nextEvent); + } + + /** + * Processa a fila de veículos quando um semáforo fica verde. + * + *

Para cada veículo na fila:

+ *
    + *
  1. Calcula o tempo de travessia com base no tipo de veículo
  2. + *
  3. Verifica se cabe na duração restante do sinal verde
  4. + *
  5. Agenda o evento de partida do veículo
  6. + *
+ * + *

Os veículos que não couberem no tempo verde ficam à espera do próximo ciclo.

+ * + * @param light o semáforo que acabou de ficar verde + * @param currentTime o tempo atual da simulação em segundos + */ + private void processQueuedVehiclesForLight(TrafficLight light, double currentTime) { + double greenDuration = light.getGreenTime(); + double timeOffset = 0.0; + + int queueSize = light.getQueueSize(); + System.out.printf("[%s] Processing queue for %s (GREEN for %.2fs, queue size: %d, currentTime=%.2f)%n", + intersectionId, light.getId(), greenDuration, queueSize, currentTime); + + // Process vehicles while queue not empty and within green light duration + while (light.getQueueSize() > 0) { + // Calculate crossing time for next vehicle (peek at queue size to estimate) + // We'll use LIGHT vehicle as default for estimation + double crossingTime = config.getLightVehicleCrossingTime(); + + // Check if another vehicle can fit in remaining green time + if (timeOffset + crossingTime > greenDuration) { + break; // No more vehicles can cross this green phase + } + + // Remove vehicle from queue + Vehicle vehicle = light.removeVehicle(); + if (vehicle == null) + break; + + // Get actual crossing time for this vehicle + crossingTime = getCrossingTimeForVehicle(vehicle); + + // Schedule crossing + double crossingStartTime = currentTime + timeOffset; + scheduleVehicleCrossing(vehicle, crossingStartTime, crossingTime); + + // Update offset for next vehicle + timeOffset += crossingTime; + + System.out.printf("[%s] Scheduled vehicle %s to cross at t=%.2f (duration=%.2fs)%n", + intersectionId, vehicle.getId(), crossingStartTime, crossingTime); + } + } + + /** + * Agenda a travessia e partida de um veículo. + * Cria um evento de fim de travessia agendado para o tempo correto. + * + * @param vehicle o veículo que vai atravessar + * @param startTime quando a travessia começa (segundos de simulação) + * @param crossingDuration quanto tempo demora a atravessar (segundos) + */ + private void scheduleVehicleCrossing(Vehicle vehicle, double startTime, double crossingDuration) { + // Schedule crossing end (when vehicle departs) + double departureTime = startTime + crossingDuration; + + // Create event with vehicle as payload + SimulationEvent departureEvent = new SimulationEvent( + departureTime, + DESEventType.VEHICLE_CROSSING_END, + vehicle); + eventQueue.schedule(departureEvent); + + eventLogger.log(sd.logging.EventType.VEHICLE_QUEUED, intersectionId, + String.format("Vehicle %s crossing scheduled: %.2fs to %.2fs", + vehicle.getId(), startTime, departureTime)); + } + + /** + * Calcula o tempo de travessia com base no tipo de veículo. + * Bicicletas são mais rápidas, veículos pesados mais lentos. + * + * @param vehicle o veículo para calcular o tempo + * @return tempo de travessia em segundos + */ + private double getCrossingTimeForVehicle(Vehicle vehicle) { + return switch (vehicle.getType()) { + case BIKE -> config.getBikeVehicleCrossingTime(); + case LIGHT -> config.getLightVehicleCrossingTime(); + case HEAVY -> config.getHeavyVehicleCrossingTime(); + default -> config.getLightVehicleCrossingTime(); + }; + } + + /** + * Trata o evento de início de travessia de um veículo. + * (Implementação futura - atualmente apenas regista o evento) + * + * @param event o evento de início de travessia + */ + private void handleVehicleCrossingStartEvent(SimulationEvent event) { + // Implementation will depend on how vehicle crossing is modeled + // For now, log the event + eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId, + "Vehicle crossing started at time " + event.getTimestamp()); + } + + /** + * Trata o fim da travessia de um veículo pela interseção. + * Atualiza estatísticas, regista o tempo de travessia e envia o veículo + * para o próximo destino na sua rota. + * + * @param event evento contendo o veículo que terminou a travessia + */ + private void handleVehicleCrossingEndEvent(SimulationEvent event) { + Vehicle vehicle = (Vehicle) event.getPayload(); + + // Add crossing time to vehicle stats + double crossingTime = getCrossingTimeForVehicle(vehicle); + vehicle.addCrossingTime(crossingTime); + + // Update intersection statistics + intersection.incrementVehiclesSent(); + + // Send vehicle to next destination + sendVehicleToNextDestination(vehicle); + + eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId, + String.format("Vehicle %s departed at time %.2f", vehicle.getId(), event.getTimestamp())); + } + + /** + * Trata o evento de fim da simulação. + * Define a flag de execução como falsa para terminar o processamento. + * + * @param event o evento de fim de simulação + */ + private void handleSimulationEndEvent(SimulationEvent event) { + eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId, + String.format("Simulation ended at time %.2f", event.getTimestamp())); + running = false; + } + + /** + * Exporta o histórico completo de eventos para um ficheiro. + * Útil para análise posterior e debugging da simulação. + * + * @param outputPath caminho do ficheiro onde guardar o histórico + */ + public void exportEventHistory(String outputPath) { + String history = eventQueue.exportEventHistory(); + try (java.io.PrintWriter writer = new java.io.PrintWriter(outputPath)) { + writer.println(history); + System.out.println("[" + intersectionId + "] Event history exported to: " + outputPath); + } catch (java.io.FileNotFoundException e) { + System.err.println("[" + intersectionId + "] Failed to export event history: " + e.getMessage()); + } + } + // Main entry point for running an intersection process public static void main(String[] args) { if (args.length < 1) { @@ -139,7 +469,7 @@ public class IntersectionProcess { } /** - * Establishes connection to the dashboard server for statistics reporting. + * Estabelece ligação ao servidor do dashboard para reportar estatísticas. */ private void connectToDashboard() { try { @@ -163,10 +493,9 @@ public class IntersectionProcess { } /** - * Creates traffic lights for this intersection based on its physical - * connections. - * Each intersection has different number and directions of traffic lights - * according to the network topology. + * Cria os semáforos para esta interseção com base nas suas ligações físicas. + * Cada interseção tem um número e direções de semáforos diferentes de acordo + * com a topologia da rede. */ private void createTrafficLights() { System.out.println("\n[" + intersectionId + "] Creating traffic lights..."); @@ -226,10 +555,11 @@ public class IntersectionProcess { } /** - * Requests permission for a traffic light to turn green. - * Blocks until permission is granted (no other light is green). + * Solicita permissão para um semáforo ficar verde. + * Bloqueia até que a permissão seja concedida (nenhum outro semáforo está + * verde). * - * @param direction The direction requesting green light + * @param direction A direção que solicita o sinal verde */ public void requestGreenLight(String direction) { trafficCoordinationLock.lock(); @@ -237,9 +567,10 @@ public class IntersectionProcess { } /** - * Releases the green light permission, allowing another light to turn green. + * Liberta a permissão de sinal verde, permitindo que outro semáforo fique + * verde. * - * @param direction The direction releasing green light + * @param direction A direção que liberta o sinal verde */ public void releaseGreenLight(String direction) { if (direction.equals(currentGreenDirection)) { @@ -249,25 +580,49 @@ public class IntersectionProcess { } /** - * Starts all traffic light threads. + * Modo DES: Agenda os eventos iniciais de mudança de semáforo. + * Isto substitui a antiga abordagem baseada em threads startTrafficLights(). */ - private void startTrafficLights() { - System.out.println("\n[" + intersectionId + "] Starting traffic light threads..."); + private void scheduleInitialTrafficLightEvents() { + System.out.println("\n[" + intersectionId + "] Scheduling initial traffic light events (DES mode)..."); + + double currentTime = clock.getCurrentTime(); + System.out.printf("[%s] Initial clock time: %.2f%n", intersectionId, currentTime); for (TrafficLight light : intersection.getTrafficLights()) { + String direction = light.getDirection(); - TrafficLightThread lightTask = new TrafficLightThread(light, this, config); + // Set initial state (first light starts green, others red) + boolean isFirstLight = intersection.getTrafficLights().indexOf(light) == 0; + TrafficLightState initialState = isFirstLight ? TrafficLightState.GREEN : TrafficLightState.RED; + light.changeState(initialState); - trafficLightPool.submit(lightTask); + // Schedule first state change + double firstChangeTime = currentTime + + (initialState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime()); - System.out.println(" Started thread for: " + light.getDirection()); + TrafficLightEvent tlEvent = new TrafficLightEvent(light, direction, intersectionId); + SimulationEvent event = new SimulationEvent( + firstChangeTime, + DESEventType.TRAFFIC_LIGHT_CHANGE, + tlEvent); + eventQueue.schedule(event); + + System.out.println(" Scheduled first event for direction " + direction + + " (initial: " + initialState + ", change at t=" + firstChangeTime + ")"); + + eventLogger.log( + initialState == TrafficLightState.GREEN ? sd.logging.EventType.LIGHT_CHANGED_GREEN + : sd.logging.EventType.LIGHT_CHANGED_RED, + intersectionId, + "Direction " + direction + " initialized to " + initialState); } } /** - * Sends a vehicle to its next destination via socket connection. + * Envia um veículo para o seu próximo destino via ligação socket. * - * @param vehicle The vehicle that has crossed this intersection. + * @param vehicle O veículo que atravessou esta interseção. */ public void sendVehicleToNextDestination(Vehicle vehicle) { String nextDestination = vehicle.getCurrentDestination(); @@ -281,7 +636,6 @@ public class IntersectionProcess { default -> multiplier = 1.0; } double travelTime = baseTime * multiplier; - long travelTimeMs = (long) (travelTime * 1000); System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n", intersectionId, vehicle.getId(), nextDestination, travelTime); @@ -289,41 +643,46 @@ public class IntersectionProcess { // Record departure immediately as it leaves the intersection recordVehicleDeparture(); - // Schedule the arrival at the next node - departureExecutor.schedule(() -> { - try { - // Get or create connection to next destination - SocketConnection connection = getOrCreateConnection(nextDestination); - - // Create and send message using Message class - MessageProtocol message = new Message( - MessageType.VEHICLE_TRANSFER, - intersectionId, - nextDestination, - vehicle, - System.currentTimeMillis()); - - connection.sendMessage(message); - - System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() + - " arrived at " + nextDestination + " (msg sent)"); - - // Note: vehicle route is advanced when it arrives at the next intersection - - } catch (IOException | InterruptedException e) { - System.err.println("[" + intersectionId + "] Failed to send vehicle " + - vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); - } - }, travelTimeMs, TimeUnit.MILLISECONDS); + // In DES mode, send immediately (no real-time delay) + sendVehicleImmediately(vehicle, nextDestination); } /** - * Gets an existing connection to a destination or creates a new one. + * Envia imediatamente um veículo para o seu destino via rede. + */ + private void sendVehicleImmediately(Vehicle vehicle, String nextDestination) { + try { + // Get or create connection to next destination + SocketConnection connection = getOrCreateConnection(nextDestination); + + // Create and send message using Message class + MessageProtocol message = new Message( + MessageType.VEHICLE_TRANSFER, + intersectionId, + nextDestination, + vehicle, + System.currentTimeMillis()); + + connection.sendMessage(message); + + System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() + + " arrived at " + nextDestination + " (msg sent)"); + + // Note: vehicle route is advanced when it arrives at the next intersection + + } catch (IOException | InterruptedException e) { + System.err.println("[" + intersectionId + "] Failed to send vehicle " + + vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); + } + } + + /** + * Obtém uma ligação existente para um destino ou cria uma nova. * - * @param destinationId The ID of the destination node. - * @return The SocketConnection to that destination. - * @throws IOException If connection cannot be established. - * @throws InterruptedException If connection attempt is interrupted. + * @param destinationId O ID do nó de destino. + * @return A SocketConnection para esse destino. + * @throws IOException Se a ligação não puder ser estabelecida. + * @throws InterruptedException Se a tentativa de ligação for interrompida. */ private synchronized SocketConnection getOrCreateConnection(String destinationId) throws IOException, InterruptedException { @@ -343,10 +702,10 @@ public class IntersectionProcess { } /** - * Gets the host address for a destination node from configuration. + * Obtém o endereço host para um nó de destino a partir da configuração. * - * @param destinationId The destination node ID. - * @return The host address. + * @param destinationId O ID do nó de destino. + * @return O endereço host. */ private String getHostForDestination(String destinationId) { if (destinationId.equals("S")) { @@ -357,10 +716,10 @@ public class IntersectionProcess { } /** - * Gets the port number for a destination node from configuration. + * Obtém o número da porta para um nó de destino a partir da configuração. * - * @param destinationId The destination node ID. - * @return The port number. + * @param destinationId O ID do nó de destino. + * @return O número da porta. */ private int getPortForDestination(String destinationId) { if (destinationId.equals("S")) { @@ -371,10 +730,10 @@ public class IntersectionProcess { } /** - * Starts the server socket and begins accepting incoming connections. - * This is the main listening loop of the process. + * Inicia o socket do servidor e começa a aceitar ligações recebidas. + * Este é o loop principal de escuta do processo. * - * @throws IOException If the server socket cannot be created. + * @throws IOException Se o socket do servidor não puder ser criado. */ public void start() throws IOException { int port = config.getIntersectionPort(intersectionId); @@ -383,8 +742,10 @@ public class IntersectionProcess { System.out.println("\n[" + intersectionId + "] Server started on port " + port); - // Start traffic light threads when running is true - startTrafficLights(); + // DES Mode: Schedule initial events and start event processor + scheduleInitialTrafficLightEvents(); + startEventProcessor(); + System.out.println("[" + intersectionId + "] Running in DES mode"); // Start stats updater statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS); @@ -429,10 +790,10 @@ public class IntersectionProcess { } /** - * Handles an incoming connection from another process. - * Continuously listens for vehicle transfer messages. + * Trata uma ligação recebida de outro processo. + * Escuta continuamente mensagens de transferência de veículos. * - * @param clientSocket The accepted socket connection. + * @param clientSocket A ligação socket aceite. */ private void handleIncomingConnection(Socket clientSocket) { try { @@ -486,6 +847,10 @@ public class IntersectionProcess { // Add vehicle to appropriate queue intersection.receiveVehicle(vehicle); + // Log queue status after adding vehicle + System.out.printf("[%s] Vehicle %s queued. Total queue size: %d%n", + intersectionId, vehicle.getId(), intersection.getTotalQueueSize()); + // Record arrival for statistics recordVehicleArrival(); } else if (message.getType() == MessageType.SHUTDOWN) { @@ -550,9 +915,7 @@ public class IntersectionProcess { } // 2. Shutdown thread pools with force - if (trafficLightPool != null && !trafficLightPool.isShutdown()) { - trafficLightPool.shutdownNow(); - } + if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) { connectionHandlerPool.shutdownNow(); } @@ -565,9 +928,7 @@ public class IntersectionProcess { // 3. Wait briefly for termination (don't block forever) try { - if (trafficLightPool != null) { - trafficLightPool.awaitTermination(1, TimeUnit.SECONDS); - } + if (connectionHandlerPool != null) { connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS); } diff --git a/main/src/main/java/sd/aggregator/AggregatorClient.java b/main/src/main/java/sd/aggregator/AggregatorClient.java new file mode 100644 index 0000000..e69de29 diff --git a/main/src/main/java/sd/aggregator/AggregatorServer.java b/main/src/main/java/sd/aggregator/AggregatorServer.java new file mode 100644 index 0000000..e69de29 diff --git a/main/src/main/java/sd/analysis/MultiRunAnalyzer.java b/main/src/main/java/sd/analysis/MultiRunAnalyzer.java new file mode 100644 index 0000000..cb927f6 --- /dev/null +++ b/main/src/main/java/sd/analysis/MultiRunAnalyzer.java @@ -0,0 +1,223 @@ +package sd.analysis; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.*; + +import sd.model.VehicleType; + +/** + * Executes multiple simulation runs and aggregates results. + * Calculates statistical measures including mean, standard deviation, + * and confidence intervals across all runs. + */ +public class MultiRunAnalyzer { + + private final List results; + private final String configurationFile; + + public MultiRunAnalyzer(String configurationFile) { + this.configurationFile = configurationFile; + this.results = new ArrayList<>(); + } + + /** + * Adds a completed simulation run result. + */ + public void addResult(SimulationRunResult result) { + results.add(result); + } + + /** + * Gets the number of completed runs. + */ + public int getRunCount() { + return results.size(); + } + + /** + * Generates a comprehensive statistical report. + */ + public String generateReport() { + if (results.isEmpty()) { + return "No simulation results to analyze."; + } + + StringBuilder report = new StringBuilder(); + + // Header + report.append("=".repeat(80)).append("\n"); + report.append("MULTI-RUN STATISTICAL ANALYSIS\n"); + report.append("=".repeat(80)).append("\n"); + report.append("Configuration: ").append(configurationFile).append("\n"); + report.append("Number of Runs: ").append(results.size()).append("\n"); + report.append("Analysis Date: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n"); + report.append("\n"); + + // Global metrics + report.append("-".repeat(80)).append("\n"); + report.append("GLOBAL METRICS\n"); + report.append("-".repeat(80)).append("\n\n"); + + report.append(analyzeMetric("Vehicles Generated", + extractValues(r -> (double) r.getTotalVehiclesGenerated()))); + report.append("\n"); + + report.append(analyzeMetric("Vehicles Completed", + extractValues(r -> (double) r.getTotalVehiclesCompleted()))); + report.append("\n"); + + report.append(analyzeMetric("Completion Rate (%)", + extractValues(r -> r.getTotalVehiclesGenerated() > 0 + ? 100.0 * r.getTotalVehiclesCompleted() / r.getTotalVehiclesGenerated() + : 0.0))); + report.append("\n"); + + report.append(analyzeMetric("Average System Time (seconds)", + extractValues(r -> r.getAverageSystemTime()))); + report.append("\n"); + + report.append(analyzeMetric("Average Waiting Time (seconds)", + extractValues(r -> r.getAverageWaitingTime()))); + report.append("\n"); + + // Per-vehicle-type analysis + report.append("\n"); + report.append("-".repeat(80)).append("\n"); + report.append("PER-VEHICLE-TYPE ANALYSIS\n"); + report.append("-".repeat(80)).append("\n\n"); + + for (VehicleType type : VehicleType.values()) { + report.append("--- ").append(type).append(" ---\n"); + + report.append(analyzeMetric(" Vehicle Count", + extractValues(r -> (double) r.getVehicleCountByType().getOrDefault(type, 0)))); + report.append("\n"); + + report.append(analyzeMetric(" Avg System Time (seconds)", + extractValues(r -> r.getAvgSystemTimeByType().getOrDefault(type, 0.0)))); + report.append("\n"); + + report.append(analyzeMetric(" Avg Waiting Time (seconds)", + extractValues(r -> r.getAvgWaitTimeByType().getOrDefault(type, 0.0)))); + report.append("\n\n"); + } + + // Per-intersection analysis + report.append("-".repeat(80)).append("\n"); + report.append("PER-INTERSECTION ANALYSIS\n"); + report.append("-".repeat(80)).append("\n\n"); + + Set allIntersections = new TreeSet<>(); + for (SimulationRunResult result : results) { + allIntersections.addAll(result.getMaxQueueSizeByIntersection().keySet()); + } + + for (String intersection : allIntersections) { + report.append("--- ").append(intersection).append(" ---\n"); + + report.append(analyzeMetric(" Max Queue Size", + extractValues(r -> (double) r.getMaxQueueSizeByIntersection().getOrDefault(intersection, 0)))); + report.append("\n"); + + report.append(analyzeMetric(" Avg Queue Size", + extractValues(r -> r.getAvgQueueSizeByIntersection().getOrDefault(intersection, 0.0)))); + report.append("\n"); + + report.append(analyzeMetric(" Vehicles Processed", + extractValues(r -> (double) r.getVehiclesProcessedByIntersection().getOrDefault(intersection, 0)))); + report.append("\n\n"); + } + + // Individual run summaries + report.append("-".repeat(80)).append("\n"); + report.append("INDIVIDUAL RUN SUMMARIES\n"); + report.append("-".repeat(80)).append("\n\n"); + + for (SimulationRunResult result : results) { + report.append(result.toString()).append("\n\n"); + } + + report.append("=".repeat(80)).append("\n"); + report.append("END OF REPORT\n"); + report.append("=".repeat(80)).append("\n"); + + return report.toString(); + } + + /** + * Analyzes a single metric and returns formatted statistics. + */ + private String analyzeMetric(String metricName, List values) { + if (values.isEmpty() || values.stream().allMatch(v -> v == 0.0)) { + return metricName + ": No data\n"; + } + + double mean = StatisticalAnalysis.mean(values); + double stdDev = StatisticalAnalysis.standardDeviation(values); + double[] ci = StatisticalAnalysis.confidenceInterval95(values); + double min = StatisticalAnalysis.min(values); + double max = StatisticalAnalysis.max(values); + double median = StatisticalAnalysis.median(values); + + return String.format( + "%s:\n" + + " Mean: %10.2f Std Dev: %10.2f\n" + + " Median: %10.2f 95%% CI: [%.2f, %.2f]\n" + + " Min: %10.2f Max: %10.2f\n", + metricName, mean, stdDev, median, ci[0], ci[1], min, max + ); + } + + /** + * Extracts values using a lambda function. + */ + private List extractValues(java.util.function.Function extractor) { + List values = new ArrayList<>(); + for (SimulationRunResult result : results) { + values.add(extractor.apply(result)); + } + return values; + } + + /** + * Saves the report to a file. + */ + public void saveReport(String filename) throws IOException { + try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) { + writer.print(generateReport()); + } + } + + /** + * Generates a CSV summary for easy import into spreadsheet tools. + */ + public void saveCSVSummary(String filename) throws IOException { + try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) { + // Header + writer.println("Run,VehiclesGenerated,VehiclesCompleted,CompletionRate," + + "AvgSystemTime,AvgWaitingTime,MinSystemTime,MaxSystemTime"); + + // Data rows + for (SimulationRunResult result : results) { + double completionRate = result.getTotalVehiclesGenerated() > 0 + ? 100.0 * result.getTotalVehiclesCompleted() / result.getTotalVehiclesGenerated() + : 0.0; + + writer.printf("%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f\n", + result.getRunNumber(), + result.getTotalVehiclesGenerated(), + result.getTotalVehiclesCompleted(), + completionRate, + result.getAverageSystemTime(), + result.getAverageWaitingTime(), + result.getMinSystemTime(), + result.getMaxSystemTime() + ); + } + } + } +} diff --git a/main/src/main/java/sd/analysis/SimulationBatchRunner.java b/main/src/main/java/sd/analysis/SimulationBatchRunner.java new file mode 100644 index 0000000..7dd9547 --- /dev/null +++ b/main/src/main/java/sd/analysis/SimulationBatchRunner.java @@ -0,0 +1,172 @@ +package sd.analysis; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Orquestra múltiplas execuções de simulação para análise estatística. + * + * Em vez de correr uma única simulação manualmente, esta ferramenta permite + * correr um "lote" + * de N simulações consecutivas. Isto é essencial para recolher dados + * estatisticamente significativos + * (calcular intervalos de confiança, etc.) conforme exigido pelas + * especificações do projeto. + * + * Utilização: + * java sd.analysis.SimulationBatchRunner + * + */ +public class SimulationBatchRunner { + + public static void main(String[] args) { + if (args.length < 3) { + System.err.println("Usage: SimulationBatchRunner "); + System.err.println("Example: SimulationBatchRunner simulation-medium.properties 10 results/medium"); + System.exit(1); + } + + String configFile = args[0]; + int numRuns; + String outputDir = args[2]; + + try { + numRuns = Integer.parseInt(args[1]); + if (numRuns < 1 || numRuns > 100) { + throw new IllegalArgumentException("Number of runs must be between 1 and 100"); + } + } catch (NumberFormatException e) { + System.err.println("Error: Invalid number of runs: " + args[1]); + System.exit(1); + return; + } + + System.out.println("=".repeat(80)); + System.out.println("SIMULATION BATCH RUNNER"); + System.out.println("=".repeat(80)); + System.out.println("Configuration: " + configFile); + System.out.println("Number of Runs: " + numRuns); + System.out.println("Output Directory: " + outputDir); + System.out.println("=".repeat(80)); + System.out.println(); + + // Create output directory + try { + Files.createDirectories(Paths.get(outputDir)); + } catch (IOException e) { + System.err.println("Failed to create output directory: " + e.getMessage()); + System.exit(1); + } + + MultiRunAnalyzer analyzer = new MultiRunAnalyzer(configFile); + + // Execute runs + for (int i = 1; i <= numRuns; i++) { + System.out.println("\n" + "=".repeat(80)); + System.out.println("STARTING RUN " + i + " OF " + numRuns); + System.out.println("=".repeat(80)); + + SimulationRunResult result = executeSimulationRun(i, configFile, outputDir); + + if (result != null) { + analyzer.addResult(result); + System.out.println("\n" + result); + } else { + System.err.println("Run " + i + " failed!"); + } + + // Pause between runs + if (i < numRuns) { + System.out.println("\nWaiting 10 seconds before next run..."); + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + // Generate reports + System.out.println("\n\n" + "=".repeat(80)); + System.out.println("ALL RUNS COMPLETE - GENERATING REPORTS"); + System.out.println("=".repeat(80)); + + try { + String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()); + String reportFile = outputDir + "/analysis-report-" + timestamp + ".txt"; + String csvFile = outputDir + "/summary-" + timestamp + ".csv"; + + analyzer.saveReport(reportFile); + analyzer.saveCSVSummary(csvFile); + + System.out.println("\nReports generated:"); + System.out.println(" - Analysis Report: " + reportFile); + System.out.println(" - CSV Summary: " + csvFile); + System.out.println(); + + // Print report to console + System.out.println(analyzer.generateReport()); + + } catch (IOException e) { + System.err.println("Failed to generate reports: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Executa uma única instância da simulação. + * + * Idealmente, este método iniciaria todos os processos necessários + * (Interseções, Nó de Saída, Coordenador), + * esperaria que terminassem e depois recolheria os resultados. + * + * Atualmente, serve como um espaço reservado estrutural para demonstrar como + * funciona o pipeline de análise. + * Para correr uma simulação real, deve iniciar os componentes manualmente ou + * usar um script shell. + */ + private static SimulationRunResult executeSimulationRun(int runNumber, String configFile, String outputDir) { + SimulationRunResult result = new SimulationRunResult(runNumber, configFile); + + try { + // TODO: Implement actual simulation execution + // This would involve: + // 1. Starting intersection processes + // 2. Starting exit node process + // 3. Starting dashboard process + // 4. Running coordinator + // 5. Collecting results from dashboard/exit node + // 6. Shutting down all processes + + System.out.println("NOTE: Actual simulation execution not yet implemented."); + System.out.println("This batch runner demonstrates the framework structure."); + System.out.println("To run actual simulations, you need to:"); + System.out.println(" 1. Start all intersection processes manually"); + System.out.println(" 2. Start exit node process"); + System.out.println(" 3. Start dashboard process"); + System.out.println(" 4. Run coordinator with the configuration file"); + System.out.println(" 5. Results will be collected automatically"); + + // Placeholder: simulate some results + // In real implementation, these would be collected from the actual simulation + result.setTotalVehiclesGenerated(100); + result.setTotalVehiclesCompleted(85); + result.setAverageSystemTime(120.5); + result.setMinSystemTime(45.2); + result.setMaxSystemTime(250.8); + result.setAverageWaitingTime(45.3); + + return result; + + } catch (Exception e) { + System.err.println("Error executing run " + runNumber + ": " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + +} diff --git a/main/src/main/java/sd/analysis/SimulationRunResult.java b/main/src/main/java/sd/analysis/SimulationRunResult.java new file mode 100644 index 0000000..4d8a608 --- /dev/null +++ b/main/src/main/java/sd/analysis/SimulationRunResult.java @@ -0,0 +1,143 @@ +package sd.analysis; + +import java.util.HashMap; +import java.util.Map; + +import sd.model.VehicleType; + +/** + * Stores the results of a single simulation run. + * Contains all key metrics for post-simulation analysis. + */ +public class SimulationRunResult { + + private final int runNumber; + private final String configurationFile; + private final long startTimeMillis; + private final long endTimeMillis; + + // Global metrics + private int totalVehiclesGenerated; + private int totalVehiclesCompleted; + private double averageSystemTime; // seconds + private double minSystemTime; // seconds + private double maxSystemTime; // seconds + private double averageWaitingTime; // seconds + + // Per-type metrics + private final Map vehicleCountByType; + private final Map avgSystemTimeByType; + private final Map avgWaitTimeByType; + + // Per-intersection metrics + private final Map maxQueueSizeByIntersection; + private final Map avgQueueSizeByIntersection; + private final Map vehiclesProcessedByIntersection; + + public SimulationRunResult(int runNumber, String configurationFile) { + this.runNumber = runNumber; + this.configurationFile = configurationFile; + this.startTimeMillis = System.currentTimeMillis(); + this.endTimeMillis = 0; + + this.vehicleCountByType = new HashMap<>(); + this.avgSystemTimeByType = new HashMap<>(); + this.avgWaitTimeByType = new HashMap<>(); + this.maxQueueSizeByIntersection = new HashMap<>(); + this.avgQueueSizeByIntersection = new HashMap<>(); + this.vehiclesProcessedByIntersection = new HashMap<>(); + } + + public void markCompleted() { + // This will be called when the run finishes + } + + // Getters + public int getRunNumber() { return runNumber; } + public String getConfigurationFile() { return configurationFile; } + public long getStartTimeMillis() { return startTimeMillis; } + public long getEndTimeMillis() { return endTimeMillis; } + public long getDurationMillis() { return endTimeMillis - startTimeMillis; } + + public int getTotalVehiclesGenerated() { return totalVehiclesGenerated; } + public int getTotalVehiclesCompleted() { return totalVehiclesCompleted; } + public double getAverageSystemTime() { return averageSystemTime; } + public double getMinSystemTime() { return minSystemTime; } + public double getMaxSystemTime() { return maxSystemTime; } + public double getAverageWaitingTime() { return averageWaitingTime; } + + public Map getVehicleCountByType() { + return new HashMap<>(vehicleCountByType); + } + public Map getAvgSystemTimeByType() { + return new HashMap<>(avgSystemTimeByType); + } + public Map getAvgWaitTimeByType() { + return new HashMap<>(avgWaitTimeByType); + } + public Map getMaxQueueSizeByIntersection() { + return new HashMap<>(maxQueueSizeByIntersection); + } + public Map getAvgQueueSizeByIntersection() { + return new HashMap<>(avgQueueSizeByIntersection); + } + public Map getVehiclesProcessedByIntersection() { + return new HashMap<>(vehiclesProcessedByIntersection); + } + + // Setters + public void setTotalVehiclesGenerated(int count) { + this.totalVehiclesGenerated = count; + } + public void setTotalVehiclesCompleted(int count) { + this.totalVehiclesCompleted = count; + } + public void setAverageSystemTime(double time) { + this.averageSystemTime = time; + } + public void setMinSystemTime(double time) { + this.minSystemTime = time; + } + public void setMaxSystemTime(double time) { + this.maxSystemTime = time; + } + public void setAverageWaitingTime(double time) { + this.averageWaitingTime = time; + } + + public void setVehicleCountByType(VehicleType type, int count) { + vehicleCountByType.put(type, count); + } + public void setAvgSystemTimeByType(VehicleType type, double time) { + avgSystemTimeByType.put(type, time); + } + public void setAvgWaitTimeByType(VehicleType type, double time) { + avgWaitTimeByType.put(type, time); + } + public void setMaxQueueSize(String intersection, int size) { + maxQueueSizeByIntersection.put(intersection, size); + } + public void setAvgQueueSize(String intersection, double size) { + avgQueueSizeByIntersection.put(intersection, size); + } + public void setVehiclesProcessed(String intersection, int count) { + vehiclesProcessedByIntersection.put(intersection, count); + } + + @Override + public String toString() { + return String.format( + "Run #%d [%s]:\n" + + " Generated: %d, Completed: %d (%.1f%%)\n" + + " Avg System Time: %.2fs\n" + + " Avg Waiting Time: %.2fs", + runNumber, + configurationFile, + totalVehiclesGenerated, + totalVehiclesCompleted, + totalVehiclesGenerated > 0 ? 100.0 * totalVehiclesCompleted / totalVehiclesGenerated : 0.0, + averageSystemTime, + averageWaitingTime + ); + } +} diff --git a/main/src/main/java/sd/analysis/StatisticalAnalysis.java b/main/src/main/java/sd/analysis/StatisticalAnalysis.java new file mode 100644 index 0000000..c62a2f5 --- /dev/null +++ b/main/src/main/java/sd/analysis/StatisticalAnalysis.java @@ -0,0 +1,160 @@ +package sd.analysis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Statistical analysis utilities for simulation results. + * Calculates mean, standard deviation, and confidence intervals. + */ +public class StatisticalAnalysis { + + /** + * Calculates the mean (average) of a list of values. + */ + public static double mean(List values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + double sum = 0.0; + for (double value : values) { + sum += value; + } + return sum / values.size(); + } + + /** + * Calculates the sample standard deviation. + */ + public static double standardDeviation(List values) { + if (values == null || values.size() < 2) { + return 0.0; + } + + double mean = mean(values); + double sumSquaredDiff = 0.0; + + for (double value : values) { + double diff = value - mean; + sumSquaredDiff += diff * diff; + } + + // Sample standard deviation (n-1 denominator) + return Math.sqrt(sumSquaredDiff / (values.size() - 1)); + } + + /** + * Calculates the 95% confidence interval for the mean. + * Uses t-distribution for small samples (n < 30). + * + * @return Array of [lowerBound, upperBound] + */ + public static double[] confidenceInterval95(List values) { + if (values == null || values.size() < 2) { + double m = mean(values); + return new double[]{m, m}; + } + + double mean = mean(values); + double stdDev = standardDeviation(values); + int n = values.size(); + + // Critical value from t-distribution (approximation for common sample sizes) + double tCritical = getTCriticalValue(n); + + // Standard error of the mean + double standardError = stdDev / Math.sqrt(n); + + // Margin of error + double marginOfError = tCritical * standardError; + + return new double[]{ + mean - marginOfError, // Lower bound + mean + marginOfError // Upper bound + }; + } + + /** + * Returns the t-critical value for 95% confidence interval. + * Approximations for common degrees of freedom (n-1). + */ + private static double getTCriticalValue(int sampleSize) { + int df = sampleSize - 1; // degrees of freedom + + // t-critical values for 95% confidence (two-tailed) + if (df >= 30) return 1.96; // z-score for large samples + if (df >= 20) return 2.086; + if (df >= 15) return 2.131; + if (df >= 10) return 2.228; + if (df >= 5) return 2.571; + if (df >= 3) return 3.182; + if (df >= 2) return 4.303; + return 12.706; // df = 1 + } + + /** + * Calculates the minimum value. + */ + public static double min(List values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + return Collections.min(values); + } + + /** + * Calculates the maximum value. + */ + public static double max(List values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + return Collections.max(values); + } + + /** + * Calculates the median value. + */ + public static double median(List values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + + List sorted = new ArrayList<>(values); + Collections.sort(sorted); + + int size = sorted.size(); + if (size % 2 == 0) { + return (sorted.get(size / 2 - 1) + sorted.get(size / 2)) / 2.0; + } else { + return sorted.get(size / 2); + } + } + + /** + * Formats a statistical summary as a string. + */ + public static String formatSummary(String metricName, List values) { + if (values == null || values.isEmpty()) { + return metricName + ": No data"; + } + + double mean = mean(values); + double stdDev = standardDeviation(values); + double[] ci = confidenceInterval95(values); + double min = min(values); + double max = max(values); + + return String.format( + "%s:\n" + + " Mean: %.2f\n" + + " Std Dev: %.2f\n" + + " 95%% CI: [%.2f, %.2f]\n" + + " Min: %.2f\n" + + " Max: %.2f\n" + + " Samples: %d", + metricName, mean, stdDev, ci[0], ci[1], min, max, values.size() + ); + } +} diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java index 6c11b66..75306e3 100644 --- a/main/src/main/java/sd/config/SimulationConfig.java +++ b/main/src/main/java/sd/config/SimulationConfig.java @@ -14,16 +14,14 @@ 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 - * type-safe getter methods for all expected configuration parameters, - * with default values to ensure robustness. + * Carrega e gere configurações da simulação. + * + *

Lê propriedades de um ficheiro .properties e fornece getters + * type-safe com valores padrão para robustez. */ public class SimulationConfig { - /** - * Holds all properties loaded from the file. - */ + /** Propriedades carregadas do ficheiro */ private final Properties properties; private NetworkConfig networkConfig; @@ -54,18 +52,17 @@ public class SimulationConfig { } /** - * Constructs a new SimulationConfig object by loading properties - * from the specified file path. + * Carrega propriedades do ficheiro especificado. * - * 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 + *

Tenta múltiplas estratégias: + *

    + *
  1. Caminho direto no sistema de ficheiros + *
  2. Recurso no classpath (com normalização automática) + *
  3. Recurso no classpath com barra inicial + *
* - * @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. + * @param filePath caminho do ficheiro .properties + * @throws IOException se o ficheiro não for encontrado */ public SimulationConfig(String filePath) throws IOException { properties = new Properties(); @@ -224,7 +221,15 @@ public class SimulationConfig { * @return The simulation duration. */ public double getSimulationDuration() { - return Double.parseDouble(properties.getProperty("simulation.duration", "3600.0")); + return Double.parseDouble(properties.getProperty("simulation.duration", "3600")); + } + + /** + * Get time scaling factor for visualization. + * 0 = instant (pure DES), 0.01 = 100x speed, 0.1 = 10x speed, 1.0 = real-time + */ + public double getTimeScale() { + return Double.parseDouble(properties.getProperty("simulation.time.scale", "0")); } /** diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java index 2fb1423..54e4387 100644 --- a/main/src/main/java/sd/coordinator/CoordinatorProcess.java +++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java @@ -6,6 +6,11 @@ import java.util.Map; import sd.config.SimulationConfig; import sd.dashboard.StatsUpdatePayload; +import sd.des.DESEventType; +import sd.des.EventQueue; +import sd.des.SimulationClock; +import sd.des.SimulationEvent; +import sd.logging.EventLogger; import sd.model.Message; import sd.model.MessageType; import sd.model.Vehicle; @@ -13,12 +18,17 @@ import sd.serialization.SerializationException; import sd.util.VehicleGenerator; /** - * Coordinator process responsible for: - * 1. Vehicle generation (using VehicleGenerator) - * 2. Distributing vehicles to intersection processes via sockets - * 3. Managing simulation timing and shutdown + * Coordenador central da simulação distribuída. * - * This is the main entry point for the distributed simulation architecture. + *

Responsabilidades: + *

    + *
  1. Gerar veículos segundo modelo configurado (Poisson/Fixed) + *
  2. Injetar veículos nas interseções de entrada + *
  3. Gerir relógio global e sincronizar componentes + *
+ * + *

Usa motor DES para agendar eventos de geração com precisão. + * Mantém fila de prioridade e processa eventos em ordem cronológica. */ public class CoordinatorProcess { @@ -26,10 +36,14 @@ public class CoordinatorProcess { private final VehicleGenerator vehicleGenerator; private final Map intersectionClients; private SocketClient dashboardClient; - private double currentTime; + + private final SimulationClock clock; + private final EventQueue eventQueue; + private final EventLogger eventLogger; + private int vehicleCounter; private boolean running; - private double nextGenerationTime; + private double timeScale; public static void main(String[] args) { System.out.println("=".repeat(60)); @@ -65,15 +79,22 @@ public class CoordinatorProcess { this.config = config; this.vehicleGenerator = new VehicleGenerator(config); this.intersectionClients = new HashMap<>(); - this.currentTime = 0.0; this.vehicleCounter = 0; this.running = false; - this.nextGenerationTime = 0.0; + this.timeScale = config.getTimeScale(); + + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); + + this.eventLogger = EventLogger.getInstance(); + eventLogger.log(sd.logging.EventType.PROCESS_STARTED, "Coordinator", + "Coordinator process initialized with DES architecture"); System.out.println("Coordinator initialized with configuration:"); System.out.println(" - Simulation duration: " + config.getSimulationDuration() + "s"); System.out.println(" - Arrival model: " + config.getArrivalModel()); System.out.println(" - Arrival rate: " + config.getArrivalRate() + " vehicles/s"); + System.out.println(" - DES Mode: ENABLED (Event-driven, no time-stepping)"); } public void initialize() { @@ -107,58 +128,151 @@ public class CoordinatorProcess { public void run() { double duration = config.getSimulationDuration(); + double drainTime = config.getDrainTime(); + double totalDuration = duration + drainTime; running = true; - System.out.println("Starting vehicle generation simulation..."); - System.out.println("Duration: " + duration + " seconds"); + System.out.println("Starting DES-based vehicle generation simulation..."); + System.out.println("Duration: " + duration + "s (+ " + drainTime + "s drain)"); System.out.println(); + // Log simulation start + eventLogger.log(sd.logging.EventType.SIMULATION_STARTED, "Coordinator", + String.format("Starting simulation - Duration: %.1fs", duration)); + // Send simulation start time to all processes for synchronization sendSimulationStartTime(); - nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime); - final double TIME_STEP = 0.1; + // Schedule first vehicle generation event + double firstArrivalTime = vehicleGenerator.getNextArrivalTime(clock.getCurrentTime()); + eventQueue.schedule(new SimulationEvent( + firstArrivalTime, + DESEventType.VEHICLE_GENERATION, + null, + "Coordinator")); - double drainTime = config.getDrainTime(); - double totalDuration = duration + drainTime; - boolean draining = false; + // Schedule simulation end event + eventQueue.schedule(new SimulationEvent( + totalDuration, + DESEventType.SIMULATION_END, + null, + "Coordinator")); - while (running && currentTime < totalDuration) { - // Only generate vehicles during the main duration - if (currentTime < duration) { - if (currentTime >= nextGenerationTime) { - generateAndSendVehicle(); - nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime); + System.out.printf("Initial event scheduled at t=%.3fs\n", firstArrivalTime); + System.out.println("Entering DES event loop...\n"); + + // Main DES loop - process events in chronological order + double lastTime = 0.0; + while (running && !eventQueue.isEmpty()) { + SimulationEvent event = eventQueue.poll(); + + // Apply time scaling for visualization + if (timeScale > 0) { + double simTimeDelta = event.getTimestamp() - lastTime; + long realDelayMs = (long) (simTimeDelta * timeScale * 1000); + if (realDelayMs > 0) { + try { + Thread.sleep(realDelayMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } } - } else if (!draining) { - draining = true; - System.out.println("\n[t=" + String.format("%.2f", currentTime) - + "] Generation complete. Entering DRAIN MODE for " + drainTime + "s..."); + lastTime = event.getTimestamp(); } - try { - Thread.sleep((long) (TIME_STEP * 1000)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } + // Advance simulation time to event time + clock.advanceTo(event.getTimestamp()); - currentTime += TIME_STEP; + // Process the event + processEvent(event, duration); } System.out.println(); - System.out.println("Simulation complete at t=" + String.format("%.2f", currentTime) + "s"); + System.out.printf("Simulation complete at t=%.2fs\n", clock.getCurrentTime()); System.out.println("Total vehicles generated: " + vehicleCounter); + System.out.println("Total events processed: " + eventQueue.getProcessedCount()); + + // Log simulation end + eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, "Coordinator", + String.format("Simulation ended - Vehicles: %d, Events: %d", + vehicleCounter, eventQueue.getProcessedCount())); + + // Export event history (spec requirement: view complete event list) + exportEventHistory(); shutdown(); } + /** + * Trata um único evento de simulação. + * + * É aqui que a magia acontece. Dependendo do tipo de evento (como + * VEHICLE_GENERATION), + * atualizamos o estado do mundo. Para a geração de veículos, criamos um novo + * veículo, + * enviamo-lo para uma interseção e depois agendamos o *próximo* evento de + * geração. + */ + private void processEvent(SimulationEvent event, double generationDuration) { + double currentTime = clock.getCurrentTime(); + + switch (event.getType()) { + case VEHICLE_GENERATION: + // Only generate if we're still in the generation phase + if (currentTime < generationDuration) { + generateAndSendVehicle(); + + // Schedule next vehicle generation + double nextArrivalTime = vehicleGenerator.getNextArrivalTime(currentTime); + eventQueue.schedule(new SimulationEvent( + nextArrivalTime, + DESEventType.VEHICLE_GENERATION, + null, + "Coordinator")); + } else if (currentTime == generationDuration) { + System.out.printf("\n[t=%.2f] Generation phase complete. Entering DRAIN MODE...\n", + currentTime); + } + break; + + case SIMULATION_END: + System.out.printf("[t=%.2f] Simulation end event reached\n", currentTime); + running = false; + break; + + default: + System.err.println("WARNING: Unknown event type: " + event.getType()); + } + } + + /** + * Guarda o histórico completo de eventos de simulação num ficheiro de texto. + * Isto permite-nos auditar exatamente o que aconteceu e quando, o que é crucial + * para depuração e verificação. + */ + private void exportEventHistory() { + try (java.io.PrintWriter writer = new java.io.PrintWriter( + new java.io.FileWriter("logs/coordinator-event-history.txt"))) { + String history = eventQueue.exportEventHistory(); + writer.println(history); + System.out.println("\nEvent history exported to: logs/coordinator-event-history.txt"); + } catch (IOException e) { + System.err.println("Failed to export event history: " + e.getMessage()); + } + } + private void generateAndSendVehicle() { + double currentTime = clock.getCurrentTime(); 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()); + // Log to event logger + eventLogger.log(sd.logging.EventType.VEHICLE_GENERATED, "Coordinator", + String.format("[%s] Type: %s, Route: %s", vehicle.getId(), vehicle.getType(), vehicle.getRoute())); + // Send generation count to dashboard sendGenerationStatsToDashboard(); diff --git a/main/src/main/java/sd/coordinator/SocketClient.java b/main/src/main/java/sd/coordinator/SocketClient.java index 88d75b2..fcb59ec 100644 --- a/main/src/main/java/sd/coordinator/SocketClient.java +++ b/main/src/main/java/sd/coordinator/SocketClient.java @@ -10,10 +10,10 @@ import sd.serialization.SerializationException; import sd.serialization.SerializerFactory; /** - * Socket client for communication with a single intersection process. + * Cliente socket para comunicação com um processo de interseção. * - * Handles a persistent TCP connection to one intersection, - * providing a simple way to send serialized messages. + *

Gere uma ligação TCP persistente para uma interseção, + * fornecendo uma forma simples de enviar mensagens serializadas.

*/ public class SocketClient { @@ -25,11 +25,11 @@ public class SocketClient { private MessageSerializer serializer; /** - * Creates a new SocketClient for a given intersection. + * Cria um novo cliente socket para uma interseção. * - * @param intersectionId Intersection ID (ex. "Cr1") - * @param host Host address (ex. "localhost") - * @param port Port number + * @param intersectionId ID da interseção (ex: "Cr1") + * @param host endereço do host (ex: "localhost") + * @param port número da porta */ public SocketClient(String intersectionId, String host, int port) { this.intersectionId = intersectionId; @@ -39,11 +39,10 @@ public class SocketClient { } /** - * Connects to the intersection process via TCP. + * Liga-se ao processo da interseção via TCP. * - * @throws IOException if the connection cannot be established + * @throws IOException se a ligação não puder ser estabelecida */ - public void connect() throws IOException { try { socket = new Socket(host, port); @@ -56,12 +55,12 @@ public class SocketClient { } /** - * Sends a message to the connected intersection. - * The message is serialized and written over the socket. + * Envia uma mensagem para a interseção ligada. + * A mensagem é serializada e enviada pelo socket. * - * @param message The message to send - * @throws SerializationException if serialization fails - * @throws IOException if the socket write fails + * @param message mensagem a enviar + * @throws SerializationException se a serialização falhar + * @throws IOException se a escrita no socket falhar */ public void send(Message message) throws SerializationException, IOException { if (socket == null || socket.isClosed()) { @@ -71,7 +70,6 @@ public class SocketClient { try { byte[] data = serializer.serialize(message); - // Prefix with message length (so receiver knows how much to read) int length = data.length; outputStream.write((length >> 24) & 0xFF); outputStream.write((length >> 16) & 0xFF); diff --git a/main/src/main/java/sd/dashboard/ConfigurationDialog.java b/main/src/main/java/sd/dashboard/ConfigurationDialog.java new file mode 100644 index 0000000..159ee35 --- /dev/null +++ b/main/src/main/java/sd/dashboard/ConfigurationDialog.java @@ -0,0 +1,167 @@ +package sd.dashboard; + +import javafx.geometry.Insets; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.Separator; +import javafx.scene.control.Spinner; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; + +/** + * Diálogo para configuração avançada de parâmetros da simulação. + * Permite ajustar parâmetros em runtime antes de iniciar a simulação. + */ +public class ConfigurationDialog { + + /** + * Mostra um diálogo com opções avançadas de configuração. + * + * @param owner janela pai + * @return true se o utilizador confirmar, false se cancelar + */ + public static boolean showAdvancedConfig(Stage owner) { + Dialog dialog = new Dialog<>(); + dialog.initOwner(owner); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.setTitle("Configuração Avançada da Simulação"); + dialog.setHeaderText("Ajustar parâmetros da simulação"); + + // Criar painel de configuração + VBox content = new VBox(15); + content.setPadding(new Insets(20)); + + // Seção 1: Parâmetros de Chegada + Label arrivalHeader = new Label("Parâmetros de Chegada de Veículos"); + arrivalHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane arrivalGrid = new GridPane(); + arrivalGrid.setHgap(10); + arrivalGrid.setVgap(10); + arrivalGrid.setPadding(new Insets(10)); + + // Modelo de chegada + Label modelLabel = new Label("Modelo de chegada:"); + ComboBox modelCombo = new ComboBox<>(); + modelCombo.getItems().addAll("POISSON", "FIXED"); + modelCombo.setValue("POISSON"); + arrivalGrid.add(modelLabel, 0, 0); + arrivalGrid.add(modelCombo, 1, 0); + + // Taxa de chegada (λ) + Label rateLabel = new Label("Taxa de chegada (λ) [veículos/s]:"); + Spinner rateSpinner = new Spinner<>(0.1, 2.0, 0.5, 0.1); + rateSpinner.setEditable(true); + rateSpinner.setPrefWidth(100); + arrivalGrid.add(rateLabel, 0, 1); + arrivalGrid.add(rateSpinner, 1, 1); + + // Intervalo fixo (se aplicável) + Label intervalLabel = new Label("Intervalo fixo [s]:"); + Spinner intervalSpinner = new Spinner<>(0.5, 10.0, 2.0, 0.5); + intervalSpinner.setEditable(true); + intervalSpinner.setPrefWidth(100); + intervalSpinner.setDisable(true); + arrivalGrid.add(intervalLabel, 0, 2); + arrivalGrid.add(intervalSpinner, 1, 2); + + // Habilitar/desabilitar intervalo baseado no modelo + modelCombo.setOnAction(e -> { + boolean isFixed = "FIXED".equals(modelCombo.getValue()); + intervalSpinner.setDisable(!isFixed); + rateSpinner.setDisable(isFixed); + }); + + // Seção 2: Parâmetros de Tempo + Label timeHeader = new Label("Parâmetros de Tempo"); + timeHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane timeGrid = new GridPane(); + timeGrid.setHgap(10); + timeGrid.setVgap(10); + timeGrid.setPadding(new Insets(10)); + + // Duração da simulação + Label durationLabel = new Label("Duração da simulação [s]:"); + Spinner durationSpinner = new Spinner<>(60, 7200, 300, 60); + durationSpinner.setEditable(true); + durationSpinner.setPrefWidth(100); + timeGrid.add(durationLabel, 0, 0); + timeGrid.add(durationSpinner, 1, 0); + + // Escala temporal (para visualização) + Label scaleLabel = new Label("Escala temporal (0=instantâneo, 1=tempo real):"); + Spinner scaleSpinner = new Spinner<>(0.0, 1.0, 0.01, 0.01); + scaleSpinner.setEditable(true); + scaleSpinner.setPrefWidth(100); + timeGrid.add(scaleLabel, 0, 1); + timeGrid.add(scaleSpinner, 1, 1); + + // Tempo de drenagem + Label drainLabel = new Label("Tempo de drenagem [s]:"); + Spinner drainSpinner = new Spinner<>(0, 300, 60, 10); + drainSpinner.setEditable(true); + drainSpinner.setPrefWidth(100); + timeGrid.add(drainLabel, 0, 2); + timeGrid.add(drainSpinner, 1, 2); + + // Seção 3: Distribuição de Tipos de Veículos + Label vehicleHeader = new Label("Distribuição de Tipos de Veículos"); + vehicleHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane vehicleGrid = new GridPane(); + vehicleGrid.setHgap(10); + vehicleGrid.setVgap(10); + vehicleGrid.setPadding(new Insets(10)); + + Label bikeLabel = new Label("Bicicletas/Motos [%]:"); + Spinner bikeSpinner = new Spinner<>(0, 100, 10, 5); + bikeSpinner.setEditable(true); + bikeSpinner.setPrefWidth(100); + vehicleGrid.add(bikeLabel, 0, 0); + vehicleGrid.add(bikeSpinner, 1, 0); + + Label lightLabel = new Label("Veículos Ligeiros [%]:"); + Spinner lightSpinner = new Spinner<>(0, 100, 70, 5); + lightSpinner.setEditable(true); + lightSpinner.setPrefWidth(100); + vehicleGrid.add(lightLabel, 0, 1); + vehicleGrid.add(lightSpinner, 1, 1); + + Label heavyLabel = new Label("Veículos Pesados [%]:"); + Spinner heavySpinner = new Spinner<>(0, 100, 20, 5); + heavySpinner.setEditable(true); + heavySpinner.setPrefWidth(100); + vehicleGrid.add(heavyLabel, 0, 2); + vehicleGrid.add(heavySpinner, 1, 2); + + // Nota informativa + Label noteLabel = new Label("Nota: Estes parâmetros sobrepõem os valores do ficheiro .properties selecionado.\n" + + "Para usar os valores padrão do ficheiro, deixe em branco ou cancele."); + noteLabel.setWrapText(true); + noteLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #666666;"); + + // Adicionar tudo ao conteúdo + content.getChildren().addAll( + arrivalHeader, arrivalGrid, + new Separator(), + timeHeader, timeGrid, + new Separator(), + vehicleHeader, vehicleGrid, + new Separator(), + noteLabel + ); + + dialog.getDialogPane().setContent(content); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + // Mostrar diálogo e processar resultado + return dialog.showAndWait() + .map(buttonType -> buttonType == ButtonType.OK) + .orElse(false); + } +} diff --git a/main/src/main/java/sd/dashboard/DashboardServer.java b/main/src/main/java/sd/dashboard/DashboardServer.java index cca71b0..8807478 100644 --- a/main/src/main/java/sd/dashboard/DashboardServer.java +++ b/main/src/main/java/sd/dashboard/DashboardServer.java @@ -10,8 +10,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import sd.config.SimulationConfig; /** - * Aggregates and displays real-time statistics from all simulation processes. - * Uses a thread pool to handle concurrent client connections. + * Agrega e apresenta estatísticas em tempo real de todos os processos da simulação. + * Usa um thread pool para gerir ligações concorrentes de clientes. */ public class DashboardServer { diff --git a/main/src/main/java/sd/dashboard/DashboardStatistics.java b/main/src/main/java/sd/dashboard/DashboardStatistics.java index da6f097..4616d60 100644 --- a/main/src/main/java/sd/dashboard/DashboardStatistics.java +++ b/main/src/main/java/sd/dashboard/DashboardStatistics.java @@ -9,8 +9,8 @@ import java.util.concurrent.atomic.AtomicLong; import sd.model.VehicleType; /** - * Thread-safe storage for aggregated simulation statistics. - * Uses atomic types and concurrent collections for lock-free updates. + * Armazenamento thread-safe de estatísticas agregadas da simulação. + * Usa tipos atómicos e coleções concorrentes para atualizações sem locks. */ public class DashboardStatistics { diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java index 3ddd536..4a705d1 100644 --- a/main/src/main/java/sd/dashboard/DashboardUI.java +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -13,6 +13,7 @@ import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -54,6 +55,11 @@ public class DashboardUI extends Application { // Update scheduler private ScheduledExecutorService updateScheduler; + // Configuration controls + private ComboBox configFileSelector; + private String selectedConfigFile = "simulation.properties"; + private Label configInfoLabel; + @Override public void start(Stage primaryStage) { try { @@ -122,6 +128,9 @@ public class DashboardUI extends Application { Label subtitle = new Label("Real-time Statistics and Monitoring"); subtitle.getStyleClass().add("header-subtitle"); + // Configuration Panel + VBox configPanel = createConfigurationPanel(); + // Control Buttons HBox controls = new HBox(15); controls.setAlignment(Pos.CENTER); @@ -137,9 +146,12 @@ public class DashboardUI extends Application { btnStart.setOnAction(e -> { try { + // Passar o ficheiro de configuração selecionado + processManager.setConfigFile(selectedConfigFile); processManager.startSimulation(); btnStart.setDisable(true); btnStop.setDisable(false); + configFileSelector.setDisable(true); // Bloquear mudanças durante simulação } catch (IOException ex) { showErrorAlert("Start Failed", "Could not start simulation processes: " + ex.getMessage()); } @@ -149,15 +161,74 @@ public class DashboardUI extends Application { processManager.stopSimulation(); btnStart.setDisable(false); btnStop.setDisable(true); + configFileSelector.setDisable(false); // Desbloquear para nova simulação }); controls.getChildren().addAll(btnStart, btnStop); - header.getChildren().addAll(title, subtitle, controls); + header.getChildren().addAll(title, subtitle, configPanel, controls); return header; } + /** + * Cria o painel de configuração com seleção de cenário e parâmetros. + */ + private VBox createConfigurationPanel() { + VBox configBox = new VBox(10); + configBox.setAlignment(Pos.CENTER); + configBox.setPadding(new Insets(10)); + configBox.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;"); + + Label configLabel = new Label("Configuração da Simulação"); + configLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); + + HBox configControls = new HBox(20); + configControls.setAlignment(Pos.CENTER); + + // Scenario selector + VBox scenarioBox = new VBox(5); + scenarioBox.setAlignment(Pos.CENTER); + Label scenarioLabel = new Label("Cenário:"); + scenarioLabel.setStyle("-fx-font-size: 12px;"); + + configFileSelector = new ComboBox<>(); + configFileSelector.getItems().addAll( + "simulation.properties", + "simulation-low.properties", + "simulation-medium.properties", + "simulation-high.properties" + ); + configFileSelector.setValue("simulation.properties"); + configFileSelector.setOnAction(e -> { + selectedConfigFile = configFileSelector.getValue(); + updateConfigInfo(); + System.out.println("Configuração selecionada: " + selectedConfigFile); + }); + + scenarioBox.getChildren().addAll(scenarioLabel, configFileSelector); + configControls.getChildren().add(scenarioBox); + + // Advanced configuration button + Button btnAdvancedConfig = new Button("Configuração Avançada..."); + btnAdvancedConfig.setStyle("-fx-font-size: 11px;"); + btnAdvancedConfig.setOnAction(e -> { + ConfigurationDialog.showAdvancedConfig((Stage) configBox.getScene().getWindow()); + }); + configControls.getChildren().add(btnAdvancedConfig); + + // Configuration info display + configInfoLabel = new Label(); + configInfoLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #aaaaaa;"); + configInfoLabel.setWrapText(true); + configInfoLabel.setMaxWidth(800); + configInfoLabel.setAlignment(Pos.CENTER); + updateConfigInfo(); + + configBox.getChildren().addAll(configLabel, configControls, configInfoLabel); + return configBox; + } + private VBox createMainContent() { VBox mainContent = new VBox(20); mainContent.setPadding(new Insets(20)); @@ -367,6 +438,28 @@ public class DashboardUI extends Application { } } + /** + * Atualiza a informação exibida sobre a configuração selecionada. + */ + private void updateConfigInfo() { + String info = ""; + switch (selectedConfigFile) { + case "simulation-low.properties": + info = "🟢 CARGA BAIXA: 0.2 veículos/s (~720/hora) | Sem congestionamento esperado"; + break; + case "simulation-medium.properties": + info = "🟡 CARGA MÉDIA: 0.5 veículos/s (~1800/hora) | Algum congestionamento esperado"; + break; + case "simulation-high.properties": + info = "🔴 CARGA ALTA: 1.0 veículo/s (~3600/hora) | Congestionamento significativo esperado"; + break; + default: + info = "⚙️ CONFIGURAÇÃO PADRÃO: Verificar ficheiro para parâmetros"; + break; + } + configInfoLabel.setText(info); + } + private void shutdown() { System.out.println("Shutting down Dashboard UI..."); diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java index 0658b2b..e219ef5 100644 --- a/main/src/main/java/sd/dashboard/SimulationProcessManager.java +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -6,25 +6,36 @@ import java.util.ArrayList; import java.util.List; /** - * Manages the lifecycle of simulation processes (Intersections, Exit Node, + * Gere o ciclo de vida dos processos de simulação (Intersections, Exit Node, * Coordinator). - * Allows starting and stopping the distributed simulation from within the Java - * application. + * Permite iniciar e parar a simulação distribuída dentro da aplicação Java. */ public class SimulationProcessManager { private final List runningProcesses; private final String classpath; + private String configFile; public SimulationProcessManager() { this.runningProcesses = new ArrayList<>(); this.classpath = System.getProperty("java.class.path"); + this.configFile = "src/main/resources/simulation.properties"; } /** - * Starts the full simulation: 5 Intersections, 1 Exit Node, and 1 Coordinator. + * Define o ficheiro de configuração a usar. * - * @throws IOException If a process fails to start. + * @param configFile nome do ficheiro (ex: "simulation-low.properties") + */ + public void setConfigFile(String configFile) { + this.configFile = "src/main/resources/" + configFile; + System.out.println("Configuration file set to: " + this.configFile); + } + + /** + * Inicia a simulação completa: 5 Intersections, 1 Exit Node, e 1 Coordinator. + * + * @throws IOException se um processo falhar ao iniciar */ public void startSimulation() throws IOException { if (!runningProcesses.isEmpty()) { @@ -83,16 +94,16 @@ public class SimulationProcessManager { } /** - * Helper to start a single Java process. + * Helper para iniciar um único processo Java. */ private void startProcess(String className, String arg) throws IOException { String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; ProcessBuilder builder; if (arg != null) { - builder = new ProcessBuilder(javaBin, "-cp", classpath, className, arg); + builder = new ProcessBuilder(javaBin, "-cp", classpath, className, arg, configFile); } else { - builder = new ProcessBuilder(javaBin, "-cp", classpath, className); + builder = new ProcessBuilder(javaBin, "-cp", classpath, className, configFile); } // get the OS temp folder diff --git a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java index a84760b..e62e866 100644 --- a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java +++ b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java @@ -7,8 +7,8 @@ import java.util.Map; import sd.model.VehicleType; /** - * Data transfer object for statistics updates to the dashboard. - * Use -1 for fields not being updated in this message. + * DTO para atualizações de estatísticas ao dashboard. + * Campos com valor -1 não são atualizados nesta mensagem. */ public class StatsUpdatePayload implements Serializable { diff --git a/main/src/main/java/sd/des/DESEventType.java b/main/src/main/java/sd/des/DESEventType.java new file mode 100644 index 0000000..789d2e9 --- /dev/null +++ b/main/src/main/java/sd/des/DESEventType.java @@ -0,0 +1,39 @@ +package sd.des; + +/** + * Tipos de eventos discretos da simulação. + * + *

Representa os eventos DES que avançam o estado da simulação, + * não categorias de logging (EventType está noutro package). + */ +public enum DESEventType { + /** Gerar novo veículo num ponto de entrada */ + VEHICLE_GENERATION, + + /** Veículo chega a uma interseção */ + VEHICLE_ARRIVAL, + + /** Veículo começa a atravessar o semáforo */ + VEHICLE_CROSSING_START, + + /** Veículo termina a travessia */ + VEHICLE_CROSSING_END, + + /** Veículo parte para o próximo destino */ + VEHICLE_DEPARTURE, + + /** Veículo sai do sistema no nó de saída */ + VEHICLE_EXIT, + + /** Semáforo muda de estado (VERMELHO para VERDE ou vice-versa) */ + TRAFFIC_LIGHT_CHANGE, + + /** Processar veículos que esperam num semáforo recém-verde */ + PROCESS_GREEN_LIGHT, + + /** Atualização periódica de estatísticas */ + STATISTICS_UPDATE, + + /** Terminação da simulação */ + SIMULATION_END +} diff --git a/main/src/main/java/sd/des/EventQueue.java b/main/src/main/java/sd/des/EventQueue.java new file mode 100644 index 0000000..c61fc8d --- /dev/null +++ b/main/src/main/java/sd/des/EventQueue.java @@ -0,0 +1,137 @@ +package sd.des; + +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; + +/** + * Gere a Lista de Eventos Futuros (FEL) para Simulação de Eventos Discretos. + * + *

A FEL é uma fila de prioridade que mantém todos os eventos futuros agendados, + * ordenados por timestamp. Este é o coração do paradigma DES - a simulação avança + * processando eventos em ordem cronológica.

+ */ +public class EventQueue { + private final PriorityQueue queue; + private final List processedEvents; // For logging and analysis + private final boolean trackHistory; + + public EventQueue() { + this(true); + } + + public EventQueue(boolean trackHistory) { + this.queue = new PriorityQueue<>(); + this.processedEvents = trackHistory ? new ArrayList<>() : null; + this.trackHistory = trackHistory; + } + + /** + * Agenda um novo evento. + * + * @param event evento a agendar + */ + public void schedule(SimulationEvent event) { + queue.offer(event); + } + + /** + * Agenda um evento com um atraso relativo ao tempo atual. + * + * @param currentTime tempo atual da simulação + * @param delay atraso em segundos + * @param type tipo de evento + * @param payload dados do evento + * @param location localização do evento + */ + public void scheduleIn(double currentTime, double delay, DESEventType type, + Object payload, String location) { + double eventTime = currentTime + delay; + schedule(new SimulationEvent(eventTime, type, payload, location)); + } + + /** Obtém o próximo evento sem o remover */ + public SimulationEvent peek() { + return queue.peek(); + } + + /** + * Obtém e remove o próximo evento. + * Se o rastreamento de histórico estiver ativo, adiciona-o aos eventos processados. + */ + public SimulationEvent poll() { + SimulationEvent event = queue.poll(); + if (event != null && trackHistory) { + processedEvents.add(event); + } + return event; + } + + /** Verifica se existem eventos pendentes */ + public boolean isEmpty() { + return queue.isEmpty(); + } + + /** @return número de eventos pendentes */ + public int size() { + return queue.size(); + } + + /** Limpa todos os eventos pendentes */ + public void clear() { + queue.clear(); + } + + /** + * Obtém todos os eventos processados (se o rastreamento estiver ativo). + * Retorna uma cópia para evitar modificações. + */ + public List getProcessedEvents() { + if (!trackHistory) { + throw new UnsupportedOperationException("History tracking is disabled"); + } + return new ArrayList<>(processedEvents); + } + + /** @return número de eventos processados */ + public int getProcessedCount() { + return trackHistory ? processedEvents.size() : 0; + } + + /** + * Exporta o histórico de eventos para uma string formatada. + * Útil para debugging e visualização da lista completa de eventos. + */ + public String exportEventHistory() { + if (!trackHistory) { + return "Event history tracking is disabled"; + } + + StringBuilder sb = new StringBuilder(); + sb.append("=".repeat(80)).append("\n"); + sb.append("SIMULATION EVENT HISTORY\n"); + sb.append("Total Events Processed: ").append(processedEvents.size()).append("\n"); + sb.append("=".repeat(80)).append("\n"); + sb.append(String.format("%-10s | %-25s | %-20s | %s\n", + "Time", "Event Type", "Location", "Details")); + sb.append("-".repeat(80)).append("\n"); + + for (SimulationEvent event : processedEvents) { + String details = event.getPayload() != null ? + event.getPayload().getClass().getSimpleName() : "null"; + sb.append(String.format("%-10.3f | %-25s | %-20s | %s\n", + event.getTimestamp(), + event.getType(), + event.getLocation() != null ? event.getLocation() : "N/A", + details)); + } + + return sb.toString(); + } + + @Override + public String toString() { + return String.format("EventQueue[pending=%d, processed=%d]", + queue.size(), getProcessedCount()); + } +} diff --git a/main/src/main/java/sd/des/SimulationClock.java b/main/src/main/java/sd/des/SimulationClock.java new file mode 100644 index 0000000..9eeda32 --- /dev/null +++ b/main/src/main/java/sd/des/SimulationClock.java @@ -0,0 +1,67 @@ +package sd.des; + +/** + * Gere o tempo de simulação para Simulação de Eventos Discretos. + * + *

No DES, o tempo avança em saltos discretos de evento para evento, + * não de forma contínua como o tempo real.

+ * + *

Esta classe garante que todos os processos no sistema distribuído + * mantêm uma visão sincronizada do tempo de simulação.

+ */ +public class SimulationClock { + private double currentTime; + private final double startTime; + private final long wallClockStart; + + public SimulationClock() { + this(0.0); + } + + public SimulationClock(double startTime) { + this.currentTime = startTime; + this.startTime = startTime; + this.wallClockStart = System.currentTimeMillis(); + } + + /** + * Avança o tempo de simulação para o timestamp dado. + * O tempo só pode avançar, nunca recuar. + * + * @param newTime novo tempo de simulação + * @throws IllegalArgumentException se newTime for anterior ao tempo atual + */ + public void advanceTo(double newTime) { + if (newTime < currentTime) { + throw new IllegalArgumentException( + String.format("Cannot move time backwards: %.3f -> %.3f", currentTime, newTime)); + } + this.currentTime = newTime; + } + + /** @return tempo atual da simulação */ + public double getCurrentTime() { + return currentTime; + } + + /** @return tempo de simulação decorrido desde o início */ + public double getElapsedTime() { + return currentTime - startTime; + } + + /** @return tempo real decorrido em milissegundos */ + public long getWallClockElapsed() { + return System.currentTimeMillis() - wallClockStart; + } + + /** Reinicia o relógio para o tempo inicial */ + public void reset() { + this.currentTime = startTime; + } + + @Override + public String toString() { + return String.format("SimulationClock[time=%.3fs, elapsed=%.3fs]", + currentTime, getElapsedTime()); + } +} diff --git a/main/src/main/java/sd/des/SimulationEvent.java b/main/src/main/java/sd/des/SimulationEvent.java new file mode 100644 index 0000000..7d486d9 --- /dev/null +++ b/main/src/main/java/sd/des/SimulationEvent.java @@ -0,0 +1,98 @@ +package sd.des; + +import java.io.Serializable; + +/** + * Evento discreto da simulação. + * + *

Unidade fundamental de execução num sistema DES: + *

    + *
  • timestamp - quando ocorre + *
  • type - o que acontece + *
  • payload - dados associados + *
  • location - qual processo o trata + *
+ */ +public class SimulationEvent implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + private final double timestamp; + private final DESEventType type; + private final Object payload; + private final String location; // Process ID (e.g., "Cr1", "Coordinator", "Exit") + + /** + * Cria um novo evento de simulação. + * + * @param timestamp instante do evento (tempo de simulação em segundos) + * @param type tipo de evento + * @param payload dados associados (ex: objeto Vehicle) + * @param location processo que trata o evento + */ + public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) { + this.timestamp = timestamp; + this.type = type; + this.payload = payload; + this.location = location; + } + + /** Cria evento sem localização (para eventos locais) */ + public SimulationEvent(double timestamp, DESEventType type, Object payload) { + this(timestamp, type, payload, null); + } + + public double getTimestamp() { + return timestamp; + } + + public DESEventType getType() { + return type; + } + + public Object getPayload() { + return payload; + } + + public String getLocation() { + return location; + } + + /** + * Ordena eventos por timestamp (mais cedo primeiro). + * Em caso de empate, ordena por tipo para determinismo. + */ + @Override + public int compareTo(SimulationEvent other) { + int timeComparison = Double.compare(this.timestamp, other.timestamp); + if (timeComparison != 0) { + return timeComparison; + } + // Tie-breaker: order by event type name + return this.type.name().compareTo(other.type.name()); + } + + @Override + public String toString() { + return String.format("Event[t=%.3f, type=%s, location=%s]", + timestamp, type, location); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof SimulationEvent)) return false; + SimulationEvent other = (SimulationEvent) obj; + return Double.compare(timestamp, other.timestamp) == 0 && + type == other.type && + (location == null ? other.location == null : location.equals(other.location)); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Double.hashCode(timestamp); + result = 31 * result + type.hashCode(); + result = 31 * result + (location != null ? location.hashCode() : 0); + return result; + } +} diff --git a/main/src/main/java/sd/des/TrafficLightEvent.java b/main/src/main/java/sd/des/TrafficLightEvent.java new file mode 100644 index 0000000..9ca1357 --- /dev/null +++ b/main/src/main/java/sd/des/TrafficLightEvent.java @@ -0,0 +1,36 @@ +package sd.des; + +import sd.model.TrafficLight; + +/** + * Payload for traffic light change events. + * Contains the traffic light and its direction. + */ +public class TrafficLightEvent { + private final TrafficLight light; + private final String direction; + private final String intersectionId; + + public TrafficLightEvent(TrafficLight light, String direction, String intersectionId) { + this.light = light; + this.direction = direction; + this.intersectionId = intersectionId; + } + + public TrafficLight getLight() { + return light; + } + + public String getDirection() { + return direction; + } + + public String getIntersectionId() { + return intersectionId; + } + + @Override + public String toString() { + return String.format("TrafficLightEvent[%s-%s]", intersectionId, direction); + } +} diff --git a/main/src/main/java/sd/engine/TrafficLightThread.java b/main/src/main/java/sd/engine/TrafficLightThread.java deleted file mode 100644 index 8a0c3a5..0000000 --- a/main/src/main/java/sd/engine/TrafficLightThread.java +++ /dev/null @@ -1,126 +0,0 @@ -package sd.engine; - -import sd.IntersectionProcess; -import sd.config.SimulationConfig; -import sd.model.TrafficLight; -import sd.model.TrafficLightState; -import sd.model.Vehicle; - -/** - * Implements the control logic for a single TrafficLight - * as a Runnable task that runs in its own Thread. - */ -public class TrafficLightThread implements Runnable { - - private final TrafficLight light; - private final IntersectionProcess process; - private final SimulationConfig config; - private volatile boolean running; - - // Store the thread reference for proper interruption - private Thread currentThread; - - public TrafficLightThread(TrafficLight light, IntersectionProcess process, SimulationConfig config) { - this.light = light; - this.process = process; - this.config = config; - this.running = false; - } - - @Override - public void run() { - this.currentThread = Thread.currentThread(); - this.running = true; - System.out.println("[" + light.getId() + "] Traffic light thread started."); - - 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"); - - // 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)); - } - } catch (InterruptedException e) { - System.out.println("[" + light.getId() + "] Traffic light thread interrupted."); - Thread.currentThread().interrupt(); - } finally { - this.running = false; - System.out.println("[" + light.getId() + "] Traffic light thread stopped."); - } - } - - 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); - } - } - } - - private double getCrossingTimeForVehicle(Vehicle vehicle) { - return switch (vehicle.getType()) { - case BIKE -> config.getBikeVehicleCrossingTime(); - case LIGHT -> config.getLightVehicleCrossingTime(); - case HEAVY -> config.getHeavyVehicleCrossingTime(); - default -> config.getLightVehicleCrossingTime(); - }; - } - - /** - * Requests the thread to stop gracefully. - * Sets the running flag and interrupts the thread to unblock any sleep() calls. - */ - public void shutdown() { - this.running = false; - if (currentThread != null && currentThread.isAlive()) { - currentThread.interrupt(); - } - } -} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventLogger.java b/main/src/main/java/sd/logging/EventLogger.java new file mode 100644 index 0000000..f653ff9 --- /dev/null +++ b/main/src/main/java/sd/logging/EventLogger.java @@ -0,0 +1,213 @@ +package sd.logging; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Sistema de registo centralizado de eventos para a simulação distribuída. + * + *

Regista todos os eventos da simulação num ficheiro com timestamps e categorização. + * Thread-safe e não-bloqueante para impacto mínimo na performance.

+ */ +public class EventLogger { + + private static EventLogger instance; + private static final Object instanceLock = new Object(); + + private final PrintWriter writer; + private final BlockingQueue logQueue; + private final Thread writerThread; + private final AtomicBoolean running; + private final SimpleDateFormat timestampFormat; + private final long simulationStartMillis; + + /** Construtor privado para padrão singleton */ + private EventLogger(String logFilePath) throws IOException { + this.writer = new PrintWriter(new BufferedWriter(new FileWriter(logFilePath, false)), true); + this.logQueue = new LinkedBlockingQueue<>(10000); + this.running = new AtomicBoolean(true); + this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + this.simulationStartMillis = System.currentTimeMillis(); + + writer.println("=".repeat(80)); + writer.println("SIMULATION EVENT LOG"); + writer.println("Started: " + timestampFormat.format(new Date())); + writer.println("=".repeat(80)); + writer.println(); + writer.printf("%-23s | %-8s | %-20s | %-15s | %s\n", + "TIMESTAMP", "REL_TIME", "EVENT_TYPE", "COMPONENT", "DESCRIPTION"); + writer.println("-".repeat(80)); + writer.flush(); + + this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer"); + this.writerThread.setDaemon(true); + this.writerThread.start(); + } + + /** Obtém ou cria a instância singleton */ + public static EventLogger getInstance() { + if (instance == null) { + synchronized (instanceLock) { + if (instance == null) { + try { + String logFile = "logs/simulation-events.log"; + java.nio.file.Files.createDirectories( + java.nio.file.Paths.get("logs")); + instance = new EventLogger(logFile); + } catch (IOException e) { + System.err.println("Failed to initialize EventLogger: " + e.getMessage()); + e.printStackTrace(); + } + } + } + } + return instance; + } + + /** + * Initialize with custom log file path. + */ + public static void initialize(String logFilePath) throws IOException { + synchronized (instanceLock) { + if (instance != null) { + instance.shutdown(); + } + instance = new EventLogger(logFilePath); + } + } + + /** + * Logs an event (non-blocking). + */ + public void log(EventType eventType, String component, String description) { + if (!running.get()) return; + + LogEntry entry = new LogEntry( + System.currentTimeMillis(), + eventType, + component, + description + ); + + // Non-blocking offer - if queue is full, drop oldest + if (!logQueue.offer(entry)) { + // Queue full - this shouldn't happen with 10k buffer, but handle gracefully + System.err.println("EventLogger queue full - dropping event: " + eventType); + } + } + + /** + * Logs an event with vehicle context. + */ + public void logVehicle(EventType eventType, String component, String vehicleId, String description) { + log(eventType, component, "[" + vehicleId + "] " + description); + } + + /** + * Logs an error event. + */ + public void logError(String component, String description, Exception e) { + String fullDescription = description + (e != null ? ": " + e.getMessage() : ""); + log(EventType.ERROR, component, fullDescription); + } + + /** + * Background thread that writes log entries to file. + */ + private void processLogQueue() { + while (running.get() || !logQueue.isEmpty()) { + try { + LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); + if (entry != null) { + writeEntry(entry); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Flush remaining entries + while (!logQueue.isEmpty()) { + LogEntry entry = logQueue.poll(); + if (entry != null) { + writeEntry(entry); + } + } + } + + /** + * Writes a single log entry to file. + */ + private void writeEntry(LogEntry entry) { + String timestamp = timestampFormat.format(new Date(entry.timestampMillis)); + double relativeTime = (entry.timestampMillis - simulationStartMillis) / 1000.0; + + writer.printf("%-23s | %8.3fs | %-20s | %-15s | %s\n", + timestamp, + relativeTime, + entry.eventType.toString(), + truncate(entry.component, 15), + entry.description + ); + + // Flush periodically for real-time viewing + if (logQueue.size() < 10) { + writer.flush(); + } + } + + private String truncate(String str, int maxLength) { + if (str == null) return ""; + return str.length() <= maxLength ? str : str.substring(0, maxLength); + } + + /** + * Shuts down the logger and flushes all pending entries. + */ + public void shutdown() { + if (!running.compareAndSet(true, false)) { + return; // Already shut down + } + + try { + // Wait for writer thread to finish + writerThread.join(5000); // Wait up to 5 seconds + + // Write footer + writer.println(); + writer.println("-".repeat(80)); + writer.println("SIMULATION ENDED"); + writer.println("Ended: " + timestampFormat.format(new Date())); + writer.println("=".repeat(80)); + + writer.close(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Internal class to represent a log entry. + */ + private static class LogEntry { + final long timestampMillis; + final EventType eventType; + final String component; + final String description; + + LogEntry(long timestampMillis, EventType eventType, String component, String description) { + this.timestampMillis = timestampMillis; + this.eventType = eventType; + this.component = component; + this.description = description; + } + } +} diff --git a/main/src/main/java/sd/logging/EventType.java b/main/src/main/java/sd/logging/EventType.java new file mode 100644 index 0000000..99d8710 --- /dev/null +++ b/main/src/main/java/sd/logging/EventType.java @@ -0,0 +1,47 @@ +package sd.logging; + +/** + * Tipos de eventos que podem ocorrer na simulação. + * Usados para categorizar e filtrar logs. + */ +public enum EventType { + VEHICLE_GENERATED("Vehicle Generated"), + VEHICLE_ARRIVED("Vehicle Arrived"), + VEHICLE_QUEUED("Vehicle Queued"), + VEHICLE_DEPARTED("Vehicle Departed"), + VEHICLE_EXITED("Vehicle Exited"), + + LIGHT_CHANGED_GREEN("Light Changed to Green"), + LIGHT_CHANGED_RED("Light Changed to Red"), + LIGHT_REQUEST_GREEN("Light Requested Green"), + LIGHT_RELEASE_GREEN("Light Released Green"), + + SIMULATION_STARTED("Simulation Started"), + SIMULATION_STOPPED("Simulation Stopped"), + PROCESS_STARTED("Process Started"), + PROCESS_STOPPED("Process Stopped"), + + STATS_UPDATE("Statistics Update"), + + CONNECTION_ESTABLISHED("Connection Established"), + CONNECTION_LOST("Connection Lost"), + MESSAGE_SENT("Message Sent"), + MESSAGE_RECEIVED("Message Received"), + + ERROR("Error"); + + private final String displayName; + + EventType(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/main/src/main/java/sd/logging/VehicleTracer.java b/main/src/main/java/sd/logging/VehicleTracer.java new file mode 100644 index 0000000..98be134 --- /dev/null +++ b/main/src/main/java/sd/logging/VehicleTracer.java @@ -0,0 +1,331 @@ +package sd.logging; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import sd.model.Vehicle; + +/** + * Rastreia e regista a viagem completa de veículos individuais. + * + *

Cria ficheiros de trace detalhados com: + *

    + *
  • Timestamps de todos os eventos + *
  • Localizações (interseções) + *
  • Tempos de espera em cada semáforo + *
  • Tempos de travessia + *
  • Tempo total no sistema + *
+ */ +public class VehicleTracer { + + private static VehicleTracer instance; + private static final Object instanceLock = new Object(); + + private final Map trackedVehicles; + private final SimpleDateFormat timestampFormat; + private final long simulationStartMillis; + private final String traceDirectory; + + /** Construtor privado (singleton) */ + private VehicleTracer(String traceDirectory) { + this.trackedVehicles = new ConcurrentHashMap<>(); + this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + this.simulationStartMillis = System.currentTimeMillis(); + this.traceDirectory = traceDirectory; + + try { + java.nio.file.Files.createDirectories(java.nio.file.Paths.get(traceDirectory)); + } catch (IOException e) { + System.err.println("Failed to create trace directory: " + e.getMessage()); + } + } + + /** Obtém ou cria a instância singleton */ + public static VehicleTracer getInstance() { + if (instance == null) { + synchronized (instanceLock) { + if (instance == null) { + instance = new VehicleTracer("logs/traces"); + } + } + } + return instance; + } + + /** Inicializa com diretório de trace customizado */ + public static void initialize(String traceDirectory) { + synchronized (instanceLock) { + if (instance != null) { + instance.shutdown(); + } + instance = new VehicleTracer(traceDirectory); + } + } + + /** + * Começa a rastrear um veículo específico. + * Cria ficheiro de trace para este veículo. + */ + public void startTracking(String vehicleId) { + if (trackedVehicles.containsKey(vehicleId)) { + return; // Already tracking + } + + VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory); + trackedVehicles.put(vehicleId, trace); + + trace.logEvent("TRACKING_STARTED", "", "Started tracking vehicle " + vehicleId); + } + + /** + * Stops tracking a vehicle and closes its trace file. + */ + public void stopTracking(String vehicleId) { + VehicleTrace trace = trackedVehicles.remove(vehicleId); + if (trace != null) { + trace.logEvent("TRACKING_STOPPED", "", "Stopped tracking vehicle " + vehicleId); + trace.close(); + } + } + + /** + * Checks if a vehicle is being tracked. + */ + public boolean isTracking(String vehicleId) { + return trackedVehicles.containsKey(vehicleId); + } + + /** + * Logs when a vehicle is generated. + */ + public void logGenerated(Vehicle vehicle) { + if (!isTracking(vehicle.getId())) return; + + VehicleTrace trace = trackedVehicles.get(vehicle.getId()); + if (trace != null) { + trace.logEvent("GENERATED", "Coordinator", + String.format("Type: %s, Entry Time: %.2fs, Route: %s", + vehicle.getType(), vehicle.getEntryTime(), vehicle.getRoute())); + } + } + + /** + * Logs when a vehicle arrives at an intersection. + */ + public void logArrival(String vehicleId, String intersection, double simulationTime) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("ARRIVED", intersection, + String.format("Arrived at %s (sim time: %.2fs)", intersection, simulationTime)); + } + } + + /** + * Logs when a vehicle is queued at a traffic light. + */ + public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("QUEUED", intersection, + String.format("Queued at %s-%s (position: %d)", intersection, direction, queuePosition)); + } + } + + /** + * Logs when a vehicle starts waiting at a red light. + */ + public void logWaitingStart(String vehicleId, String intersection, String direction) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("WAITING_START", intersection, + String.format("Started waiting at %s-%s (light is RED)", intersection, direction)); + } + } + + /** + * Logs when a vehicle finishes waiting (light turns green). + */ + public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("WAITING_END", intersection, + String.format("Finished waiting at %s-%s (waited %.2fs)", intersection, direction, waitTime)); + } + } + + /** + * Logs when a vehicle starts crossing an intersection. + */ + public void logCrossingStart(String vehicleId, String intersection, String direction) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("CROSSING_START", intersection, + String.format("Started crossing %s-%s (light is GREEN)", intersection, direction)); + } + } + + /** + * Logs when a vehicle finishes crossing an intersection. + */ + public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("CROSSING_END", intersection, + String.format("Finished crossing %s (took %.2fs)", intersection, crossingTime)); + } + } + + /** + * Logs when a vehicle departs from an intersection. + */ + public void logDeparture(String vehicleId, String intersection, String nextDestination) { + if (!isTracking(vehicleId)) return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("DEPARTED", intersection, + String.format("Departed from %s toward %s", intersection, nextDestination)); + } + } + + /** + * Logs when a vehicle exits the system. + */ + public void logExit(Vehicle vehicle, double systemTime) { + if (!isTracking(vehicle.getId())) return; + + VehicleTrace trace = trackedVehicles.get(vehicle.getId()); + if (trace != null) { + trace.logEvent("EXITED", "Exit Node", + String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs", + systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime())); + + // Write summary + trace.writeSummary(vehicle, systemTime); + + // Stop tracking and close file + stopTracking(vehicle.getId()); + } + } + + /** + * Shuts down the tracer and closes all trace files. + */ + public void shutdown() { + for (VehicleTrace trace : trackedVehicles.values()) { + trace.close(); + } + trackedVehicles.clear(); + } + + /** + * Internal class to handle tracing for a single vehicle. + */ + private class VehicleTrace { + private final String vehicleId; + private final PrintWriter writer; + private final long traceStartMillis; + + VehicleTrace(String vehicleId, String directory) { + this.vehicleId = vehicleId; + this.traceStartMillis = System.currentTimeMillis(); + + PrintWriter w = null; + try { + String filename = String.format("%s/vehicle-%s.trace", directory, vehicleId); + w = new PrintWriter(new BufferedWriter(new FileWriter(filename, false)), true); + + // Write header + w.println("=".repeat(80)); + w.println("VEHICLE TRACE: " + vehicleId); + w.println("Trace Started: " + timestampFormat.format(new Date())); + w.println("=".repeat(80)); + w.println(); + w.printf("%-23s | %-8s | %-15s | %-15s | %s\n", + "TIMESTAMP", "REL_TIME", "EVENT", "LOCATION", "DESCRIPTION"); + w.println("-".repeat(80)); + + } catch (IOException e) { + System.err.println("Failed to create trace file for " + vehicleId + ": " + e.getMessage()); + } + + this.writer = w; + } + + void logEvent(String eventType, String location, String description) { + if (writer == null) return; + + long now = System.currentTimeMillis(); + String timestamp = timestampFormat.format(new Date(now)); + double relativeTime = (now - traceStartMillis) / 1000.0; + + writer.printf("%-23s | %8.3fs | %-15s | %-15s | %s\n", + timestamp, + relativeTime, + truncate(eventType, 15), + truncate(location, 15), + description + ); + writer.flush(); + } + + void writeSummary(Vehicle vehicle, double systemTime) { + if (writer == null) return; + + writer.println(); + writer.println("=".repeat(80)); + writer.println("JOURNEY SUMMARY"); + writer.println("=".repeat(80)); + writer.println("Vehicle ID: " + vehicle.getId()); + writer.println("Vehicle Type: " + vehicle.getType()); + writer.println("Route: " + vehicle.getRoute()); + writer.println(); + writer.printf("Entry Time: %.2f seconds\n", vehicle.getEntryTime()); + writer.printf("Total System Time: %.2f seconds\n", systemTime); + writer.printf("Total Waiting Time: %.2f seconds (%.1f%%)\n", + vehicle.getTotalWaitingTime(), + 100.0 * vehicle.getTotalWaitingTime() / systemTime); + writer.printf("Total Crossing Time: %.2f seconds (%.1f%%)\n", + vehicle.getTotalCrossingTime(), + 100.0 * vehicle.getTotalCrossingTime() / systemTime); + writer.printf("Travel Time: %.2f seconds (%.1f%%)\n", + systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime(), + 100.0 * (systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime()) / systemTime); + writer.println("=".repeat(80)); + } + + void close() { + if (writer != null) { + writer.println(); + writer.println("-".repeat(80)); + writer.println("END OF TRACE"); + writer.println("=".repeat(80)); + writer.close(); + } + } + + private String truncate(String str, int maxLength) { + if (str == null) return ""; + return str.length() <= maxLength ? str : str.substring(0, maxLength); + } + } +} diff --git a/main/src/main/java/sd/model/Intersection.java b/main/src/main/java/sd/model/Intersection.java index 7d6ff32..33bcd86 100644 --- a/main/src/main/java/sd/model/Intersection.java +++ b/main/src/main/java/sd/model/Intersection.java @@ -6,65 +6,51 @@ import java.util.List; import java.util.Map; /** - * Represents an intersection in the traffic simulation. - * * An Intersection acts as a central hub. It does not control logic itself, - * but it *owns* and *manages* a set of {@link TrafficLight} objects. - * * Its primary responsibilities are: - * 1. Holding a {@link TrafficLight} for each direction ("North", "East", etc.). - * 2. Maintaining a {@code routing} table that maps a vehicle's *next* - * destination (e.g., "Cr3") to a specific *direction* at *this* - * intersection (e.g., "East"). - * 3. Receiving incoming vehicles and placing them in the correct - * traffic light's queue based on the routing table. - * 4. Tracking aggregate statistics for all traffic passing through it. + * Representa uma interseção na simulação de tráfego. + * + *

Uma interseção funciona como um nó central da rede. Não controla lógica diretamente, + * mas gere um conjunto de semáforos ({@link TrafficLight}).

+ * + *

Responsabilidades principais:

+ *
    + *
  • Manter um {@link TrafficLight} para cada direção (Norte, Este, etc.)
  • + *
  • Gerir uma tabela de encaminhamento que mapeia destinos para direções
  • + *
  • Receber veículos e colocá-los na fila do semáforo correto
  • + *
  • Acompanhar estatísticas agregadas do tráfego
  • + *
*/ public class Intersection { - // --- Identity and configuration --- - - /** - * Unique identifier for the intersection (e.g., "Cr1", "Cr2"). - */ + /** Identificador único da interseção (ex: "Cr1", "Cr2") */ private final String id; /** - * A map holding all traffic lights managed by this intersection. - * Key: Direction (String, e.g., "North", "East"). - * Value: The {@link TrafficLight} object for that direction. + * Mapa com todos os semáforos desta interseção. + * Chave: Direção (String, ex: "Norte", "Este") + * Valor: Objeto {@link TrafficLight} correspondente */ private final Map trafficLights; /** - * The routing table for this intersection. - * Key: The *next* destination ID (String, e.g., "Cr3", "S" for exit). - * Value: The *direction* (String, e.g., "East") a vehicle must take - * at *this* intersection to reach that destination. + * Tabela de encaminhamento da interseção. + * Chave: Próximo destino (String, ex: "Cr3", "S" para saída) + * Valor: Direção que o veículo deve tomar nesta interseção */ private final Map routing; - - // --- Statistics --- - - /** - * Total number of vehicles that have been received by this intersection. - */ + /** Número total de veículos recebidos por esta interseção */ private int totalVehiclesReceived; - /** - * Total number of vehicles that have successfully passed through (sent from) this intersection. - */ + /** Número total de veículos que partiram desta interseção */ private int totalVehiclesSent; - /** - * A running average of the waiting time for vehicles at this intersection. - * Note: This calculation might be simplified. - */ + /** Média acumulada do tempo de espera dos veículos nesta interseção */ private double averageWaitingTime; /** - * Constructs a new Intersection with a given ID. - * Initializes empty maps for traffic lights and routing. + * Cria uma nova interseção. + * Inicializa mapas vazios para semáforos e encaminhamento. * - * @param id The unique identifier for this intersection (e.g., "Cr1"). + * @param id identificador único da interseção (ex: "Cr1") */ public Intersection(String id) { this.id = id; @@ -76,40 +62,41 @@ public class Intersection { } /** - * Registers a new {@link TrafficLight} with this intersection. - * The light is mapped by its direction. + * Regista um novo semáforo nesta interseção. + * O semáforo é mapeado pela sua direção. * - * @param trafficLight The {@link TrafficLight} object to add. + * @param trafficLight o semáforo a adicionar */ public void addTrafficLight(TrafficLight trafficLight) { trafficLights.put(trafficLight.getDirection(), trafficLight); } /** - * Defines a routing rule for this intersection. - * * This method builds the routing table. For example, calling - * {@code configureRoute("Cr3", "East")} means "Any vehicle - * arriving here whose next destination is 'Cr3' should be sent to - * the 'East' traffic light queue." + * Define uma regra de encaminhamento para esta interseção. + * + *

Por exemplo, {@code configureRoute("Cr3", "Este")} significa: + * "Qualquer veículo que chegue aqui com destino 'Cr3' deve ser enviado + * para a fila do semáforo da direção Este."

* - * @param nextDestination The ID of the *next* intersection or exit (e.g., "Cr3", "S"). - * @param direction The direction (and thus, the traffic light) - * at *this* intersection to use (e.g., "East"). + * @param nextDestination ID da próxima interseção ou saída (ex: "Cr3", "S") + * @param direction direção (e respetivo semáforo) a usar nesta interseção */ public void configureRoute(String nextDestination, String direction) { routing.put(nextDestination, direction); } /** - * Accepts an incoming vehicle and places it in the correct queue. - * * This method: - * 1. Increments the {@link #totalVehiclesReceived} counter. - * 2. Advances the vehicle's route (since it just arrived here) - * 3. Gets the vehicle's *next* destination (from {@link Vehicle#getCurrentDestination()}). - * 4. Uses the {@link #routing} map to find the correct *direction* for that destination. - * 5. Adds the vehicle to the queue of the {@link TrafficLight} for that direction. + * Recebe um veículo e coloca-o na fila correta. + * + *

Passos executados:

+ *
    + *
  1. Incrementa o contador de veículos recebidos
  2. + *
  3. Obtém o próximo destino do veículo
  4. + *
  5. Consulta a tabela de encaminhamento para encontrar a direção
  6. + *
  7. Adiciona o veículo à fila do semáforo apropriado
  8. + *
* - * @param vehicle The {@link Vehicle} arriving at the intersection. + * @param vehicle o veículo que chega à interseção */ public void receiveVehicle(Vehicle vehicle) { totalVehiclesReceived++; @@ -141,115 +128,99 @@ public class Intersection { } /** - * Returns the direction a vehicle should take to reach a given destination. + * Retorna a direção que um veículo deve tomar para alcançar um destino. * - * @param destination The next destination (e.g., "Cr3", "S"). - * @return The direction (e.g., "East"), or null if no route is configured. + * @param destination o próximo destino (ex: "Cr3", "S") + * @return a direção (ex: "Este"), ou null se não houver rota configurada */ public String getDirectionForDestination(String destination) { return routing.get(destination); } /** - * Returns the traffic light controlling the given direction. + * Retorna o semáforo que controla uma determinada direção. * - * @param direction The direction (e.g., "North"). - * @return The {@link TrafficLight} object, or null if no light exists - * for that direction. + * @param direction a direção (ex: "Norte") + * @return o objeto {@link TrafficLight}, ou null se não existir */ public TrafficLight getTrafficLight(String direction) { return trafficLights.get(direction); } /** - * Returns a list of all traffic lights managed by this intersection. + * Retorna uma lista com todos os semáforos desta interseção. * - * @return A new {@link List} containing all {@link TrafficLight} objects. + * @return uma nova {@link List} com todos os semáforos */ public List getTrafficLights() { - // Return a copy to prevent external modification of the internal map's values return new ArrayList<>(trafficLights.values()); } /** - * Returns the total number of vehicles currently queued across *all* - * traffic lights at this intersection. + * Retorna o número total de veículos em fila em todos os semáforos. + * Usa Java Stream API para somar os tamanhos de todas as filas. * - * @return The sum of all queue sizes. + * @return a soma dos tamanhos de todas as filas */ public int getTotalQueueSize() { - // Uses Java Stream API: - // 1. trafficLights.values().stream() - Get a stream of TrafficLight objects - // 2. .mapToInt(TrafficLight::getQueueSize) - Convert each light to its queue size (an int) - // 3. .sum() - Sum all the integers return trafficLights.values().stream() .mapToInt(TrafficLight::getQueueSize) .sum(); } - // --- Stats and getters --- - /** - * @return The unique ID of this intersection. + * @return o identificador único desta interseção */ public String getId() { return id; } /** - * @return The total number of vehicles that have arrived at this intersection. + * @return o número total de veículos que chegaram a esta interseção */ public int getTotalVehiclesReceived() { return totalVehiclesReceived; } /** - * @return The total number of vehicles that have successfully - * departed from this intersection. + * @return o número total de veículos que partiram desta interseção */ public int getTotalVehiclesSent() { return totalVehiclesSent; } /** - * Increments the counter for vehicles that have successfully departed. - * This is typically called by the {@link sd.engine.SimulationEngine} - * after a vehicle finishes crossing. + * Incrementa o contador de veículos que partiram com sucesso. + * Tipicamente chamado após um veículo completar a travessia. */ public void incrementVehiclesSent() { totalVehiclesSent++; } /** - * @return The running average of vehicle waiting time at this intersection. + * @return a média do tempo de espera dos veículos nesta interseção */ public double getAverageWaitingTime() { return averageWaitingTime; } /** - * Updates the running average waiting time with a new sample (a new - * vehicle's wait time). - * * Uses an incremental/weighted average formula: - * NewAvg = (OldAvg * (N-1) + NewValue) / N - * where N is the total number of vehicles sent. + * Atualiza a média do tempo de espera com uma nova amostra. + * Usa a fórmula: Nova Média = (Média Antiga * (N-1) + Novo Valor) / N * - * @param newTime The waiting time (in seconds) of the vehicle that just - * departed. + * @param newTime tempo de espera (em segundos) do veículo que acabou de partir */ public void updateAverageWaitingTime(double newTime) { - // Avoid division by zero if this is called before any vehicle is sent if (totalVehiclesSent > 0) { averageWaitingTime = (averageWaitingTime * (totalVehiclesSent - 1) + newTime) / totalVehiclesSent; } else if (totalVehiclesSent == 1) { - // This is the first vehicle averageWaitingTime = newTime; } } /** - * @return A string summary of the intersection's current state. + * @return representação textual do estado atual da interseção */ @Override public String toString() { diff --git a/main/src/main/java/sd/model/Message.java b/main/src/main/java/sd/model/Message.java index 87e1200..623bc0d 100644 --- a/main/src/main/java/sd/model/Message.java +++ b/main/src/main/java/sd/model/Message.java @@ -5,52 +5,41 @@ import java.util.UUID; import sd.protocol.MessageProtocol; /** - * Represents a message exchanged between processes in the distributed simulation. - * Each message has a unique ID, a type, a sender, a destination, and a payload. - * This class implements {@link MessageProtocol} which extends Serializable for network transmission. + * Representa uma mensagem trocada entre processos na simulação distribuída. + * + *

Cada mensagem tem um ID único, tipo, remetente, destino e payload. + * Implementa {@link MessageProtocol} que estende Serializable para transmissão pela rede.

*/ public class Message implements MessageProtocol { private static final long serialVersionUID = 1L; - /** - * Unique identifier for this message. - */ + /** Identificador único desta mensagem */ private final String messageId; - /** - * The type of this message (e.g., VEHICLE_TRANSFER, STATS_UPDATE). - */ + /** Tipo desta mensagem (ex: VEHICLE_TRANSFER, STATS_UPDATE) */ private final MessageType type; - /** - * Identifier of the process that sent this message. - */ + /** Identificador do processo que enviou esta mensagem */ private final String senderId; - /** - * Identifier of the destination process. Can be null for broadcast messages. - */ + /** Identificador do processo de destino (pode ser null para broadcast) */ private final String destinationId; - /** - * The actual data being transmitted. Type depends on the message type. - */ + /** Dados a serem transmitidos (o tipo depende do tipo de mensagem) */ private final Object payload; - /** - * Timestamp when this message was created (simulation time or real time). - */ + /** Timestamp de criação da mensagem (tempo de simulação ou real) */ private final long timestamp; /** - * Creates a new message with all parameters. + * Cria uma nova mensagem com todos os parâmetros. * - * @param type The message type - * @param senderId The ID of the sending process - * @param destinationId The ID of the destination process (null for broadcast) - * @param payload The message payload - * @param timestamp The timestamp of message creation + * @param type tipo da mensagem + * @param senderId ID do processo remetente + * @param destinationId ID do processo de destino (null para broadcast) + * @param payload conteúdo da mensagem + * @param timestamp timestamp de criação da mensagem */ public Message(MessageType type, String senderId, String destinationId, Object payload, long timestamp) { @@ -63,23 +52,23 @@ public class Message implements MessageProtocol { } /** - * Creates a new message with current system time as timestamp. + * Cria uma nova mensagem usando o tempo atual do sistema como timestamp. * - * @param type The message type - * @param senderId The ID of the sending process - * @param destinationId The ID of the destination process - * @param payload The message payload + * @param type tipo da mensagem + * @param senderId ID do processo remetente + * @param destinationId ID do processo de destino + * @param payload conteúdo da mensagem */ public Message(MessageType type, String senderId, String destinationId, Object payload) { this(type, senderId, destinationId, payload, System.currentTimeMillis()); } /** - * Creates a broadcast message (no specific destination). + * Cria uma mensagem de broadcast (sem destino específico). * - * @param type The message type - * @param senderId The ID of the sending process - * @param payload The message payload + * @param type tipo da mensagem + * @param senderId ID do processo remetente + * @param payload conteúdo da mensagem */ public Message(MessageType type, String senderId, Object payload) { this(type, senderId, null, payload, System.currentTimeMillis()); diff --git a/main/src/main/java/sd/model/MessageType.java b/main/src/main/java/sd/model/MessageType.java index a9f4794..23202c3 100644 --- a/main/src/main/java/sd/model/MessageType.java +++ b/main/src/main/java/sd/model/MessageType.java @@ -1,87 +1,43 @@ package sd.model; /** - * Enumeration representing all possible message types for distributed communication. - * These types are used for inter-process communication between different components - * of the distributed traffic simulation system. + * Enumeração que representa todos os tipos de mensagens possíveis para + * comunicação distribuída. + * Estes tipos são usados para a comunicação entre processos dos diferentes + * componentes + * do sistema de simulação de tráfego distribuído. */ public enum MessageType { - + /** - * Message to transfer a vehicle between intersections or processes. - * Payload: Vehicle object with current state + * Mensagem para transferir um veículo entre interseções ou processos. + * Payload: Objeto Vehicle com o estado atual */ VEHICLE_TRANSFER, - + /** - * Message to update statistics across the distributed system. - * Payload: Statistics data (waiting times, queue sizes, etc.) + * Mensagem para atualizar estatísticas em todo o sistema distribuído. + * Payload: Dados estatísticos (tempos de espera, tamanhos de fila, etc.) */ STATS_UPDATE, - + /** - * Message to synchronize simulation start time across all processes. - * Payload: Start timestamp (long milliseconds) + * Mensagem para sincronizar a hora de início da simulação em todos os + * processos. + * Payload: Timestamp de início (long milissegundos) */ SIMULATION_START, - + /** - * Message to synchronize traffic light states between processes. - * Payload: TrafficLight state and timing information - */ - TRAFFIC_LIGHT_SYNC, - - /** - * Heartbeat message to check if a process is alive. - * Payload: Process ID and timestamp - */ - HEARTBEAT, - - /** - * Request to join the distributed simulation. - * Payload: Process information and capabilities - */ - JOIN_REQUEST, - - /** - * Response to a join request. - * Payload: Acceptance status and configuration - */ - JOIN_RESPONSE, - - /** - * Message to notify about a new vehicle generation. - * Payload: Vehicle generation parameters + * Mensagem para notificar sobre a geração de um novo veículo. + * Payload: Parâmetros de geração do veículo */ VEHICLE_SPAWN, - + /** - * Message to request the current state of an intersection. - * Payload: Intersection ID - */ - STATE_REQUEST, - - /** - * Response containing the current state of an intersection. - * Payload: Complete intersection state - */ - STATE_RESPONSE, - - /** - * Message to signal shutdown of a process. - * Payload: Process ID and reason + * Mensagem para sinalizar o encerramento de um processo. + * Payload: ID do processo e motivo */ SHUTDOWN, - - /** - * Acknowledgment message for reliable communication. - * Payload: Message ID being acknowledged - */ - ACK, - - /** - * Error message to report problems in the distributed system. - * Payload: Error description and context - */ - ERROR + } diff --git a/main/src/main/java/sd/model/TrafficLight.java b/main/src/main/java/sd/model/TrafficLight.java index 7cfd393..b6d82ea 100644 --- a/main/src/main/java/sd/model/TrafficLight.java +++ b/main/src/main/java/sd/model/TrafficLight.java @@ -9,114 +9,69 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** - * Represents a single traffic light controlling one direction at an intersection. - * * Each light maintains its own queue of {@link Vehicle} objects and - * alternates between {@link TrafficLightState#GREEN} and - * {@link TrafficLightState#RED} states. - * * This class is designed to be thread-safe for a potential concurrent - * simulation (though the current engine {@link sd.engine.SimulationEngine} - * is single-threaded). It uses a {@link ReentrantLock} to protect its - * internal state (the queue and the light state) from simultaneous access. - * * The {@link Condition} variables ({@code vehicleAdded}, {@code lightGreen}) - * are included for a concurrent model where: - * - A "vehicle" thread might wait on {@code lightGreen} until the light changes. - * - A "controller" thread might wait on {@code vehicleAdded} to know when to - * process a queue. - * (Note: These Conditions are *not* used by the current discrete-event engine). + * Representa um semáforo numa interseção. + * + *

Cada semáforo controla uma direção específica e mantém uma fila de veículos à espera. + * Alterna entre os estados VERDE e VERMELHO de acordo com a temporização configurada.

+ * + *

Thread-safety: Usa locks para permitir acesso concorrente seguro entre + * a thread de processamento de eventos e as threads de I/O de rede.

*/ public class TrafficLight { - // --- Identity and configuration --- - - /** - * Unique identifier for the light (e.g., "Cr1-N"). - */ + /** Identificador único do semáforo (ex: "Cr1-N") */ private final String id; - - /** - * The direction this light controls (e.g., "North", "South"). - */ + + /** Direção que este semáforo controla (ex: "Norte", "Sul") */ private final String direction; - - /** - * The current state of the light (GREEN or RED). - */ + + /** Estado atual do semáforo (VERDE ou VERMELHO) */ private TrafficLightState state; - // --- Vehicle management --- - - /** - * The queue of vehicles waiting at this light. - * {@link LinkedList} is used as it's a standard {@link Queue} implementation. - */ + /** Fila de veículos à espera neste semáforo */ private final Queue queue; - // --- Synchronization primitives (for thread-safety) --- - /** - * A lock to protect all mutable state ({@link #queue} and {@link #state}) - * from concurrent access. Any method reading or writing these fields - * *must* acquire this lock first. + * Lock para proteger o estado mutável ({@link #queue} e {@link #state}) + * de acesso concorrente. */ private final Lock lock; - - /** - * A condition variable for a potential concurrent model. - * It could be used to signal threads (e.g., a controller) that - * a new vehicle has been added to the queue. - * (Not used in the current discrete-event engine). - */ + + /** Variável de condição para sinalizar adição de veículos (uso futuro) */ private final Condition vehicleAdded; - - /** - * A condition variable for a potential concurrent model. - * It could be used to signal waiting vehicle threads that the - * light has just turned GREEN. - * (Not used in the current discrete-event engine). - */ + + /** Variável de condição para sinalizar que o semáforo ficou verde (uso futuro) */ private final Condition lightGreen; - // --- Timing configuration --- - - /** - * The duration (in seconds) this light stays GREEN. - */ + /** Duração (segundos) que o semáforo permanece VERDE */ private double greenTime; - - /** - * The duration (in seconds) this light stays RED. - */ + + /** Duração (segundos) que o semáforo permanece VERMELHO */ private double redTime; - // --- Statistics --- - - /** - * Counter for the total number of vehicles that have - * been dequeued (processed) by this light. - */ + /** Número total de veículos processados por este semáforo */ private int totalVehiclesProcessed; - + /** - * Track when vehicles arrive at this light for wait time calculation. - * Maps vehicle ID to arrival timestamp (milliseconds). + * Regista quando os veículos chegam ao semáforo para cálculo do tempo de espera. + * Mapeia ID do veículo para timestamp de chegada (milissegundos). */ private final Map vehicleArrivalTimes; /** - * Constructs a new TrafficLight. + * Cria um novo semáforo. * - * @param id The unique ID (e.g., "Cr1-N"). - * @param direction The direction (e.g., "North"). - * @param greenTime The duration of the GREEN state in seconds. - * @param redTime The duration of the RED state in seconds. + * @param id identificador único (ex: "Cr1-N") + * @param direction direção controlada (ex: "Norte") + * @param greenTime duração do estado VERDE em segundos + * @param redTime duração do estado VERMELHO em segundos */ public TrafficLight(String id, String direction, double greenTime, double redTime) { this.id = id; this.direction = direction; - this.state = TrafficLightState.RED; // All lights start RED + this.state = TrafficLightState.RED; this.queue = new LinkedList<>(); - // Initialize synchronization objects this.lock = new ReentrantLock(); this.vehicleAdded = lock.newCondition(); this.lightGreen = lock.newCondition(); @@ -128,42 +83,45 @@ public class TrafficLight { } /** - * Adds a vehicle to the *end* of the waiting queue. - * This method is thread-safe. - * - * @param vehicle The {@link Vehicle} to add. + * Coloca um veículo na fila deste semáforo. + * + * Registamos a hora de chegada para podermos calcular mais tarde quanto tempo o + * veículo esperou. + * + * @param vehicle O veículo que chega ao semáforo. */ public void addVehicle(Vehicle vehicle) { - lock.lock(); // Acquire the lock + lock.lock(); try { - queue.offer(vehicle); // Add vehicle to queue + queue.offer(vehicle); vehicleArrivalTimes.put(vehicle.getId(), System.currentTimeMillis()); - vehicleAdded.signalAll(); // Signal (for concurrent models) + vehicleAdded.signalAll(); } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } /** - * Removes and returns the {@link Vehicle} from the *front* of the queue. - * * This only succeeds if: - * 1. The light's state is {@link TrafficLightState#GREEN}. - * 2. The queue is not empty. - * * If these conditions are not met, it returns {@code null}. - * This method is thread-safe. - * - * @return The {@link Vehicle} at the front of the queue, or {@code null} - * if the light is RED or the queue is empty. + * Remove um veículo da fila para travessia. + * + *

Só remove se:

+ *
    + *
  • O semáforo estiver VERDE
  • + *
  • Existir pelo menos um veículo na fila
  • + *
+ * + *

Atualiza automaticamente as estatísticas de tempo de espera do veículo.

+ * + * @return o veículo que vai atravessar, ou null se não for possível */ public Vehicle removeVehicle() { - lock.lock(); // Acquire the lock + lock.lock(); try { if (state == TrafficLightState.GREEN && !queue.isEmpty()) { - Vehicle vehicle = queue.poll(); // Remove vehicle from queue + Vehicle vehicle = queue.poll(); if (vehicle != null) { totalVehiclesProcessed++; - - // Calculate wait time (time spent in queue) + Long arrivalTime = vehicleArrivalTimes.remove(vehicle.getId()); if (arrivalTime != null) { double waitTimeSeconds = (System.currentTimeMillis() - arrivalTime) / 1000.0; @@ -172,161 +130,138 @@ public class TrafficLight { } return vehicle; } - return null; // Light is RED or queue is empty + return null; } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } /** - * Changes the light’s state (e.g., RED -> GREEN). - * If the new state is GREEN, it signals any waiting threads - * (for a potential concurrent model). - * This method is thread-safe. - * - * @param newState The {@link TrafficLightState} to set. + * Muda o estado do semáforo. + * + * @param newState novo estado (VERDE ou VERMELHO) */ public void changeState(TrafficLightState newState) { - lock.lock(); // Acquire the lock + lock.lock(); try { this.state = newState; if (newState == TrafficLightState.GREEN) { - lightGreen.signalAll(); // Signal (for concurrent models) + lightGreen.signalAll(); } } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } /** - * Returns how many vehicles are currently in the queue. - * This method is thread-safe. - * * @return The size of the queue. + * Retorna quantos veículos estão atualmente na fila. + * Método thread-safe. + * + * @return tamanho da fila */ public int getQueueSize() { - lock.lock(); // Acquire the lock + lock.lock(); try { return queue.size(); } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } /** - * Checks whether the queue is empty. - * This method is thread-safe. + * Verifica se a fila está vazia. + * Método thread-safe. * - * @return {@code true} if the queue has no vehicles, {@code false} otherwise. + * @return {@code true} se não houver veículos, {@code false} caso contrário */ public boolean isQueueEmpty() { - lock.lock(); // Acquire the lock + lock.lock(); try { return queue.isEmpty(); } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } - // --- Getters & Setters --- - - /** - * @return The unique ID of this light (e.g., "Cr1-N"). - */ + /** @return identificador único do semáforo */ public String getId() { return id; } - /** - * @return The direction this light controls (e.g., "North"). - */ + /** @return direção controlada por este semáforo */ public String getDirection() { return direction; } /** - * Gets the current state of the light (GREEN or RED). - * This method is thread-safe. + * Obtém o estado atual do semáforo. + * Método thread-safe. * - * @return The current {@link TrafficLightState}. + * @return estado atual (VERDE ou VERMELHO) */ public TrafficLightState getState() { - lock.lock(); // Acquire the lock + lock.lock(); try { return state; } finally { - lock.unlock(); // Always release the lock + lock.unlock(); } } - /** - * @return The configured GREEN light duration in seconds. - */ + /** @return duração configurada do sinal verde em segundos */ public double getGreenTime() { return greenTime; } /** - * Sets the GREEN light duration. - * @param greenTime The new duration in seconds. + * Define a duração do sinal verde. + * + * @param greenTime nova duração em segundos */ public void setGreenTime(double greenTime) { this.greenTime = greenTime; } - /** - * @return The configured RED light duration in seconds. - */ + /** @return duração configurada do sinal vermelho em segundos */ public double getRedTime() { return redTime; } /** - * Sets the RED light duration. - * @param redTime The new duration in seconds. + * Define a duração do sinal vermelho. + * + * @param redTime nova duração em segundos */ public void setRedTime(double redTime) { this.redTime = redTime; } - /** - * @return The total number of vehicles processed (dequeued) by this light. - */ + /** @return número total de veículos processados por este semáforo */ public int getTotalVehiclesProcessed() { - // Note: This read is not locked, assuming it's okay - // for it to be "eventually consistent" for stats. - // For strict accuracy, it should also be locked. return totalVehiclesProcessed; } - /** - * @return The {@link Lock} object for advanced synchronization. - */ + /** @return objeto {@link Lock} para sincronização avançada */ public Lock getLock() { return lock; } - /** - * @return The {@link Condition} for vehicle additions. - */ + /** @return condição para adição de veículos */ public Condition getVehicleAdded() { return vehicleAdded; } - /** - * @return The {@link Condition} for the light turning green. - */ + /** @return condição para semáforo ficar verde */ public Condition getLightGreen() { return lightGreen; } - /** - * @return A string summary of the light's current state. - */ + /** @return representação textual do estado atual do semáforo */ @Override public String toString() { return String.format( - "TrafficLight{id='%s', direction='%s', state=%s, queueSize=%d}", - id, direction, getState(), getQueueSize() // Use getters for thread-safety + "TrafficLight{id='%s', direction='%s', state=%s, queueSize=%d}", + id, direction, getState(), getQueueSize() ); } } \ No newline at end of file diff --git a/main/src/main/java/sd/model/TrafficLightState.java b/main/src/main/java/sd/model/TrafficLightState.java index c21d6e4..aab7682 100644 --- a/main/src/main/java/sd/model/TrafficLightState.java +++ b/main/src/main/java/sd/model/TrafficLightState.java @@ -1,17 +1,13 @@ package sd.model; /** - * Enumeration representing the two possible states of a {@link TrafficLight}. + * Estados possíveis de um semáforo ({@link TrafficLight}). */ public enum TrafficLightState { - /** - * The light is GREEN, allowing vehicles to pass (be dequeued). - */ + /** Sinal verde - veículos podem passar */ GREEN, - /** - * The light is RED, blocking vehicles (they remain in the queue). - */ + /** Sinal vermelho - veículos aguardam na fila */ RED } \ 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 2ee7d23..c3e79c2 100644 --- a/main/src/main/java/sd/model/Vehicle.java +++ b/main/src/main/java/sd/model/Vehicle.java @@ -5,94 +5,74 @@ import java.util.ArrayList; import java.util.List; /** - * Represents a single vehicle moving through the simulation. - * - * This class is a data object that holds the state of a vehicle, including: - * - Its unique ID, type, and entry time. - * - 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 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). + * Representa um veículo que se move pela rede de interseções. + * + *

Esta classe é o "gémeo digital" de um carro, mota ou camião. + * Mantém toda a informação necessária:

+ *
    + *
  • Identificação e tipo do veículo
  • + *
  • Rota completa a percorrer
  • + *
  • Métricas de tempo (espera, travessia, total)
  • + *
+ * + *

O objeto é serializado e enviado pela rede à medida que o veículo + * se move entre processos distribuídos.

*/ public class Vehicle implements Serializable { private static final long serialVersionUID = 1L; - // --- Identity and configuration --- - - /** - * Unique identifier for the vehicle (e.g., "V1", "V2"). - */ + /** Identificador único do veículo (ex: "V1", "V2") */ private final String id; - /** - * The type of vehicle (BIKE, LIGHT, HEAVY). - */ + /** Tipo de veículo (BIKE, LIGHT, HEAVY) */ private final VehicleType type; - /** - * The simulation time (in seconds) when the vehicle was generated. - */ + /** Tempo de simulação (em segundos) em que o veículo foi gerado */ private final double entryTime; /** - * The complete, ordered list of destinations (intersection IDs and the - * final exit "S"). Example: ["Cr1", "Cr3", "S"]. + * Lista ordenada completa de destinos (IDs de interseções e saída "S"). + * Exemplo: ["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* - * destination (i.e., the one it is traveling *towards* or *arriving at*). + * Índice que acompanha o progresso do veículo ao longo da {@link #route}. + * {@code route.get(currentRouteIndex)} é o destino *atual* do veículo. */ private int currentRouteIndex; - // --- Metrics --- - - /** - * The total accumulated time (in seconds) this vehicle has spent - * waiting at red lights. - */ + /** Tempo total acumulado (segundos) que o veículo passou à espera em semáforos vermelhos */ private double totalWaitingTime; - /** - * The total accumulated time (in seconds) this vehicle has spent - * actively crossing intersections. - */ + /** Tempo total acumulado (segundos) que o veículo passou a atravessar interseções */ private double totalCrossingTime; /** - * Constructs a new Vehicle. - * - * @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"]). + * Cria um novo veículo pronto para se fazer à estrada. + * + * @param id Identificador único (ex: "V1"). + * @param type O tipo de veículo (determina velocidade/tamanho). + * @param entryTime Quando este veículo entrou na simulação (segundos). + * @param route A lista ordenada de paragens (Interseções -> Saída). */ public Vehicle(String id, VehicleType type, double entryTime, List route) { this.id = id; this.type = type; this.entryTime = entryTime; - // Create a copy of the route list to ensure immutability this.route = new ArrayList<>(route); - this.currentRouteIndex = 0; // Starts at the first destination + this.currentRouteIndex = 0; this.totalWaitingTime = 0.0; this.totalCrossingTime = 0.0; } /** - * Advances the vehicle to the next stop in its route by - * incrementing the {@link #currentRouteIndex}. - * * This is typically called *after* a vehicle *arrives* at an intersection, - * 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. + * Move o GPS interno do veículo para o próximo destino. + * + * Chame isto quando um veículo chega a uma interseção para atualizar para onde + * deve ir a seguir. + * + * @return true se houver mais paragens, false se a viagem terminou. */ public boolean advanceRoute() { currentRouteIndex++; @@ -100,116 +80,89 @@ public class Vehicle implements Serializable { } /** - * Gets the current destination (the next intersection or exit) that - * the vehicle is heading towards. + * Obtém o destino atual (próxima interseção ou saída) para onde o veículo se dirige. * - * @return The ID of the current destination (e.g., "Cr1"), or - * {@code null} if the route is complete. + * @return ID do destino atual (ex: "Cr1"), ou {@code null} se a rota terminou */ public String getCurrentDestination() { return (currentRouteIndex < route.size()) ? route.get(currentRouteIndex) : null; } /** - * Checks if the vehicle has completed its entire route. + * Verifica se o veículo completou toda a sua rota. * - * @return {@code true} if the route index is at or past the end - * of the route list, {@code false} otherwise. + * @return {@code true} se chegou ao fim da rota, {@code false} caso contrário */ public boolean hasReachedEnd() { return currentRouteIndex >= route.size(); } - // --- Getters and metrics management --- - - /** - * @return The vehicle's unique ID. - */ + /** @return identificador único do veículo */ public String getId() { return id; } - /** - * @return The vehicle's {@link VehicleType}. - */ + /** @return tipo do veículo */ public VehicleType getType() { return type; } - /** - * @return The simulation time when the vehicle entered the system. - */ + /** @return tempo de simulação em que o veículo entrou no sistema */ public double getEntryTime() { return entryTime; } - /** - * @return A *copy* of the vehicle's complete route. - */ + /** @return cópia da rota completa do veículo */ public List getRoute() { - // Return a copy to prevent external modification return new ArrayList<>(route); } - /** - * @return The current index pointing to the vehicle's destination in its route - * list. - */ + /** @return índice atual apontando para o destino do veículo na sua rota */ public int getCurrentRouteIndex() { return currentRouteIndex; } - /** - * @return The total accumulated waiting time in seconds. - */ + /** @return tempo total acumulado de espera em segundos */ public double getTotalWaitingTime() { return totalWaitingTime; } /** - * Adds a duration to the vehicle's total waiting time. - * This is called by the simulation engine when a vehicle - * starts crossing an intersection. + * Adiciona uma duração ao tempo total de espera do veículo. + * Chamado quando um veículo começa a atravessar uma interseção. * - * @param time The duration (in seconds) to add. + * @param time duração (em segundos) a adicionar */ public void addWaitingTime(double time) { totalWaitingTime += time; } - /** - * @return The total accumulated crossing time in seconds. - */ + /** @return tempo total acumulado de travessia em segundos */ public double getTotalCrossingTime() { return totalCrossingTime; } /** - * Adds a duration to the vehicle's total crossing time. - * This is called by the simulation engine when a vehicle - * finishes crossing an intersection. + * Adiciona uma duração ao tempo total de travessia do veículo. + * Chamado quando um veículo termina de atravessar uma interseção. * - * @param time The duration (in seconds) to add. + * @param time duração (em segundos) a adicionar */ public void addCrossingTime(double time) { totalCrossingTime += time; } /** - * Calculates the vehicle's total time spent in the system so far. - * This is a "live" calculation. + * Calcula o tempo total que o veículo passou no sistema até agora. * - * @param currentTime The current simulation time. - * @return The total elapsed time (in seconds) since the vehicle - * was generated ({@code currentTime - entryTime}). + * @param currentTime tempo atual da simulação + * @return tempo total decorrido (em segundos) desde que o veículo foi gerado */ public double getTotalTravelTime(double currentTime) { return currentTime - entryTime; } - /** - * @return A string summary of the vehicle's current state. - */ + /** @return representação textual do estado atual do veículo */ @Override public String toString() { return String.format( diff --git a/main/src/main/java/sd/model/VehicleType.java b/main/src/main/java/sd/model/VehicleType.java index fce00d0..e3aa195 100644 --- a/main/src/main/java/sd/model/VehicleType.java +++ b/main/src/main/java/sd/model/VehicleType.java @@ -1,27 +1,19 @@ package sd.model; /** - * Enumeration representing the different types of vehicles in the simulation. - * Each type can have different properties, such as crossing time - * and generation probability, defined in {@link sd.config.SimulationConfig}. + * Enumeração dos diferentes tipos de veículos na simulação. + * + *

Cada tipo pode ter propriedades diferentes como tempo de travessia + * e probabilidade de geração, definidas na {@link sd.config.SimulationConfig}.

*/ public enum VehicleType { - /** - * A bike or motorcycle. - * Typically has a short crossing time. - */ + /** Bicicleta ou motocicleta - tempo de travessia curto */ BIKE, - /** - * A standard light vehicle, such as a car. - * This is usually the most common type. - */ + /** Veículo ligeiro padrão (carro) - tipo mais comum */ LIGHT, - /** - * A heavy vehicle, such as a truck or bus. - * Typically has a long crossing time. - */ + /** Veículo pesado (camião ou autocarro) - tempo de travessia longo */ HEAVY } \ No newline at end of file diff --git a/main/src/main/java/sd/protocol/MessageProtocol.java b/main/src/main/java/sd/protocol/MessageProtocol.java index 47975be..715bfc7 100644 --- a/main/src/main/java/sd/protocol/MessageProtocol.java +++ b/main/src/main/java/sd/protocol/MessageProtocol.java @@ -1,41 +1,45 @@ package sd.protocol; import java.io.Serializable; + import sd.model.MessageType; // Assuming MessageType is in sd.model or sd.protocol /** - * Interface defining the contract for all messages exchanged in the simulator. - * Ensures that any message can be identified and routed. - * * This interface extends Serializable to allow objects that implement it - * to be sent over Sockets (ObjectOutputStream). - * + * Contrato para todas as mensagens trocadas no simulador. + * + *

Garante que mensagens podem ser identificadas e encaminhadas. + * Extende Serializable para permitir envio via sockets. */ public interface MessageProtocol extends Serializable { /** - * Returns the type of the message, indicating its purpose. - * @return The MessageType (e.g., VEHICLE_TRANSFER, STATS_UPDATE). + * Tipo da mensagem, indicando o seu propósito. + * @return tipo (ex: VEHICLE_TRANSFER, STATS_UPDATE) */ MessageType getType(); /** - * Returns the data object (payload) that this message carries. - * The type of object will depend on the MessageType. - * * - If getType() == VEHICLE_TRANSFER, the payload will be a {@link sd.model.Vehicle} object. - * - If getType() == STATS_UPDATE, the payload will be a statistics object. - * * @return The data object (payload), which must also be Serializable. + * Dados (payload) que esta mensagem transporta. + * + *

Tipo depende do MessageType: + *

    + *
  • VEHICLE_TRANSFER → objeto Vehicle + *
  • STATS_UPDATE → objeto de estatísticas + *
+ * + * @return payload (deve ser Serializable) */ Object getPayload(); /** - * Returns the ID of the node (Process) that sent this message. - * @return String (e.g., "Cr1", "Cr5", "S"). + * ID do nó (processo) que enviou a mensagem. + * @return ID de origem (ex: "Cr1", "Cr5", "S") */ String getSourceNode(); /** - * Returns the ID of the destination node (Process) for this message. - * @return String (e.g., "Cr2", "DashboardServer"). + * ID do nó de destino. + * @return ID de destino (ex: "Cr2", "DashboardServer") */ String getDestinationNode(); } \ No newline at end of file diff --git a/main/src/main/java/sd/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java index c65680b..90801b0 100644 --- a/main/src/main/java/sd/protocol/SocketConnection.java +++ b/main/src/main/java/sd/protocol/SocketConnection.java @@ -18,8 +18,8 @@ import sd.serialization.SerializerFactory; /** - * Wrapper class that simplifies communication via Sockets. - * Includes connection retry logic for robustness. + * Simplifica comunicação via sockets. + * Inclui lógica de retry para robustez. */ public class SocketConnection implements Closeable { @@ -28,22 +28,20 @@ public class SocketConnection implements Closeable { private final InputStream inputStream; private final MessageSerializer serializer; - // --- Configuration for Retry Logic --- - /** Maximum number of connection attempts. */ + /** Número máximo de tentativas de ligação */ private static final int MAX_RETRIES = 5; - /** Delay between retry attempts in milliseconds. */ + /** Atraso entre tentativas (milissegundos) */ private static final long RETRY_DELAY_MS = 1000; /** - * Constructor for the "Client" (who initiates the connection). - * Tries to connect to a process that is already listening (Server). - * Includes retry logic in case of initial connection failure. + * Construtor do cliente que inicia a ligação. + * Tenta ligar a um servidor já em escuta, com retry. * - * @param host The host address (e.g., "localhost" from your simulation.properties) - * @param port The port (e.g., 8001 from your simulation.properties) - * @throws IOException If connection fails after all retries. - * @throws UnknownHostException If the host is not found (this error usually doesn't need retry). - * @throws InterruptedException If the thread is interrupted while waiting between retries. + * @param host endereço do host (ex: "localhost") + * @param port número da porta + * @throws IOException se falhar após todas as tentativas + * @throws UnknownHostException se o host não for encontrado + * @throws InterruptedException se a thread for interrompida */ public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException { Socket tempSocket = null; diff --git a/main/src/main/java/sd/util/RandomGenerator.java b/main/src/main/java/sd/util/RandomGenerator.java index f1122d5..5b9c65c 100644 --- a/main/src/main/java/sd/util/RandomGenerator.java +++ b/main/src/main/java/sd/util/RandomGenerator.java @@ -3,84 +3,82 @@ package sd.util; import java.util.Random; /** - * Utility class for generating random values used throughout the simulation. - * * Provides static methods for: - * - Generating exponentially distributed intervals (for Poisson processes). - * - Generating random integers and doubles in a range. - * - Making decisions based on probability. - * - Choosing random elements from an array. - * * It uses a single, static {@link Random} instance. + * Utilitário para gerar valores aleatórios usados na simulação. + * + *

Fornece métodos estáticos para:

+ *
    + *
  • Gerar intervalos exponencialmente distribuídos (processos de Poisson)
  • + *
  • Gerar inteiros e doubles aleatórios num intervalo
  • + *
  • Tomar decisões baseadas em probabilidade
  • + *
  • Escolher elementos aleatórios de um array
  • + *
+ * + *

Usa uma única instância estática de {@link Random}.

*/ public class RandomGenerator { - /** - * The single, shared Random instance for the entire simulation. - */ + /** Instância partilhada de Random para toda a simulação */ private static final Random random = new Random(); /** - * Returns a random time interval that follows an exponential distribution. - * * This is a key component for modeling a Poisson process, where the - * *inter-arrival times* (time between events) are exponentially distributed. - * The formula used is the inverse transform sampling method: - * {@code Time = -ln(1 - U) / λ} - * where U is a uniform random number [0, 1) and λ (lambda) is the - * average arrival rate. + * Retorna um intervalo de tempo que segue uma distribuição exponencial. + * + *

Componente essencial para modelar processos de Poisson, onde os + * tempos entre chegadas seguem uma distribuição exponencial.

+ * + *

Fórmula: {@code Time = -ln(1 - U) / λ}
+ * onde U é um número aleatório uniforme [0, 1) e λ (lambda) é a taxa média de chegada.

* - * @param lambda The average arrival rate (λ) (e.g., 0.5 vehicles per second). - * @return The time interval (in seconds) until the next arrival. + * @param lambda taxa média de chegada λ (ex: 0.5 veículos por segundo) + * @return intervalo de tempo (segundos) até à próxima chegada */ public static double generateExponentialInterval(double lambda) { - // Math.log is the natural logarithm (ln) - // random.nextDouble() returns a value in [0.0, 1.0) return Math.log(1 - random.nextDouble()) / -lambda; } /** - * Returns a random integer between {@code min} and {@code max}, inclusive. + * Retorna um inteiro aleatório entre {@code min} e {@code max}, inclusive. * - * @param min The minimum possible value. - * @param max The maximum possible value. - * @return A random integer in the range [min, max]. + * @param min valor mínimo possível + * @param max valor máximo possível + * @return inteiro aleatório no intervalo [min, max] */ public static int generateRandomInt(int min, int max) { - // random.nextInt(N) returns a value from 0 to N-1 - // (max - min + 1) is the total number of integers in the range - // + min offsets the range return random.nextInt(max - min + 1) + min; } /** - * Returns a random double between {@code min} (inclusive) and {@code max} (exclusive). + * Retorna um double aleatório entre {@code min} (inclusive) e {@code max} (exclusivo). * - * @param min The minimum possible value. - * @param max The maximum possible value. - * @return A random double in the range [min, max). + * @param min valor mínimo possível + * @param max valor máximo possível + * @return double aleatório no intervalo [min, max) */ public static double generateRandomDouble(double min, double max) { return min + (max - min) * random.nextDouble(); } /** - * Returns {@code true} with a given probability. - * * This is useful for making weighted decisions. For example, - * {@code occursWithProbability(0.3)} will return {@code true} - * approximately 30% of the time. + * Retorna {@code true} com uma dada probabilidade. + * + *

Útil para tomar decisões ponderadas. Por exemplo, + * {@code occursWithProbability(0.3)} retorna {@code true} + * aproximadamente 30% das vezes.

* - * @param probability A value between 0.0 (never) and 1.0 (always). - * @return {@code true} or {@code false}, based on the probability. + * @param probability valor entre 0.0 (nunca) e 1.0 (sempre) + * @return {@code true} ou {@code false}, baseado na probabilidade */ public static boolean occursWithProbability(double probability) { return random.nextDouble() < probability; } /** - * Picks a random element from the given array. + * Escolhe um elemento aleatório do array fornecido. * - * @param The generic type of the array. - * @param array The array to choose from. - * @return A randomly selected element from the array. - * @throws IllegalArgumentException if the array is null or empty. + * @param tipo genérico do array + * @param array array de onde escolher + * @return elemento selecionado aleatoriamente + * @throws IllegalArgumentException se o array for null ou vazio */ public static T chooseRandom(T[] array) { if (array == null || array.length == 0) { @@ -90,12 +88,13 @@ public class RandomGenerator { } /** - * Sets the seed of the shared random number generator. - * This is extremely useful for debugging and testing, as it allows - * the simulation to be run multiple times with the *exact same* - * sequence of "random" events, making the results reproducible. + * Define a seed do gerador de números aleatórios partilhado. + * + *

Extremamente útil para debugging e testes, pois permite executar + * a simulação múltiplas vezes com a mesma sequência de eventos "aleatórios", + * tornando os resultados reproduzíveis.

* - * @param seed The seed to use. + * @param seed seed a usar */ public static void setSeed(long seed) { random.setSeed(seed); diff --git a/main/src/main/java/sd/util/VehicleGenerator.java b/main/src/main/java/sd/util/VehicleGenerator.java index c6c7611..dbd987f 100644 --- a/main/src/main/java/sd/util/VehicleGenerator.java +++ b/main/src/main/java/sd/util/VehicleGenerator.java @@ -9,37 +9,38 @@ import sd.model.Vehicle; import sd.model.VehicleType; /** - * Generates vehicles for the simulation. - * * This class is responsible for two key tasks: - * 1. Determining *when* the next vehicle should arrive, based on the - * arrival model (POISSON or FIXED) from the {@link SimulationConfig}. - * 2. Creating a new {@link Vehicle} object with a randomly selected - * type (e.g., BIKE, LIGHT) and a randomly selected route. - * * Routes are predefined and organized by entry point (E1, E2, E3). + * Gera veículos para a simulação. + * + *

Esta classe é responsável por duas tarefas principais:

+ *
    + *
  1. Determinar quando o próximo veículo deve chegar, baseado no + * modelo de chegada (POISSON ou FIXED) da {@link SimulationConfig}
  2. + *
  3. Criar um novo objeto {@link Vehicle} com tipo e rota selecionados aleatoriamente
  4. + *
+ * + *

As rotas são predefinidas e organizadas por ponto de entrada (E1, E2, E3).

*/ public class VehicleGenerator { private final SimulationConfig config; private final String arrivalModel; - private final double arrivalRate; // Lambda (λ) for POISSON - private final double fixedInterval; // Interval for FIXED + /** Lambda (λ) para modelo POISSON */ + private final double arrivalRate; + /** Intervalo para modelo FIXED */ + private final double fixedInterval; - // --- Predefined Routes --- - // These lists store all possible routes, grouped by where they start. - - /** Routes starting from entry point E1. */ + /** Rotas possíveis a partir do ponto de entrada E1 */ private final List e1Routes; - /** Routes starting from entry point E2. */ + /** Rotas possíveis a partir do ponto de entrada E2 */ private final List e2Routes; - /** Routes starting from entry point E3. */ + /** Rotas possíveis a partir do ponto de entrada E3 */ private final List e3Routes; /** - * Constructs a new VehicleGenerator. - * It reads the necessary configuration and initializes the - * predefined routes. + * Cria um novo gerador de veículos. + * Lê a configuração necessária e inicializa as rotas predefinidas. * - * @param config The {@link SimulationConfig} object. + * @param config objeto de {@link SimulationConfig} */ public VehicleGenerator(SimulationConfig config) { this.config = config; @@ -57,64 +58,62 @@ public class VehicleGenerator { } /** - * Defines all possible routes that vehicles can take, organized by - * their entry point (E1, E2, E3). Each route is given a - * probability, which determines how often it's chosen. + * Define todas as rotas possíveis que os veículos podem tomar. + * As rotas são organizadas por ponto de entrada (E1, E2, E3). + * Cada rota tem uma probabilidade que determina a frequência com que é escolhida. */ private void initializePossibleRoutes() { - // E1 routes (Starts at Cr1) e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34)); // E1 -> Cr1 -> Cr4 -> Cr5 -> Exit + Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34)); e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33)); // E1 -> Cr1 -> Cr2 -> Cr5 -> Exit + Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33)); e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33)); // E1 -> Cr1 -> Cr2 -> Cr3 -> Exit + Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33)); - // E2 routes (Starts at Cr2) e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr5", "S"), 0.34)); // E2 -> Cr2 -> Cr5 -> Exit + Arrays.asList("Cr2", "Cr5", "S"), 0.34)); e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr3", "S"), 0.33)); // E2 -> Cr2 -> Cr3 -> Exit + Arrays.asList("Cr2", "Cr3", "S"), 0.33)); e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); // E2 -> Cr2 -> ... -> Exit + Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); - // E3 routes (Starts at Cr3) e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "S"), 0.34)); // E3 -> Cr3 -> Exit + Arrays.asList("Cr3", "S"), 0.34)); e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33)); // E3 -> Cr3 -> Cr2 -> Cr5 -> Exit + Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33)); e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); // E3 -> Cr3 -> ... -> Exit + Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); } /** - * Calculates the *absolute* time of the next vehicle arrival - * based on the configured model. - * * @param currentTime The current simulation time, used as the base. - * @return The absolute time (e.g., {@code currentTime + interval}) - * when the next vehicle should be generated. + * Calcula o tempo absoluto da próxima chegada de veículo + * baseado no modelo configurado. + * + * @param currentTime tempo atual da simulação, usado como base + * @return tempo absoluto (ex: {@code currentTime + intervalo}) + * em que o próximo veículo deve ser gerado */ public double getNextArrivalTime(double currentTime) { if ("POISSON".equalsIgnoreCase(arrivalModel)) { - // For a Poisson process, the time *between* arrivals - // follows an exponential distribution. double interval = RandomGenerator.generateExponentialInterval(arrivalRate); return currentTime + interval; } else { - // For a Fixed model, the interval is constant. return currentTime + fixedInterval; } } /** - * Generates a new {@link Vehicle} object. - * This involves: - * 1. Selecting a random {@link VehicleType} based on probabilities. - * 2. Selecting a random route (entry point + path) based on probabilities. + * Gera um novo objeto {@link Vehicle}. + * + *

Passos executados:

+ *
    + *
  1. Seleciona um {@link VehicleType} aleatório baseado em probabilidades
  2. + *
  3. Seleciona uma rota aleatória (ponto de entrada + caminho)
  4. + *
* - * @param vehicleId The unique identifier for the new vehicle (e.g., "V123"). - * @param entryTime The simulation time when this vehicle is being created. - * @return A new, configured {@link Vehicle} object. + * @param vehicleId identificador único do novo veículo (ex: "V123") + * @param entryTime tempo de simulação em que o veículo é criado + * @return novo objeto {@link Vehicle} configurado */ public Vehicle generateVehicle(String vehicleId, double entryTime) { VehicleType type = selectVehicleType(); @@ -124,22 +123,24 @@ public class VehicleGenerator { } /** - * Selects a {@link VehicleType} (BIKE, LIGHT, HEAVY) based on the - * probabilities defined in the {@link SimulationConfig}. - * * Uses a standard "cumulative probability" technique: - * 1. Get a random number {@code rand} from [0, 1). - * 2. If {@code rand < P(Bike)}, return BIKE. - * 3. Else if {@code rand < P(Bike) + P(Light)}, return LIGHT. - * 4. Else, return HEAVY. + * Seleciona um {@link VehicleType} (BIKE, LIGHT, HEAVY) baseado nas + * probabilidades definidas na {@link SimulationConfig}. + * + *

Usa técnica de "probabilidade cumulativa":

+ *
    + *
  1. Obtém número aleatório {@code rand} de [0, 1)
  2. + *
  3. Se {@code rand < P(Bike)}, retorna BIKE
  4. + *
  5. Senão se {@code rand < P(Bike) + P(Light)}, retorna LIGHT
  6. + *
  7. Caso contrário, retorna HEAVY
  8. + *
* - * @return The selected {@link VehicleType}. + * @return tipo de veículo selecionado */ private VehicleType selectVehicleType() { double bikeProbability = config.getBikeVehicleProbability(); double lightProbability = config.getLightVehicleProbability(); double heavyProbability = config.getHeavyVehicleProbability(); - // Normalize probabilities in case they don't sum to exactly 1.0 double total = bikeProbability + lightProbability + heavyProbability; if (total == 0) return VehicleType.LIGHT; // Avoid division by zero bikeProbability /= total; diff --git a/main/src/main/resources/network_config.json b/main/src/main/resources/network_config.json index 8bbd50a..4e8df68 100644 --- a/main/src/main/resources/network_config.json +++ b/main/src/main/resources/network_config.json @@ -27,15 +27,18 @@ }, { "id": "Cr4", - "lights": ["East"], + "lights": ["East", "West"], "routes": { + "Cr1": "North", "Cr5": "East" } }, { "id": "Cr5", - "lights": ["East"], + "lights": ["East", "West", "North"], "routes": { + "Cr2": "North", + "Cr4": "West", "S": "East" } } diff --git a/main/src/main/resources/simulation-high.properties b/main/src/main/resources/simulation-high.properties new file mode 100644 index 0000000..202d3eb --- /dev/null +++ b/main/src/main/resources/simulation-high.properties @@ -0,0 +1,117 @@ +# ========================================================= +# Traffic Simulation Configuration - HIGH LOAD SCENARIO +# --------------------------------------------------------- +# High traffic scenario for testing system under heavy load. +# Expected: Significant congestion, large queues, system stress test +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): HIGH LOAD = 1.0 vehicle per second (60 vehicles/minute, 3600 vehicles/hour) +# This is 2x medium load - tests system capacity limits +simulation.arrival.rate=1.0 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=1.0 + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Aggressive timings to maximize throughput under high load + +# Intersection 1 (Entry point - longer greens to prevent early backup) +trafficlight.Cr1.South.green=60.0 +trafficlight.Cr1.South.red=3.0 +trafficlight.Cr1.East.green=60.0 +trafficlight.Cr1.East.red=3.0 + +# Intersection 2 (Main hub - CRITICAL BOTTLENECK, maximum green times) +# This is the most critical intersection - all routes converge here +trafficlight.Cr2.South.green=70.0 +trafficlight.Cr2.South.red=3.0 +trafficlight.Cr2.East.green=80.0 +trafficlight.Cr2.East.red=3.0 +trafficlight.Cr2.West.green=70.0 +trafficlight.Cr2.West.red=3.0 + +# Intersection 3 (Path to exit - maximize East throughput to exit) +trafficlight.Cr3.South.green=50.0 +trafficlight.Cr3.South.red=3.0 +trafficlight.Cr3.West.green=40.0 +trafficlight.Cr3.West.red=3.0 + +# Intersection 4 (High throughput needed toward Cr5) +trafficlight.Cr4.East.green=70.0 +trafficlight.Cr4.East.red=3.0 + +# Intersection 5 (Near exit - MAJOR BOTTLENECK, longest green time) +# All routes funnel through here before exit +trafficlight.Cr5.East.green=90.0 +trafficlight.Cr5.East.red=3.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 × car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - HIGH LOAD === +# - Average system time: 200-400+ seconds (3-7+ minutes) +# - Maximum queue sizes: 15-30+ vehicles at Cr2 and Cr5 +# - Average queue sizes: 8-15+ vehicles +# - Severe congestion at Cr2 (main convergence point) +# - Severe congestion at Cr5 (pre-exit bottleneck) +# - System utilization: ~80-95% +# - Many vehicles will remain in system at simulation end +# - Queue growth may be unbounded if arrival rate exceeds service rate +# - Primary bottlenecks: Cr2 (3-way convergence) and Cr5 (final funnel) +# - This scenario tests maximum system capacity and traffic light optimization +# - Expected to demonstrate need for adaptive traffic light policies diff --git a/main/src/main/resources/simulation-low.properties b/main/src/main/resources/simulation-low.properties new file mode 100644 index 0000000..09d4a77 --- /dev/null +++ b/main/src/main/resources/simulation-low.properties @@ -0,0 +1,111 @@ +# ========================================================= +# Traffic Simulation Configuration - LOW LOAD SCENARIO +# --------------------------------------------------------- +# Low traffic scenario for testing system under light load. +# Expected: No congestion, minimal queues, fast vehicle throughput +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): LOW LOAD = 0.2 vehicles per second (12 vehicles/minute, 720 vehicles/hour) +# This is approximately 40% of medium load +simulation.arrival.rate=0.2 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=5.0 + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Standard timings - should be more than adequate for low load + +# Intersection 1 (Entry point - balanced) +trafficlight.Cr1.South.green=30.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=30.0 +trafficlight.Cr1.East.red=5.0 + +# Intersection 2 (Main hub - shorter cycles, favor East-West) +trafficlight.Cr2.South.green=30.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=30.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=30.0 +trafficlight.Cr2.West.red=5.0 + +# Intersection 3 (Path to exit - favor East) +trafficlight.Cr3.South.green=30.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=30.0 +trafficlight.Cr3.West.red=5.0 + +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.East.green=30.0 +trafficlight.Cr4.East.red=5.0 + +# Intersection 5 (Near exit - favor East) +trafficlight.Cr5.East.green=30.0 +trafficlight.Cr5.East.red=5.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 × car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - LOW LOAD === +# - Average system time: 40-80 seconds +# - Maximum queue sizes: 1-3 vehicles +# - Average queue sizes: < 1 vehicle +# - Vehicles should flow smoothly through the system +# - Minimal waiting at traffic lights (mostly travel time) +# - System utilization: ~20-30% +# - All vehicles should exit within simulation time diff --git a/main/src/main/resources/simulation-medium.properties b/main/src/main/resources/simulation-medium.properties new file mode 100644 index 0000000..844a384 --- /dev/null +++ b/main/src/main/resources/simulation-medium.properties @@ -0,0 +1,112 @@ +# ========================================================= +# Traffic Simulation Configuration - MEDIUM LOAD SCENARIO +# --------------------------------------------------------- +# Medium traffic scenario for testing system under normal load. +# Expected: Moderate queues, some congestion at peak intersections +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): MEDIUM LOAD = 0.5 vehicles per second (30 vehicles/minute, 1800 vehicles/hour) +# This represents normal traffic conditions +simulation.arrival.rate=0.5 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=2.0 + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Optimized timings for medium load + +# Intersection 1 (Entry point - balanced) +trafficlight.Cr1.South.green=40.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=40.0 +trafficlight.Cr1.East.red=5.0 + +# Intersection 2 (Main hub - CRITICAL BOTTLENECK, longer green times) +trafficlight.Cr2.South.green=45.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=50.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=45.0 +trafficlight.Cr2.West.red=5.0 + +# Intersection 3 (Path to exit - favor East toward exit) +trafficlight.Cr3.South.green=40.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=35.0 +trafficlight.Cr3.West.red=5.0 + +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.East.green=40.0 +trafficlight.Cr4.East.red=5.0 + +# Intersection 5 (Near exit - POTENTIAL BOTTLENECK, longer green) +trafficlight.Cr5.East.green=50.0 +trafficlight.Cr5.East.red=5.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 × car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - MEDIUM LOAD === +# - Average system time: 80-150 seconds +# - Maximum queue sizes: 5-10 vehicles at Cr2 and Cr5 +# - Average queue sizes: 2-5 vehicles +# - Moderate congestion at Cr2 (main hub) and Cr5 (pre-exit) +# - System utilization: ~50-60% +# - Most vehicles should exit, some may remain at simulation end +# - Cr2 is the primary bottleneck (3 directions converge) +# - Cr5 is secondary bottleneck (all routes pass through) diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties index 9ac81cc..31ce0f8 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -31,7 +31,11 @@ dashboard.port=9000 # === SIMULATION CONFIGURATION === # Total duration in seconds (3600 = 1 hour) -simulation.duration=3600 +simulation.duration=300 + +# Time scaling factor for visualization (real_seconds = sim_seconds * scale) +# 0 = instant (pure DES), 0.01 = 100x speed, 0.1 = 10x speed, 1.0 = real-time +simulation.time.scale=0.01 # Vehicle arrival model: FIXED or POISSON simulation.arrival.model=POISSON diff --git a/main/src/test/java/sd/des/DESComponentsTest.java b/main/src/test/java/sd/des/DESComponentsTest.java new file mode 100644 index 0000000..ea55b2d --- /dev/null +++ b/main/src/test/java/sd/des/DESComponentsTest.java @@ -0,0 +1,174 @@ +package sd.des; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import static org.junit.jupiter.api.Assertions.*; + +import sd.model.TrafficLight; +import sd.model.TrafficLightState; + +/** + * Unit tests for DES (Discrete Event Simulation) components. + * Tests the core infrastructure: SimulationClock, EventQueue, SimulationEvent. + */ +public class DESComponentsTest { + + private SimulationClock clock; + private EventQueue eventQueue; + + @BeforeEach + public void setUp() { + clock = new SimulationClock(); + eventQueue = new EventQueue(false); // Don't track history for basic tests + } + + @Test + public void testSimulationClockInitialization() { + assertEquals(0.0, clock.getCurrentTime(), 0.001, "Clock should start at time 0"); + assertEquals(0.0, clock.getElapsedTime(), 0.001, "Elapsed time should be 0 initially"); + } + + @Test + public void testSimulationClockAdvancement() { + clock.advanceTo(10.0); + assertEquals(10.0, clock.getCurrentTime(), 0.001, "Clock should advance to 10.0"); + + clock.advanceTo(25.5); + assertEquals(25.5, clock.getCurrentTime(), 0.001, "Clock should advance to 25.5"); + assertEquals(25.5, clock.getElapsedTime(), 0.001, "Elapsed time should be 25.5"); + } + + @Test + public void testSimulationClockBackwardTimePrevention() { + clock.advanceTo(20.0); + + // Attempting to go backward should throw an exception + assertThrows(IllegalArgumentException.class, () -> { + clock.advanceTo(15.0); + }, "Clock should throw exception when trying to go backward"); + + // Clock should still be at 20.0 + assertEquals(20.0, clock.getCurrentTime(), 0.001, "Clock should remain at 20.0"); + } + + @Test + public void testEventQueueOrdering() { + // Schedule events out of order + SimulationEvent event3 = new SimulationEvent(30.0, DESEventType.VEHICLE_ARRIVAL, null); + SimulationEvent event1 = new SimulationEvent(10.0, DESEventType.VEHICLE_GENERATION, null); + SimulationEvent event2 = new SimulationEvent(20.0, DESEventType.TRAFFIC_LIGHT_CHANGE, null); + + eventQueue.schedule(event3); + eventQueue.schedule(event1); + eventQueue.schedule(event2); + + // Events should come out in chronological order + SimulationEvent first = eventQueue.poll(); + assertEquals(10.0, first.getTimestamp(), 0.001, "First event should be at time 10.0"); + assertEquals(DESEventType.VEHICLE_GENERATION, first.getType()); + + SimulationEvent second = eventQueue.poll(); + assertEquals(20.0, second.getTimestamp(), 0.001, "Second event should be at time 20.0"); + assertEquals(DESEventType.TRAFFIC_LIGHT_CHANGE, second.getType()); + + SimulationEvent third = eventQueue.poll(); + assertEquals(30.0, third.getTimestamp(), 0.001, "Third event should be at time 30.0"); + assertEquals(DESEventType.VEHICLE_ARRIVAL, third.getType()); + } + + @Test + public void testEventQueueEmpty() { + assertTrue(eventQueue.isEmpty(), "New queue should be empty"); + + eventQueue.schedule(new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null)); + assertFalse(eventQueue.isEmpty(), "Queue should not be empty after scheduling"); + + eventQueue.poll(); + assertTrue(eventQueue.isEmpty(), "Queue should be empty after polling all events"); + } + + @Test + public void testEventQueueSize() { + assertEquals(0, eventQueue.size(), "New queue should have size 0"); + + eventQueue.schedule(new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null)); + eventQueue.schedule(new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null)); + assertEquals(2, eventQueue.size(), "Queue should have size 2"); + + eventQueue.poll(); + assertEquals(1, eventQueue.size(), "Queue should have size 1 after polling"); + } + + @Test + public void testSimulationEventComparison() { + SimulationEvent early = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null); + SimulationEvent late = new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null); + + assertTrue(early.compareTo(late) < 0, "Earlier event should compare less than later event"); + assertTrue(late.compareTo(early) > 0, "Later event should compare greater than earlier event"); + assertEquals(0, early.compareTo(early), "Event should compare equal to itself"); + } + + @Test + public void testSimulationEventEqualTimestamp() { + // When timestamps are equal, events are ordered by type name + SimulationEvent event1 = new SimulationEvent(5.0, DESEventType.TRAFFIC_LIGHT_CHANGE, null); + SimulationEvent event2 = new SimulationEvent(5.0, DESEventType.VEHICLE_ARRIVAL, null); + + int comparison = event1.compareTo(event2); + // TRAFFIC_LIGHT_CHANGE comes before VEHICLE_ARRIVAL alphabetically + assertTrue(comparison < 0, "When equal timestamp, should order by type name alphabetically"); + } + + @Test + public void testTrafficLightEvent() { + TrafficLight light = new TrafficLight("Cr1-N", "North", 10.0, 15.0); + light.changeState(TrafficLightState.RED); + TrafficLightEvent tlEvent = new TrafficLightEvent(light, "North", "Cr1"); + + assertEquals(light, tlEvent.getLight(), "Should return correct traffic light"); + assertEquals("North", tlEvent.getDirection(), "Should return correct direction"); + assertEquals("Cr1", tlEvent.getIntersectionId(), "Should return correct intersection ID"); + } + + @Test + public void testEventHistoryTracking() { + EventQueue historyQueue = new EventQueue(true); // Enable history tracking + + SimulationEvent event1 = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null); + SimulationEvent event2 = new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null); + + historyQueue.schedule(event1); + historyQueue.schedule(event2); + + historyQueue.poll(); + historyQueue.poll(); + + String history = historyQueue.exportEventHistory(); + assertNotNull(history, "Event history should not be null"); + assertTrue(history.contains("VEHICLE_GENERATION"), "History should contain first event type"); + assertTrue(history.contains("VEHICLE_ARRIVAL"), "History should contain second event type"); + } + + @Test + public void testEventQueuePeek() { + SimulationEvent event = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null); + eventQueue.schedule(event); + + SimulationEvent peeked = eventQueue.peek(); + assertNotNull(peeked, "Peek should return event"); + assertEquals(5.0, peeked.getTimestamp(), 0.001, "Peeked event should have correct timestamp"); + + // Queue should still have the event + assertEquals(1, eventQueue.size(), "Peek should not remove event from queue"); + } + + @Test + public void testSimulationEndEvent() { + SimulationEvent endEvent = new SimulationEvent(100.0, DESEventType.SIMULATION_END, null); + + assertEquals(100.0, endEvent.getTimestamp(), 0.001); + assertEquals(DESEventType.SIMULATION_END, endEvent.getType()); + assertNull(endEvent.getPayload(), "End event should have no payload"); + } +}