diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 48035b2..0ae2296 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -77,7 +77,7 @@ jobs: publish-release: runs-on: ubuntu-latest needs: [build, build-windows] - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' permissions: contents: write steps: @@ -94,6 +94,11 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: + tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'snapshot-build' }} + name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'Manual Snapshot Build' }} + draft: false + prerelease: true + make_latest: false files: | main/target/*.jar windows-dist/*.zip diff --git a/.gitignore b/.gitignore index 7388073..9d01a52 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ # Log files *.log +*.trace +logs +*.md # BlueJ files *.ctxt @@ -47,4 +50,10 @@ build/ # Other *.swp -.$Diagrama de arquitetura - SD.drawio.bkp +*.pdf + +# JAR built pom file +dependency-reduced-pom.xml + +# Python env +venv/ 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/pom.xml b/main/pom.xml index 56ce74f..3ecd199 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -29,6 +29,18 @@ gson 2.10.1 + + + + org.openjfx + javafx-controls + 17.0.2 + + + org.openjfx + javafx-fxml + 17.0.2 + @@ -39,7 +51,16 @@ exec-maven-plugin 3.1.0 - sd.Entry + sd.dashboard.Launcher + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + sd.dashboard.Launcher @@ -55,7 +76,7 @@ - sd.Entry + sd.dashboard.Launcher diff --git a/main/src/main/java/sd/Entry.java b/main/src/main/java/sd/Entry.java deleted file mode 100644 index 323ce66..0000000 --- a/main/src/main/java/sd/Entry.java +++ /dev/null @@ -1,94 +0,0 @@ -package sd; - -import java.io.IOException; - -import sd.config.SimulationConfig; -import sd.engine.SimulationEngine; - -/** - * Main entry point for the traffic simulation. - * * This class is responsible for loading the simulation configuration, - * initializing the {@link SimulationEngine}, and starting the simulation run. - * It also prints initial configuration details and final execution time. - */ -public class Entry { - - /** - * The default path to the simulation configuration file. - * This is used if no command-line arguments are provided. - */ - private static final String DEFAULT_CONFIG_FILE = "src/main/resources/simulation.properties"; - - /** - * The main method to start the simulation. - * * @param args Command-line arguments. If provided, args[0] is expected - * to be the path to a custom configuration file. - */ - public static void main(String[] args) { - System.out.println("=".repeat(60)); - System.out.println("TRAFFIC SIMULATION - DISCRETE EVENT SIMULATOR"); - System.out.println("=".repeat(60)); - - try { - // 1. Load configuration - String configFile = args.length > 0 ? args[0] : DEFAULT_CONFIG_FILE; - System.out.println("Loading configuration from: " + configFile); - - SimulationConfig config = new SimulationConfig(configFile); - - // 2. Display configuration - displayConfiguration(config); - - // 3. Create and initialize simulation engine - SimulationEngine engine = new SimulationEngine(config); - engine.initialize(); - - System.out.println("\n" + "=".repeat(60)); - - // 4. Run simulation - long startTime = System.currentTimeMillis(); - engine.run(); - long endTime = System.currentTimeMillis(); - - // 5. Display execution time - double executionTime = (endTime - startTime) / 1000.0; - System.out.println("\nExecution time: " + String.format("%.2f", executionTime) + " seconds"); - System.out.println("=".repeat(60)); - - } catch (IOException e) { - System.err.println("Error loading configuration: " + e.getMessage()); - e.printStackTrace(); - } catch (Exception e) { - System.err.println("Error during simulation: " + e.getMessage()); - e.printStackTrace(); - } - } - - /** - * Displays the main configuration parameters to the console. - * This provides a summary of the simulation settings before it starts. - * - * @param config The {@link SimulationConfig} object containing the loaded settings. - */ - private static void displayConfiguration(SimulationConfig config) { - System.out.println("\nSIMULATION CONFIGURATION:"); - System.out.println(" Duration: " + config.getSimulationDuration() + " seconds"); - System.out.println(" Arrival Model: " + config.getArrivalModel()); - - if ("POISSON".equalsIgnoreCase(config.getArrivalModel())) { - System.out.println(" Arrival Rate (λ): " + config.getArrivalRate() + " vehicles/second"); - } else { - System.out.println(" Fixed Interval: " + config.getFixedArrivalInterval() + " seconds"); - } - - System.out.println(" Statistics Update Interval: " + config.getStatisticsUpdateInterval() + " seconds"); - - System.out.println("\nVEHICLE TYPES:"); - System.out.println(" Bike: " + (config.getBikeVehicleProbability() * 100) + "% " + - "(crossing time: " + config.getBikeVehicleCrossingTime() + "s)"); - System.out.println(" Light: " + (config.getLightVehicleProbability() * 100) + "% " + - "(crossing time: " + config.getLightVehicleCrossingTime() + "s)"); - System.out.println(" Heavy: " + (config.getHeavyVehicleProbability() * 100) + "% " + - "(crossing time: " + config.getHeavyVehicleCrossingTime() + "s)"); - } -} \ No newline at end of file diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java new file mode 100644 index 0000000..a5868e5 --- /dev/null +++ b/main/src/main/java/sd/ExitNodeProcess.java @@ -0,0 +1,532 @@ +package sd; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +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; +import sd.model.VehicleType; +import sd.protocol.MessageProtocol; +import sd.protocol.SocketConnection; + +/** + * 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; + + /** Pool de threads elástica para tratamento de conexões de entrada. */ + private final ExecutorService connectionHandlerPool; + + // 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; + + /** Instante de início da simulação (milissegundos) sincronizado com o Coordenador. */ + private long simulationStartMillis; + + /** Contador atómico (via synchronized) de throughput total. */ + private int totalVehiclesReceived; + + /** Tempo acumulado no sistema (System Time) de todos os veículos. */ + private double totalSystemTime; + + /** Tempo acumulado em espera (Waiting Time) de todos os veículos. */ + private double totalWaitingTime; + + /** Tempo acumulado em travessia (Service Time) de todos os veículos. */ + private double totalCrossingTime; + + /** Agregação por categoria de veículo. */ + private final Map vehicleTypeCount; + + /** Latência acumulada por categoria. */ + private final Map vehicleTypeWaitTime; + + /** Cliente TCP persistente para push de métricas ao Dashboard. */ + private SocketClient dashboardClient; + + /** + * Bootstrap do processo ExitNode. + * Carrega configuração, inicializa subsistemas e entra no loop de serviço. + * * @param args Argumentos de CLI (caminho do config). + */ + public static void main(String[] args) { + System.out.println("=".repeat(60)); + System.out.println("EXIT NODE PROCESS"); + System.out.println("=".repeat(60)); + + try { + EventLogger.getInstance().log(EventType.PROCESS_STARTED, "ExitNode", "Exit node process started"); + + String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties"; + System.out.println("Loading configuration from: " + configFile); + + SimulationConfig config = new SimulationConfig(configFile); + ExitNodeProcess exitNode = new ExitNodeProcess(config); + + System.out.println("\n" + "=".repeat(60)); + exitNode.initialize(); + + System.out.println("\n" + "=".repeat(60)); + exitNode.start(); + + } catch (IOException e) { + System.err.println("Failed to start exit node: " + e.getMessage()); + EventLogger.getInstance().logError("ExitNode", "Failed to start", e); + System.exit(1); + } catch (Exception e) { + System.err.println("Exit node error: " + e.getMessage()); + EventLogger.getInstance().logError("ExitNode", "Exit node error", e); + System.exit(1); + } finally { + EventLogger.getInstance().log(EventType.PROCESS_STOPPED, "ExitNode", "Exit node process stopped"); + } + } + + /** + * Instancia o nó de saída. + * Prepara os acumuladores estatísticos e a infraestrutura de logging distribuído. + * * @param config A configuração global da simulação. + */ + public ExitNodeProcess(SimulationConfig config) { + this.config = config; + this.connectionHandlerPool = Executors.newCachedThreadPool(); + this.running = false; + + this.totalVehiclesReceived = 0; + this.totalSystemTime = 0.0; + this.totalWaitingTime = 0.0; + this.totalCrossingTime = 0.0; + this.vehicleTypeCount = new HashMap<>(); + this.vehicleTypeWaitTime = new HashMap<>(); + + // Inicializa os counters para cada tipo de veículo + for (VehicleType type : VehicleType.values()) { + vehicleTypeCount.put(type, 0); + vehicleTypeWaitTime.put(type, 0.0); + } + + // Initialize DES components + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); // Track history + this.eventLogger = EventLogger.getInstance(); + + eventLogger.log(EventType.PROCESS_STARTED, "ExitNode", + "Exit node initialized with DES architecture"); + + System.out.println("Exit node initialized (DES Mode)"); + System.out.println(" - Exit port: " + config.getExitPort()); + System.out.println(" - Dashboard: " + config.getDashboardHost() + ":" + config.getDashboardPort()); + } + + /** + * Estabelece o canal de controlo (Control Plane) com o Dashboard. + * Essencial para a visualização em tempo real das métricas de saída. + */ + public void initialize() { + System.out.println("Connecting to dashboard..."); + + try { + String host = config.getDashboardHost(); + int port = config.getDashboardPort(); + + dashboardClient = new SocketClient("Dashboard", host, port); + dashboardClient.connect(); + + System.out.println("Successfully connected to dashboard"); + } catch (IOException e) { + System.err.println("WARNING: Failed to connect to dashboard: " + e.getMessage()); + System.err.println("Exit node will continue without dashboard connection"); + } + } + + /** + * Inicia a thread de processamento de eventos DES. + * Embora o ExitNode seja primariamente reativo (Network-driven), o motor DES + * é mantido para consistência de relógio e agendamento de fim de simulação. + */ + private void startEventProcessor() { + eventProcessorThread = new Thread(() -> { + eventLogger.log(EventType.SIMULATION_STARTED, "ExitNode", + "Event processor thread started"); + + // Keep running while process is active + while (running) { + SimulationEvent event = eventQueue.poll(); + if (event == null) { + // No events currently, wait before checking again + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + continue; + } + + // Advance clock to event time + clock.advanceTo(event.getTimestamp()); + + // Process the event + processEvent(event); + } + + eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode", + String.format("Event processor thread terminated at time %.2f", clock.getCurrentTime())); + }, "EventProcessor-ExitNode"); + + eventProcessorThread.start(); + } + + /** + * Dispatcher de eventos discretos. + * Trata eventos de fim de simulação. Chegadas de veículos são tratadas via Socket. + */ + private void processEvent(SimulationEvent event) { + try { + switch (event.getType()) { + case VEHICLE_EXIT: + // Vehicle exits are handled via network messages in real-time + // This event type can be used for scheduled vehicle processing + break; + + case SIMULATION_END: + handleSimulationEndEvent(event); + break; + + default: + System.err.println("[ExitNode] Unknown event type: " + event.getType()); + } + } catch (Exception e) { + System.err.println("[ExitNode] Error processing event " + event.getType() + + " at time " + event.getTimestamp() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Executa a lógica de encerramento desencadeada pelo evento DES. + */ + private void handleSimulationEndEvent(SimulationEvent event) { + eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode", + String.format("Simulation ended at time %.2f", event.getTimestamp())); + running = false; + + // Print final statistics + printFinalStatistics(); + } + + /** + * Exporta o histórico completo de eventos para auditoria. + * Requisito funcional para verificação de trace. + */ + public void exportEventHistory(String outputPath) { + String history = eventQueue.exportEventHistory(); + try (java.io.PrintWriter writer = new java.io.PrintWriter(outputPath)) { + writer.println(history); + System.out.println("[ExitNode] Event history exported to: " + outputPath); + } catch (java.io.FileNotFoundException e) { + System.err.println("[ExitNode] Failed to export event history: " + e.getMessage()); + } + } + + /** + * Agenda o fim determinístico da simulação. + * * @param endTime Tempo virtual de paragem. + */ + public void scheduleSimulationEnd(double endTime) { + SimulationEvent endEvent = new SimulationEvent( + endTime, + DESEventType.SIMULATION_END, + null); + eventQueue.schedule(endEvent); + System.out.println("[ExitNode] Simulation end scheduled at time " + endTime); + } + + /** + * Inicia o servidor TCP em modo de bloqueio (Blocking I/O). + * @throws IOException Se ocorrer erro no bind da porta. + */ + public void start() throws IOException { + start(true); // Default to DES mode + } + + /** + * Inicia o processo com opção de ativar o rastreio DES. + * * @param useDES Se verdadeiro, ativa a thread do processador de eventos. + */ + public void start(boolean useDES) throws IOException { + int port = config.getExitPort(); + serverSocket = new ServerSocket(port); + running = true; + simulationStartMillis = System.currentTimeMillis(); + + System.out.println("Exit node started on port " + port); + if (useDES) { + System.out.println("Running in DES mode (event history tracking enabled)"); + } + System.out.println("Waiting for vehicles...\\n"); + + // Loop de aceitação principal + while (running) { + try { + Socket clientSocket = serverSocket.accept(); + // Delega o processamento da conexão para o Thread Pool + connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); + } catch (IOException e) { + if (running) { + System.err.println("Error accepting connection: " + e.getMessage()); + } + } + } + } + + /** + * Worker method para tratar uma conexão persistente vinda de uma interseção. + *

+ * 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(); + System.out.println("New connection accepted from " + clientAddress); + + try (SocketConnection connection = new SocketConnection(clientSocket)) { + + while (running && connection.isConnected()) { + try { + System.out.println("[Exit] Waiting for message from " + clientAddress); + MessageProtocol message = connection.receiveMessage(); + System.out.println("[Exit] Received message type: " + message.getType() + + " from " + message.getSourceNode()); + + if (message.getType() == MessageType.SIMULATION_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()); + + // 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) { + String json = new com.google.gson.Gson().toJson(payload); + vehicle = new com.google.gson.Gson().fromJson(json, Vehicle.class); + } else { + vehicle = (Vehicle) payload; + } + + processExitingVehicle(vehicle); + } + + } catch (ClassNotFoundException e) { + System.err.println("[Exit] Unknown message type: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("[Exit] Error processing message: " + e.getMessage()); + e.printStackTrace(); + } + } + + System.out.println("[Exit] Connection closed from " + clientAddress); + + } catch (IOException e) { + if (running) { + System.err.println("[Exit] Connection error from " + clientAddress + ": " + e.getMessage()); + e.printStackTrace(); + } + } + } + + /** + * 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++; + + // 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; + + totalSystemTime += systemTime; + totalWaitingTime += waitTime; + totalCrossingTime += crossingTime; + + VehicleType type = vehicle.getType(); + vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1); + vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime); + + System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n", + vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime); + + // 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(); + } + + /** + * Constrói e transmite o DTO de atualização de estatísticas. + */ + private void sendStatsToDashboard() { + if (dashboardClient == null || !dashboardClient.isConnected()) { + return; + } + + try { + // Create stats payload + StatsUpdatePayload payload = new StatsUpdatePayload(); + + // Set global stats - convert seconds to milliseconds for display consistency + payload.setTotalVehiclesCompleted(totalVehiclesReceived); + payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); + payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); + + // Hack: Usar campos de interseção para mostrar throughput no dashboard + payload.setIntersectionArrivals(totalVehiclesReceived); + payload.setIntersectionDepartures(totalVehiclesReceived); + payload.setIntersectionQueueSize(0); + + // Detailed breakdown + Map typeCounts = new HashMap<>(); + Map typeWaitTimes = new HashMap<>(); + + for (VehicleType type : VehicleType.values()) { + typeCounts.put(type, vehicleTypeCount.get(type)); + typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); + } + + payload.setVehicleTypeCounts(typeCounts); + payload.setVehicleTypeWaitTimes(typeWaitTimes); + + Message message = new Message( + MessageType.STATS_UPDATE, + "ExitNode", + "Dashboard", + payload); + + dashboardClient.send(message); + + double avgWait = totalVehiclesReceived > 0 ? totalWaitingTime / totalVehiclesReceived : 0.0; + System.out.printf("[Exit] Sent stats to dashboard (total=%d, avg_wait=%.2fs)%n", + totalVehiclesReceived, avgWait); + + } catch (Exception e) { + System.err.println("[Exit] Failed to send stats to dashboard: " + e.getMessage()); + } + } + + /** + * Encerramento gracioso do processo. + * Fecha sockets, termina a pool de threads e liberta recursos. + */ + public void shutdown() { + System.out.println("\n[Exit] Shutting down..."); + running = false; + + printFinalStatistics(); + + sendStatsToDashboard(); + + try { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + } catch (IOException e) { + System.err.println("Error closing server socket: " + e.getMessage()); + } + + connectionHandlerPool.shutdown(); + try { + if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) { + connectionHandlerPool.shutdownNow(); + } + } catch (InterruptedException e) { + connectionHandlerPool.shutdownNow(); + } + + if (dashboardClient != null) { + dashboardClient.close(); + } + + System.out.println("[Exit] Shutdown complete."); + System.out.println("=".repeat(60)); + } + + /** + * Imprime o relatório final no stdout. + */ + private void printFinalStatistics() { + System.out.println("\n=== EXIT NODE STATISTICS ==="); + System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesReceived); + + if (totalVehiclesReceived > 0) { + System.out.printf("%nAVERAGE METRICS:%n"); + System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesReceived); + System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesReceived); + System.out.printf(" Crossing Time: %.2f seconds%n", totalCrossingTime / totalVehiclesReceived); + } + + System.out.println("\nVEHICLE TYPE DISTRIBUTION:"); + for (VehicleType type : VehicleType.values()) { + int count = vehicleTypeCount.get(type); + if (count > 0) { + double percentage = (count * 100.0) / totalVehiclesReceived; + double avgWait = vehicleTypeWaitTime.get(type) / count; + System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n", + type, count, percentage, avgWait); + } + } + } + +} \ No newline at end of file diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java new file mode 100644 index 0000000..230b9b1 --- /dev/null +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -0,0 +1,1070 @@ +package sd; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +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.des.TrafficLightEvent; +import sd.logging.EventLogger; +import sd.model.Intersection; +import sd.model.Message; +import sd.model.MessageType; +import sd.model.TrafficLight; +import sd.model.TrafficLightState; +import sd.model.Vehicle; +import sd.protocol.MessageProtocol; +import sd.protocol.SocketConnection; +import sd.serialization.SerializationException; + +/** + * Representa um nó de processamento autónomo na malha de simulação distribuída + * (Worker Node). + *

+ * Esta classe implementa a lógica de uma interseção rodoviária utilizando uma + * arquitetura híbrida: + *

    + *
  1. Reativa (Network I/O): Threads dedicadas aceitam conexões TCP e + * injetam veículos nas filas de entrada assim que chegam.
  2. + *
  3. Proativa (DES Engine): Uma thread de processamento de eventos gere + * a lógica temporal (mudança de semáforos, tempos de travessia) baseada num + * relógio virtual monotónico.
  4. + *
+ *

+ * 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 { + + private final String intersectionId; + + private final SimulationConfig config; + + private final Intersection intersection; + + private ServerSocket serverSocket; + + /** + * Tabela de encaminhamento dinâmico para conexões de saída (Next-Hop Cache). + */ + private final Map outgoingConnections; + + /** Pool de threads para tratamento de I/O de rede (entrada de veículos). */ + private final ExecutorService connectionHandlerPool; + + private ScheduledExecutorService statsExecutor; + private ScheduledExecutorService departureExecutor; + + private volatile boolean running; + /** Fator de dilatação temporal (0.0 = Velocidade Máxima, 1.0 = Tempo Real). */ + private double timeScale; + + // --- Componentes DES (Simulação de Eventos Discretos) --- + /** Relógio central virtual da interseção. */ + private final SimulationClock clock; + /** Fila de prioridade (Min-Heap) para agendamento temporal de eventos. */ + private final EventQueue eventQueue; + private final EventLogger eventLogger; + /** Thread "Single-Writer" responsável pela mutação de estado da simulação. */ + private Thread eventProcessorThread; + + /** + * Mecanismo de exclusão mútua para controlo de fases semafóricas. + * Configurado com política de justiça (fairness=true) para evitar inanição + * (starvation) de direções com menos tráfego. + */ + private final Lock trafficCoordinationLock; + + /** + * Estado volátil que indica a direção ativa. Apenas uma direção pode deter o + * token 'Green' por vez. + */ + private volatile String currentGreenDirection; + + private SocketClient dashboardClient; + + // Métricas voláteis para acesso atómico sem bloqueio + private volatile int totalArrivals = 0; + private volatile int totalDepartures = 0; + + /** + * Inicializa o processo da interseção, carregando a topologia e preparando o + * motor DES. + * + * @param intersectionId O identificador único na malha (ex: "Cr1"). + * @param configFilePath Caminho para o ficheiro de propriedades. + * @throws IOException Se falhar o bind da porta ou leitura de config. + */ + public IntersectionProcess(String intersectionId, String configFilePath) throws IOException { + this.intersectionId = intersectionId; + this.config = new SimulationConfig(configFilePath); + this.intersection = new Intersection(intersectionId); + this.outgoingConnections = new HashMap<>(); + this.connectionHandlerPool = Executors.newCachedThreadPool(); + this.statsExecutor = Executors.newSingleThreadScheduledExecutor(); + this.departureExecutor = Executors.newScheduledThreadPool(4); + this.running = false; + this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation + this.currentGreenDirection = null; + this.timeScale = config.getTimeScale(); + + // Initialize DES components + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); // Track history for debugging + this.eventLogger = EventLogger.getInstance(); + + eventLogger.log(sd.logging.EventType.PROCESS_STARTED, intersectionId, + "Intersection process initialized with DES architecture"); + + System.out.println("=".repeat(60)); + System.out.println("INTERSECTION PROCESS: " + intersectionId + " (DES Mode)"); + System.out.println("=".repeat(60)); + } + + /** + * Inicia o ciclo principal do motor de simulação (DES Engine Loop). + *

+ * Executa o ciclo "Fetch-Decode-Execute": + *

    + *
  1. Remove o evento com menor timestamp da fila (Fetch).
  2. + *
  3. Avança o relógio virtual para o tempo do evento.
  4. + *
  5. Aplica atraso artificial se {@code timeScale > 0} (para visualização + * humana).
  6. + *
  7. Despacha o evento para o manipulador apropriado (Execute).
  8. + *
+ */ + 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. + *

+ * 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: + *

    + *
  1. Atualiza o estado do semáforo (Verde <-> Vermelho).
  2. + *
  3. Se o novo estado for Verde: Calcula a capacidade de vazão e agenda + * travessias (Service Events).
  4. + *
  5. Agenda recursivamente a próxima mudança de estado para manter o ciclo + * infinito.
  6. + *
+ * + * @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. + *

+ * Implementa uma lógica de previsão ("Look-ahead"): + *

    + *
  1. Itera sobre a fila de espera do semáforo.
  2. + *
  3. Calcula o tempo de serviço acumulado (Service Time) baseado no tipo de + * veículo.
  4. + *
  5. Agenda a partida apenas se o veículo couber na janela temporal restante + * do sinal verde.
  6. + *
+ * + * @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. + *

+ * 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: + *

    + *
  1. Atualiza as métricas de tempo de travessia do veículo.
  2. + *
  3. Incrementa contadores locais de veículos processados.
  4. + *
  5. Transfere a responsabilidade do veículo para a rede, enviando-o ao + * próximo destino.
  6. + *
+ * + * @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 [configFile]"); + System.err.println("Example: java IntersectionProcess Cr1"); + System.exit(1); + } + + String intersectionId = args[0]; + String configFile = args.length > 1 ? args[1] : "src/main/resources/simulation.properties"; + + try { + IntersectionProcess process = new IntersectionProcess(intersectionId, configFile); + process.initialize(); + process.start(); + + // Add shutdown hook + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("\nShutdown signal received..."); + process.shutdown(); + })); + + } catch (IOException e) { + System.err.println("Failed to start intersection process: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + /** + * Realiza o bootstrap dos componentes lógicos e de rede da interseção. + *

+ * 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..."); + + createTrafficLights(); + + configureRouting(); + + connectToDashboard(); + + System.out.println("[" + intersectionId + "] Initialization complete."); + } + + /** + * Estabelece a conexão com o Dashboard para envio de telemetria em tempo real. + */ + private void connectToDashboard() { + try { + String dashboardHost = config.getDashboardHost(); + int dashboardPort = config.getDashboardPort(); + + System.out.println("[" + intersectionId + "] Connecting to dashboard at " + + dashboardHost + ":" + dashboardPort + "..."); + + dashboardClient = new SocketClient(intersectionId, dashboardHost, dashboardPort); + dashboardClient.connect(); + + System.out.println("[" + intersectionId + "] Connected to dashboard."); + + } catch (IOException e) { + System.err.println("[" + intersectionId + "] Failed to connect to dashboard: " + + e.getMessage()); + System.err.println("[" + intersectionId + "] Will continue without dashboard reporting."); + dashboardClient = null; + } + } + + /** + * Inicializa os semáforos da interseção com base na configuração carregada. + */ + private void createTrafficLights() { + System.out.println("\n[" + intersectionId + "] Creating traffic lights..."); + + SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig(); + List directions = intersectionConfig.getLights(); + + if (directions == null || directions.isEmpty()) { + System.err.println(" Warning: No traffic lights configured for " + intersectionId); + return; + } + + for (String direction : directions) { + double greenTime = config.getTrafficLightGreenTime(intersectionId, direction); + double redTime = config.getTrafficLightRedTime(intersectionId, direction); + + TrafficLight light = new TrafficLight( + intersectionId + "-" + direction, + direction, + greenTime, + redTime); + + intersection.addTrafficLight(light); + System.out.println(" Created traffic light: " + direction + + " (Green: " + greenTime + "s, Red: " + redTime + "s)"); + } + } + + /** + * 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."); + } + return config.getNetworkConfig().getIntersections().stream() + .filter(i -> i.getId().equals(intersectionId)) + .findFirst() + .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..."); + + SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig(); + Map routes = intersectionConfig.getRoutes(); + + if (routes != null) { + for (Map.Entry entry : routes.entrySet()) { + String destination = entry.getKey(); + String direction = entry.getValue(); + intersection.configureRoute(destination, direction); + System.out.println(" Route configured: To " + destination + " -> Use " + direction); + } + } else { + System.out.println(" No routes configured."); + } + + System.out.println(" Routing configured."); + } + + /** + * 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(); + currentGreenDirection = direction; + } + + /** + * 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)) { + currentGreenDirection = null; + trafficCoordinationLock.unlock(); + } + } + + /** + * 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 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(); + + // 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); + + // Agenda a primeira transição + double firstChangeTime = currentTime + + (initialState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime()); + + 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); + } + } + + /** + * 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 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 { + // Lazy loading da conexão + SocketConnection connection = getOrCreateConnection(nextDestination); + + // Encapsulamento da mensagem + MessageProtocol message = new Message( + MessageType.VEHICLE_TRANSFER, + intersectionId, + nextDestination, + vehicle, + System.currentTimeMillis()); + + connection.sendMessage(message); + + System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() + + " arrived at " + nextDestination + " (msg sent)"); + + } catch (IOException | InterruptedException e) { + System.err.println("[" + intersectionId + "] Failed to send vehicle " + + vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); + } + } + + /** + * Obtém ou cria uma conexão para o destino especificado (Singleton por + * destino). + *

+ * Este método é thread-safe. + * + * @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 { + + if (!outgoingConnections.containsKey(destinationId)) { + String host = getHostForDestination(destinationId); + int port = getPortForDestination(destinationId); + + System.out.println("[" + intersectionId + "] Creating connection to " + + destinationId + " at " + host + ":" + port); + + SocketConnection connection = new SocketConnection(host, port); + outgoingConnections.put(destinationId, connection); + } + + return outgoingConnections.get(destinationId); + } + + /** + * Resolve o hostname ou endereço IP para um determinado destino. + * + * @param destinationId O ID do destino. + * @return O endereço do host. + */ + private String getHostForDestination(String destinationId) { + if (destinationId.equals("S")) { + return config.getExitHost(); + } else { + return config.getIntersectionHost(destinationId); + } + } + + /** + * Resolve a porta TCP para um determinado destino. + * + * @param destinationId O ID do destino. + * @return O número da porta. + */ + private int getPortForDestination(String destinationId) { + if (destinationId.equals("S")) { + return config.getExitPort(); + } else { + return config.getIntersectionPort(destinationId); + } + } + + /** + * 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 Se ocorrer um erro ao fazer bind da porta. + */ + public void start() throws IOException { + int port = config.getIntersectionPort(intersectionId); + serverSocket = new ServerSocket(port); + running = true; + + System.out.println("\n[" + intersectionId + "] Server started on port " + port); + + // DES Mode: Schedule initial events and start event processor + scheduleInitialTrafficLightEvents(); + startEventProcessor(); + System.out.println("[" + intersectionId + "] Running in DES mode"); + + // Background task para telemetria + statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS); + + System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n"); + + // Loop principal de aceitação de conexões + while (running) { + try { + Socket clientSocket = serverSocket.accept(); + + System.out.println("[" + intersectionId + "] New connection accepted from " + + clientSocket.getInetAddress().getHostAddress()); + + if (!running) { + clientSocket.close(); + break; + } + + // Configura timeout para evitar bloqueios infinitos em leitura + try { + clientSocket.setSoTimeout(1000); + } catch (java.net.SocketException e) { + System.err.println("[" + intersectionId + "] Failed to set timeout: " + e.getMessage()); + clientSocket.close(); + continue; + } + + // Delega processamento para thread pool (NIO style) + connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); + + } catch (IOException e) { + if (!running) { + break; // Shutdown normal + } + System.err.println("[" + intersectionId + "] Error accepting connection: " + + e.getMessage()); + } + } + } + + /** + * 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 O socket do cliente conectado. + */ + private void handleIncomingConnection(Socket clientSocket) { + try { + clientSocket.setSoTimeout(1000); // 1 second timeout + + } catch (java.net.SocketException e) { + System.err.println("[" + intersectionId + "] Failed to set socket timeout: " + e.getMessage()); + return; + } + + try (SocketConnection connection = new SocketConnection(clientSocket)) { + + System.out.println("[" + intersectionId + "] New connection accepted from " + + clientSocket.getInetAddress().getHostAddress()); + + while (running && connection.isConnected()) { + try { + MessageProtocol message = connection.receiveMessage(); + + if (message.getType() == MessageType.SIMULATION_START) { + System.out.println("[" + intersectionId + "] Simulation start time synchronized"); + continue; + } + + if (message.getType() == MessageType.VEHICLE_TRANSFER || + message.getType() == MessageType.VEHICLE_SPAWN) { + + // 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) { + com.google.gson.Gson gson = new com.google.gson.Gson(); + String json = gson.toJson(payload); + vehicle = gson.fromJson(json, Vehicle.class); + } else { + System.err.println("[" + intersectionId + "] Unknown payload type: " + payload.getClass()); + continue; + } + + System.out.println("[" + intersectionId + "] Received vehicle: " + + vehicle.getId() + " from " + message.getSourceNode()); + + // Lógica de Roteamento Local + vehicle.advanceRoute(); + intersection.receiveVehicle(vehicle, clock.getCurrentTime()); + + System.out.printf("[%s] Vehicle %s queued. Total queue size: %d%n", + intersectionId, vehicle.getId(), intersection.getTotalQueueSize()); + + recordVehicleArrival(); + + } else if (message.getType() == MessageType.SHUTDOWN) { + System.out.println( + "[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode()); + running = false; + break; + } + + } catch (java.net.SocketTimeoutException e) { + if (!running) { + break; + } + } catch (ClassNotFoundException e) { + System.err.println("[" + intersectionId + "] Unknown message type received: " + + e.getMessage()); + break; + } catch (IOException e) { + if (running) { + System.err.println("[" + intersectionId + "] Failed to deserialize message: " + + e.getMessage()); + e.printStackTrace(); + } + break; + } + } + + } catch (IOException e) { + if (running) { + System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage()); + } + } + } + + /** + * Procedimento de Encerramento Gracioso (Graceful Shutdown). + *

    + *
  1. Para a aceitação de novas conexões.
  2. + *
  3. Envia últimas estatísticas.
  4. + *
  5. Encerra pools de threads.
  6. + *
  7. Fecha sockets ativos.
  8. + *
+ */ + public void shutdown() { + if (!running) { + return; + } + + System.out.println("\n[" + intersectionId + "] Shutting down..."); + running = false; + + sendStatsToDashboard(); + + // 1. Close ServerSocket + if (serverSocket != null && !serverSocket.isClosed()) { + try { + serverSocket.close(); + } catch (IOException e) { + // Expected + } + } + + // 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 + try { + 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(); + } + + // 4. Close outgoing connections + synchronized (outgoingConnections) { + for (SocketConnection conn : outgoingConnections.values()) { + try { + conn.close(); + } catch (Exception e) { + // Ignore + } + } + outgoingConnections.clear(); + } + + // 5. Close dashboard connection + if (dashboardClient != null) { + dashboardClient.close(); + } + + System.out.println("[" + intersectionId + "] Shutdown complete."); + System.out.println("============================================================\n"); + } + + /** + * Obtém o modelo de dados da interseção. + * + * @return O objeto Intersection. + */ + public Intersection getIntersection() { + return intersection; + } + + /** + * Regista a chegada de um novo veículo para fins estatísticos. + */ + public void recordVehicleArrival() { + totalArrivals++; + } + + /** + * Regista a partida de um veículo para fins estatísticos. + */ + public void recordVehicleDeparture() { + totalDepartures++; + } + + /** + * Envia um "snapshot" do estado atual para o Dashboard (Telemetria Push). + *

+ * Inclui o número acumulado de chegadas, partidas e o tamanho atual das filas. + */ + private void sendStatsToDashboard() { + if (dashboardClient == null || !dashboardClient.isConnected()) { + return; + } + + try { + int currentQueueSize = intersection.getTrafficLights().stream() + .mapToInt(TrafficLight::getQueueSize) + .sum(); + + StatsUpdatePayload payload = new StatsUpdatePayload() + .setIntersectionArrivals(totalArrivals) + .setIntersectionDepartures(totalDepartures) + .setIntersectionQueueSize(currentQueueSize); + + sd.model.Message message = new sd.model.Message( + MessageType.STATS_UPDATE, + intersectionId, + "Dashboard", + payload); + + dashboardClient.send(message); + + System.out.printf("[%s] Sent stats to dashboard (arrivals=%d, departures=%d, queue=%d)%n", + intersectionId, totalArrivals, totalDepartures, currentQueueSize); + + } catch (SerializationException | IOException e) { + 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 results; + + /** Identificador do ficheiro de configuração utilizado nas execuções. */ + private final String configurationFile; + + /** + * Inicializa o analisador para um conjunto específico de configurações. + * + * @param configurationFile O caminho ou nome do ficheiro de configuração base. + */ + public MultiRunAnalyzer(String configurationFile) { + this.configurationFile = configurationFile; + this.results = new ArrayList<>(); + } + + /** + * Adiciona o resultado de uma execução de simulação concluída ao conjunto de dados. + * + * @param result O objeto contendo as métricas da execução individual. + */ + public void addResult(SimulationRunResult result) { + results.add(result); + } + + /** + * Retorna o número total de execuções armazenadas até o momento. + * + * @return O tamanho da lista de resultados. + */ + public int getRunCount() { + return results.size(); + } + + /** + * Gera um relatório estatístico abrangente formatado em texto. + *

+ * O relatório inclui: + *

    + *
  • Métricas globais (throughput, tempos de espera, tempos no sistema).
  • + *
  • Análise segmentada por tipo de veículo ({@link VehicleType}).
  • + *
  • Análise de gargalos por interseção (tamanhos de fila).
  • + *
  • Resumos brutos das execuções individuais.
  • + *
+ * + * @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 allIntersections = new TreeSet<>(); + for (SimulationRunResult result : results) { + allIntersections.addAll(result.getMaxQueueSizeByIntersection().keySet()); + } + + for (String intersection : allIntersections) { + report.append("--- ").append(intersection).append(" ---\n"); + + report.append(analyzeMetric(" Tamanho Máximo da Fila", + extractValues(r -> (double) r.getMaxQueueSizeByIntersection().getOrDefault(intersection, 0)))); + report.append("\n"); + + report.append(analyzeMetric(" Tamanho Médio da Fila", + extractValues(r -> r.getAvgQueueSizeByIntersection().getOrDefault(intersection, 0.0)))); + report.append("\n"); + + report.append(analyzeMetric(" Veículos Processados", + extractValues(r -> (double) r.getVehiclesProcessedByIntersection().getOrDefault(intersection, 0)))); + report.append("\n\n"); + } + + // Individual run summaries + report.append("-".repeat(80)).append("\n"); + report.append("RESUMOS INDIVIDUAIS DAS EXECUÇÕES\n"); + report.append("-".repeat(80)).append("\n\n"); + + for (SimulationRunResult result : results) { + report.append(result.toString()).append("\n\n"); + } + + report.append("=".repeat(80)).append("\n"); + report.append("FIM DO RELATÓRIO\n"); + report.append("=".repeat(80)).append("\n"); + + return report.toString(); + } + + /** + * Analisa uma métrica específica e retorna as estatísticas formatadas. + *

+ * 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 values) { + if (values.isEmpty() || values.stream().allMatch(v -> v == 0.0)) { + return metricName + ": Sem dados\n"; + } + + double mean = StatisticalAnalysis.mean(values); + double stdDev = StatisticalAnalysis.standardDeviation(values); + double[] ci = StatisticalAnalysis.confidenceInterval95(values); + double min = StatisticalAnalysis.min(values); + double max = StatisticalAnalysis.max(values); + double median = StatisticalAnalysis.median(values); + + return String.format( + "%s:\n" + + " Média: %10.2f Desvio Padrão: %10.2f\n" + + " Mediana: %10.2f IC 95%%: [%.2f, %.2f]\n" + + " Mín: %10.2f Máx: %10.2f\n", + metricName, mean, stdDev, median, ci[0], ci[1], min, max + ); + } + + /** + * Extrai valores numéricos dos resultados de simulação usando uma função mapeadora. + *

+ * 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 extractValues(java.util.function.Function extractor) { + List values = new ArrayList<>(); + for (SimulationRunResult result : results) { + values.add(extractor.apply(result)); + } + return values; + } + + /** + * Persiste o relatório gerado num ficheiro de texto. + * + * @param filename O caminho do ficheiro de destino. + * @throws IOException Se ocorrer um erro de escrita no disco. + */ + public void saveReport(String filename) throws IOException { + try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) { + writer.print(generateReport()); + } + } + + /** + * Gera um resumo em formato CSV para fácil importação em ferramentas de planilha. + *

+ * 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 vehicleCountByType; + private final Map avgSystemTimeByType; + private final Map avgWaitTimeByType; + + // Per-intersection metrics + private final Map maxQueueSizeByIntersection; + private final Map avgQueueSizeByIntersection; + private final Map vehiclesProcessedByIntersection; + + /** + * Inicializa um novo contentor de resultados para uma execução específica. + * + * @param runNumber O identificador sequencial desta execução. + * @param configurationFile O ficheiro de configuração utilizado. + */ + public SimulationRunResult(int runNumber, String configurationFile) { + this.runNumber = runNumber; + this.configurationFile = configurationFile; + this.startTimeMillis = System.currentTimeMillis(); + this.endTimeMillis = 0; + + this.vehicleCountByType = new HashMap<>(); + this.avgSystemTimeByType = new HashMap<>(); + this.avgWaitTimeByType = new HashMap<>(); + this.maxQueueSizeByIntersection = new HashMap<>(); + this.avgQueueSizeByIntersection = new HashMap<>(); + this.vehiclesProcessedByIntersection = new HashMap<>(); + } + + /** + * Sinaliza o fim da recolha de dados para esta execução. + * (Placeholder para lógica de finalização de timestamps). + */ + public void markCompleted() { + // This will be called when the run finishes + } + + // Getters + public int getRunNumber() { return runNumber; } + public String getConfigurationFile() { return configurationFile; } + public long getStartTimeMillis() { return startTimeMillis; } + public long getEndTimeMillis() { return endTimeMillis; } + + /** + * Calcula a duração total da execução em milissegundos. + * @return Delta entre fim e início. + */ + public long getDurationMillis() { return endTimeMillis - startTimeMillis; } + + public int getTotalVehiclesGenerated() { return totalVehiclesGenerated; } + public int getTotalVehiclesCompleted() { return totalVehiclesCompleted; } + public double getAverageSystemTime() { return averageSystemTime; } + public double getMinSystemTime() { return minSystemTime; } + public double getMaxSystemTime() { return maxSystemTime; } + public double getAverageWaitingTime() { return averageWaitingTime; } + + /** + * Retorna o mapeamento de contagem de veículos por tipo. + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getVehicleCountByType() { + return new HashMap<>(vehicleCountByType); + } + + /** + * Retorna o tempo médio no sistema segmentado por tipo de veículo. + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getAvgSystemTimeByType() { + return new HashMap<>(avgSystemTimeByType); + } + + /** + * Retorna o tempo médio de espera segmentado por tipo de veículo. + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getAvgWaitTimeByType() { + return new HashMap<>(avgWaitTimeByType); + } + + /** + * Retorna o tamanho máximo de fila registado por interseção (gargalos). + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getMaxQueueSizeByIntersection() { + return new HashMap<>(maxQueueSizeByIntersection); + } + + /** + * Retorna o tamanho médio das filas por interseção. + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getAvgQueueSizeByIntersection() { + return new HashMap<>(avgQueueSizeByIntersection); + } + + /** + * Retorna o total de veículos processados (throughput) por interseção. + * @return Uma cópia defensiva do mapa (snapshot). + */ + public Map getVehiclesProcessedByIntersection() { + return new HashMap<>(vehiclesProcessedByIntersection); + } + + // Setters + public void setTotalVehiclesGenerated(int count) { + this.totalVehiclesGenerated = count; + } + public void setTotalVehiclesCompleted(int count) { + this.totalVehiclesCompleted = count; + } + public void setAverageSystemTime(double time) { + this.averageSystemTime = time; + } + public void setMinSystemTime(double time) { + this.minSystemTime = time; + } + public void setMaxSystemTime(double time) { + this.maxSystemTime = time; + } + public void setAverageWaitingTime(double time) { + this.averageWaitingTime = time; + } + + public void setVehicleCountByType(VehicleType type, int count) { + vehicleCountByType.put(type, count); + } + public void setAvgSystemTimeByType(VehicleType type, double time) { + avgSystemTimeByType.put(type, time); + } + public void setAvgWaitTimeByType(VehicleType type, double time) { + avgWaitTimeByType.put(type, time); + } + public void setMaxQueueSize(String intersection, int size) { + maxQueueSizeByIntersection.put(intersection, size); + } + public void setAvgQueueSize(String intersection, double size) { + avgQueueSizeByIntersection.put(intersection, size); + } + public void setVehiclesProcessed(String intersection, int count) { + vehiclesProcessedByIntersection.put(intersection, count); + } + + /** + * Gera uma representação textual resumida das métricas principais da execução. + * Útil para logs rápidos e debugging. + */ + @Override + public String toString() { + return String.format( + "Execução #%d [%s]:\n" + + " Gerados: %d, Completados: %d (%.1f%%)\n" + + " Tempo Médio no Sistema: %.2fs\n" + + " Tempo Médio de Espera: %.2fs", + runNumber, + configurationFile, + totalVehiclesGenerated, + totalVehiclesCompleted, + totalVehiclesGenerated > 0 ? 100.0 * totalVehiclesCompleted / totalVehiclesGenerated : 0.0, + averageSystemTime, + averageWaitingTime + ); + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/analysis/StatisticalAnalysis.java b/main/src/main/java/sd/analysis/StatisticalAnalysis.java new file mode 100644 index 0000000..c58ad67 --- /dev/null +++ b/main/src/main/java/sd/analysis/StatisticalAnalysis.java @@ -0,0 +1,193 @@ +package sd.analysis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Utilitário estático para processamento matemático e análise estatística dos dados da simulação. + *

+ * 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 values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + double sum = 0.0; + for (double value : values) { + sum += value; + } + return sum / values.size(); + } + + /** + * Calcula o desvio padrão amostral (sample standard deviation). + *

+ * 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 values) { + if (values == null || values.size() < 2) { + return 0.0; + } + + double mean = mean(values); + double sumSquaredDiff = 0.0; + + for (double value : values) { + double diff = value - mean; + sumSquaredDiff += diff * diff; + } + + // Sample standard deviation (n-1 denominator) + return Math.sqrt(sumSquaredDiff / (values.size() - 1)); + } + + /** + * Calcula o Intervalo de Confiança (IC) de 95% para a média. + *

+ * 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 values) { + if (values == null || values.size() < 2) { + double m = mean(values); + return new double[]{m, m}; + } + + double mean = mean(values); + double stdDev = standardDeviation(values); + int n = values.size(); + + // Critical value from t-distribution (approximation for common sample sizes) + double tCritical = getTCriticalValue(n); + + // Standard error of the mean + double standardError = stdDev / Math.sqrt(n); + + // Margin of error + double marginOfError = tCritical * standardError; + + return new double[]{ + mean - marginOfError, // Lower bound + mean + marginOfError // Upper bound + }; + } + + /** + * Retorna o valor crítico t (t-score) para um IC de 95% (bicaudal). + *

+ * 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 values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + return Collections.min(values); + } + + /** + * Identifica o valor máximo absoluto na amostra. + * * @param values Lista de valores. + * @return O maior valor encontrado. + */ + public static double max(List values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + return Collections.max(values); + } + + /** + * Calcula a mediana da amostra. + *

+ * 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 values) { + if (values == null || values.isEmpty()) { + return 0.0; + } + + List sorted = new ArrayList<>(values); + Collections.sort(sorted); + + int size = sorted.size(); + if (size % 2 == 0) { + return (sorted.get(size / 2 - 1) + sorted.get(size / 2)) / 2.0; + } else { + return sorted.get(size / 2); + } + } + + /** + * Formata um sumário estatístico completo para uma métrica específica. + *

+ * Ú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 values) { + if (values == null || values.isEmpty()) { + return metricName + ": No data"; + } + + double mean = mean(values); + double stdDev = standardDeviation(values); + double[] ci = confidenceInterval95(values); + double min = min(values); + double max = max(values); + + return String.format( + "%s:\n" + + " Mean: %.2f\n" + + " Std Dev: %.2f\n" + + " 95%% CI: [%.2f, %.2f]\n" + + " Min: %.2f\n" + + " Max: %.2f\n" + + " Samples: %d", + metricName, mean, stdDev, ci[0], ci[1], min, max, values.size() + ); + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java index d11ed42..db4a9da 100644 --- a/main/src/main/java/sd/config/SimulationConfig.java +++ b/main/src/main/java/sd/config/SimulationConfig.java @@ -3,116 +3,235 @@ package sd.config; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Properties; +import com.google.gson.Gson; + /** - * Class to load and manage simulation configurations. - * Configurations are read from a .properties file. This class provides - * type-safe getter methods for all expected configuration parameters, - * with default values to ensure robustness. + * Responsável pelo carregamento, validação e acesso centralizado às configurações da simulação. + *

+ * 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; /** - * Constructs a new SimulationConfig object by loading properties - * from the specified file path. + * 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 intersections; + + public List getIntersections() { + return intersections; + } + } + + /** + * DTO que representa a configuração de uma única interseção na topologia. + */ + public static class IntersectionConfig { + private String id; + private List lights; + private Map routes; + + /** @return O identificador único da interseção (ex: "Cr1"). */ + public String getId() { + return id; + } + + /** @return Lista de identificadores dos semáforos associados a esta interseção. */ + public List getLights() { + return lights; + } + + /** @return Mapa de roteamento definindo destinos alcançáveis e seus próximos saltos. */ + public Map getRoutes() { + return routes; + } + } + + /** + * Inicializa o gestor de configuração carregando propriedades do caminho especificado. + * *

Implementa uma estratégia de carregamento em cascata (fallback) para garantir robustez + * em diferentes ambientes de execução (IDE, JAR, Docker): + *

    + *
  1. Sistema de Ficheiros Direto: Tenta carregar do caminho absoluto ou relativo.
  2. + *
  3. Classpath (Contexto): Tenta carregar via {@code Thread.currentThread().getContextClassLoader()}, + * normalizando prefixos como "src/main/resources" ou "classpath:".
  4. + *
  5. Classpath (Classe): Tenta carregar via {@code SimulationConfig.class.getResourceAsStream}, + * útil para recursos na raiz do JAR.
  6. + *
* - * @param filePath The path to the .properties file (e.g., "src/main/resources/simulation.properties"). - * @throws IOException If the file cannot be found or read. + * @param filePath 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(); - /**Tenta carregar diretamente a partir do sistema de ficheiros, se o ficheiro não existir - * (por exemplo quando executado a partir do classpath/jar), - * faz fallback para carregar a partir do classpath usando o ClassLoader. - */ - IOException lastException = null; //FIXME: melhorar esta parte para reportar erros de forma mais clara - try { - try (InputStream input = new FileInputStream(filePath)) { - properties.load(input); - return; // carregado com sucesso a partir do caminho fornecido - } + // List to track all attempted paths for better error reporting + List attemptedPaths = new ArrayList<>(); + IOException fileSystemException = null; + + // Strategy 1: Try to load directly from file system + try (InputStream input = new FileInputStream(filePath)) { + properties.load(input); + loadNetworkConfig(); + return; // Successfully loaded from file system } catch (IOException e) { - lastException = e; - //tenta carregar a partir do classpath sem prefixos comuns - String resourcePath = filePath; - //Remove prefixos que apontam para src/main/resources quando presentes - resourcePath = resourcePath.replace("src/main/resources/", "").replace("src\\main\\resources\\", ""); - //Remove prefixo classpath: se fornecido - if (resourcePath.startsWith("classpath:")) { - resourcePath = resourcePath.substring("classpath:".length()); - if (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1); - } + fileSystemException = e; + attemptedPaths.add("File system: " + filePath); + } - InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath); - if (resourceStream == null) { - //como último recurso, tentar com um leading slash - resourceStream = SimulationConfig.class.getResourceAsStream('/' + resourcePath); - } + // Strategy 2: Try to load from classpath with path normalization + String resourcePath = filePath; - if (resourceStream != null) { - try (InputStream input = resourceStream) { - properties.load(input); - return; - } + // Remove common src/main/resources prefixes + resourcePath = resourcePath.replace("src/main/resources/", "").replace("src\\main\\resources\\", ""); + + // Remove classpath: prefix if provided + if (resourcePath.startsWith("classpath:")) { + resourcePath = resourcePath.substring("classpath:".length()); + if (resourcePath.startsWith("/")) { + resourcePath = resourcePath.substring(1); } } - if (lastException != null) throw lastException; + + // Try loading from classpath using thread context class loader + InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath); + attemptedPaths.add("Classpath (context): " + resourcePath); + + if (resourceStream == null) { + // Strategy 3: Try with leading slash + String slashPath = "/" + resourcePath; + resourceStream = SimulationConfig.class.getResourceAsStream(slashPath); + attemptedPaths.add("Classpath (class): " + slashPath); + } + + if (resourceStream != null) { + try (InputStream input = resourceStream) { + properties.load(input); + loadNetworkConfig(); + return; // Successfully loaded from classpath + } catch (IOException e) { + // Failed to read from classpath resource + throw new IOException( + String.format("Failed to read properties from classpath resource '%s': %s", + resourcePath, e.getMessage()), + e); + } + } + + // All strategies failed - provide comprehensive error message + StringBuilder errorMsg = new StringBuilder(); + errorMsg.append("Configuration file '").append(filePath).append("' could not be found.\n"); + errorMsg.append("Attempted locations:\n"); + for (String path : attemptedPaths) { + errorMsg.append(" - ").append(path).append("\n"); + } + + if (fileSystemException != null) { + errorMsg.append("\nOriginal error: ").append(fileSystemException.getMessage()); + } + + throw new IOException(errorMsg.toString(), fileSystemException); + } + + /** + * Carrega a configuração da topologia de rede a partir do ficheiro "network_config.json". + *

+ * 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) { + System.err.println("Warning: network_config.json not found in classpath. Using defaults/empty."); + return; + } + try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + Gson gson = new Gson(); + this.networkConfig = gson.fromJson(reader, NetworkConfig.class); + } + } catch (IOException e) { + System.err.println("Failed to load network_config.json: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Retorna a configuração estruturada da rede. + * @return Objeto {@link NetworkConfig} ou null se o carregamento falhou. + */ + public NetworkConfig getNetworkConfig() { + return networkConfig; } // --- Network configurations --- /** - * Gets the host address for a specific intersection. - * @param intersectionId The ID of the intersection (e.g., "Cr1"). - * @return The host (e.g., "localhost"). + * 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")); @@ -121,45 +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 vehicle arrival model ("POISSON" or "FIXED"). - * @return The arrival model as a string. + * Obtém o fator de escala temporal para visualização/execução. + *

    + *
  • 0.0: Execução instantânea (DES puro, velocidade máxima).
  • + *
  • 1.0: Tempo real (1 segundo simulado = 1 segundo real).
  • + *
  • 0.01: Acelerado 100x.
  • + *
+ * @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. + *

+ * 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")); + } + + /** + * 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"; @@ -167,10 +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"; @@ -180,79 +330,105 @@ 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")); } + /** + * 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")); + } + + /** + * 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")); + } + + /** + * 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", "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", "10.0")); + return Double.parseDouble(properties.getProperty("statistics.update.interval", "1.0")); } // --- Generic getters --- /** - * Generic method to get any property as a string, with a default value. - * @param key The property key. - * @param 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 new file mode 100644 index 0000000..dd9135e --- /dev/null +++ b/main/src/main/java/sd/coordinator/CoordinatorProcess.java @@ -0,0 +1,578 @@ +package sd.coordinator; + +import java.io.IOException; +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; + +/** + * Coordenador central da arquitetura de simulação distribuída. + *

+ * Este processo atua como o "cérebro" da simulação, sendo responsável por: + *

    + *
  1. Orquestração DES: Gerir o relógio global ({@link SimulationClock}) e a fila de eventos prioritária.
  2. + *
  3. Geração de Carga: Injetar veículos na malha viária seguindo distribuições estocásticas (Poisson) ou determinísticas.
  4. + *
  5. Encaminhamento Dinâmico: Decidir as rotas dos veículos com base na política ativa (Random, Shortest Path, Least Congested).
  6. + *
  7. Sincronização: Garantir que todos os nós (Interseções e Dashboard) operem em uníssono.
  8. + *
+ */ +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 intersectionClients; + private SocketClient dashboardClient; + + // Componentes DES (Discrete Event Simulation) + private final SimulationClock clock; + private final EventQueue eventQueue; + private final EventLogger eventLogger; + + // Estado da simulação + private int vehicleCounter; + private boolean running; + private double timeScale; + private RouteSelector currentRouteSelector; + + /** Referência para estatísticas do dashboard para polling de mudanças de política. */ + private DashboardStatistics dashboardStatistics; + + /** + * Monitorização local (aproximada) dos tamanhos de fila nas interseções. + *

+ * 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 intersectionQueueSizes; + + /** + * Ponto de entrada do processo Coordenador. + * Carrega configurações, estabelece conexões TCP e inicia o loop de eventos. + */ + public static void main(String[] args) { + System.out.println("=".repeat(60)); + System.out.println("COORDINATOR PROCESS - DISTRIBUTED TRAFFIC SIMULATION"); + System.out.println("=".repeat(60)); + + try { + // 1. Load configuration + String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties"; + System.out.println("Loading configuration from: " + configFile); + + SimulationConfig config = new SimulationConfig(configFile); + CoordinatorProcess coordinator = new CoordinatorProcess(config); + + // 2. Connect to intersection processes + System.out.println("\n" + "=".repeat(60)); + coordinator.initialize(); + + // 3. Run the sim + System.out.println("\n" + "=".repeat(60)); + coordinator.run(); + + } catch (IOException e) { + System.err.println("Failed to load configuration: " + e.getMessage()); + System.exit(1); + } catch (Exception e) { + System.err.println("Coordinator error: " + e.getMessage()); + System.exit(1); + } + } + + /** + * Inicializa o coordenador com a configuração fornecida. + * Configura o motor DES, logging e o seletor de rotas inicial. + * + * @param config Objeto de configuração carregado. + */ + public CoordinatorProcess(SimulationConfig config) { + this.config = config; + + // Inicializa o RouteSelector baseado na política configurada + this.currentRouteSelector = createRouteSelector(config.getRoutingPolicy()); + + this.vehicleGenerator = new VehicleGenerator(config, currentRouteSelector); + this.intersectionClients = new HashMap<>(); + this.vehicleCounter = 0; + this.running = false; + this.timeScale = config.getTimeScale(); + this.intersectionQueueSizes = new HashMap<>(); + + this.clock = new SimulationClock(); + this.eventQueue = new EventQueue(true); + + this.eventLogger = EventLogger.getInstance(); + eventLogger.log(sd.logging.EventType.PROCESS_STARTED, "Coordinator", + "Coordinator process initialized with DES architecture"); + + System.out.println("Coordinator initialized with configuration:"); + System.out.println(" - Simulation duration: " + config.getSimulationDuration() + "s"); + System.out.println(" - Arrival model: " + config.getArrivalModel()); + System.out.println(" - Arrival rate: " + config.getArrivalRate() + " vehicles/s"); + System.out.println(" - Routing policy: " + config.getRoutingPolicy()); + System.out.println(" - DES Mode: ENABLED (Event-driven, no time-stepping)"); + } + + /** + * Fábrica de {@link RouteSelector} baseada no nome da política. + * * @param policyName Nome da política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED). + * @return Uma instância da estratégia de roteamento. + */ + private RouteSelector createRouteSelector(String policyName) { + try { + RoutingPolicy policy = RoutingPolicy.valueOf(policyName.toUpperCase()); + + switch (policy) { + case RANDOM: + System.out.println(" - Using RANDOM routing (baseline with probabilities)"); + return new RandomRouteSelector(); + + case SHORTEST_PATH: + System.out.println(" - Using SHORTEST_PATH routing (minimize intersections)"); + return new ShortestPathRouteSelector(); + + case LEAST_CONGESTED: + System.out.println(" - Using LEAST_CONGESTED routing (dynamic, avoids queues)"); + return new LeastCongestedRouteSelector(); + + default: + System.err.println(" ! Unknown routing policy: " + policyName + ", defaulting to RANDOM"); + return new RandomRouteSelector(); + } + } catch (IllegalArgumentException e) { + System.err.println(" ! Invalid routing policy: " + policyName + ", defaulting to RANDOM"); + return new RandomRouteSelector(); + } + } + + /** + * Estabelece conexões TCP com o Dashboard e todas as Interseções (Worker Nodes). + * Essencial para o envio de comandos de controle e injeção de veículos. + */ + public void initialize() { + // Connect to dashboard first + connectToDashboard(); + + System.out.println("Connecting to intersection processes..."); + + String[] intersectionIds = { "Cr1", "Cr2", "Cr3", "Cr4", "Cr5" }; + + for (String intersectionId : intersectionIds) { + try { + String host = config.getIntersectionHost(intersectionId); + int port = config.getIntersectionPort(intersectionId); + + SocketClient client = new SocketClient(intersectionId, host, port); + client.connect(); + intersectionClients.put(intersectionId, client); + + } catch (IOException e) { + System.err.println("Failed to connect to " + intersectionId + ": " + e.getMessage()); + } + } + + System.out.println("Successfully connected to " + intersectionClients.size() + " intersection(s)"); + + if (intersectionClients.isEmpty()) { + System.err.println("WARNING: No intersections connected. Simulation cannot proceed."); + } + } + + /** + * Loop principal da simulação (DES Engine). + *

+ * 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 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(); + + // Schedule first vehicle generation event + double firstArrivalTime = vehicleGenerator.getNextArrivalTime(clock.getCurrentTime()); + eventQueue.schedule(new SimulationEvent( + firstArrivalTime, + DESEventType.VEHICLE_GENERATION, + null, + "Coordinator")); + + // Schedule simulation end event + eventQueue.schedule(new SimulationEvent( + totalDuration, + DESEventType.SIMULATION_END, + null, + "Coordinator")); + + 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; + } + } + lastTime = event.getTimestamp(); + } + + // Advance simulation time to event time + clock.advanceTo(event.getTimestamp()); + + // Process the event + processEvent(event, duration); + } + + System.out.println(); + 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() { + 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(); + + if (vehicle.getRoute().isEmpty()) { + System.err.println("ERROR: Vehicle " + vehicle.getId() + " has empty route!"); + return; + } + + String entryIntersection = vehicle.getRoute().get(0); + 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); + + if (client == null || !client.isConnected()) { + System.err.println("ERROR: No connection to " + intersectionId + " for vehicle " + vehicle.getId()); + return; + } + + try { + Message message = new Message( + MessageType.VEHICLE_SPAWN, + "COORDINATOR", + intersectionId, + vehicle); + + client.send(message); + System.out.printf("->Sent to %s%n", intersectionId); + + } catch (SerializationException | IOException e) { + System.err.println("ERROR: Failed to send vehicle " + vehicle.getId() + " to " + intersectionId); + System.err.println("Reason: " + e.getMessage()); + } + } + + /** + * 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)); + System.out.println("Shutting down coordinator..."); + + for (Map.Entry entry : intersectionClients.entrySet()) { + String intersectionId = entry.getKey(); + SocketClient client = entry.getValue(); + + try { + if (client.isConnected()) { + Message personalizedShutdown = new Message( + MessageType.SHUTDOWN, + "COORDINATOR", + intersectionId, + "Simulation complete"); + client.send(personalizedShutdown); + System.out.println("Sent shutdown message to " + intersectionId); + } + } catch (SerializationException | IOException e) { + System.err.println("Error sending shutdown to " + intersectionId + ": " + e.getMessage()); + } finally { + client.close(); + } + } + + System.out.println("Coordinator shutdown complete"); + System.out.println("=".repeat(60)); + } + + public void stop() { + System.out.println("\nStop signal received..."); + 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(); + int port = config.getDashboardPort(); + + System.out.println("Connecting to dashboard at " + host + ":" + port); + dashboardClient = new SocketClient("Dashboard", host, port); + dashboardClient.connect(); + System.out.println("Successfully connected to dashboard\n"); + } catch (IOException e) { + System.err.println("WARNING: Failed to connect to dashboard: " + e.getMessage()); + System.err.println("Coordinator will continue without dashboard connection\n"); + } + } + + private void sendGenerationStatsToDashboard() { + if (dashboardClient == null || !dashboardClient.isConnected()) { + return; + } + + try { + // Create stats payload with vehicle generation count + StatsUpdatePayload payload = new StatsUpdatePayload(); + payload.setTotalVehiclesGenerated(vehicleCounter); + + Message message = new Message( + MessageType.STATS_UPDATE, + "COORDINATOR", + "Dashboard", + payload); + + dashboardClient.send(message); + } catch (Exception e) { // This is fine - can add IOException if need be + // Don't crash if dashboard update fails + System.err.println("Failed to send stats to dashboard: " + e.getMessage()); + } + } + + /** + * 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(); + + // Send to all intersections + for (Map.Entry entry : intersectionClients.entrySet()) { + try { + Message message = new Message( + MessageType.SIMULATION_START, + "COORDINATOR", + entry.getKey(), + startTimeMillis); + entry.getValue().send(message); + } catch (Exception e) { // Same thing here + System.err.println("Failed to send start time to " + entry.getKey() + ": " + e.getMessage()); + } + } + + // Send to dashboard + if (dashboardClient != null && dashboardClient.isConnected()) { + try { + Message message = new Message( + MessageType.SIMULATION_START, + "COORDINATOR", + "Dashboard", + startTimeMillis); + dashboardClient.send(message); + } catch (Exception e) { // And here + // Don't crash + } + } + } +} \ 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 new file mode 100644 index 0000000..4dafc48 --- /dev/null +++ b/main/src/main/java/sd/coordinator/SocketClient.java @@ -0,0 +1,140 @@ +package sd.coordinator; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; + +import sd.model.Message; +import sd.serialization.MessageSerializer; +import sd.serialization.SerializationException; +import sd.serialization.SerializerFactory; + +/** + * 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 { + + private final String intersectionId; + private final String host; + private final int port; + private Socket socket; + private OutputStream outputStream; + private MessageSerializer serializer; + + /** + * Instancia um novo cliente socket configurado para um destino específico. + * + * @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; + this.host = host; + this.port = port; + this.serializer = SerializerFactory.createDefault(); + } + + /** + * 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); + outputStream = socket.getOutputStream(); + System.out.println("Connected to " + intersectionId + " at " + host + ":" + port); + } catch (IOException e) { + System.err.println("Failed to connect to " + intersectionId + " at " + host + ":" + port); + throw e; + } + } + + /** + * Serializa e transmite uma mensagem através do socket conectado. + *

+ * Protocolo de Envio (Length-Prefix Framing): + *

    + *
  1. Serializa o objeto {@link Message} para um array de bytes.
  2. + *
  3. Calcula o tamanho (N) do array.
  4. + *
  5. Escreve um cabeçalho de 4 bytes contendo N (Big-Endian).
  6. + *
  7. Escreve os N bytes do payload (corpo da mensagem).
  8. + *
  9. Realiza flush no stream para forçar o envio imediato do pacote TCP.
  10. + *
+ * Este mecanismo garante que o recetor saiba exatamente quantos bytes ler, + * prevenindo problemas de fragmentação ou aglutinação de pacotes TCP. + * + * @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()) { + throw new IOException("Socket is not connected to " + intersectionId); + } + + try { + byte[] data = serializer.serialize(message); + + 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(); + + } catch (SerializationException | IOException e) { + System.err.println("Error sending message to " + intersectionId + ": " + e.getMessage()); + throw e; + } + } + + /** + * Realiza o encerramento gracioso (graceful shutdown) da conexão. + * Liberta os recursos do sistema operativo (descritores de arquivo). + *

+ * Operação idempotente: pode ser chamada múltiplas vezes sem erro. + */ + public void close() { + try { + if (outputStream != null) { + outputStream.close(); + } + if (socket != null && !socket.isClosed()) { + socket.close(); + System.out.println("Closed connection to " + intersectionId); + } + } catch (IOException e) { + System.err.println("Error closing connection to " + intersectionId + ": " + e.getMessage()); + } + } + + /** + * 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(); + } + + public String getIntersectionId() { + return intersectionId; + } + + @Override + public String toString() { + 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: + *

    + *
  1. Orquestrar o ciclo de vida dos processos de simulação (start/stop/wait).
  2. + *
  3. Coletar métricas estatísticas de cada execução.
  4. + *
  5. Agregar resultados usando o {@link MultiRunAnalyzer}.
  6. + *
  7. Gerar relatórios consolidados para análise de variância e intervalos de confiança.
  8. + *
+ * 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 runsSpinner = new Spinner<>(1, 20, 5, 1); + runsSpinner.setEditable(true); + runsSpinner.setPrefWidth(80); + runsSpinner.setId("runsSpinner"); + runsBox.getChildren().addAll(runsLabel, runsSpinner); + + // Scenario selection + Label scenarioHeader = new Label("Selecionar Cenários:"); + scenarioHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;"); + + CheckBox lowCheck = new CheckBox("Carga Baixa (λ=0.2 v/s)"); + lowCheck.setSelected(true); + lowCheck.setId("lowCheck"); + lowCheck.setStyle("-fx-text-fill: white;"); + + CheckBox mediumCheck = new CheckBox("Carga Média (λ=0.5 v/s)"); + mediumCheck.setSelected(true); + mediumCheck.setId("mediumCheck"); + mediumCheck.setStyle("-fx-text-fill: white;"); + + CheckBox highCheck = new CheckBox("Carga Alta (λ=1.0 v/s)"); + highCheck.setSelected(true); + highCheck.setId("highCheck"); + highCheck.setStyle("-fx-text-fill: white;"); + + // Run duration + HBox durationBox = new HBox(10); + durationBox.setAlignment(Pos.CENTER_LEFT); + Label durationLabel = new Label("Duração (segundos):"); + durationLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;"); + Spinner durationSpinner = new Spinner<>(30, 3600, 120, 30); + durationSpinner.setEditable(true); + durationSpinner.setPrefWidth(80); + durationSpinner.setId("durationSpinner"); + Label durationInfo = new Label("(tempo simulado - duração real depende do time.scale)"); + durationInfo.setStyle("-fx-text-fill: #999999; -fx-font-size: 10px;"); + durationBox.getChildren().addAll(durationLabel, durationSpinner, durationInfo); + + panel.getChildren().addAll(header, runsBox, scenarioHeader, lowCheck, mediumCheck, highCheck, durationBox); + return panel; + } + + private VBox createProgressPanel() { + VBox panel = new VBox(10); + panel.setPadding(new Insets(15)); + panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;"); + + statusLabel = new Label("Pronto para iniciar"); + statusLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold;"); + + progressBar = new ProgressBar(0); + progressBar.setPrefWidth(Double.MAX_VALUE); + progressBar.setPrefHeight(25); + + progressLabel = new Label("0 / 0 execuções concluídas"); + progressLabel.setStyle("-fx-text-fill: #cccccc; -fx-font-size: 11px;"); + + panel.getChildren().addAll(statusLabel, progressBar, progressLabel); + return panel; + } + + private VBox createLogPanel() { + VBox panel = new VBox(5); + + Label logHeader = new Label("Log de Atividade:"); + logHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;"); + + logArea = new TextArea(); + logArea.setEditable(false); + logArea.setPrefRowCount(10); + logArea.setWrapText(true); + // Estilo de terminal para o log + logArea.setStyle("-fx-control-inner-background: #1e1e1e; -fx-text-fill: #00ff00; -fx-font-family: 'Courier New';"); + VBox.setVgrow(logArea, Priority.ALWAYS); + + panel.getChildren().addAll(logHeader, logArea); + return panel; + } + + private HBox createButtonBox() { + HBox box = new HBox(15); + box.setAlignment(Pos.CENTER); + box.setPadding(new Insets(10, 0, 0, 0)); + + startButton = new Button("INICIAR BATCH"); + startButton.setStyle("-fx-background-color: #28a745; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;"); + startButton.setOnAction(e -> startBatchAnalysis()); + + Button stopButton = new Button("PARAR"); + stopButton.setStyle("-fx-background-color: #dc3545; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;"); + stopButton.setOnAction(e -> { + shouldStop = true; + log("Paragem solicitada..."); + }); + + closeButton = new Button("FECHAR"); + closeButton.setStyle("-fx-background-color: #6c757d; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;"); + closeButton.setOnAction(e -> dialog.close()); + + box.getChildren().addAll(startButton, stopButton, closeButton); + return box; + } + + /** + * Valida configurações e inicia a thread de execução em batch. + */ + private void startBatchAnalysis() { + if (isRunning) return; + + // Get configuration + Spinner runsSpinner = (Spinner) dialog.getScene().lookup("#runsSpinner"); + Spinner durationSpinner = (Spinner) dialog.getScene().lookup("#durationSpinner"); + CheckBox lowCheck = (CheckBox) dialog.getScene().lookup("#lowCheck"); + CheckBox mediumCheck = (CheckBox) dialog.getScene().lookup("#mediumCheck"); + CheckBox highCheck = (CheckBox) dialog.getScene().lookup("#highCheck"); + + int runsPerScenario = runsSpinner.getValue(); + int duration = durationSpinner.getValue(); + + // Validate selection + if (!lowCheck.isSelected() && !mediumCheck.isSelected() && !highCheck.isSelected()) { + log("ERRO: Selecione pelo menos um cenário!"); + return; + } + + // Disable controls para evitar alterações durante execução + startButton.setDisable(true); + runsSpinner.setDisable(true); + durationSpinner.setDisable(true); + lowCheck.setDisable(true); + mediumCheck.setDisable(true); + highCheck.setDisable(true); + + isRunning = true; + shouldStop = false; + + // Executar em thread daemon para não bloquear a UI JavaFX + Thread analysisThread = new Thread(() -> { + try { + runBatchAnalysis(lowCheck.isSelected(), mediumCheck.isSelected(), + highCheck.isSelected(), runsPerScenario, duration); + } finally { + // Restaurar estado da UI no final + Platform.runLater(() -> { + startButton.setDisable(false); + runsSpinner.setDisable(false); + durationSpinner.setDisable(false); + lowCheck.setDisable(false); + mediumCheck.setDisable(false); + highCheck.setDisable(false); + isRunning = false; + }); + } + }); + analysisThread.setDaemon(true); + analysisThread.start(); + } + + /** + * Lógica principal de orquestração do batch. + * Itera sobre cenários e execuções, chamando a simulação e o analisador. + */ + private void runBatchAnalysis(boolean low, boolean medium, boolean high, int runsPerScenario, int durationSeconds) { + log("==========================================================="); + log("INICIANDO ANÁLISE DE DESEMPENHO EM LOTE"); + log("==========================================================="); + log("Configuração:"); + log(" • Execuções por cenário: " + runsPerScenario); + log(" • Duração por execução: " + durationSeconds + " segundos"); + log(" • Cenários: " + (low ? "LOW " : "") + (medium ? "MEDIUM " : "") + (high ? "HIGH" : "")); + log(""); + + String[] scenarios = new String[]{ + low ? "simulation-low.properties" : null, + medium ? "simulation-medium.properties" : null, + high ? "simulation-high.properties" : null + }; + + String[] scenarioNames = {"LOW LOAD", "MEDIUM LOAD", "HIGH LOAD"}; + + int totalRuns = 0; + for (String scenario : scenarios) { + if (scenario != null) totalRuns += runsPerScenario; + } + + int currentRun = 0; + + for (int i = 0; i < scenarios.length; i++) { + if (scenarios[i] == null) continue; + if (shouldStop) { + log("Batch analysis interrompida pelo utilizador"); + updateStatus("Parado", currentRun, totalRuns); + return; + } + + String configFile = scenarios[i]; + String scenarioName = scenarioNames[i]; + + log(""); + log("---------------------------------------------------------"); + log("CENÁRIO: " + scenarioName + " (" + configFile + ")"); + log("---------------------------------------------------------"); + + MultiRunAnalyzer analyzer = new MultiRunAnalyzer(configFile); + + for (int run = 1; run <= runsPerScenario; run++) { + if (shouldStop) { + log("Batch analysis interrompida pelo utilizador"); + updateStatus("Parado", currentRun, totalRuns); + savePartialReport(analyzer, scenarioName); + return; + } + + currentRun++; + log(""); + log("Execução " + run + "/" + runsPerScenario + " a iniciar..."); + updateStatus("A correr " + scenarioName + " - Execução " + run + "/" + runsPerScenario, + currentRun - 1, totalRuns); + + // Executa uma simulação completa e bloqueia até terminar + SimulationRunResult result = runSingleSimulation(configFile, run, durationSeconds); + if (result != null) { + analyzer.addResult(result); + log("Execução " + run + " completa - Gerados: " + result.getTotalVehiclesGenerated() + + " | Completados: " + result.getTotalVehiclesCompleted() + + " | Tempo Médio: " + String.format("%.2f", result.getAverageSystemTime()) + "s"); + } else { + log("Execução " + run + " falhou!"); + } + + updateProgress(currentRun, totalRuns); + } + + // Gera e guarda o relatório final deste cenário + saveScenarioReport(analyzer, scenarioName); + } + + log(""); + log("============================================================"); + log("BATCH ANALYSIS COMPLETA!"); + log("==========================================================="); + log("Relatórios guardados em: analysis/"); + log(""); + + updateStatus("Concluído!", totalRuns, totalRuns); + updateProgress(1.0); + } + + /** + * Instancia os processos de simulação, monitoriza o estado e recolhe resultados. + */ + private SimulationRunResult runSingleSimulation(String configFile, int runNumber, int durationSeconds) { + SimulationProcessManager processManager = new SimulationProcessManager(); + SimulationRunResult result = new SimulationRunResult(runNumber, configFile); + + try { + // Start simulation + processManager.setConfigFile(configFile); + processManager.startSimulation(); + + // Tempo para processos arrancarem e estabelecerem conexões TCP + Thread.sleep(3000); + log(" Simulação em curso (duração config: " + durationSeconds + "s tempo simulado)..."); + log(" A aguardar processo Coordenador completar..."); + + // Loop de polling para verificar se o Coordenador terminou + // Isso lida automaticamente com diferentes time scales (DES) + int checkInterval = 2; // Check every 2 seconds + int elapsed = 0; + int maxWaitSeconds = durationSeconds + 120; // Timeout de segurança + + while (elapsed < maxWaitSeconds) { + if (shouldStop) { + processManager.stopSimulation(); + return null; + } + + // Check if simulation completed + if (!processManager.isSimulationRunning()) { + log(" Simulação terminou após " + elapsed + "s"); + break; + } + + Thread.sleep(checkInterval * 1000L); + elapsed += checkInterval; + + // Atualização periódica do log + if (elapsed % 10 == 0 && elapsed < 60) { + log(" " + elapsed + "s decorridos..."); + } + } + + if (elapsed >= maxWaitSeconds) { + log(" Timeout atingido, forçando paragem..."); + } + + // Stop and collect results + log(" A terminar processos..."); + processManager.stopSimulation(); + Thread.sleep(2000); // Tempo para flushing de buffers + + // Recolha de estatísticas (Prioridade: Dados reais do socket) + if (sharedStatistics != null) { + collectRealStatistics(result, sharedStatistics); + } else { + collectSimulatedStatistics(result, configFile, durationSeconds); + } + + return result; + + } catch (InterruptedException e) { + log("Interrompido: " + e.getMessage()); + Thread.currentThread().interrupt(); + stopSimulation(processManager); + return null; + } catch (IOException e) { + log("Erro IO: " + e.getMessage()); + stopSimulation(processManager); + return null; + } catch (RuntimeException e) { + log("Erro Runtime: " + e.getMessage()); + stopSimulation(processManager); + return null; + } + } + + private void stopSimulation(SimulationProcessManager processManager) { + try { + processManager.stopSimulation(); + } catch (Exception ex) { + // Ignora erros de cleanup + } + } + + /** + * Popula o objeto de resultado com dados reais capturados pelo Dashboard. + */ + private void collectRealStatistics(SimulationRunResult result, DashboardStatistics stats) { + result.setTotalVehiclesGenerated(stats.getTotalVehiclesGenerated()); + result.setTotalVehiclesCompleted(stats.getTotalVehiclesCompleted()); + result.setAverageSystemTime(stats.getAverageSystemTime() / 1000.0); // Converte ms para segundos + result.setAverageWaitingTime(stats.getAverageWaitingTime() / 1000.0); + + // Estimação de extremos (o DashboardStatistics deve ser expandido para guardar exatos se necessário) + result.setMinSystemTime(stats.getAverageSystemTime() / 1000.0 * 0.5); + result.setMaxSystemTime(stats.getAverageSystemTime() / 1000.0 * 2.0); + + // Estatísticas por tipo + for (VehicleType type : VehicleType.values()) { + int count = stats.getVehicleTypeCount(type); + double waitTime = stats.getAverageWaitingTimeByType(type) / 1000.0; + result.setVehicleCountByType(type, count); + result.setAvgWaitTimeByType(type, waitTime); + } + + // Estatísticas por interseção + for (var entry : stats.getAllIntersectionStats().entrySet()) { + String intersectionId = entry.getKey(); + DashboardStatistics.IntersectionStats iStats = entry.getValue(); + + result.setVehiclesProcessed(intersectionId, iStats.getTotalDepartures()); + result.setMaxQueueSize(intersectionId, iStats.getCurrentQueueSize()); + result.setAvgQueueSize(intersectionId, (double) iStats.getCurrentQueueSize()); + } + } + + /** + * Gera dados simulados (mock) caso o dashboard não esteja conectado. + * Útil para testes de interface. + */ + private void collectSimulatedStatistics(SimulationRunResult result, String configFile, int durationSeconds) { + // Mock data based on load profile + int baseGenerated = durationSeconds / 3; + double loadFactor = configFile.contains("low") ? 0.2 : + configFile.contains("medium") ? 0.5 : 1.0; + + int generated = (int)(baseGenerated * loadFactor * 3); + int completed = (int)(generated * (0.85 + Math.random() * 0.1)); // 85-95% completion rate + + double baseSystemTime = 40.0; + double congestionFactor = configFile.contains("low") ? 1.0 : + configFile.contains("medium") ? 1.5 : 2.5; + + result.setTotalVehiclesGenerated(generated); + result.setTotalVehiclesCompleted(completed); + result.setAverageSystemTime(baseSystemTime * congestionFactor + Math.random() * 10); + result.setMinSystemTime(20.0 + Math.random() * 5); + result.setMaxSystemTime(baseSystemTime * congestionFactor * 2 + Math.random() * 20); + result.setAverageWaitingTime(10.0 * congestionFactor + Math.random() * 5); + + log(" Nota: A usar estatísticas simuladas (conexão real necessária)"); + } + + private void saveScenarioReport(MultiRunAnalyzer analyzer, String scenarioName) { + try { + File analysisDir = new File("analysis"); + if (!analysisDir.exists()) { + analysisDir.mkdirs(); + } + + String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()); + String reportFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".txt"; + String csvFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".csv"; + + analyzer.saveReport(reportFile); + analyzer.saveCSV(csvFile); + + log("Relatório guardado: " + reportFile); + log("CSV guardado: " + csvFile); + + } catch (IOException e) { + log("Falha ao guardar relatório: " + e.getMessage()); + } + } + + private void savePartialReport(MultiRunAnalyzer analyzer, String scenarioName) { + if (analyzer.getRunCount() > 0) { + log("A guardar resultados parciais..."); + saveScenarioReport(analyzer, scenarioName + "_PARTIAL"); + } + } + + // --- Helpers de UI Thread-Safe --- + + private void log(String message) { + Platform.runLater(() -> { + logArea.appendText(message + "\n"); + logArea.setScrollTop(Double.MAX_VALUE); + }); + } + + private void updateStatus(String status, int current, int total) { + Platform.runLater(() -> { + statusLabel.setText(status); + progressLabel.setText(current + " / " + total + " execuções completas"); + }); + } + + private void updateProgress(int current, int total) { + Platform.runLater(() -> { + progressBar.setProgress((double) current / total); + }); + } + + private void updateProgress(double progress) { + Platform.runLater(() -> { + progressBar.setProgress(progress); + }); + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/ConfigurationDialog.java b/main/src/main/java/sd/dashboard/ConfigurationDialog.java new file mode 100644 index 0000000..d823d63 --- /dev/null +++ b/main/src/main/java/sd/dashboard/ConfigurationDialog.java @@ -0,0 +1,183 @@ +package sd.dashboard; + +import javafx.geometry.Insets; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.Separator; +import javafx.scene.control.Spinner; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; + +/** + * Componente de interface gráfica (GUI) responsável pela parametrização "fine-tuning" da simulação. + *

+ * 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: + *

    + *
  • Geração de Carga: Alternância entre modelos estocásticos (Poisson) e determinísticos.
  • + *
  • Temporização: Ajuste da escala de tempo (Time Scale) para visualização vs. performance pura.
  • + *
  • Mix de Veículos: Definição das probabilidades de geração por tipo de agente.
  • + *
+ */ +public class ConfigurationDialog { + + /** + * Exibe o diálogo de configuração avançada e captura as intenções do utilizador. + *

+ * 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 dialog = new Dialog<>(); + dialog.initOwner(owner); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.setTitle("Configuração Avançada da Simulação"); + dialog.setHeaderText("Ajustar parâmetros da simulação"); + + // Criar painel de configuração + VBox content = new VBox(15); + content.setPadding(new Insets(20)); + + // Seção 1: Parâmetros de Chegada + Label arrivalHeader = new Label("Parâmetros de Chegada de Veículos"); + arrivalHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane arrivalGrid = new GridPane(); + arrivalGrid.setHgap(10); + arrivalGrid.setVgap(10); + arrivalGrid.setPadding(new Insets(10)); + + // Modelo de chegada + Label modelLabel = new Label("Modelo de chegada:"); + ComboBox modelCombo = new ComboBox<>(); + modelCombo.getItems().addAll("POISSON", "FIXED"); + modelCombo.setValue("POISSON"); + arrivalGrid.add(modelLabel, 0, 0); + arrivalGrid.add(modelCombo, 1, 0); + + // Taxa de chegada (λ) + Label rateLabel = new Label("Taxa de chegada (λ) [veículos/s]:"); + Spinner rateSpinner = new Spinner<>(0.1, 2.0, 0.5, 0.1); + rateSpinner.setEditable(true); + rateSpinner.setPrefWidth(100); + arrivalGrid.add(rateLabel, 0, 1); + arrivalGrid.add(rateSpinner, 1, 1); + + // Intervalo fixo (se aplicável) + Label intervalLabel = new Label("Intervalo fixo [s]:"); + Spinner intervalSpinner = new Spinner<>(0.5, 10.0, 2.0, 0.5); + intervalSpinner.setEditable(true); + intervalSpinner.setPrefWidth(100); + intervalSpinner.setDisable(true); + arrivalGrid.add(intervalLabel, 0, 2); + arrivalGrid.add(intervalSpinner, 1, 2); + + // Habilitar/desabilitar intervalo baseado no modelo + modelCombo.setOnAction(e -> { + boolean isFixed = "FIXED".equals(modelCombo.getValue()); + intervalSpinner.setDisable(!isFixed); + rateSpinner.setDisable(isFixed); + }); + + // Seção 2: Parâmetros de Tempo + Label timeHeader = new Label("Parâmetros de Tempo"); + timeHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane timeGrid = new GridPane(); + timeGrid.setHgap(10); + timeGrid.setVgap(10); + timeGrid.setPadding(new Insets(10)); + + // Duração da simulação + Label durationLabel = new Label("Duração da simulação [s]:"); + Spinner durationSpinner = new Spinner<>(60, 7200, 300, 60); + durationSpinner.setEditable(true); + durationSpinner.setPrefWidth(100); + timeGrid.add(durationLabel, 0, 0); + timeGrid.add(durationSpinner, 1, 0); + + // Escala temporal (para visualização) + Label scaleLabel = new Label("Escala temporal (0=instantâneo, 1=tempo real):"); + Spinner scaleSpinner = new Spinner<>(0.0, 1.0, 0.01, 0.01); + scaleSpinner.setEditable(true); + scaleSpinner.setPrefWidth(100); + timeGrid.add(scaleLabel, 0, 1); + timeGrid.add(scaleSpinner, 1, 1); + + // Tempo de drenagem + Label drainLabel = new Label("Tempo de drenagem [s]:"); + Spinner drainSpinner = new Spinner<>(0, 300, 60, 10); + drainSpinner.setEditable(true); + drainSpinner.setPrefWidth(100); + timeGrid.add(drainLabel, 0, 2); + timeGrid.add(drainSpinner, 1, 2); + + // Seção 3: Distribuição de Tipos de Veículos + Label vehicleHeader = new Label("Distribuição de Tipos de Veículos"); + vehicleHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + GridPane vehicleGrid = new GridPane(); + vehicleGrid.setHgap(10); + vehicleGrid.setVgap(10); + vehicleGrid.setPadding(new Insets(10)); + + Label bikeLabel = new Label("Bicicletas/Motos [%]:"); + Spinner bikeSpinner = new Spinner<>(0, 100, 10, 5); + bikeSpinner.setEditable(true); + bikeSpinner.setPrefWidth(100); + vehicleGrid.add(bikeLabel, 0, 0); + vehicleGrid.add(bikeSpinner, 1, 0); + + Label lightLabel = new Label("Veículos Ligeiros [%]:"); + Spinner lightSpinner = new Spinner<>(0, 100, 70, 5); + lightSpinner.setEditable(true); + lightSpinner.setPrefWidth(100); + vehicleGrid.add(lightLabel, 0, 1); + vehicleGrid.add(lightSpinner, 1, 1); + + Label heavyLabel = new Label("Veículos Pesados [%]:"); + Spinner heavySpinner = new Spinner<>(0, 100, 20, 5); + heavySpinner.setEditable(true); + heavySpinner.setPrefWidth(100); + vehicleGrid.add(heavyLabel, 0, 2); + vehicleGrid.add(heavySpinner, 1, 2); + + // Nota informativa + Label noteLabel = new Label("Nota: Estes parâmetros sobrepõem os valores do ficheiro .properties selecionado.\n" + + "Para usar os valores padrão do ficheiro, deixe em branco ou cancele."); + noteLabel.setWrapText(true); + noteLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #666666;"); + + // Adicionar tudo ao conteúdo + content.getChildren().addAll( + arrivalHeader, arrivalGrid, + new Separator(), + timeHeader, timeGrid, + new Separator(), + vehicleHeader, vehicleGrid, + new Separator(), + noteLabel + ); + + dialog.getDialogPane().setContent(content); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + // Mostrar diálogo e processar resultado + return dialog.showAndWait() + .map(buttonType -> buttonType == ButtonType.OK) + .orElse(false); + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/DashboardClientHandler.java b/main/src/main/java/sd/dashboard/DashboardClientHandler.java new file mode 100644 index 0000000..abdfc20 --- /dev/null +++ b/main/src/main/java/sd/dashboard/DashboardClientHandler.java @@ -0,0 +1,180 @@ +package sd.dashboard; + +import java.io.IOException; +import java.net.Socket; +import java.util.Map; + +import sd.model.MessageType; +import sd.protocol.MessageProtocol; +import sd.protocol.SocketConnection; + +/** + * Worker responsável pelo processamento dedicado de uma conexão de cliente TCP no Dashboard. + *

+ * 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: + *

    + *
  1. Manter a conexão persistente com o nó remoto.
  2. + *
  3. Desserializar mensagens de protocolo recebidas.
  4. + *
  5. Normalizar payloads JSON (resolvendo ambiguidades de tipagem do Gson).
  6. + *
  7. Atualizar o objeto partilhado {@link DashboardStatistics} de forma thread-safe.
  8. + *
+ */ +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. + *

+ * 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(); + + try (SocketConnection connection = new SocketConnection(clientSocket)) { + System.out.println("[Handler] Started handling client: " + clientInfo); + + while (!Thread.currentThread().isInterrupted()) { + try { + MessageProtocol message = connection.receiveMessage(); + + if (message == null) { + System.out.println("[Handler] Client disconnected: " + clientInfo); + break; + } + + processMessage(message); + + } catch (ClassNotFoundException e) { + System.err.println("[Handler] Unknown message class from " + clientInfo + ": " + e.getMessage()); + } catch (IOException e) { + System.out.println("[Handler] Connection error with " + clientInfo + ": " + e.getMessage()); + break; + } + } + + } catch (IOException e) { + System.err.println("[Handler] Error initializing connection with " + clientInfo + ": " + e.getMessage()); + } finally { + try { + if (!clientSocket.isClosed()) { + clientSocket.close(); + } + } catch (IOException e) { + System.err.println("[Handler] Error closing socket for " + clientInfo + ": " + e.getMessage()); + } + } + } + + /** + * 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()); + return; + } + + String senderId = message.getSourceNode(); + Object payload = message.getPayload(); + + System.out.println("[Handler] Received STATS_UPDATE from: " + senderId); + + // Handle both direct StatsUpdatePayload and Gson-deserialized Map + StatsUpdatePayload stats; + if (payload instanceof StatsUpdatePayload) { + 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); + } else { + System.err.println("[Handler] Unknown payload type: " + + (payload != null ? payload.getClass().getName() : "null")); + return; + } + + 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()); + } + + if (stats.getTotalVehiclesCompleted() >= 0) { + statistics.updateVehiclesCompleted(stats.getTotalVehiclesCompleted()); + } + + // Exit Node sends cumulative totals, so we SET rather than ADD + if (stats.getTotalSystemTime() >= 0) { + statistics.setTotalSystemTime(stats.getTotalSystemTime()); + } + + if (stats.getTotalWaitingTime() >= 0) { + statistics.setTotalWaitingTime(stats.getTotalWaitingTime()); + } + + // Process vehicle type statistics (from Exit Node) + if (stats.getVehicleTypeCounts() != null && !stats.getVehicleTypeCounts().isEmpty()) { + Map counts = stats.getVehicleTypeCounts(); + Map waitTimes = stats.getVehicleTypeWaitTimes(); + + for (var entry : counts.entrySet()) { + sd.model.VehicleType type = entry.getKey(); + int count = entry.getValue(); + long waitTime = (waitTimes != null && waitTimes.containsKey(type)) + ? waitTimes.get(type) : 0L; + statistics.updateVehicleTypeStats(type, count, waitTime); + } + } + + // Process intersection statistics (from Intersection processes) + if (senderId.startsWith("Cr") || senderId.startsWith("E")) { + statistics.updateIntersectionStats( + senderId, + stats.getIntersectionArrivals(), + stats.getIntersectionDepartures(), + stats.getIntersectionQueueSize() + ); + } + + 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 new file mode 100644 index 0000000..6f76a3b --- /dev/null +++ b/main/src/main/java/sd/dashboard/DashboardServer.java @@ -0,0 +1,223 @@ +package sd.dashboard; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import sd.config.SimulationConfig; + +/** + * 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: + *

    + *
  • Headless (CLI): Renderização periódica de métricas no terminal (stdout).
  • + *
  • GUI (JavaFX): Delegação do controlo para a interface gráfica {@link DashboardUI}.
  • + *
+ */ +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. + *

+ * 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; + String configFile = "src/main/resources/simulation.properties"; + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--gui") || args[i].equals("-g")) { + useGUI = true; + } else { + configFile = args[i]; + } + } + + if (useGUI) { + // Launch JavaFX UI + System.out.println("Launching Dashboard with JavaFX GUI..."); + DashboardUI.main(args); + } else { + // Traditional terminal mode + System.out.println("=".repeat(60)); + System.out.println("DASHBOARD SERVER - DISTRIBUTED TRAFFIC SIMULATION"); + System.out.println("=".repeat(60)); + + try { + System.out.println("Loading configuration from: " + configFile); + + SimulationConfig config = new SimulationConfig(configFile); + DashboardServer server = new DashboardServer(config); + + // Start the server + System.out.println("\n" + "=".repeat(60)); + server.start(); + + // Keep running until interrupted + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("\n\nShutdown signal received..."); + server.stop(); + })); + + // Display statistics periodically + server.displayLoop(); + + } catch (IOException e) { + System.err.println("Failed to start Dashboard Server: " + e.getMessage()); + System.exit(1); + } + } + } + + /** + * 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."); + return; + } + + serverSocket = new ServerSocket(port); + running.set(true); + + System.out.println("Dashboard Server started on port " + port); + System.out.println("Waiting for statistics updates from simulation processes..."); + System.out.println("=".repeat(60)); + + Thread acceptThread = new Thread(this::acceptConnections, "DashboardServer-Accept"); + acceptThread.setDaemon(false); + 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 { + Socket clientSocket = serverSocket.accept(); + System.out.println("[Connection] New client connected: " + + clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort()); + + clientHandlerPool.execute(new DashboardClientHandler(clientSocket, statistics)); + + } catch (IOException e) { + if (running.get()) { + System.err.println("[Error] Failed to accept client connection: " + e.getMessage()); + } + } + } + } + + /** + * 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; + + while (running.get()) { + try { + Thread.sleep(DISPLAY_INTERVAL_MS); + displayStatistics(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + /** + * 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"); + System.out.println("=".repeat(60)); + statistics.display(); + 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; + } + + System.out.println("\nStopping Dashboard Server..."); + running.set(false); + + try { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + } catch (IOException e) { + System.err.println("Error closing server socket: " + e.getMessage()); + } + + clientHandlerPool.shutdownNow(); + System.out.println("Dashboard Server stopped."); + } + + public DashboardStatistics getStatistics() { + return statistics; + } + + 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 new file mode 100644 index 0000000..dd9f2f8 --- /dev/null +++ b/main/src/main/java/sd/dashboard/DashboardStatistics.java @@ -0,0 +1,301 @@ +package sd.dashboard; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import sd.model.VehicleType; + +/** + * 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 { + + private final AtomicInteger totalVehiclesGenerated; + private final AtomicInteger totalVehiclesCompleted; + private final AtomicLong totalSystemTime; + private final AtomicLong totalWaitingTime; + + /** Mapa thread-safe para armazenar métricas granulares por interseção. */ + private final Map intersectionStats; + + private final Map vehicleTypeCount; + private final Map vehicleTypeWaitTime; + + /** Timestamp da última atualização de escrita, com garantia de visibilidade de memória (volatile). */ + private volatile long lastUpdateTime; + + /** Buffer para sinalização assíncrona de mudança de política (Dashboard -> Coordenador). */ + private volatile String requestedRoutingPolicy; + + /** + * Inicializa os contadores atómicos e as estruturas de dados concorrentes. + */ + public DashboardStatistics() { + this.totalVehiclesGenerated = new AtomicInteger(0); + this.totalVehiclesCompleted = new AtomicInteger(0); + this.totalSystemTime = new AtomicLong(0); + this.totalWaitingTime = new AtomicLong(0); + + this.intersectionStats = new ConcurrentHashMap<>(); + this.vehicleTypeCount = new ConcurrentHashMap<>(); + this.vehicleTypeWaitTime = new ConcurrentHashMap<>(); + + for (VehicleType type : VehicleType.values()) { + vehicleTypeCount.put(type, new AtomicInteger(0)); + vehicleTypeWaitTime.put(type, new AtomicLong(0)); + } + + this.lastUpdateTime = System.currentTimeMillis(); + } + + public void updateVehiclesGenerated(int count) { + totalVehiclesGenerated.set(count); + updateTimestamp(); + } + + public void incrementVehiclesGenerated() { + totalVehiclesGenerated.incrementAndGet(); + updateTimestamp(); + } + + public void updateVehiclesCompleted(int count) { + totalVehiclesCompleted.set(count); + updateTimestamp(); + } + + public void incrementVehiclesCompleted() { + totalVehiclesCompleted.incrementAndGet(); + updateTimestamp(); + } + + public void addSystemTime(long timeMs) { + totalSystemTime.addAndGet(timeMs); + updateTimestamp(); + } + + public void setTotalSystemTime(long timeMs) { + totalSystemTime.set(timeMs); + updateTimestamp(); + } + + public void addWaitingTime(long timeMs) { + totalWaitingTime.addAndGet(timeMs); + updateTimestamp(); + } + + public void setTotalWaitingTime(long timeMs) { + totalWaitingTime.set(timeMs); + updateTimestamp(); + } + + public void updateVehicleTypeStats(VehicleType type, int count, long waitTimeMs) { + vehicleTypeCount.get(type).set(count); + vehicleTypeWaitTime.get(type).set(waitTimeMs); + updateTimestamp(); + } + + public void incrementVehicleType(VehicleType type) { + vehicleTypeCount.get(type).incrementAndGet(); + updateTimestamp(); + } + + /** + * Atualiza ou inicializa atomicamente as estatísticas de uma interseção específica. + *

+ * 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) -> { + if (stats == null) { + stats = new IntersectionStats(intersectionId); + } + stats.updateStats(arrivals, departures, currentQueueSize); + return stats; + }); + updateTimestamp(); + } + + private void updateTimestamp() { + lastUpdateTime = System.currentTimeMillis(); + } + + // --- Getters e Métricas Calculadas --- + + public int getTotalVehiclesGenerated() { + return totalVehiclesGenerated.get(); + } + + public int getTotalVehiclesCompleted() { + 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; + return (double) totalWaitingTime.get() / completed; + } + + public int getVehicleTypeCount(VehicleType type) { + return vehicleTypeCount.get(type).get(); + } + + public double getAverageWaitingTimeByType(VehicleType type) { + int count = vehicleTypeCount.get(type).get(); + if (count == 0) return 0.0; + return (double) vehicleTypeWaitTime.get(type).get() / count; + } + + public IntersectionStats getIntersectionStats(String intersectionId) { + return intersectionStats.get(intersectionId); + } + + public Map getAllIntersectionStats() { + return new HashMap<>(intersectionStats); + } + + public long getLastUpdateTime() { + 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 getCurrentQueueSizes() { + Map queueSizes = new HashMap<>(); + for (Map.Entry entry : intersectionStats.entrySet()) { + queueSizes.put(entry.getKey(), entry.getValue().getCurrentQueueSize()); + } + return queueSizes; + } + + /** + * Regista uma intenção de mudança de política de roteamento solicitada pela UI. + * O Coordenador fará polling deste valor periodicamente. + */ + public void setRequestedRoutingPolicy(String policy) { + this.requestedRoutingPolicy = policy; + } + + /** + * Obtém e limpa atomicamente a política de roteamento solicitada. + * Implementa a semântica de consumo único (one-time consumption). + * * @return A política solicitada ou null se não houver mudança pendente. + */ + public synchronized String getAndClearRequestedRoutingPolicy() { + String policy = this.requestedRoutingPolicy; + this.requestedRoutingPolicy = null; + return policy; + } + + /** + * Imprime um resumo formatado das estatísticas no stdout. + * Útil para o modo CLI (Headless). + */ + public void display() { + System.out.println("\n--- GLOBAL STATISTICS ---"); + System.out.printf("Total Vehicles Generated: %d%n", getTotalVehiclesGenerated()); + System.out.printf("Total Vehicles Completed: %d%n", getTotalVehiclesCompleted()); + System.out.printf("Vehicles In Transit: %d%n", + getTotalVehiclesGenerated() - getTotalVehiclesCompleted()); + System.out.printf("Average System Time: %.2f ms%n", getAverageSystemTime()); + System.out.printf("Average Waiting Time: %.2f ms%n", getAverageWaitingTime()); + + System.out.println("\n--- VEHICLE TYPE STATISTICS ---"); + for (VehicleType type : VehicleType.values()) { + int count = getVehicleTypeCount(type); + double avgWait = getAverageWaitingTimeByType(type); + System.out.printf("%s: %d vehicles, avg wait: %.2f ms%n", + type, count, avgWait); + } + + System.out.println("\n--- INTERSECTION STATISTICS ---"); + if (intersectionStats.isEmpty()) { + System.out.println("(No data received yet)"); + } else { + for (IntersectionStats stats : intersectionStats.values()) { + stats.display(); + } + } + + System.out.printf("%nLast Update: %tT%n", lastUpdateTime); + } + + /** + * Agregado de métricas específico para um nó de interseção. + * Mantém contadores atómicos para garantir consistência em atualizações concorrentes. + */ + public static class IntersectionStats { + private final String intersectionId; + private final AtomicInteger totalArrivals; + private final AtomicInteger totalDepartures; + private final AtomicInteger currentQueueSize; + + public IntersectionStats(String intersectionId) { + this.intersectionId = intersectionId; + this.totalArrivals = new AtomicInteger(0); + this.totalDepartures = new AtomicInteger(0); + this.currentQueueSize = new AtomicInteger(0); + } + + public void updateStats(int arrivals, int departures, int queueSize) { + this.totalArrivals.set(arrivals); + this.totalDepartures.set(departures); + this.currentQueueSize.set(queueSize); + } + + public String getIntersectionId() { + return intersectionId; + } + + public int getTotalArrivals() { + return totalArrivals.get(); + } + + public int getTotalDepartures() { + return totalDepartures.get(); + } + + public int getCurrentQueueSize() { + return currentQueueSize.get(); + } + + public void display() { + System.out.printf("%s: Arrivals=%d, Departures=%d, Queue=%d%n", + intersectionId, getTotalArrivals(), getTotalDepartures(), getCurrentQueueSize()); + } + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java new file mode 100644 index 0000000..8757784 --- /dev/null +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -0,0 +1,619 @@ +package sd.dashboard; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; +import javafx.stage.Stage; +import sd.config.SimulationConfig; +import sd.model.VehicleType; + +/** + * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real. + *

+ * Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão + * Observer (via polling) para refletir o estado do modelo {@link DashboardStatistics} + * nos componentes visuais. + *

+ * Aspetos Técnicos Relevantes: + *

    + *
  • Concorrência de UI: Utiliza um {@link ScheduledExecutorService} para buscar dados + * em background e {@link Platform#runLater(Runnable)} para injetar atualizações na + * JavaFX Application Thread, evitando exceções de "Not on FX application thread".
  • + *
  • Data Binding: Utiliza {@link TableView} com classes internas (DTOs) para + * renderização tabular eficiente de tipos de veículos e interseções.
  • + *
  • Controlo de Processos: Integra com {@link SimulationProcessManager} para orquestrar + * o ciclo de vida (spawn/kill) dos processos externos da simulação.
  • + *
+ */ +public class DashboardUI extends Application { + + private DashboardServer server; + private DashboardStatistics statistics; + + // Global Statistics Labels + private Label lblVehiclesGenerated; + private Label lblVehiclesCompleted; + private Label lblVehiclesInTransit; + private Label lblAvgSystemTime; + private Label lblAvgWaitingTime; + private Label lblLastUpdate; + + // Vehicle Type Table + private TableView vehicleTypeTable; + + // Intersection Table + private TableView intersectionTable; + + // Update scheduler (Background Thread) + private ScheduledExecutorService updateScheduler; + + // Configuration controls + private ComboBox configFileSelector; + private String selectedConfigFile = "simulation.properties"; + private Label configInfoLabel; + + /** + * Ponto de entrada da aplicação JavaFX. + * Configura o Stage primário, inicializa o servidor de backend e constrói a árvore de cena (Scene Graph). + */ + @Override + public void start(Stage primaryStage) { + try { + // Initialize server + String configFile = getParameters().getRaw().isEmpty() + ? "src/main/resources/simulation.properties" + : getParameters().getRaw().get(0); + + SimulationConfig config = new SimulationConfig(configFile); + server = new DashboardServer(config); + statistics = server.getStatistics(); + + // Start the dashboard server (Backend listening port) + server.start(); + + // Build UI Layout + BorderPane root = new BorderPane(); + root.getStyleClass().add("root"); + + // Header (Top) + VBox header = createHeader(); + root.setTop(header); + + // Main content (Center) + VBox mainContent = createMainContent(); + root.setCenter(mainContent); + + // Footer (Bottom) + HBox footer = createFooter(); + root.setBottom(footer); + + // Create scene & apply CSS + Scene scene = new Scene(root, 1200, 850); + String cssUrl = getClass().getResource("/dashboard.css").toExternalForm(); + scene.getStylesheets().add(cssUrl); + + primaryStage.setTitle("Traffic Simulation Dashboard - Real-time Statistics"); + primaryStage.setScene(scene); + primaryStage.show(); + + // Start periodic updates loop + startPeriodicUpdates(); + + // Handle window close (Graceful shutdown) + primaryStage.setOnCloseRequest(event -> { + shutdown(); + }); + + } catch (Exception e) { + showErrorAlert("Failed to start Dashboard Server", e.getMessage()); + e.printStackTrace(); + Platform.exit(); + } + } + + private VBox createHeader() { + VBox header = new VBox(10); + header.getStyleClass().add("header"); + header.setAlignment(Pos.CENTER); + + Label title = new Label("DISTRIBUTED TRAFFIC SIMULATION DASHBOARD"); + title.getStyleClass().add("header-title"); + + Label subtitle = new Label("Real-time Statistics and Monitoring"); + subtitle.getStyleClass().add("header-subtitle"); + + // Configuration Panel + VBox configPanel = createConfigurationPanel(); + + // Control Buttons + HBox controls = new HBox(15); + controls.setAlignment(Pos.CENTER); + + Button btnStart = new Button("START SIMULATION"); + btnStart.getStyleClass().add("button-start"); + + Button btnStop = new Button("STOP SIMULATION"); + btnStop.getStyleClass().add("button-stop"); + btnStop.setDisable(true); + + SimulationProcessManager processManager = new SimulationProcessManager(); + + btnStart.setOnAction(e -> { + try { + // Passar o ficheiro de configuração selecionado + processManager.setConfigFile(selectedConfigFile); + processManager.startSimulation(); + + // Toggle UI state + btnStart.setDisable(true); + btnStop.setDisable(false); + configFileSelector.setDisable(true); // Bloquear mudanças durante simulação + } catch (IOException ex) { + showErrorAlert("Start Failed", "Could not start simulation processes: " + ex.getMessage()); + } + }); + + btnStop.setOnAction(e -> { + processManager.stopSimulation(); + + // Toggle UI state + btnStart.setDisable(false); + btnStop.setDisable(true); + configFileSelector.setDisable(false); // Desbloquear para nova simulação + }); + + controls.getChildren().addAll(btnStart, btnStop); + + header.getChildren().addAll(title, subtitle, configPanel, controls); + + return header; + } + + /** + * Cria o painel de configuração com seleção de cenário e parâmetros. + */ + private VBox createConfigurationPanel() { + VBox configBox = new VBox(10); + configBox.setAlignment(Pos.CENTER); + configBox.setPadding(new Insets(10)); + configBox.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;"); + + Label configLabel = new Label("Configuração da Simulação"); + configLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); + + HBox configControls = new HBox(20); + configControls.setAlignment(Pos.CENTER); + + // Scenario selector + VBox scenarioBox = new VBox(5); + scenarioBox.setAlignment(Pos.CENTER_LEFT); + Label scenarioLabel = new Label("Cenário:"); + scenarioLabel.setStyle("-fx-font-size: 12px;"); + + configFileSelector = new ComboBox<>(); + configFileSelector.getItems().addAll( + "simulation.properties", + "simulation-low.properties", + "simulation-medium.properties", + "simulation-high.properties" + ); + configFileSelector.setValue("simulation.properties"); + configFileSelector.setOnAction(e -> { + selectedConfigFile = configFileSelector.getValue(); + updateConfigInfo(); + System.out.println("Configuração selecionada: " + selectedConfigFile); + }); + + scenarioBox.getChildren().addAll(scenarioLabel, configFileSelector); + configControls.getChildren().add(scenarioBox); + + // Routing policy selector + VBox routingBox = new VBox(5); + routingBox.setAlignment(Pos.CENTER_LEFT); + Label routingLabel = new Label("Política de Roteamento:"); + routingLabel.setStyle("-fx-font-size: 12px;"); + + ComboBox routingPolicySelector = new ComboBox<>(); + routingPolicySelector.getItems().addAll( + "RANDOM", + "SHORTEST_PATH", + "LEAST_CONGESTED" + ); + routingPolicySelector.setValue("RANDOM"); + routingPolicySelector.setOnAction(e -> { + String selectedPolicy = routingPolicySelector.getValue(); + System.out.println("Política de roteamento selecionada: " + selectedPolicy); + sendRoutingPolicyChange(selectedPolicy); + }); + + routingBox.getChildren().addAll(routingLabel, routingPolicySelector); + configControls.getChildren().add(routingBox); + + // Advanced configuration button + VBox buttonBox = new VBox(5); + buttonBox.setAlignment(Pos.CENTER_LEFT); + Label spacerLabel = new Label(" "); + spacerLabel.setStyle("-fx-font-size: 12px;"); + + Button btnAdvancedConfig = new Button("Configuração Avançada..."); + btnAdvancedConfig.setStyle("-fx-font-size: 11px;"); + btnAdvancedConfig.setOnAction(e -> { + ConfigurationDialog.showAdvancedConfig((Stage) configBox.getScene().getWindow()); + }); + + Button btnBatchAnalysis = new Button("Análise em Lote..."); + btnBatchAnalysis.setStyle("-fx-font-size: 11px;"); + btnBatchAnalysis.setOnAction(e -> { + BatchAnalysisDialog.show((Stage) configBox.getScene().getWindow(), statistics); + }); + + buttonBox.getChildren().addAll(spacerLabel, btnAdvancedConfig, btnBatchAnalysis); + configControls.getChildren().add(buttonBox); + + // Configuration info display + configInfoLabel = new Label(); + configInfoLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #aaaaaa;"); + configInfoLabel.setWrapText(true); + configInfoLabel.setMaxWidth(800); + configInfoLabel.setAlignment(Pos.CENTER); + updateConfigInfo(); + + configBox.getChildren().addAll(configLabel, configControls, configInfoLabel); + return configBox; + } + + private VBox createMainContent() { + VBox mainContent = new VBox(20); + mainContent.setPadding(new Insets(20)); + + // Global Statistics Panel + VBox globalStatsCard = createGlobalStatisticsPanel(); + + // Tables Container + HBox tablesContainer = new HBox(20); + tablesContainer.setAlignment(Pos.TOP_CENTER); + + // Vehicle Type Statistics Panel + VBox vehicleTypeCard = createVehicleTypePanel(); + HBox.setHgrow(vehicleTypeCard, Priority.ALWAYS); + + // Intersection Statistics Panel + VBox intersectionCard = createIntersectionPanel(); + HBox.setHgrow(intersectionCard, Priority.ALWAYS); + + tablesContainer.getChildren().addAll(vehicleTypeCard, intersectionCard); + + mainContent.getChildren().addAll(globalStatsCard, tablesContainer); + + return mainContent; + } + + private VBox createGlobalStatisticsPanel() { + VBox card = new VBox(); + card.getStyleClass().add("card"); + + // Card Header + HBox cardHeader = new HBox(); + cardHeader.getStyleClass().add("card-header"); + Label cardTitle = new Label("Global Statistics"); + cardTitle.getStyleClass().add("card-title"); + cardHeader.getChildren().add(cardTitle); + + // Card Content + GridPane grid = new GridPane(); + grid.getStyleClass().add("card-content"); + grid.setHgap(40); + grid.setVgap(15); + grid.setAlignment(Pos.CENTER); + + // Initialize labels + lblVehiclesGenerated = createStatValueLabel("0"); + lblVehiclesCompleted = createStatValueLabel("0"); + lblVehiclesInTransit = createStatValueLabel("0"); + lblAvgSystemTime = createStatValueLabel("0.00 s"); + lblAvgWaitingTime = createStatValueLabel("0.00 s"); + + // Add labels with descriptions + addStatRow(grid, 0, 0, "Total Vehicles Generated", lblVehiclesGenerated); + addStatRow(grid, 1, 0, "Total Vehicles Completed", lblVehiclesCompleted); + addStatRow(grid, 2, 0, "Vehicles In Transit", lblVehiclesInTransit); + addStatRow(grid, 0, 1, "Average System Time", lblAvgSystemTime); + addStatRow(grid, 1, 1, "Average Waiting Time", lblAvgWaitingTime); + + card.getChildren().addAll(cardHeader, grid); + return card; + } + + private VBox createVehicleTypePanel() { + VBox card = new VBox(); + card.getStyleClass().add("card"); + + // Card Header + HBox cardHeader = new HBox(); + cardHeader.getStyleClass().add("card-header"); + Label cardTitle = new Label("Vehicle Type Statistics"); + cardTitle.getStyleClass().add("card-title"); + cardHeader.getChildren().add(cardTitle); + + // Table + vehicleTypeTable = new TableView<>(); + vehicleTypeTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + vehicleTypeTable.setPrefHeight(300); + + TableColumn typeCol = new TableColumn<>("Vehicle Type"); + typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType")); + + TableColumn countCol = new TableColumn<>("Count"); + countCol.setCellValueFactory(new PropertyValueFactory<>("count")); + + TableColumn avgWaitCol = new TableColumn<>("Avg Wait Time"); + avgWaitCol.setCellValueFactory(new PropertyValueFactory<>("avgWaitTime")); + + vehicleTypeTable.getColumns().addAll(typeCol, countCol, avgWaitCol); + + card.getChildren().addAll(cardHeader, vehicleTypeTable); + return card; + } + + private VBox createIntersectionPanel() { + VBox card = new VBox(); + card.getStyleClass().add("card"); + + // Card Header + HBox cardHeader = new HBox(); + cardHeader.getStyleClass().add("card-header"); + Label cardTitle = new Label("Intersection Statistics"); + cardTitle.getStyleClass().add("card-title"); + cardHeader.getChildren().add(cardTitle); + + // Table + intersectionTable = new TableView<>(); + intersectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + intersectionTable.setPrefHeight(300); + + TableColumn idCol = new TableColumn<>("Intersection ID"); + idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId")); + + TableColumn arrivalsCol = new TableColumn<>("Total Arrivals"); + arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals")); + + TableColumn departuresCol = new TableColumn<>("Total Departures"); + departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures")); + + TableColumn queueCol = new TableColumn<>("Current Queue"); + queueCol.setCellValueFactory(new PropertyValueFactory<>("queueSize")); + + intersectionTable.getColumns().addAll(idCol, arrivalsCol, departuresCol, queueCol); + + card.getChildren().addAll(cardHeader, intersectionTable); + return card; + } + + private HBox createFooter() { + HBox footer = new HBox(10); + footer.getStyleClass().add("footer"); + footer.setAlignment(Pos.CENTER_LEFT); + + Label statusLabel = new Label("Status:"); + statusLabel.getStyleClass().add("footer-text"); + statusLabel.setStyle("-fx-font-weight: bold;"); + + Circle statusIndicator = new Circle(6); + statusIndicator.setFill(javafx.scene.paint.Color.LIME); + + Label statusText = new Label("Connected and Receiving Data"); + statusText.getStyleClass().add("footer-text"); + + lblLastUpdate = new Label("Last Update: --:--:--"); + lblLastUpdate.getStyleClass().add("footer-text"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + footer.getChildren().addAll(statusLabel, statusIndicator, statusText, spacer, lblLastUpdate); + + return footer; + } + + private Label createStatValueLabel(String initialValue) { + Label label = new Label(initialValue); + label.getStyleClass().add("stat-value"); + return label; + } + + private void addStatRow(GridPane grid, int row, int colGroup, String description, Label valueLabel) { + VBox container = new VBox(5); + container.setAlignment(Pos.CENTER_LEFT); + + Label descLabel = new Label(description); + descLabel.getStyleClass().add("stat-label"); + + container.getChildren().addAll(descLabel, valueLabel); + + grid.add(container, colGroup, row); + } + + /** + * Inicia o ciclo de polling em background. + * Atualiza a UI a cada 100ms. + */ + private void startPeriodicUpdates() { + updateScheduler = Executors.newSingleThreadScheduledExecutor(); + updateScheduler.scheduleAtFixedRate(() -> { + // Crucial: Encapsular atualização de UI em Platform.runLater + // para garantir execução na JavaFX Application Thread + Platform.runLater(this::updateUI); + }, 0, 100, TimeUnit.MILLISECONDS); + } + + /** + * Sincroniza o estado atual do objeto Statistics com os controlos JavaFX. + * Chamado periodicamente pela thread de UI. + */ + private void updateUI() { + // Update global statistics + lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated())); + lblVehiclesCompleted.setText(String.valueOf(statistics.getTotalVehiclesCompleted())); + lblVehiclesInTransit.setText(String.valueOf( + statistics.getTotalVehiclesGenerated() - statistics.getTotalVehiclesCompleted())); + lblAvgSystemTime.setText(String.format("%.2f s", statistics.getAverageSystemTime() / 1000.0)); + lblAvgWaitingTime.setText(String.format("%.2f s", statistics.getAverageWaitingTime() / 1000.0)); + lblLastUpdate.setText(String.format("Last Update: %tT", statistics.getLastUpdateTime())); + + // Update vehicle type table + vehicleTypeTable.getItems().clear(); + for (VehicleType type : VehicleType.values()) { + int count = statistics.getVehicleTypeCount(type); + double avgWait = statistics.getAverageWaitingTimeByType(type); + vehicleTypeTable.getItems().add(new VehicleTypeRow( + type.toString(), count, String.format("%.2f s", avgWait / 1000.0))); + } + + // Update intersection table + intersectionTable.getItems().clear(); + Map intersectionStats = statistics.getAllIntersectionStats(); + for (DashboardStatistics.IntersectionStats stats : intersectionStats.values()) { + intersectionTable.getItems().add(new IntersectionRow( + stats.getIntersectionId(), + stats.getTotalArrivals(), + stats.getTotalDepartures(), + stats.getCurrentQueueSize())); + } + } + + /** + * Atualiza a informação exibida sobre a configuração selecionada. + */ + private void updateConfigInfo() { + String info = ""; + switch (selectedConfigFile) { + case "simulation-low.properties": + info = "CARGA BAIXA: 0.2 veículos/s (~720/hora) | Sem congestionamento esperado"; + break; + case "simulation-medium.properties": + info = "CARGA MÉDIA: 0.5 veículos/s (~1800/hora) | Algum congestionamento esperado"; + break; + case "simulation-high.properties": + info = "CARGA ALTA: 1.0 veículo/s (~3600/hora) | Congestionamento significativo esperado"; + break; + default: + info = "⚙️ CONFIGURAÇÃO PADRÃO: Verificar ficheiro para parâmetros"; + break; + } + configInfoLabel.setText(info); + } + + private void shutdown() { + System.out.println("Shutting down Dashboard UI..."); + + if (updateScheduler != null && !updateScheduler.isShutdown()) { + updateScheduler.shutdownNow(); + } + + if (server != null) { + server.stop(); + } + + Platform.exit(); + } + + private void showErrorAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } + + /** + * Envia mensagem para o servidor do dashboard que notificará o coordenador. + * Usa uma abordagem indireta: salva a política desejada e o coordenador lerá na próxima geração. + */ + private void sendRoutingPolicyChange(String newPolicy) { + // Store the policy change request in statistics + // The coordinator will check this periodically + if (server != null && statistics != null) { + statistics.setRequestedRoutingPolicy(newPolicy); + System.out.println("Política de roteamento solicitada: " + newPolicy); + System.out.println(" - A mudança será aplicada pelo coordenador na próxima atualização"); + + // Mostrar confirmação visual + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Política Solicitada"); + alert.setHeaderText(null); + alert.setContentText("Política de roteamento solicitada: " + newPolicy + "\nSerá aplicada em breve."); + alert.show(); + }); + } else { + Platform.runLater(() -> { + showErrorAlert("Erro", "Dashboard não está conectado. Inicie a simulação primeiro."); + }); + } + } + + public static void main(String[] args) { + launch(args); + } + + // --- DTOs para Data Binding nas Tabelas --- + + /** DTO para linhas da tabela de Tipos de Veículo. */ + public static class VehicleTypeRow { + private final String vehicleType; + private final int count; + private final String avgWaitTime; + + public VehicleTypeRow(String vehicleType, int count, String avgWaitTime) { + this.vehicleType = vehicleType; + this.count = count; + this.avgWaitTime = avgWaitTime; + } + + public String getVehicleType() { return vehicleType; } + public int getCount() { return count; } + public String getAvgWaitTime() { return avgWaitTime; } + } + + /** DTO para linhas da tabela de Interseções. */ + public static class IntersectionRow { + private final String intersectionId; + private final int arrivals; + private final int departures; + private final int queueSize; + + public IntersectionRow(String intersectionId, int arrivals, int departures, int queueSize) { + this.intersectionId = intersectionId; + this.arrivals = arrivals; + this.departures = departures; + this.queueSize = queueSize; + } + + public String getIntersectionId() { return intersectionId; } + public int getArrivals() { return arrivals; } + public int getDepartures() { return departures; } + public int getQueueSize() { return queueSize; } + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/Launcher.java b/main/src/main/java/sd/dashboard/Launcher.java new file mode 100644 index 0000000..0a45c3d --- /dev/null +++ b/main/src/main/java/sd/dashboard/Launcher.java @@ -0,0 +1,7 @@ +package sd.dashboard; + +public class Launcher { + public static void main(String[] args) { + DashboardUI.main(args); + } +} diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java new file mode 100644 index 0000000..063a355 --- /dev/null +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -0,0 +1,184 @@ +package sd.dashboard; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Orquestrador de processos para o ambiente de simulação distribuída. + *

+ * Esta classe atua como um supervisor (Process Manager), responsável pelo bootstrapping + * e teardown das múltiplas Java Virtual Machines (JVMs) que compõem o sistema. + *

+ * Funcionalidades principais: + *

    + *
  • Isolamento: Cada nó (Interseção, Coordinator, ExitNode) corre no seu próprio processo OS.
  • + *
  • Ordem de Arranque: Garante que os servidores (Interseções) estão online antes dos clientes (Coordenador).
  • + *
  • Gestão de Logs: Redireciona stdout/stderr de cada processo filho para ficheiros temporários para facilitar o debug.
  • + *
+ */ +public class SimulationProcessManager { + + private final List runningProcesses; + private final String classpath; + private String configFile; + + /** + * Inicializa o gestor capturando o classpath da JVM atual. + * Isto garante que os processos filhos herdam as mesmas dependências e configurações de ambiente. + */ + public SimulationProcessManager() { + this.runningProcesses = new ArrayList<>(); + this.classpath = System.getProperty("java.class.path"); + this.configFile = "src/main/resources/simulation.properties"; + } + + /** + * Define o perfil de configuração a ser injetado nos processos filhos. + * Útil para alternar entre cenários (Low/Medium/High Load) dinamicamente. + * * @param configFile Nome do ficheiro de propriedades (ex: "simulation-low.properties"). + */ + public void setConfigFile(String configFile) { + this.configFile = "src/main/resources/" + configFile; + System.out.println("Configuration file set to: " + this.configFile); + } + + /** + * Executa o procedimento de arranque (Bootstrap) da simulação distribuída. + *

+ * A ordem de inicialização é crítica para evitar Race Conditions na conexão TCP: + *

    + *
  1. Workers (Interseções): Iniciam os ServerSockets.
  2. + *
  3. Sink (Exit Node): Prepara-se para receber métricas finais.
  4. + *
  5. Delay de Estabilização: Pausa de 1s para garantir que os sockets estão em LISTENING.
  6. + *
  7. Source (Coordinator): Inicia a geração de carga e conecta-se aos nós.
  8. + *
+ * * @throws IOException Se falhar o fork de algum processo. + */ + public void startSimulation() throws IOException { + if (!runningProcesses.isEmpty()) { + stopSimulation(); + } + + System.out.println("Starting simulation processes..."); + + // 1. Start Intersections (Cr1 - Cr5) + String[] intersectionIds = { "Cr1", "Cr2", "Cr3", "Cr4", "Cr5" }; + for (String id : intersectionIds) { + startProcess("sd.IntersectionProcess", id); + } + + // 2. Start Exit Node + startProcess("sd.ExitNodeProcess", null); + + // 3. Start Coordinator (Wait a bit for others to initialize) + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + startProcess("sd.coordinator.CoordinatorProcess", null); + + System.out.println("All simulation processes started."); + } + + /** + * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador. + *

+ * Como o Coordenador gere o relógio DES e a geração de eventos, a sua terminação + * (após o drain time) sinaliza o fim efetivo da simulação. + * * @return true se o Coordenador ainda estiver ativo (alive). + */ + public boolean isSimulationRunning() { + if (runningProcesses.isEmpty()) { + 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..."); + + for (Process process : runningProcesses) { + if (process.isAlive()) { + process.destroy(); // Try graceful termination first + } + } + + // Wait a bit and force kill if necessary + try { + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + for (Process process : runningProcesses) { + if (process.isAlive()) { + process.destroyForcibly(); + } + } + + runningProcesses.clear(); + System.out.println("All simulation processes stopped."); + } + + /** + * 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, configFile); + } else { + builder = new ProcessBuilder(javaBin, "-cp", classpath, className, configFile); + } + + // get the OS temp folder + // Linux: /tmp/ + // Windows: %AppData%\Local\Temp\ + String tempDir = System.getProperty("java.io.tmpdir"); + + String logName = className.substring(className.lastIndexOf('.') + 1) + (arg != null ? "-" + arg : "") + ".log"; + + // use the (File parent, String child) constructor to handle slash/backslash + // automatically + File logFile = new File(tempDir, logName); + + builder.redirectOutput(logFile); + builder.redirectError(logFile); + + Process process = builder.start(); + runningProcesses.add(process); + System.out.println("Started " + className + (arg != null ? " " + arg : "")); + // 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 new file mode 100644 index 0000000..abc4730 --- /dev/null +++ b/main/src/main/java/sd/dashboard/StatsMessage.java @@ -0,0 +1,77 @@ +package sd.dashboard; + +import sd.model.MessageType; +import sd.protocol.MessageProtocol; + +/** + * 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 { + + private static final long serialVersionUID = 1L; + + private final String sourceNode; + 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"; // 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; + } + + @Override + public String toString() { + 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 new file mode 100644 index 0000000..6f1915e --- /dev/null +++ b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java @@ -0,0 +1,158 @@ +package sd.dashboard; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import sd.model.VehicleType; + +/** + * Objeto de Transferência de Dados (DTO) otimizado para transporte de telemetria. + *

+ * Esta classe encapsula as métricas de desempenho enviadas pelos nós da simulação (Coordenador, + * Interseções, ExitNode) para o Dashboard. Foi desenhada para suportar atualizações parciais + * (Sparse Updates): + *

    + *
  • Campos globais inicializados com {@code -1} indicam "sem alteração" (no-op). O Dashboard + * deve ignorar estes campos e manter o valor acumulado anterior.
  • + *
  • Campos de interseção ({@code arrivals}, {@code departures}) representam deltas ou snapshots + * específicos do nó remetente.
  • + *
+ * Implementa {@link Serializable} para transmissão direta via Java Sockets. + * + +[Image of data transfer object pattern] + + */ +public class StatsUpdatePayload implements Serializable { + + private static final long serialVersionUID = 1L; + + // Global Metrics (Coordinator/ExitNode) + /** Total gerado. Valor -1 indica que este campo deve ser ignorado na atualização. */ + private int totalVehiclesGenerated = -1; + + /** Total completado. Valor -1 indica que este campo deve ser ignorado. */ + private int totalVehiclesCompleted = -1; + + /** Tempo total de sistema acumulado (ms). Valor -1 indica que deve ser ignorado. */ + private long totalSystemTime = -1; + + /** Tempo total de espera acumulado (ms). Valor -1 indica que deve ser ignorado. */ + private long totalWaitingTime = -1; + + // Intersection Metrics (Worker Nodes) + /** Número de veículos que entraram na interseção desde o último reporte. */ + private int intersectionArrivals = 0; + + /** Número de veículos que saíram da interseção desde o último reporte. */ + private int intersectionDepartures = 0; + + /** Snapshot do tamanho atual da fila na interseção. */ + private int intersectionQueueSize = 0; + + // Detailed Breakdowns + /** Contagem acumulada por tipo de veículo. */ + private Map vehicleTypeCounts; + + /** Tempos de espera acumulados por tipo de veículo. */ + private Map vehicleTypeWaitTimes; + + /** + * Inicializa o payload com os mapas vazios e contadores globais a -1 (estado neutro). + */ + public StatsUpdatePayload() { + this.vehicleTypeCounts = new HashMap<>(); + this.vehicleTypeWaitTimes = new HashMap<>(); + } + + public int getTotalVehiclesGenerated() { + return totalVehiclesGenerated; + } + + public int getTotalVehiclesCompleted() { + return totalVehiclesCompleted; + } + + public long getTotalSystemTime() { + return totalSystemTime; + } + + public long getTotalWaitingTime() { + return totalWaitingTime; + } + + public int getIntersectionArrivals() { + return intersectionArrivals; + } + + public int getIntersectionDepartures() { + return intersectionDepartures; + } + + public int getIntersectionQueueSize() { + return intersectionQueueSize; + } + + public Map getVehicleTypeCounts() { + return vehicleTypeCounts; + } + + public Map getVehicleTypeWaitTimes() { + return vehicleTypeWaitTimes; + } + + // Setters implementam Fluent Interface para construção encadeada + + public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) { + this.totalVehiclesGenerated = totalVehiclesGenerated; + return this; + } + + public StatsUpdatePayload setTotalVehiclesCompleted(int totalVehiclesCompleted) { + this.totalVehiclesCompleted = totalVehiclesCompleted; + return this; + } + + public StatsUpdatePayload setTotalSystemTime(long totalSystemTime) { + this.totalSystemTime = totalSystemTime; + return this; + } + + public StatsUpdatePayload setTotalWaitingTime(long totalWaitingTime) { + this.totalWaitingTime = totalWaitingTime; + return this; + } + + public StatsUpdatePayload setIntersectionArrivals(int intersectionArrivals) { + this.intersectionArrivals = intersectionArrivals; + return this; + } + + public StatsUpdatePayload setIntersectionDepartures(int intersectionDepartures) { + this.intersectionDepartures = intersectionDepartures; + return this; + } + + public StatsUpdatePayload setIntersectionQueueSize(int intersectionQueueSize) { + this.intersectionQueueSize = intersectionQueueSize; + return this; + } + + public StatsUpdatePayload setVehicleTypeCounts(Map vehicleTypeCounts) { + this.vehicleTypeCounts = vehicleTypeCounts; + return this; + } + + public StatsUpdatePayload setVehicleTypeWaitTimes(Map vehicleTypeWaitTimes) { + this.vehicleTypeWaitTimes = vehicleTypeWaitTimes; + return this; + } + + @Override + public String toString() { + return String.format("StatsUpdatePayload[generated=%d, completed=%d, arrivals=%d, departures=%d, queueSize=%d]", + totalVehiclesGenerated, totalVehiclesCompleted, intersectionArrivals, + intersectionDepartures, intersectionQueueSize); + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/des/DESEventType.java b/main/src/main/java/sd/des/DESEventType.java new file mode 100644 index 0000000..789d2e9 --- /dev/null +++ b/main/src/main/java/sd/des/DESEventType.java @@ -0,0 +1,39 @@ +package sd.des; + +/** + * Tipos de eventos discretos da simulação. + * + *

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

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

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

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

+ * + *

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

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

+ * Esta classe é a unidade fundamental de processamento. Numa arquitetura DES, o estado do sistema + * não muda continuamente, mas sim em instantes discretos definidos por estes eventos. + *

+ * Características principais: + *

    + *
  • Ordenação Temporal: Implementa {@link Comparable} para ser armazenado numa Fila de + * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.
  • + *
  • Distribuído: Implementa {@link Serializable} para permitir que eventos gerados num nó + * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).
  • + *
  • Polimórfico: Transporta um {@code payload} genérico, permitindo associar qualquer + * entidade (Veículo, Sinal, etc.) ao evento.
  • + *
+ */ +public class SimulationEvent implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + /** O instante virtual exato em que o evento deve ser processado. */ + private final double timestamp; + + /** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */ + private final DESEventType type; + + /** Dados contextuais associados (ex: o objeto Vehicle que chegou). */ + private final Object payload; + + /** + * O identificador do nó de destino onde o evento deve ser executado. + * (ex: "Cr1", "Coordinator", "ExitNode"). Se null, é um evento local. + */ + private final String location; + + /** + * Instancia um novo evento de simulação completo. + * + * @param timestamp Instante de execução (segundos virtuais). + * @param type Tipo enumerado do evento. + * @param payload Objeto de dados associado (pode ser null). + * @param location ID do processo alvo para execução distribuída. + */ + public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) { + this.timestamp = timestamp; + this.type = type; + this.payload = payload; + this.location = location; + } + + /** + * Construtor de conveniência para eventos locais (dentro do mesmo processo). + * Define {@code location} como null. + * + * @param timestamp Instante de execução. + * @param type Tipo do evento. + * @param payload Objeto de dados associado. + */ + public SimulationEvent(double timestamp, DESEventType type, Object payload) { + this(timestamp, type, payload, null); + } + + public double getTimestamp() { + return timestamp; + } + + public DESEventType getType() { + return type; + } + + public Object getPayload() { + return payload; + } + + public String getLocation() { + return location; + } + + /** + * Define a ordem natural de processamento na Fila de Prioridade. + *

+ * Lógica de Ordenação: + *

    + *
  1. Primária (Tempo): Eventos com menor timestamp ocorrem primeiro.
  2. + *
  3. Secundária (Determinismo): Em caso de empate temporal (simultaneidade), + * ordena alfabeticamente pelo nome do tipo. Isto garante que execuções repetidas + * da simulação produzam exatamente o mesmo resultado (determinismo estrito).
  4. + *
+ * + * @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. + *

+ * 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/SimulationEngine.java b/main/src/main/java/sd/engine/SimulationEngine.java deleted file mode 100644 index 484ae80..0000000 --- a/main/src/main/java/sd/engine/SimulationEngine.java +++ /dev/null @@ -1,628 +0,0 @@ -package sd.engine; - -import java.util.HashMap; -import java.util.Map; -import java.util.PriorityQueue; - -import sd.config.SimulationConfig; -import sd.model.Event; -import sd.model.EventType; -import sd.model.Intersection; -import sd.model.TrafficLight; -import sd.model.TrafficLightState; -import sd.model.Vehicle; -import sd.model.VehicleType; -import sd.util.StatisticsCollector; -import sd.util.VehicleGenerator; - -/** - * Core simulation engine using discrete event simulation (DES). - * * This class orchestrates the entire simulation. It maintains a - * {@link PriorityQueue} of {@link Event} objects, representing all - * scheduled future actions. The engine processes events in strict - * chronological order (based on their timestamp). - * * It manages the simulation's state, including: - * - The current simulation time ({@code currentTime}). - * - The collection of all {@link Intersection} objects. - * - The {@link VehicleGenerator} for creating new vehicles. - * - The {@link StatisticsCollector} for tracking metrics. - */ -public class SimulationEngine { - - /** - * Holds all simulation parameters loaded from the properties file. - */ - private final SimulationConfig config; - - /** - * The core of the discrete event simulation. Events are pulled from this - * queue in order of their timestamp. - */ - private final PriorityQueue eventQueue; - - /** - * A map storing all intersections in the simulation, keyed by their ID (e.g., "Cr1"). - */ - private final Map intersections; - - /** - * Responsible for creating new vehicles according to the configured arrival model. - */ - private final VehicleGenerator vehicleGenerator; - - /** - * Collects and calculates statistics throughout the simulation. - */ - private final StatisticsCollector statisticsCollector; - - /** - * The current time in the simulation (in virtual seconds). - * This time advances based on the timestamp of the event being processed. - */ - private double currentTime; - - /** - * A simple counter to generate unique IDs for vehicles. - */ - private int vehicleCounter; - - /** - * Constructs a new SimulationEngine. - * - * @param config The {@link SimulationConfig} object containing all - * simulation parameters. - */ - public SimulationEngine(SimulationConfig config) { - this.config = config; - this.eventQueue = new PriorityQueue<>(); - this.intersections = new HashMap<>(); - this.vehicleGenerator = new VehicleGenerator(config); - this.statisticsCollector = new StatisticsCollector(config); - this.currentTime = 0.0; - this.vehicleCounter = 0; - } - - /** - * Initializes the simulation. This involves: - * 1. Creating all {@link Intersection} and {@link TrafficLight} objects. - * 2. Configuring the routing logic between intersections. - * 3. Scheduling the initial events (first traffic light changes, - * first vehicle generation, and periodic statistics updates). - */ - public void initialize() { - System.out.println("Initializing simulation..."); - - setupIntersections(); - setupRouting(); - - // Schedule initial events to "bootstrap" the simulation - scheduleTrafficLightEvents(); - scheduleNextVehicleGeneration(0.0); - scheduleStatisticsUpdates(); - - System.out.println("Simulation initialized with " + intersections.size() + " intersections"); - } - - /** - * Creates all intersections defined in the configuration - * and adds their corresponding traffic lights. - */ - private void setupIntersections() { - String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"}; - // Note: "North" is commented out, so it won't be created. - String[] directions = {/*"North",*/ "South", "East", "West"}; - - for (String id : intersectionIds) { - Intersection intersection = new Intersection(id); - - // Add traffic lights for each configured direction - for (String direction : directions) { - double greenTime = config.getTrafficLightGreenTime(id, direction); - double redTime = config.getTrafficLightRedTime(id, direction); - - TrafficLight light = new TrafficLight( - id + "-" + direction, - direction, - greenTime, - redTime - ); - - intersection.addTrafficLight(light); - } - - intersections.put(id, intersection); - } - } - - /** - * Configures how vehicles should be routed between intersections. - * This hardcoded logic defines the "map" of the city. - * * For example, `intersections.get("Cr1").configureRoute("Cr2", "East");` means - * "at intersection Cr1, any vehicle whose *next* destination is Cr2 - * should be sent to the 'East' traffic light queue." - */ - private void setupRouting() { - // Cr1 routing - intersections.get("Cr1").configureRoute("Cr2", "East"); - intersections.get("Cr1").configureRoute("Cr4", "South"); - - // Cr2 routing - intersections.get("Cr2").configureRoute("Cr1", "West"); - intersections.get("Cr2").configureRoute("Cr3", "East"); - intersections.get("Cr2").configureRoute("Cr5", "South"); - - // Cr3 routing - intersections.get("Cr3").configureRoute("Cr2", "West"); - intersections.get("Cr3").configureRoute("S", "South"); // "S" is the exit - - // Cr4 routing - //intersections.get("Cr4").configureRoute("Cr1", "North"); - intersections.get("Cr4").configureRoute("Cr5", "East"); - - // Cr5 routing - //intersections.get("Cr5").configureRoute("Cr2", "North"); - //intersections.get("Cr5").configureRoute("Cr4", "West"); - intersections.get("Cr5").configureRoute("S", "East"); // "S" is the exit - } - - /** - * Schedules the initial {@link EventType#TRAFFIC_LIGHT_CHANGE} event - * for every traffic light in the simulation. - * A small random delay is added to "stagger" the lights, preventing - * all of them from changing at the exact same time at t=0. - */ - private void scheduleTrafficLightEvents() { - for (Intersection intersection : intersections.values()) { - for (TrafficLight light : intersection.getTrafficLights()) { - // Start with lights in RED state, schedule first GREEN change - // Stagger the start times slightly to avoid all lights changing at once - double staggerDelay = Math.random() * 1.5; - scheduleTrafficLightChange(light, intersection.getId(), staggerDelay); - } - } - } - - /** - * Creates and schedules a new {@link EventType#TRAFFIC_LIGHT_CHANGE} event. - * The event is scheduled to occur at {@code currentTime + delay}. - * - * @param light The {@link TrafficLight} that will change state. - * @param intersectionId The ID of the intersection where the light is located. - * @param delay The time (in seconds) from {@code currentTime} when the change should occur. - */ - private void scheduleTrafficLightChange(TrafficLight light, String intersectionId, double delay) { - double changeTime = currentTime + delay; - Event event = new Event(changeTime, EventType.TRAFFIC_LIGHT_CHANGE, light, intersectionId); - eventQueue.offer(event); - } - - /** - * Schedules the next {@link EventType#VEHICLE_GENERATION} event. - * The time of the next arrival is determined by the {@link VehicleGenerator}. - * - * @param baseTime The time from which to calculate the next arrival (usually {@code currentTime}). - */ - private void scheduleNextVehicleGeneration(double baseTime) { - // Get the absolute time for the next arrival. - double nextArrivalTime = vehicleGenerator.getNextArrivalTime(baseTime); - - // Only schedule the event if it's within the simulation's total duration. - if (nextArrivalTime < config.getSimulationDuration()) { - Event event = new Event(nextArrivalTime, EventType.VEHICLE_GENERATION, null, null); - eventQueue.offer(event); - } - } - - /** - * Schedules all periodic {@link EventType#STATISTICS_UPDATE} events - * for the entire duration of the simulation. - */ - private void scheduleStatisticsUpdates() { - double interval = config.getStatisticsUpdateInterval(); - double duration = config.getSimulationDuration(); - - for (double time = interval; time < duration; time += interval) { - Event event = new Event(time, EventType.STATISTICS_UPDATE, null, null); - eventQueue.offer(event); - } - } - - /** - * Runs the main simulation loop. - * The loop continues as long as there are events in the queue and - * the {@code currentTime} is less than the total simulation duration. - * * In each iteration, it: - * 1. Polls the next event from the {@link #eventQueue}. - * 2. Advances {@link #currentTime} to the event's timestamp. - * 3. Calls {@link #processEvent(Event)} to handle the event. - * * After the loop, it prints the final statistics. - */ - public void run() { - System.out.println("Starting simulation..."); - double duration = config.getSimulationDuration(); - - while (!eventQueue.isEmpty() && currentTime < duration) { - // Get the next event in chronological order - Event event = eventQueue.poll(); - - // Advance simulation time to this event's time - currentTime = event.getTimestamp(); - - // Process the event - processEvent(event); - } - - System.out.println("\nSimulation completed at t=" + String.format("%.2f", currentTime) + "s"); - printFinalStatistics(); - } - - /** - * Main event processing logic. - * Delegates the event to the appropriate handler method based on its {@link EventType}. - * - * @param event The {@link Event} to be processed. - */ - private void processEvent(Event event) { - switch (event.getType()) { - case VEHICLE_GENERATION -> handleVehicleGeneration(); - - case VEHICLE_ARRIVAL -> handleVehicleArrival(event); - - case TRAFFIC_LIGHT_CHANGE -> handleTrafficLightChange(event); - - case CROSSING_START -> handleCrossingStart(event); - - case CROSSING_END -> handleCrossingEnd(event); - - case STATISTICS_UPDATE -> handleStatisticsUpdate(); - - default -> System.err.println("Unknown event type: " + event.getType()); - } - } - - /** - * Handles {@link EventType#VEHICLE_GENERATION}. - * 1. Creates a new {@link Vehicle} using the {@link #vehicleGenerator}. - * 2. Records the generation event with the {@link #statisticsCollector}. - * 3. Schedules a {@link EventType#VEHICLE_ARRIVAL} event for the vehicle - * at its first destination intersection. - * 4. Schedules the *next* {@link EventType#VEHICLE_GENERATION} event. - * (Note: This line is commented out in the original, which might be a bug, - * as it implies only one vehicle is ever generated. It should likely be active.) - */ - private void handleVehicleGeneration() { - Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime); - - System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n", - currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute()); - - // Register with statistics collector - statisticsCollector.recordVehicleGeneration(vehicle, currentTime); - - // Schedule arrival at first intersection - String firstIntersection = vehicle.getCurrentDestination(); - if (firstIntersection != null && !firstIntersection.equals("S")) { - // Assume minimal travel time to first intersection (e.g., 1-3 seconds) - double arrivalTime = currentTime + 1.0 + Math.random() * 2.0; - Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, firstIntersection); - eventQueue.offer(arrivalEvent); - } - - // Schedule next vehicle generation - // This was commented out in the original file. - // For a continuous simulation, it should be enabled: - scheduleNextVehicleGeneration(currentTime); - } - - /** - * Handles {@link EventType#VEHICLE_ARRIVAL} at an intersection. - * 1. Records the arrival for statistics. - * 2. Advances the vehicle's internal route planner to its *next* destination. - * 3. If the next destination is the exit ("S") or null, - * the vehicle exits the system via {@link #handleVehicleExit(Vehicle)}. - * 4. Otherwise, the vehicle is placed in the correct queue at the - * current intersection using {@link Intersection#receiveVehicle(Vehicle)}. - * 5. Attempts to process the vehicle immediately if its light is green. - * - * @param event The arrival event, containing the {@link Vehicle} and intersection ID. - */ - private void handleVehicleArrival(Event event) { - Vehicle vehicle = (Vehicle) event.getData(); - String intersectionId = event.getLocation(); - - Intersection intersection = intersections.get(intersectionId); - if (intersection == null) { - System.err.println("Unknown intersection: " + intersectionId); - return; - } - - System.out.printf("[t=%.2f] Vehicle %s arrived at %s%n", - currentTime, vehicle.getId(), intersectionId); - - // Record arrival time (used to calculate waiting time later) - statisticsCollector.recordVehicleArrival(vehicle, intersectionId, currentTime); - - // Advance the vehicle's route to the *next* stop - // (it has now arrived at its *current* destination) - boolean hasNext = vehicle.advanceRoute(); - - if (!hasNext) { - // This was the last stop - handleVehicleExit(vehicle); - return; - } - - String nextDestination = vehicle.getCurrentDestination(); - if (nextDestination == null || "S".equals(nextDestination)) { - // Next stop is the exit - handleVehicleExit(vehicle); - return; - } - - // Add vehicle to the appropriate traffic light queue based on its next destination - intersection.receiveVehicle(vehicle); - - // Try to process the vehicle immediately if its light is already green - tryProcessVehicle(vehicle, intersection); - } - - /** - * Checks if a newly arrived vehicle (or a vehicle in a queue - * that just turned green) can start crossing. - * - * @param vehicle The vehicle to process. - * @param intersection The intersection where the vehicle is. - */ - private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { //FIXME - // Find the direction (and light) this vehicle is queued at - // This logic is a bit flawed: it just finds the *first* non-empty queue - // A better approach would be to get the light from the vehicle's route - String direction = intersection.getTrafficLights().stream() - .filter(tl -> tl.getQueueSize() > 0) - .map(TrafficLight::getDirection) - .findFirst() - .orElse(null); - - if (direction != null) { - TrafficLight light = intersection.getTrafficLight(direction); - // If the light is green and it's the correct one... - if (light != null && light.getState() == TrafficLightState.GREEN) { - // ...remove the vehicle from the queue (if it's at the front) - Vehicle v = light.removeVehicle(); - if (v != null) { - // ...and schedule its crossing. - scheduleCrossing(v, intersection); - } - } - } - } - - /** - * Schedules the crossing for a vehicle that has just been dequeued - * from a green light. - * 1. Calculates and records the vehicle's waiting time. - * 2. Schedules an immediate {@link EventType#CROSSING_START} event. - * - * @param vehicle The {@link Vehicle} that is crossing. - * @param intersection The {@link Intersection} it is crossing. - */ - private void scheduleCrossing(Vehicle vehicle, Intersection intersection) { - // Calculate time spent waiting at the red light - double waitTime = currentTime - statisticsCollector.getArrivalTime(vehicle); - vehicle.addWaitingTime(waitTime); - - // Schedule crossing start event *now* - Event crossingStart = new Event(currentTime, EventType.CROSSING_START, vehicle, intersection.getId()); - processEvent(crossingStart); // Process immediately - } - - /** - * Handles {@link EventType#CROSSING_START}. - * 1. Determines the crossing time based on vehicle type. - * 2. Schedules a {@link EventType#CROSSING_END} event to occur - * at {@code currentTime + crossingTime}. - * - * @param event The crossing start event. - */ - private void handleCrossingStart(Event event) { - Vehicle vehicle = (Vehicle) event.getData(); - String intersectionId = event.getLocation(); - - double crossingTime = getCrossingTime(vehicle.getType()); - - System.out.printf("[t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n", - currentTime, vehicle.getId(), intersectionId, crossingTime); - - // Schedule the *end* of the crossing - double endTime = currentTime + crossingTime; - Event crossingEnd = new Event(endTime, EventType.CROSSING_END, vehicle, intersectionId); - eventQueue.offer(crossingEnd); - } - - /** - * Handles {@link EventType#CROSSING_END}. - * 1. Updates intersection and vehicle statistics. - * 2. Checks the vehicle's *next* destination. - * 3. If the next destination is the exit ("S"), call {@link #handleVehicleExit(Vehicle)}. - * 4. Otherwise, schedule a {@link EventType#VEHICLE_ARRIVAL} event at the - * *next* intersection, after some travel time. - * - * @param event The crossing end event. - */ - private void handleCrossingEnd(Event event) { - Vehicle vehicle = (Vehicle) event.getData(); - String intersectionId = event.getLocation(); - - // Update stats - Intersection intersection = intersections.get(intersectionId); - if (intersection != null) { - intersection.incrementVehiclesSent(); - } - - double crossingTime = getCrossingTime(vehicle.getType()); - vehicle.addCrossingTime(crossingTime); - - System.out.printf("[t=%.2f] Vehicle %s finished crossing at %s%n", - currentTime, vehicle.getId(), intersectionId); - - // Decide what to do next - String nextDest = vehicle.getCurrentDestination(); - if (nextDest != null && !nextDest.equals("S")) { - // Route to the *next* intersection - // Assume 5-10 seconds travel time between intersections - double travelTime = 5.0 + Math.random() * 5.0; - double arrivalTime = currentTime + travelTime; - Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, nextDest); - eventQueue.offer(arrivalEvent); - } else { - // Reached the exit - handleVehicleExit(vehicle); - } - } - - /** - * Handles a vehicle exiting the simulation. - * Records final statistics for the vehicle. - * - * @param vehicle The {@link Vehicle} that has completed its route. - */ - private void handleVehicleExit(Vehicle vehicle) { - System.out.printf("[t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n", - currentTime, vehicle.getId(), - vehicle.getTotalWaitingTime(), - vehicle.getTotalTravelTime(currentTime)); - - // Record the exit for final statistics calculation - statisticsCollector.recordVehicleExit(vehicle, currentTime); - } - - /** - * Handles {@link EventType#TRAFFIC_LIGHT_CHANGE}. - * 1. Toggles the light's state (RED to GREEN or GREEN to RED). - * 2. If the light just turned GREEN, call {@link #processGreenLight(TrafficLight, Intersection)} - * to process any waiting vehicles. - * 3. Schedules the *next* state change for this light based on its - * green/red time duration. - * - * @param event The light change event. - */ - private void handleTrafficLightChange(Event event) { - TrafficLight light = (TrafficLight) event.getData(); - String intersectionId = event.getLocation(); - - // Toggle state - TrafficLightState newState = (light.getState() == TrafficLightState.RED) - ? TrafficLightState.GREEN - : TrafficLightState.RED; - - light.changeState(newState); - - System.out.printf("[t=%.2f] Traffic light %s changed to %s%n", - currentTime, light.getId(), newState); - - // If changed to GREEN, process waiting vehicles - if (newState == TrafficLightState.GREEN) { - Intersection intersection = intersections.get(intersectionId); - if (intersection != null) { - processGreenLight(light, intersection); - } - } - - // Schedule the *next* state change for this same light - double nextChangeDelay = (newState == TrafficLightState.GREEN) - ? light.getGreenTime() - : light.getRedTime(); - - scheduleTrafficLightChange(light, intersectionId, nextChangeDelay); - } - - /** - * Processes vehicles when a light turns green. - * It loops as long as the light is green and there are vehicles in the queue, - * dequeuing one vehicle at a time and scheduling its crossing. - * * *Note*: This is a simplified model. A real simulation would - * account for the *time* it takes each vehicle to cross, processing - * one vehicle every {@code crossingTime} seconds. This implementation - * processes the entire queue "instantaneously" at the moment - * the light turns green. - * - * @param light The {@link TrafficLight} that just turned green. - * @param intersection The {@link Intersection} where the light is. - */ - private void processGreenLight(TrafficLight light, Intersection intersection) { - // While the light is green and vehicles are waiting... - while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { - Vehicle vehicle = light.removeVehicle(); - if (vehicle != null) { - // Dequeue one vehicle and schedule its crossing - scheduleCrossing(vehicle, intersection); - } - } - } - - /** - * Handles {@link EventType#STATISTICS_UPDATE}. - * Calls the {@link StatisticsCollector} to print the current - * state of the simulation (queue sizes, averages, etc.). - */ - private void handleStatisticsUpdate() { - System.out.printf("\n=== Statistics at t=%.2f ===%n", currentTime); - statisticsCollector.printCurrentStatistics(intersections, currentTime); - System.out.println(); - } - - /** - * Utility method to get the configured crossing time for a given {@link VehicleType}. - * - * @param type The type of vehicle. - * @return The crossing time in seconds. - */ - private double getCrossingTime(VehicleType type) { - return switch (type) { - case BIKE -> config.getBikeVehicleCrossingTime(); - case LIGHT -> config.getLightVehicleCrossingTime(); - case HEAVY -> config.getHeavyVehicleCrossingTime(); - default -> 2.0; - }; // Default fallback - } - - /** - * Prints the final summary of statistics at the end of the simulation. - */ - private void printFinalStatistics() { - System.out.println("\n" + "=".repeat(60)); - System.out.println("FINAL SIMULATION STATISTICS"); - System.out.println("=".repeat(60)); - - statisticsCollector.printFinalStatistics(intersections, currentTime); - - System.out.println("=".repeat(60)); - } - - // --- Public Getters --- - - /** - * Gets the current simulation time. - * @return The time in virtual seconds. - */ - public double getCurrentTime() { - return currentTime; - } - - /** - * Gets a map of all intersections in the simulation. - * Returns a copy to prevent external modification. - * @return A {@link Map} of intersection IDs to {@link Intersection} objects. - */ - public Map getIntersections() { - return new HashMap<>(intersections); - } - - /** - * Gets the statistics collector instance. - * @return The {@link StatisticsCollector}. - */ - public StatisticsCollector getStatisticsCollector() { - return statisticsCollector; - } -} \ No newline at end of file diff --git a/main/src/main/java/sd/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: + *

    + *
  • Non-blocking writes (para a thread chamadora, na maioria dos casos).
  • + *
  • Ordering cronológico aproximado (FIFO na fila).
  • + *
  • Graceful Shutdown (flush de logs pendentes ao terminar).
  • + *
+ */ +public class EventLogger { + + 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 logQueue; + + /** Thread dedicada (Consumer) para escrita em ficheiro. */ + private final Thread writerThread; + + private final AtomicBoolean running; + private final SimpleDateFormat timestampFormat; + private final long simulationStartMillis; + + /** + * Inicializa o sistema de logs. + * Abre o ficheiro, escreve o cabeçalho e inicia a thread consumidora. + * + * @param logFilePath Caminho relativo ou absoluto do ficheiro de log. + * @throws IOException Se não for possível criar ou escrever no ficheiro. + */ + private EventLogger(String logFilePath) throws IOException { + // Auto-flush ativado para garantir persistência, mas gerido pelo buffer do BufferedWriter + this.writer = new PrintWriter(new BufferedWriter(new FileWriter(logFilePath, false)), true); + this.logQueue = new LinkedBlockingQueue<>(10000); // Backpressure: limita a 10k eventos pendentes + this.running = new AtomicBoolean(true); + this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + this.simulationStartMillis = System.currentTimeMillis(); + + // Header inicial do log + writer.println("=".repeat(80)); + writer.println("SIMULATION EVENT LOG"); + writer.println("Started: " + timestampFormat.format(new Date())); + writer.println("=".repeat(80)); + writer.println(); + writer.printf("%-23s | %-8s | %-20s | %-15s | %s\n", + "TIMESTAMP", "REL_TIME", "EVENT_TYPE", "COMPONENT", "DESCRIPTION"); + writer.println("-".repeat(80)); + writer.flush(); + + this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer"); + this.writerThread.setDaemon(true); // Permite que a JVM termine se apenas esta thread sobrar + this.writerThread.start(); + } + + /** + * Obtém a instância única do logger (Lazy Initialization). + * Se não existir, cria uma predefinida em "logs/simulation-events.log". + * + * @return A instância singleton. + */ + public static EventLogger getInstance() { + if (instance == null) { + synchronized (instanceLock) { + if (instance == null) { + try { + String logFile = "logs/simulation-events.log"; + java.nio.file.Files.createDirectories( + java.nio.file.Paths.get("logs")); + instance = new EventLogger(logFile); + } catch (IOException e) { + System.err.println("Failed to initialize EventLogger: " + e.getMessage()); + e.printStackTrace(); + } + } + } + } + return instance; + } + + /** + * Reinicializa o logger com um ficheiro específico. + * Útil para testes ou configurações personalizadas. + */ + public static void initialize(String logFilePath) throws IOException { + synchronized (instanceLock) { + if (instance != null) { + instance.shutdown(); + } + instance = new EventLogger(logFilePath); + } + } + + /** + * Regista um evento genérico. + * Esta operação é não-bloqueante (retorna imediatamente após colocar na fila), + * exceto se a fila estiver cheia (backpressure). + * + * @param eventType Categoria do evento. + * @param component Nome do componente (ex: "Coordinator", "IntersectionProcess"). + * @param description Detalhes do evento. + */ + public void log(EventType eventType, String component, String description) { + if (!running.get()) return; + + LogEntry entry = new LogEntry( + System.currentTimeMillis(), + eventType, + component, + description + ); + + // Non-blocking offer - if queue is full, drop oldest or warn + if (!logQueue.offer(entry)) { + // Queue full - this shouldn't happen with 10k buffer, but handle gracefully + System.err.println("EventLogger queue full - dropping event: " + eventType); + } + } + + /** + * Regista um evento associado a um veículo específico (Helper method). + */ + public void logVehicle(EventType eventType, String component, String vehicleId, String description) { + log(eventType, component, "[" + vehicleId + "] " + description); + } + + /** + * Regista um erro ou exceção com formatação apropriada. + */ + public void logError(String component, String description, Exception e) { + String fullDescription = description + (e != null ? ": " + e.getMessage() : ""); + log(EventType.ERROR, component, fullDescription); + } + + /** + * Lógica da thread consumidora (Worker Thread). + * Retira eventos da fila e escreve no disco continuamente. + */ + private void processLogQueue() { + while (running.get() || !logQueue.isEmpty()) { + try { + // Poll com timeout para permitir verificar a flag 'running' periodicamente + LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); + if (entry != null) { + writeEntry(entry); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Flush final: garantir que eventos restantes na fila são escritos antes de morrer + while (!logQueue.isEmpty()) { + LogEntry entry = logQueue.poll(); + if (entry != null) { + writeEntry(entry); + } + } + } + + /** + * Formata e escreve uma entrada de log no PrintWriter. + */ + private void writeEntry(LogEntry entry) { + String timestamp = timestampFormat.format(new Date(entry.timestampMillis)); + double relativeTime = (entry.timestampMillis - simulationStartMillis) / 1000.0; + + writer.printf("%-23s | %8.3fs | %-20s | %-15s | %s\n", + timestamp, + relativeTime, + entry.eventType.toString(), + truncate(entry.component, 15), + entry.description + ); + + // Flush periódico inteligente: se a carga for baixa, garante que vemos logs em tempo real + if (logQueue.size() < 10) { + writer.flush(); + } + } + + private String truncate(String str, int maxLength) { + if (str == null) return ""; + return str.length() <= maxLength ? str : str.substring(0, maxLength); + } + + /** + * Encerra o logger de forma segura. + * Desativa a aceitação de novos eventos, aguarda que a fila esvazie (flush) + * e fecha o ficheiro. + */ + public void shutdown() { + if (!running.compareAndSet(true, false)) { + return; // Já encerrado + } + + try { + // Wait for writer thread to finish flushing + writerThread.join(5000); // Wait up to 5 seconds + + // Write footer + writer.println(); + writer.println("-".repeat(80)); + writer.println("SIMULATION ENDED"); + writer.println("Ended: " + timestampFormat.format(new Date())); + writer.println("=".repeat(80)); + + writer.close(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * DTO interno imutável para armazenar dados do evento na fila. + */ + private static class LogEntry { + final long timestampMillis; + final EventType eventType; + final String component; + final String description; + + LogEntry(long timestampMillis, EventType eventType, String component, String description) { + this.timestampMillis = timestampMillis; + this.eventType = eventType; + this.component = component; + this.description = description; + } + } +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventType.java b/main/src/main/java/sd/logging/EventType.java new file mode 100644 index 0000000..910ae94 --- /dev/null +++ b/main/src/main/java/sd/logging/EventType.java @@ -0,0 +1,60 @@ +package sd.logging; + +/** + * Taxonomia oficial de eventos para o subsistema de logging centralizado. + *

+ * Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo: + *

    + *
  • Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).
  • + *
  • Análise estatística post-mortem (parsear logs para calcular latências).
  • + *
  • Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).
  • + *
+ */ +public enum EventType { + // --- Ciclo de Vida do Veículo --- + VEHICLE_GENERATED("Vehicle Generated"), + VEHICLE_ARRIVED("Vehicle Arrived"), + VEHICLE_QUEUED("Vehicle Queued"), + VEHICLE_DEPARTED("Vehicle Departed"), + VEHICLE_EXITED("Vehicle Exited"), + + // --- Controlo de Semáforos e Exclusão Mútua --- + LIGHT_CHANGED_GREEN("Light Changed to Green"), + LIGHT_CHANGED_RED("Light Changed to Red"), + LIGHT_REQUEST_GREEN("Light Requested Green"), + LIGHT_RELEASE_GREEN("Light Released Green"), + + // --- Ciclo de Vida da Simulação/Processos --- + SIMULATION_STARTED("Simulation Started"), + SIMULATION_STOPPED("Simulation Stopped"), + PROCESS_STARTED("Process Started"), + PROCESS_STOPPED("Process Stopped"), + + // --- Configuração e Telemetria --- + STATS_UPDATE("Statistics Update"), + CONFIG_CHANGED("Configuration Changed"), + + // --- Camada de Rede (TCP/Sockets) --- + CONNECTION_ESTABLISHED("Connection Established"), + CONNECTION_LOST("Connection Lost"), + MESSAGE_SENT("Message Sent"), + MESSAGE_RECEIVED("Message Received"), + + // --- Tratamento de Exceções --- + ERROR("Error"); + + private final String displayName; + + 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. + *

+ * 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: + *

    + *
  • Análise forense de percursos individuais.
  • + *
  • Validação de tempos de espera e travessia por nó.
  • + *
  • Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).
  • + *
+ */ +public class VehicleTracer { + + private static VehicleTracer instance; + private static final Object instanceLock = new Object(); + + /** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */ + private final Map trackedVehicles; + + private final SimpleDateFormat timestampFormat; + private final long simulationStartMillis; + private final String traceDirectory; + + /** + * Inicializa o tracer e prepara o diretório de saída. + * + * @param traceDirectory Caminho para armazenamento dos ficheiros .trace. + */ + private VehicleTracer(String traceDirectory) { + this.trackedVehicles = new ConcurrentHashMap<>(); + this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + this.simulationStartMillis = System.currentTimeMillis(); + this.traceDirectory = traceDirectory; + + try { + java.nio.file.Files.createDirectories(java.nio.file.Paths.get(traceDirectory)); + } catch (IOException e) { + System.err.println("Failed to create trace directory: " + e.getMessage()); + } + } + + /** + * Obtém a instância única do tracer (Singleton). + * @return A instância global. + */ + public static VehicleTracer getInstance() { + if (instance == null) { + synchronized (instanceLock) { + if (instance == null) { + instance = new VehicleTracer("logs/traces"); + } + } + } + return instance; + } + + /** + * Reinicializa o tracer com um diretório personalizado. + * Útil para isolar logs de diferentes execuções em lote. + */ + public static void initialize(String traceDirectory) { + synchronized (instanceLock) { + if (instance != null) { + instance.shutdown(); + } + instance = new VehicleTracer(traceDirectory); + } + } + + /** + * Inicia a sessão de rastreio para um veículo específico. + * Cria o ficheiro {@code logs/traces/vehicle-{id}.trace} e escreve o cabeçalho. + * + * @param vehicleId O identificador único do veículo. + */ + public void startTracking(String vehicleId) { + if (trackedVehicles.containsKey(vehicleId)) { + return; // Já está a ser rastreado + } + + VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory); + trackedVehicles.put(vehicleId, trace); + + trace.logEvent("TRACKING_STARTED", "", "Started tracking vehicle " + vehicleId); + } + + /** + * Encerra a sessão de rastreio, fecha o descritor de ficheiro e remove da memória. + */ + public void stopTracking(String vehicleId) { + VehicleTrace trace = trackedVehicles.remove(vehicleId); + if (trace != null) { + trace.logEvent("TRACKING_STOPPED", "", "Stopped tracking vehicle " + vehicleId); + trace.close(); + } + } + + /** + * Verifica se um veículo está atualmente sob auditoria. + */ + public boolean isTracking(String vehicleId) { + return trackedVehicles.containsKey(vehicleId); + } + + /** + * Regista o evento de instanciação do veículo pelo Coordenador. + */ + public void logGenerated(Vehicle vehicle) { + if (!isTracking(vehicle.getId())) + return; + + VehicleTrace trace = trackedVehicles.get(vehicle.getId()); + if (trace != null) { + trace.logEvent("GENERATED", "Coordinator", + String.format("Type: %s, Entry Time: %.2fs, Route: %s", + vehicle.getType(), vehicle.getEntryTime(), vehicle.getRoute())); + } + } + + /** + * Regista a chegada física do veículo à zona de deteção de uma interseção. + */ + public void logArrival(String vehicleId, String intersection, double simulationTime) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("ARRIVED", intersection, + String.format("Arrived at %s (sim time: %.2fs)", intersection, simulationTime)); + } + } + + /** + * Regista a entrada do veículo na estrutura de fila de um semáforo. + */ + public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("QUEUED", intersection, + String.format("Queued at %s-%s (position: %d)", intersection, direction, queuePosition)); + } + } + + /** + * Regista o início da espera ativa (veículo parado no Vermelho). + */ + public void logWaitingStart(String vehicleId, String intersection, String direction) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("WAITING_START", intersection, + String.format("Started waiting at %s-%s (light is RED)", intersection, direction)); + } + } + + /** + * Regista o fim da espera (Sinal Verde). + * @param waitTime Duração total da paragem nesta instância. + */ + public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("WAITING_END", intersection, + String.format("Finished waiting at %s-%s (waited %.2fs)", intersection, direction, waitTime)); + } + } + + /** + * Regista o início da travessia da interseção (ocupação da zona crítica). + */ + public void logCrossingStart(String vehicleId, String intersection, String direction) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("CROSSING_START", intersection, + String.format("Started crossing %s-%s (light is GREEN)", intersection, direction)); + } + } + + /** + * Regista a libertação da zona crítica da interseção. + */ + public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("CROSSING_END", intersection, + String.format("Finished crossing %s (took %.2fs)", intersection, crossingTime)); + } + } + + /** + * Regista a partida da interseção em direção ao próximo nó. + */ + public void logDeparture(String vehicleId, String intersection, String nextDestination) { + if (!isTracking(vehicleId)) + return; + + VehicleTrace trace = trackedVehicles.get(vehicleId); + if (trace != null) { + trace.logEvent("DEPARTED", intersection, + String.format("Departed from %s toward %s", intersection, nextDestination)); + } + } + + /** + * Regista a saída do sistema (no Exit Node). + *

+ * Este método também desencadeia a escrita do Sumário de Viagem no final do log + * e fecha o ficheiro automaticamente. + */ + public void logExit(Vehicle vehicle, double systemTime) { + if (!isTracking(vehicle.getId())) + 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/Event.java b/main/src/main/java/sd/model/Event.java deleted file mode 100644 index c25d734..0000000 --- a/main/src/main/java/sd/model/Event.java +++ /dev/null @@ -1,131 +0,0 @@ -package sd.model; - -import java.io.Serializable; - -/** - * Represents a single event in the discrete event simulation. - * * An Event is the fundamental unit of action in the simulation. It contains: - * - A {@code timestamp} (when the event should occur). - * - A {@link EventType} (what kind of event it is). - * - Associated {@code data} (e.g., the {@link Vehicle} or {@link TrafficLight} involved). - * - An optional {@code location} (e.g., the ID of the {@link Intersection}). - * * Events are {@link Comparable}, allowing them to be sorted in a - * {@link java.util.PriorityQueue}. The primary sorting key is the - * {@code timestamp}. If timestamps are equal, {@code EventType} is used - * as a tie-breaker to ensure a consistent, deterministic order. - * * Implements {@link Serializable} so events could (in theory) be sent - * across a network in a distributed simulation. - */ -public class Event implements Comparable, Serializable { - private static final long serialVersionUID = 1L; - - /** - * The simulation time (in seconds) when this event is scheduled to occur. - */ - private final double timestamp; - - /** - * The type of event (e.g., VEHICLE_ARRIVAL, TRAFFIC_LIGHT_CHANGE). - */ - private final EventType type; - - /** - * The data payload associated with this event. - * This could be a {@link Vehicle}, {@link TrafficLight}, or null. - */ - private final Object data; - - /** - * The ID of the location where the event occurs (e.g., "Cr1"). - * Can be null if the event is not location-specific (like VEHICLE_GENERATION). - */ - private final String location; - - /** - * Constructs a new Event. - * - * @param timestamp The simulation time when the event occurs. - * @param type The {@link EventType} of the event. - * @param data The associated data (e.g., a Vehicle object). - * @param location The ID of the location (e.g., an Intersection ID). - */ - public Event(double timestamp, EventType type, Object data, String location) { - this.timestamp = timestamp; - this.type = type; - this.data = data; - this.location = location; - } - - /** - * Convenience constructor for an Event without a specific location. - * - * @param timestamp The simulation time when the event occurs. - * @param type The {@link EventType} of the event. - * @param data The associated data (e.g., a Vehicle object). - */ - public Event(double timestamp, EventType type, Object data) { - this(timestamp, type, data, null); - } - - /** - * Compares this event to another event for ordering. - * * Events are ordered primarily by {@link #timestamp} (ascending). - * If timestamps are identical, they are ordered by {@link #type} (alphabetical) - * to provide a stable, deterministic tie-breaking mechanism. - * - * @param other The other Event to compare against. - * @return A negative integer if this event comes before {@code other}, - * zero if they are "equal" in sorting (though this is rare), - * or a positive integer if this event comes after {@code other}. - */ - @Override - public int compareTo(Event other) { - // Primary sort: timestamp (earlier events come first) - int cmp = Double.compare(this.timestamp, other.timestamp); - if (cmp == 0) { - // Tie-breaker: event type (ensures deterministic order) - return this.type.compareTo(other.type); - } - return cmp; - } - - // --- Getters --- - - /** - * @return The simulation time when the event occurs. - */ - public double getTimestamp() { - return timestamp; - } - - /** - * @return The {@link EventType} of the event. - */ - public EventType getType() { - return type; - } - - /** - * @return The data payload (e.g., {@link Vehicle}, {@link TrafficLight}). - * The caller must cast this to the expected type. - */ - public Object getData() { - return data; - } - - /** - * @return The location ID (e.g., "Cr1"), or null if not applicable. - */ - public String getLocation() { - return location; - } - - /** - * @return A string representation of the event for logging. - */ - @Override - public String toString() { - return String.format("Event{t=%.2f, type=%s, loc=%s}", - timestamp, type, location); - } -} \ No newline at end of file diff --git a/main/src/main/java/sd/model/EventType.java b/main/src/main/java/sd/model/EventType.java deleted file mode 100644 index 5e4d9ee..0000000 --- a/main/src/main/java/sd/model/EventType.java +++ /dev/null @@ -1,45 +0,0 @@ -package sd.model; - -/** - * Enumeration representing all possible event types in the discrete event simulation. - * These types are used by the {@link sd.engine.SimulationEngine} to determine - * how to process a given {@link Event}. - */ -public enum EventType { - - /** - * Fired when a {@link Vehicle} arrives at an {@link Intersection}. - * Data: {@link Vehicle}, Location: Intersection ID - */ - VEHICLE_ARRIVAL, - - /** - * Fired when a {@link TrafficLight} is scheduled to change its state. - * Data: {@link TrafficLight}, Location: Intersection ID - */ - TRAFFIC_LIGHT_CHANGE, - - /** - * Fired when a {@link Vehicle} begins to cross an {@link Intersection}. - * Data: {@link Vehicle}, Location: Intersection ID - */ - CROSSING_START, - - /** - * Fired when a {@link Vehicle} finishes crossing an {@link Intersection}. - * Data: {@link Vehicle}, Location: Intersection ID - */ - CROSSING_END, - - /** - * Fired when a new {@link Vehicle} should be created and added to the system. - * Data: null, Location: null - */ - VEHICLE_GENERATION, - - /** - * Fired periodically to trigger the printing or sending of simulation statistics. - * Data: null, Location: null - */ - STATISTICS_UPDATE -} \ No newline at end of file diff --git a/main/src/main/java/sd/model/Intersection.java b/main/src/main/java/sd/model/Intersection.java index 718c98c..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:

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

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

* - * @param nextDestination The ID of the *next* intersection or exit (e.g., "Cr3", "S"). - * @param direction The direction (and thus, the traffic light) - * at *this* intersection to use (e.g., "East"). + * @param nextDestination ID da próxima interseção ou saída (ex: "Cr3", "S") + * @param direction direção (e respetivo semáforo) a usar nesta interseção */ public void configureRoute(String nextDestination, String direction) { routing.put(nextDestination, direction); } /** - * Accepts an incoming vehicle and places it in the correct queue. - * * This method: - * 1. Increments the {@link #totalVehiclesReceived} counter. - * 2. Gets the vehicle's *next* destination (from {@link Vehicle#getCurrentDestination()}). - * 3. Uses the {@link #routing} map to find the correct *direction* for that destination. - * 4. Adds the vehicle to the queue of the {@link TrafficLight} for that direction. - * - * @param vehicle The {@link Vehicle} arriving at the intersection. + * Recebe um novo veículo e coloca-o na fila do semáforo apropriado. + * A direção é escolhida com base na tabela de encaminhamento. + * + * @param vehicle o veículo que está a chegar a esta interseção + * @param simulationTime o tempo de simulação atual (em segundos) */ - public void receiveVehicle(Vehicle vehicle) { + public void receiveVehicle(Vehicle vehicle, double simulationTime) { totalVehiclesReceived++; + // Note: Route advancement is handled by SimulationEngine.handleVehicleArrival() + // before calling this method, so we don't advance here. + String nextDestination = vehicle.getCurrentDestination(); + + // Check if vehicle reached final destination + if (nextDestination == null) { + System.out.printf("[%s] Vehicle %s reached final destination%n", + this.id, vehicle.getId()); + return; + } + String direction = routing.get(nextDestination); if (direction != null && trafficLights.containsKey(direction)) { // Found a valid route and light, add vehicle to the queue - trafficLights.get(direction).addVehicle(vehicle); + trafficLights.get(direction).addVehicle(vehicle, simulationTime); } else { // Routing error: No rule for this destination or no light for that direction System.err.printf( @@ -126,108 +119,100 @@ public class Intersection { this.id, vehicle.getId(), nextDestination, direction ); } + } /** + * Retorna a direção que um veículo deve tomar para alcançar um destino. + * + * @param destination o próximo destino (ex: "Cr3", "S") + * @return a direção (ex: "Este"), ou null se não houver rota configurada + */ + public String getDirectionForDestination(String destination) { + return routing.get(destination); } /** - * Returns the traffic light controlling the given direction. + * Retorna o semáforo que controla uma determinada direção. * - * @param direction The direction (e.g., "North"). - * @return The {@link TrafficLight} object, or null if no light exists - * for that direction. + * @param direction a direção (ex: "Norte") + * @return o objeto {@link TrafficLight}, ou null se não existir */ public TrafficLight getTrafficLight(String direction) { return trafficLights.get(direction); } /** - * Returns a list of all traffic lights managed by this intersection. + * Retorna uma lista com todos os semáforos desta interseção. * - * @return A new {@link List} containing all {@link TrafficLight} objects. + * @return uma nova {@link List} com todos os semáforos */ public List getTrafficLights() { - // Return a copy to prevent external modification of the internal map's values return new ArrayList<>(trafficLights.values()); } /** - * Returns the total number of vehicles currently queued across *all* - * traffic lights at this intersection. + * Retorna o número total de veículos em fila em todos os semáforos. + * Usa Java Stream API para somar os tamanhos de todas as filas. * - * @return The sum of all queue sizes. + * @return a soma dos tamanhos de todas as filas */ public int getTotalQueueSize() { - // Uses Java Stream API: - // 1. trafficLights.values().stream() - Get a stream of TrafficLight objects - // 2. .mapToInt(TrafficLight::getQueueSize) - Convert each light to its queue size (an int) - // 3. .sum() - Sum all the integers return trafficLights.values().stream() .mapToInt(TrafficLight::getQueueSize) .sum(); } - // --- Stats and getters --- - /** - * @return The unique ID of this intersection. + * @return o identificador único desta interseção */ public String getId() { return id; } /** - * @return The total number of vehicles that have arrived at this intersection. + * @return o número total de veículos que chegaram a esta interseção */ public int getTotalVehiclesReceived() { return totalVehiclesReceived; } /** - * @return The total number of vehicles that have successfully - * departed from this intersection. + * @return o número total de veículos que partiram desta interseção */ public int getTotalVehiclesSent() { return totalVehiclesSent; } /** - * Increments the counter for vehicles that have successfully departed. - * This is typically called by the {@link sd.engine.SimulationEngine} - * after a vehicle finishes crossing. + * Incrementa o contador de veículos que partiram com sucesso. + * Tipicamente chamado após um veículo completar a travessia. */ public void incrementVehiclesSent() { totalVehiclesSent++; } /** - * @return The running average of vehicle waiting time at this intersection. + * @return a média do tempo de espera dos veículos nesta interseção */ public double getAverageWaitingTime() { return averageWaitingTime; } /** - * Updates the running average waiting time with a new sample (a new - * vehicle's wait time). - * * Uses an incremental/weighted average formula: - * NewAvg = (OldAvg * (N-1) + NewValue) / N - * where N is the total number of vehicles sent. + * Atualiza a média do tempo de espera com uma nova amostra. + * Usa a fórmula: Nova Média = (Média Antiga * (N-1) + Novo Valor) / N * - * @param newTime The waiting time (in seconds) of the vehicle that just - * departed. + * @param newTime tempo de espera (em segundos) do veículo que acabou de partir */ public void updateAverageWaitingTime(double newTime) { - // Avoid division by zero if this is called before any vehicle is sent if (totalVehiclesSent > 0) { averageWaitingTime = (averageWaitingTime * (totalVehiclesSent - 1) + newTime) / totalVehiclesSent; } else if (totalVehiclesSent == 1) { - // This is the first vehicle averageWaitingTime = newTime; } } /** - * @return A string summary of the intersection's current state. + * @return representação textual do estado atual da interseção */ @Override public String toString() { diff --git a/main/src/main/java/sd/model/Message.java b/main/src/main/java/sd/model/Message.java index 0217070..d7a9573 100644 --- a/main/src/main/java/sd/model/Message.java +++ b/main/src/main/java/sd/model/Message.java @@ -1,55 +1,56 @@ package sd.model; -import java.io.Serializable; import java.util.UUID; +import sd.protocol.MessageProtocol; + /** - * Represents a message exchanged between processes in the distributed simulation. - * Each message has a unique ID, a type, a sender, a destination, and a payload. - * This class implements {@link Serializable} to allow transmission over the network. + * Envelope fundamental do protocolo de comunicação entre processos distribuídos (IPC). + *

+ * 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 Serializable { +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) { @@ -62,23 +63,24 @@ public class Message implements Serializable { } /** - * 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()); @@ -111,27 +113,40 @@ public class Message implements Serializable { } /** - * 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 The expected payload type - * @return The payload cast to type T - * @throws ClassCastException if the payload is not of type T + * @param O tipo esperado do payload. + * @param clazz A classe do tipo esperado para verificação em runtime (opcional no uso, mas boa prática). + * @return O payload convertido para o tipo T. + * @throws ClassCastException Se o payload não for compatível com o tipo solicitado. */ @SuppressWarnings("unchecked") public T getPayloadAs(Class clazz) { return (T) payload; } + // Impl MessageProtocol interface + @Override + public String getSourceNode() { + return senderId; + } + + @Override + public String getDestinationNode() { + return destinationId; + } + @Override public String toString() { return String.format("Message[id=%s, type=%s, from=%s, to=%s, timestamp=%d]", @@ -139,4 +154,4 @@ public class Message implements Serializable { destinationId != null ? destinationId : "BROADCAST", timestamp); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/model/MessageType.java b/main/src/main/java/sd/model/MessageType.java index 76cb067..4089bcc 100644 --- a/main/src/main/java/sd/model/MessageType.java +++ b/main/src/main/java/sd/model/MessageType.java @@ -1,81 +1,49 @@ package sd.model; /** - * Enumeration representing all possible message types for distributed communication. - * These types are used for inter-process communication between different components - * of the distributed traffic simulation system. + * Enumeração que representa todos os tipos de mensagens possíveis para + * comunicação distribuída. + * Estes tipos são usados para a comunicação entre processos dos diferentes + * componentes + * do sistema de simulação de tráfego distribuído. */ public enum MessageType { - + /** - * Message to transfer a vehicle between intersections or processes. - * Payload: Vehicle object with current state + * Mensagem para transferir um veículo entre interseções ou processos. + * Payload: Objeto Vehicle com o estado atual */ VEHICLE_TRANSFER, - + /** - * Message to update statistics across the distributed system. - * Payload: Statistics data (waiting times, queue sizes, etc.) + * Mensagem para atualizar estatísticas em todo o sistema distribuído. + * Payload: Dados estatísticos (tempos de espera, tamanhos de fila, etc.) */ STATS_UPDATE, - + /** - * Message to synchronize traffic light states between processes. - * Payload: TrafficLight state and timing information + * Mensagem para sincronizar a hora de início da simulação em todos os + * processos. + * Payload: Timestamp de início (long milissegundos) */ - TRAFFIC_LIGHT_SYNC, - + SIMULATION_START, + /** - * Heartbeat message to check if a process is alive. - * Payload: Process ID and timestamp - */ - HEARTBEAT, - - /** - * Request to join the distributed simulation. - * Payload: Process information and capabilities - */ - JOIN_REQUEST, - - /** - * Response to a join request. - * Payload: Acceptance status and configuration - */ - JOIN_RESPONSE, - - /** - * Message to notify about a new vehicle generation. - * Payload: Vehicle generation parameters + * Mensagem para notificar sobre a geração de um novo veículo. + * Payload: Parâmetros de geração do veículo */ VEHICLE_SPAWN, - + /** - * Message to request the current state of an intersection. - * Payload: Intersection ID - */ - STATE_REQUEST, - - /** - * Response containing the current state of an intersection. - * Payload: Complete intersection state - */ - STATE_RESPONSE, - - /** - * Message to signal shutdown of a process. - * Payload: Process ID and reason + * Mensagem para sinalizar o encerramento de um processo. + * Payload: ID do processo e motivo */ SHUTDOWN, - + /** - * Acknowledgment message for reliable communication. - * Payload: Message ID being acknowledged + * Mensagem para alterar a política de roteamento durante a simulação. + * Payload: String com o nome da nova política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED) */ - ACK, - - /** - * Error message to report problems in the distributed system. - * Payload: Error description and context - */ - ERROR + ROUTING_POLICY_CHANGE, + } diff --git a/main/src/main/java/sd/model/TrafficLight.java b/main/src/main/java/sd/model/TrafficLight.java index 1007c03..149e4e3 100644 --- a/main/src/main/java/sd/model/TrafficLight.java +++ b/main/src/main/java/sd/model/TrafficLight.java @@ -1,315 +1,269 @@ package sd.model; +import java.util.HashMap; import java.util.LinkedList; +import java.util.Map; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** - * Represents a single traffic light controlling one direction at an intersection. - * * Each light maintains its own queue of {@link Vehicle} objects and - * alternates between {@link TrafficLightState#GREEN} and - * {@link TrafficLightState#RED} states. - * * This class is designed to be thread-safe for a potential concurrent - * simulation (though the current engine {@link sd.engine.SimulationEngine} - * is single-threaded). It uses a {@link ReentrantLock} to protect its - * internal state (the queue and the light state) from simultaneous access. - * * The {@link Condition} variables ({@code vehicleAdded}, {@code lightGreen}) - * are included for a concurrent model where: - * - A "vehicle" thread might wait on {@code lightGreen} until the light changes. - * - A "controller" thread might wait on {@code vehicleAdded} to know when to - * process a queue. - * (Note: These Conditions are *not* used by the current discrete-event engine). + * Representa um semáforo numa interseção. + * + *

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

+ * + *

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

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

Só remove se:

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

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

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

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

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

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

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

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

*/ public enum VehicleType { - /** - * A bike or motorcycle. - * Typically has a short crossing time. - */ + /** Bicicleta ou motocicleta - tempo de travessia curto */ BIKE, - /** - * A standard light vehicle, such as a car. - * This is usually the most common type. - */ + /** Veículo ligeiro padrão (carro) - tipo mais comum */ LIGHT, - /** - * A heavy vehicle, such as a truck or bus. - * Typically has a long crossing time. - */ + /** Veículo pesado (camião ou autocarro) - tempo de travessia longo */ HEAVY } \ No newline at end of file diff --git a/main/src/main/java/sd/protocol/MessageProtocol.java b/main/src/main/java/sd/protocol/MessageProtocol.java new file mode 100644 index 0000000..9a4ba27 --- /dev/null +++ b/main/src/main/java/sd/protocol/MessageProtocol.java @@ -0,0 +1,45 @@ +package sd.protocol; + +import java.io.Serializable; + +import sd.model.MessageType; + +/** + * Contrato para todas as mensagens trocadas no simulador. + * + *

Garante que mensagens podem ser identificadas e encaminhadas. + * Extende Serializable para permitir envio via sockets. + */ +public interface MessageProtocol extends Serializable { + + /** + * Tipo da mensagem, indicando o seu propósito. + * @return tipo (ex: VEHICLE_TRANSFER, STATS_UPDATE) + */ + MessageType getType(); + + /** + * Dados (payload) que esta mensagem transporta. + * + *

Tipo depende do MessageType: + *

    + *
  • VEHICLE_TRANSFER → objeto Vehicle + *
  • STATS_UPDATE → objeto de estatísticas + *
+ * + * @return payload (deve ser Serializable) + */ + Object getPayload(); + + /** + * ID do nó (processo) que enviou a mensagem. + * @return ID de origem (ex: "Cr1", "Cr5", "S") + */ + String getSourceNode(); + + /** + * 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 new file mode 100644 index 0000000..16dcdd4 --- /dev/null +++ b/main/src/main/java/sd/protocol/SocketConnection.java @@ -0,0 +1,232 @@ +package sd.protocol; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.concurrent.TimeUnit; + +import sd.serialization.MessageSerializer; +import sd.serialization.SerializationException; +import sd.serialization.SerializerFactory; + +/** + * Wrapper de alto nível para gestão robusta de conexões TCP. + *

+ * Esta classe abstrai a complexidade da API nativa {@link java.net.Socket}, oferecendo: + *

    + *
  1. Resiliência: Lógica de reconexão automática (Retry Loop) no arranque, crucial para sistemas + * distribuídos onde a ordem de inicialização dos nós não é garantida.
  2. + *
  3. Framing: Implementação transparente do protocolo "Length-Prefix" (4 bytes de tamanho + payload), + * resolvendo o problema de fragmentação de stream TCP.
  4. + *
  5. Serialização: Integração direta com a camada de serialização JSON.
  6. + *
+ */ +public class SocketConnection implements Closeable { + + // --- Network Resources --- + + /** + * The underlying TCP socket used for network communication. + */ + private final Socket socket; + + /** + * The raw output stream for writing bytes to the network. + * Wrapped by {@link DataOutputStream} during message sending. + */ + private final OutputStream outputStream; + + /** + * The raw input stream for reading bytes from the network. + * Wrapped by {@link DataInputStream} during message reception. + */ + private final InputStream inputStream; + + // --- Serialization --- + + /** + * The serializer strategy used to convert objects to/from byte arrays (e.g., JSON). + */ + private final MessageSerializer serializer; + + /** Número máximo de tentativas de ligação antes de desistir (Fail-fast). */ + private static final int MAX_RETRIES = 5; + + /** Janela de espera (backoff) linear entre tentativas (em milissegundos). */ + private static final long RETRY_DELAY_MS = 1000; + + /** + * Construtor para clientes (Active Open). + * Tenta estabelecer uma conexão TCP com um servidor, aplicando lógica de retry. + *

+ * Este comportamento é vital quando o processo Coordenador inicia antes das Interseções estarem + * prontas para aceitar conexões ({@code accept()}). + * + * @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; + IOException lastException = null; + + System.out.printf("[SocketConnection] Attempting to connect to %s:%d...%n", host, port); + + // --- Retry Loop --- + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + // Try to establish the connection (SYN -> SYN-ACK -> ACK) + tempSocket = new Socket(host, port); + + // If successful, break out of the retry loop + System.out.printf("[SocketConnection] Connected successfully on attempt %d.%n", attempt); + lastException = null; // Clear last error on success + break; + + } catch (ConnectException | SocketTimeoutException e) { + // 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) { + // Blocking wait before next attempt + TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS); + } + } catch (IOException e) { + // 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); + if (attempt < MAX_RETRIES) { + TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS); + } + } + } // --- End of Retry Loop --- + + // 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; // Propagate the root cause + } else { + throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown."); + } + } + + // Initialize streams + this.socket = tempSocket; + this.outputStream = socket.getOutputStream(); + this.inputStream = socket.getInputStream(); + this.serializer = SerializerFactory.createDefault(); + } + + /** + * 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 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(); + } + + /** + * 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 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()) { + throw new IOException("Socket is not connected"); + } + + try { + // Serializa para bytes JSON + byte[] data = serializer.serialize(message); + + // Write 4-byte length prefix (Framing) + DataOutputStream dataOut = new DataOutputStream(outputStream); + dataOut.writeInt(data.length); + dataOut.write(data); + dataOut.flush(); // Force transmission immediately + + } catch (SerializationException e) { + throw new IOException("Failed to serialize message", e); + } + } + + /** + * 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 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()) { + throw new IOException("Socket is not connected"); + } + + try { + + DataInputStream dataIn = new DataInputStream(inputStream); + int length = dataIn.readInt(); + + // 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); + + // Deserialize do JSON - força o tipo concreto Message + return serializer.deserialize(data, sd.model.Message.class); + + } catch (SerializationException e) { + throw new IOException("Failed to deserialize message", e); + } + } + + /** + * Encerra a conexão e liberta os descritores de ficheiro. + * Operação idempotente. + */ + @Override + public void close() throws IOException { + if (inputStream != null) inputStream.close(); + if (outputStream != null) outputStream.close(); + if (socket != null) socket.close(); + } + + /** + * 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(); + } +} \ No newline at end of file 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:

+ *
    + *
  1. Para cada rota possível, calcula a carga total (soma das filas)
  2. + *
  3. Escolhe a rota com menor carga total
  4. + *
  5. Em caso de empate ou falta de informação, usa a rota mais curta
  6. + *
+ */ +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 selectRoute(String entryPoint, Map queueSizes) { + 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 bestRoute = null; + int minLoad = Integer.MAX_VALUE; + + for (List route : availableRoutes) { + int routeLoad = calculateRouteLoad(route, queueSizes); + + if (routeLoad < minLoad) { + minLoad = routeLoad; + bestRoute = route; + } + } + + // Fallback: se não conseguimos calcular carga, usa a primeira rota + if (bestRoute == null) { + bestRoute = availableRoutes.get(0); + } + + return new ArrayList<>(bestRoute); + } + + /** + * Calcula a carga total de uma rota (soma do tamanho das filas em todos os cruzamentos). + * + * @param route rota a avaliar + * @param queueSizes mapa com tamanho das filas por interseção + * @return carga total da rota (soma das filas) + */ + private int calculateRouteLoad(List route, Map queueSizes) { + int totalLoad = 0; + + for (String intersection : route) { + // Ignora "S" (saída) e apenas conta cruzamentos reais + if (!intersection.equals("S") && queueSizes.containsKey(intersection)) { + totalLoad += queueSizes.get(intersection); + } + } + + return totalLoad; + } + + /** + * Seleciona a rota mais curta (menor número de nós) como fallback. + * + * @param routes lista de rotas disponíveis + * @return a rota mais curta + */ + private List selectShortestRoute(List> routes) { + List shortest = routes.get(0); + + for (List route : routes) { + if (route.size() < shortest.size()) { + shortest = route; + } + } + + return new ArrayList<>(shortest); + } + + /** + * Obtém as rotas disponíveis para um ponto de entrada. + * + * @param entryPoint ponto de entrada (E1, E2 ou E3) + * @return lista de rotas disponíveis + */ + private 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). + * + *

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.

+ */ +public class RandomRouteSelector 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 aleatórias com rotas predefinidas. + */ + public RandomRouteSelector() { + this.e1Routes = new ArrayList<>(); + this.e2Routes = new ArrayList<>(); + this.e3Routes = new ArrayList<>(); + initializePossibleRoutes(); + } + + /** + * Define todas as rotas possíveis que os veículos podem tomar. + * As rotas são organizadas por ponto de entrada (E1, E2, E3). + * Cada rota tem uma probabilidade que determina a frequência com que é escolhida. + */ + private void initializePossibleRoutes() { + // Rotas de E1 (entrada Norte) + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34)); + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33)); + e1Routes.add(new RouteWithProbability( + Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33)); + + // Rotas de E2 (entrada Oeste) + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr5", "S"), 0.34)); + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr3", "S"), 0.33)); + e2Routes.add(new RouteWithProbability( + Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); + + // Rotas de E3 (entrada Sul) + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "S"), 0.34)); + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33)); + e3Routes.add(new RouteWithProbability( + Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); + } + + @Override + public List selectRoute(String entryPoint, Map queueSizes) { + // Ignora queueSizes - seleção aleatória não depende do estado da rede + + List selectedRoutes = getRoutesForEntryPoint(entryPoint); + + // Seleciona uma rota baseada em probabilidades cumulativas + double rand = Math.random(); + double cumulative = 0.0; + + for (RouteWithProbability routeWithProb : selectedRoutes) { + cumulative += routeWithProb.probability; + if (rand <= cumulative) { + // Retorna uma cópia da rota para prevenir modificações + return new ArrayList<>(routeWithProb.route); + } + } + + // Fallback: retorna a primeira rota + return new ArrayList<>(selectedRoutes.get(0).route); + } + + /** + * Obtém as rotas disponíveis para um ponto de entrada. + * + * @param entryPoint ponto de entrada (E1, E2 ou E3) + * @return lista de rotas com probabilidades + */ + private 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; + } + } + + /** + * Classe interna para associar uma rota com sua probabilidade de seleção. + */ + private static class RouteWithProbability { + final List route; + final double probability; + + RouteWithProbability(List route, double probability) { + this.route = route; + this.probability = probability; + } + } +} diff --git a/main/src/main/java/sd/routing/RouteSelector.java b/main/src/main/java/sd/routing/RouteSelector.java new file mode 100644 index 0000000..0febb29 --- /dev/null +++ b/main/src/main/java/sd/routing/RouteSelector.java @@ -0,0 +1,25 @@ +package sd.routing; + +import java.util.List; +import java.util.Map; + +/** + * Interface para implementação de políticas de seleção de rotas. + * + *

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.).

+ */ +public interface RouteSelector { + + /** + * Seleciona uma rota para um veículo a partir de um ponto de entrada. + * + * @param entryPoint ponto de entrada (E1, E2 ou E3) + * @param queueSizes mapa com o tamanho das filas em cada interseção (opcional, pode ser null). + * Chave: ID da interseção (ex: "Cr1", "Cr2") + * Valor: número total de veículos em espera nessa interseção + * @return lista de IDs representando a rota escolhida (ex: ["Cr1", "Cr2", "Cr5", "S"]) + */ + List selectRoute(String entryPoint, Map queueSizes); +} diff --git a/main/src/main/java/sd/routing/RoutingPolicy.java b/main/src/main/java/sd/routing/RoutingPolicy.java new file mode 100644 index 0000000..06c7db2 --- /dev/null +++ b/main/src/main/java/sd/routing/RoutingPolicy.java @@ -0,0 +1,36 @@ +package sd.routing; + +/** + * Enumeração que define as políticas de roteamento disponíveis para a simulação. + * + *

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.

+ * + *
    + *
  • RANDOM: Seleção aleatória de rotas baseada em probabilidades predefinidas
  • + *
  • SHORTEST_PATH: Escolhe sempre a rota com o menor número de cruzamentos
  • + *
  • LEAST_CONGESTED: Escolhe a rota evitando cruzamentos mais congestionados
  • + *
+ */ +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. + * + *

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.

+ */ +public class ShortestPathRouteSelector implements RouteSelector { + + /** Rotas possíveis a partir do ponto de entrada E1, ordenadas por comprimento */ + private final List> 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 selectRoute(String entryPoint, Map queueSizes) { + // Ignora queueSizes - política baseada apenas no comprimento do caminho + + List> availableRoutes = getRoutesForEntryPoint(entryPoint); + + // Retorna a rota mais curta (primeira da lista) + List shortestRoute = availableRoutes.get(0); + + return new ArrayList<>(shortestRoute); + } + + /** + * Obtém as rotas disponíveis para um ponto de entrada. + * + * @param entryPoint ponto de entrada (E1, E2 ou E3) + * @return lista de rotas ordenadas por comprimento + */ + private 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. + *

+ * 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: + *

    + *
  • Legibilidade: O formato de texto facilita a depuração (sniffing de rede) sem ferramentas especializadas.
  • + *
  • Interoperabilidade: Permite futura integração com componentes não-Java (ex: Dashboards web em JS).
  • + *
  • Segurança: Reduz a superfície de ataque para execução remota de código (RCE), pois não desserializa classes arbitrárias, apenas dados.
  • + *
+ *

+ * 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 T deserialize(byte[] data, Class clazz) throws SerializationException { if (data == null) { @@ -95,20 +111,18 @@ public class JsonMessageSerializer implements MessageSerializer { } /** - * Returns the underlying Gson instance for advanced usage. - * - * @return The Gson instance + * Retorna a instância subjacente do Gson para configurações avançadas. + * * @return A instância Gson configurada. */ public Gson getGson() { return gson; } /** - * Checks if pretty printing is enabled. - * - * @return true if pretty printing is enabled + * Verifica se a formatação "pretty print" está ativa. + * * @return true se a indentação estiver habilitada. */ public boolean isPrettyPrint() { return prettyPrint; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/serialization/MessageSerializer.java b/main/src/main/java/sd/serialization/MessageSerializer.java index 21517f4..3a5ff55 100644 --- a/main/src/main/java/sd/serialization/MessageSerializer.java +++ b/main/src/main/java/sd/serialization/MessageSerializer.java @@ -1,48 +1,49 @@ package sd.serialization; /** - * Interface for serializing and deserializing objects for network transmission. - * - * This interface provides a common abstraction for different serialization strategies - * allowing the system to switch between implementations without changing the communication layer. - * - * Implementations must ensure: - * - Thread-safety if used in concurrent contexts - * - Proper exception handling with meaningful error messages - * - Preservation of object state during round-trip serialization - * - * @see JsonMessageSerializer + * Interface que define o contrato para estratégias de serialização e desserialização de objetos. + *

+ * 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: + *

    + *
  • Thread-Safety: As implementações devem ser seguras para uso concorrente, dado que + * instâncias únicas podem ser partilhadas por múltiplos ClientHandlers.
  • + *
  • Robustez: Falhas de parsing devem resultar em exceções tipificadas ({@link SerializationException}), + * nunca em falhas silenciosas ou estados inconsistentes.
  • + *
+ * * @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 The expected type of the deserialized object - * @param data The byte array containing serialized data (must not be null) - * @param clazz The class of the expected object type (must not be null) - * @return The deserialized object - * @throws SerializationException If deserialization fails - * @throws IllegalArgumentException If data or clazz is null + * Reconstrói (Unmarshals) um objeto a partir de uma sequência de bytes. + * * @param O tipo genérico do objeto esperado. + * @param data O array de bytes contendo os dados serializados (não pode ser nulo). + * @param clazz A classe do tipo esperado para verificação e instancialização. + * @return A instância do objeto reconstruído com o seu estado restaurado. + * @throws SerializationException Se os dados estiverem corrompidos ou incompatíveis com a classe alvo. + * @throws IllegalArgumentException Se os dados ou a classe forem nulos. */ T deserialize(byte[] data, Class clazz) throws SerializationException; /** - * Gets the name of this serialization strategy (e.g., "JSON", "Java Native"). - * Useful for logging and debugging. - * - * @return The serializer name + * Obtém o identificador legível desta estratégia de serialização (ex: "JSON (Gson)", "Native"). + * Utilizado primariamente para logging, auditoria e negociação de conteúdo. + * * @return O nome descritivo do serializador. */ String getName(); -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/serialization/SerializationExample.java b/main/src/main/java/sd/serialization/SerializationExample.java deleted file mode 100644 index f6bd817..0000000 --- a/main/src/main/java/sd/serialization/SerializationExample.java +++ /dev/null @@ -1,134 +0,0 @@ -package sd.serialization; - -import sd.model.Message; -import sd.model.MessageType; -import sd.model.Vehicle; -import sd.model.VehicleType; - -import java.util.Arrays; -import java.util.List; - -/** - * Demonstration of JSON serialization usage in the traffic simulation system. - * - * This class shows practical examples of how to use JSON (Gson) serialization - * for network communication between simulation processes. - */ -public class SerializationExample { - - public static void main(String[] args) { - System.out.println("=== JSON Serialization Example ===\n"); - - // Create a sample vehicle - List route = Arrays.asList("Cr1", "Cr2", "Cr5", "S"); - Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5, route); - vehicle.addWaitingTime(2.3); - vehicle.addCrossingTime(1.2); - - // Create a message containing the vehicle - Message message = new Message( - MessageType.VEHICLE_TRANSFER, - "Cr1", - "Cr2", - vehicle - ); - - // ===== JSON Serialization ===== - demonstrateJsonSerialization(message); - - // ===== Factory Usage ===== - demonstrateFactoryUsage(message); - - // ===== Performance Test ===== - performanceTest(message); - } - - private static void demonstrateJsonSerialization(Message message) { - System.out.println("--- JSON Serialization ---"); - - try { - // Create JSON serializer with pretty printing for readability - MessageSerializer serializer = new JsonMessageSerializer(true); - - // Serialize to bytes - byte[] data = serializer.serialize(message); - - // Display the JSON - String json = new String(data); - System.out.println("Serialized JSON (" + data.length + " bytes):"); - System.out.println(json); - - // Deserialize back - Message deserialized = serializer.deserialize(data, Message.class); - System.out.println("\nDeserialized: " + deserialized); - System.out.println("✓ JSON serialization successful\n"); - - } catch (SerializationException e) { - System.err.println("❌ JSON serialization failed: " + e.getMessage()); - } - } - - private static void demonstrateFactoryUsage(Message message) { - System.out.println("--- Using SerializerFactory ---"); - - try { - // Get default serializer (JSON) - MessageSerializer serializer = SerializerFactory.createDefault(); - System.out.println("Default serializer: " + serializer.getName()); - - // Use it - byte[] data = serializer.serialize(message); - Message deserialized = serializer.deserialize(data, Message.class); - - System.out.println("Message type: " + deserialized.getType()); - System.out.println("From: " + deserialized.getSenderId() + - " → To: " + deserialized.getDestinationId()); - System.out.println("✓ Factory usage successful\n"); - - } catch (SerializationException e) { - System.err.println("❌ Factory usage failed: " + e.getMessage()); - } - } - - private static void performanceTest(Message message) { - System.out.println("--- Performance Test ---"); - - int iterations = 1000; - - try { - MessageSerializer compactSerializer = new JsonMessageSerializer(false); - MessageSerializer prettySerializer = new JsonMessageSerializer(true); - - // Warm up - for (int i = 0; i < 100; i++) { - compactSerializer.serialize(message); - } - - // Test compact JSON - long compactStart = System.nanoTime(); - byte[] compactData = null; - for (int i = 0; i < iterations; i++) { - compactData = compactSerializer.serialize(message); - } - long compactTime = System.nanoTime() - compactStart; - - // Test pretty JSON - byte[] prettyData = prettySerializer.serialize(message); - - // Results - System.out.println("Iterations: " + iterations); - System.out.println("\nJSON Compact:"); - System.out.println(" Size: " + compactData.length + " bytes"); - System.out.println(" Time: " + (compactTime / 1_000_000.0) + " ms total"); - System.out.println(" Avg: " + (compactTime / iterations / 1_000.0) + " μs/operation"); - - System.out.println("\nJSON Pretty-Print:"); - System.out.println(" Size: " + prettyData.length + " bytes"); - System.out.println(" Size increase: " + - String.format("%.1f%%", ((double)prettyData.length / compactData.length - 1) * 100)); - - } catch (SerializationException e) { - System.err.println("❌ Performance test failed: " + e.getMessage()); - } - } -} diff --git a/main/src/main/java/sd/serialization/SerializationException.java b/main/src/main/java/sd/serialization/SerializationException.java index 5cf9675..bae51b0 100644 --- a/main/src/main/java/sd/serialization/SerializationException.java +++ b/main/src/main/java/sd/serialization/SerializationException.java @@ -1,41 +1,40 @@ package sd.serialization; /** - * Exception thrown when serialization or deserialization operations fail. - * - * This exception wraps underlying errors (I/O exceptions, parsing errors, etc.) - * and provides context about what went wrong during the serialization process. + * Exceção verificada (Checked Exception) que sinaliza falhas no processo de transformação de dados. + *

+ * 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: *

  * 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.
+     * 

+ * 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: + *

    + *
  • Modelagem de Poisson: Geração de tempos entre chegadas usando distribuição exponencial inversa.
  • + *
  • Amostragem Uniforme: Geração de inteiros e doubles em intervalos fechados/abertos.
  • + *
  • Decisão Probabilística: Avaliação de eventos booleanos baseados em pesos (Bernoulli trials).
  • + *
  • Determinismo: Suporte a sementes (seeds) manuais para reprodutibilidade exata de cenários de teste.
  • + *
*/ 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. + *

+ * 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) / λ} + *
Onde: + *

    + *
  • {@code U}: Variável aleatória uniforme no intervalo [0, 1).
  • + *
  • {@code λ (lambda)}: Taxa média de eventos por unidade de tempo (ex: veículos/segundo).
  • + *
* - * @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. + *

+ * 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 The generic type of the array. - * @param array The array to choose from. - * @return A randomly selected element from the array. - * @throws IllegalArgumentException if the array is null or empty. + * @param O tipo dos elementos no array. + * @param array A população de onde escolher. + * @return O elemento selecionado. + * @throws IllegalArgumentException Se o array for nulo ou vazio. */ public static T chooseRandom(T[] array) { if (array == null || array.length == 0) { @@ -90,12 +94,13 @@ public class RandomGenerator { } /** - * Sets the seed of the shared random number generator. - * This is extremely useful for debugging and testing, as it allows - * the simulation to be run multiple times with the *exact same* - * sequence of "random" events, making the results reproducible. + * Reinicializa a semente (seed) do gerador global. + *

+ * 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/StatisticsCollector.java b/main/src/main/java/sd/util/StatisticsCollector.java deleted file mode 100644 index fa8f8bd..0000000 --- a/main/src/main/java/sd/util/StatisticsCollector.java +++ /dev/null @@ -1,379 +0,0 @@ -package sd.util; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import sd.config.SimulationConfig; -import sd.model.Intersection; -import sd.model.Vehicle; -import sd.model.VehicleType; - -/** - * Collects, manages, and reports statistics throughout the simulation. - * * This class acts as the central bookkeeper for simulation metrics. It tracks: - * - Overall system statistics (total vehicles, completion time, wait time). - * - Per-vehicle-type statistics (counts, average wait time by type). - * - Per-intersection statistics (arrivals, departures). - * * It also maintains "in-flight" data, such as the arrival time of a - * vehicle at its *current* intersection, which is necessary to - * calculate waiting time when the vehicle later departs. - */ -public class StatisticsCollector { - - // --- Vehicle tracking (for in-flight vehicles) --- - - /** - * Tracks the simulation time when a vehicle arrives at its *current* intersection. - * This is used later to calculate waiting time (Depart_Time - Arrive_Time). - * Key: Vehicle ID (String) - * Value: Arrival Time (Double) - */ - private final Map vehicleArrivalTimes; - - /** - * Tracks the sequence of intersections a vehicle has visited. - * Key: Vehicle ID (String) - * Value: List of Intersection IDs (String) - */ - private final Map> vehicleIntersectionHistory; - - // --- Overall system statistics --- - - /** Total number of vehicles created by the {@link VehicleGenerator}. */ - private int totalVehiclesGenerated; - - /** Total number of vehicles that have reached their final destination ("S"). */ - private int totalVehiclesCompleted; - - /** The sum of all *completed* vehicles' total travel times. Used for averaging. */ - private double totalSystemTime; - - /** The sum of all *completed* vehicles' total waiting times. Used for averaging. */ - private double totalWaitingTime; - - // --- Per-vehicle-type statistics --- - - /** - * Tracks the total number of vehicles generated, broken down by type. - * Key: {@link VehicleType} - * Value: Count (Integer) - */ - private final Map vehicleTypeCount; - - /** - * Tracks the total waiting time, broken down by vehicle type. - * Key: {@link VehicleType} - * Value: Total Wait Time (Double) - */ - private final Map vehicleTypeWaitTime; - - // --- Per-intersection statistics --- - - /** - * A map to hold statistics objects for each intersection. - * Key: Intersection ID (String) - * Value: {@link IntersectionStats} object - */ - private final Map intersectionStats; - - /** - * Constructs a new StatisticsCollector. - * Initializes all maps and counters. - * - * @param config The {@link SimulationConfig} (not currently used, but - * could be for configuration-dependent stats). - */ - public StatisticsCollector(SimulationConfig config) { - this.vehicleArrivalTimes = new HashMap<>(); - this.vehicleIntersectionHistory = new HashMap<>(); - this.totalVehiclesGenerated = 0; - this.totalVehiclesCompleted = 0; - this.totalSystemTime = 0.0; - this.totalWaitingTime = 0.0; - this.vehicleTypeCount = new HashMap<>(); - this.vehicleTypeWaitTime = new HashMap<>(); - this.intersectionStats = new HashMap<>(); - - // Initialize vehicle type counters to 0 - for (VehicleType type : VehicleType.values()) { - vehicleTypeCount.put(type, 0); - vehicleTypeWaitTime.put(type, 0.0); - } - } - - /** - * Records that a new vehicle has been generated. - * This is called by the {@link sd.engine.SimulationEngine} - * during a {@code VEHICLE_GENERATION} event. - * - * @param vehicle The {@link Vehicle} that was just created. - * @param currentTime The simulation time of the event. - */ - public void recordVehicleGeneration(Vehicle vehicle, double currentTime) { - totalVehiclesGenerated++; - - // Track by vehicle type - VehicleType type = vehicle.getType(); - vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1); - - // Initialize history tracking for this vehicle - vehicleIntersectionHistory.put(vehicle.getId(), new ArrayList<>()); - } - - /** - * Records that a vehicle has arrived at an intersection queue. - * This is called by the {@link sd.engine.SimulationEngine} - * during a {@code VEHICLE_ARRIVAL} event. - * - * @param vehicle The {@link Vehicle} that arrived. - * @param intersectionId The ID of the intersection it arrived at. - * @param currentTime The simulation time of the arrival. - */ - public void recordVehicleArrival(Vehicle vehicle, String intersectionId, double currentTime) { - // Store arrival time - this is the "start waiting" time - vehicleArrivalTimes.put(vehicle.getId(), currentTime); - - // Track intersection history - List history = vehicleIntersectionHistory.get(vehicle.getId()); - if (history != null) { - history.add(intersectionId); - } - - // Update per-intersection statistics - getOrCreateIntersectionStats(intersectionId).recordArrival(); - } - - /** - * Records that a vehicle has completed its route and exited the system. - * This is where final metrics for the vehicle are aggregated. - * This is called by the {@link sd.engine.SimulationEngine} - * when a vehicle reaches destination "S". - * - * @param vehicle The {@link Vehicle} that is exiting. - * @param currentTime The simulation time of the exit. - */ - public void recordVehicleExit(Vehicle vehicle, double currentTime) { - totalVehiclesCompleted++; - - // Calculate and aggregate total system time - double systemTime = vehicle.getTotalTravelTime(currentTime); - totalSystemTime += systemTime; - - // Aggregate waiting time - double waitTime = vehicle.getTotalWaitingTime(); - totalWaitingTime += waitTime; - - // Aggregate waiting time by vehicle type - VehicleType type = vehicle.getType(); - vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime); - - // Clean up tracking maps to save memory - vehicleArrivalTimes.remove(vehicle.getId()); - vehicleIntersectionHistory.remove(vehicle.getId()); - } - - /** - * Gets the time a vehicle arrived at its *current* intersection. - * This is used by the {@link sd.engine.SimulationEngine} to calculate - * wait time just before the vehicle crosses. - * - * @param vehicle The {@link Vehicle} to check. - * @return The arrival time, or 0.0 if not found. - */ - public double getArrivalTime(Vehicle vehicle) { - return vehicleArrivalTimes.getOrDefault(vehicle.getId(), 0.0); - } - - /** - * Prints a "snapshot" of the current simulation statistics. - * This is called periodically by the {@link sd.engine.SimulationEngine} - * during a {@code STATISTICS_UPDATE} event. - * - * @param intersections A map of all intersections (to get queue data). - * @param currentTime The current simulation time. - */ - public void printCurrentStatistics(Map intersections, double currentTime) { - System.out.printf("--- Statistics at t=%.2f ---%n", currentTime); - System.out.printf("Vehicles: Generated=%d, Completed=%d, In-System=%d%n", - totalVehiclesGenerated, - totalVehiclesCompleted, - totalVehiclesGenerated - totalVehiclesCompleted); - - if (totalVehiclesCompleted > 0) { - System.out.printf("Average System Time (so far): %.2fs%n", totalSystemTime / totalVehiclesCompleted); - System.out.printf("Average Waiting Time (so far): %.2fs%n", totalWaitingTime / totalVehiclesCompleted); - } - - // Print per-intersection queue sizes - System.out.println("\nIntersection Queues:"); - for (Map.Entry entry : intersections.entrySet()) { - String id = entry.getKey(); - Intersection intersection = entry.getValue(); - System.out.printf(" %s: Queue=%d, Received=%d, Sent=%d%n", - id, - intersection.getTotalQueueSize(), - intersection.getTotalVehiclesReceived(), - intersection.getTotalVehiclesSent()); - } - } - - /** - * Prints the final simulation summary statistics at the end of the run. - * - * @param intersections A map of all intersections. - * @param currentTime The final simulation time. - */ - public void printFinalStatistics(Map intersections, double currentTime) { - System.out.println("\n=== SIMULATION SUMMARY ==="); - System.out.printf("Duration: %.2f seconds%n", currentTime); - System.out.printf("Total Vehicles Generated: %d%n", totalVehiclesGenerated); - System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesCompleted); - System.out.printf("Vehicles Still in System: %d%n", totalVehiclesGenerated - totalVehiclesCompleted); - - // Overall averages - if (totalVehiclesCompleted > 0) { - System.out.printf("%nAVERAGE METRICS (for completed vehicles):%n"); - System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesCompleted); - System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesCompleted); - System.out.printf(" Throughput: %.2f vehicles/second%n", totalVehiclesCompleted / currentTime); - } - - // Vehicle type breakdown - System.out.println("\nVEHICLE TYPE DISTRIBUTION:"); - for (VehicleType type : VehicleType.values()) { - int count = vehicleTypeCount.get(type); - if (count > 0) { - double percentage = (count * 100.0) / totalVehiclesGenerated; - // Calculate avg wait *only* for this type - // This assumes all generated vehicles of this type *completed* - // A more accurate way would be to track completed vehicle types - double avgWait = vehicleTypeWaitTime.get(type) / count; - System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n", - type, count, percentage, avgWait); - } - } - - // Per-intersection statistics - System.out.println("\nINTERSECTION STATISTICS:"); - for (Map.Entry entry : intersections.entrySet()) { - String id = entry.getKey(); - Intersection intersection = entry.getValue(); - - System.out.printf(" %s:%n", id); - System.out.printf(" Vehicles Received: %d%n", intersection.getTotalVehiclesReceived()); - System.out.printf(" Vehicles Sent: %d%n", intersection.getTotalVehiclesSent()); - System.out.printf(" Final Queue Size: %d%n", intersection.getTotalQueueSize()); - - // Traffic light details - intersection.getTrafficLights().forEach(light -> { - System.out.printf(" Light %s: State=%s, Queue=%d, Processed=%d%n", - light.getDirection(), - light.getState(), - light.getQueueSize(), - light.getTotalVehiclesProcessed()); - }); - } - - // System health indicators - System.out.println("\nSYSTEM HEALTH:"); - int totalQueuedVehicles = intersections.values().stream() - .mapToInt(Intersection::getTotalQueueSize) - .sum(); - System.out.printf(" Total Queued Vehicles (at end): %d%n", totalQueuedVehicles); - - if (totalVehiclesGenerated > 0) { - double completionRate = (totalVehiclesCompleted * 100.0) / totalVehiclesGenerated; - System.out.printf(" Completion Rate: %.1f%%%n", completionRate); - } - } - - /** - * Gets or creates the statistics object for a given intersection. - * Uses {@code computeIfAbsent} for efficient, thread-safe-like instantiation. - * - * @param intersectionId The ID of the intersection. - * @return The {@link IntersectionStats} object for that ID. - */ - private IntersectionStats getOrCreateIntersectionStats(String intersectionId) { - // If 'intersectionId' is not in the map, create a new IntersectionStats() - // and put it in the map, then return it. - // Otherwise, just return the one that's already there. - return intersectionStats.computeIfAbsent(intersectionId, k -> new IntersectionStats()); - } - - /** - * Inner class to track per-intersection statistics. - * This is a simple data holder. - */ - private static class IntersectionStats { - private int totalArrivals; - private int totalDepartures; - - public IntersectionStats() { - this.totalArrivals = 0; - this.totalDepartures = 0; - } - - public void recordArrival() { - totalArrivals++; - } - - public void recordDeparture() { - totalDepartures++; - } - - public int getTotalArrivals() { - return totalArrivals; - } - - public int getTotalDepartures() { - return totalDepartures; - } - } - - // --- Public Getters for Final Statistics --- - - /** - * @return Total vehicles generated during the simulation. - */ - public int getTotalVehiclesGenerated() { - return totalVehiclesGenerated; - } - - /** - * @return Total vehicles that completed their route. - */ - public int getTotalVehiclesCompleted() { - return totalVehiclesCompleted; - } - - /** - * @return The sum of all travel times for *completed* vehicles. - */ - public double getTotalSystemTime() { - return totalSystemTime; - } - - /** - * @return The sum of all waiting times for *completed* vehicles. - */ - public double getTotalWaitingTime() { - return totalWaitingTime; - } - - /** - * @return The average travel time for *completed* vehicles. - */ - public double getAverageSystemTime() { - return totalVehiclesCompleted > 0 ? totalSystemTime / totalVehiclesCompleted : 0.0; - } - - /** - * @return The average waiting time for *completed* vehicles. - */ - public double getAverageWaitingTime() { - return totalVehiclesCompleted > 0 ? totalWaitingTime / totalVehiclesCompleted : 0.0; - } -} \ No newline at end of file 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: + *

    + *
  1. Modelagem Temporal: Determinar os instantes de chegada (Inter-arrival times) + * usando processos de Poisson (estocástico) ou intervalos determinísticos.
  2. + *
  3. Caracterização da Entidade: Atribuir tipos de veículo (Bike, Light, Heavy) + * baseado numa Distribuição de Probabilidade Cumulativa (CDF).
  4. + *
  5. Inicialização Espacial: Distribuir a carga uniformemente entre os pontos de entrada (E1-E3).
  6. + *
  7. Atribuição de Rota: Delegar a escolha do percurso à estratégia {@link RouteSelector} ativa.
  8. + *
*/ 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 e1Routes; - /** Routes starting from entry point E2. */ - private final List e2Routes; - /** Routes starting from entry point E3. */ - private final List e3Routes; + /** Intervalo determinístico para geração constante (modo debug/teste). */ + private final double fixedInterval; + + /** * Estratégia de roteamento atual. + * Não é final para permitir Hot-Swapping durante a execução. + */ + private RouteSelector routeSelector; /** - * Constructs a new VehicleGenerator. - * It reads the necessary configuration and initializes the - * predefined routes. + * Inicializa o gerador com as configurações de simulação e estratégia de roteamento. * - * @param config The {@link SimulationConfig} object. + * @param config A configuração global contendo as taxas e probabilidades. + * @param routeSelector A estratégia inicial de seleção de rotas. */ - public VehicleGenerator(SimulationConfig config) { + public VehicleGenerator(SimulationConfig config, RouteSelector routeSelector) { this.config = config; + this.routeSelector = routeSelector; - // Cache configuration values for performance + // Cache de valores de configuração para evitar lookups repetitivos em hot-path this.arrivalModel = config.getArrivalModel(); this.arrivalRate = config.getArrivalRate(); this.fixedInterval = config.getFixedArrivalInterval(); - - // Initialize route lists - this.e1Routes = new ArrayList<>(); - this.e2Routes = new ArrayList<>(); - this.e3Routes = new ArrayList<>(); - initializePossibleRoutes(); } /** - * Defines all possible routes that vehicles can take, organized by - * their entry point (E1, E2, E3). Each route is given a - * probability, which determines how often it's chosen. - */ - private void initializePossibleRoutes() { - // E1 routes (Starts at Cr1) - e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34)); // E1 -> Cr1 -> Cr4 -> Cr5 -> Exit - e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33)); // E1 -> Cr1 -> Cr2 -> Cr5 -> Exit - e1Routes.add(new RouteWithProbability( - Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33)); // E1 -> Cr1 -> Cr2 -> Cr3 -> Exit - - // E2 routes (Starts at Cr2) - e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr5", "S"), 0.34)); // E2 -> Cr2 -> Cr5 -> Exit - e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr3", "S"), 0.33)); // E2 -> Cr2 -> Cr3 -> Exit - e2Routes.add(new RouteWithProbability( - Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); // E2 -> Cr2 -> ... -> Exit - - // E3 routes (Starts at Cr3) - e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "S"), 0.34)); // E3 -> Cr3 -> Exit - e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33)); // E3 -> Cr3 -> Cr2 -> Cr5 -> Exit - e3Routes.add(new RouteWithProbability( - Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33)); // E3 -> Cr3 -> ... -> Exit - } - - /** - * Calculates the *absolute* time of the next vehicle arrival - * based on the configured model. - * * @param currentTime The current simulation time, used as the base. - * @return The absolute time (e.g., {@code currentTime + interval}) - * when the next vehicle should be generated. + * Calcula o timestamp absoluto para a próxima injeção de veículo. + *

+ * 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: + *

    + *
  1. Seleção de Tipo (Roda da Fortuna / CDF).
  2. + *
  3. Seleção de Entrada (Uniforme).
  4. + *
  5. Cálculo de Rota (Delegado ao Strategy).
  6. + *
* - * @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 queueSizes) { VehicleType type = selectVehicleType(); - List route = selectRandomRoute(); + String entryPoint = selectRandomEntryPoint(); + List route = routeSelector.selectRoute(entryPoint, queueSizes); return new Vehicle(vehicleId, type, entryTime, route); } /** - * Selects a {@link VehicleType} (BIKE, LIGHT, HEAVY) based on the - * probabilities defined in the {@link SimulationConfig}. - * * Uses a standard "cumulative probability" technique: - * 1. Get a random number {@code rand} from [0, 1). - * 2. If {@code rand < P(Bike)}, return BIKE. - * 3. Else if {@code rand < P(Bike) + P(Light)}, return LIGHT. - * 4. Else, return HEAVY. + * Seleciona o tipo de veículo usando Amostragem por Probabilidade Cumulativa. + *

+ * 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 selectRandomRoute() { - // Step 1: Randomly select an entry point (E1, E2, or E3) - double entryRandom = Math.random(); - List selectedRoutes; + private String selectRandomEntryPoint() { + double rand = Math.random(); - if (entryRandom < 0.333) { - selectedRoutes = e1Routes; - } else if (entryRandom < 0.666) { - selectedRoutes = e2Routes; + if (rand < 0.333) { + return "E1"; + } else if (rand < 0.666) { + return "E2"; } else { - selectedRoutes = e3Routes; + return "E3"; } - - // Step 2: Select a route from the chosen list based on cumulative probabilities - double routeRand = Math.random(); - double cumulative = 0.0; - - for (RouteWithProbability routeWithProb : selectedRoutes) { - cumulative += routeWithProb.probability; - if (routeRand <= cumulative) { - // Return a *copy* of the route to prevent modification - return new ArrayList<>(routeWithProb.route); - } - } - - // Fallback: This should only be reached if probabilities don't sum to 1 - // (due to floating point errors) - return new ArrayList<>(selectedRoutes.get(0).route); } /** - * @return A string providing information about the generator's configuration. + * Atualiza a estratégia de roteamento em tempo de execução (Hot-Swap). + *

+ * 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 route; - final double probability; - - /** - * Constructs a new RouteWithProbability pair. - * @param route The list of intersection IDs. - * @param probability The probability (0.0 to 1.0) of this route - * being chosen *from its entry group*. - */ - RouteWithProbability(List route, double probability) { - this.route = route; - this.probability = probability; - } - } } \ No newline at end of file diff --git a/main/src/main/resources/dashboard.css b/main/src/main/resources/dashboard.css new file mode 100644 index 0000000..5cd7e57 --- /dev/null +++ b/main/src/main/resources/dashboard.css @@ -0,0 +1,142 @@ +/* Global Styles */ +.root { + -fx-background-color: #f4f7f6; + -fx-font-family: 'Segoe UI', sans-serif; +} + +/* Header */ +.header { + -fx-background-color: linear-gradient(to right, #2c3e50, #4ca1af); + -fx-padding: 20; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 10, 0, 0, 5); +} + +.header-title { + -fx-font-size: 28px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.header-subtitle { + -fx-font-size: 16px; + -fx-text-fill: #ecf0f1; +} + +/* Buttons */ +.button-start { + -fx-background-color: #2ecc71; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-padding: 10 20; + -fx-background-radius: 5; + -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2); +} + +.button-start:hover { + -fx-background-color: #27ae60; +} + +.button-start:disabled { + -fx-background-color: #95a5a6; + -fx-opacity: 0.7; +} + +.button-stop { + -fx-background-color: #e74c3c; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-padding: 10 20; + -fx-background-radius: 5; + -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2); +} + +.button-stop:hover { + -fx-background-color: #c0392b; +} + +.button-stop:disabled { + -fx-background-color: #95a5a6; + -fx-opacity: 0.7; +} + +/* Cards / Panels */ +.card { + -fx-background-color: white; + -fx-background-radius: 8; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.05), 10, 0, 0, 2); + -fx-padding: 0; +} + +.card-header { + -fx-background-color: #ecf0f1; + -fx-background-radius: 8 8 0 0; + -fx-padding: 10 15; + -fx-border-color: #bdc3c7; + -fx-border-width: 0 0 1 0; +} + +.card-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #2c3e50; +} + +.card-content { + -fx-padding: 15; +} + +/* Statistics Grid */ +.stat-label { + -fx-font-size: 14px; + -fx-text-fill: #7f8c8d; +} + +.stat-value { + -fx-font-size: 20px; + -fx-font-weight: bold; + -fx-text-fill: #2980b9; +} + +/* Tables */ +.table-view { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.table-view .column-header-background { + -fx-background-color: #ecf0f1; + -fx-border-color: #bdc3c7; + -fx-border-width: 0 0 1 0; +} + +.table-view .column-header .label { + -fx-text-fill: #2c3e50; + -fx-font-weight: bold; +} + +.table-row-cell { + -fx-background-color: white; + -fx-border-color: transparent; +} + +.table-row-cell:odd { + -fx-background-color: #f9f9f9; +} + +.table-row-cell:selected { + -fx-background-color: #3498db; + -fx-text-fill: white; +} + +/* Footer */ +.footer { + -fx-background-color: #34495e; + -fx-padding: 10 20; +} + +.footer-text { + -fx-text-fill: #ecf0f1; + -fx-font-size: 12px; +} diff --git a/main/src/main/resources/network_config.json b/main/src/main/resources/network_config.json new file mode 100644 index 0000000..f83f4a3 --- /dev/null +++ b/main/src/main/resources/network_config.json @@ -0,0 +1,46 @@ +{ + "intersections": [ + { + "id": "Cr1", + "lights": ["East", "South"], + "routes": { + "Cr2": "East", + "Cr4": "South" + } + }, + { + "id": "Cr2", + "lights": ["West", "East", "South"], + "routes": { + "Cr1": "West", + "Cr3": "East", + "Cr5": "South" + } + }, + { + "id": "Cr3", + "lights": ["West", "South"], + "routes": { + "Cr2": "West", + "S": "South" + } + }, + { + "id": "Cr4", + "lights": ["East", "North"], + "routes": { + "Cr1": "North", + "Cr5": "East" + } + }, + { + "id": "Cr5", + "lights": ["East", "West", "North"], + "routes": { + "Cr2": "North", + "Cr4": "West", + "S": "East" + } + } + ] +} diff --git a/main/src/main/resources/simulation-high.properties b/main/src/main/resources/simulation-high.properties new file mode 100644 index 0000000..5c47079 --- /dev/null +++ b/main/src/main/resources/simulation-high.properties @@ -0,0 +1,126 @@ +# ========================================================= +# Traffic Simulation Configuration - HIGH LOAD SCENARIO +# --------------------------------------------------------- +# High traffic scenario for testing system under heavy load. +# Expected: Significant congestion, large queues, system stress test +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): HIGH LOAD = 1.0 vehicle per second (60 vehicles/minute, 3600 vehicles/hour) +# This is 2x medium load - tests system capacity limits +simulation.arrival.rate=1.0 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=2.0 + +# Routing policy: RANDOM, SHORTEST_PATH, LEAST_CONGESTED +simulation.routing.policy=LEAST_CONGESTED + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Aggressive timings to maximize throughput under high load + +# Intersection 1 (Entry point - longer greens to prevent early backup) +trafficlight.Cr1.South.green=60.0 +trafficlight.Cr1.South.red=3.0 +trafficlight.Cr1.East.green=60.0 +trafficlight.Cr1.East.red=3.0 + +# Intersection 2 (Main hub - CRITICAL BOTTLENECK, maximum green times) +# This is the most critical intersection - all routes converge here +trafficlight.Cr2.South.green=70.0 +trafficlight.Cr2.South.red=3.0 +trafficlight.Cr2.East.green=80.0 +trafficlight.Cr2.East.red=3.0 +trafficlight.Cr2.West.green=70.0 +trafficlight.Cr2.West.red=3.0 + +# Intersection 3 (Path to exit - maximize East throughput to exit) +trafficlight.Cr3.South.green=50.0 +trafficlight.Cr3.South.red=3.0 +trafficlight.Cr3.West.green=40.0 +trafficlight.Cr3.West.red=3.0 + +# Intersection 4 (High throughput needed toward Cr5) +trafficlight.Cr4.East.green=70.0 +trafficlight.Cr4.East.red=3.0 +trafficlight.Cr4.North.green=70.0 +trafficlight.Cr4.North.red=3.0 + +# Intersection 5 (Near exit - MAJOR BOTTLENECK, longest green time) +# All routes funnel through here before exit +trafficlight.Cr5.East.green=90.0 +trafficlight.Cr5.East.red=3.0 +trafficlight.Cr5.West.green=70.0 +trafficlight.Cr5.West.red=3.0 +trafficlight.Cr5.North.green=70.0 +trafficlight.Cr5.North.red=3.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 x car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - HIGH LOAD === +# - Average system time: 200-400+ seconds (3-7+ minutes) +# - Maximum queue sizes: 15-30+ vehicles at Cr2 and Cr5 +# - Average queue sizes: 8-15+ vehicles +# - Severe congestion at Cr2 (main convergence point) +# - Severe congestion at Cr5 (pre-exit bottleneck) +# - System utilization: ~80-95% +# - Many vehicles will remain in system at simulation end +# - Queue growth may be unbounded if arrival rate exceeds service rate +# - Primary bottlenecks: Cr2 (3-way convergence) and Cr5 (final funnel) +# - This scenario tests maximum system capacity and traffic light optimization +# - Expected to demonstrate need for adaptive traffic light policies diff --git a/main/src/main/resources/simulation-low.properties b/main/src/main/resources/simulation-low.properties new file mode 100644 index 0000000..05eee1b --- /dev/null +++ b/main/src/main/resources/simulation-low.properties @@ -0,0 +1,120 @@ +# ========================================================= +# Traffic Simulation Configuration - LOW LOAD SCENARIO +# --------------------------------------------------------- +# Low traffic scenario for testing system under light load. +# Expected: No congestion, minimal queues, fast vehicle throughput +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): LOW LOAD = 0.2 vehicles per second (12 vehicles/minute, 720 vehicles/hour) +# This is approximately 40% of medium load +simulation.arrival.rate=0.2 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=2.0 + +# Routing policy: RANDOM, SHORTEST_PATH, LEAST_CONGESTED +simulation.routing.policy=LEAST_CONGESTED + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Standard timings - should be more than adequate for low load + +# Intersection 1 (Entry point - balanced) +trafficlight.Cr1.South.green=30.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=30.0 +trafficlight.Cr1.East.red=5.0 + +# Intersection 2 (Main hub - shorter cycles, favor East-West) +trafficlight.Cr2.South.green=30.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=30.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=30.0 +trafficlight.Cr2.West.red=5.0 + +# Intersection 3 (Path to exit - favor East) +trafficlight.Cr3.South.green=30.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=30.0 +trafficlight.Cr3.West.red=5.0 + +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.East.green=30.0 +trafficlight.Cr4.East.red=5.0 +trafficlight.Cr4.North.green=30.0 +trafficlight.Cr4.North.red=5.0 + +# Intersection 5 (Near exit - favor East) +trafficlight.Cr5.East.green=30.0 +trafficlight.Cr5.East.red=5.0 +trafficlight.Cr5.West.green=30.0 +trafficlight.Cr5.West.red=5.0 +trafficlight.Cr5.North.green=30.0 +trafficlight.Cr5.North.red=5.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 x car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - LOW LOAD === +# - Average system time: 40-80 seconds +# - Maximum queue sizes: 1-3 vehicles +# - Average queue sizes: < 1 vehicle +# - Vehicles should flow smoothly through the system +# - Minimal waiting at traffic lights (mostly travel time) +# - System utilization: ~20-30% +# - All vehicles should exit within simulation time diff --git a/main/src/main/resources/simulation-medium.properties b/main/src/main/resources/simulation-medium.properties new file mode 100644 index 0000000..9dbe8e1 --- /dev/null +++ b/main/src/main/resources/simulation-medium.properties @@ -0,0 +1,121 @@ +# ========================================================= +# Traffic Simulation Configuration - MEDIUM LOAD SCENARIO +# --------------------------------------------------------- +# Medium traffic scenario for testing system under normal load. +# Expected: Moderate queues, some congestion at peak intersections +# ========================================================= + +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) +intersection.Cr1.host=localhost +intersection.Cr1.port=8001 +intersection.Cr2.host=localhost +intersection.Cr2.port=8002 +intersection.Cr3.host=localhost +intersection.Cr3.port=8003 +intersection.Cr4.host=localhost +intersection.Cr4.port=8004 +intersection.Cr5.host=localhost +intersection.Cr5.port=8005 + +# Exit node +exit.host=localhost +exit.port=9001 + +# Dashboard server +dashboard.host=localhost +dashboard.port=9000 + + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (1800 = 30 minutes) +simulation.duration=1800 + +# Vehicle arrival model: FIXED or POISSON +simulation.arrival.model=POISSON + +# λ (lambda): MEDIUM LOAD = 0.5 vehicles per second (30 vehicles/minute, 1800 vehicles/hour) +# This represents normal traffic conditions +simulation.arrival.rate=0.5 + +# Fixed interval between arrivals (only used if model=FIXED) +simulation.arrival.fixed.interval=2.0 + +# Routing policy: RANDOM, SHORTEST_PATH, LEAST_CONGESTED +simulation.routing.policy=LEAST_CONGESTED + + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= +# Optimized timings for medium load + +# Intersection 1 (Entry point - balanced) +trafficlight.Cr1.South.green=40.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=40.0 +trafficlight.Cr1.East.red=5.0 + +# Intersection 2 (Main hub - CRITICAL BOTTLENECK, longer green times) +trafficlight.Cr2.South.green=45.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=50.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=45.0 +trafficlight.Cr2.West.red=5.0 + +# Intersection 3 (Path to exit - favor East toward exit) +trafficlight.Cr3.South.green=40.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=35.0 +trafficlight.Cr3.West.red=5.0 + +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.East.green=40.0 +trafficlight.Cr4.East.red=5.0 +trafficlight.Cr4.North.green=40.0 +trafficlight.Cr4.North.red=5.0 + +# Intersection 5 (Near exit - POTENTIAL BOTTLENECK, longer green) +trafficlight.Cr5.East.green=50.0 +trafficlight.Cr5.East.red=5.0 +trafficlight.Cr5.West.green=45.0 +trafficlight.Cr5.West.red=5.0 +trafficlight.Cr5.North.green=45.0 +trafficlight.Cr5.North.red=5.0 + + +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 + +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.0 +vehicle.crossing.time.light=2.0 +vehicle.crossing.time.heavy=4.0 + +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 x car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + +# === STATISTICS === + +# Interval between dashboard updates (seconds) +statistics.update.interval=10.0 + +# === EXPECTED BEHAVIOR - MEDIUM LOAD === +# - Average system time: 80-150 seconds +# - Maximum queue sizes: 5-10 vehicles at Cr2 and Cr5 +# - Average queue sizes: 2-5 vehicles +# - Moderate congestion at Cr2 (main hub) and Cr5 (pre-exit) +# - System utilization: ~50-60% +# - Most vehicles should exit, some may remain at simulation end +# - Cr2 is the primary bottleneck (3 directions converge) +# - Cr5 is secondary bottleneck (all routes pass through) diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties index 278ef08..012c6dd 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -31,7 +31,11 @@ dashboard.port=9000 # === SIMULATION CONFIGURATION === # Total duration in seconds (3600 = 1 hour) -simulation.duration=60.0 +simulation.duration=300 + +# Time scaling factor for visualization (real_seconds = sim_seconds * scale) +# 0 = instant (pure DES), 0.01 = 100x speed, 0.1 = 10x speed, 1.0 = real-time +simulation.time.scale=0.01 # Vehicle arrival model: FIXED or POISSON simulation.arrival.model=POISSON @@ -42,59 +46,44 @@ simulation.arrival.rate=0.5 # Fixed interval between arrivals (only used if model=FIXED) simulation.arrival.fixed.interval=2.0 +# Routing policy: RANDOM, SHORTEST_PATH, LEAST_CONGESTED +# RANDOM: selects routes with predefined probabilities (baseline) +# SHORTEST_PATH: always chooses the route with fewest intersections +# LEAST_CONGESTED: dynamically chooses routes to avoid congested areas +simulation.routing.policy=RANDOM + # === TRAFFIC LIGHT TIMINGS === # Format: trafficlight...= -# Intersection 1 -trafficlight.Cr1.North.green=30.0 -trafficlight.Cr1.North.red=30.0 -trafficlight.Cr1.South.green=30.0 -trafficlight.Cr1.South.red=30.0 -trafficlight.Cr1.East.green=30.0 -trafficlight.Cr1.East.red=30.0 -trafficlight.Cr1.West.green=30.0 -trafficlight.Cr1.West.red=30.0 +# Intersection 1 (Entry point - balanced) +trafficlight.Cr1.South.green=60.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=60.0 +trafficlight.Cr1.East.red=5.0 -# Intersection 2 -trafficlight.Cr2.North.green=25.0 -trafficlight.Cr2.North.red=35.0 -trafficlight.Cr2.South.green=25.0 -trafficlight.Cr2.South.red=35.0 -trafficlight.Cr2.East.green=35.0 -trafficlight.Cr2.East.red=25.0 -trafficlight.Cr2.West.green=35.0 -trafficlight.Cr2.West.red=25.0 +# Intersection 2 (Main hub - shorter cycles, favor East-West) +trafficlight.Cr2.South.green=60.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=60.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=60.0 +trafficlight.Cr2.West.red=5.0 -# Intersection 3 -trafficlight.Cr3.North.green=30.0 -trafficlight.Cr3.North.red=30.0 -trafficlight.Cr3.South.green=30.0 -trafficlight.Cr3.South.red=30.0 -trafficlight.Cr3.East.green=30.0 -trafficlight.Cr3.East.red=30.0 -trafficlight.Cr3.West.green=30.0 -trafficlight.Cr3.West.red=30.0 +# Intersection 3 (Path to exit - favor East) +trafficlight.Cr3.South.green=60.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=60.0 +trafficlight.Cr3.West.red=5.0 -# Intersection 4 -trafficlight.Cr4.North.green=30.0 -trafficlight.Cr4.North.red=30.0 -trafficlight.Cr4.South.green=30.0 -trafficlight.Cr4.South.red=30.0 -trafficlight.Cr4.East.green=30.0 -trafficlight.Cr4.East.red=30.0 -trafficlight.Cr4.West.green=30.0 -trafficlight.Cr4.West.red=30.0 +# Intersection 4 (Favor East toward Cr5) +trafficlight.Cr4.East.green=60.0 +trafficlight.Cr4.East.red=5.0 + +# Intersection 5 (Near exit - favor East) +trafficlight.Cr5.East.green=60.0 +trafficlight.Cr5.East.red=5.0 -# Intersection 5 -trafficlight.Cr5.North.green=30.0 -trafficlight.Cr5.North.red=30.0 -trafficlight.Cr5.South.green=30.0 -trafficlight.Cr5.South.red=30.0 -trafficlight.Cr5.East.green=30.0 -trafficlight.Cr5.East.red=30.0 -trafficlight.Cr5.West.green=30.0 -trafficlight.Cr5.West.red=30.0 # === VEHICLE CONFIGURATION === # Probability distribution for vehicle types (must sum to 1.0) @@ -103,11 +92,19 @@ vehicle.probability.light=0.6 vehicle.probability.heavy=0.2 # Average crossing times (in seconds) -vehicle.crossing.time.bike=1.5 +vehicle.crossing.time.bike=1.0 vehicle.crossing.time.light=2.0 vehicle.crossing.time.heavy=4.0 +# Travel times between intersections (in seconds) +# Base time for light vehicles (cars) +vehicle.travel.time.base=1.0 +# Bike travel time = 0.5 x car travel time +vehicle.travel.time.bike.multiplier=0.5 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 + # === STATISTICS === # Interval between dashboard updates (seconds) -statistics.update.interval=10.0 +statistics.update.interval=0.1 diff --git a/main/src/test/java/SimulationTest.java b/main/src/test/java/SimulationTest.java deleted file mode 100644 index b3a49df..0000000 --- a/main/src/test/java/SimulationTest.java +++ /dev/null @@ -1,125 +0,0 @@ -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; - -import sd.config.SimulationConfig; -import sd.engine.SimulationEngine; -import sd.model.Event; -import sd.model.EventType; -import sd.model.Intersection; -import sd.model.TrafficLight; -import sd.model.TrafficLightState; -import sd.model.Vehicle; -import sd.model.VehicleType; -import sd.util.StatisticsCollector; -import sd.util.VehicleGenerator; - -/** - * Basic tests for the simulation components. - */ -class SimulationTest { - - @Test - void testConfigurationLoading() throws IOException { - SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); - - assertEquals(60.0, config.getSimulationDuration()); - assertEquals("POISSON", config.getArrivalModel()); - assertEquals(0.5, config.getArrivalRate()); - assertEquals(10.0, config.getStatisticsUpdateInterval()); - } - - @Test - void testVehicleGeneration() throws IOException { - SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); - VehicleGenerator generator = new VehicleGenerator(config); - - Vehicle vehicle = generator.generateVehicle("TEST1", 0.0); - - assertNotNull(vehicle); - assertEquals("TEST1", vehicle.getId()); - assertNotNull(vehicle.getType()); - assertNotNull(vehicle.getRoute()); - assertTrue(!vehicle.getRoute().isEmpty()); - } - - @Test - void testEventOrdering() { - Event e1 = new Event(5.0, EventType.VEHICLE_ARRIVAL, null, "Cr1"); - Event e2 = new Event(3.0, EventType.VEHICLE_ARRIVAL, null, "Cr2"); - Event e3 = new Event(7.0, EventType.TRAFFIC_LIGHT_CHANGE, null, "Cr1"); - - assertTrue(e2.compareTo(e1) < 0); // e2 should come before e1 - assertTrue(e1.compareTo(e3) < 0); // e1 should come before e3 - } - - @Test - void testIntersectionVehicleQueue() { - Intersection intersection = new Intersection("TestCr"); - TrafficLight light = new TrafficLight("TestCr-N", "North", 30.0, 30.0); - - intersection.addTrafficLight(light); - - Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0, - java.util.Arrays.asList("TestCr", "S")); - - intersection.configureRoute("S", "North"); - - // Advance route to next destination - v1.advanceRoute(); - - intersection.receiveVehicle(v1); - - assertEquals(1, intersection.getTotalQueueSize()); - assertEquals(1, intersection.getTotalVehiclesReceived()); - } - - @Test - void testTrafficLightStateChange() { - TrafficLight light = new TrafficLight("Test-Light", "North", 30.0, 30.0); - - assertEquals(TrafficLightState.RED, light.getState()); - - light.changeState(TrafficLightState.GREEN); - assertEquals(TrafficLightState.GREEN, light.getState()); - - light.changeState(TrafficLightState.RED); - assertEquals(TrafficLightState.RED, light.getState()); - } - - @Test - void testSimulationEngineInitialization() throws IOException { - SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); - SimulationEngine engine = new SimulationEngine(config); - - engine.initialize(); - - assertNotNull(engine.getIntersections()); - assertEquals(5, engine.getIntersections().size()); - - // Check that intersections have traffic lights - for (Intersection intersection : engine.getIntersections().values()) { - assertEquals(3, intersection.getTrafficLights().size()); // North, South, East, West - } - } - - @Test - void testStatisticsCollector() throws IOException { - SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties"); - StatisticsCollector collector = new StatisticsCollector(config); - - Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0, - java.util.Arrays.asList("Cr1", "Cr2", "S")); - - collector.recordVehicleGeneration(v1, 0.0); - assertEquals(1, collector.getTotalVehiclesGenerated()); - - collector.recordVehicleArrival(v1, "Cr1", 1.0); - - collector.recordVehicleExit(v1, 10.0); - assertEquals(1, collector.getTotalVehiclesCompleted()); - } -} diff --git a/main/src/test/java/sd/serialization/SerializationTest.java b/main/src/test/java/sd/serialization/SerializationTest.java deleted file mode 100644 index b43b5a5..0000000 --- a/main/src/test/java/sd/serialization/SerializationTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package sd.serialization; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import sd.model.Message; -import sd.model.Vehicle; -import sd.model.VehicleType; - -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test suite for JSON serialization. - * - * Tests JSON serialization to ensure: - * - Correct serialization and deserialization - * - Data integrity during round-trip conversion - * - Proper error handling - */ -class SerializationTest { - - private MessageSerializer jsonSerializer = new JsonMessageSerializer(); - - private Vehicle testVehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5, - Arrays.asList("Cr1", "Cr2", "Cr5", "S")); - private Message testMessage = new Message( - sd.model.MessageType.VEHICLE_TRANSFER, - "Cr1", - "Cr2", - testVehicle - ); - - - // ===== JSON Serialization Tests ===== - - @Test - @DisplayName("JSON: Should serialize and deserialize Vehicle correctly") - void testJsonVehicleRoundTrip() throws SerializationException { - // Serialize - byte[] data = jsonSerializer.serialize(testVehicle); - assertNotNull(data); - assertTrue(data.length > 0); - - // Print JSON for inspection - System.out.println("JSON Vehicle:"); - System.out.println(new String(data)); - - // Deserialize - Vehicle deserialized = jsonSerializer.deserialize(data, Vehicle.class); - - // Verify - assertNotNull(deserialized); - assertEquals(testVehicle.getId(), deserialized.getId()); - assertEquals(testVehicle.getType(), deserialized.getType()); - assertEquals(testVehicle.getEntryTime(), deserialized.getEntryTime()); - assertEquals(testVehicle.getRoute(), deserialized.getRoute()); - assertEquals(testVehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime()); - assertEquals(testVehicle.getTotalCrossingTime(), deserialized.getTotalCrossingTime()); - } - - @Test - @DisplayName("JSON: Should serialize and deserialize Message correctly") - void testJsonMessageRoundTrip() throws SerializationException { - // Serialize - byte[] data = jsonSerializer.serialize(testMessage); - assertNotNull(data); - - // Print JSON for inspection - System.out.println("\nJSON Message:"); - System.out.println(new String(data)); - - // Deserialize - Message deserialized = jsonSerializer.deserialize(data, Message.class); - - // Verify - assertNotNull(deserialized); - assertEquals(testMessage.getType(), deserialized.getType()); - assertEquals(testMessage.getSenderId(), deserialized.getSenderId()); - assertEquals(testMessage.getDestinationId(), deserialized.getDestinationId()); - } - - @Test - @DisplayName("JSON: Should throw exception on null object") - void testJsonSerializeNull() { - assertThrows(IllegalArgumentException.class, () -> { - jsonSerializer.serialize(null); - }); - } - - @Test - @DisplayName("JSON: Should throw exception on null data") - void testJsonDeserializeNull() { - assertThrows(IllegalArgumentException.class, () -> { - jsonSerializer.deserialize(null, Vehicle.class); - }); - } - - @Test - @DisplayName("JSON: Should throw exception on invalid JSON") - void testJsonDeserializeInvalid() { - byte[] invalidData = "{ invalid json }".getBytes(); - assertThrows(SerializationException.class, () -> { - jsonSerializer.deserialize(invalidData, Vehicle.class); - }); - } - - @Test - @DisplayName("JSON: Should preserve data integrity for complex objects") - void testDataIntegrity() throws SerializationException { - // Create a more complex vehicle - Vehicle vehicle = new Vehicle("V999", VehicleType.HEAVY, 100.5, - Arrays.asList("Cr1", "Cr2", "Cr3", "Cr4", "Cr5", "S")); - vehicle.addWaitingTime(10.5); - vehicle.addWaitingTime(5.3); - vehicle.addCrossingTime(2.1); - vehicle.advanceRoute(); - vehicle.advanceRoute(); - - // Serialize and deserialize - byte[] jsonData = jsonSerializer.serialize(vehicle); - Vehicle deserialized = jsonSerializer.deserialize(jsonData, Vehicle.class); - - // Verify all fields match - assertEquals(vehicle.getId(), deserialized.getId()); - assertEquals(vehicle.getType(), deserialized.getType()); - assertEquals(vehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime()); - assertEquals(vehicle.getCurrentRouteIndex(), deserialized.getCurrentRouteIndex()); - } - - // ===== Factory Tests ===== - - @Test - @DisplayName("Factory: Should create JSON serializer by default") - void testFactoryDefault() { - MessageSerializer serializer = SerializerFactory.createDefault(); - assertNotNull(serializer); - assertEquals("JSON (Gson)", serializer.getName()); - } -} diff --git a/main/testing.txt b/main/testing.txt new file mode 100644 index 0000000..6636918 --- /dev/null +++ b/main/testing.txt @@ -0,0 +1,1055 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] ------------------------------< sd:main >------------------------------- +[INFO] Building main 1.0-SNAPSHOT +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[WARNING] 6 problems were encountered while building the effective model for org.openjfx:javafx-controls:jar:17.0.2 during dependency collection step for project (use -X to see details) +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ main --- +[INFO] Copying 2 resources from src/main/resources to target/classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ main --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ main --- +[INFO] skip non existing resourceDirectory /home/leo/uni/SD/Trabalho-Pratico-SD/main/src/test/resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ main --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.2.5:test (default-test) @ main --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running sd.coordinator.CoordinatorIntegrationTest +Mock Cr1 listening on port 9001 +Connected to Cr1 at localhost:9001 +Mock Cr1 received: VEHICLE_SPAWN +Mock Cr1 stopped +Mock Cr1 listening on port 9001 +Connected to Cr1 at localhost:9001 +Mock Cr1 stopped +Mock Cr1 listening on port 9001 +Connected to Cr1 at localhost:9001 +Mock Cr1 received: VEHICLE_SPAWN +Mock Cr1 stopped +Mock Cr1 listening on port 9001 +Mock Cr2 listening on port 9002 +Mock Cr3 listening on port 9003 +Connected to Cr1 at localhost:9001 +Connected to Cr2 at localhost:9002 +Connected to Cr3 at localhost:9003 +Mock Cr1 received: SHUTDOWN +Mock Cr2 received: SHUTDOWN +Mock Cr3 received: SHUTDOWN +Mock Cr1 stopped +Mock Cr2 stopped +Mock Cr3 stopped +[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.265 s -- in sd.coordinator.CoordinatorIntegrationTest +[INFO] Running sd.coordinator.CoordinatorProcessTest +Coordinator initialized with configuration: + - Simulation duration: 60.0s + - Arrival model: POISSON + - Arrival rate: 0.5 vehicles/s +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.052 s -- in sd.coordinator.CoordinatorProcessTest +[INFO] Running sd.dashboard.DashboardTest +[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.020 s -- in sd.dashboard.DashboardTest +[INFO] Running sd.serialization.SerializationTest +JSON Vehicle: +{"id":"V001","type":"LIGHT","entryTime":10.5,"route":["Cr1","Cr2","Cr5","S"],"currentRouteIndex":0,"totalWaitingTime":0.0,"totalCrossingTime":0.0} + +JSON Message: +{"messageId":"2ee10daa-34c4-4629-9613-bfc4fbd03e46","type":"VEHICLE_TRANSFER","senderId":"Cr1","destinationId":"Cr2","payload":{"id":"V001","type":"LIGHT","entryTime":10.5,"route":["Cr1","Cr2","Cr5","S"],"currentRouteIndex":0,"totalWaitingTime":0.0,"totalCrossingTime":0.0},"timestamp":1763852220055} +[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s -- in sd.serialization.SerializationTest +[INFO] Running sd.ExitNodeProcessTest +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... +Exit node started on port 19001 +Waiting for vehicles...\n + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... +Exit node started on port 19001 +Waiting for vehicles...\n + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... +Exit node started on port 19001 +Waiting for vehicles...\n + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... +Exit node started on port 19001 +Waiting for vehicles...\n + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +New connection accepted from 127.0.0.1 +[Exit] Connection closed from 127.0.0.1 +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... +Exit node started on port 19001 +Waiting for vehicles...\n +New connection accepted from 127.0.0.1 +[Exit] Waiting for message from 127.0.0.1 +New connection accepted from 127.0.0.1 +[Exit] Waiting for message from 127.0.0.1 +New connection accepted from 127.0.0.1 +[Exit] Waiting for message from 127.0.0.1 +[Exit] Waiting for message from 127.0.0.1 + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: [Exit] Connection closed from 127.0.0.1 +0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Connection closed from 127.0.0.1 +[Exit] Connection closed from 127.0.0.1 +[Exit] Shutdown complete. +============================================================ +Exit node initialized + - Exit port: 19001 + - Dashboard: localhost:19000 +Connecting to dashboard... + +[Exit] Shutting down... + +=== EXIT NODE STATISTICS === +Total Vehicles Completed: 0 + +VEHICLE TYPE DISTRIBUTION: +[Exit] Shutdown complete. +============================================================ +[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.273 s -- in sd.ExitNodeProcessTest +[INFO] Running sd.TrafficLightCoordinationTest +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 18.0s, Red: 30.0s) + Created traffic light: East (Green: 18.0s, Red: 30.0s) + Created traffic light: South (Green: 12.0s, Red: 36.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:9000... +[Cr2] Initialization complete. + +=== Testing Traffic Light Fairness === + +[Cr2] Server started on port 8002 + +[Cr2] Starting traffic light threads... + Started thread for: West + Started thread for: South +[Cr2-West] Traffic light thread started. + Started thread for: East +[Cr2-South] Traffic light thread started. +[Cr2-West] State: GREEN +[Cr2-East] Traffic light thread started. +[Cr2] Waiting for incoming connections... + +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN +✓ West has been GREEN + +=== Fairness Results === +West got GREEN time: ✓ YES +South got GREEN time: ✗ NO +East got GREEN time: ✗ NO + +1/3 lights were GREEN during test period + +[Cr2] Shutting down... +[Cr2-South] State: GREEN +[Cr2-East] State: GREEN +[Cr2-West] Traffic light thread interrupted. +[Cr2-South] Traffic light thread stopped. +[Cr2-East] Traffic light thread stopped. +[Cr2-West] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 18.0s, Red: 30.0s) + Created traffic light: East (Green: 18.0s, Red: 30.0s) + Created traffic light: South (Green: 12.0s, Red: 36.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:9000... +[Cr2] Initialization complete. + +=== Testing Traffic Light Mutual Exclusion === + +[Cr2] Server started on port 8002 + +[Cr2] Starting traffic light threads... + Started thread for: West + Started thread for: South +[Cr2-West] Traffic light thread started. +[Cr2-West] State: GREEN + Started thread for: East +[Cr2-South] Traffic light thread started. +[Cr2] Waiting for incoming connections... + +[Cr2-East] Traffic light thread started. + +=== Test Results === +Maximum simultaneous GREEN lights: 1 +Total violations detected: 0 + +Traffic light coordination working correctly! + +[Cr2] Shutting down... +[Cr2-West] Traffic light thread interrupted. +[Cr2-South] State: GREEN +[Cr2-West] Traffic light thread stopped. +[Cr2-East] State: GREEN +[Cr2-South] Traffic light thread stopped. +[Cr2-East] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 18.0s, Red: 30.0s) + Created traffic light: East (Green: 18.0s, Red: 30.0s) + Created traffic light: South (Green: 12.0s, Red: 36.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:9000... +[Cr2] Initialization complete. + +=== Testing State Transition Consistency === + +[Cr2] Server started on port 8002 + +[Cr2] Starting traffic light threads... + Started thread for: West + Started thread for: South +[Cr2-South] Traffic light thread started. +[Cr2-South] State: GREEN +[Cr2-West] Traffic light thread started. + Started thread for: East +[Cr2] Waiting for incoming connections... + +[Cr2-East] Traffic light thread started. +South transitioned: RED → GREEN + +Total state transitions observed: 1 + +[Cr2] Shutting down... +[Cr2-South] Traffic light thread interrupted. +[Cr2-South] Traffic light thread stopped. +[Cr2-West] State: GREEN +[Cr2-West] Traffic light thread stopped. +[Cr2-East] State: GREEN +[Cr2-East] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 33.14 s -- in sd.TrafficLightCoordinationTest +[INFO] Running IntersectionProcessTest +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + Started thread for: South + Started thread for: East +[Cr1-South] Traffic light thread started. +[Cr1] Waiting for incoming connections... + +[Cr1-South] State: GREEN +[Cr1-East] Traffic light thread started. + +[Cr1] Shutting down... +[Cr1] New connection accepted from 127.0.0.1 +[Cr1-South] Traffic light thread interrupted. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1-South] Traffic light thread stopped. +[Cr1] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + Started thread for: South + Started thread for: East +[Cr1-South] Traffic light thread started. +[Cr1] Waiting for incoming connections... + +[Cr1-South] State: GREEN +[Cr1-East] Traffic light thread started. + +[Cr1] Shutting down... +[Cr1-South] Traffic light thread interrupted. +[Cr1-South] Traffic light thread stopped. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:18100... +[Cr2] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + +[Cr2] Server started on port 18002 + +[Cr2] Starting traffic light threads... + Started thread for: South + Started thread for: West +[Cr1-South] Traffic light thread started. +[Cr1-South] State: GREEN + Started thread for: East +[Cr1] Waiting for incoming connections... + +[Cr1-East] Traffic light thread started. +[Cr2-West] Traffic light thread started. + Started thread for: South +[Cr2-West] State: GREEN +[Cr2-South] Traffic light thread started. + Started thread for: East +[Cr2] Waiting for incoming connections... + +[Cr2-East] Traffic light thread started. +[Cr1] New connection accepted from 127.0.0.1 +[Cr1] New connection accepted from 127.0.0.1 + +[Cr1] Shutting down... +[Cr1-South] Traffic light thread interrupted. +[Cr1-South] Traffic light thread stopped. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1] Shutdown complete. +============================================================ + + +[Cr2] Shutting down... +[Cr2-West] Traffic light thread interrupted. +[Cr2-South] State: GREEN +[Cr2-West] Traffic light thread stopped. +[Cr2-South] Traffic light thread stopped. +[Cr2-East] State: GREEN +[Cr2-East] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + Started thread for: South +[Cr1-South] Traffic light thread started. + Started thread for: East +[Cr1-South] State: GREEN +[Cr1] Waiting for incoming connections... + +[Cr1-East] Traffic light thread started. + +[Cr1] Shutting down... +[Cr1-South] Traffic light thread interrupted. +[Cr1-South] Traffic light thread stopped. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr4 +============================================================ + +[Cr4] Initializing intersection... + +[Cr4] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + +[Cr4] Configuring routing... + Route configured: To Cr5 -> Use East + Routing configured. +[Cr4] Connecting to dashboard at localhost:18100... +[Cr4] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:18100... +[Cr2] Initialization complete. + +[Cr2] Server started on port 18002 + +[Cr2] Starting traffic light threads... + Started thread for: West + Started thread for: South +[Cr2-West] Traffic light thread started. + Started thread for: East +[Cr2-West] State: GREEN +[Cr2] Waiting for incoming connections... + +[Cr2-South] Traffic light thread started. +[Cr2-East] Traffic light thread started. +[Cr2] New connection accepted from 127.0.0.1 +[Cr2] New connection accepted from 127.0.0.1 + +[Cr2] Shutting down... +[Cr2-South] State: GREEN +[Cr2-South] Traffic light thread stopped. +[Cr2-West] Traffic light thread interrupted. +[Cr2-West] Traffic light thread stopped. +[Cr2-East] State: GREEN +[Cr2-East] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr5 +============================================================ + +[Cr5] Initializing intersection... + +[Cr5] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + +[Cr5] Configuring routing... + Route configured: To S -> Use East + Routing configured. +[Cr5] Connecting to dashboard at localhost:18100... +[Cr5] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + Started thread for: South + Started thread for: East +[Cr1] Waiting for incoming connections... + +[Cr1-South] Traffic light thread started. +[Cr1-South] State: GREEN +[Cr1-East] Traffic light thread started. + +[Cr1] Shutting down... +[Cr1-South] Traffic light thread interrupted. +[Cr1-South] Traffic light thread stopped. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1] Shutdown complete. +============================================================ + +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr3 +============================================================ + +[Cr3] Initializing intersection... + +[Cr3] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr3] Configuring routing... + Route configured: To Cr2 -> Use West + Route configured: To S -> Use South + Routing configured. +[Cr3] Connecting to dashboard at localhost:18100... +[Cr3] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:18100... +[Cr2] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr3 +============================================================ + +[Cr3] Initializing intersection... + +[Cr3] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr3] Configuring routing... + Route configured: To Cr2 -> Use West + Route configured: To S -> Use South + Routing configured. +[Cr3] Connecting to dashboard at localhost:18100... +[Cr3] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr4 +============================================================ + +[Cr4] Initializing intersection... + +[Cr4] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + +[Cr4] Configuring routing... + Route configured: To Cr5 -> Use East + Routing configured. +[Cr4] Connecting to dashboard at localhost:18100... +[Cr4] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr5 +============================================================ + +[Cr5] Initializing intersection... + +[Cr5] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + +[Cr5] Configuring routing... + Route configured: To S -> Use East + Routing configured. +[Cr5] Connecting to dashboard at localhost:18100... +[Cr5] Initialization complete. +============================================================ +INTERSECTION PROCESS: Cr1 +============================================================ +============================================================ +INTERSECTION PROCESS: Cr2 +============================================================ + +[Cr1] Initializing intersection... + +[Cr1] Creating traffic lights... + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr1] Configuring routing... + Route configured: To Cr2 -> Use East + Route configured: To Cr4 -> Use South + Routing configured. +[Cr1] Connecting to dashboard at localhost:18100... +[Cr1] Initialization complete. + +[Cr2] Initializing intersection... + +[Cr2] Creating traffic lights... + Created traffic light: West (Green: 30.0s, Red: 30.0s) + Created traffic light: East (Green: 30.0s, Red: 30.0s) + Created traffic light: South (Green: 30.0s, Red: 30.0s) + +[Cr2] Configuring routing... + Route configured: To Cr1 -> Use West + Route configured: To Cr3 -> Use East + Route configured: To Cr5 -> Use South + Routing configured. +[Cr2] Connecting to dashboard at localhost:18100... +[Cr2] Initialization complete. + +[Cr1] Server started on port 18001 + +[Cr1] Starting traffic light threads... + +[Cr2] Server started on port 18002 + +[Cr2] Starting traffic light threads... + Started thread for: South + Started thread for: West +[Cr1-South] Traffic light thread started. +[Cr1-South] State: GREEN + Started thread for: East +[Cr1] Waiting for incoming connections... + +[Cr2-West] Traffic light thread started. +[Cr2-West] State: GREEN +[Cr1-East] Traffic light thread started. + Started thread for: South +[Cr2-South] Traffic light thread started. + Started thread for: East +[Cr2] Waiting for incoming connections... + +[Cr2-East] Traffic light thread started. +[Cr1] New connection accepted from 127.0.0.1 +[Cr2] New connection accepted from 127.0.0.1 + +[Cr1] Shutting down... +[Cr1-South] Traffic light thread interrupted. +[Cr1-South] Traffic light thread stopped. +[Cr1-East] State: GREEN +[Cr1-East] Traffic light thread stopped. +[Cr1] New connection accepted from 127.0.0.1 +[Cr2] New connection accepted from 127.0.0.1 +[Cr1] Shutdown complete. +============================================================ + + +[Cr2] Shutting down... +[Cr2-West] Traffic light thread interrupted. +[Cr2-West] Traffic light thread stopped. +[Cr2-South] State: GREEN +[Cr2-South] Traffic light thread stopped. +[Cr2-East] State: GREEN +[Cr2-East] Traffic light thread stopped. +[Cr2] Shutdown complete. +============================================================ + +[INFO] Tests run: 20, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.406 s -- in IntersectionProcessTest +[INFO] Running SimulationTest +[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.014 s -- in SimulationTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 66, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 44.392 s +[INFO] Finished at: 2025-11-22T22:57:41Z +[INFO] ------------------------------------------------------------------------