diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java
index 88378d3..8757784 100644
--- a/main/src/main/java/sd/dashboard/DashboardUI.java
+++ b/main/src/main/java/sd/dashboard/DashboardUI.java
@@ -30,8 +30,22 @@ import sd.config.SimulationConfig;
import sd.model.VehicleType;
/**
- * JavaFX-based Dashboard UI for displaying real-time simulation statistics.
- * Provides a graphical interface with auto-updating statistics panels.
+ * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real.
+ *
+ * Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão
+ * Observer (via polling) para refletir o estado do modelo {@link DashboardStatistics}
+ * nos componentes visuais.
+ *
intersectionTable;
- // Update scheduler
+ // Update scheduler (Background Thread)
private ScheduledExecutorService updateScheduler;
// Configuration controls
@@ -60,6 +74,10 @@ public class DashboardUI extends Application {
private String selectedConfigFile = "simulation.properties";
private Label configInfoLabel;
+ /**
+ * Ponto de entrada da aplicação JavaFX.
+ * Configura o Stage primário, inicializa o servidor de backend e constrói a árvore de cena (Scene Graph).
+ */
@Override
public void start(Stage primaryStage) {
try {
@@ -72,29 +90,27 @@ public class DashboardUI extends Application {
server = new DashboardServer(config);
statistics = server.getStatistics();
- // Start the dashboard server
+ // Start the dashboard server (Backend listening port)
server.start();
- // Build UI
+ // Build UI Layout
BorderPane root = new BorderPane();
root.getStyleClass().add("root");
- // Header
+ // Header (Top)
VBox header = createHeader();
root.setTop(header);
- // Main content
+ // Main content (Center)
VBox mainContent = createMainContent();
root.setCenter(mainContent);
- // Footer
+ // Footer (Bottom)
HBox footer = createFooter();
root.setBottom(footer);
- // Create scene
+ // Create scene & apply CSS
Scene scene = new Scene(root, 1200, 850);
-
- // Load CSS
String cssUrl = getClass().getResource("/dashboard.css").toExternalForm();
scene.getStylesheets().add(cssUrl);
@@ -102,10 +118,10 @@ public class DashboardUI extends Application {
primaryStage.setScene(scene);
primaryStage.show();
- // Start periodic updates
+ // Start periodic updates loop
startPeriodicUpdates();
- // Handle window close
+ // Handle window close (Graceful shutdown)
primaryStage.setOnCloseRequest(event -> {
shutdown();
});
@@ -149,6 +165,8 @@ public class DashboardUI extends Application {
// Passar o ficheiro de configuração selecionado
processManager.setConfigFile(selectedConfigFile);
processManager.startSimulation();
+
+ // Toggle UI state
btnStart.setDisable(true);
btnStop.setDisable(false);
configFileSelector.setDisable(true); // Bloquear mudanças durante simulação
@@ -159,6 +177,8 @@ public class DashboardUI extends Application {
btnStop.setOnAction(e -> {
processManager.stopSimulation();
+
+ // Toggle UI state
btnStart.setDisable(false);
btnStop.setDisable(true);
configFileSelector.setDisable(false); // Desbloquear para nova simulação
@@ -435,13 +455,23 @@ public class DashboardUI extends Application {
grid.add(container, colGroup, row);
}
+ /**
+ * Inicia o ciclo de polling em background.
+ * Atualiza a UI a cada 100ms.
+ */
private void startPeriodicUpdates() {
updateScheduler = Executors.newSingleThreadScheduledExecutor();
updateScheduler.scheduleAtFixedRate(() -> {
+ // Crucial: Encapsular atualização de UI em Platform.runLater
+ // para garantir execução na JavaFX Application Thread
Platform.runLater(this::updateUI);
}, 0, 100, TimeUnit.MILLISECONDS);
}
+ /**
+ * Sincroniza o estado atual do objeto Statistics com os controlos JavaFX.
+ * Chamado periodicamente pela thread de UI.
+ */
private void updateUI() {
// Update global statistics
lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated()));
@@ -548,7 +578,9 @@ public class DashboardUI extends Application {
launch(args);
}
- // Inner classes for TableView data models
+ // --- DTOs para Data Binding nas Tabelas ---
+
+ /** DTO para linhas da tabela de Tipos de Veículo. */
public static class VehicleTypeRow {
private final String vehicleType;
private final int count;
@@ -560,19 +592,12 @@ public class DashboardUI extends Application {
this.avgWaitTime = avgWaitTime;
}
- public String getVehicleType() {
- return vehicleType;
- }
-
- public int getCount() {
- return count;
- }
-
- public String getAvgWaitTime() {
- return avgWaitTime;
- }
+ public String getVehicleType() { return vehicleType; }
+ public int getCount() { return count; }
+ public String getAvgWaitTime() { return avgWaitTime; }
}
+ /** DTO para linhas da tabela de Interseções. */
public static class IntersectionRow {
private final String intersectionId;
private final int arrivals;
@@ -586,20 +611,9 @@ public class DashboardUI extends Application {
this.queueSize = queueSize;
}
- public String getIntersectionId() {
- return intersectionId;
- }
-
- public int getArrivals() {
- return arrivals;
- }
-
- public int getDepartures() {
- return departures;
- }
-
- public int getQueueSize() {
- return queueSize;
- }
+ public String getIntersectionId() { return intersectionId; }
+ public int getArrivals() { return arrivals; }
+ public int getDepartures() { return departures; }
+ public int getQueueSize() { return queueSize; }
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java
index 56d1c33..063a355 100644
--- a/main/src/main/java/sd/dashboard/SimulationProcessManager.java
+++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java
@@ -6,9 +6,17 @@ import java.util.ArrayList;
import java.util.List;
/**
- * Gere o ciclo de vida dos processos de simulação (Intersections, Exit Node,
- * Coordinator).
- * Permite iniciar e parar a simulação distribuída dentro da aplicação Java.
+ * Orquestrador de processos para o ambiente de simulação distribuída.
+ *
+ * Esta classe atua como um supervisor (Process Manager), responsável pelo bootstrapping
+ * e teardown das múltiplas Java Virtual Machines (JVMs) que compõem o sistema.
+ *
+ * Funcionalidades principais:
+ *
+ * - Isolamento: Cada nó (Interseção, Coordinator, ExitNode) corre no seu próprio processo OS.
+ * - Ordem de Arranque: Garante que os servidores (Interseções) estão online antes dos clientes (Coordenador).
+ * - Gestão de Logs: Redireciona stdout/stderr de cada processo filho para ficheiros temporários para facilitar o debug.
+ *
*/
public class SimulationProcessManager {
@@ -16,6 +24,10 @@ public class SimulationProcessManager {
private final String classpath;
private String configFile;
+ /**
+ * Inicializa o gestor capturando o classpath da JVM atual.
+ * Isto garante que os processos filhos herdam as mesmas dependências e configurações de ambiente.
+ */
public SimulationProcessManager() {
this.runningProcesses = new ArrayList<>();
this.classpath = System.getProperty("java.class.path");
@@ -23,9 +35,9 @@ public class SimulationProcessManager {
}
/**
- * Define o ficheiro de configuração a usar.
- *
- * @param configFile nome do ficheiro (ex: "simulation-low.properties")
+ * Define o perfil de configuração a ser injetado nos processos filhos.
+ * Útil para alternar entre cenários (Low/Medium/High Load) dinamicamente.
+ * * @param configFile Nome do ficheiro de propriedades (ex: "simulation-low.properties").
*/
public void setConfigFile(String configFile) {
this.configFile = "src/main/resources/" + configFile;
@@ -33,9 +45,16 @@ public class SimulationProcessManager {
}
/**
- * Inicia a simulação completa: 5 Intersections, 1 Exit Node, e 1 Coordinator.
- *
- * @throws IOException se um processo falhar ao iniciar
+ * Executa o procedimento de arranque (Bootstrap) da simulação distribuída.
+ *
+ * A ordem de inicialização é crítica para evitar Race Conditions na conexão TCP:
+ *
+ * - Workers (Interseções): Iniciam os ServerSockets.
+ * - Sink (Exit Node): Prepara-se para receber métricas finais.
+ * - Delay de Estabilização: Pausa de 1s para garantir que os sockets estão em LISTENING.
+ * - Source (Coordinator): Inicia a geração de carga e conecta-se aos nós.
+ *
+ * * @throws IOException Se falhar o fork de algum processo.
*/
public void startSimulation() throws IOException {
if (!runningProcesses.isEmpty()) {
@@ -65,8 +84,11 @@ public class SimulationProcessManager {
}
/**
- * Checks if the coordinator process (last process started) is still running.
- * When the coordinator finishes, the simulation is complete.
+ * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador.
+ *
+ * Como o Coordenador gere o relógio DES e a geração de eventos, a sua terminação
+ * (após o drain time) sinaliza o fim efetivo da simulação.
+ * * @return true se o Coordenador ainda estiver ativo (alive).
*/
public boolean isSimulationRunning() {
if (runningProcesses.isEmpty()) {
@@ -78,8 +100,10 @@ public class SimulationProcessManager {
}
/**
- * Waits for the simulation to complete naturally.
- * Returns true if completed, false if timeout.
+ * Bloqueia a thread atual até que a simulação termine naturalmente ou ocorra timeout.
+ * * @param timeoutSeconds Tempo máximo de espera.
+ * @return true se terminou, false se o timeout expirou.
+ * @throws InterruptedException Se a espera for interrompida.
*/
public boolean waitForCompletion(long timeoutSeconds) throws InterruptedException {
if (runningProcesses.isEmpty()) {
@@ -91,7 +115,11 @@ public class SimulationProcessManager {
}
/**
- * Stops all running simulation processes.
+ * Executa o procedimento de encerramento (Teardown) de todos os processos.
+ *
+ * Tenta primeiro uma paragem graciosa (`SIGTERM`), aguarda meio segundo, e
+ * força a paragem (`SIGKILL`) para processos persistentes, garantindo que não
+ * ficam processos órfãos no SO.
*/
public void stopSimulation() {
System.out.println("Stopping simulation processes...");
@@ -120,7 +148,8 @@ public class SimulationProcessManager {
}
/**
- * Helper para iniciar um único processo Java.
+ * Helper de baixo nível para construção e lançamento de processos Java.
+ * Configura o redirecionamento de I/O para ficheiros de log na diretoria temporária do SO.
*/
private void startProcess(String className, String arg) throws IOException {
String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
@@ -152,4 +181,4 @@ public class SimulationProcessManager {
// print where the logs are actually going
System.out.println("Logs redirected to: " + logFile.getAbsolutePath());
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/StatsMessage.java b/main/src/main/java/sd/dashboard/StatsMessage.java
index 7209130..abc4730 100644
--- a/main/src/main/java/sd/dashboard/StatsMessage.java
+++ b/main/src/main/java/sd/dashboard/StatsMessage.java
@@ -4,7 +4,14 @@ import sd.model.MessageType;
import sd.protocol.MessageProtocol;
/**
- * Message wrapper for sending statistics to the dashboard.
+ * Implementação concreta do protocolo de mensagens destinada ao transporte de telemetria.
+ *
+ * Esta classe atua como um envelope especializado para o envio de dados estatísticos
+ * (encapsulados em {@link StatsUpdatePayload}) dos nós operacionais (Interseções, Coordenador)
+ * para o servidor de Dashboard centralizado.
+ *
+ * Diferencia-se das mensagens de controlo genéricas por ter o destino fixado no
+ * "DashboardServer" e um tipo de mensagem imutável ({@code STATS_UPDATE}).
*/
public class StatsMessage implements MessageProtocol {
@@ -14,27 +21,49 @@ public class StatsMessage implements MessageProtocol {
private final String destinationNode;
private final StatsUpdatePayload payload;
+ /**
+ * Cria uma nova mensagem de estatística.
+ *
+ * @param sourceNode O ID do nó que gerou as estatísticas (ex: "Cr1", "ExitNode").
+ * @param payload O objeto DTO contendo os dados estatísticos brutos ou agregados.
+ */
public StatsMessage(String sourceNode, StatsUpdatePayload payload) {
this.sourceNode = sourceNode;
- this.destinationNode = "DashboardServer";
+ this.destinationNode = "DashboardServer"; // Destino implícito e fixo
this.payload = payload;
}
+ /**
+ * Retorna o tipo da mensagem, que identifica semanticamente o conteúdo para o recetor.
+ * @return Sempre {@link MessageType#STATS_UPDATE}.
+ */
@Override
public MessageType getType() {
return MessageType.STATS_UPDATE;
}
+ /**
+ * Obtém a carga útil da mensagem.
+ * @return O objeto {@link StatsUpdatePayload} associado.
+ */
@Override
public Object getPayload() {
return payload;
}
+ /**
+ * Identifica a origem da mensagem.
+ * @return O ID do nó remetente.
+ */
@Override
public String getSourceNode() {
return sourceNode;
}
+ /**
+ * Identifica o destino da mensagem.
+ * @return Sempre "DashboardServer".
+ */
@Override
public String getDestinationNode() {
return destinationNode;
@@ -45,4 +74,4 @@ public class StatsMessage implements MessageProtocol {
return String.format("StatsMessage[from=%s, to=%s, payload=%s]",
sourceNode, destinationNode, payload);
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java
index e62e866..6f1915e 100644
--- a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java
+++ b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java
@@ -7,25 +7,60 @@ import java.util.Map;
import sd.model.VehicleType;
/**
- * DTO para atualizações de estatísticas ao dashboard.
- * Campos com valor -1 não são atualizados nesta mensagem.
+ * Objeto de Transferência de Dados (DTO) otimizado para transporte de telemetria.
+ *
+ * Esta classe encapsula as métricas de desempenho enviadas pelos nós da simulação (Coordenador,
+ * Interseções, ExitNode) para o Dashboard. Foi desenhada para suportar atualizações parciais
+ * (Sparse Updates):
+ *
+ * - Campos globais inicializados com {@code -1} indicam "sem alteração" (no-op). O Dashboard
+ * deve ignorar estes campos e manter o valor acumulado anterior.
+ * - Campos de interseção ({@code arrivals}, {@code departures}) representam deltas ou snapshots
+ * específicos do nó remetente.
+ *
+ * Implementa {@link Serializable} para transmissão direta via Java Sockets.
+ *
+
+[Image of data transfer object pattern]
+
*/
public class StatsUpdatePayload implements Serializable {
private static final long serialVersionUID = 1L;
+ // Global Metrics (Coordinator/ExitNode)
+ /** Total gerado. Valor -1 indica que este campo deve ser ignorado na atualização. */
private int totalVehiclesGenerated = -1;
+
+ /** Total completado. Valor -1 indica que este campo deve ser ignorado. */
private int totalVehiclesCompleted = -1;
+
+ /** Tempo total de sistema acumulado (ms). Valor -1 indica que deve ser ignorado. */
private long totalSystemTime = -1;
+
+ /** Tempo total de espera acumulado (ms). Valor -1 indica que deve ser ignorado. */
private long totalWaitingTime = -1;
+ // Intersection Metrics (Worker Nodes)
+ /** Número de veículos que entraram na interseção desde o último reporte. */
private int intersectionArrivals = 0;
+
+ /** Número de veículos que saíram da interseção desde o último reporte. */
private int intersectionDepartures = 0;
+
+ /** Snapshot do tamanho atual da fila na interseção. */
private int intersectionQueueSize = 0;
+ // Detailed Breakdowns
+ /** Contagem acumulada por tipo de veículo. */
private Map vehicleTypeCounts;
+
+ /** Tempos de espera acumulados por tipo de veículo. */
private Map vehicleTypeWaitTimes;
+ /**
+ * Inicializa o payload com os mapas vazios e contadores globais a -1 (estado neutro).
+ */
public StatsUpdatePayload() {
this.vehicleTypeCounts = new HashMap<>();
this.vehicleTypeWaitTimes = new HashMap<>();
@@ -67,6 +102,8 @@ public class StatsUpdatePayload implements Serializable {
return vehicleTypeWaitTimes;
}
+ // Setters implementam Fluent Interface para construção encadeada
+
public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) {
this.totalVehiclesGenerated = totalVehiclesGenerated;
return this;
@@ -118,4 +155,4 @@ public class StatsUpdatePayload implements Serializable {
totalVehiclesGenerated, totalVehiclesCompleted, intersectionArrivals,
intersectionDepartures, intersectionQueueSize);
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/des/SimulationEvent.java b/main/src/main/java/sd/des/SimulationEvent.java
index 7d486d9..589bc5e 100644
--- a/main/src/main/java/sd/des/SimulationEvent.java
+++ b/main/src/main/java/sd/des/SimulationEvent.java
@@ -3,31 +3,46 @@ package sd.des;
import java.io.Serializable;
/**
- * Evento discreto da simulação.
- *
- * Unidade fundamental de execução num sistema DES:
+ * Representa um evento atómico e imutável no contexto da Simulação de Eventos Discretos (DES).
+ *
+ * Esta classe é a unidade fundamental de processamento. Numa arquitetura DES, o estado do sistema
+ * não muda continuamente, mas sim em instantes discretos definidos por estes eventos.
+ *
+ * Características principais:
*
- * - timestamp - quando ocorre
- *
- type - o que acontece
- *
- payload - dados associados
- *
- location - qual processo o trata
+ *
- Ordenação Temporal: Implementa {@link Comparable} para ser armazenado numa Fila de
+ * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.
+ * - Distribuído: Implementa {@link Serializable} para permitir que eventos gerados num nó
+ * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).
+ * - Polimórfico: Transporta um {@code payload} genérico, permitindo associar qualquer
+ * entidade (Veículo, Sinal, etc.) ao evento.
*
*/
public class SimulationEvent implements Comparable, Serializable {
private static final long serialVersionUID = 1L;
+ /** O instante virtual exato em que o evento deve ser processado. */
private final double timestamp;
+
+ /** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */
private final DESEventType type;
+
+ /** Dados contextuais associados (ex: o objeto Vehicle que chegou). */
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
+ * O identificador do nó de destino onde o evento deve ser executado.
+ * (ex: "Cr1", "Coordinator", "ExitNode"). Se null, é um evento local.
+ */
+ private final String location;
+
+ /**
+ * Instancia um novo evento de simulação completo.
+ *
+ * @param timestamp Instante de execução (segundos virtuais).
+ * @param type Tipo enumerado do evento.
+ * @param payload Objeto de dados associado (pode ser null).
+ * @param location ID do processo alvo para execução distribuída.
*/
public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) {
this.timestamp = timestamp;
@@ -36,7 +51,14 @@ public class SimulationEvent implements Comparable, Serializabl
this.location = location;
}
- /** Cria evento sem localização (para eventos locais) */
+ /**
+ * Construtor de conveniência para eventos locais (dentro do mesmo processo).
+ * Define {@code location} como null.
+ *
+ * @param timestamp Instante de execução.
+ * @param type Tipo do evento.
+ * @param payload Objeto de dados associado.
+ */
public SimulationEvent(double timestamp, DESEventType type, Object payload) {
this(timestamp, type, payload, null);
}
@@ -58,8 +80,18 @@ public class SimulationEvent implements Comparable, Serializabl
}
/**
- * Ordena eventos por timestamp (mais cedo primeiro).
- * Em caso de empate, ordena por tipo para determinismo.
+ * Define a ordem natural de processamento na Fila de Prioridade.
+ *
+ * Lógica de Ordenação:
+ *
+ * - Primária (Tempo): Eventos com menor timestamp ocorrem primeiro.
+ * - Secundária (Determinismo): Em caso de empate temporal (simultaneidade),
+ * ordena alfabeticamente pelo nome do tipo. Isto garante que execuções repetidas
+ * da simulação produzam exatamente o mesmo resultado (determinismo estrito).
+ *
+ *
+ * @param other O outro evento a comparar.
+ * @return Inteiro negativo, zero ou positivo conforme a ordem.
*/
@Override
public int compareTo(SimulationEvent other) {
@@ -67,7 +99,7 @@ public class SimulationEvent implements Comparable, Serializabl
if (timeComparison != 0) {
return timeComparison;
}
- // Tie-breaker: order by event type name
+ // Tie-breaker: order by event type name to ensure reproducible runs
return this.type.name().compareTo(other.type.name());
}
@@ -95,4 +127,4 @@ public class SimulationEvent implements Comparable, Serializabl
result = 31 * result + (location != null ? location.hashCode() : 0);
return result;
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/des/TrafficLightEvent.java b/main/src/main/java/sd/des/TrafficLightEvent.java
index 9ca1357..0b27107 100644
--- a/main/src/main/java/sd/des/TrafficLightEvent.java
+++ b/main/src/main/java/sd/des/TrafficLightEvent.java
@@ -3,28 +3,47 @@ package sd.des;
import sd.model.TrafficLight;
/**
- * Payload for traffic light change events.
- * Contains the traffic light and its direction.
+ * Encapsula o contexto de dados para eventos de mudança de estado de semáforos.
+ *
+ * Este objeto atua como o payload transportado por um {@link SimulationEvent}
+ * quando o tipo de evento é relacionado com controlo de tráfego (ex: mudança Verde -> Amarelo).
+ * Permite que o motor DES identifique exatamente qual instância de {@link TrafficLight}
+ * deve ser atualizada numa determinada interseção e direção.
*/
public class TrafficLightEvent {
private final TrafficLight light;
private final String direction;
private final String intersectionId;
+ /**
+ * Cria um novo payload de evento de semáforo.
+ * @param light A instância do objeto semáforo a ser manipulado.
+ * @param direction A direção cardeal associada (ex: "North", "East").
+ * @param intersectionId O identificador da interseção onde o semáforo reside.
+ */
public TrafficLightEvent(TrafficLight light, String direction, String intersectionId) {
this.light = light;
this.direction = direction;
this.intersectionId = intersectionId;
}
+ /**
+ * @return A referência direta para o objeto de domínio do semáforo.
+ */
public TrafficLight getLight() {
return light;
}
+ /**
+ * @return A direção do fluxo controlado por este semáforo.
+ */
public String getDirection() {
return direction;
}
+ /**
+ * @return O ID da interseção pai.
+ */
public String getIntersectionId() {
return intersectionId;
}
@@ -33,4 +52,4 @@ public class TrafficLightEvent {
public String toString() {
return String.format("TrafficLightEvent[%s-%s]", intersectionId, direction);
}
-}
+}
\ 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
index f653ff9..dd08b23 100644
--- a/main/src/main/java/sd/logging/EventLogger.java
+++ b/main/src/main/java/sd/logging/EventLogger.java
@@ -11,10 +11,19 @@ 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.
+ * Motor de logging assíncrono e thread-safe para a simulação distribuída.
+ *
+ * Implementa o padrão Singleton para garantir um ponto centralizado de registo.
+ * Utiliza o padrão Producer-Consumer com uma {@link BlockingQueue} para desacoplar
+ * a geração de eventos (crítica para a performance da simulação) da persistência em disco
+ * (operação de I/O lenta).
+ *
+ * Garantias:
+ *
+ * - Non-blocking writes (para a thread chamadora, na maioria dos casos).
+ * - Ordering cronológico aproximado (FIFO na fila).
+ * - Graceful Shutdown (flush de logs pendentes ao terminar).
+ *
*/
public class EventLogger {
@@ -22,20 +31,33 @@ public class EventLogger {
private static final Object instanceLock = new Object();
private final PrintWriter writer;
+
+ /** Buffer de memória para absorver picos de eventos (Burst traffic). */
private final BlockingQueue logQueue;
+
+ /** Thread dedicada (Consumer) para escrita em ficheiro. */
private final Thread writerThread;
+
private final AtomicBoolean running;
private final SimpleDateFormat timestampFormat;
private final long simulationStartMillis;
- /** Construtor privado para padrão singleton */
+ /**
+ * Inicializa o sistema de logs.
+ * Abre o ficheiro, escreve o cabeçalho e inicia a thread consumidora.
+ *
+ * @param logFilePath Caminho relativo ou absoluto do ficheiro de log.
+ * @throws IOException Se não for possível criar ou escrever no ficheiro.
+ */
private EventLogger(String logFilePath) throws IOException {
+ // Auto-flush ativado para garantir persistência, mas gerido pelo buffer do BufferedWriter
this.writer = new PrintWriter(new BufferedWriter(new FileWriter(logFilePath, false)), true);
- this.logQueue = new LinkedBlockingQueue<>(10000);
+ this.logQueue = new LinkedBlockingQueue<>(10000); // Backpressure: limita a 10k eventos pendentes
this.running = new AtomicBoolean(true);
this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
this.simulationStartMillis = System.currentTimeMillis();
+ // Header inicial do log
writer.println("=".repeat(80));
writer.println("SIMULATION EVENT LOG");
writer.println("Started: " + timestampFormat.format(new Date()));
@@ -47,11 +69,16 @@ public class EventLogger {
writer.flush();
this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer");
- this.writerThread.setDaemon(true);
+ this.writerThread.setDaemon(true); // Permite que a JVM termine se apenas esta thread sobrar
this.writerThread.start();
}
- /** Obtém ou cria a instância singleton */
+ /**
+ * Obtém a instância única do logger (Lazy Initialization).
+ * Se não existir, cria uma predefinida em "logs/simulation-events.log".
+ *
+ * @return A instância singleton.
+ */
public static EventLogger getInstance() {
if (instance == null) {
synchronized (instanceLock) {
@@ -72,7 +99,8 @@ public class EventLogger {
}
/**
- * Initialize with custom log file path.
+ * Reinicializa o logger com um ficheiro específico.
+ * Útil para testes ou configurações personalizadas.
*/
public static void initialize(String logFilePath) throws IOException {
synchronized (instanceLock) {
@@ -84,7 +112,13 @@ public class EventLogger {
}
/**
- * Logs an event (non-blocking).
+ * Regista um evento genérico.
+ * Esta operação é não-bloqueante (retorna imediatamente após colocar na fila),
+ * exceto se a fila estiver cheia (backpressure).
+ *
+ * @param eventType Categoria do evento.
+ * @param component Nome do componente (ex: "Coordinator", "IntersectionProcess").
+ * @param description Detalhes do evento.
*/
public void log(EventType eventType, String component, String description) {
if (!running.get()) return;
@@ -96,7 +130,7 @@ public class EventLogger {
description
);
- // Non-blocking offer - if queue is full, drop oldest
+ // Non-blocking offer - if queue is full, drop oldest or warn
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);
@@ -104,14 +138,14 @@ public class EventLogger {
}
/**
- * Logs an event with vehicle context.
+ * Regista um evento associado a um veículo específico (Helper method).
*/
public void logVehicle(EventType eventType, String component, String vehicleId, String description) {
log(eventType, component, "[" + vehicleId + "] " + description);
}
/**
- * Logs an error event.
+ * Regista um erro ou exceção com formatação apropriada.
*/
public void logError(String component, String description, Exception e) {
String fullDescription = description + (e != null ? ": " + e.getMessage() : "");
@@ -119,11 +153,13 @@ public class EventLogger {
}
/**
- * Background thread that writes log entries to file.
+ * Lógica da thread consumidora (Worker Thread).
+ * Retira eventos da fila e escreve no disco continuamente.
*/
private void processLogQueue() {
while (running.get() || !logQueue.isEmpty()) {
try {
+ // Poll com timeout para permitir verificar a flag 'running' periodicamente
LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS);
if (entry != null) {
writeEntry(entry);
@@ -134,7 +170,7 @@ public class EventLogger {
}
}
- // Flush remaining entries
+ // Flush final: garantir que eventos restantes na fila são escritos antes de morrer
while (!logQueue.isEmpty()) {
LogEntry entry = logQueue.poll();
if (entry != null) {
@@ -144,7 +180,7 @@ public class EventLogger {
}
/**
- * Writes a single log entry to file.
+ * Formata e escreve uma entrada de log no PrintWriter.
*/
private void writeEntry(LogEntry entry) {
String timestamp = timestampFormat.format(new Date(entry.timestampMillis));
@@ -158,7 +194,7 @@ public class EventLogger {
entry.description
);
- // Flush periodically for real-time viewing
+ // Flush periódico inteligente: se a carga for baixa, garante que vemos logs em tempo real
if (logQueue.size() < 10) {
writer.flush();
}
@@ -170,15 +206,17 @@ public class EventLogger {
}
/**
- * Shuts down the logger and flushes all pending entries.
+ * Encerra o logger de forma segura.
+ * Desativa a aceitação de novos eventos, aguarda que a fila esvazie (flush)
+ * e fecha o ficheiro.
*/
public void shutdown() {
if (!running.compareAndSet(true, false)) {
- return; // Already shut down
+ return; // Já encerrado
}
try {
- // Wait for writer thread to finish
+ // Wait for writer thread to finish flushing
writerThread.join(5000); // Wait up to 5 seconds
// Write footer
@@ -195,7 +233,7 @@ public class EventLogger {
}
/**
- * Internal class to represent a log entry.
+ * DTO interno imutável para armazenar dados do evento na fila.
*/
private static class LogEntry {
final long timestampMillis;
@@ -210,4 +248,4 @@ public class EventLogger {
this.description = description;
}
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/logging/EventType.java b/main/src/main/java/sd/logging/EventType.java
index 634fabd..910ae94 100644
--- a/main/src/main/java/sd/logging/EventType.java
+++ b/main/src/main/java/sd/logging/EventType.java
@@ -1,34 +1,46 @@
package sd.logging;
/**
- * Tipos de eventos que podem ocorrer na simulação.
- * Usados para categorizar e filtrar logs.
+ * Taxonomia oficial de eventos para o subsistema de logging centralizado.
+ *
+ * Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo:
+ *
+ * - Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).
+ * - Análise estatística post-mortem (parsear logs para calcular latências).
+ * - Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).
+ *
*/
public enum EventType {
+ // --- Ciclo de Vida do Veículo ---
VEHICLE_GENERATED("Vehicle Generated"),
VEHICLE_ARRIVED("Vehicle Arrived"),
VEHICLE_QUEUED("Vehicle Queued"),
VEHICLE_DEPARTED("Vehicle Departed"),
VEHICLE_EXITED("Vehicle Exited"),
+ // --- Controlo de Semáforos e Exclusão Mútua ---
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"),
+ // --- Ciclo de Vida da Simulação/Processos ---
SIMULATION_STARTED("Simulation Started"),
SIMULATION_STOPPED("Simulation Stopped"),
PROCESS_STARTED("Process Started"),
PROCESS_STOPPED("Process Stopped"),
+ // --- Configuração e Telemetria ---
STATS_UPDATE("Statistics Update"),
CONFIG_CHANGED("Configuration Changed"),
+ // --- Camada de Rede (TCP/Sockets) ---
CONNECTION_ESTABLISHED("Connection Established"),
CONNECTION_LOST("Connection Lost"),
MESSAGE_SENT("Message Sent"),
MESSAGE_RECEIVED("Message Received"),
+ // --- Tratamento de Exceções ---
ERROR("Error");
private final String displayName;
@@ -45,4 +57,4 @@ public enum EventType {
public String toString() {
return displayName;
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/logging/VehicleTracer.java b/main/src/main/java/sd/logging/VehicleTracer.java
index 194f319..611e6aa 100644
--- a/main/src/main/java/sd/logging/VehicleTracer.java
+++ b/main/src/main/java/sd/logging/VehicleTracer.java
@@ -12,16 +12,18 @@ import java.util.concurrent.ConcurrentHashMap;
import sd.model.Vehicle;
/**
- * Rastreia e regista a viagem completa de veículos individuais.
- *
+ * Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais.
*
- * Cria ficheiros de trace detalhados com:
+ * Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se
+ * na perspetiva do agente. Cria um ficheiro de rastro dedicado (`.trace`) para cada veículo
+ * monitorizado, registando cronologicamente cada interação com a infraestrutura (interseções,
+ * filas, semáforos).
+ *
+ * Funcionalidades:
*
- * - Timestamps de todos os eventos
- *
- Localizações (interseções)
- *
- Tempos de espera em cada semáforo
- *
- Tempos de travessia
- *
- Tempo total no sistema
+ *
- Análise forense de percursos individuais.
+ * - Validação de tempos de espera e travessia por nó.
+ * - Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).
*
*/
public class VehicleTracer {
@@ -29,12 +31,18 @@ public class VehicleTracer {
private static VehicleTracer instance;
private static final Object instanceLock = new Object();
+ /** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */
private final Map trackedVehicles;
+
private final SimpleDateFormat timestampFormat;
private final long simulationStartMillis;
private final String traceDirectory;
- /** Construtor privado (singleton) */
+ /**
+ * Inicializa o tracer e prepara o diretório de saída.
+ *
+ * @param traceDirectory Caminho para armazenamento dos ficheiros .trace.
+ */
private VehicleTracer(String traceDirectory) {
this.trackedVehicles = new ConcurrentHashMap<>();
this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@@ -48,7 +56,10 @@ public class VehicleTracer {
}
}
- /** Obtém ou cria a instância singleton */
+ /**
+ * Obtém a instância única do tracer (Singleton).
+ * @return A instância global.
+ */
public static VehicleTracer getInstance() {
if (instance == null) {
synchronized (instanceLock) {
@@ -60,7 +71,10 @@ public class VehicleTracer {
return instance;
}
- /** Inicializa com diretório de trace customizado */
+ /**
+ * Reinicializa o tracer com um diretório personalizado.
+ * Útil para isolar logs de diferentes execuções em lote.
+ */
public static void initialize(String traceDirectory) {
synchronized (instanceLock) {
if (instance != null) {
@@ -71,12 +85,14 @@ public class VehicleTracer {
}
/**
- * Começa a rastrear um veículo específico.
- * Cria ficheiro de trace para este veículo.
+ * Inicia a sessão de rastreio para um veículo específico.
+ * Cria o ficheiro {@code logs/traces/vehicle-{id}.trace} e escreve o cabeçalho.
+ *
+ * @param vehicleId O identificador único do veículo.
*/
public void startTracking(String vehicleId) {
if (trackedVehicles.containsKey(vehicleId)) {
- return; // Already tracking
+ return; // Já está a ser rastreado
}
VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory);
@@ -86,7 +102,7 @@ public class VehicleTracer {
}
/**
- * Stops tracking a vehicle and closes its trace file.
+ * Encerra a sessão de rastreio, fecha o descritor de ficheiro e remove da memória.
*/
public void stopTracking(String vehicleId) {
VehicleTrace trace = trackedVehicles.remove(vehicleId);
@@ -97,14 +113,14 @@ public class VehicleTracer {
}
/**
- * Checks if a vehicle is being tracked.
+ * Verifica se um veículo está atualmente sob auditoria.
*/
public boolean isTracking(String vehicleId) {
return trackedVehicles.containsKey(vehicleId);
}
/**
- * Logs when a vehicle is generated.
+ * Regista o evento de instanciação do veículo pelo Coordenador.
*/
public void logGenerated(Vehicle vehicle) {
if (!isTracking(vehicle.getId()))
@@ -119,7 +135,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle arrives at an intersection.
+ * Regista a chegada física do veículo à zona de deteção de uma interseção.
*/
public void logArrival(String vehicleId, String intersection, double simulationTime) {
if (!isTracking(vehicleId))
@@ -133,7 +149,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle is queued at a traffic light.
+ * Regista a entrada do veículo na estrutura de fila de um semáforo.
*/
public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) {
if (!isTracking(vehicleId))
@@ -147,7 +163,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle starts waiting at a red light.
+ * Regista o início da espera ativa (veículo parado no Vermelho).
*/
public void logWaitingStart(String vehicleId, String intersection, String direction) {
if (!isTracking(vehicleId))
@@ -161,7 +177,8 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle finishes waiting (light turns green).
+ * Regista o fim da espera (Sinal Verde).
+ * @param waitTime Duração total da paragem nesta instância.
*/
public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) {
if (!isTracking(vehicleId))
@@ -175,7 +192,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle starts crossing an intersection.
+ * Regista o início da travessia da interseção (ocupação da zona crítica).
*/
public void logCrossingStart(String vehicleId, String intersection, String direction) {
if (!isTracking(vehicleId))
@@ -189,7 +206,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle finishes crossing an intersection.
+ * Regista a libertação da zona crítica da interseção.
*/
public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) {
if (!isTracking(vehicleId))
@@ -203,7 +220,7 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle departs from an intersection.
+ * Regista a partida da interseção em direção ao próximo nó.
*/
public void logDeparture(String vehicleId, String intersection, String nextDestination) {
if (!isTracking(vehicleId))
@@ -217,7 +234,10 @@ public class VehicleTracer {
}
/**
- * Logs when a vehicle exits the system.
+ * Regista a saída do sistema (no Exit Node).
+ *
+ * Este método também desencadeia a escrita do Sumário de Viagem no final do log
+ * e fecha o ficheiro automaticamente.
*/
public void logExit(Vehicle vehicle, double systemTime) {
if (!isTracking(vehicle.getId()))
@@ -229,7 +249,7 @@ public class VehicleTracer {
String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs",
systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime()));
- // Write summary
+ // Escreve estatísticas sumarizadas
trace.writeSummary(vehicle, systemTime);
// Stop tracking and close file
@@ -238,7 +258,8 @@ public class VehicleTracer {
}
/**
- * Shuts down the tracer and closes all trace files.
+ * Fecha forçosamente todos os traces abertos.
+ * Deve ser chamado no shutdown da simulação para evitar corrupção de logs.
*/
public void shutdown() {
for (VehicleTrace trace : trackedVehicles.values()) {
@@ -248,7 +269,7 @@ public class VehicleTracer {
}
/**
- * Internal class to handle tracing for a single vehicle.
+ * Classe interna auxiliar que gere o descritor de ficheiro e a formatação para um único veículo.
*/
private class VehicleTrace {
private final String vehicleId;
@@ -340,4 +361,4 @@ public class VehicleTracer {
return str.length() <= maxLength ? str : str.substring(0, maxLength);
}
}
-}
+}
\ No newline at end of file