diff --git a/.gitignore b/.gitignore index 5c91afc..8b0c728 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ # Log files *.log +*.trace +logs +*.md # BlueJ files *.ctxt @@ -50,4 +53,7 @@ build/ *.pdf # JAR built pom file -dependency-reduced-pom.xml \ No newline at end of file +dependency-reduced-pom.xml + +# Python env +venv/ \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index cc47c34..0000000 --- a/README.md +++ /dev/null @@ -1,620 +0,0 @@ -# Sistema de Simulação de Tráfego Distribuído - -Sistema distribuído de simulação de tráfego. ---- - -## Índice - -- [Visão Geral](#visão-geral) -- [Arquitetura](#arquitetura) -- [Protocolo de Comunicação](#protocolo-de-comunicação) -- [Estrutura do Projeto](#estrutura-do-projeto) -- [Instalação e Execução](#instalação-e-execução) -- [Documentação](#documentação) -- [Desenvolvimento](#desenvolvimento) - ---- - -## Visão Geral - -Este projeto implementa uma simulação distribuída de tráfego veicular numa rede de cruzamentos. O sistema utiliza: - -- **Processos independentes** para cada cruzamento -- **Threads** para controlar os semáforos dentro de cada cruzamento -- **Comunicação via sockets** para transferência de veículos entre cruzamentos -- **Simulação de eventos discretos** (DES) para gerir o tempo de simulação - -### Características Principais - -- Simulação determinística e reproduzível -- Comunicação assíncrona entre processos -- Protocolo de mensagens baseado em JSON -- Dashboard em tempo real (planeado) -- Estatísticas detalhadas de desempenho - ---- - -## Arquitetura - -### Visão Geral do Sistema - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ SISTEMA DISTRIBUÍDO │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ Coordenador │ ────────────────────────>│ Dashboard │ │ -│ │ / Gerador │ │ │ -│ └──────┬───────┘ └──────▲───────┘ │ -│ │ │ │ -│ │ Gera veículos Stats │ │ -│ │ │ │ -│ ▼ │ │ -│ ┌─────────────────────────────────────────────────┴──────┐ │ -│ │ Rede de Cruzamentos (Processos) │ │ -│ │ │ │ -│ │ ┌────┐ ┌────┐ ┌────┐ │ │ -│ │ │Cr1 │◄───────►│Cr2 │◄───────►│Cr3 │ │ │ -│ │ └─┬──┘ └─┬──┘ └─┬──┘ │ │ -│ │ │ │ │ │ │ -│ │ │ ┌────▼────┐ │ │ │ -│ │ └────────►│ Cr4 │◄────────┘ │ │ -│ │ └────┬────┘ │ │ -│ │ │ │ │ -│ │ ┌────▼────┐ │ │ -│ │ │ Cr5 │ │ │ -│ │ └────┬────┘ │ │ -│ └───────────────────┼─────────────────────────────────────┤ │ -│ │ │ │ -│ ▼ │ │ -│ ┌──────────────┐ │ │ -│ │ Nó de Saída │ │ │ -│ │ (S) │ │ │ -│ └──────────────┘ │ │ -│ │ │ -└────────────────────────────────────────────────────────────┘ │ -``` - -### Componentes - -1. **Coordenador/Gerador**: Gera veículos e injeta no sistema -2. **Cruzamentos (Cr1-Cr5)**: Processos independentes que gerem tráfego local -3. **Nó de Saída (S)**: Recolhe estatísticas de veículos que saem do sistema -4. **Dashboard Server**: Agrega e exibe dados em tempo real - ---- - -## Protocolo de Comunicação - -### Formato de Serialização: JSON (Gson) - -O sistema utiliza JSON como formato de serialização por ser mais rápido, seguro e legível que a serialização em Java. - -### Estrutura de Mensagens - -Todas as mensagens seguem o formato base: - -```json -{ - "messageId": "uuid", - "type": "MESSAGE_TYPE", - "senderId": "sender_id", - "destinationId": "destination_id", - "timestamp": 1729595234567, - "payload": { ... } -} -``` - -### Tipos de Mensagens - -#### 1. VEHICLE_TRANSFER - -Transfere um veículo entre cruzamentos. - -**Estrutura:** -```json -{ - "messageId": "a3c5e7f9-1234-5678-90ab-cdef12345678", - "type": "VEHICLE_TRANSFER", - "senderId": "Cr1", - "destinationId": "Cr2", - "timestamp": 1729595234567, - "payload": { - "id": "V123", - "type": "LIGHT", - "entryTime": 15.7, - "route": ["Cr1", "Cr2", "Cr5", "S"], - "currentRouteIndex": 1, - "totalWaitingTime": 3.2, - "totalCrossingTime": 1.8 - } -} -``` - -**Fluxo:** -1. Veículo completa travessia no Cr1 -2. Cr1 serializa mensagem VEHICLE_TRANSFER -3. Envia para Cr2 via socket -4. Cr2 desserializa e adiciona veículo à fila - -#### 2. STATS_UPDATE - -Envia estatísticas de um cruzamento para o Dashboard. - -**Estrutura:** -```json -{ - "messageId": "b4d6e8f0-2345-6789-01bc-def123456789", - "type": "STATS_UPDATE", - "senderId": "Cr3", - "destinationId": "Dashboard", - "timestamp": 1729595234789, - "payload": { - "intersectionId": "Cr3", - "queueLengths": { - "North": 5, - "South": 3, - "East": 7, - "West": 2 - }, - "vehiclesProcessed": 142, - "averageWaitTime": 4.5, - "currentTime": 123.45 - } -} -``` - -**Frequência:** A cada 10 segundos (configurável) - -#### 3. VEHICLE_EXIT - -Notifica quando um veículo sai do sistema. - -**Estrutura:** -```json -{ - "messageId": "c5e7f9a1-3456-7890-12bc-def123456789", - "type": "VEHICLE_EXIT", - "senderId": "Cr5", - "destinationId": "ExitNode", - "timestamp": 1729595234890, - "payload": { - "id": "V123", - "type": "LIGHT", - "entryTime": 15.7, - "exitTime": 45.2, - "totalSystemTime": 29.5, - "totalWaitingTime": 8.3, - "totalCrossingTime": 4.8, - "routeTaken": ["Cr1", "Cr2", "Cr5", "S"] - } -} -``` - -#### 4. HEARTBEAT - -Mantém a ligação ativa e monitoriza a saúde dos processos. - -**Estrutura:** -```json -{ - "messageId": "d6e8f0a2-4567-8901-23cd-ef1234567890", - "type": "HEARTBEAT", - "senderId": "Cr1", - "destinationId": "Coordinator", - "timestamp": 1729595235000, - "payload": { - "status": "RUNNING", - "uptime": 120.5, - "vehiclesInQueue": 12 - } -} -``` - -**Frequência:** A cada 5 segundos - -#### 5. LIGHT_CHANGE - -Notifica mudança de estado de semáforo (para logging/debugging). - -**Estrutura:** -```json -{ - "messageId": "e7f9a1b3-5678-9012-34de-f12345678901", - "type": "LIGHT_CHANGE", - "senderId": "Cr1-North", - "destinationId": "Dashboard", - "timestamp": 1729595235100, - "payload": { - "lightId": "Cr1-North", - "previousState": "RED", - "newState": "GREEN", - "queueSize": 5 - } -} -``` - -### Tipos de Veículos - -```json -{ - "BIKE": { - "probability": 0.20, - "crossingTime": 1.5 - }, - "LIGHT": { - "probability": 0.60, - "crossingTime": 2.0 - }, - "HEAVY": { - "probability": 0.20, - "crossingTime": 4.0 - } -} -``` - -### Estados dos Semáforos - -``` -RED → Veículos aguardam na fila -GREEN → Veículos podem atravessar -``` - -### Exemplo de Comunicação Completa - -``` -Tempo Processo Ação Mensagem ------- --------- ------------------------------------- ------------------ -15.7s Gerador Gera veículo V123 - -15.7s Gerador → Injeta V123 em Cr1 VEHICLE_TRANSFER -18.2s Cr1 V123 inicia travessia - -20.2s Cr1 V123 completa travessia - -20.2s Cr1 → Cr2 Transfere V123 para Cr2 VEHICLE_TRANSFER -23.5s Cr2 V123 inicia travessia - -25.5s Cr2 V123 completa travessia - -25.5s Cr2 → Cr5 Transfere V123 para Cr5 VEHICLE_TRANSFER -28.0s Cr5 V123 inicia travessia - -30.0s Cr5 V123 completa travessia - -30.0s Cr5 → Exit V123 sai do sistema VEHICLE_EXIT -30.0s Exit → Dash Estatísticas de V123 STATS_UPDATE -``` - ---- - -## Estrutura do Projeto - -``` -Trabalho-Pratico-SD/ -├── README.md # Este ficheiro -├── TODO.md # Plano de desenvolvimento -├── main/ -│ ├── pom.xml # Configuração do Maven -│ ├── docs/ -│ │ ├── README.md # Índice da documentação -│ │ ├── SERIALIZATION_SPECIFICATION.md -│ │ ├── SERIALIZATION_DECISION.md -│ │ ├── SERIALIZATION_SUMMARY.md -│ │ └── SERIALIZATION_ARCHITECTURE.md -│ ├── src/ -│ │ ├── main/java/sd/ -│ │ │ ├── Entry.java # Ponto de entrada -│ │ │ ├── config/ -│ │ │ │ └── SimulationConfig.java -│ │ │ ├── engine/ -│ │ │ │ └── SimulationEngine.java -│ │ │ ├── model/ -│ │ │ │ ├── Event.java -│ │ │ │ ├── EventType.java -│ │ │ │ ├── Intersection.java -│ │ │ │ ├── Message.java # Estrutura de mensagens -│ │ │ │ ├── MessageType.java # Tipos de mensagens -│ │ │ │ ├── TrafficLight.java -│ │ │ │ ├── Vehicle.java -│ │ │ │ └── VehicleType.java -│ │ │ ├── serialization/ # Sistema de serialização -│ │ │ │ ├── MessageSerializer.java -│ │ │ │ ├── SerializationException.java -│ │ │ │ ├── JsonMessageSerializer.java -│ │ │ │ ├── SerializerFactory.java -│ │ │ │ ├── SerializationExample.java -│ │ │ │ └── README.md -│ │ │ └── util/ -│ │ │ ├── RandomGenerator.java -│ │ │ ├── StatisticsCollector.java -│ │ │ └── VehicleGenerator.java -│ │ └── test/java/ -│ │ ├── SimulationTest.java -│ │ └── sd/serialization/ -│ │ └── SerializationTest.java -│ └── target/ # Ficheiros compilados -└── .vscode/ # Configuração do VS Code -``` - ---- - -## Instalação e Execução - -### Pré-requisitos - -- **Java 17** ou superior -- **Maven 3.8+** -- **Git** - -### Instalação - -```bash -# Clonar o repositório -git clone https://github.com/davidalves04/Trabalho-Pratico-SD.git -cd Trabalho-Pratico-SD/main - -# Compilar o projeto -mvn clean compile - -# Executar os testes -mvn test -``` - -### Execução - -#### Simulação Básica (Single Process) - -```bash -mvn exec:java -Dexec.mainClass="sd.Entry" -``` - -#### Exemplo de Serialização - -```bash -mvn exec:java -Dexec.mainClass="sd.serialization.SerializationExample" -``` - -#### Configuração - -Editar `src/main/resources/simulation.properties`: - -```properties -# Duração da simulação (segundos) -simulation.duration=60.0 - -# Modelo de chegada: FIXED ou POISSON -arrival.model=POISSON - -# Taxa de chegada (veículos/segundo) -arrival.rate=0.5 - -# Intervalo de atualização de estatísticas (segundos) -stats.update.interval=10.0 - -# Distribuição de tipos de veículos -vehicle.type.bike.probability=0.20 -vehicle.type.light.probability=0.60 -vehicle.type.heavy.probability=0.20 - -# Tempos de travessia por tipo (segundos) -vehicle.type.bike.crossing.time=1.5 -vehicle.type.light.crossing.time=2.0 -vehicle.type.heavy.crossing.time=4.0 -``` - ---- - -## Documentação - -### Documentação de Serialização - -A documentação completa sobre o protocolo de serialização está disponível em: - -- **[Índice Completo](./main/docs/README.md)** - Navegação da documentação -- **[Especificação](./main/docs/SERIALIZATION_SPECIFICATION.md)** - Design detalhado -- **[Guia de Decisão](./main/docs/SERIALIZATION_DECISION.md)** - Porquê JSON? -- **[Resumo](./main/docs/SERIALIZATION_SUMMARY.md)** - Estado de implementação -- **[Arquitetura](./main/docs/SERIALIZATION_ARCHITECTURE.md)** - Diagramas visuais - -### Guias de Utilização - -- **[Serialization README](./main/src/main/java/sd/serialization/README.md)** - Como utilizar os serializers - -### Exemplos de Código - -```java -// Criar serializer -MessageSerializer serializer = SerializerFactory.createDefault(); - -// Serializar mensagem -Vehicle vehicle = new Vehicle("V123", VehicleType.LIGHT, 10.5, route); -Message message = new Message( - MessageType.VEHICLE_TRANSFER, - "Cr1", - "Cr2", - vehicle -); -byte[] data = serializer.serialize(message); - -// Enviar via socket -outputStream.write(data); - -// Receber e desserializar -byte[] received = inputStream.readAllBytes(); -Message msg = serializer.deserialize(received, Message.class); -Vehicle v = msg.getPayloadAs(Vehicle.class); -``` - ---- - -## Desenvolvimento - -### Estado do Projeto - -| Componente | Estado | Notas | -|------------|--------|-------| -| Modelo de Dados | Completo | Vehicle, Message, Event, etc. | -| Simulação DES | Completo | Single-process funcional | -| Serialização | Completo | JSON e Java implementados | -| Testes | 14/14 | Suite de serialização | -| Processos Distribuídos | Planeado | Próxima etapa | -| Comunicação Sockets | Planeado | Em design | -| Dashboard | Planeado | UI web | - -### Roteiro de Desenvolvimento - -#### Fase 1: Fundações (Concluído) -- Modelação de classes -- Simulação DES single-process -- Design de protocolo de serialização -- Implementação JSON/Java serialization -- Testes unitários - -#### Fase 2: Distribuição (Em Curso) -- Implementar comunicação via sockets -- Separar cruzamentos em processos -- Implementar threads de semáforos -- Testar comunicação entre processos - -#### Fase 3: Dashboard e Monitorização -- Dashboard server -- UI web em tempo real -- Visualização de estatísticas -- Logs estruturados - -#### Fase 4: Optimização e Análise -- Testes de carga -- Análise de diferentes políticas -- Recolha de métricas -- Relatório final - -### Executar Testes - -```bash -# Todos os testes -mvn test - -# Apenas testes de serialização -mvn test -Dtest=SerializationTest - -# Com relatório de cobertura -mvn test jacoco:report -``` - -### Contribuir - -1. Fork o projeto -2. Criar uma branch para a funcionalidade (`git checkout -b feature/MinhaFuncionalidade`) -3. Commit das alterações (`git commit -m 'Adiciona MinhaFuncionalidade'`) -4. Push para a branch (`git push origin feature/MinhaFuncionalidade`) -5. Abrir um Pull Request - ---- - -## Métricas de Desempenho - -### Serialização - -| Formato | Tamanho | Latência | Throughput | -|---------|---------|----------|------------| -| JSON | 300 bytes | 40.79 μs | ~24k msgs/s | -| Java | 657 bytes | 33.34 μs | ~30k msgs/s | - -**Conclusão**: JSON é 54% menor com overhead desprezível (7 μs) - -### Simulação - -- **Veículos gerados/s**: ~0.5-1.0 (configurável) -- **Throughput**: ~0.2 veículos/s (saída) -- **Tempo de execução**: 140ms para 60s de simulação -- **Overhead**: < 0.25% do tempo simulado - ---- - -## Protocolo de Mensagens - Resumo - -### Formato Base - -``` -+------------------+ -| Message Header | -|------------------| -| messageId | UUID único -| type | Enum MessageType -| senderId | ID do processo remetente -| destinationId | ID do processo destino (null = broadcast) -| timestamp | Tempo de criação (ms) -+------------------+ -| Payload | -|------------------| -| Object | Dados específicos do tipo de mensagem -+------------------+ -``` - -### Serialização - -- **Formato**: JSON (UTF-8) -- **Biblioteca**: Gson 2.10.1 -- **Codificação**: UTF-8 -- **Compressão**: Opcional (gzip) - -### Transporte - -- **Protocolo**: TCP/IP -- **Porta base**: 5000+ (configurável) -- **Timeout**: 30s -- **Keep-alive**: Heartbeat a cada 5s - ---- - -## Segurança - -### Considerações - -1. **Validação de Mensagens** - - Verificar tipos esperados - - Validar intervalos de valores - - Rejeitar mensagens malformadas - -2. **Autenticação** (Planeado) - - Autenticação baseada em token - - Whitelist de processos - -3. **Encriptação** (Opcional) - - TLS/SSL para produção - - Não necessário para ambiente de desenvolvimento local - ---- - -## Licença - -Este projeto é desenvolvido para fins académicos no âmbito da disciplina de Sistemas Distribuídos (SD) do Instituto Politécnico do Porto. - ---- - -## Equipa - -**Instituição**: Instituto Politécnico do Porto -**Curso**: Sistemas Distribuídos -**Ano Letivo**: 2025-2026 (1º Semestre) - ---- - -## Suporte - -Para questões ou problemas: - -1. Consultar a [documentação](./main/docs/README.md) -2. Ver [exemplos de código](./main/src/main/java/sd/serialization/SerializationExample.java) -3. Executar testes: `mvn test` -4. Abrir issue no GitHub - ---- - -## Ligações Úteis - -- [Documentação do Projeto](./main/docs/README.md) -- [Plano de Desenvolvimento](./TODO.md) -- [Especificação de Serialização](./main/docs/SERIALIZATION_SPECIFICATION.md) -- [Guia de Serialização](./main/src/main/java/sd/serialization/README.md) - ---- - -**Última actualização**: 23 de outubro de 2025 -**Versão**: 1.0.0 -**Estado**: Em Desenvolvimento Activo diff --git a/STEP2_SUMMARY.md b/STEP2_SUMMARY.md deleted file mode 100644 index b234c44..0000000 --- a/STEP2_SUMMARY.md +++ /dev/null @@ -1,134 +0,0 @@ -# 🏁 Single-Process Prototype — Implementation Summary - -**Status:** ✅ Complete -**Date:** October 22, 2025 -**Branch:** `8-single-process-prototype` - ---- - -## Overview - -The single-process prototype implements a **discrete event simulation (DES)** of a 3×3 urban grid with five intersections, realistic vehicle behavior, and fully synchronized traffic lights. Everything runs under one process, laying the groundwork for the distributed architecture in Phase 3. - ---- - -## Core Architecture - -### **SimulationEngine** - -Drives the DES loop with a priority queue of timestamped events — vehicles, lights, crossings, and periodic stats updates. Handles five intersections (Cr1–Cr5) and six event types. - -**Main loop:** - -``` -while (events && time < duration): - event = nextEvent() - time = event.timestamp - handle(event) -``` - -### **VehicleGenerator** - -Spawns vehicles via: - -* **Poisson arrivals** (λ = 0.5 veh/s) or fixed intervals -* **Probabilistic routes** from E1–E3 -* **Type distribution**: 20% BIKE, 60% LIGHT, 20% HEAVY - -### **StatisticsCollector** - -Tracks system-wide and per-type metrics: throughput, avg. wait, queue sizes, light cycles — updated every 10 s and at simulation end. - ---- - -## Model Highlights - -* **Vehicle** – type, route, timings, lifecycle. -* **Intersection** – routing tables, traffic lights, queues. -* **TrafficLight** – red/green cycles with FIFO queues. -* **Event** – timestamped, comparable; 6 types for all DES actions. - ---- - -## Configuration (`simulation.properties`) - -```properties -simulation.duration=60.0 -simulation.arrival.model=POISSON -simulation.arrival.rate=0.5 - -vehicle.bike.crossingTime=1.5 -vehicle.light.crossingTime=2.0 -vehicle.heavy.crossingTime=4.0 - -statistics.update.interval=10.0 -``` - -**Speed logic:** -`t_bike = 0.5×t_car`, `t_heavy = 2×t_car`. - ---- - -## Topology - -``` -E1→Cr1→Cr4→Cr5→S -E2→Cr2→Cr5→S -E3→Cr3→S -Bi-dir: Cr1↔Cr2, Cr2↔Cr3 -``` - ---- - -## Results - -**Unit Tests:** 7/7 ✅ -**60-Second Simulation:** - -* Generated: 22 vehicles -* Completed: 5 (22.7%) -* Avg system time: 15.47 s -* Throughput: 0.08 veh/s -* All lights & intersections operational - -**Performance:** -~0.03 s real-time run (≈2000× speed-up), < 50 MB RAM. - ---- - -## Code Structure - -``` -sd/ -├── engine/SimulationEngine.java -├── model/{Vehicle,Intersection,TrafficLight,Event}.java -├── util/{VehicleGenerator,StatisticsCollector}.java -└── config/SimulationConfig.java -``` - ---- - -## Key Flow - -1. Initialize intersections, lights, first events. -2. Process events chronologically. -3. Vehicles follow routes → queue → cross → exit. -4. Lights toggle, queues drain, stats update. -5. Print summary and performance metrics. - ---- - -## Next Steps — Phase 3 - -* Split intersections into independent **processes**. -* Add **socket-based communication**. -* Run **traffic lights as threads**. -* Enable **distributed synchronization** and fault handling. - ---- - -## TL;DR - -Solid single-process DES ✅ -Everything’s working — traffic lights, routing, vehicles, stats. -Ready to go distributed next. \ No newline at end of file diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 97d1982..0000000 --- a/TODO.md +++ /dev/null @@ -1,198 +0,0 @@ -## ✅ SINGLE-PROCESS PROTOTYPE - COMPLETED - -### Phase 2 Status: DONE ✅ - -All components for the single-process prototype have been successfully implemented and tested: - -- ✅ **SimulationEngine** - Priority queue-based discrete event simulation -- ✅ **VehicleGenerator** - Poisson and Fixed arrival models -- ✅ **StatisticsCollector** - Comprehensive metrics tracking -- ✅ **Entry point** - Main simulation runner -- ✅ **60s test simulation** - Successfully validated event processing and routing - -### Test Results: -- All 7 unit tests passing -- 60-second simulation completed successfully -- Generated 22 vehicles with 5 completing their routes -- Traffic light state changes working correctly -- Vehicle routing through intersections validated - ---- - -## NEXT: Distributed Architecture Implementation - -### Compreender os Conceitos Fundamentais - -Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem ser totalmente compreendidos. - -- **Processos vs. Threads:** O projeto especifica o uso de ambos. - - - **Processos (para Cruzamentos)** são programas independentes, cada um com o seu próprio espaço de memória. Em Java, cada cruzamento será provavelmente executado como uma aplicação Java separada (uma instância distinta da JVM). - - - **Threads (para Semáforos)** existem _dentro_ de um processo e partilham memória. Isto é adequado para os semáforos, pois eles precisam de ser coordenados e partilhar dados (como filas de veículos) dentro do mesmo cruzamento. - -- **Comunicação Entre Processos (IPC - Inter-Process Communication):** Como os cruzamentos são processos separados, é necessário um método para que eles comuniquem. **Sockets** são o método especificado. Quando um veículo sai de um cruzamento (ex: `Cr1`) e vai para outro (ex: `Cr2`), o processo `Cr1` precisa de enviar uma mensagem contendo os dados do veículo para o processo `Cr2` através de uma conexão por socket. - -- **Simulação de Eventos Discretos (DES - Discrete-Event Simulation):** Este é o paradigma de simulação que deve ser utilizado. Em vez de o tempo fluir continuamente, o relógio da simulação salta de um evento para o seguinte. - - - Um **evento** é um objeto que representa algo que acontece num ponto específico no tempo (ex: "Veículo A chega ao Cr2 no tempo 15.7s"). - - - Uma **lista de eventos** central, frequentemente uma fila de prioridades, será necessária para armazenar eventos futuros, ordenados pelo seu timestamp. O ciclo principal da simulação retira o próximo evento da lista, processa-o e adiciona quaisquer novos eventos que resultem dele. - -- **Processo de Poisson:** Para o modelo 'mais realista' de chegadas de veículos, é especificado um processo de Poisson. A principal conclusão é que o tempo _entre_ chegadas consecutivas de veículos segue uma **distribuição exponencial**. Em Java, este intervalo pode ser gerado usando `Math.log(1 - Math.random()) / -lambda`, onde `lambda` (λi) é a taxa de chegada especificada. - - ---- - -### Uma Sugestão de Arquitetura de Alto Nível - -Abaixo, é apresentada uma possível estrutura para a aplicação distribuída. Pode ser vista como um conjunto de programas independentes que comunicam através de uma rede. - -1. **Processo Coordenador/Gerador (1 Processo):** - - - **Propósito:** Iniciar a simulação, gerar veículos e gerir o relógio global da simulação ou os critérios de paragem. - - - **Responsabilidades:** - - - Lê a configuração da simulação (ex: carga de tráfego λi, tempos dos semáforos). - - - Gera veículos de acordo com o modelo selecionado (intervalo fixo ou processo de Poisson). - - - Atribui a cada novo veículo um percurso com base na distribuição uniforme especificada. - - - Injeta o veículo no sistema enviando uma mensagem para o primeiro processo de cruzamento no seu percurso (ex: de um ponto de entrada E1 para Cr1). - -2. **Processos de Cruzamento (5 Processos):** - - - **Propósito:** Simular cada cruzamento (`Cr1` a `Cr5`) como um processo distinto. - - - **Responsabilidades:** - - - Escuta por veículos a chegar de outros processos. - - - Gere as filas de veículos para os seus semáforos. - - - Executa múltiplas **threads de Semáforo** internamente. - - - Coordena estas threads para garantir que apenas uma direção de tráfego está aberta a cada momento. - - - Quando um veículo atravessa, é encaminhado para o processo seguinte no seu percurso. - - - Envia periodicamente as suas estatísticas (ex: comprimentos atuais das filas) para o Servidor do Dashboard. - -3. **Processo de Nó de Saída (1 Processo):** - - - **Propósito:** Representar o ponto de saída `S` e atuar como um coletor de dados para estatísticas globais. - - - **Responsabilidades:** - - - Recebe veículos que completaram o seu percurso. - - - Calcula métricas globais como o tempo total de viagem (tempo de permanência) para cada veículo. - - - Agrega e calcula as estatísticas finais (ex: tempo de viagem mínimo, máximo e médio por tipo de veículo). - - - Envia estas estatísticas globais para o Servidor do Dashboard. - -4. **Processo do Servidor do Dashboard (1 Processo):** - - - **Propósito:** Agregar e exibir todos os dados da simulação em tempo real. - - - **Responsabilidades:** - - - Abre um socket de servidor e escuta por dados a chegar de todos os processos de Cruzamento e de Saída. - - - Armazena e atualiza as estatísticas à medida que chegam. - - - Apresenta os dados numa interface de utilizador, que deve exibir métricas e ser atualizada durante a simulação. - - ---- - -### Plano - -Nem tudo deve ser construído de uma só vez. Os seguintes passos incrementais são recomendados. - -#### **Passo 1: Modelação e Classes Principais (Não-distribuído)** - -Antes de escrever qualquer lógica complexa, as estruturas de dados devem ser definidas. Devem ser criados Plain Old Java Objects (POJOs) para: - -- `Veiculo`: Com atributos como um identificador único, tipo, tempo de entrada e o percurso realizado. Deve ser tornado `Serializable` para que possa ser enviado através de sockets. - -- `Evento`: Com atributos como um timestamp e o tipo de evento (ex: `VEHICLE_ARRIVAL`), bem como dados associados. - -- `Semaforo`: Para conter o seu estado (`VERDE`/`VERMELHO`) e a fila de veículos. - -- `Cruzamento`: Para conter os seus semáforos e a lógica operacional. - - -#### **Passo 2: Construir um Protótipo de Processo Único** - -Este é um passo crucial. Sockets e processos devem ser deixados de lado por agora para construir toda a simulação numa única aplicação Java. - -- Deve ser criado um ciclo de simulação central baseado numa fila de prioridades para objetos `Evento`. - -- Todos os objetos `Cruzamento` e `Semaforo` devem ser instanciados. - -- A lógica principal deve ser tornada funcional: veículos a moverem-se entre filas, semáforos a mudar de estado e estatísticas básicas a serem recolhidas. - -- **Objetivo:** Uma simulação totalmente funcional e não-distribuída. Isto torna a depuração significativamente mais fácil. - - -#### **Passo 3: Distribuir os Cruzamentos** - -O protótipo pode agora ser convertido num sistema distribuído. - -- A classe `Cruzamento` deve ser tornada executável como uma aplicação Java autónoma (com um método `main`). Serão lançadas cinco instâncias, uma para cada cruzamento. - -- Devem ser configurados sockets TCP para comunicação. Cada processo de cruzamento precisa de saber o endereço/porta dos vizinhos para os quais pode enviar veículos. - -- Um **protocolo de comunicação** claro deve ser definido. Por exemplo, quando `Cr1` envia um veículo para `Cr2`, o objeto `Veiculo` é serializado e escrito no socket conectado a `Cr2`. O processo `Cr2` terá uma thread dedicada para escutar estas conexões de entrada. - - -#### **Passo 4: Implementar as Threads dos Semáforos** - -Dentro de cada processo `Cruzamento`, os semáforos devem ser implementados como threads. - -- O principal desafio aqui é a **sincronização**. As threads dos semáforos num único cruzamento partilham as filas de veículos. - -- As ferramentas de concorrência do Java (como `synchronized`, `ReentrantLock`, `Semaphore`) devem ser usadas para garantir que apenas um semáforo pode estar verde para um percurso conflituante e que o acesso às filas partilhadas é seguro (thread-safe). - - -#### **Passo 5: Implementar o Dashboard** - -- O processo `DashboardServer` deve ser criado. Ele irá escutar numa porta específica por estatísticas a chegar. - -- Nos processos `Cruzamento` e `Saida`, deve ser adicionado um mecanismo para enviar periodicamente um resumo das suas estatísticas atuais para o Servidor do Dashboard. - -- A UI deve ser construída para exibir estes dados em tempo real. - - -#### **Passo 6: Testes e Análise** - -Assim que o sistema completo estiver a funcionar, as experiências exigidas pela descrição do projeto podem ser realizadas. - -- A simulação deve ser executada com diferentes taxas de chegada de veículos para simular cargas baixas, médias e altas. - -- Diferentes políticas de temporização dos semáforos devem ser testadas para medir o seu impacto no congestionamento. - -- Diferentes algoritmos de seleção de percurso e o seu impacto no desempenho do sistema devem ser avaliados. - -- Para cada cenário, a simulação deve ser executada várias vezes para recolher estatísticas fiáveis (médias, desvios padrão, intervalos de confiança), conforme solicitado. - - -#### **Passo 7: Escrever o Relatório** - -À medida que cada passo é concluído, deve ser documentado. Isto tornará a escrita do relatório final muito mais fácil. Todos os pontos mencionados nas secções "Entrega" e "Critérios de Avaliação" devem ser abordados. - ---- - -### OBS: - -- **Começar de Forma Simples:** O protótipo de processo único (Passo 2) evitará grandes dificuldades mais tarde. - -- **Protocolo de Comunicação:** O protocolo de mensagens deve ser definido o mais cedo possível. A informação exata que um processo envia para outro deve ser clara//simples//consistente. - -- **Debugging:** Debugging de sistemas distribuídos podem ser difíceis. Uma framework de logging (como Log4j 2 ou SLF4J) pode ser usada para registar eventos//alterações de estado nos diferentes processos. - -- **Configuração:** Valores como endereços IP, números de porta ou parâmetros da simulação não devem ser "hardcoded". Um ficheiro de configuração (ex: um ficheiro `.properties` ou `.json`) torna a aplicação mais fácil de executar e testar. diff --git a/main/analysis/HIGH_LOAD_20251207-001113.csv b/main/analysis/HIGH_LOAD_20251207-001113.csv new file mode 100644 index 0000000..0e268bf --- /dev/null +++ b/main/analysis/HIGH_LOAD_20251207-001113.csv @@ -0,0 +1,6 @@ +Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema +1,1784,877,49.16,64.58,61.43,32.29,129.16 +2,1782,363,20.37,53.77,51.01,26.88,107.53 +3,1786,883,49.44,53.09,50.08,26.54,106.17 +4,1845,179,9.70,63.92,60.27,31.96,127.84 +5,1872,953,50.91,65.41,62.16,32.70,130.81 diff --git a/main/analysis/HIGH_LOAD_20251207-001113.txt b/main/analysis/HIGH_LOAD_20251207-001113.txt new file mode 100644 index 0000000..fb8a356 --- /dev/null +++ b/main/analysis/HIGH_LOAD_20251207-001113.txt @@ -0,0 +1,215 @@ +================================================================================ +ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO +================================================================================ +Configuração: simulation-high.properties +Número de Execuções: 5 +Data da Análise: 2025-12-07 00:11:13 + +-------------------------------------------------------------------------------- +MÉTRICAS GLOBAIS +-------------------------------------------------------------------------------- + +Veículos Gerados: + Média: 1813.80 Desvio Padrão: 41.93 + Mediana: 1786.00 IC 95%: [1754.13, 1873.47] + Mín: 1782.00 Máx: 1872.00 + +Veículos Completados: + Média: 651.00 Desvio Padrão: 354.20 + Mediana: 877.00 IC 95%: [146.96, 1155.04] + Mín: 179.00 Máx: 953.00 + +Taxa de Conclusão (%): + Média: 35.92 Desvio Padrão: 19.44 + Mediana: 49.16 IC 95%: [8.25, 63.58] + Mín: 9.70 Máx: 50.91 + +Tempo Médio no Sistema (segundos): + Média: 60.15 Desvio Padrão: 6.17 + Mediana: 63.92 IC 95%: [51.38, 68.93] + Mín: 53.09 Máx: 65.41 + +Tempo Médio de Espera (segundos): + Média: 56.99 Desvio Padrão: 5.93 + Mediana: 60.27 IC 95%: [48.55, 65.43] + Mín: 50.08 Máx: 62.16 + + +-------------------------------------------------------------------------------- +ANÁLISE POR TIPO DE VEÍCULO +-------------------------------------------------------------------------------- + +--- BIKE --- + Contagem de Veículos: + Média: 135.40 Desvio Padrão: 77.66 + Mediana: 167.00 IC 95%: [24.89, 245.91] + Mín: 37.00 Máx: 211.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 55.15 Desvio Padrão: 12.01 + Mediana: 54.23 IC 95%: [38.07, 72.24] + Mín: 43.41 Máx: 74.99 + + +--- LIGHT --- + Contagem de Veículos: + Média: 395.00 Desvio Padrão: 207.62 + Mediana: 540.00 IC 95%: [99.55, 690.45] + Mín: 107.00 Máx: 548.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 59.79 Desvio Padrão: 7.28 + Mediana: 61.58 IC 95%: [49.43, 70.15] + Mín: 50.81 Máx: 69.26 + + +--- HEAVY --- + Contagem de Veículos: + Média: 120.60 Desvio Padrão: 72.95 + Mediana: 142.00 IC 95%: [16.79, 224.41] + Mín: 35.00 Máx: 202.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 49.20 Desvio Padrão: 8.62 + Mediana: 50.31 IC 95%: [36.94, 61.46] + Mín: 35.51 Máx: 58.20 + + +-------------------------------------------------------------------------------- +ANÁLISE POR INTERSEÇÃO +-------------------------------------------------------------------------------- + +--- Cr1 --- + Tamanho Máximo da Fila: + Média: 3.20 Desvio Padrão: 5.54 + Mediana: 1.00 IC 95%: [-4.68, 11.08] + Mín: 0.00 Máx: 13.00 + + Tamanho Médio da Fila: + Média: 3.20 Desvio Padrão: 5.54 + Mediana: 1.00 IC 95%: [-4.68, 11.08] + Mín: 0.00 Máx: 13.00 + + Veículos Processados: + Média: 378.40 Desvio Padrão: 252.94 + Mediana: 512.00 IC 95%: [18.46, 738.34] + Mín: 58.00 Máx: 600.00 + + +--- Cr2 --- + Tamanho Máximo da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Tamanho Médio da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Veículos Processados: + Média: 390.40 Desvio Padrão: 223.14 + Mediana: 409.00 IC 95%: [72.87, 707.93] + Mín: 59.00 Máx: 599.00 + + +--- Cr3 --- + Tamanho Máximo da Fila: + Média: 6.20 Desvio Padrão: 8.67 + Mediana: 0.00 IC 95%: [-6.14, 18.54] + Mín: 0.00 Máx: 18.00 + + Tamanho Médio da Fila: + Média: 6.20 Desvio Padrão: 8.67 + Mediana: 0.00 IC 95%: [-6.14, 18.54] + Mín: 0.00 Máx: 18.00 + + Veículos Processados: + Média: 339.00 Desvio Padrão: 239.34 + Mediana: 416.00 IC 95%: [-1.59, 679.59] + Mín: 57.00 Máx: 622.00 + + +--- Cr4 --- + Tamanho Máximo da Fila: + Média: 0.60 Desvio Padrão: 0.89 + Mediana: 0.00 IC 95%: [-0.67, 1.87] + Mín: 0.00 Máx: 2.00 + + Tamanho Médio da Fila: + Média: 0.60 Desvio Padrão: 0.89 + Mediana: 0.00 IC 95%: [-0.67, 1.87] + Mín: 0.00 Máx: 2.00 + + Veículos Processados: + Média: 123.40 Desvio Padrão: 116.13 + Mediana: 109.00 IC 95%: [-41.85, 288.65] + Mín: 21.00 Máx: 316.00 + + +--- Cr5 --- + Tamanho Máximo da Fila: + Média: 2.40 Desvio Padrão: 1.14 + Mediana: 2.00 IC 95%: [0.78, 4.02] + Mín: 1.00 Máx: 4.00 + + Tamanho Médio da Fila: + Média: 2.40 Desvio Padrão: 1.14 + Mediana: 2.00 IC 95%: [0.78, 4.02] + Mín: 1.00 Máx: 4.00 + + Veículos Processados: + Média: 200.80 Desvio Padrão: 114.19 + Mediana: 261.00 IC 95%: [38.31, 363.29] + Mín: 70.00 Máx: 305.00 + + +--- ExitNode --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 651.00 Desvio Padrão: 354.20 + Mediana: 877.00 IC 95%: [146.96, 1155.04] + Mín: 179.00 Máx: 953.00 + + +-------------------------------------------------------------------------------- +RESUMOS INDIVIDUAIS DAS EXECUÇÕES +-------------------------------------------------------------------------------- + +Execução #1 [simulation-high.properties]: + Gerados: 1784, Completados: 877 (49.2%) + Tempo Médio no Sistema: 64.58s + Tempo Médio de Espera: 61.43s + +Execução #2 [simulation-high.properties]: + Gerados: 1782, Completados: 363 (20.4%) + Tempo Médio no Sistema: 53.77s + Tempo Médio de Espera: 51.01s + +Execução #3 [simulation-high.properties]: + Gerados: 1786, Completados: 883 (49.4%) + Tempo Médio no Sistema: 53.09s + Tempo Médio de Espera: 50.08s + +Execução #4 [simulation-high.properties]: + Gerados: 1845, Completados: 179 (9.7%) + Tempo Médio no Sistema: 63.92s + Tempo Médio de Espera: 60.27s + +Execução #5 [simulation-high.properties]: + Gerados: 1872, Completados: 953 (50.9%) + Tempo Médio no Sistema: 65.41s + Tempo Médio de Espera: 62.16s + +================================================================================ +FIM DO RELATÓRIO +================================================================================ diff --git a/main/analysis/LOW_LOAD_20251207-000957.csv b/main/analysis/LOW_LOAD_20251207-000957.csv new file mode 100644 index 0000000..01d1b2c --- /dev/null +++ b/main/analysis/LOW_LOAD_20251207-000957.csv @@ -0,0 +1,6 @@ +Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema +1,371,187,50.40,42.28,38.65,21.14,84.57 +2,361,263,72.85,29.15,25.29,14.57,58.30 +3,368,197,53.53,38.02,33.95,19.01,76.04 +4,350,239,68.29,32.38,28.36,16.19,64.75 +5,373,212,56.84,23.36,19.96,11.68,46.73 diff --git a/main/analysis/LOW_LOAD_20251207-000957.txt b/main/analysis/LOW_LOAD_20251207-000957.txt new file mode 100644 index 0000000..073c0fb --- /dev/null +++ b/main/analysis/LOW_LOAD_20251207-000957.txt @@ -0,0 +1,209 @@ +================================================================================ +ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO +================================================================================ +Configuração: simulation-low.properties +Número de Execuções: 5 +Data da Análise: 2025-12-07 00:09:57 + +-------------------------------------------------------------------------------- +MÉTRICAS GLOBAIS +-------------------------------------------------------------------------------- + +Veículos Gerados: + Média: 364.60 Desvio Padrão: 9.34 + Mediana: 368.00 IC 95%: [351.30, 377.90] + Mín: 350.00 Máx: 373.00 + +Veículos Completados: + Média: 219.60 Desvio Padrão: 31.19 + Mediana: 212.00 IC 95%: [175.22, 263.98] + Mín: 187.00 Máx: 263.00 + +Taxa de Conclusão (%): + Média: 60.38 Desvio Padrão: 9.71 + Mediana: 56.84 IC 95%: [46.57, 74.20] + Mín: 50.40 Máx: 72.85 + +Tempo Médio no Sistema (segundos): + Média: 33.04 Desvio Padrão: 7.41 + Mediana: 32.38 IC 95%: [22.50, 43.58] + Mín: 23.36 Máx: 42.28 + +Tempo Médio de Espera (segundos): + Média: 29.24 Desvio Padrão: 7.30 + Mediana: 28.36 IC 95%: [18.85, 39.63] + Mín: 19.96 Máx: 38.65 + + +-------------------------------------------------------------------------------- +ANÁLISE POR TIPO DE VEÍCULO +-------------------------------------------------------------------------------- + +--- BIKE --- + Contagem de Veículos: + Média: 41.00 Desvio Padrão: 6.96 + Mediana: 43.00 IC 95%: [31.09, 50.91] + Mín: 33.00 Máx: 50.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 25.91 Desvio Padrão: 3.91 + Mediana: 26.98 IC 95%: [20.35, 31.47] + Mín: 19.60 Máx: 30.06 + + +--- LIGHT --- + Contagem de Veículos: + Média: 134.00 Desvio Padrão: 24.07 + Mediana: 130.00 IC 95%: [99.74, 168.26] + Mín: 104.00 Máx: 167.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 29.34 Desvio Padrão: 6.83 + Mediana: 27.89 IC 95%: [19.62, 39.06] + Mín: 20.73 Máx: 36.42 + + +--- HEAVY --- + Contagem de Veículos: + Média: 44.60 Desvio Padrão: 3.44 + Mediana: 46.00 IC 95%: [39.71, 49.49] + Mín: 40.00 Máx: 48.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 32.11 Desvio Padrão: 15.90 + Mediana: 30.74 IC 95%: [9.48, 54.74] + Mín: 18.09 Máx: 58.73 + + +-------------------------------------------------------------------------------- +ANÁLISE POR INTERSEÇÃO +-------------------------------------------------------------------------------- + +--- Cr1 --- + Tamanho Máximo da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Tamanho Médio da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Veículos Processados: + Média: 63.80 Desvio Padrão: 17.25 + Mediana: 57.00 IC 95%: [39.25, 88.35] + Mín: 48.00 Máx: 91.00 + + +--- Cr2 --- + Tamanho Máximo da Fila: + Média: 0.80 Desvio Padrão: 1.79 + Mediana: 0.00 IC 95%: [-1.75, 3.35] + Mín: 0.00 Máx: 4.00 + + Tamanho Médio da Fila: + Média: 0.80 Desvio Padrão: 1.79 + Mediana: 0.00 IC 95%: [-1.75, 3.35] + Mín: 0.00 Máx: 4.00 + + Veículos Processados: + Média: 56.20 Desvio Padrão: 18.51 + Mediana: 50.00 IC 95%: [29.86, 82.54] + Mín: 35.00 Máx: 78.00 + + +--- Cr3 --- + Tamanho Máximo da Fila: + Média: 1.00 Desvio Padrão: 1.41 + Mediana: 0.00 IC 95%: [-1.01, 3.01] + Mín: 0.00 Máx: 3.00 + + Tamanho Médio da Fila: + Média: 1.00 Desvio Padrão: 1.41 + Mediana: 0.00 IC 95%: [-1.01, 3.01] + Mín: 0.00 Máx: 3.00 + + Veículos Processados: + Média: 63.20 Desvio Padrão: 23.97 + Mediana: 56.00 IC 95%: [29.09, 97.31] + Mín: 41.00 Máx: 104.00 + + +--- Cr4 --- + Tamanho Máximo da Fila: + Média: 1.80 Desvio Padrão: 2.49 + Mediana: 0.00 IC 95%: [-1.74, 5.34] + Mín: 0.00 Máx: 5.00 + + Tamanho Médio da Fila: + Média: 1.80 Desvio Padrão: 2.49 + Mediana: 0.00 IC 95%: [-1.74, 5.34] + Mín: 0.00 Máx: 5.00 + + Veículos Processados: + Média: 51.00 Desvio Padrão: 16.05 + Mediana: 53.00 IC 95%: [28.16, 73.84] + Mín: 31.00 Máx: 70.00 + + +--- Cr5 --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 86.60 Desvio Padrão: 34.20 + Mediana: 65.00 IC 95%: [37.94, 135.26] + Mín: 62.00 Máx: 139.00 + + +--- ExitNode --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 219.60 Desvio Padrão: 31.19 + Mediana: 212.00 IC 95%: [175.22, 263.98] + Mín: 187.00 Máx: 263.00 + + +-------------------------------------------------------------------------------- +RESUMOS INDIVIDUAIS DAS EXECUÇÕES +-------------------------------------------------------------------------------- + +Execução #1 [simulation-low.properties]: + Gerados: 371, Completados: 187 (50.4%) + Tempo Médio no Sistema: 42.28s + Tempo Médio de Espera: 38.65s + +Execução #2 [simulation-low.properties]: + Gerados: 361, Completados: 263 (72.9%) + Tempo Médio no Sistema: 29.15s + Tempo Médio de Espera: 25.29s + +Execução #3 [simulation-low.properties]: + Gerados: 368, Completados: 197 (53.5%) + Tempo Médio no Sistema: 38.02s + Tempo Médio de Espera: 33.95s + +Execução #4 [simulation-low.properties]: + Gerados: 350, Completados: 239 (68.3%) + Tempo Médio no Sistema: 32.38s + Tempo Médio de Espera: 28.36s + +Execução #5 [simulation-low.properties]: + Gerados: 373, Completados: 212 (56.8%) + Tempo Médio no Sistema: 23.36s + Tempo Médio de Espera: 19.96s + +================================================================================ +FIM DO RELATÓRIO +================================================================================ diff --git a/main/analysis/MEDIUM_LOAD_20251207-001034.csv b/main/analysis/MEDIUM_LOAD_20251207-001034.csv new file mode 100644 index 0000000..50a86c8 --- /dev/null +++ b/main/analysis/MEDIUM_LOAD_20251207-001034.csv @@ -0,0 +1,6 @@ +Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema +1,950,416,43.79,49.34,45.70,24.67,98.68 +2,886,480,54.18,35.08,31.69,17.54,70.16 +3,954,535,56.08,43.76,40.30,21.88,87.51 +4,948,354,37.34,41.68,37.96,20.84,83.37 +5,898,312,34.74,52.56,49.26,26.28,105.13 diff --git a/main/analysis/MEDIUM_LOAD_20251207-001034.txt b/main/analysis/MEDIUM_LOAD_20251207-001034.txt new file mode 100644 index 0000000..3b57656 --- /dev/null +++ b/main/analysis/MEDIUM_LOAD_20251207-001034.txt @@ -0,0 +1,203 @@ +================================================================================ +ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO +================================================================================ +Configuração: simulation-medium.properties +Número de Execuções: 5 +Data da Análise: 2025-12-07 00:10:34 + +-------------------------------------------------------------------------------- +MÉTRICAS GLOBAIS +-------------------------------------------------------------------------------- + +Veículos Gerados: + Média: 927.20 Desvio Padrão: 32.48 + Mediana: 948.00 IC 95%: [880.97, 973.43] + Mín: 886.00 Máx: 954.00 + +Veículos Completados: + Média: 419.40 Desvio Padrão: 90.64 + Mediana: 416.00 IC 95%: [290.42, 548.38] + Mín: 312.00 Máx: 535.00 + +Taxa de Conclusão (%): + Média: 45.23 Desvio Padrão: 9.64 + Mediana: 43.79 IC 95%: [31.50, 58.95] + Mín: 34.74 Máx: 56.08 + +Tempo Médio no Sistema (segundos): + Média: 44.48 Desvio Padrão: 6.81 + Mediana: 43.76 IC 95%: [34.79, 54.18] + Mín: 35.08 Máx: 52.56 + +Tempo Médio de Espera (segundos): + Média: 40.98 Desvio Padrão: 6.83 + Mediana: 40.30 IC 95%: [31.26, 50.71] + Mín: 31.69 Máx: 49.26 + + +-------------------------------------------------------------------------------- +ANÁLISE POR TIPO DE VEÍCULO +-------------------------------------------------------------------------------- + +--- BIKE --- + Contagem de Veículos: + Média: 75.80 Desvio Padrão: 15.96 + Mediana: 71.00 IC 95%: [53.09, 98.51] + Mín: 56.00 Máx: 95.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 42.34 Desvio Padrão: 10.81 + Mediana: 39.70 IC 95%: [26.96, 57.72] + Mín: 31.96 Máx: 55.19 + + +--- LIGHT --- + Contagem de Veículos: + Média: 263.20 Desvio Padrão: 58.29 + Mediana: 265.00 IC 95%: [180.25, 346.15] + Mín: 204.00 Máx: 344.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 39.13 Desvio Padrão: 6.35 + Mediana: 38.08 IC 95%: [30.09, 48.17] + Mín: 30.47 Máx: 47.99 + + +--- HEAVY --- + Contagem de Veículos: + Média: 80.40 Desvio Padrão: 19.11 + Mediana: 80.00 IC 95%: [53.20, 107.60] + Mín: 52.00 Máx: 102.00 + + Tempo Médio no Sistema (segundos): Sem dados + + Tempo Médio de Espera (segundos): + Média: 48.02 Desvio Padrão: 30.99 + Mediana: 34.44 IC 95%: [3.92, 92.11] + Mín: 32.46 Máx: 103.40 + + +-------------------------------------------------------------------------------- +ANÁLISE POR INTERSEÇÃO +-------------------------------------------------------------------------------- + +--- Cr1 --- + Tamanho Máximo da Fila: + Média: 5.60 Desvio Padrão: 11.44 + Mediana: 0.00 IC 95%: [-10.67, 21.87] + Mín: 0.00 Máx: 26.00 + + Tamanho Médio da Fila: + Média: 5.60 Desvio Padrão: 11.44 + Mediana: 0.00 IC 95%: [-10.67, 21.87] + Mín: 0.00 Máx: 26.00 + + Veículos Processados: + Média: 156.00 Desvio Padrão: 122.81 + Mediana: 98.00 IC 95%: [-18.76, 330.76] + Mín: 35.00 Máx: 306.00 + + +--- Cr2 --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 172.00 Desvio Padrão: 121.88 + Mediana: 116.00 IC 95%: [-1.44, 345.44] + Mín: 66.00 Máx: 322.00 + + +--- Cr3 --- + Tamanho Máximo da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Tamanho Médio da Fila: + Média: 0.60 Desvio Padrão: 1.34 + Mediana: 0.00 IC 95%: [-1.31, 2.51] + Mín: 0.00 Máx: 3.00 + + Veículos Processados: + Média: 168.40 Desvio Padrão: 133.38 + Mediana: 121.00 IC 95%: [-21.40, 358.20] + Mín: 48.00 Máx: 326.00 + + +--- Cr4 --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 71.80 Desvio Padrão: 20.39 + Mediana: 77.00 IC 95%: [42.79, 100.81] + Mín: 38.00 Máx: 92.00 + + +--- Cr5 --- + Tamanho Máximo da Fila: + Média: 3.60 Desvio Padrão: 3.85 + Mediana: 2.00 IC 95%: [-1.87, 9.07] + Mín: 0.00 Máx: 10.00 + + Tamanho Médio da Fila: + Média: 3.60 Desvio Padrão: 3.85 + Mediana: 2.00 IC 95%: [-1.87, 9.07] + Mín: 0.00 Máx: 10.00 + + Veículos Processados: + Média: 150.60 Desvio Padrão: 43.37 + Mediana: 126.00 IC 95%: [88.88, 212.32] + Mín: 116.00 Máx: 209.00 + + +--- ExitNode --- + Tamanho Máximo da Fila: Sem dados + + Tamanho Médio da Fila: Sem dados + + Veículos Processados: + Média: 419.40 Desvio Padrão: 90.64 + Mediana: 416.00 IC 95%: [290.42, 548.38] + Mín: 312.00 Máx: 535.00 + + +-------------------------------------------------------------------------------- +RESUMOS INDIVIDUAIS DAS EXECUÇÕES +-------------------------------------------------------------------------------- + +Execução #1 [simulation-medium.properties]: + Gerados: 950, Completados: 416 (43.8%) + Tempo Médio no Sistema: 49.34s + Tempo Médio de Espera: 45.70s + +Execução #2 [simulation-medium.properties]: + Gerados: 886, Completados: 480 (54.2%) + Tempo Médio no Sistema: 35.08s + Tempo Médio de Espera: 31.69s + +Execução #3 [simulation-medium.properties]: + Gerados: 954, Completados: 535 (56.1%) + Tempo Médio no Sistema: 43.76s + Tempo Médio de Espera: 40.30s + +Execução #4 [simulation-medium.properties]: + Gerados: 948, Completados: 354 (37.3%) + Tempo Médio no Sistema: 41.68s + Tempo Médio de Espera: 37.96s + +Execução #5 [simulation-medium.properties]: + Gerados: 898, Completados: 312 (34.7%) + Tempo Médio no Sistema: 52.56s + Tempo Médio de Espera: 49.26s + +================================================================================ +FIM DO RELATÓRIO +================================================================================ diff --git a/main/graphing.py b/main/graphing.py new file mode 100644 index 0000000..9bda368 --- /dev/null +++ b/main/graphing.py @@ -0,0 +1,169 @@ +import pandas as pd +import matplotlib.pyplot as plt +import glob +import os + +# Find CSV files using glob +def load_latest_csv(pattern): + """Load the most recent CSV file matching the pattern""" + files = glob.glob(pattern) + if not files: + print(f"Warning: No files found matching '{pattern}'") + return None + # Sort by modification time, get the latest + latest_file = max(files, key=os.path.getmtime) + print(f"Loading: {latest_file}") + return pd.read_csv(latest_file) + +# Carregar dados +print("Looking for analysis files...") +low = load_latest_csv('analysis/LOW_LOAD_*.csv') +medium = load_latest_csv('analysis/MEDIUM_LOAD_*.csv') +high = load_latest_csv('analysis/HIGH_LOAD_*.csv') + +# Check if we have all data +if low is None or medium is None or high is None: + print("\nError: Missing analysis files!") + print("Please run the batch analysis first:") + exit(1) + +# Print available columns for debugging +print("\nAvailable columns in LOW_LOAD CSV:") +print(low.columns.tolist()) + +# Create output directory for graphs +os.makedirs('graphs', exist_ok=True) + +# 1. Gráfico: Dwelling Time vs Load +plt.figure(figsize=(10, 6)) +dwelling_times = [ + low['TempoMédioSistema'].mean(), + medium['TempoMédioSistema'].mean(), + high['TempoMédioSistema'].mean() +] +plt.bar(['Baixa', 'Média', 'Alta'], dwelling_times, color=['green', 'orange', 'red']) +plt.ylabel('Tempo Médio no Sistema (s)') +plt.title('Desempenho do Sistema vs Carga') +plt.xlabel('Cenário de Carga') +plt.grid(axis='y', alpha=0.3) +for i, v in enumerate(dwelling_times): + plt.text(i, v + 1, f'{v:.2f}s', ha='center', va='bottom') +plt.savefig('graphs/dwelling_time_comparison.png', dpi=300, bbox_inches='tight') +print("\nGraph saved: graphs/dwelling_time_comparison.png") +plt.close() + +# 2. Gráfico: Completion Rate vs Load +plt.figure(figsize=(10, 6)) +completion_rates = [ + low['TaxaConclusão'].mean(), + medium['TaxaConclusão'].mean(), + high['TaxaConclusão'].mean() +] +plt.bar(['Baixa', 'Média', 'Alta'], completion_rates, color=['green', 'orange', 'red']) +plt.ylabel('Taxa de Conclusão (%)') +plt.title('Taxa de Conclusão de Veículos vs Carga') +plt.xlabel('Cenário de Carga') +plt.grid(axis='y', alpha=0.3) +plt.ylim(0, 100) +for i, v in enumerate(completion_rates): + plt.text(i, v + 2, f'{v:.1f}%', ha='center', va='bottom') +plt.savefig('graphs/completion_rate_comparison.png', dpi=300, bbox_inches='tight') +print("Graph saved: graphs/completion_rate_comparison.png") +plt.close() + +# 3. Gráfico: Waiting Time vs Load +plt.figure(figsize=(10, 6)) +waiting_times = [ + low['TempoMédioEspera'].mean(), + medium['TempoMédioEspera'].mean(), + high['TempoMédioEspera'].mean() +] +plt.bar(['Baixa', 'Média', 'Alta'], waiting_times, color=['green', 'orange', 'red']) +plt.ylabel('Tempo Médio de Espera (s)') +plt.title('Tempo Médio de Espera vs Carga') +plt.xlabel('Cenário de Carga') +plt.grid(axis='y', alpha=0.3) +for i, v in enumerate(waiting_times): + plt.text(i, v + 1, f'{v:.2f}s', ha='center', va='bottom') +plt.savefig('graphs/waiting_time_comparison.png', dpi=300, bbox_inches='tight') +print("Graph saved: graphs/waiting_time_comparison.png") +plt.close() + +# 4. Gráfico: Summary Statistics +fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 10)) +loads = ['Baixa', 'Média', 'Alta'] + +# Vehicles generated +ax1.bar(loads, [low['VeículosGerados'].mean(), medium['VeículosGerados'].mean(), high['VeículosGerados'].mean()], color=['green', 'orange', 'red']) +ax1.set_title('Veículos Gerados') +ax1.set_ylabel('Quantidade') +ax1.grid(axis='y', alpha=0.3) + +# Vehicles completed +ax2.bar(loads, [low['VeículosCompletados'].mean(), medium['VeículosCompletados'].mean(), high['VeículosCompletados'].mean()], color=['green', 'orange', 'red']) +ax2.set_title('Veículos Concluídos') +ax2.set_ylabel('Quantidade') +ax2.grid(axis='y', alpha=0.3) + +# Min/Max dwelling time +x = range(3) +width = 0.35 +ax3.bar([i - width/2 for i in x], [low['TempoMínimoSistema'].mean(), medium['TempoMínimoSistema'].mean(), high['TempoMínimoSistema'].mean()], width, label='Mín', color='lightblue') +ax3.bar([i + width/2 for i in x], [low['TempoMáximoSistema'].mean(), medium['TempoMáximoSistema'].mean(), high['TempoMáximoSistema'].mean()], width, label='Máx', color='darkblue') +ax3.set_title('Tempo no Sistema Mín/Máx') +ax3.set_ylabel('Tempo (s)') +ax3.set_xticks(x) +ax3.set_xticklabels(loads) +ax3.legend() +ax3.grid(axis='y', alpha=0.3) + +# Performance summary +metrics = ['Tempo no\nSistema', 'Tempo de\nEspera', 'Taxa de\nConclusão'] +low_vals = [low['TempoMédioSistema'].mean(), low['TempoMédioEspera'].mean(), low['TaxaConclusão'].mean()] +med_vals = [medium['TempoMédioSistema'].mean(), medium['TempoMédioEspera'].mean(), medium['TaxaConclusão'].mean()] +high_vals = [high['TempoMédioSistema'].mean(), high['TempoMédioEspera'].mean(), high['TaxaConclusão'].mean()] + +x = range(len(metrics)) +width = 0.25 +ax4.bar([i - width for i in x], low_vals, width, label='Baixa', color='green') +ax4.bar(x, med_vals, width, label='Média', color='orange') +ax4.bar([i + width for i in x], high_vals, width, label='Alta', color='red') +ax4.set_title('Resumo de Desempenho') +ax4.set_xticks(x) +ax4.set_xticklabels(metrics) +ax4.legend() +ax4.grid(axis='y', alpha=0.3) + +plt.tight_layout() +plt.savefig('graphs/summary_statistics.png', dpi=300, bbox_inches='tight') +print("Graph saved: graphs/summary_statistics.png") +plt.close() + +# Print summary statistics +print("\n" + "="*60) +print("SUMMARY STATISTICS") +print("="*60) +print(f"\nLOW LOAD:") +print(f" Avg Dwelling Time: {low['TempoMédioSistema'].mean():.2f}s") +print(f" Avg Waiting Time: {low['TempoMédioEspera'].mean():.2f}s") +print(f" Completion Rate: {low['TaxaConclusão'].mean():.1f}%") +print(f" Vehicles Generated: {low['VeículosGerados'].mean():.0f}") +print(f" Vehicles Completed: {low['VeículosCompletados'].mean():.0f}") + +print(f"\nMEDIUM LOAD:") +print(f" Avg Dwelling Time: {medium['TempoMédioSistema'].mean():.2f}s") +print(f" Avg Waiting Time: {medium['TempoMédioEspera'].mean():.2f}s") +print(f" Completion Rate: {medium['TaxaConclusão'].mean():.1f}%") +print(f" Vehicles Generated: {medium['VeículosGerados'].mean():.0f}") +print(f" Vehicles Completed: {medium['VeículosCompletados'].mean():.0f}") + +print(f"\nHIGH LOAD:") +print(f" Avg Dwelling Time: {high['TempoMédioSistema'].mean():.2f}s") +print(f" Avg Waiting Time: {high['TempoMédioEspera'].mean():.2f}s") +print(f" Completion Rate: {high['TaxaConclusão'].mean():.1f}%") +print(f" Vehicles Generated: {high['VeículosGerados'].mean():.0f}") +print(f" Vehicles Completed: {high['VeículosCompletados'].mean():.0f}") + +print("\n" + "="*60) +print("All graphs saved in 'graphs/' directory!") +print("="*60) \ No newline at end of file diff --git a/main/graphs/completion_rate_comparison.png b/main/graphs/completion_rate_comparison.png new file mode 100644 index 0000000..a86957b Binary files /dev/null and b/main/graphs/completion_rate_comparison.png differ diff --git a/main/graphs/dwelling_time_comparison.png b/main/graphs/dwelling_time_comparison.png new file mode 100644 index 0000000..d56ec41 Binary files /dev/null and b/main/graphs/dwelling_time_comparison.png differ diff --git a/main/graphs/summary_statistics.png b/main/graphs/summary_statistics.png new file mode 100644 index 0000000..c3dd6c4 Binary files /dev/null and b/main/graphs/summary_statistics.png differ diff --git a/main/graphs/waiting_time_comparison.png b/main/graphs/waiting_time_comparison.png new file mode 100644 index 0000000..8d03cc8 Binary files /dev/null and b/main/graphs/waiting_time_comparison.png differ diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java index a7e771a..a5868e5 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,65 +27,63 @@ import sd.protocol.MessageProtocol; import sd.protocol.SocketConnection; /** - * Represents the final Exit Node ("S") of the distributed traffic simulation. - * - * This process acts as a "sink" for all vehicles completing their routes. - * Unlike intersections, it does not route traffic but focuses on data collection. - * Its primary responsibilities are: - * - Accepting TCP connections from intersection processes. - * - Receiving {@link Vehicle} objects via the network. - * - Calculating final performance metrics (Total System Time, Waiting Time, Crossing Time). - * - Aggregating statistics by {@link VehicleType}. - * - Reporting real-time data to the Dashboard service via {@link SocketClient}. - * - Generating a final statistical report upon shutdown. + * Ponto terminal da malha de simulação (Sink Node). + *
+ * Este processo atua como o sumidouro da rede de filas. A sua função primária é + * a coleta de telemetria final. Diferente das interseções, não encaminha veículos; + * em vez disso, retira-os do sistema, calcula as métricas de latência "end-to-end" + * (tempo no sistema, tempo de espera acumulado) e reporta ao Dashboard. + *
+ * Arquitetura de Concorrência:
+ * Utiliza um {@link ServerSocket} multithreaded para aceitar conexões simultâneas de
+ * qualquer interseção de fronteira (Cr1, Cr5, etc.) que envie veículos para fora da malha.
*/
public class ExitNodeProcess {
// --- Configuration and Networking ---
private final SimulationConfig config;
private ServerSocket serverSocket;
-
- /**
- * Thread pool to handle concurrent incoming connections from multiple intersections.
- */
+
+ /** Pool de threads elástica para tratamento de conexões de entrada. */
private final ExecutorService connectionHandlerPool;
- /**
- * Volatile flag to control the lifecycle of the process.
- */
+ // 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 de I/O e lógica). */
private volatile boolean running;
- /** * Simulation start time (in milliseconds) used to calculate relative arrival times.
- * This is synchronized with the Coordinator.
- */
+ /** Instante de início da simulação (milissegundos) sincronizado com o Coordenador. */
private long simulationStartMillis;
- // --- Statistics and Metrics ---
-
- /** Total count of vehicles that have successfully completed their route. */
+ /** Contador atómico (via synchronized) de throughput total. */
private int totalVehiclesReceived;
- /** Accumulated time (in seconds) that all vehicles spent in the system. */
+ /** Tempo acumulado no sistema (System Time) de todos os veículos. */
private double totalSystemTime;
- /** Accumulated time (in seconds) that all vehicles spent waiting at red lights. */
+ /** Tempo acumulado em espera (Waiting Time) de todos os veículos. */
private double totalWaitingTime;
- /** Accumulated time (in seconds) that all vehicles spent crossing intersections. */
+ /** Tempo acumulado em travessia (Service Time) de todos os veículos. */
private double totalCrossingTime;
- /** Counter of vehicles separated by {@link VehicleType}. */
+ /** Agregação por categoria de veículo. */
private final Map
+ * Mantém o socket aberto e consome mensagens num loop até que a conexão seja fechada
+ * pelo remetente. Responsável pela desserialização polimórfica (JSON/Gson).
+ * * @param clientSocket O socket conectado.
*/
private void handleIncomingConnection(Socket clientSocket) {
String clientAddress = clientSocket.getInetAddress().getHostAddress();
@@ -209,14 +337,14 @@ public class ExitNodeProcess {
" from " + message.getSourceNode());
if (message.getType() == MessageType.SIMULATION_START) {
- // Coordinator sends start time - use it instead of our local start
+ // Sincronização de relógio com o Coordenador
simulationStartMillis = ((Number) message.getPayload()).longValue();
System.out.println("[Exit] Simulation start time synchronized");
} else if (message.getType() == MessageType.VEHICLE_TRANSFER) {
Object payload = message.getPayload();
System.out.println("[Exit] Payload type: " + payload.getClass().getName());
- // Handle Gson LinkedHashMap
+ // Tratamento de artefatos de desserialização do Gson (LinkedTreeMap -> POJO)
Vehicle vehicle;
if (payload instanceof com.google.gson.internal.LinkedTreeMap ||
payload instanceof java.util.LinkedHashMap) {
@@ -249,26 +377,21 @@ public class ExitNodeProcess {
}
/**
- * Processes a vehicle that has completed its route.
- * * This method is {@code synchronized} to ensure thread safety when updating
- * shared statistical counters. It calculates final metrics:
- * - System Time: Total time since entry.
- * - Wait Time: Total time spent at red lights.
- * - Crossing Time: Total time spent crossing intersections.
- * * It triggers an immediate update to the Dashboard.
- * * @param vehicle The vehicle that has arrived at the Exit Node.
+ * Processa atomicamente a saída de um veículo.
+ *
+ * Secção Crítica: Método {@code synchronized} para garantir que a atualização
+ * das estatísticas globais (totalSystemTime, contadores) é atómica, prevenindo
+ * Race Conditions quando múltiplos veículos chegam simultaneamente de interseções diferentes.
+ * * @param vehicle O veículo que completou a 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();
+ // Cálculo de métricas finais baseadas no tempo virtual de simulação acumulado no veículo
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;
totalWaitingTime += waitTime;
totalCrossingTime += crossingTime;
@@ -280,15 +403,20 @@ 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);
- // Send stats after every vehicle to ensure dashboard updates quickly
+ // Logging estruturado
+ EventLogger.getInstance().logVehicle(EventType.VEHICLE_EXITED, "ExitNode", vehicle.getId(),
+ String.format("Completed - System: %.2fs, Wait: %.2fs, Crossing: %.2fs", systemTime, waitTime,
+ crossingTime));
+
+ // Finaliza o trace individual do veículo
+ VehicleTracer.getInstance().logExit(vehicle, systemTime);
+
+ // Push imediato para o Dashboard para visualização em tempo real
sendStatsToDashboard();
}
/**
- * Sends current aggregated statistics to the Dashboard.
- * * Constructs a {@link StatsUpdatePayload} containing global counters and
- * per-type metrics, converting seconds to milliseconds as expected by the frontend.
- * Wraps the payload in a {@link Message} and sends it via the dashboard client.
+ * Constrói e transmite o DTO de atualização de estatísticas.
*/
private void sendStatsToDashboard() {
if (dashboardClient == null || !dashboardClient.isConnected()) {
@@ -299,29 +427,28 @@ public class ExitNodeProcess {
// Create stats payload
StatsUpdatePayload payload = new StatsUpdatePayload();
- // Set global stats - convert seconds to milliseconds
+ // Set global stats - convert seconds to milliseconds for display consistency
payload.setTotalVehiclesCompleted(totalVehiclesReceived);
- payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); // s -> ms
- payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); // s -> ms
+ payload.setTotalSystemTime((long) (totalSystemTime * 1000.0));
+ payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0));
- // Set intersection-like stats so it shows up correctly in the dashboard table
+ // Hack: Usar campos de interseção para mostrar throughput no dashboard
payload.setIntersectionArrivals(totalVehiclesReceived);
payload.setIntersectionDepartures(totalVehiclesReceived);
payload.setIntersectionQueueSize(0);
- // Set vehicle type stats
+ // Detailed breakdown
Map
+ * Esta classe implementa a lógica de uma interseção rodoviária utilizando uma
+ * arquitetura híbrida:
+ *
+ * A sincronização entre a chegada assíncrona de veículos (Rede) e o
+ * processamento determinístico (DES) é gerida através de estruturas de dados
+ * concorrentes e bloqueios justos (Fair Locks).
*/
public class IntersectionProcess {
@@ -42,41 +60,56 @@ public class IntersectionProcess {
private ServerSocket serverSocket;
+ /**
+ * Tabela de encaminhamento dinâmico para conexões de saída (Next-Hop Cache).
+ */
private final Map
+ * Executa o ciclo "Fetch-Decode-Execute":
+ *
+ * Encaminha o evento para a lógica de negócio específica baseada no tipo
+ * {@link DESEventType}.
+ *
+ * @param event O evento de simulação a ser processado.
+ */
+ private void processEvent(SimulationEvent event) {
+ try {
+ switch (event.getType()) {
+ case TRAFFIC_LIGHT_CHANGE:
+ handleTrafficLightChangeEvent(event);
+ break;
+
+ case VEHICLE_ARRIVAL:
+ // Chegadas são tratadas reativamente via Socket, mas eventos podem ser usados
+ // para métricas
+ 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();
+ }
+ }
+
+ /**
+ * Gere a máquina de estados dos semáforos.
+ *
+ * O fluxo de execução é o seguinte:
+ *
+ * Implementa uma lógica de previsão ("Look-ahead"):
+ *
+ * Atualmente serve como placeholder para lógica futura de animação ou
+ * ocupação de zonas críticas na interseção.
+ *
+ * @param event O evento de início de travessia.
+ */
+ private void handleVehicleCrossingStartEvent(SimulationEvent event) {
+ // Placeholder para lógica futura de animação ou ocupação de zona crítica
+ eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
+ "Vehicle crossing started at time " + event.getTimestamp());
+ }
+
+ /**
+ * Finaliza a lógica de travessia e inicia a transferência (handover) para o
+ * próximo nó.
+ *
+ * Este método é invocado quando o tempo de travessia expira no relógio virtual.
+ * Executa as seguintes ações:
+ *
+ * Inclui a criação de semáforos, configuração de encaminhamento e conexão ao
+ * Dashboard.
+ */
public void initialize() {
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
@@ -137,7 +506,7 @@ public class IntersectionProcess {
}
/**
- * Establishes connection to the dashboard server for statistics reporting.
+ * Estabelece a conexão com o Dashboard para envio de telemetria em tempo real.
*/
private void connectToDashboard() {
try {
@@ -161,10 +530,7 @@ 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.
+ * Inicializa os semáforos da interseção com base na configuração carregada.
*/
private void createTrafficLights() {
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
@@ -193,6 +559,13 @@ public class IntersectionProcess {
}
}
+ /**
+ * Obtém a configuração específica para esta interseção a partir da configuração
+ * global.
+ *
+ * @return O objeto de configuração da interseção.
+ * @throws RuntimeException Se a configuração estiver em falta.
+ */
private SimulationConfig.IntersectionConfig getIntersectionConfig() {
if (config.getNetworkConfig() == null || config.getNetworkConfig().getIntersections() == null) {
throw new RuntimeException("Network configuration not loaded or empty.");
@@ -203,6 +576,11 @@ public class IntersectionProcess {
.orElseThrow(() -> new RuntimeException("Intersection config not found for " + intersectionId));
}
+ /**
+ * Configura a tabela de encaminhamento (routing) da interseção.
+ *
+ * Define para cada destino qual a direção de saída (semáforo) correspondente.
+ */
private void configureRouting() {
System.out.println("\n[" + intersectionId + "] Configuring routing...");
@@ -224,10 +602,10 @@ public class IntersectionProcess {
}
/**
- * Requests permission for a traffic light to turn green.
- * Blocks until permission is granted (no other light is green).
- *
- * @param direction The direction requesting green light
+ * Primitiva de bloqueio: Solicita acesso exclusivo à zona crítica da
+ * interseção.
+ *
+ * @param direction A direção que solicita passagem.
*/
public void requestGreenLight(String direction) {
trafficCoordinationLock.lock();
@@ -235,9 +613,9 @@ public class IntersectionProcess {
}
/**
- * Releases the green light permission, allowing another light to turn green.
- *
- * @param direction The direction releasing green light
+ * Primitiva de bloqueio: Liberta o acesso exclusivo à zona crítica.
+ *
+ * @param direction A direção que está a libertar a passagem.
*/
public void releaseGreenLight(String direction) {
if (direction.equals(currentGreenDirection)) {
@@ -247,34 +625,89 @@ public class IntersectionProcess {
}
/**
- * Starts all traffic light threads.
+ * Inicializa o estado dos semáforos no arranque da simulação (t=0).
+ *
+ * Garante que apenas um semáforo começa em Verde e os restantes em Vermelho,
+ * agendando os eventos iniciais na fila do DES.
*/
- 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);
+ // Lógica de arranque: Primeiro da lista = Verde, outros = Vermelho
+ boolean isFirstLight = intersection.getTrafficLights().indexOf(light) == 0;
+ TrafficLightState initialState = isFirstLight ? TrafficLightState.GREEN : TrafficLightState.RED;
+ light.changeState(initialState);
- trafficLightPool.submit(lightTask);
+ // Agenda a primeira transição
+ 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.
+ * Encaminhamento de rede: Serializa e envia o objeto veículo para o próximo nó.
+ *
+ * Calcula também o tempo de viagem virtual entre nós (Edge Weight).
*
- * @param vehicle The vehicle that has crossed this intersection.
+ * @param vehicle O veículo a ser enviado.
*/
public void sendVehicleToNextDestination(Vehicle vehicle) {
String nextDestination = vehicle.getCurrentDestination();
+ // Cálculo de latência de viagem (Edge Weight)
+ double baseTime = config.getBaseTravelTime();
+ double multiplier = 1.0;
+ switch (vehicle.getType()) {
+ case BIKE -> multiplier = config.getBikeTravelTimeMultiplier();
+ case HEAVY -> multiplier = config.getHeavyTravelTimeMultiplier();
+ default -> multiplier = 1.0;
+ }
+ double travelTime = baseTime * multiplier;
+
+ System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n",
+ intersectionId, vehicle.getId(), nextDestination, travelTime);
+
+ recordVehicleDeparture();
+
+ // Envio imediato (o delay de viagem é implícito no tempo de chegada no próximo
+ // nó ou simulado aqui)
+ sendVehicleImmediately(vehicle, nextDestination);
+ }
+
+ /**
+ * Envia o veículo imediatamente para o próximo nó via conexão TCP persistente.
+ *
+ * @param vehicle O veículo a ser enviado.
+ * @param nextDestination O identificador do próximo nó destino.
+ */
+ private void sendVehicleImmediately(Vehicle vehicle, String nextDestination) {
try {
- // Get or create connection to next destination
+ // Lazy loading da conexão
SocketConnection connection = getOrCreateConnection(nextDestination);
- // Create and send message using Message class
+ // Encapsulamento da mensagem
MessageProtocol message = new Message(
MessageType.VEHICLE_TRANSFER,
intersectionId,
@@ -284,13 +717,8 @@ public class IntersectionProcess {
connection.sendMessage(message);
- System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
- " to " + nextDestination);
-
- // Record departure for statistics
- recordVehicleDeparture();
-
- // Note: vehicle route is advanced when it arrives at the next intersection
+ System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() +
+ " arrived at " + nextDestination + " (msg sent)");
} catch (IOException | InterruptedException e) {
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
@@ -299,12 +727,15 @@ public class IntersectionProcess {
}
/**
- * Gets an existing connection to a destination or creates a new one.
+ * Obtém ou cria uma conexão para o destino especificado (Singleton por
+ * destino).
+ *
+ * Este método é thread-safe.
*
- * @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 identificador do nó destino.
+ * @return A conexão TCP estabelecida.
+ * @throws IOException Se ocorrer um erro de I/O na criação da conexão.
+ * @throws InterruptedException Se a thread for interrompida durante a espera.
*/
private synchronized SocketConnection getOrCreateConnection(String destinationId)
throws IOException, InterruptedException {
@@ -324,10 +755,10 @@ public class IntersectionProcess {
}
/**
- * Gets the host address for a destination node from configuration.
+ * Resolve o hostname ou endereço IP para um determinado destino.
*
- * @param destinationId The destination node ID.
- * @return The host address.
+ * @param destinationId O ID do destino.
+ * @return O endereço do host.
*/
private String getHostForDestination(String destinationId) {
if (destinationId.equals("S")) {
@@ -338,10 +769,10 @@ public class IntersectionProcess {
}
/**
- * Gets the port number for a destination node from configuration.
+ * Resolve a porta TCP para um determinado destino.
*
- * @param destinationId The destination node ID.
- * @return The port number.
+ * @param destinationId O ID do destino.
+ * @return O número da porta.
*/
private int getPortForDestination(String destinationId) {
if (destinationId.equals("S")) {
@@ -352,10 +783,11 @@ public class IntersectionProcess {
}
/**
- * Starts the server socket and begins accepting incoming connections.
- * This is the main listening loop of the process.
+ * Inicia o servidor e o loop de aceitação de conexões.
+ *
+ * Este método bloqueia a thread chamadora durante a execução do servidor.
*
- * @throws IOException If the server socket cannot be created.
+ * @throws IOException Se ocorrer um erro ao fazer bind da porta.
*/
public void start() throws IOException {
int port = config.getIntersectionPort(intersectionId);
@@ -364,15 +796,17 @@ 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
+ // Background task para telemetria
statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS);
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
- // Main accept loop
+ // Loop principal de aceitação de conexões
while (running) {
try {
Socket clientSocket = serverSocket.accept();
@@ -380,13 +814,12 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress());
- // Check running flag again before handling
if (!running) {
clientSocket.close();
break;
}
- // **Set timeout before submitting to handler**
+ // Configura timeout para evitar bloqueios infinitos em leitura
try {
clientSocket.setSoTimeout(1000);
} catch (java.net.SocketException e) {
@@ -395,13 +828,12 @@ public class IntersectionProcess {
continue;
}
- // Handle each connection in a separate thread
+ // Delega processamento para thread pool (NIO style)
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
} catch (IOException e) {
- // Expected when serverSocket.close() is called during shutdown
if (!running) {
- break; // Normal shutdown
+ break; // Shutdown normal
}
System.err.println("[" + intersectionId + "] Error accepting connection: " +
e.getMessage());
@@ -410,10 +842,13 @@ public class IntersectionProcess {
}
/**
- * Handles an incoming connection from another process.
- * Continuously listens for vehicle transfer messages.
+ * Lógica de tratamento de conexões de entrada (Consumer).
+ *
+ * Lê continuamente do socket até que a conexão seja fechada, processando
+ * mensagens
+ * de chegada de veículos ou comandos de simulação.
*
- * @param clientSocket The accepted socket connection.
+ * @param clientSocket O socket do cliente conectado.
*/
private void handleIncomingConnection(Socket clientSocket) {
try {
@@ -429,27 +864,24 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress());
- // Continuously receive messages while connection is active
while (running && connection.isConnected()) {
try {
MessageProtocol message = connection.receiveMessage();
- // Handle simulation start time synchronization
if (message.getType() == MessageType.SIMULATION_START) {
System.out.println("[" + intersectionId + "] Simulation start time synchronized");
continue;
}
- // Accept both VEHICLE_TRANSFER and VEHICLE_SPAWN (from coordinator)
if (message.getType() == MessageType.VEHICLE_TRANSFER ||
message.getType() == MessageType.VEHICLE_SPAWN) {
- // Cast payload to Vehicle - handle Gson deserialization
+
+ // Lógica de desserialização polimórfica (Vehicle ou Map)
Vehicle vehicle;
Object payload = message.getPayload();
if (payload instanceof Vehicle) {
vehicle = (Vehicle) payload;
} else if (payload instanceof java.util.Map) {
- // Gson deserialized as LinkedHashMap - re-serialize and deserialize as Vehicle
com.google.gson.Gson gson = new com.google.gson.Gson();
String json = gson.toJson(payload);
vehicle = gson.fromJson(json, Vehicle.class);
@@ -461,39 +893,37 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] Received vehicle: " +
vehicle.getId() + " from " + message.getSourceNode());
- // Advance vehicle to next destination in its route
+ // Lógica de Roteamento Local
vehicle.advanceRoute();
+ intersection.receiveVehicle(vehicle, clock.getCurrentTime());
- // Add vehicle to appropriate queue
- intersection.receiveVehicle(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) {
System.out.println(
"[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode());
running = false;
- // Close this specific connection
break;
}
} catch (java.net.SocketTimeoutException e) {
- // Timeout - check running flag and continue
if (!running) {
break;
}
- // Continue waiting for next message
} catch (ClassNotFoundException e) {
System.err.println("[" + intersectionId + "] Unknown message type received: " +
e.getMessage());
- break; // Invalid message, close connection
+ break;
} catch (IOException e) {
if (running) {
System.err.println("[" + intersectionId + "] Failed to deserialize message: " +
e.getMessage());
- e.printStackTrace(); // For debugging - maybe change//remove later
+ e.printStackTrace();
}
- break; // Connection error, close connection
+ break;
}
}
@@ -501,27 +931,29 @@ public class IntersectionProcess {
if (running) {
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
}
- // Expected during shutdown
}
}
/**
- * Stops the intersection process gracefully.
- * Shuts down all threads and closes all connections.
+ * Procedimento de Encerramento Gracioso (Graceful Shutdown).
+ *
+ * Inclui o número acumulado de chegadas, partidas e o tamanho atual das filas.
*/
private void sendStatsToDashboard() {
if (dashboardClient == null || !dashboardClient.isConnected()) {
@@ -610,7 +1043,6 @@ public class IntersectionProcess {
}
try {
- // Calculate current queue size
int currentQueueSize = intersection.getTrafficLights().stream()
.mapToInt(TrafficLight::getQueueSize)
.sum();
@@ -620,7 +1052,6 @@ public class IntersectionProcess {
.setIntersectionDepartures(totalDepartures)
.setIntersectionQueueSize(currentQueueSize);
- // Send StatsUpdatePayload directly as the message payload
sd.model.Message message = new sd.model.Message(
MessageType.STATS_UPDATE,
intersectionId,
@@ -636,4 +1067,4 @@ public class IntersectionProcess {
System.err.println("[" + intersectionId + "] Failed to send stats to dashboard: " + e.getMessage());
}
}
-}
+}
\ No newline at end of file
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..02c3307
--- /dev/null
+++ b/main/src/main/java/sd/analysis/MultiRunAnalyzer.java
@@ -0,0 +1,285 @@
+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.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import sd.model.VehicleType;
+
+/**
+ * Responsável pela agregação e análise estatística de múltiplas execuções da simulação.
+ *
+ * Esta classe coleta resultados individuais ({@link SimulationRunResult}) e calcula
+ * métricas consolidadas, incluindo média, desvio padrão, mediana e intervalos de
+ * confiança de 95%. O objetivo é fornecer uma visão robusta do comportamento do
+ * sistema, mitigando a variância estocástica de execuções isoladas.
+ */
+public class MultiRunAnalyzer {
+
+ /** Lista acumulada de resultados de execuções individuais. */
+ private final List
+ * O relatório inclui:
+ *
+ * Calcula média, desvio padrão, mediana, intervalo de confiança (95%) e extremos (min/max).
+ *
+ * @param metricName O nome descritivo da métrica (ex: "Tempo de Espera").
+ * @param values A lista de valores numéricos brutos extraídos das execuções.
+ * @return Uma string formatada com os dados estatísticos.
+ */
+ private String analyzeMetric(String metricName, List
+ * Utilizado internamente para transformar a lista de objetos complexos {@link SimulationRunResult}
+ * em listas simples de Doubles para processamento estatístico.
+ *
+ * @param extractor Função lambda que define qual campo extrair de cada resultado.
+ * @return Lista de valores double correspondentes.
+ */
+ private List
+ * Este método atua como um wrapper para {@link #saveCSVSummary(String)}.
+ *
+ * @param filename O caminho do ficheiro CSV de destino.
+ * @throws IOException Se ocorrer um erro de escrita no disco.
+ */
+ public void saveCSV(String filename) throws IOException {
+ saveCSVSummary(filename);
+ }
+
+ /**
+ * Gera e grava o sumário CSV detalhado com métricas chave por execução.
+ *
+ * Colunas incluídas: Execução, VeículosGerados, VeículosCompletados, TaxaConclusão,
+ * TempoMédioSistema, TempoMédioEspera, TempoMínimoSistema, TempoMáximoSistema.
+ *
+ * @param filename O caminho do ficheiro CSV de destino.
+ * @throws IOException Se ocorrer um erro de escrita no disco.
+ */
+ public void saveCSVSummary(String filename) throws IOException {
+ try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
+ // Header
+ writer.println("Execução,VeículosGerados,VeículosCompletados,TaxaConclusão," +
+ "TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema");
+
+ // 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()
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
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..465ef36
--- /dev/null
+++ b/main/src/main/java/sd/analysis/SimulationRunResult.java
@@ -0,0 +1,206 @@
+package sd.analysis;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import sd.model.VehicleType;
+
+/**
+ * Encapsula os dados telemétricos e estatísticos resultantes de uma única execução da simulação.
+ *
+ * Esta classe atua como um registo estruturado de métricas de desempenho, armazenando
+ * dados de latência (tempos de sistema/espera), vazão (throughput) e ocupação de recursos
+ * (tamanhos de fila). Os dados aqui contidos servem como base para a análise
+ * estatística agregada realizada pelo {@link MultiRunAnalyzer}.
+ */
+public class SimulationRunResult {
+
+ private final int runNumber;
+ private final String configurationFile;
+ private final long startTimeMillis;
+ private final long endTimeMillis;
+
+ // Global metrics
+ /** Total de veículos instanciados pelos geradores durante a execução. */
+ private int totalVehiclesGenerated;
+
+ /** Total de veículos que completaram o percurso e saíram do sistema com sucesso. */
+ private int totalVehiclesCompleted;
+
+ /** Média global do tempo total (em segundos) desde a geração até a saída. */
+ private double averageSystemTime; // seconds
+
+ /** Menor tempo de sistema registado (em segundos). */
+ private double minSystemTime; // seconds
+
+ /** Maior tempo de sistema registado (em segundos). */
+ private double maxSystemTime; // seconds
+
+ /** Média global do tempo (em segundos) que os veículos passaram parados em filas. */
+ private double averageWaitingTime; // seconds
+
+ // Per-type metrics
+ private final Map
+ * Esta classe fornece algoritmos para cálculo de medidas de tendência central (média, mediana),
+ * dispersão (desvio padrão amostral) e inferência estatística (Intervalos de Confiança).
+ * É utilizada para normalizar e validar os resultados estocásticos obtidos através de
+ * múltiplas execuções do sistema.
+ */
+public class StatisticalAnalysis {
+
+ /**
+ * Calcula a média aritmética de um conjunto de valores.
+ * * @param values Lista de valores numéricos (double).
+ * @return A soma dos valores dividida pelo tamanho da amostra, ou 0.0 se a lista for nula/vazia.
+ */
+ public static double mean(List
+ * Utiliza o denominador {@code n - 1} (Correção de Bessel) para fornecer um
+ * estimador não viesado da variância populacional, adequado para as amostras
+ * de simulação.
+ * * @param values Lista de observações.
+ * @return O desvio padrão calculado, ou 0.0 se o tamanho da amostra for < 2.
+ */
+ public static double standardDeviation(List
+ * Utiliza a distribuição t de Student para maior precisão em amostras pequenas (n < 30),
+ * onde a aproximação pela distribuição Normal (Z) seria inadequada. O intervalo define
+ * a faixa onde a verdadeira média populacional reside com 95% de probabilidade.
+ * * @param values Lista de observações.
+ * @return Um array de double onde índice 0 é o limite inferior e índice 1 é o limite superior.
+ */
+ public static double[] confidenceInterval95(List
+ * Baseia-se nos graus de liberdade (gl = n - 1). Para amostras grandes (gl >= 30),
+ * aproxima-se do valor Z de 1.96.
+ * * @param sampleSize O tamanho da amostra (n).
+ * @return O fator multiplicativo t apropriado.
+ */
+ 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
+ }
+
+ /**
+ * Identifica o valor mínimo absoluto na amostra.
+ * * @param values Lista de valores.
+ * @return O menor valor encontrado.
+ */
+ public static double min(List
+ * Nota de Desempenho: Este método ordena uma cópia da lista, resultando em
+ * complexidade O(n log n).
+ * * @param values Lista de valores.
+ * @return O valor central (ou média dos dois centrais) da distribuição ordenada.
+ */
+ public static double median(List
+ * Útil para logging e geração de relatórios textuais.
+ * * @param metricName Nome da métrica a ser exibida.
+ * @param values Os dados brutos associados à métrica.
+ * @return String formatada contendo Média, Desvio Padrão, IC95%, Min, Max e N.
+ */
+ public static String formatSummary(String metricName, List
+ * Esta classe atua como uma fachada (Facade) para os parâmetros do sistema, abstraindo a origem
+ * dos dados (ficheiros {@code .properties} ou JSON). Implementa uma estratégia robusta de
+ * carregamento de recursos, suportando tanto caminhos absolutos do sistema de ficheiros quanto
+ * recursos embutidos no classpath.
+ *
+ * Além de propriedades chave-valor simples, gerencia a desserialização da topologia da rede
+ * através da classe interna {@link NetworkConfig}.
*/
public class SimulationConfig {
- /**
- * Holds all properties loaded from the file.
- */
+ /** Armazenamento em memória das propriedades chave-valor carregadas. */
private final Properties properties;
+
+ /** Estrutura hierárquica da configuração da rede carregada via JSON. */
private NetworkConfig networkConfig;
+ /**
+ * Objeto de transferência de dados (DTO) que representa a configuração global da rede.
+ * Mapeado a partir do ficheiro {@code network_config.json}.
+ */
public static class NetworkConfig {
private List Implementa uma estratégia de carregamento em cascata (fallback) para garantir robustez
+ * em diferentes ambientes de execução (IDE, JAR, Docker):
+ *
+ * Utiliza a biblioteca Gson para desserialização. Em caso de falha, emite um aviso para o
+ * {@code System.err} mas não aborta a execução, permitindo o uso de defaults ou redes vazias.
+ */
private void loadNetworkConfig() {
try (InputStream is = getClass().getClassLoader().getResourceAsStream("network_config.json")) {
if (is == null) {
@@ -154,6 +177,10 @@ public class SimulationConfig {
}
}
+ /**
+ * Retorna a configuração estruturada da rede.
+ * @return Objeto {@link NetworkConfig} ou null se o carregamento falhou.
+ */
public NetworkConfig getNetworkConfig() {
return networkConfig;
}
@@ -161,56 +188,50 @@ public class SimulationConfig {
// --- Network configurations ---
/**
- * Gets the host address for a specific intersection.
- *
- * @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @return The host (e.g., "localhost").
+ * Obtém o endereço de host (nome DNS ou IP) para uma interseção específica.
+ * * @param intersectionId O ID da interseção (ex: "Cr1").
+ * @return O host configurado ou "localhost" por omissão.
*/
public String getIntersectionHost(String intersectionId) {
return properties.getProperty("intersection." + intersectionId + ".host", "localhost");
}
/**
- * Gets the port number for a specific intersection.
- *
- * @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @return The port number.
+ * Obtém a porta de escuta TCP para uma interseção específica.
+ * * @param intersectionId O ID da interseção (ex: "Cr1").
+ * @return O número da porta. Retorna 0 se não configurado.
*/
public int getIntersectionPort(String intersectionId) {
return Integer.parseInt(properties.getProperty("intersection." + intersectionId + ".port", "0"));
}
/**
- * Gets the host address for the dashboard server.
- *
- * @return The dashboard host.
+ * Obtém o endereço de host do servidor de Dashboard (monitorização).
+ * @return O host do dashboard (padrão: "localhost").
*/
public String getDashboardHost() {
return properties.getProperty("dashboard.host", "localhost");
}
/**
- * Gets the port number for the dashboard server.
- *
- * @return The dashboard port.
+ * Obtém a porta de conexão do servidor de Dashboard.
+ * @return A porta do dashboard (padrão: 9000).
*/
public int getDashboardPort() {
return Integer.parseInt(properties.getProperty("dashboard.port", "9000"));
}
/**
- * Gets the host address for the exit node.
- *
- * @return The exit node host.
+ * Obtém o endereço de host do nó de saída (Exit Node), para onde os veículos são encaminhados ao sair da malha.
+ * @return O host do nó de saída (padrão: "localhost").
*/
public String getExitHost() {
return properties.getProperty("exit.host", "localhost");
}
/**
- * Gets the port number for the exit node.
- *
- * @return The exit node port.
+ * Obtém a porta de conexão do nó de saída.
+ * @return A porta do nó de saída (padrão: 9001).
*/
public int getExitPort() {
return Integer.parseInt(properties.getProperty("exit.port", "9001"));
@@ -219,60 +240,76 @@ public class SimulationConfig {
// --- Simulation configurations ---
/**
- * Gets the total duration of the simulation in virtual seconds.
- *
- * @return The simulation duration.
+ * Define a duração total da execução da simulação em segundos virtuais.
+ * @return A duração em segundos (padrão: 3600).
*/
public double getSimulationDuration() {
- return Double.parseDouble(properties.getProperty("simulation.duration", "3600.0"));
+ return Double.parseDouble(properties.getProperty("simulation.duration", "3600"));
}
/**
- * Gets the drain time (in virtual seconds) to allow vehicles to exit after
- * generation stops.
- *
- * @return The drain time.
+ * Obtém o fator de escala temporal para visualização/execução.
+ *
+ * Este é o período adicional executado após o fim da geração de veículos para permitir
+ * que os veículos restantes no sistema completem os seus percursos.
+ * @return O tempo de drenagem (padrão: 60.0s).
*/
public double getDrainTime() {
return Double.parseDouble(properties.getProperty("simulation.drain.time", "60.0"));
}
/**
- * Gets the vehicle arrival model ("POISSON" or "FIXED").
- *
- * @return The arrival model as a string.
+ * Determina o modelo estocástico utilizado para a chegada de veículos.
+ * @return "POISSON" (distribuição exponencial) ou "FIXED" (intervalo determinístico).
*/
public String getArrivalModel() {
return properties.getProperty("simulation.arrival.model", "POISSON");
}
/**
- * Gets the average arrival rate (lambda) for the POISSON model.
- * This represents the average number of vehicles arriving per second.
- *
- * @return The arrival rate.
+ * Obtém a taxa média de chegada (lambda) para o modelo Poisson.
+ * @return Veículos por segundo (padrão: 0.5).
*/
public double getArrivalRate() {
return Double.parseDouble(properties.getProperty("simulation.arrival.rate", "0.5"));
}
/**
- * Gets the fixed time interval between vehicle arrivals for the FIXED model.
- *
- * @return The fixed interval in seconds.
+ * Obtém o intervalo fixo entre chegadas para o modelo determinístico.
+ * @return O intervalo em segundos (padrão: 2.0).
*/
public double getFixedArrivalInterval() {
return Double.parseDouble(properties.getProperty("simulation.arrival.fixed.interval", "2.0"));
}
+ /**
+ * Obtém a política de roteamento utilizada pelos veículos para navegar na malha.
+ * @return A política: "RANDOM", "SHORTEST_PATH" ou "LEAST_CONGESTED".
+ */
+ public String getRoutingPolicy() {
+ return properties.getProperty("simulation.routing.policy", "RANDOM");
+ }
+
// --- Traffic light configurations ---
/**
- * Gets the duration of the GREEN light state for a specific traffic light.
- *
- * @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @param direction The direction of the light (e.g., "North").
- * @return The green light time in seconds.
+ * Obtém a duração do estado VERDE para um semáforo específico.
+ * * @param intersectionId ID da interseção.
+ * @param direction Direção do fluxo (ex: "North").
+ * @return Duração em segundos (padrão: 30.0).
*/
public double getTrafficLightGreenTime(String intersectionId, String direction) {
String key = "trafficlight." + intersectionId + "." + direction + ".green";
@@ -280,11 +317,10 @@ public class SimulationConfig {
}
/**
- * Gets the duration of the RED light state for a specific traffic light.
- *
- * @param intersectionId The ID of the intersection (e.g., "Cr1").
- * @param direction The direction of the light (e.g., "North").
- * @return The red light time in seconds.
+ * Obtém a duração do estado VERMELHO para um semáforo específico.
+ * * @param intersectionId ID da interseção.
+ * @param direction Direção do fluxo.
+ * @return Duração em segundos (padrão: 30.0).
*/
public double getTrafficLightRedTime(String intersectionId, String direction) {
String key = "trafficlight." + intersectionId + "." + direction + ".red";
@@ -294,94 +330,84 @@ public class SimulationConfig {
// --- Vehicle configurations ---
/**
- * Gets the probability (0.0 to 1.0) that a generated vehicle is of type LIGHT.
- *
- * @return The probability for LIGHT vehicles.
+ * Probabilidade (0.0 a 1.0) de geração de um veículo do tipo LIGEIRO (LIGHT).
+ * @return Probabilidade (padrão: 0.7).
*/
public double getLightVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.light", "0.7"));
}
/**
- * Gets the average time it takes a LIGHT vehicle to cross an intersection.
- *
- * @return The crossing time in seconds.
+ * Tempo médio necessário para um veículo LIGEIRO atravessar uma interseção.
+ * @return Tempo em segundos (padrão: 2.0).
*/
public double getLightVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.light", "2.0"));
}
/**
- * Gets the probability (0.0 to 1.0) that a generated vehicle is of type BIKE.
- *
- * @return The probability for BIKE vehicles.
+ * Probabilidade (0.0 a 1.0) de geração de um veículo do tipo BICICLETA (BIKE).
+ * @return Probabilidade (padrão: 0.0).
*/
public double getBikeVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.bike", "0.0"));
}
/**
- * Gets the average time it takes a BIKE vehicle to cross an intersection.
- *
- * @return The crossing time in seconds.
+ * Tempo médio necessário para uma BICICLETA atravessar uma interseção.
+ * @return Tempo em segundos (padrão: 1.5).
*/
public double getBikeVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.bike", "1.5"));
}
/**
- * Gets the probability (0.0 to 1.0) that a generated vehicle is of type HEAVY.
- *
- * @return The probability for HEAVY vehicles.
+ * Probabilidade (0.0 a 1.0) de geração de um veículo PESADO (HEAVY).
+ * @return Probabilidade (padrão: 0.0).
*/
public double getHeavyVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.heavy", "0.0"));
}
/**
- * Gets the average time it takes a HEAVY vehicle to cross an intersection.
- *
- * @return The crossing time in seconds.
+ * Tempo médio necessário para um veículo PESADO atravessar uma interseção.
+ * @return Tempo em segundos (padrão: 4.0).
*/
public double getHeavyVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.heavy", "4.0"));
}
/**
- * Gets the base travel time between intersections for light vehicles.
- *
- * @return The base travel time in seconds.
+ * Define o tempo base de viagem entre interseções para veículos padrão.
+ * @return Tempo em segundos (padrão: 8.0).
*/
public double getBaseTravelTime() {
return Double.parseDouble(properties.getProperty("vehicle.travel.time.base", "8.0"));
}
/**
- * Gets the travel time multiplier for bike vehicles.
- * Bike travel time = base time × this multiplier.
- *
- * @return The multiplier for bike travel time.
+ * Multiplicador de tempo de viagem para bicicletas.
+ * Tempo efetivo = Base * Multiplicador.
+ * @return Fator multiplicativo (padrão: 0.5).
*/
public double getBikeTravelTimeMultiplier() {
return Double.parseDouble(properties.getProperty("vehicle.travel.time.bike.multiplier", "0.5"));
}
/**
- * Gets the travel time multiplier for heavy vehicles.
- * Heavy vehicle travel time = base time × this multiplier.
- *
- * @return The multiplier for heavy vehicle travel time.
+ * Multiplicador de tempo de viagem para veículos pesados.
+ * Tempo efetivo = Base * Multiplicador.
+ * @return Fator multiplicativo (padrão: 4.0).
*/
public double getHeavyTravelTimeMultiplier() {
- return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "2.0"));
+ return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "4.0"));
}
// --- Statistics ---
/**
- * Gets the interval (in virtual seconds) between periodic statistics updates.
- *
- * @return The statistics update interval.
+ * Intervalo de tempo (em segundos virtuais) para agregação e envio de estatísticas periódicas.
+ * @return Intervalo de atualização (padrão: 1.0).
*/
public double getStatisticsUpdateInterval() {
return Double.parseDouble(properties.getProperty("statistics.update.interval", "1.0"));
@@ -390,21 +416,19 @@ public class SimulationConfig {
// --- Generic getters ---
/**
- * Generic method to get any property as a string, with a default value.
- *
- * @param key The property key.
- * @param defaultValue The value to return if the key is not found.
- * @return The property value or the default.
+ * Recupera uma propriedade genérica como String, com valor padrão de segurança.
+ * * @param key A chave da propriedade.
+ * @param defaultValue O valor a retornar caso a chave não exista.
+ * @return O valor da propriedade ou o default.
*/
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
/**
- * Generic method to get any property as a string.
- *
- * @param key The property key.
- * @return The property value, or null if not found.
+ * Recupera uma propriedade genérica como String.
+ * * @param key A chave da propriedade.
+ * @return O valor da propriedade ou null se não encontrada.
*/
public String getProperty(String key) {
return properties.getProperty(key);
diff --git a/main/src/main/java/sd/coordinator/CoordinatorProcess.java b/main/src/main/java/sd/coordinator/CoordinatorProcess.java
index 2fb1423..dd9135e 100644
--- a/main/src/main/java/sd/coordinator/CoordinatorProcess.java
+++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java
@@ -5,32 +5,72 @@ import java.util.HashMap;
import java.util.Map;
import sd.config.SimulationConfig;
+import sd.dashboard.DashboardStatistics;
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;
+import sd.routing.LeastCongestedRouteSelector;
+import sd.routing.RandomRouteSelector;
+import sd.routing.RouteSelector;
+import sd.routing.RoutingPolicy;
+import sd.routing.ShortestPathRouteSelector;
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
- *
- * This is the main entry point for the distributed simulation architecture.
+ * Coordenador central da arquitetura de simulação distribuída.
+ *
+ * Este processo atua como o "cérebro" da simulação, sendo responsável por:
+ *
+ * Utilizado exclusivamente pela política {@link LeastCongestedRouteSelector}.
+ * O coordenador incrementa este contador ao enviar um veículo para uma interseção.
+ * Nota: Esta é uma visão "borda" (edge) e pode não refletir a saída em tempo real
+ * dos veículos, mas serve como heurística suficiente para balanceamento de carga.
+ */
+ private final Map
+ * Executa a sequência:
+ * 1. Retira o próximo evento da fila prioritária.
+ * 2. Avança o relógio virtual para o timestamp do evento.
+ * 3. Aplica escala temporal (Time Scale) para visualização, se necessário.
+ * 4. Processa o evento.
+ */
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 o processamento de um evento DES retirado da fila.
+ * * @param event O evento a ser processado.
+ * @param generationDuration Duração da fase de geração ativa (antes do 'drain time').
+ */
+ 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) {
+ // Check for routing policy changes from dashboard
+ checkForPolicyChanges();
+
+ generateAndSendVehicle();
+
+ // Schedule next vehicle generation (Recursive scheduling)
+ 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());
+ }
+ }
+
+ /**
+ * Exporta o log completo de eventos DES para auditoria e debug.
+ * Caminho: {@code logs/coordinator-event-history.txt}.
+ */
+ 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());
+ }
+ }
+
+ /**
+ * Gera um novo veículo e envia-o via TCP para a interseção de entrada apropriada.
+ * Também atualiza o rastreio local de filas para balanceamento de carga.
+ */
private void generateAndSendVehicle() {
- Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime);
+ double currentTime = clock.getCurrentTime();
+
+ // Usa os tamanhos de fila rastreados localmente para política LEAST_CONGESTED
+ // Isto permite roteamento dinâmico baseado no estado atual da rede
+ Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime, intersectionQueueSizes);
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()));
+
+ // Update local queue size tracking (increment first intersection's queue)
+ String firstIntersection = vehicle.getRoute().get(0);
+ intersectionQueueSizes.put(firstIntersection,
+ intersectionQueueSizes.getOrDefault(firstIntersection, 0) + 1);
+
// Send generation count to dashboard
sendGenerationStatsToDashboard();
@@ -171,6 +377,9 @@ public class CoordinatorProcess {
sendVehicleToIntersection(vehicle, entryIntersection);
}
+ /**
+ * Serializa e transmite o objeto Veículo para o nó (interseção) de destino.
+ */
private void sendVehicleToIntersection(Vehicle vehicle, String intersectionId) {
SocketClient client = intersectionClients.get(intersectionId);
@@ -195,6 +404,9 @@ public class CoordinatorProcess {
}
}
+ /**
+ * Encerra graciosamente a simulação, enviando sinais de SHUTDOWN para todos os nós.
+ */
public void shutdown() {
System.out.println();
System.out.println("=".repeat(60));
@@ -230,6 +442,65 @@ public class CoordinatorProcess {
running = false;
}
+ /**
+ * Altera dinamicamente a política de roteamento durante a simulação (Hot-swap).
+ * Thread-safe.
+ * * @param policyName nome da nova política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED)
+ */
+ public synchronized void changeRoutingPolicy(String policyName) {
+ System.out.println("\n" + "=".repeat(60));
+ System.out.println("ROUTING POLICY CHANGE REQUEST");
+ System.out.println("=".repeat(60));
+ System.out.println("Current policy: " + getCurrentPolicyName());
+ System.out.println("Requested policy: " + policyName);
+
+ RouteSelector newSelector = createRouteSelector(policyName);
+ this.currentRouteSelector = newSelector;
+ this.vehicleGenerator.setRouteSelector(newSelector);
+
+ System.out.println("Routing policy successfully changed to: " + policyName);
+ System.out.println(" - New vehicles will use the updated policy");
+ System.out.println("=".repeat(60) + "\n");
+
+ eventLogger.log(sd.logging.EventType.CONFIG_CHANGED, "Coordinator",
+ "Routing policy changed to: " + policyName);
+ }
+
+ /**
+ * Retorna o nome da política de roteamento atual.
+ */
+ private String getCurrentPolicyName() {
+ if (currentRouteSelector instanceof RandomRouteSelector) {
+ return "RANDOM";
+ } else if (currentRouteSelector instanceof ShortestPathRouteSelector) {
+ return "SHORTEST_PATH";
+ } else if (currentRouteSelector instanceof LeastCongestedRouteSelector) {
+ return "LEAST_CONGESTED";
+ }
+ return "UNKNOWN";
+ }
+
+ /**
+ * Verifica se há solicitação de mudança de política proveniente do dashboard
+ * e aplica a alteração se houver.
+ */
+ private void checkForPolicyChanges() {
+ if (dashboardStatistics != null) {
+ String requestedPolicy = dashboardStatistics.getAndClearRequestedRoutingPolicy();
+ if (requestedPolicy != null && !requestedPolicy.isEmpty()) {
+ changeRoutingPolicy(requestedPolicy);
+ }
+ }
+ }
+
+ /**
+ * Injeta a referência para as estatísticas do dashboard.
+ * Permite que o coordenador consuma intenções de mudança de política do utilizador.
+ */
+ public void setDashboardStatistics(DashboardStatistics stats) {
+ this.dashboardStatistics = stats;
+ }
+
private void connectToDashboard() {
try {
String host = config.getDashboardHost();
@@ -268,6 +539,11 @@ public class CoordinatorProcess {
}
}
+ /**
+ * Sincronização Global: Envia o timestamp de início (System.currentTimeMillis)
+ * para todos os componentes distribuídos, garantindo uma base de tempo comum
+ * para métricas de latência.
+ */
private void sendSimulationStartTime() {
long startTimeMillis = System.currentTimeMillis();
@@ -299,4 +575,4 @@ public class CoordinatorProcess {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/coordinator/SocketClient.java b/main/src/main/java/sd/coordinator/SocketClient.java
index 88d75b2..4dafc48 100644
--- a/main/src/main/java/sd/coordinator/SocketClient.java
+++ b/main/src/main/java/sd/coordinator/SocketClient.java
@@ -10,10 +10,14 @@ import sd.serialization.SerializationException;
import sd.serialization.SerializerFactory;
/**
- * Socket client for communication with a single intersection process.
- *
- * Handles a persistent TCP connection to one intersection,
- * providing a simple way to send serialized messages.
+ * Abstração de cliente TCP para comunicação outbound (de saída) com nós da rede.
+ *
+ * Esta classe encapsula a gestão do socket raw, oferecendo uma interface de alto nível
+ * para envio de objetos {@link Message}. Implementa o protocolo de camada de aplicação
+ * proprietário, garantindo a serialização correta e o enquadramento (framing) dos dados
+ * na stream TCP.
+ *
+ * É utilizada pelo Coordenador para controlar Interseções e enviar telemetria para o Dashboard.
*/
public class SocketClient {
@@ -25,11 +29,11 @@ public class SocketClient {
private MessageSerializer serializer;
/**
- * Creates a new SocketClient for a given intersection.
+ * Instancia um novo cliente socket configurado para um destino específico.
*
- * @param intersectionId Intersection ID (ex. "Cr1")
- * @param host Host address (ex. "localhost")
- * @param port Port number
+ * @param intersectionId Identificador lógico do nó de destino (ex: "Cr1", "Dashboard").
+ * @param host Endereço IP ou hostname do destino.
+ * @param port Porta TCP de escuta do destino.
*/
public SocketClient(String intersectionId, String host, int port) {
this.intersectionId = intersectionId;
@@ -39,11 +43,9 @@ public class SocketClient {
}
/**
- * Connects to the intersection process via TCP.
- *
- * @throws IOException if the connection cannot be established
+ * Estabelece a conexão TCP (Handshake SYN/ACK) com o host remoto.
+ * * @throws IOException Se o host for inalcançável ou a conexão for recusada.
*/
-
public void connect() throws IOException {
try {
socket = new Socket(host, port);
@@ -56,12 +58,22 @@ public class SocketClient {
}
/**
- * Sends a message to the connected intersection.
- * The message is serialized and written over the socket.
+ * Serializa e transmite uma mensagem através do socket conectado.
+ *
+ * Protocolo de Envio (Length-Prefix Framing):
+ *
+ * Operação idempotente: pode ser chamada múltiplas vezes sem erro.
*/
public void close() {
try {
@@ -106,7 +121,8 @@ public class SocketClient {
}
/**
- * @return true if connected and socket is open, false otherwise
+ * Verifica o estado atual da conexão.
+ * * @return true se o socket estiver instanciado, conectado e aberto; false caso contrário.
*/
public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed();
@@ -121,4 +137,4 @@ public class SocketClient {
return String.format("SocketClient[intersection=%s, host=%s, port=%d, connected=%s]",
intersectionId, host, port, isConnected());
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/BatchAnalysisDialog.java b/main/src/main/java/sd/dashboard/BatchAnalysisDialog.java
new file mode 100644
index 0000000..ebb9b0c
--- /dev/null
+++ b/main/src/main/java/sd/dashboard/BatchAnalysisDialog.java
@@ -0,0 +1,576 @@
+package sd.dashboard;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.control.Spinner;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import sd.analysis.MultiRunAnalyzer;
+import sd.analysis.SimulationRunResult;
+import sd.model.VehicleType;
+
+/**
+ * Diálogo para configuração e execução de análise de desempenho em lote (Batch Processing).
+ *
+ * Esta classe fornece uma interface gráfica para automatizar múltiplas execuções da simulação
+ * sob diferentes cenários de carga. É responsável por:
+ *
+ * Esta classe apresenta um diálogo modal que permite ao operador sobrepor (override)
+ * as configurações estáticas carregadas do ficheiro {@code .properties} imediatamente
+ * antes da execução. Oferece controlo granular sobre:
+ *
+ * A interface é construída dinamicamente usando layouts JavaFX ({@link GridPane}, {@link VBox}).
+ * Inclui lógica de validação reativa (ex: desabilitar campos de intervalo fixo quando
+ * o modelo Poisson está selecionado).
+ * *
+
+[Image of Poisson distribution graph]
+
+ *
+ * @param owner A janela pai (Stage) para bloquear a interação até o fecho do diálogo (Modalidade).
+ * @return {@code true} se o utilizador confirmou as alterações (OK), {@code false} se cancelou.
+ */
+ public static boolean showAdvancedConfig(Stage owner) {
+ Dialog
+ * Esta classe implementa o padrão Thread-per-Client. Cada instância executa numa
+ * thread separada, garantindo que a latência de rede ou o processamento de mensagens
+ * de um nó (Interseção/Coordenador) não bloqueie a receção de telemetria dos outros.
+ *
+ * As suas principais funções são:
+ *
+ * Estabelece o wrapper {@link SocketConnection}, entra num loop de leitura bloqueante
+ * e gere exceções de I/O. Garante o fecho limpo do socket em caso de desconexão ou erro.
+ */
@Override
public void run() {
String clientInfo = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort();
@@ -61,6 +84,16 @@ public class DashboardClientHandler implements Runnable {
}
}
+ /**
+ * Valida e extrai os dados estatísticos da mensagem.
+ *
+ * Implementa uma lógica de correção de tipagem para payloads desserializados via Gson.
+ * Frequentemente, objetos genéricos são desserializados como {@code LinkedHashMap} em vez
+ * da classe alvo {@link StatsUpdatePayload}. Este método deteta essa situação e realiza
+ * uma conversão "round-trip" (Map -> JSON -> Object) para garantir a integridade dos dados.
+ *
+ * @param message A mensagem recebida da rede.
+ */
private void processMessage(MessageProtocol message) {
if (message.getType() != MessageType.STATS_UPDATE) {
System.out.println("[Handler] Ignoring non-statistics message type: " + message.getType());
@@ -78,6 +111,7 @@ public class DashboardClientHandler implements Runnable {
stats = (StatsUpdatePayload) payload;
} else if (payload instanceof java.util.Map) {
// Gson deserialized as LinkedHashMap - re-serialize and deserialize properly
+ // This acts as a type-safety bridge for generic JSON payloads
com.google.gson.Gson gson = new com.google.gson.Gson();
String json = gson.toJson(payload);
stats = gson.fromJson(json, StatsUpdatePayload.class);
@@ -90,6 +124,15 @@ public class DashboardClientHandler implements Runnable {
updateStatistics(senderId, stats);
}
+ /**
+ * Aplica os dados recebidos ao modelo global de estatísticas.
+ *
+ * Distingue entre atualizações incrementais (ex: contagem de veículos) e
+ * substituições de estado (ex: tempo total de sistema reportado pelo nó de saída).
+ *
+ * @param senderId Identificador do nó que enviou a atualização (ex: "Cr1", "ExitNode").
+ * @param stats O objeto DTO contendo as métricas normalizadas.
+ */
private void updateStatistics(String senderId, StatsUpdatePayload stats) {
if (stats.getTotalVehiclesGenerated() >= 0) {
statistics.updateVehiclesGenerated(stats.getTotalVehiclesGenerated());
@@ -134,4 +177,4 @@ public class DashboardClientHandler implements Runnable {
System.out.println("[Handler] Successfully updated statistics from: " + senderId);
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/DashboardServer.java b/main/src/main/java/sd/dashboard/DashboardServer.java
index cca71b0..6f76a3b 100644
--- a/main/src/main/java/sd/dashboard/DashboardServer.java
+++ b/main/src/main/java/sd/dashboard/DashboardServer.java
@@ -10,17 +10,43 @@ 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.
+ * Servidor central de agregação de telemetria e estatísticas.
+ *
+ * Este componente atua como o nó de monitorização do sistema distribuído.
+ * Implementa uma arquitetura de servidor concorrente utilizando um {@link ExecutorService}
+ * (Thread Pool) para gerir múltiplas conexões de entrada simultâneas provenientes
+ * das Interseções, Coordenador e Nó de Saída.
+ *
+ * Suporta dois modos de operação:
+ *
+ * Analisa os argumentos de linha de comando para determinar o modo de execução.
+ * Se a flag {@code --gui} ou {@code -g} estiver presente, inicia o subsistema JavaFX.
+ * Caso contrário, inicia o modo servidor de terminal padrão.
+ *
+ * @param args Argumentos de CLI (ex: caminho do config, flags de modo).
+ */
public static void main(String[] args) {
// Check if GUI mode is requested
boolean useGUI = false;
@@ -70,13 +96,24 @@ public class DashboardServer {
}
}
+ /**
+ * Inicializa a infraestrutura do servidor.
+ *
+ * @param config A configuração carregada contendo a porta de escuta.
+ */
public DashboardServer(SimulationConfig config) {
this.port = config.getDashboardPort();
this.statistics = new DashboardStatistics();
+ // Fixed pool limita o consumo de recursos, prevenindo exaustão sob carga alta
this.clientHandlerPool = Executors.newFixedThreadPool(10);
this.running = new AtomicBoolean(false);
}
+ /**
+ * Inicia a escuta por conexões (Bind & Listen) e a thread de despacho.
+ *
+ * @throws IOException Se a porta já estiver em uso ou ocorrer erro de bind.
+ */
public void start() throws IOException {
if (running.get()) {
System.out.println("Dashboard Server is already running.");
@@ -95,6 +132,13 @@ public class DashboardServer {
acceptThread.start();
}
+ /**
+ * Loop principal de aceitação de conexões (Dispatcher).
+ *
+ * Bloqueia em {@code accept()} até que uma nova conexão chegue, delegando
+ * imediatamente o processamento para um {@link DashboardClientHandler} gerido
+ * pelo Thread Pool.
+ */
private void acceptConnections() {
while (running.get()) {
try {
@@ -112,6 +156,10 @@ public class DashboardServer {
}
}
+ /**
+ * Ciclo de renderização de métricas para o modo CLI (Headless).
+ * Atualiza o ecrã a cada 5 segundos.
+ */
@SuppressWarnings("BusyWait")
private void displayLoop() {
final long DISPLAY_INTERVAL_MS = 5000;
@@ -127,6 +175,9 @@ public class DashboardServer {
}
}
+ /**
+ * Renderiza o snapshot atual das estatísticas no stdout.
+ */
public void displayStatistics() {
System.out.println("\n" + "=".repeat(60));
System.out.println("REAL-TIME SIMULATION STATISTICS");
@@ -135,6 +186,13 @@ public class DashboardServer {
System.out.println("=".repeat(60));
}
+ /**
+ * Procedimento de encerramento gracioso (Graceful Shutdown).
+ *
+ * 1. Altera flag de execução.
+ * 2. Fecha o socket do servidor para desbloquear a thread de aceitação.
+ * 3. Força o encerramento do pool de threads de clientes.
+ */
public void stop() {
if (!running.get()) {
return;
@@ -162,4 +220,4 @@ public class DashboardServer {
public boolean isRunning() {
return running.get();
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/dashboard/DashboardStatistics.java b/main/src/main/java/sd/dashboard/DashboardStatistics.java
index da6f097..dd9f2f8 100644
--- a/main/src/main/java/sd/dashboard/DashboardStatistics.java
+++ b/main/src/main/java/sd/dashboard/DashboardStatistics.java
@@ -9,8 +9,13 @@ 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.
+ * Repositório central de estado da simulação, desenhado para acesso concorrente de alta frequência.
+ *
+ * Esta classe atua como a "Single Source of Truth" para o Dashboard. Utiliza primitivas
+ * de concorrência do pacote {@code java.util.concurrent} (como {@link AtomicInteger} e
+ * {@link ConcurrentHashMap}) para permitir leituras e escritas simultâneas sem a necessidade
+ * de bloqueios explícitos (Lock-Free), minimizando a latência de processamento das mensagens
+ * recebidas dos múltiplos nós da rede.
*/
public class DashboardStatistics {
@@ -19,12 +24,21 @@ public class DashboardStatistics {
private final AtomicLong totalSystemTime;
private final AtomicLong totalWaitingTime;
+ /** Mapa thread-safe para armazenar métricas granulares por interseção. */
private final Map
+ * Utiliza {@link Map#compute} para garantir que a criação do objeto {@link IntersectionStats}
+ * seja thread-safe sem necessidade de blocos synchronized externos.
+ *
+ * @param intersectionId ID da interseção.
+ * @param arrivals Total acumulado de chegadas.
+ * @param departures Total acumulado de partidas.
+ * @param currentQueueSize Tamanho instantâneo da fila.
+ */
public void updateIntersectionStats(String intersectionId, int arrivals,
int departures, int currentQueueSize) {
intersectionStats.compute(intersectionId, (id, stats) -> {
@@ -110,6 +135,8 @@ public class DashboardStatistics {
lastUpdateTime = System.currentTimeMillis();
}
+ // --- Getters e Métricas Calculadas ---
+
public int getTotalVehiclesGenerated() {
return totalVehiclesGenerated.get();
}
@@ -118,12 +145,20 @@ public class DashboardStatistics {
return totalVehiclesCompleted.get();
}
+ /**
+ * Calcula o tempo médio no sistema em tempo real.
+ * @return Média em milissegundos (0.0 se nenhum veículo completou).
+ */
public double getAverageSystemTime() {
int completed = totalVehiclesCompleted.get();
if (completed == 0) return 0.0;
return (double) totalSystemTime.get() / completed;
}
+ /**
+ * Calcula o tempo médio de espera em tempo real.
+ * @return Média em milissegundos (0.0 se nenhum veículo completou).
+ */
public double getAverageWaitingTime() {
int completed = totalVehiclesCompleted.get();
if (completed == 0) return 0.0;
@@ -152,6 +187,44 @@ public class DashboardStatistics {
return lastUpdateTime;
}
+ /**
+ * Obtém um snapshot dos tamanhos atuais das filas de todas as interseções.
+ *
+ * Utilizado primariamente pelo algoritmo de roteamento dinâmico (LEAST_CONGESTED)
+ * para tomar decisões de encaminhamento baseadas na carga atual da rede.
+ * * @return Mapa contendo {@code intersectionId -> queueSize}.
+ */
+ public Map
+ * 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.
+ *
+ * Aspetos Técnicos Relevantes:
+ *
+ * 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:
+ *
+ * A ordem de inicialização é crítica para evitar Race Conditions na conexão TCP:
+ *
+ * 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()) {
+ return false;
+ }
+ // Coordinator is the last process in the list
+ Process coordinator = runningProcesses.get(runningProcesses.size() - 1);
+ return coordinator.isAlive();
+ }
+
+ /**
+ * 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()) {
+ return true;
+ }
+
+ Process coordinator = runningProcesses.get(runningProcesses.size() - 1);
+ return coordinator.waitFor(timeoutSeconds, java.util.concurrent.TimeUnit.SECONDS);
+ }
+
+ /**
+ * 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...");
@@ -83,16 +148,17 @@ public class SimulationProcessManager {
}
/**
- * Helper to start a single Java process.
+ * 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";
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
@@ -115,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 a84760b..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;
/**
- * Data transfer object for statistics updates to the dashboard.
- * Use -1 for fields not being updated in this message.
+ * 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):
+ * 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.
+ * 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.
+ *
+ * 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:
+ *
+ * Lógica de Ordenação:
+ *
+ * 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;
+ }
+
+ @Override
+ 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/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..dd08b23
--- /dev/null
+++ b/main/src/main/java/sd/logging/EventLogger.java
@@ -0,0 +1,251 @@
+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;
+
+/**
+ * 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:
+ *
+ * Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo:
+ *
+ * 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:
+ *
+ * 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()))
+ 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()));
+
+ // Escreve estatísticas sumarizadas
+ trace.writeSummary(vehicle, systemTime);
+
+ // Stop tracking and close file
+ stopTracking(vehicle.getId());
+ }
+ }
+
+ /**
+ * 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()) {
+ trace.close();
+ }
+ trackedVehicles.clear();
+ }
+
+ /**
+ * 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;
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/model/Intersection.java b/main/src/main/java/sd/model/Intersection.java
index 7d6ff32..612a266 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: 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."
+ * Esta classe atua como a Unidade de Dados de Aplicação (ADU), encapsulando tanto
+ * os metadados de roteamento (origem, destino, tipo) quanto a carga útil (payload)
+ * polimórfica. É agnóstica ao conteúdo, servindo como contentor genérico para
+ * transferência de estado (Veículos, Estatísticas) ou sinais de controlo (Semáforos).
+ *
+ * A imutabilidade dos campos (exceto via serialização) garante a integridade da mensagem
+ * durante o trânsito na rede.
*/
public class Message implements MessageProtocol {
private static final long serialVersionUID = 1L;
- /**
- * Unique identifier for this message.
+ /** * Identificador único universal (UUID).
+ * Essencial para rastreabilidade (tracing), logs de auditoria e mecanismos de deduplicação.
*/
private final String messageId;
- /**
- * The type of this message (e.g., VEHICLE_TRANSFER, STATS_UPDATE).
- */
+ /** Discriminador semântico que define como o recetor deve processar o payload. */
private final MessageType type;
- /**
- * Identifier of the process that sent this message.
- */
+ /** Identificador lógico do nó emissor (ex: "Cr1", "Coordinator"). */
private final String senderId;
- /**
- * Identifier of the destination process. Can be null for broadcast messages.
+ /** * Identificador lógico do nó recetor.
+ * Se {@code null}, a mensagem deve ser tratada como Broadcast.
*/
private final String destinationId;
- /**
- * The actual data being transmitted. Type depends on the message type.
+ /** * Carga útil polimórfica.
+ * Deve implementar {@link java.io.Serializable} para garantir transmissão correta.
*/
private final Object payload;
- /**
- * Timestamp when this message was created (simulation time or real time).
- */
+ /** Marca temporal da criação da mensagem (Unix Timestamp), usada para cálculo de latência de rede. */
private final long timestamp;
/**
- * Creates a new message with all parameters.
+ * Construtor completo para reconstrução de mensagens ou envio com timestamp manual.
*
- * @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 Classificação semântica da mensagem.
+ * @param senderId ID do processo origem.
+ * @param destinationId ID do processo destino (ou null para broadcast).
+ * @param payload Objeto de domínio a ser transportado.
+ * @param timestamp Instante de criação (ms).
*/
public Message(MessageType type, String senderId, String destinationId,
Object payload, long timestamp) {
@@ -63,23 +63,24 @@ public class Message implements MessageProtocol {
}
/**
- * Creates a new message with current system time as timestamp.
+ * Construtor de conveniência que atribui automaticamente o timestamp atual do sistema.
*
- * @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 Classificação semântica.
+ * @param senderId ID do processo origem.
+ * @param destinationId ID do processo destino.
+ * @param payload Objeto de domínio.
*/
public Message(MessageType type, String senderId, String destinationId, Object payload) {
this(type, senderId, destinationId, payload, System.currentTimeMillis());
}
/**
- * Creates a broadcast message (no specific destination).
+ * Construtor de conveniência para mensagens de difusão (Broadcast).
+ * Define {@code destinationId} como null.
*
- * @param type The message type
- * @param senderId The ID of the sending process
- * @param payload The message payload
+ * @param type Classificação semântica.
+ * @param senderId ID do processo origem.
+ * @param payload Objeto de domínio.
*/
public Message(MessageType type, String senderId, Object payload) {
this(type, senderId, null, payload, System.currentTimeMillis());
@@ -112,21 +113,23 @@ public class Message implements MessageProtocol {
}
/**
- * Checks if this is a broadcast message (no specific destination).
+ * Verifica se a mensagem se destina a todos os nós da rede.
*
- * @return true if destinationId is null, false otherwise
+ * @return {@code true} se o destinationId for nulo.
*/
public boolean isBroadcast() {
return destinationId == null;
}
/**
- * Gets the payload cast to a specific type.
- * Use with caution and ensure type safety.
+ * Utilitário para casting seguro e fluente do payload.
+ *
+ * Evita a necessidade de casts explícitos e supressão de warnings no código cliente.
*
- * @param 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. Só remove se: Atualiza automaticamente as estatísticas de tempo de espera do veículo.
+ * Esta classe é o "gémeo digital" de um carro, mota ou camião.
+ * Mantém toda a informação necessária:
+ *
+ * O objeto é serializado e enviado pela rede à medida que o veículo
+ * se move entre processos distribuídos.
+ * Cada tipo pode ter propriedades diferentes como tempo de travessia
+ * e probabilidade de geração, definidas na {@link sd.config.SimulationConfig}. 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:
+ *
+ * Esta classe abstrai a complexidade da API nativa {@link java.net.Socket}, oferecendo:
+ *
+ * Este comportamento é vital quando o processo Coordenador inicia antes das Interseções estarem
+ * prontas para aceitar conexões ({@code accept()}).
*
- * @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 nó de destino (ex: "localhost").
+ * @param port Porta de serviço.
+ * @throws IOException Se a conexão falhar após todas as {@code MAX_RETRIES} tentativas.
+ * @throws UnknownHostException Se o DNS não resolver o hostname.
+ * @throws InterruptedException Se a thread for interrompida durante o sleep de retry.
*/
public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException {
Socket tempSocket = null;
@@ -75,7 +84,7 @@ public class SocketConnection implements Closeable {
// --- Retry Loop ---
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
- // Try to establish the connection
+ // Try to establish the connection (SYN -> SYN-ACK -> ACK)
tempSocket = new Socket(host, port);
// If successful, break out of the retry loop
@@ -84,17 +93,17 @@ public class SocketConnection implements Closeable {
break;
} catch (ConnectException | SocketTimeoutException e) {
- // These are common errors indicating the server might not be ready.
+ // Common errors: "Connection refused" (server not up) or "Timeout" (firewall/network)
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
if (attempt < MAX_RETRIES) {
- // Wait before the next attempt
+ // Blocking wait before next attempt
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
}
} catch (IOException e) {
- // Other IOExceptions might be more permanent, but we retry anyway.
+ // Other IO errors
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed with IOException: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
@@ -104,51 +113,49 @@ public class SocketConnection implements Closeable {
}
} // --- End of Retry Loop ---
- // If after all retries tempSocket is still null, it means connection failed permanently.
+ // Final validation
if (tempSocket == null) {
System.err.printf("[SocketConnection] Failed to connect to %s:%d after %d attempts.%n", host, port, MAX_RETRIES);
if (lastException != null) {
- throw lastException; // Throw the last exception encountered
+ throw lastException; // Propagate the root cause
} else {
- // Should not happen if loop ran, but as a fallback
throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown.");
}
}
- // If connection was successful, assign to final variable and create streams
+ // Initialize streams
this.socket = tempSocket;
-
this.outputStream = socket.getOutputStream();
this.inputStream = socket.getInputStream();
this.serializer = SerializerFactory.createDefault();
-
}
-
/**
- * Constructor for the "Server" (who accepts the connection).
- * Receives a Socket that has already been accepted by a ServerSocket.
- * No retry logic needed here as the connection is already established.
+ * Construtor para servidores (Passive Open).
+ * Envolve um socket já conectado (retornado por {@code serverSocket.accept()}).
+ * Não necessita de retry logic pois a conexão física já existe.
*
- * @param acceptedSocket The Socket returned by serverSocket.accept().
- * @throws IOException If stream creation fails.
+ * @param acceptedSocket O socket ativo retornado pelo SO.
+ * @throws IOException Se falhar a obtenção dos streams de I/O.
*/
public SocketConnection(Socket acceptedSocket) throws IOException {
this.socket = acceptedSocket;
this.outputStream = socket.getOutputStream();
this.inputStream = socket.getInputStream();
this.serializer = SerializerFactory.createDefault();
-
}
/**
- * Sends (serializes) a MessageProtocol object over the socket.
+ * Serializa e transmite uma mensagem através do canal.
+ *
+ * Utiliza sincronização ({@code synchronized}) para garantir que escritas concorrentes
+ * na mesma conexão não corrompem a stream de bytes (thread-safety).
*
- * @param message The "envelope" (which contains the Vehicle) to be sent.
- * @throws IOException If writing to the stream fails or socket is not connected.
+ * @param message O objeto de protocolo a enviar.
+ * @throws IOException Se o socket estiver fechado ou ocorrer erro de escrita.
*/
public synchronized void sendMessage(MessageProtocol message) throws IOException {
- if (socket == null || !socket.isConnected()) {
+ if (socket == null || !socket.isConnected()) {
throw new IOException("Socket is not connected");
}
@@ -156,11 +163,11 @@ public class SocketConnection implements Closeable {
// Serializa para bytes JSON
byte[] data = serializer.serialize(message);
- // Write 4-byte length prefix
+ // Write 4-byte length prefix (Framing)
DataOutputStream dataOut = new DataOutputStream(outputStream);
dataOut.writeInt(data.length);
dataOut.write(data);
- dataOut.flush();
+ dataOut.flush(); // Force transmission immediately
} catch (SerializationException e) {
throw new IOException("Failed to serialize message", e);
@@ -168,11 +175,14 @@ public class SocketConnection implements Closeable {
}
/**
- * Tries to read (deserialize) a MessageProtocol object from the socket.
+ * Bloqueia à espera de uma mensagem completa do socket.
+ *
+ * Lê primeiro o cabeçalho de tamanho (4 bytes) e depois o payload exato,
+ * garantindo que processa mensagens completas mesmo se chegarem fragmentadas em múltiplos pacotes TCP.
*
- * @return The "envelope" (MessageProtocol) that was received.
- * @throws IOException If the connection is lost, the stream is corrupted, or socket is not connected.
- * @throws ClassNotFoundException If the received object is unknown.
+ * @return O objeto {@link MessageProtocol} reconstruído.
+ * @throws IOException Se a conexão for perdida (EOF) ou o stream corrompido.
+ * @throws ClassNotFoundException Se o tipo desserializado não for encontrado no classpath.
*/
public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException {
if (socket == null || !socket.isConnected()) {
@@ -184,15 +194,16 @@ public class SocketConnection implements Closeable {
DataInputStream dataIn = new DataInputStream(inputStream);
int length = dataIn.readInt();
- if (length <= 0 || length > 10_000_000) { // Sanity check (10MB max)
+ // Sanity check para evitar OutOfMemory em caso de corrupção de stream
+ if (length <= 0 || length > 10_000_000) { // Max 10MB payload
throw new IOException("Invalid message length: " + length);
}
-
+ // Ler dados exatos da mensagem
byte[] data = new byte[length];
dataIn.readFully(data);
- // JSON deserialization- use concrete Message class, not interface
+ // Deserialize do JSON - força o tipo concreto Message
return serializer.deserialize(data, sd.model.Message.class);
} catch (SerializationException e) {
@@ -200,8 +211,9 @@ public class SocketConnection implements Closeable {
}
}
- /**
- * Closes the socket and all streams (Input and Output).
+ /**
+ * Encerra a conexão e liberta os descritores de ficheiro.
+ * Operação idempotente.
*/
@Override
public void close() throws IOException {
@@ -211,8 +223,8 @@ public class SocketConnection implements Closeable {
}
/**
- * Checks the current state of the connection.
- * @return {@code true} if the socket is connected and currently open, {@code false} otherwise.
+ * Verifica o estado operacional da conexão.
+ * @return true se o socket está aberto e conectado.
*/
public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed();
diff --git a/main/src/main/java/sd/routing/LeastCongestedRouteSelector.java b/main/src/main/java/sd/routing/LeastCongestedRouteSelector.java
new file mode 100644
index 0000000..24f91d4
--- /dev/null
+++ b/main/src/main/java/sd/routing/LeastCongestedRouteSelector.java
@@ -0,0 +1,151 @@
+package sd.routing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementação da política de roteamento por menor congestionamento.
+ *
+ * Esta política escolhe dinamicamente a rota que passa pelos cruzamentos
+ * menos congestionados, com base no tamanho atual das filas em cada interseção.
+ * É uma política dinâmica que adapta as decisões ao estado da rede. Objetivo: Distribuir o tráfego pela rede, evitando bottlenecks e
+ * minimizando o tempo de espera total. Algoritmo: Esta política seleciona rotas com base em probabilidades predefinidas,
+ * sem considerar o estado atual da rede. É a implementação de referência
+ * para comparação com outras políticas. As rotas são organizadas por ponto de entrada (E1, E2, E3) e cada rota
+ * tem uma probabilidade de seleção associada. Define o contrato que todas as políticas de roteamento devem seguir.
+ * Permite a implementação de diferentes estratégias de roteamento
+ * (aleatória, caminho mais curto, menor congestionamento, etc.). As políticas de roteamento determinam como os veículos escolhem o caminho
+ * a seguir desde o ponto de entrada até à saída da rede de interseções. Esta política sempre escolhe a rota com o menor número de cruzamentos
+ * entre o ponto de entrada e a saída. É uma política determinística que
+ * não considera o estado da rede (tamanho das filas). Objetivo: Minimizar a distância teórica percorrida pelos veículos.
+ * Este serializador converte objetos Java para o formato de texto JSON antes da transmissão.
+ * Oferece várias vantagens técnicas sobre a serialização nativa do Java:
+ *
+ * Thread-Safety: A instância interna do {@code Gson} é imutável e thread-safe, permitindo
+ * que este serializador seja partilhado entre múltiplas threads (ex: no pool do DashboardServer).
+ * * @see MessageSerializer
*/
public class JsonMessageSerializer implements MessageSerializer {
@@ -28,16 +27,16 @@ public class JsonMessageSerializer implements MessageSerializer {
private final boolean prettyPrint;
/**
- * Creates a new JSON serializer with default configuration (no pretty printing).
+ * Cria um novo serializador JSON com configuração otimizada para produção (compacto).
*/
public JsonMessageSerializer() {
this(false);
}
/**
- * Creates a new JSON serializer with optional pretty printing.
- *
- * @param prettyPrint If true, JSON output will be formatted with indentation
+ * Cria um novo serializador JSON com formatação opcional.
+ * * @param prettyPrint Se {@code true}, o JSON gerado incluirá indentação e quebras de linha.
+ * Útil para debug, mas aumenta significativamente o tamanho do payload.
*/
public JsonMessageSerializer(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
@@ -53,6 +52,13 @@ public class JsonMessageSerializer implements MessageSerializer {
this.gson = builder.create();
}
+ /**
+ * Converte um objeto em memória para um array de bytes JSON (UTF-8).
+ *
+ * @param object O objeto a ser serializado.
+ * @return O payload em bytes pronto para transmissão TCP.
+ * @throws SerializationException Se o objeto não for compatível com JSON ou ocorrer erro de encoding.
+ */
@Override
public byte[] serialize(Object object) throws SerializationException {
if (object == null) {
@@ -68,6 +74,16 @@ public class JsonMessageSerializer implements MessageSerializer {
}
}
+ /**
+ * Reconstrói um objeto Java a partir de um array de bytes JSON.
+ *
+ * Realiza a validação sintática do JSON e a validação de tipo baseada na classe alvo.
+ *
+ * @param data O array de bytes recebido da rede.
+ * @param clazz A classe do objeto esperado (Type Token).
+ * @return A instância do objeto reconstruído.
+ * @throws SerializationException Se o JSON for malformado ou incompatível com a classe alvo.
+ */
@Override
public
+ * Esta abstração permite desacoplar a camada de transporte (Sockets TCP) da camada de
+ * apresentação de dados. Ao implementar o padrão Strategy, o sistema ganha flexibilidade
+ * para alternar entre diferentes formatos de codificação (JSON, Binário Nativo, XML, Protobuf)
+ * sem necessidade de refatorização da lógica de rede.
+ *
+ * Requisitos para Implementações:
+ *
+ * Esta classe atua como um wrapper unificador para erros ocorridos na camada de serialização,
+ * abstraindo falhas de baixo nível (como erros de I/O, sintaxe JSON inválida ou incompatibilidade
+ * de tipos) numa única exceção de domínio. Permite que o código cliente trate falhas de
+ * protocolo de forma consistente, independentemente da implementação subjacente (Gson, Nativa, etc.).
*/
public class SerializationException extends Exception {
private static final long serialVersionUID = 1L; // Long(64bits) instead of int(32bits)
/**
- * Constructs a new serialization exception with the specified detail message.
- *
- * @param message The detail message
+ * Constrói uma nova exceção de serialização com uma mensagem descritiva.
+ * * @param message A mensagem detalhando o erro.
*/
public SerializationException(String message) {
super(message);
}
/**
- * Constructs a new serialization exception with the specified detail message
- * and cause.
- *
- * @param message The detail message
- * @param cause The cause of this exception
+ * Constrói uma nova exceção encapsulando a causa raiz do problema.
+ * Útil para preservar a stack trace original de erros de bibliotecas terceiras (ex: Gson).
+ * * @param message A mensagem detalhando o erro.
+ * @param cause A exceção original que causou a falha.
*/
public SerializationException(String message, Throwable cause) {
super(message, cause);
}
/**
- * Constructs a new serialization exception with the specified cause.
- *
- * @param cause The cause of this exception
+ * Constrói uma nova exceção baseada apenas na causa raiz.
+ * * @param cause A exceção original.
*/
public SerializationException(Throwable cause) {
super(cause);
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/serialization/SerializerFactory.java b/main/src/main/java/sd/serialization/SerializerFactory.java
index a2261d3..70d2f0e 100644
--- a/main/src/main/java/sd/serialization/SerializerFactory.java
+++ b/main/src/main/java/sd/serialization/SerializerFactory.java
@@ -1,14 +1,14 @@
package sd.serialization;
/**
- * Factory for creating {@link MessageSerializer} instances.
- *
- * This factory provides a centralized way to create and configure JSON serializers
- * using Gson, making it easy to configure serialization throughout the application.
- *
- * The factory can be configured via system properties for easy deployment configuration.
- *
- * Example usage:
+ * Fábrica estática (Factory Pattern) para instanciação controlada de {@link MessageSerializer}.
+ *
+ * Esta classe centraliza a criação de estratégias de serialização, garantindo consistência
+ * de configuração em todo o sistema distribuído. Permite a injeção de configurações via
+ * Propriedades de Sistema (System Properties), facilitando a alternância entre modos de
+ * depuração (Pretty Print) e produção (Compacto) sem recompilação.
+ *
+ * Exemplo de Uso:
*
+ * Verifica a propriedade de sistema {@value #JSON_PRETTY_PRINT_PROPERTY}.
+ * Se não definida, assume o padrão de produção (falso/compacto).
+ * * @return Uma instância configurada de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createDefault() {
boolean prettyPrint = Boolean.getBoolean(JSON_PRETTY_PRINT_PROPERTY);
@@ -46,21 +45,20 @@ public class SerializerFactory {
}
/**
- * Creates a JSON serializer with default configuration (no pretty printing).
- *
- * @return A JsonMessageSerializer instance
+ * Cria um serializador JSON com configuração padrão otimizada (sem indentação).
+ * Ideal para ambientes de produção onde a largura de banda é prioritária.
+ * * @return Uma instância compacta de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createSerializer() {
return createSerializer(DEFAULT_JSON_PRETTY_PRINT);
}
/**
- * Creates a JSON serializer with specified pretty-print setting.
- *
- * @param prettyPrint Whether to enable pretty printing
- * @return A JsonMessageSerializer instance
+ * Cria um serializador JSON com configuração explícita de formatação.
+ * * @param prettyPrint {@code true} para ativar indentação (Debug), {@code false} para compacto.
+ * @return Uma instância personalizada de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createSerializer(boolean prettyPrint) {
return new JsonMessageSerializer(prettyPrint);
}
-}
+}
\ No newline at end of file
diff --git a/main/src/main/java/sd/util/RandomGenerator.java b/main/src/main/java/sd/util/RandomGenerator.java
index f1122d5..e9ab436 100644
--- a/main/src/main/java/sd/util/RandomGenerator.java
+++ b/main/src/main/java/sd/util/RandomGenerator.java
@@ -3,84 +3,88 @@ 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 central de geração estocástica para a simulação.
+ *
+ * Esta classe fornece primitivas para geração de números pseudo-aleatórios, abstraindo
+ * a complexidade de distribuições estatísticas.
+ *
+ * Funcionalidades Principais:
+ *
+ * Este método implementa o algoritmo de Inverse Transform Sampling para simular
+ * um Processo de Poisson homogêneo. É fundamental para modelar a chegada natural de
+ * veículos, onde eventos independentes ocorrem a uma taxa média constante.
+ *
+ * Fórmula Matemática: {@code T = -ln(1 - U) / λ}
+ *
+ * Utilizado para decisões de ramificação estocástica (ex: "Este veículo é um camião?").
*
- * @param probability A value between 0.0 (never) and 1.0 (always).
- * @return {@code true} or {@code false}, based on the probability.
+ * @param probability A probabilidade de retorno {@code true} (0.0 a 1.0).
+ * @return {@code true} se o evento ocorrer, {@code false} caso contrário.
*/
public static boolean occursWithProbability(double probability) {
return random.nextDouble() < probability;
}
/**
- * Picks a random element from the given array.
+ * Seleciona aleatoriamente um elemento de um array genérico (Amostragem Uniforme Discreta).
*
- * @param
+ * Importância Crítica: Permite tornar a simulação determinística. Ao fixar a seed,
+ * a sequência de números "aleatórios" gerada será idêntica em execuções subsequentes,
+ * facilitando a depuração de race conditions ou lógica complexa.
*
- * @param seed The seed to use.
+ * @param seed O valor da semente inicial (ex: timestamp ou constante).
*/
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..344b4d7 100644
--- a/main/src/main/java/sd/util/VehicleGenerator.java
+++ b/main/src/main/java/sd/util/VehicleGenerator.java
@@ -1,147 +1,116 @@
package sd.util;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import sd.config.SimulationConfig;
import sd.model.Vehicle;
import sd.model.VehicleType;
+import sd.routing.RouteSelector;
/**
- * 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).
+ * Motor de injeção de carga (Load Injector) para a simulação de tráfego.
+ *
+ * Esta classe atua como uma fábrica estocástica de veículos, sendo responsável por:
+ *
+ * Se o modelo for "POISSON", utiliza a técnica de Inverse Transform Sampling
+ * (via {@link RandomGenerator}) para gerar intervalos exponencialmente distribuídos,
+ * simulando a aleatoriedade natural do tráfego.
+ * * @param currentTime O tempo atual da simulação (base de cálculo).
+ * @return O instante futuro (t + delta) para agendamento do evento de geração.
*/
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.
+ * Instancia (Spawn) um novo veículo configurado e roteado.
+ *
+ * O processo de criação segue um pipeline:
+ *
+ * Normaliza as probabilidades configuradas e mapeia um número aleatório [0, 1)
+ * para o intervalo correspondente ao tipo de veículo.
*
- * @return The selected {@link VehicleType}.
+ * @return O tipo enumerado {@link VehicleType} 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
+ if (total == 0) return VehicleType.LIGHT; // Fallback de segurança
+
+ // Normalização
bikeProbability /= total;
lightProbability /= total;
@@ -157,73 +126,42 @@ public class VehicleGenerator {
}
/**
- * Selects a random route for a new vehicle.
- * This is a two-step process:
- * 1. Randomly select an entry point (E1, E2, or E3) with equal probability.
- * 2. From the chosen entry point's list of routes, select one
- * based on their defined probabilities (using cumulative probability).
+ * Seleciona um ponto de injeção na borda da rede (Edge Node).
+ * Distribuição Uniforme: ~33.3% para cada entrada (E1, E2, E3).
*
- * @return A {@link List} of strings representing the chosen route (e.g., ["Cr1", "Cr4", "S"]).
+ * @return O ID da interseção de entrada.
*/
- private List
+ * Permite que o Coordenador altere o comportamento da frota (ex: de RANDOM para SHORTEST_PATH)
+ * sem necessidade de reiniciar a simulação.
+ * * @param newRouteSelector A nova implementação de estratégia a utilizar.
+ */
+ public void setRouteSelector(RouteSelector newRouteSelector) {
+ this.routeSelector = newRouteSelector;
+ }
+
+ /**
+ * Retorna uma representação textual do estado interno do gerador.
+ * Útil para logs de auditoria e debugging.
*/
public String getInfo() {
- int totalRoutes = e1Routes.size() + e2Routes.size() + e3Routes.size();
return String.format(
- "VehicleGenerator{model=%s, rate=%.2f, interval=%.2f, routes=%d (E1:%d, E2:%d, E3:%d)}",
- arrivalModel, arrivalRate, fixedInterval, totalRoutes,
- e1Routes.size(), e2Routes.size(), e3Routes.size()
+ "VehicleGenerator{model=%s, rate=%.2f, interval=%.2f, routeSelector=%s}",
+ arrivalModel, arrivalRate, fixedInterval, routeSelector.getClass().getSimpleName()
);
}
-
- /**
- * A private inner "struct-like" class to hold a route (a List of strings)
- * and its associated selection probability.
- */
- private static class RouteWithProbability {
- final List
+ *
+ *
+ *
+ */
+ 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) {
+ // Backoff exponencial ou sleep curto para evitar busy-waiting em idle
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ continue;
+ }
+
+ // Aplicação de escala temporal (Throttle)
+ 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();
+ }
+
+ // Atualização atómica do tempo de simulação
+ clock.advanceTo(event.getTimestamp());
+
+ // Processamento polimórfico
+ 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();
+ }
+
+ /**
+ * Despachante central de eventos.
+ *
+ *
+ *
+ * @param event O evento que desencadeou a mudança de estado.
+ */
+ 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()));
+
+ // Processamento de lote (Batch Processing) para a fase Verde
+ if (newState == TrafficLightState.GREEN) {
+ processQueuedVehiclesForLight(light, event.getTimestamp());
+ }
+
+ // Agendamento do próximo ciclo (Feedback Loop)
+ double nextChangeTime = event.getTimestamp() +
+ (newState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime());
+
+ SimulationEvent nextEvent = new SimulationEvent(
+ nextChangeTime,
+ DESEventType.TRAFFIC_LIGHT_CHANGE,
+ tlEvent);
+ eventQueue.schedule(nextEvent);
+ }
+
+ /**
+ * Calcula a vazão da interseção durante uma fase verde.
+ *
+ *
+ *
+ * @param light O semáforo ativo.
+ * @param currentTime O instante de início da fase verde.
+ */
+ 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);
+
+ // Algoritmo de esvaziamento de fila baseado em Time Budget
+ while (light.getQueueSize() > 0) {
+ // Estimativa inicial (optimista)
+ double crossingTime = config.getLightVehicleCrossingTime();
+
+ // Verificação de limite de tempo (Hard Deadline do sinal vermelho)
+ if (timeOffset + crossingTime > greenDuration) {
+ break; // Veículo não cabe no ciclo atual
+ }
+
+ // Commit: Remove da fila
+ Vehicle vehicle = light.removeVehicle(currentTime + timeOffset);
+ if (vehicle == null)
+ break;
+
+ // Recálculo preciso baseado no tipo real do veículo
+ crossingTime = getCrossingTimeForVehicle(vehicle);
+
+ // Agendamento do evento futuro de término de travessia
+ double crossingStartTime = currentTime + timeOffset;
+ scheduleVehicleCrossing(vehicle, crossingStartTime, crossingTime);
+
+ // Incrementa offset para serializar as travessias (Head-of-Line Blocking)
+ timeOffset += crossingTime;
+
+ System.out.printf("[%s] Scheduled vehicle %s to cross at t=%.2f (duration=%.2fs)%n",
+ intersectionId, vehicle.getId(), crossingStartTime, crossingTime);
+ }
+ }
+
+ /**
+ * Cria e agenda o evento de conclusão de travessia (Partida).
+ *
+ * @param vehicle O veículo que está a atravessar.
+ * @param startTime Instante de início da travessia.
+ * @param crossingDuration Duração estimada da travessia.
+ */
+ 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));
+ }
+
+ /**
+ * Determina o custo temporal da travessia baseado na física do veículo.
+ *
+ * @param vehicle O veículo em questão.
+ * @return O tempo em segundos necessário para atravessar a interseção.
+ */
+ private double getCrossingTimeForVehicle(Vehicle vehicle) {
+ return switch (vehicle.getType()) {
+ case BIKE -> config.getBikeVehicleCrossingTime();
+ case LIGHT -> config.getLightVehicleCrossingTime();
+ case HEAVY -> config.getHeavyVehicleCrossingTime();
+ default -> config.getLightVehicleCrossingTime();
+ };
+ }
+
+ /**
+ * Manipula o evento de início de travessia de um veículo.
+ *
+ *
+ *
+ * @param event O evento de fim de travessia.
+ */
+ private void handleVehicleCrossingEndEvent(SimulationEvent event) {
+ Vehicle vehicle = (Vehicle) event.getPayload();
+
+ // Atualiza métricas do veículo
+ double crossingTime = getCrossingTimeForVehicle(vehicle);
+ vehicle.addCrossingTime(crossingTime);
+
+ // Atualiza métricas locais
+ intersection.incrementVehiclesSent();
+
+ // Handover: Transfere a responsabilidade do veículo para a rede
+ sendVehicleToNextDestination(vehicle);
+
+ eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
+ String.format("Vehicle %s departed at time %.2f", vehicle.getId(), event.getTimestamp()));
+ }
+
+ /**
+ * Finaliza a execução do processo de simulação.
+ *
+ * @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 análise post-mortem.
+ *
+ * @param outputPath O caminho do ficheiro onde o histórico será guardado.
+ */
+ 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());
+ }
+ }
+
+ /**
+ * Ponto de entrada principal da aplicação.
+ *
+ * @param args Argumentos da linha de comando (ID da interseção e ficheiro de
+ * configuração opcional).
+ */
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java IntersectionProcess
+ *
*/
public void shutdown() {
- // Check if already shutdown
if (!running) {
- return; // Already shutdown, do nothing
+ return;
}
System.out.println("\n[" + intersectionId + "] Shutting down...");
running = false;
- // Send final stats before closing connections
sendStatsToDashboard();
- // 1. Close ServerSocket first
+ // 1. Close ServerSocket
if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close();
@@ -530,28 +962,28 @@ public class IntersectionProcess {
}
}
- // 2. Shutdown thread pools with force
- if (trafficLightPool != null && !trafficLightPool.isShutdown()) {
- trafficLightPool.shutdownNow();
- }
+ // 2. Shutdown thread pools
if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) {
connectionHandlerPool.shutdownNow();
}
if (statsExecutor != null && !statsExecutor.isShutdown()) {
statsExecutor.shutdownNow();
}
+ if (departureExecutor != null && !departureExecutor.isShutdown()) {
+ departureExecutor.shutdownNow();
+ }
- // 3. Wait briefly for termination (don't block forever)
+ // 3. Wait briefly for termination
try {
- if (trafficLightPool != null) {
- trafficLightPool.awaitTermination(1, TimeUnit.SECONDS);
- }
if (connectionHandlerPool != null) {
connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS);
}
if (statsExecutor != null) {
statsExecutor.awaitTermination(1, TimeUnit.SECONDS);
}
+ if (departureExecutor != null) {
+ departureExecutor.awaitTermination(1, TimeUnit.SECONDS);
+ }
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@@ -578,31 +1010,32 @@ public class IntersectionProcess {
}
/**
- * Gets the Intersection object managed by this process.
- * Useful for testing and monitoring.
- *
- * @return The Intersection object.
+ * Obtém o modelo de dados da interseção.
+ *
+ * @return O objeto Intersection.
*/
public Intersection getIntersection() {
return intersection;
}
/**
- * Records that a vehicle has arrived at this intersection.
+ * Regista a chegada de um novo veículo para fins estatísticos.
*/
public void recordVehicleArrival() {
totalArrivals++;
}
/**
- * Records that a vehicle has departed from this intersection.
+ * Regista a partida de um veículo para fins estatísticos.
*/
public void recordVehicleDeparture() {
totalDepartures++;
}
/**
- * Sends current statistics to the dashboard server.
+ * Envia um "snapshot" do estado atual para o Dashboard (Telemetria Push).
+ *
+ *
+ *
+ * @return Uma String contendo o relatório completo formatado.
+ */
+ 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("ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO\n");
+ report.append("=".repeat(80)).append("\n");
+ report.append("Configuração: ").append(configurationFile).append("\n");
+ report.append("Número de Execuções: ").append(results.size()).append("\n");
+ report.append("Data da Análise: ").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("MÉTRICAS GLOBAIS\n");
+ report.append("-".repeat(80)).append("\n\n");
+
+ report.append(analyzeMetric("Veículos Gerados",
+ extractValues(r -> (double) r.getTotalVehiclesGenerated())));
+ report.append("\n");
+
+ report.append(analyzeMetric("Veículos Completados",
+ extractValues(r -> (double) r.getTotalVehiclesCompleted())));
+ report.append("\n");
+
+ report.append(analyzeMetric("Taxa de Conclusão (%)",
+ extractValues(r -> r.getTotalVehiclesGenerated() > 0
+ ? 100.0 * r.getTotalVehiclesCompleted() / r.getTotalVehiclesGenerated()
+ : 0.0)));
+ report.append("\n");
+
+ report.append(analyzeMetric("Tempo Médio no Sistema (segundos)",
+ extractValues(r -> r.getAverageSystemTime())));
+ report.append("\n");
+
+ report.append(analyzeMetric("Tempo Médio de Espera (segundos)",
+ extractValues(r -> r.getAverageWaitingTime())));
+ report.append("\n");
+
+ // Per-vehicle-type analysis
+ report.append("\n");
+ report.append("-".repeat(80)).append("\n");
+ report.append("ANÁLISE POR TIPO DE VEÍCULO\n");
+ report.append("-".repeat(80)).append("\n\n");
+
+ for (VehicleType type : VehicleType.values()) {
+ report.append("--- ").append(type).append(" ---\n");
+
+ report.append(analyzeMetric(" Contagem de Veículos",
+ extractValues(r -> (double) r.getVehicleCountByType().getOrDefault(type, 0))));
+ report.append("\n");
+
+ report.append(analyzeMetric(" Tempo Médio no Sistema (segundos)",
+ extractValues(r -> r.getAvgSystemTimeByType().getOrDefault(type, 0.0))));
+ report.append("\n");
+
+ report.append(analyzeMetric(" Tempo Médio de Espera (segundos)",
+ extractValues(r -> r.getAvgWaitTimeByType().getOrDefault(type, 0.0))));
+ report.append("\n\n");
+ }
+
+ // Per-intersection analysis
+ report.append("-".repeat(80)).append("\n");
+ report.append("ANÁLISE POR INTERSEÇÃO\n");
+ report.append("-".repeat(80)).append("\n\n");
+
+ Set
+ *
*
- * @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 O caminho ou nome do recurso do ficheiro {@code .properties}.
+ * @throws IOException Se o ficheiro não puder ser localizado em nenhuma das estratégias,
+ * com uma mensagem detalhada das tentativas falhadas.
*/
public SimulationConfig(String filePath) throws IOException {
properties = new Properties();
@@ -138,6 +155,12 @@ public class SimulationConfig {
throw new IOException(errorMsg.toString(), fileSystemException);
}
+ /**
+ * Carrega a configuração da topologia de rede a partir do ficheiro "network_config.json".
+ *
+ *
+ * @return O fator de escala.
+ */
+ public double getTimeScale() {
+ return Double.parseDouble(properties.getProperty("simulation.time.scale", "0"));
+ }
+
+ /**
+ * Obtém o tempo de "drenagem" (drain time) em segundos virtuais.
+ *
+ *
*/
public class CoordinatorProcess {
private final SimulationConfig config;
private final VehicleGenerator vehicleGenerator;
+
+ /** Mapa de clientes TCP persistentes para cada interseção (Worker Nodes). */
private final Map
+ *
+ * Este mecanismo garante que o recetor saiba exatamente quantos bytes ler,
+ * prevenindo problemas de fragmentação ou aglutinação de pacotes TCP.
*
- * @param message The message to send
- * @throws SerializationException if serialization fails
- * @throws IOException if the socket write fails
+ * @param message O objeto de domínio a ser enviado.
+ * @throws SerializationException Se o objeto não puder ser convertido para bytes.
+ * @throws IOException Se houver falha na escrita do socket (ex: conexão resetada).
*/
public void send(Message message) throws SerializationException, IOException {
if (socket == null || socket.isClosed()) {
@@ -71,13 +83,14 @@ public class SocketClient {
try {
byte[] data = serializer.serialize(message);
- // Prefix with message length (so receiver knows how much to read)
int length = data.length;
+ // Write 4-byte length header (Big Endian)
outputStream.write((length >> 24) & 0xFF);
outputStream.write((length >> 16) & 0xFF);
outputStream.write((length >> 8) & 0xFF);
outputStream.write(length & 0xFF);
+ // Write payload
outputStream.write(data);
outputStream.flush();
@@ -88,8 +101,10 @@ public class SocketClient {
}
/**
- * Closes the socket connection safely.
- * Calling it multiple times won’t cause issues.
+ * Realiza o encerramento gracioso (graceful shutdown) da conexão.
+ * Liberta os recursos do sistema operativo (descritores de arquivo).
+ *
+ *
+ * A execução ocorre numa thread separada (background) para manter a responsividade da UI.
+ */
+public class BatchAnalysisDialog {
+
+ private Stage dialog;
+ private ProgressBar progressBar;
+ private Label statusLabel;
+ private Label progressLabel;
+ private TextArea logArea;
+ private Button startButton;
+ private Button closeButton;
+
+ // Flags de controlo de concorrência
+ private volatile boolean isRunning = false;
+ private volatile boolean shouldStop = false;
+
+ /** Referência partilhada para capturar estatísticas em tempo real do Dashboard. */
+ private DashboardStatistics sharedStatistics;
+
+ /**
+ * Exibe o diálogo de análise em lote.
+ * * @param owner A janela pai (Stage) para modalidade.
+ * @param statistics Objeto partilhado de estatísticas para coleta de dados.
+ */
+ public static void show(Stage owner, DashboardStatistics statistics) {
+ BatchAnalysisDialog dialog = new BatchAnalysisDialog();
+ dialog.sharedStatistics = statistics;
+ dialog.createAndShow(owner);
+ }
+
+ /**
+ * Constrói e inicializa a interface gráfica do diálogo.
+ */
+ private void createAndShow(Stage owner) {
+ dialog = new Stage();
+ dialog.initOwner(owner);
+ dialog.initModality(Modality.APPLICATION_MODAL);
+ dialog.setTitle("Batch Performance Analysis");
+
+ VBox root = new VBox(20);
+ root.setPadding(new Insets(20));
+ root.setAlignment(Pos.TOP_CENTER);
+ // Estilo Dark Mode conforme guidelines visuais
+ root.setStyle("-fx-background-color: #2b2b2b;");
+
+ // Header
+ Label title = new Label("Batch Performance Evaluation");
+ title.setStyle("-fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: white;");
+
+ Label subtitle = new Label("Executar múltiplas simulações para gerar análise estatística consolidada");
+ subtitle.setStyle("-fx-font-size: 12px; -fx-text-fill: #cccccc;");
+ subtitle.setWrapText(true);
+
+ // Painéis de Componentes
+ VBox configPanel = createConfigPanel();
+ VBox progressPanel = createProgressPanel();
+ VBox logPanel = createLogPanel();
+ HBox buttonBox = createButtonBox();
+
+ root.getChildren().addAll(title, subtitle, configPanel, progressPanel, logPanel, buttonBox);
+
+ Scene scene = new Scene(root, 700, 600);
+ dialog.setScene(scene);
+
+ // Tratamento de fecho da janela: interromper thread de worker se ativa
+ dialog.setOnCloseRequest(e -> {
+ if (isRunning) {
+ e.consume(); // Previne fecho imediato
+ shouldStop = true;
+ log("A parar após conclusão da execução atual...");
+ }
+ });
+
+ dialog.show();
+ }
+
+ private VBox createConfigPanel() {
+ VBox panel = new VBox(15);
+ panel.setPadding(new Insets(15));
+ panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");
+
+ Label header = new Label("Configuração");
+ header.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: white;");
+
+ // Runs per scenario
+ HBox runsBox = new HBox(10);
+ runsBox.setAlignment(Pos.CENTER_LEFT);
+ Label runsLabel = new Label("Execuções por cenário:");
+ runsLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;");
+ Spinner
+ *
+ */
+public class ConfigurationDialog {
+
+ /**
+ * Exibe o diálogo de configuração avançada e captura as intenções do utilizador.
+ *
+ *
*/
public class DashboardClientHandler implements Runnable {
private final Socket clientSocket;
private final DashboardStatistics statistics;
+ /**
+ * Inicializa o handler com o socket ativo e a referência para o agregador de estatísticas.
+ *
+ * @param clientSocket O socket TCP conectado ao nó remoto.
+ * @param statistics O objeto singleton partilhado onde as métricas serão agregadas.
+ */
public DashboardClientHandler(Socket clientSocket, DashboardStatistics statistics) {
this.clientSocket = clientSocket;
this.statistics = statistics;
}
+ /**
+ * Ciclo de vida da conexão.
+ *
+ *
*/
public class DashboardServer {
private final int port;
+
+ /** Armazenamento em memória (Thread-safe) do estado global do sistema. */
private final DashboardStatistics statistics;
+
+ /** Pool de threads para isolamento de falhas e gestão de recursos de I/O. */
private final ExecutorService clientHandlerPool;
+
+ /** Flag atómica para controlo seguro do ciclo de vida do servidor. */
private final AtomicBoolean running;
+
private ServerSocket serverSocket;
+ /**
+ * Ponto de entrada (Bootstrap) da aplicação de monitorização.
+ *
+ *
*/
public class DashboardUI extends Application {
@@ -51,9 +66,18 @@ public class DashboardUI extends Application {
// Intersection Table
private TableView
+ *
*/
public class SimulationProcessManager {
private final List
+ *
+ * * @throws IOException Se falhar o fork de algum processo.
*/
public void startSimulation() throws IOException {
if (!runningProcesses.isEmpty()) {
@@ -54,7 +84,42 @@ public class SimulationProcessManager {
}
/**
- * Stops all running simulation processes.
+ * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador.
+ *
+ *
+ * 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
+ *
+ */
+public class SimulationEvent implements Comparable
+ *
+ *
+ * @param other O outro evento a comparar.
+ * @return Inteiro negativo, zero ou positivo conforme a ordem.
+ */
+ @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 to ensure reproducible runs
+ 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;
+ }
+}
\ 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
new file mode 100644
index 0000000..0b27107
--- /dev/null
+++ b/main/src/main/java/sd/des/TrafficLightEvent.java
@@ -0,0 +1,55 @@
+package sd.des;
+
+import sd.model.TrafficLight;
+
+/**
+ * Encapsula o contexto de dados para eventos de mudança de estado de semáforos.
+ *
+ *
+ */
+public class EventLogger {
+
+ private static EventLogger instance;
+ 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
+ *
+ */
+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;
+
+ EventType(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ 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
new file mode 100644
index 0000000..611e6aa
--- /dev/null
+++ b/main/src/main/java/sd/logging/VehicleTracer.java
@@ -0,0 +1,364 @@
+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;
+
+/**
+ * Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais.
+ *
+ *
+ */
+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
+ *
*/
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
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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 65dd4e5..16dcdd4 100644
--- a/main/src/main/java/sd/protocol/SocketConnection.java
+++ b/main/src/main/java/sd/protocol/SocketConnection.java
@@ -16,10 +16,17 @@ import sd.serialization.MessageSerializer;
import sd.serialization.SerializationException;
import sd.serialization.SerializerFactory;
-
/**
- * Wrapper class that simplifies communication via Sockets.
- * Includes connection retry logic for robustness.
+ * Wrapper de alto nível para gestão robusta de conexões TCP.
+ *
+ *
*/
public class SocketConnection implements Closeable {
@@ -49,22 +56,24 @@ public class SocketConnection implements Closeable {
*/
private final MessageSerializer serializer;
- // --- Configuration for Retry Logic ---
- /** Maximum number of connection attempts. */
+ /** Número máximo de tentativas de ligação antes de desistir (Fail-fast). */
private static final int MAX_RETRIES = 5;
- /** Delay between retry attempts in milliseconds. */
+
+ /** Janela de espera (backoff) linear entre tentativas (em 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 para clientes (Active Open).
+ * Tenta estabelecer uma conexão TCP com um servidor, aplicando lógica de retry.
+ *
+ *
+ */
+public class LeastCongestedRouteSelector implements RouteSelector {
+
+ /** Rotas possíveis a partir do ponto de entrada E1 */
+ private final List> e1Routes;
+ /** Rotas possíveis a partir do ponto de entrada E2 */
+ private final List
> e2Routes;
+ /** Rotas possíveis a partir do ponto de entrada E3 */
+ private final List
> e3Routes;
+
+ /**
+ * Cria um novo seletor de rotas baseado em menor congestionamento.
+ */
+ public LeastCongestedRouteSelector() {
+ this.e1Routes = new ArrayList<>();
+ this.e2Routes = new ArrayList<>();
+ this.e3Routes = new ArrayList<>();
+ initializeRoutes();
+ }
+
+ /**
+ * Inicializa as rotas possíveis para cada ponto de entrada.
+ */
+ private void initializeRoutes() {
+ // Rotas de E1 (entrada Norte)
+ e1Routes.add(Arrays.asList("Cr1", "Cr4", "Cr5", "S"));
+ e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr5", "S"));
+ e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr3", "S"));
+
+ // Rotas de E2 (entrada Oeste)
+ e2Routes.add(Arrays.asList("Cr2", "Cr5", "S"));
+ e2Routes.add(Arrays.asList("Cr2", "Cr3", "S"));
+ e2Routes.add(Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"));
+
+ // Rotas de E3 (entrada Sul)
+ e3Routes.add(Arrays.asList("Cr3", "S"));
+ e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr5", "S"));
+ e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"));
+ }
+
+ @Override
+ public List
> availableRoutes = getRoutesForEntryPoint(entryPoint);
+
+ // Se não temos informação sobre filas, usa a rota mais curta como fallback
+ if (queueSizes == null || queueSizes.isEmpty()) {
+ return selectShortestRoute(availableRoutes);
+ }
+
+ // Calcula a carga de cada rota e escolhe a menos congestionada
+ List
> routes) {
+ List
> getRoutesForEntryPoint(String entryPoint) {
+ switch (entryPoint.toUpperCase()) {
+ case "E1":
+ return e1Routes;
+ case "E2":
+ return e2Routes;
+ case "E3":
+ return e3Routes;
+ default:
+ System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
+ return e1Routes;
+ }
+ }
+}
diff --git a/main/src/main/java/sd/routing/RandomRouteSelector.java b/main/src/main/java/sd/routing/RandomRouteSelector.java
new file mode 100644
index 0000000..5b9df21
--- /dev/null
+++ b/main/src/main/java/sd/routing/RandomRouteSelector.java
@@ -0,0 +1,122 @@
+package sd.routing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementação da política de roteamento aleatória (baseline).
+ *
+ *
+ *
+ */
+public enum RoutingPolicy {
+ /**
+ * Política aleatória (baseline).
+ * Seleciona rotas com base em probabilidades predefinidas, sem considerar
+ * o estado atual da rede.
+ */
+ RANDOM,
+
+ /**
+ * Política do caminho mais curto.
+ * Sempre escolhe a rota com o menor número de cruzamentos entre o ponto
+ * de entrada e a saída, minimizando a distância teórica.
+ */
+ SHORTEST_PATH,
+
+ /**
+ * Política das menores filas (roteamento dinâmico).
+ * Escolhe a rota que passa pelos cruzamentos menos congestionados,
+ * com base no tamanho atual das filas em cada interseção.
+ */
+ LEAST_CONGESTED
+}
diff --git a/main/src/main/java/sd/routing/ShortestPathRouteSelector.java b/main/src/main/java/sd/routing/ShortestPathRouteSelector.java
new file mode 100644
index 0000000..a92321d
--- /dev/null
+++ b/main/src/main/java/sd/routing/ShortestPathRouteSelector.java
@@ -0,0 +1,89 @@
+package sd.routing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementação da política de roteamento por caminho mais curto.
+ *
+ * > e1Routes;
+ /** Rotas possíveis a partir do ponto de entrada E2, ordenadas por comprimento */
+ private final List
> e2Routes;
+ /** Rotas possíveis a partir do ponto de entrada E3, ordenadas por comprimento */
+ private final List
> e3Routes;
+
+ /**
+ * Cria um novo seletor de rotas por caminho mais curto.
+ * As rotas são ordenadas por comprimento (número de cruzamentos).
+ */
+ public ShortestPathRouteSelector() {
+ this.e1Routes = new ArrayList<>();
+ this.e2Routes = new ArrayList<>();
+ this.e3Routes = new ArrayList<>();
+ initializeRoutes();
+ }
+
+ /**
+ * Inicializa as rotas possíveis para cada ponto de entrada.
+ * As rotas são organizadas da mais curta para a mais longa.
+ */
+ private void initializeRoutes() {
+ // Rotas de E1 (entrada Norte) - ordenadas por comprimento
+ e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr3", "S")); // 4 nós
+ e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr5", "S")); // 4 nós
+ e1Routes.add(Arrays.asList("Cr1", "Cr4", "Cr5", "S")); // 4 nós
+
+ // Rotas de E2 (entrada Oeste) - ordenadas por comprimento
+ e2Routes.add(Arrays.asList("Cr2", "Cr3", "S")); // 3 nós (mais curta!)
+ e2Routes.add(Arrays.asList("Cr2", "Cr5", "S")); // 3 nós
+ e2Routes.add(Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S")); // 5 nós
+
+ // Rotas de E3 (entrada Sul) - ordenadas por comprimento
+ e3Routes.add(Arrays.asList("Cr3", "S")); // 2 nós (mais curta!)
+ e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr5", "S")); // 4 nós
+ e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S")); // 6 nós
+ }
+
+ @Override
+ public List
> availableRoutes = getRoutesForEntryPoint(entryPoint);
+
+ // Retorna a rota mais curta (primeira da lista)
+ List
> getRoutesForEntryPoint(String entryPoint) {
+ switch (entryPoint.toUpperCase()) {
+ case "E1":
+ return e1Routes;
+ case "E2":
+ return e2Routes;
+ case "E3":
+ return e3Routes;
+ default:
+ System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
+ return e1Routes;
+ }
+ }
+}
diff --git a/main/src/main/java/sd/serialization/JsonMessageSerializer.java b/main/src/main/java/sd/serialization/JsonMessageSerializer.java
index 1b70c68..68c6b06 100644
--- a/main/src/main/java/sd/serialization/JsonMessageSerializer.java
+++ b/main/src/main/java/sd/serialization/JsonMessageSerializer.java
@@ -1,26 +1,25 @@
package sd.serialization;
+import java.nio.charset.StandardCharsets;
+
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
-import java.nio.charset.StandardCharsets;
-
/**
- * JSON-based implementation of {@link MessageSerializer} using Google's Gson library.
- *
- * This serializer converts objects to JSON format for transmission, providing:
- * - Human-readable message format (easy debugging)
- * - Cross-platform compatibility
- * - Smaller message sizes compared to Java native serialization
- * - Better security (no code execution during deserialization)
- *
- * The serializer is configured with pretty printing disabled by default for
- * production use, but can be enabled for debugging purposes.
- *
- * Thread-safety: This class is thread-safe as Gson instances are thread-safe.
- *
- * @see MessageSerializer
+ * Implementação baseada em JSON da estratégia {@link MessageSerializer}, utilizando a biblioteca Gson.
+ *
+ *
+ *
+ *
+ * * @see JsonMessageSerializer
*/
public interface MessageSerializer {
/**
- * Serializes an object into a byte array for transmission.
- *
- * @param object The object to serialize (must not be null)
- * @return A byte array containing the serialized representation
- * @throws SerializationException If serialization fails
- * @throws IllegalArgumentException If object is null
+ * Converte (Marshals) um objeto em memória para uma sequência de bytes para transmissão.
+ * * @param object O objeto de domínio a ser serializado (não pode ser nulo).
+ * @return Um array de bytes contendo a representação codificada do objeto.
+ * @throws SerializationException Se ocorrer um erro durante a codificação (ex: ciclo de referências).
+ * @throws IllegalArgumentException Se o objeto fornecido for nulo.
*/
byte[] serialize(Object object) throws SerializationException;
/**
- * Deserializes a byte array back into an object of the specified type.
- *
- * @param
* MessageSerializer serializer = SerializerFactory.createDefault();
* byte[] data = serializer.serialize(myObject);
@@ -17,28 +17,27 @@ package sd.serialization;
public class SerializerFactory {
/**
- * System property key for enabling pretty-print in JSON serialization.
- * Set to "true" for debugging, "false" for production.
+ * Chave da propriedade de sistema para ativar a formatação JSON legível (Pretty Print).
+ * Defina {@code -Dsd.serialization.json.prettyPrint=true} na JVM para ativar.
*/
public static final String JSON_PRETTY_PRINT_PROPERTY = "sd.serialization.json.prettyPrint";
- // Default configuration
+ // Default configuration (Production-ready)
private static final boolean DEFAULT_JSON_PRETTY_PRINT = false;
/**
- * Private constructor to prevent instantiation.
+ * Construtor privado para prevenir instanciação acidental desta classe utilitária.
*/
private SerializerFactory() {
throw new UnsupportedOperationException("Factory class cannot be instantiated");
}
/**
- * Creates a JSON serializer based on system configuration.
- *
- * Pretty-print is determined by checking the system property
- * {@value #JSON_PRETTY_PRINT_PROPERTY}. If not set, defaults to false.
- *
- * @return A configured JsonMessageSerializer instance
+ * Cria um serializador JSON configurado dinamicamente pelo ambiente.
+ *
+ *
*/
public class RandomGenerator {
- /**
- * The single, shared Random instance for the entire simulation.
+ /** * Instância singleton estática do gerador PRNG (Pseudo-Random Number Generator).
+ * Thread-safe (java.util.Random é sincronizado), embora possa haver contenção em alta concorrência.
*/
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.
+ * Gera um intervalo de tempo seguindo uma Distribuição Exponencial.
+ *
Onde:
+ *
+ *
*
- * @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 A taxa média de chegada (λ > 0).
+ * @return O intervalo de tempo (delta t) até o próximo evento, em segundos.
*/
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.
+ * Gera um número inteiro uniformemente distribuído no intervalo fechado {@code [min, max]}.
*
- * @param min The minimum possible value.
- * @param max The maximum possible value.
- * @return A random integer in the range [min, max].
+ * @param min Limite inferior (inclusivo).
+ * @param max Limite superior (inclusivo).
+ * @return Um inteiro aleatório I tal que {@code min <= I <= 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).
+ * Gera um número de ponto flutuante uniformemente distribuído no intervalo semi-aberto {@code [min, max)}.
*
- * @param min The minimum possible value.
- * @param max The maximum possible value.
- * @return A random double in the range [min, max).
+ * @param min Limite inferior (inclusivo).
+ * @param max Limite superior (exclusivo).
+ * @return Um double aleatório D tal que {@code min <= D < 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.
+ * Realiza um teste de Bernoulli (Sim/Não) com uma probabilidade de sucesso especificada.
+ *
+ *
*/
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
- // --- Predefined Routes ---
- // These lists store all possible routes, grouped by where they start.
+ /** Parâmetro Lambda (λ) para a distribuição de Poisson (taxa de chegada). */
+ private final double arrivalRate;
- /** Routes starting from entry point E1. */
- private final List
+ *
*
- * @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 O identificador único sequencial (ex: "V104").
+ * @param entryTime O timestamp de criação.
+ * @param queueSizes Snapshot atual das filas (usado apenas por estratégias dinâmicas como LEAST_CONGESTED).
+ * @return A entidade {@link Vehicle} pronta para inserção na malha.
*/
- public Vehicle generateVehicle(String vehicleId, double entryTime) {
+ public Vehicle generateVehicle(String vehicleId, double entryTime, Map