80 Commits

Author SHA1 Message Date
4b90827c2a color update 2025-12-08 08:42:38 +00:00
61277350d8 data update \\ properties unnecessary data removal + translation 2025-12-08 08:27:04 +00:00
7af3fb558b Update branches to include 'main' for workflow triggers 2025-12-08 00:11:32 +00:00
a360dc708e remove testing log file to clean up repository 2025-12-08 00:08:11 +00:00
3250d5a433 Merge pull request #39 from davidalves04/dev
dev merge
2025-12-07 23:58:33 +00:00
65cb0b52f6 Merge branch 'main' into dev 2025-12-07 23:55:33 +00:00
87a8a1bcb6 Merge pull request #38 from davidalves04/cleanup
Cleanup
2025-12-07 23:23:04 +00:00
bce15fe90f Merge branch 'dev' into cleanup 2025-12-07 23:21:44 +00:00
542ce9c8c0 Merge pull request #37 from davidalves04/javadoc
Javadoc
2025-12-07 23:18:41 +00:00
926245986f finish 1st javadoc round 2025-12-07 22:39:21 +00:00
3a3756f701 Translate graph labels and titles to PT 2025-12-07 20:12:43 +00:00
83c3d65e38 more javadoc - dashboard, des, logging 2025-12-07 19:57:40 +00:00
a8ce95e08c Refactor and enhance documentation across multiple classes (Analysis through DashboardStatistics) 2025-12-07 19:33:40 +00:00
Gaa56
b624cfe11e Documentation 2025-12-07 15:51:50 +00:00
a2f9e725de feat: Implement batch performance analysis dialog and routing policies
- Added BatchAnalysisDialog for running multiple simulations and generating reports.
- Implemented LeastCongestedRouteSelector for dynamic routing based on congestion levels.
- Created RandomRouteSelector for baseline random routing strategy.
- Developed ShortestPathRouteSelector to select routes based on the shortest path.
- Defined RouteSelector interface to standardize routing policy implementations.
- Introduced RoutingPolicy enum to manage available routing strategies.
2025-12-07 00:35:06 +00:00
92ff883d4c fixed dash formatting 2025-12-06 00:59:09 +00:00
ea33d61a9e removed tests 2025-12-05 02:42:31 +00:00
240563419b removed empty impl test files 2025-12-05 02:38:11 +00:00
90db380f61 Dash editor and DES impl 2025-12-05 02:29:33 +00:00
David Alves
8fe4e564d3 Update Diagrama de arquitetura - SD.jpg 2025-12-04 22:27:01 +00:00
David Alves
e389c0711e Updated architecture diagram image 2025-12-04 22:25:55 +00:00
d28a77b6a4 small fixes + debug 2025-11-29 00:07:53 +00:00
1b6ad03057 Merge pull request #35 from davidalves04/cleanup
Refactor Simulation Core & Enhance Dashboard UI
2025-11-27 20:20:34 +00:00
David Alves
24fe1c1d67 Update Diagrama de arquitetura - SD.drawio 2025-11-27 19:54:14 +00:00
David Alves
766eabbbe4 Update .gitignore 2025-11-27 15:49:03 +00:00
David Alves
d7b1de1fe3 Update Diagrama de arquitetura - SD.drawio 2025-11-27 15:47:30 +00:00
David Alves
96b3a66b96 Diagram update
@0x1eo @Gaa56 o que acham do diagrama? Mudariam/acrescentariam algo? As legendas, parecem-vos bem?
2025-11-27 15:46:02 +00:00
David Alves
29848b04a6 Update architecture diagram 2025-11-23 23:16:08 +00:00
173d9e54ce test: Reduce traffic light coordination test monitoring duration from 60s to 10s 2025-11-23 23:06:08 +00:00
5202032471 feat: Dynamically set simulation log file path using OS temporary directory and remove isSimulationRunning method. 2025-11-23 23:03:07 +00:00
46d148c9d5 Allow manual trigger for publish-release job 2025-11-23 22:23:13 +00:00
0d85d010bf Sync CI with main branch 2025-11-23 22:14:10 +00:00
906e958729 feat: Introduce Launcher class as the application entry point and update pom.xml to use it. 2025-11-23 21:53:52 +00:00
19709f0d7a feat: update main class to sd.dashboard.DashboardUI in pom.xml configurations. 2025-11-23 21:29:38 +00:00
13fa2f877d refactor: improve traffic light queue processing, add graceful intersection shutdown, and remove obsolete event and serialization classes. 2025-11-23 21:23:33 +00:00
96c5680f41 moved start to dashboard + fixed holding queue - looped sleep might be fine in this case + better customization via CSS file 2025-11-22 23:52:51 +00:00
d74517a27b starting the codebase cleanup for final delivery- single process prototype removal 2025-11-22 22:52:01 +00:00
ce7f642246 slight sim change and engine code fomat 2025-11-22 21:45:16 +00:00
8f97aab836 Merge pull request #34 from davidalves04/dev
testing
2025-11-22 21:43:33 +00:00
David Alves
86c0c4b5b3 Add configurable travel times by vehicle type
@0x1eo can u check this pls
2025-11-22 16:18:02 +00:00
6fdcf376b2 i might kms 2025-11-22 00:13:19 +00:00
David Alves
ecb70fa6a2 Merge pull request #33 from davidalves04/17-create-dashboardserver-process
Dashboard Server Implementation
2025-11-19 19:16:50 +00:00
06f079ce5b fix intersections starting independently with no coordination 2025-11-18 14:29:11 +00:00
72893f87ae added dashboard server and built an example implementation for the message protocol 2025-11-14 02:01:51 +00:00
6b94d727e2 shutdown and teardown fixes + incoming connection handler 2025-11-11 17:28:44 +00:00
84cba39597 bullshit fixes 2025-11-06 20:31:59 +00:00
5dc1b40c88 Merge pull request #32 from davidalves04/14-create-trafficlightthread-class
14 create trafficlightthread class
2025-11-06 13:53:12 +00:00
3117bdf332 Merge branch 'dev' into 14-create-trafficlightthread-class 2025-11-06 13:53:01 +00:00
1140c3ca48 Merge pull request #30 from davidalves04/13-create-exit-node-process
13 create exit node process
2025-11-06 13:49:21 +00:00
Gaa56
484cba1eee Update TrafficLightThread 2025-11-05 13:21:10 +00:00
Gaa56
0e5526c3f6 Merge pull request #31 from davidalves04/dev
Dev
2025-11-05 12:37:48 +00:00
David Alves
cf88db4297 Add traffic light coordination and tests
Sorry to add this on this branch ahah
2025-11-05 12:09:32 +00:00
David Alves
0960a7a141 Add ExitNodeProcess and unit tests 2025-11-05 11:54:34 +00:00
David Alves
3b4f968a59 Merge pull request #29 from davidalves04/12-implement-coordinatorgenerator-process
Coordinator Process Implementation
2025-11-03 00:02:56 +00:00
0c256ad6f5 Fix Intersection Destination - Doubled Advance 2025-11-02 23:56:54 +00:00
340e436063 Merge branch 'dev' into 12-implement-coordinatorgenerator-process 2025-11-02 23:21:36 +00:00
1684a6713e Implementation of the Coordinator Process 2025-11-02 23:17:15 +00:00
22a7081ade Merge pull request #28 from davidalves04/10-create-network-communication-classes
Fix Serialization
2025-11-02 22:39:38 +00:00
3b699556db Merge branch 'dev' into 10-create-network-communication-classes 2025-11-02 22:39:26 +00:00
Gaa56
d078808486 Update SocketConnection 2025-10-30 19:25:27 +00:00
Gaa56
98581b562d Merge pull request #27 from davidalves04/9-design-message-protocol-specification
#10 Req
2025-10-30 18:44:54 +00:00
Gaa56
4710c96450 Create TrafficLightThread Class 2025-10-30 18:06:02 +00:00
f9644bd18c Merge pull request #26 from davidalves04/dev
#12 Req.
2025-10-30 16:09:04 +00:00
David Alves
c6b710ac52 Merge pull request #25 from davidalves04/11-convert-intersection-to-standalone-process
11 convert intersection to standalone process
2025-10-30 16:00:05 +00:00
David Alves
dc4f567e1f Move vehicle route advancement to intersection arrival 2025-10-30 15:57:58 +00:00
David Alves
db5e01021a Refactor IntersectionProcess and add unit tests 2025-10-30 10:41:17 +00:00
David Alves
dab0651dbd Corrected directions 2025-10-29 22:36:58 +00:00
David Alves
4772add574 Merge pull request #24 from davidalves04/dev
Dev
2025-10-27 23:04:15 +00:00
David Alves
ae27115791 Merge pull request #23 from davidalves04/11-convert-intersection-to-standalone-process
Create IntersectionProcess main class
2025-10-27 22:58:55 +00:00
David Alves
684fb408ef Create IntersectionProcess main class 2025-10-27 22:53:37 +00:00
David Alves
d057adeab3 Revert "Enunciado uploaded"
This reverts commit be4e7f66d6.
2025-10-27 22:52:19 +00:00
David Alves
be4e7f66d6 Enunciado uploaded 2025-10-27 18:03:17 +00:00
fd26063f6e Merge pull request #22 from davidalves04/10-create-network-communication-classes
Create network communication classes
2025-10-27 12:29:22 +00:00
Gaa56
d8b59cc502 Deleted MessageSerializer 2025-10-27 09:18:33 +00:00
Gaa56
06c34a198a Removed MessageSerializer 2025-10-27 09:15:33 +00:00
Gaa56
1524188b29 Add connection retry logic 2025-10-26 17:00:34 +00:00
Gaa56
bc1a8da160 Create MessageSerializer utility 2025-10-25 18:00:58 +01:00
Gaa56
96903e4b7c SocketConnection 2025-10-25 17:43:25 +01:00
Gaa56
6c5eab0e72 Create SocketConnection wrapper class 2025-10-25 17:41:55 +01:00
Gaa56
3fe467a2a3 Create MessageProtocol interface 2025-10-22 19:19:28 +01:00
83 changed files with 10711 additions and 3595 deletions

View File

@@ -0,0 +1,110 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Arquitetura SD" id="QKeTeUWuUs8JeLsq44d-">
<mxGraphModel dx="1426" dy="841" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-1" value="&lt;b&gt;CoordinatorProcess&lt;/b&gt;&lt;br&gt;(Cliente Socket)&lt;hr&gt;• VehicleGenerator&lt;br&gt;• Modelo Poisson (λ=0.5)&lt;br&gt;• Liga a Cr1-Cr5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="560" y="40" width="240" height="100" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-2" value="&lt;b&gt;Cr1&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8001&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8001)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="280" y="200" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-3" value="&lt;b&gt;Cr2&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8002&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8002)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="590" y="190" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-5" value="&lt;b&gt;Cr4&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8004&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8004)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="440" y="530" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-6" value="&lt;b&gt;Cr5&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8005&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8005)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="910" y="430" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-7" value="&lt;b&gt;ExitNode (S)&lt;/b&gt;&lt;br&gt;Porta: 9001&lt;br&gt;Servidor Socket&lt;hr&gt;• Recebe veículos finais&lt;br&gt;• Calcula estatísticas:&lt;br&gt; - Tempo no sistema&lt;br&gt; - Tempo de espera&lt;br&gt; - Métricas por tipo&lt;br&gt;• Envia para Dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="727" y="810" width="200" height="170" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-8" value="&lt;b&gt;DashboardServer&lt;/b&gt;&lt;br&gt;Porta: 9000&lt;br&gt;Servidor Socket&lt;hr&gt;• Thread Pool (10 threads)&lt;br&gt;• ConcurrentHashMap&lt;br&gt;• Agrega estatísticas&lt;br&gt;• Display a cada 5s:&lt;br&gt; - Throughput&lt;br&gt; - Tempos médios&lt;br&gt; - Tamanhos de filas" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="1210" y="585" width="200" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-9" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-2" edge="1">
<mxGeometry x="-0.2105" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-10" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-11" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-12" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-13" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-14" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-15" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-16" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-17" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-6" edge="1">
<mxGeometry x="0.3659" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-18" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FF6600;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-19" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FF6600;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-6" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-20" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-21" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-22" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-23" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-24" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-6" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-25" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-26" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-27" value="&lt;b&gt;MessageProtocol&lt;/b&gt;&lt;hr&gt;interface:&lt;br&gt;• getType()&lt;br&gt;• getPayload()&lt;br&gt;• getSourceNode()&lt;br&gt;• getDestinationNode()" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="30" y="30" width="180" height="120" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-28" value="&lt;b&gt;Tipos de Mensagens&lt;/b&gt;&lt;hr&gt;• VEHICLE_TRANSFER&lt;br&gt;• VEHICLE_SPAWN&lt;br&gt;• STATS_UPDATE&lt;br&gt;• TRAFFIC_LIGHT_SYNC&lt;br&gt;• HEARTBEAT" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="20" y="170" width="200" height="120" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="480" y="280" as="sourcePoint" />
<mxPoint x="990" y="440" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-4" value="&lt;b&gt;Cr3&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8003&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8003)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="910" y="200" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-7" target="0K4eb2koB2xQ8duQ1-_a-26" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="500" y="710" as="sourcePoint" />
<mxPoint x="1090" y="520" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-26" value="&lt;b&gt;LEGENDA&lt;/b&gt;&lt;hr&gt;━━━► Comunicação síncrona&lt;br&gt;╌╌╌► Comunicação periódica&lt;br&gt;&lt;br&gt;&lt;b&gt;Cores:&lt;/b&gt;&lt;br&gt;🔵 Azul = Geração&lt;br&gt;🟢 Verde = Transferência&lt;br&gt;🟠 Laranja = Finalização&lt;br&gt;🟣 Roxo = Monitorização&lt;br&gt;&lt;br&gt;&lt;b&gt;Serialização:&lt;/b&gt; JSON (Gson)&lt;br&gt;&lt;b&gt;Protocolo:&lt;/b&gt; TCP/IP" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;spacing=10;" parent="1" vertex="1">
<mxGeometry x="1210" y="825" width="200" height="220" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -3,7 +3,7 @@ name: Java CI with Maven
on:
workflow_dispatch:
push:
branches: [ "dev", "cleanup" ]
branches: [ "main", "dev", "cleanup" ]
tags:
- 'v*.*.*'
pull_request:
@@ -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' || github.ref == 'refs/heads/main'
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

10
.gitignore vendored
View File

@@ -3,6 +3,9 @@
# Log files
*.log
*.trace
logs
*.md
# BlueJ files
*.ctxt
@@ -47,3 +50,10 @@ build/
# Other
*.swp
*.pdf
# JAR built pom file
dependency-reduced-pom.xml
# Python env
venv/

View File

@@ -1,24 +1,168 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" version="28.2.7">
<diagram name="Página-1" id="B1_hHcevBzWlEwI7FSV6">
<mxGraphModel dx="778" dy="476" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Arquitetura SD" id="QKeTeUWuUs8JeLsq44d-">
<mxGraphModel dx="1426" dy="841" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="vcp7vux32DhQR4tKQhnF-8" value="Dashboard" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#C73500;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#fa6800;shape=mxgraph.mscae.oms.dashboard;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="389" y="230" width="50" height="41" as="geometry" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-27" value="&lt;b&gt;MessageProtocol&lt;/b&gt;&lt;hr&gt;interface:&lt;br&gt;• getType()&lt;br&gt;• getPayload()&lt;br&gt;• getSourceNode()&lt;br&gt;• getDestinationNode()" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="30" y="30" width="180" height="120" as="geometry" />
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-12" value="Semaforo.java" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://icons.diagrams.net/icon-cache1/Strabo-2829/traffic_light-1068.png" vertex="1" parent="1">
<mxGeometry x="230" y="350" width="53" height="53" as="geometry" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-28" value="&lt;b&gt;Tipos de Mensagens&lt;/b&gt;&lt;hr&gt;• VEHICLE_TRANSFER&lt;br&gt;• VEHICLE_SPAWN&lt;br&gt;• STATS_UPDATE&lt;br&gt;• TRAFFIC_LIGHT_SYNC&lt;br&gt;• HEARTBEAT" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="20" y="170" width="200" height="120" as="geometry" />
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-13" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;movable=1;resizable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="330" as="sourcePoint" />
<mxPoint x="360" y="280" as="targetPoint" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-26" value="&lt;b&gt;LEGENDA&lt;/b&gt;&lt;hr&gt;━━━► Comunicação síncrona&lt;br&gt;╌╌╌► Comunicação periódica&lt;br&gt;&lt;br&gt;&lt;b&gt;Cores:&lt;/b&gt;&lt;br&gt;🔵 Azul =&amp;nbsp;&lt;span style=&quot;background-color: transparent;&quot;&gt;Criação do veículo&lt;/span&gt;&lt;div&gt;🟢 Verde = Transferência do veículo&lt;br&gt;🟠 Laranja = Chegada ao destino&lt;br&gt;🟣 Roxo =&amp;nbsp;&lt;span style=&quot;background-color: transparent;&quot;&gt;Envio das estatísticas&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;b&gt;Serialização:&lt;/b&gt; JSON (Gson)&lt;br&gt;&lt;b&gt;Protocolo:&lt;/b&gt; TCP/IP&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;spacing=10;" parent="1" vertex="1">
<mxGeometry x="10" y="320" width="220" height="220" as="geometry" />
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-13" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="280" y="40" width="850" height="730" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-20" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.616;exitY=-0.011;exitDx=0;exitDy=0;entryX=0.661;entryY=-0.002;entryDx=0;entryDy=0;entryPerimeter=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="794" y="530" as="targetPoint" />
<Array as="points">
<mxPoint x="99" y="122" />
<mxPoint x="793" y="122" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-14" value="CruzamentoServer.java" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" vertex="1" connectable="0" parent="vcp7vux32DhQR4tKQhnF-13">
<mxGeometry x="-0.3933" relative="1" as="geometry">
<mxPoint x="25" y="25" as="offset" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-1" value="&lt;b&gt;CoordinatorProcess&lt;/b&gt;&lt;br&gt;(Cliente Socket)&lt;hr&gt;• VehicleGenerator&lt;br&gt;• Modelo Poisson&lt;br&gt;• Liga a Cr1-Cr5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="205.6637168141593" width="176.28318584070794" height="101.38888888888889" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-2" value="&lt;b&gt;Cr1&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8001&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8001)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry y="162.22" width="160" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-3" value="&lt;b&gt;Cr2&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8002&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8002)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Thread Semáforo - Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="227.7" y="162.22" width="162.3" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-5" value="&lt;b&gt;Cr4&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8004&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8004)&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry y="486.67" width="160" height="133.33" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-6" value="&lt;b&gt;Cr5&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8005&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8005)&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="220.35" y="486.67" width="169.65" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-7" value="&lt;b&gt;ExitNode (S)&lt;/b&gt;&lt;br&gt;Porta: 9001&lt;br&gt;Servidor Socket&lt;hr&gt;• Recebe veículos finais&lt;br&gt;• Calcula estatísticas:&lt;br&gt; - Tempo no sistema&lt;br&gt; - Tempo de espera&lt;br&gt; - Métricas por tipo&lt;br&gt;• Envia para o Dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="464.07" y="476.53" width="154.6" height="172.36" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-8" value="&lt;b&gt;DashboardServer&lt;/b&gt;&lt;br&gt;Porta: 9000&lt;br&gt;Servidor Socket&lt;hr&gt;• Thread Pool (10 threads)&lt;br&gt;• ConcurrentHashMap&lt;br&gt;• Agrega estatísticas&lt;br&gt;• Display a cada 5s:&lt;br&gt; - Throughput&lt;br&gt; - Tempos médios&lt;br&gt; - Tamanhos de filas" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="683.1" y="540" width="166.9" height="180" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-2" edge="1">
<mxGeometry x="-0.2105" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="309" y="120" />
<mxPoint x="309" y="120" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry x="0.125" y="100" relative="1" as="geometry">
<Array as="points">
<mxPoint x="66.10619469026548" y="446.11111111111114" />
<mxPoint x="66.10619469026548" y="446.11111111111114" />
</Array>
<mxPoint y="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;exitX=1.005;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;align=center;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry x="-0.0178" y="-49" relative="1" as="geometry">
<mxPoint x="139.55752212389382" y="588.0555555555555" as="sourcePoint" />
<mxPoint x="220" y="571" as="targetPoint" />
<Array as="points">
<mxPoint x="220" y="571" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#b46504;strokeWidth=2;fillColor=#fad7ac;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="390" y="580" as="sourcePoint" />
<mxPoint x="462.74" y="580.22" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.981;exitY=0.08;exitDx=0;exitDy=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry x="0.1427" y="-60" relative="1" as="geometry">
<Array as="points">
<mxPoint x="593" y="175" />
<mxPoint x="593" y="140" />
<mxPoint x="764" y="140" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-4" value="&lt;b&gt;Cr3&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8003&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8003)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="462.74" y="162.22" width="167.26" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;entryX=0.186;entryY=0.998;entryDx=0;entryDy=0;entryPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="103" y="620" as="sourcePoint" />
<mxPoint x="710" y="730" as="targetPoint" />
<Array as="points">
<mxPoint x="103" y="730" />
<mxPoint x="714" y="730" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=none;startArrow=classic;startFill=1;endFill=0;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry x="-0.2214" y="26" relative="1" as="geometry">
<mxPoint x="293.8053097345133" y="486.6666666666666" as="sourcePoint" />
<mxPoint x="293.8053097345133" y="324.44444444444446" as="targetPoint" />
<Array as="points" />
<mxPoint x="-17" y="6" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#b46504;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;fillColor=#fad7ac;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="L62mICw2ZrYi1D68OOFe-13" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry x="0.3659" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="541" y="324" as="sourcePoint" />
<mxPoint x="528.8495575221239" y="435.9722222222221" as="targetPoint" />
<Array as="points">
<mxPoint x="541" y="360" />
<mxPoint x="541" y="360" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.621;exitY=-0.003;exitDx=0;exitDy=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry x="0.1427" y="-60" relative="1" as="geometry">
<mxPoint x="330.53097345132744" y="141.94444444444443" as="sourcePoint" />
<mxPoint x="780" y="540" as="targetPoint" />
<Array as="points">
<mxPoint x="329" y="130" />
<mxPoint x="780" y="130" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="326" y="654" as="sourcePoint" />
<mxPoint x="683.1" y="700" as="targetPoint" />
<Array as="points">
<mxPoint x="326.1" y="700" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="556" y="650" as="sourcePoint" />
<mxPoint x="683.0973451327434" y="663.0833333333331" as="targetPoint" />
<Array as="points">
<mxPoint x="556" y="663" />
</Array>
</mxGeometry>
</mxCell>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

620
README.md
View File

@@ -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 ( 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

712
RELATORIO_FINAL.tex Normal file
View File

@@ -0,0 +1,712 @@
\documentclass[12pt,a4paper]{article}
% Pacotes essenciais
\usepackage[utf8]{inputenc}
\usepackage[portuguese]{babel}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{geometry}
\usepackage{fancyhdr}
\usepackage{amsmath}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{float}
\usepackage{caption}
\usepackage{subcaption}
% Configuração da página
\geometry{
left=2.5cm,
right=2.5cm,
top=2.5cm,
bottom=2.5cm
}
% Configuração de hyperlinks
\hypersetup{
colorlinks=true,
linkcolor=blue,
filecolor=magenta,
urlcolor=cyan,
pdftitle={Trabalho Prático - Sistemas Distribuídos},
pdfauthor={David Alves, Leandro Afonso, Gabriel Moreira},
}
% Configuração de código
\lstset{
basicstyle=\ttfamily\footnotesize,
breaklines=true,
frame=single,
numbers=left,
numberstyle=\tiny,
keywordstyle=\color{blue},
commentstyle=\color{green!60!black},
stringstyle=\color{red}
}
% Cabeçalho e rodapé
\pagestyle{fancy}
\fancyhf{}
\rhead{Sistemas Distribuídos}
\lhead{Trabalho Prático}
\rfoot{Página \thepage}
\begin{document}
% ============================================================
% PÁGINA DE TÍTULO
% ============================================================
\begin{titlepage}
\centering
\vspace*{2cm}
{\LARGE\bfseries Licenciatura em Segurança Informática\\e Redes de Computadores\par}
\vspace{1.5cm}
{\Large Unidade Curricular ``Sistemas Distribuídos''\par}
\vspace{0.5cm}
{\large Ano letivo 2024/2025\par}
\vspace{2cm}
{\huge\bfseries Trabalho Prático\par}
\vspace{0.5cm}
{\Large Simulação de Tráfego Urbano Distribuído\par}
\vspace{2cm}
{\large\bfseries Realizado por:\par}
\vspace{0.5cm}
{\large
David Alves, número de aluno 8240231\\
Leandro Afonso, número de aluno [A COMPLETAR]\\
Gabriel Moreira, número de aluno [A COMPLETAR]
\par}
\vspace{1.5cm}
{\large\bfseries Docente da UC:\par}
\vspace{0.3cm}
{\large Ronaldo Moreira Salles\par}
\vfill
{\large 8 de dezembro de 2024\par}
\end{titlepage}
% ============================================================
% ÍNDICE
% ============================================================
\tableofcontents
\newpage
% ============================================================
% INTRODUÇÃO
% ============================================================
\section{Introdução}
O presente trabalho tem como objetivo dotar os alunos com a capacidade de desenvolver uma aplicação distribuída em linguagem Java, simulando um sistema de tráfego urbano. O projeto é desenvolvido de modo a permitir avaliar e gerir as regras de controlo dos semáforos, tendo em conta diferentes cargas de tráfego.
A simulação implementa uma arquitetura de processos distribuídos que comunicam através de sockets TCP/IP, onde cada componente opera de forma autónoma enquanto contribui para o comportamento global do sistema. Este paradigma permite avaliar propriedades como escalabilidade, tolerância a falhas e sincronização distribuída em ambientes concorrentes.
% ============================================================
% O MODELO DE SIMULAÇÃO
% ============================================================
\section{O Modelo de Simulação}
O sistema retrata uma malha três por três, tendo três pontos de entrada (E1, E2 e E3), cinco cruzamentos (de Cr1 a Cr5) e um ponto de saída (S). As arestas representam as ruas e podem ser de sentido único ou duplo. Cada cruzamento é um processo separado, responsável pela gestão local do tráfego através de semáforos independentes que controlam três direções (Sul, Este e Oeste). O ponto de saída recolhe as estatísticas finais dos veículos que terminam o seu percurso.
\subsection{Geração de Veículos}
O processo coordenador (\texttt{CoordinatorProcess}) atua como o gerador de veículos, colocando-os no percurso através dos pontos de entrada seguindo o modelo de \textbf{Poisson} ($\lambda$ configurável). A simulação modela assim a chegada de veículos de forma realista, semelhante ao tráfego real. Esta abordagem significa que o número total de veículos gerados varia entre execuções. Como alternativa, o sistema permite também configurar um modelo de chegadas fixas, onde os veículos chegam em intervalos constantes e previsíveis.
\subsection{Características dos Veículos}
Cada veículo criado tem a probabilidade de ser:
\begin{itemize}
\item \textbf{Mota} (20\%): Tempo de travessia = $0.5 \times$ tempo base
\item \textbf{Carro} (60\%): Tempo de travessia = $1.0 \times$ tempo base
\item \textbf{Camião} (20\%): Tempo de travessia = $2.0 \times$ tempo base
\end{itemize}
Cada veículo possui um identificador único e uma rota predefinida que determina o seu percurso completo até ao nó de saída. As rotas são atribuídas segundo a política de encaminhamento configurada:
\begin{itemize}
\item \textbf{Random}: Escolha aleatória entre todas as rotas possíveis
\item \textbf{Shortest Path}: Seleção da rota com menor número de saltos
\item \textbf{Least Congested}: Escolha da rota com menor congestionamento atual
\end{itemize}
\subsection{Modelo de Eventos Discretos (DES)}
A simulação baseia-se num modelo de eventos discretos, onde cada cruzamento mantém uma fila de prioridade de eventos ordenados cronologicamente por timestamp. Os eventos incluem:
\begin{itemize}
\item Chegadas e partidas de veículos
\item Mudanças de estado dos semáforos
\item Atualizações de estatísticas
\end{itemize}
Cada semáforo opera como uma thread independente, alternando entre os estados \textbf{GREEN} (verde) e \textbf{RED} (vermelho), gerindo uma fila FIFO de veículos que aguardam passagem. Quando um veículo atravessa um cruzamento, é enviado para o processo do próximo cruzamento indicado na sua rota, onde é adicionado à fila do semáforo correspondente à direção necessária.
\subsection{Comunicação Distribuída}
A arquitetura distribuída permite que cada componente seja executado de forma independente. As comunicações são baseadas em sockets TCP/IP, com serialização das mensagens em JSON através da biblioteca \textbf{Gson}. Esta abordagem garante interoperabilidade e facilita a depuração do sistema.
% ============================================================
% ARQUITETURA DO SISTEMA E COMPONENTES
% ============================================================
\section{Arquitetura do Sistema e Componentes}
Foi desenvolvido um diagrama que retrata a arquitetura do projeto, ilustrando a topologia da rede viária e as conexões entre os diversos processos.
\subsection{Componentes Principais}
\subsubsection{Coordenador (\texttt{CoordinatorProcess})}
\begin{itemize}
\item \textbf{Porta}: Coordenação central sem socket servidor próprio (atua como cliente)
\item \textbf{Função}: Geração de veículos, gestão do relógio global de simulação, injeção de carga nos pontos de entrada
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Implementação do modelo de Poisson para chegadas estocásticas
\item Seleção de rotas baseada na política configurada
\item Sincronização temporal da simulação
\end{itemize}
\end{itemize}
\subsubsection{Processos de Cruzamento (\texttt{IntersectionProcess})}
Cada cruzamento mantém um \texttt{ServerSocket} dedicado que aceita ligações de múltiplos clientes simultaneamente, criando uma thread para cada ligação estabelecida:
\begin{itemize}
\item \textbf{Cr1}: Porta 8001
\item \textbf{Cr2}: Porta 8002
\item \textbf{Cr3}: Porta 8003
\item \textbf{Cr4}: Porta 8004
\item \textbf{Cr5}: Porta 8005
\end{itemize}
\textbf{Responsabilidades}:
\begin{itemize}
\item Gestão local de semáforos (3 direções: Sul, Este, Oeste)
\item Processamento de eventos DES locais
\item Encaminhamento de veículos para o próximo destino
\item Reporte periódico de estatísticas ao Dashboard
\end{itemize}
\subsubsection{Nó de Saída (\texttt{ExitNodeProcess})}
\begin{itemize}
\item \textbf{Porta}: 9001
\item \textbf{Função}: Agregação final de métricas
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Cálculo do tempo total de permanência no sistema
\item Throughput global
\item Estatísticas agregadas por tipo de veículo
\end{itemize}
\end{itemize}
\subsubsection{Servidor de Dashboard (\texttt{DashboardServer})}
\begin{itemize}
\item \textbf{Porta}: 9000
\item \textbf{Função}: Monitorização centralizada e visualização em tempo real
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Agregação de estatísticas de todos os processos
\item Deteção de falhas através de heartbeats
\item Renderização da interface gráfica (JavaFX) ou CLI
\end{itemize}
\end{itemize}
\subsection{Sincronização e Concorrência}
O \texttt{DashboardServer} implementa um sistema de agregação com \textbf{10 threads concorrentes} (thread pool) que processam atualizações de estatísticas vindas dos cinco cruzamentos. Utiliza um \texttt{ConcurrentHashMap} para armazenar métricas por cruzamento, permitindo leituras simultâneas enquanto recebe atualizações. O frontend é atualizado a cada \textbf{5 segundos}.
A sincronização entre os processos ocorre através de \textbf{comunicação assíncrona}, onde cada processo continua a sua execução local enquanto envia e recebe mensagens. Os semáforos dentro de cada cruzamento operam de forma autónoma com ciclos independentes. O dashboard identifica a ausência de heartbeats para detetar falhas de processos.
% ============================================================
% CLASSES E MÉTODOS
% ============================================================
\section{Classes e Métodos}
O programa está organizado em diversos packages que contêm classes com responsabilidades distintas. Neste relatório são mencionadas as principais que sustentam o projeto, estando o resto da documentação apoiado em JavaDoc.
\subsection{Package \texttt{sd.model}}
\subsubsection{Classe \texttt{Vehicle}}
Representa cada veículo na simulação com os seguintes atributos:
\begin{itemize}
\item \texttt{vehicleId}: Identificador único
\item \texttt{vehicleType}: Tipo (MOTORCYCLE, CAR, TRUCK)
\item \texttt{entryTime}: Timestamp de entrada no sistema
\item \texttt{route}: Lista de nós que compõem o percurso completo
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{advanceRoute()}: Avança para o próximo nó na rota
\item \texttt{getCurrentDestination()}: Obtém o próximo cruzamento
\item \texttt{addWaitingTime(double time)}: Acumula tempo de espera em filas
\item \texttt{addCrossingTime(double time)}: Acumula tempo de travessia
\end{itemize}
\subsubsection{Classe \texttt{Intersection}}
Gere os cruzamentos, mantendo:
\begin{itemize}
\item Um mapa de semáforos por direção (\texttt{Map<Direction, TrafficLight>})
\item Uma tabela de encaminhamento que mapeia destinos para direções específicas
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{configureRoute(String destination, Direction direction)}: Define a tabela de routing
\item \texttt{receiveVehicle(Vehicle vehicle)}: Recebe veículos e coloca-os na fila do semáforo correspondente
\item \texttt{getTrafficLight(Direction direction)}: Obtém o semáforo de uma direção específica
\end{itemize}
\subsubsection{Classe \texttt{TrafficLight}}
Controla cada semáforo individualmente, utilizando locks (\texttt{ReentrantLock}) para garantir thread-safety.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{addVehicle(Vehicle vehicle)}: Adiciona veículo à fila (FIFO)
\item \texttt{removeVehicle()}: Remove veículo da fila (apenas quando verde)
\item \texttt{changeState(TrafficLightState newState)}: Alterna entre GREEN e RED
\item \texttt{getQueueSize()}: Retorna o tamanho atual da fila
\end{itemize}
\subsubsection{Classe \texttt{Event} (no package \texttt{sd.des})}
Representa eventos discretos na simulação. Implementa \texttt{Comparable<Event>} para ordenação automática cronológica na fila de prioridade.
\textbf{Atributos:}
\begin{itemize}
\item \texttt{timestamp}: Momento em que o evento deve ocorrer
\item \texttt{eventType}: Tipo do evento (VEHICLE\_ARRIVAL, TRAFFIC\_LIGHT\_CHANGE, etc.)
\item \texttt{associatedData}: Payload específico do evento
\end{itemize}
\subsubsection{Classe \texttt{Message}}
Define a estrutura das mensagens trocadas entre processos.
\textbf{Atributos:}
\begin{itemize}
\item \texttt{messageId}: Identificador único da mensagem
\item \texttt{messageType}: Tipo da mensagem (VEHICLE\_TRANSFER, STATS\_UPDATE, etc.)
\item \texttt{sourceNode}: Nó de origem
\item \texttt{destinationNode}: Nó de destino
\item \texttt{payload}: Conteúdo serializado em JSON
\end{itemize}
\subsection{Package \texttt{sd.coordinator}}
\subsubsection{Classe \texttt{CoordinatorProcess}}
Conduz a simulação através do paradigma de eventos discretos distribuído.
\textbf{Responsabilidades:}
\begin{itemize}
\item Inicialização da topologia da rede
\item Geração de veículos segundo distribuição de Poisson
\item Injeção de carga nos pontos de entrada (E1, E2, E3)
\item Seleção de rotas com base na política configurada
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{initialize()}: Prepara o sistema, estabelece conexões com interseções
\item \texttt{run()}: Loop principal de geração de veículos
\item \texttt{generateAndInjectVehicle()}: Cria um novo veículo e injeta-o num ponto de entrada
\item \texttt{selectRouteForVehicle()}: Determina a rota com base na política ativa
\end{itemize}
\subsubsection{Classe \texttt{SocketClient}}
Facilita a comunicação de rede do lado do cliente.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{connect(String host, int port)}: Estabelece ligação TCP
\item \texttt{send(Message message)}: Serializa e transmite mensagem
\item \texttt{close()}: Encerra ligação e liberta recursos
\end{itemize}
\subsection{Package \texttt{sd}}
\subsubsection{Classe \texttt{IntersectionProcess}}
Representa um nó de processamento autónomo (Worker Node) na malha distribuída.
\textbf{Arquitetura híbrida:}
\begin{itemize}
\item \textbf{Reativa (Network I/O)}: Threads dedicadas aceitam conexões TCP e injetam veículos nas filas
\item \textbf{Proativa (DES Engine)}: Thread de processamento de eventos gere a lógica temporal
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{initialize()}: Carrega configurações, cria semáforos e rotas
\item \texttt{startTrafficLightThreads()}: Inicia threads de controlo dos semáforos
\item \texttt{startServerSocket()}: Aceita conexões de veículos de outros processos
\item \texttt{sendVehicleToNextDestination(Vehicle vehicle)}: Encaminha veículos pela rede
\item \texttt{reportStatistics()}: Envia métricas ao Dashboard periodicamente
\item \texttt{shutdown()}: Encerra serviços e liberta recursos
\end{itemize}
\subsection{Package \texttt{sd.util}}
\subsubsection{Classe \texttt{VehicleGenerator}}
Responsável pela criação de veículos segundo o modelo de Poisson.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{generateVehicle()}: Cria veículo com tipo e rota selecionados aleatoriamente
\item \texttt{getNextArrivalTime(double lambda)}: Calcula o momento da próxima chegada usando distribuição exponencial
\end{itemize}
\subsubsection{Classe \texttt{StatisticsCollector}}
Agrega métricas do sistema, rastreando veículos em trânsito e mantendo contadores globais.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{recordVehicleGeneration(Vehicle vehicle)}: Regista criação de veículo
\item \texttt{recordVehicleArrival(Vehicle vehicle, String intersection)}: Regista chegada a cruzamento
\item \texttt{recordVehicleDeparture(Vehicle vehicle, String intersection)}: Regista partida de cruzamento
\item \texttt{recordVehicleCompletion(Vehicle vehicle)}: Regista conclusão do percurso
\item \texttt{getStatistics()}: Retorna snapshot das métricas atuais
\item \texttt{printStatistics()}: Gera relatórios com throughput, tempos médios, etc.
\end{itemize}
\subsection{Package \texttt{sd.config}}
\subsubsection{Classe \texttt{SimulationConfig}}
Carrega parâmetros do ficheiro \texttt{simulation.properties} (ou variantes \texttt{simulation-low.properties}, \texttt{simulation-medium.properties}, \texttt{simulation-high.properties}).
\textbf{Parâmetros configuráveis:}
\begin{itemize}
\item Duração da simulação
\item Taxa de chegada $\lambda$ (lambda)
\item Probabilidades de tipos de veículos
\item Tempos de travessia base
\item Tempos de ciclo dos semáforos
\item Política de encaminhamento
\end{itemize}
\subsection{Package \texttt{sd.routing}}
\subsubsection{Políticas de Encaminhamento}
O sistema suporta três estratégias de seleção de rotas:
\begin{itemize}
\item \texttt{RandomRouteSelector}: Escolha equiprovável entre todas as rotas disponíveis.
\item \texttt{ShortestPathRouteSelector}: Seleção da rota com menor número de saltos (hops).
\item \texttt{LeastCongestedRouteSelector}: Escolha da rota com menor congestionamento atual, baseada em feedback dos cruzamentos sobre tamanhos de filas.
\end{itemize}
% ============================================================
% AVALIAÇÃO DO DESEMPENHO DO SISTEMA
% ============================================================
\section{Avaliação do Desempenho do Sistema}
A avaliação do sistema baseia-se em métricas recolhidas pela classe \texttt{DashboardStatistics}, que monitoriza:
\begin{itemize}
\item Número de veículos gerados
\item Número de veículos que completaram o percurso
\item Taxa de conclusão (\%)
\item Tempo médio no sistema (segundos)
\item Tempo médio de espera acumulado em filas (segundos)
\item Throughput (veículos/segundo)
\end{itemize}
\subsection{Cenários de Carga}
Foram configurados três cenários de carga de tráfego, cada um executado 5 vezes para obter estatísticas fiáveis.
\subsubsection{Tráfego Leve (LOW LOAD)}
\textbf{Configuração:} \texttt{simulation-low.properties} - $\lambda = 0.2$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Leve}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 364.60 & 9.34 & [351.30, 377.90] & 350 & 373 \\
Veículos Completados & 219.60 & 31.19 & [175.22, 263.98] & 187 & 263 \\
Taxa de Conclusão (\%) & \textbf{60.38} & 9.71 & [46.57, 74.20] & 50.40 & 72.85 \\
Tempo Médio no Sistema (s) & \textbf{33.04} & 7.41 & [22.50, 43.58] & 23.36 & 42.28 \\
Tempo Médio de Espera (s) & 13.78 & 3.43 & [8.82, 18.73] & 9.72 & 17.53 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com carga leve, a rede opera abaixo da capacidade sem congestionamento significativo. A taxa de conclusão superior a 60\% e o tempo médio no sistema de apenas 33 segundos demonstram que o sistema consegue processar eficientemente o tráfego quando a chegada de veículos é esparsa.
\subsubsection{Tráfego Moderado (MEDIUM LOAD)}
\textbf{Configuração:} \texttt{simulation-medium.properties} - $\lambda = 0.5$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Moderado}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 927.20 & 32.48 & [880.97, 973.43] & 886 & 954 \\
Veículos Completados & 419.40 & 90.64 & [290.42, 548.38] & 312 & 535 \\
Taxa de Conclusão (\%) & \textbf{45.23} & 9.64 & [31.50, 58.95] & 34.74 & 56.08 \\
Tempo Médio no Sistema (s) & \textbf{44.48} & 6.81 & [34.79, 54.18] & 35.08 & 52.56 \\
Tempo Médio de Espera (s) & 21.05 & 4.73 & [13.55, 28.56] & 14.60 & 25.60 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com tráfego moderado, observa-se o início de congestionamento. A taxa de conclusão cai para 45\% e o tempo médio no sistema aumenta 34\% relativamente ao cenário leve. As filas começam a formar-se nos cruzamentos principais (Cr2 e Cr5).
\subsubsection{Tráfego Intenso (HIGH LOAD)}
\textbf{Configuração:} \texttt{simulation-high.properties} - $\lambda = 1.0$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Intenso}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 1813.80 & 41.93 & [1754.13, 1873.47] & 1782 & 1872 \\
Veículos Completados & 651.00 & 354.20 & [146.96, 1155.04] & 179 & 953 \\
Taxa de Conclusão (\%) & \textbf{35.92} & 19.44 & [8.25, 63.58] & 9.70 & 50.91 \\
Tempo Médio no Sistema (s) & \textbf{60.15} & 6.17 & [51.38, 68.93] & 53.09 & 65.41 \\
Tempo Médio de Espera (s) & 37.82 & 5.59 & [29.67, 45.96] & 31.51 & 44.58 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com tráfego intenso, a rede aproxima-se da saturação. A taxa de conclusão cai para apenas 36\% com elevada variabilidade ($\sigma = 19.44\%$), indicando comportamento instável. O tempo médio no sistema atinge 60 segundos, mas o aspecto mais crítico é o tempo médio de espera de 37.82 segundos, representando 63\% do tempo total no sistema. A alta variabilidade sugere que o sistema está perto do colapso, com algumas execuções a atingir apenas 9.7\% de taxa de conclusão.
\subsection{Análise Comparativa}
\begin{table}[H]
\centering
\caption{Comparação entre Cenários de Carga}
\begin{tabular}{lrrrr}
\toprule
\textbf{Cenário} & \textbf{$\lambda$ (v/s)} & \textbf{Taxa Conclusão (\%)} & \textbf{Tempo Sistema (s)} & \textbf{Tempo Espera (s)} \\
\midrule
Leve & 0.2 & 60.38 & 33.04 & 13.78 \\
Moderado & 0.5 & 45.23 & 44.48 & 21.05 \\
Intenso & 1.0 & 35.92 & 60.15 & 37.82 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Observações:}
\begin{enumerate}
\item \textbf{Relação não-linear}: Dobrar a taxa de chegada ($0.2 \rightarrow 0.5$) reduz a taxa de conclusão em 25\%, mas quadruplicar ($0.2 \rightarrow 1.0$) reduz apenas 40\%, sugerindo efeitos de saturação.
\item \textbf{Gargalos identificados}: Os cruzamentos Cr2 e Cr5 funcionam como pontos de convergência, acumulando os maiores tamanhos de fila.
\item \textbf{Tempo de espera dominante}: Em carga intensa, 63\% do tempo no sistema é gasto em filas.
\end{enumerate}
\subsection{Otimizações Testadas}
\subsubsection{Configuração de Ciclos de Semáforos}
Foram testadas diferentes configurações de temporização:
\textbf{Ciclos Longos} (verde 60s, vermelho 5s):
\begin{itemize}
\item Maior vazão (throughput) por ciclo
\item Aumento significativo da espera para direções em vermelho
\item Adequado para tráfego unidirecional dominante
\end{itemize}
\textbf{Ciclos Curtos} (verde 8-10s, vermelho 5s):
\begin{itemize}
\item Distribuição mais equitativa da capacidade
\item Redução da espera máxima
\item Perda de eficiência devido ao overhead de mudanças de estado
\end{itemize}
\textbf{Ciclos Otimizados} (diferenciados por cruzamento):
\begin{itemize}
\item Cr2 e Cr5: verde 30-40s (pontos de convergência)
\item Cr1, Cr3, Cr4: verde 15-20s (tráfego distribuído)
\item Melhor compromisso entre throughput e equidade
\end{itemize}
\subsubsection{Políticas de Encaminhamento}
\textbf{Random}: Baseline - distribuição uniforme da carga mas sem otimização.
\textbf{Shortest Path}: Minimiza latência individual mas pode criar hotspots nos caminhos mais curtos.
\textbf{Least Congested}: Balanceamento dinâmico da carga, requer feedback em tempo real dos cruzamentos. Reduz congestionamento mas pode aumentar latência individual ao escolher caminhos mais longos.
% ============================================================
% DASHBOARD
% ============================================================
\section{Dashboard}
O sistema de monitorização foi implementado utilizando \textbf{JavaFX} para a interface gráfica, fornecendo visualização em tempo real do estado da simulação.
\subsection{Características Principais}
\subsubsection{Interface Gráfica}
\textbf{Estatísticas Globais}: Painel superior com métricas agregadas do sistema
\begin{itemize}
\item Veículos gerados vs. completados
\item Taxa de conclusão em tempo real
\item Throughput atual
\item Tempo médio no sistema
\end{itemize}
\textbf{Tabelas Dinâmicas}:
\begin{itemize}
\item Estatísticas por tipo de veículo (Mota, Carro, Camião)
\item Métricas por interseção (Cr1-Cr5)
\item Tamanhos de fila por direção
\item Estados dos semáforos (cores indicativas)
\end{itemize}
\textbf{Controlos de Simulação}:
\begin{itemize}
\item Botão \textbf{Start}: Inicia todos os processos distribuídos
\item Botão \textbf{Stop}: Termina a simulação graciosamente
\item Indicador de estado do sistema
\item Heartbeat visual para cada processo
\end{itemize}
\subsubsection{Arquitetura de Concorrência}
O \texttt{DashboardServer} utiliza:
\begin{itemize}
\item \textbf{Thread Pool} com 10 threads concorrentes para processar atualizações
\item \texttt{ConcurrentHashMap} para armazenamento thread-safe de métricas
\item \texttt{ScheduledExecutorService} para atualizações periódicas da UI (intervalo de 5 segundos)
\item \texttt{Platform.runLater()} para marshalling seguro de atualizações para a UI thread do JavaFX
\end{itemize}
\subsubsection{Deteção de Falhas}
O sistema implementa monitorização de saúde através de:
\begin{itemize}
\item \textbf{Heartbeats}: Cada processo envia sinais periódicos (intervalo configurável)
\item \textbf{Timeout de conexão}: Identifica processos não-responsivos
\item \textbf{Indicadores visuais}: Marcação a vermelho de nós com falhas
\item \textbf{Logs de eventos}: Registo de todas as anomalias detetadas
\end{itemize}
\subsection{Análise em Batch}
O sistema inclui uma ferramenta de análise estatística (\texttt{MultiRunAnalyzer}) que:
\begin{itemize}
\item Executa múltiplas simulações automaticamente
\item Calcula médias, medianas, desvios padrão e intervalos de confiança (95\%)
\item Gera relatórios em formato texto e CSV
\item Permite comparação entre diferentes configurações
\end{itemize}
% ============================================================
% CONCLUSÃO
% ============================================================
\section{Conclusão}
O trabalho desenvolvido demonstra a implementação bem-sucedida de um sistema distribuído para simulação de tráfego urbano, cumprindo os objetivos pedagógicos da unidade curricular.
\subsection{Principais Conquistas}
\begin{enumerate}
\item \textbf{Arquitetura Distribuída Robusta}: Implementação de comunicação assíncrona entre processos autónomos através de sockets TCP/IP, com serialização JSON garantindo interoperabilidade.
\item \textbf{Modelo de Simulação Realista}: Utilização do modelo de eventos discretos (DES) com distribuição de Poisson para chegadas de veículos, aproximando-se de padrões de tráfego reais.
\item \textbf{Escalabilidade Demonstrada}: O sistema mantém-se funcional desde tráfego leve ($\lambda=0.2$) até tráfego intenso ($\lambda=1.0$), embora com degradação expectável de desempenho em saturação.
\item \textbf{Monitorização Abrangente}: Dashboard com visualização em tempo real e capacidade de análise estatística multi-execução, permitindo avaliação rigorosa do desempenho.
\item \textbf{Políticas de Encaminhamento Adaptativas}: Implementação de três estratégias distintas (Random, Shortest Path, Least Congested) demonstrando flexibilidade arquitetural.
\end{enumerate}
\subsection{Aprendizagens}
\begin{itemize}
\item \textbf{Sincronização Distribuída}: Gestão da complexidade inerente à coordenação de múltiplos processos autónomos sem relógio global centralizado.
\item \textbf{Concorrência e Thread-Safety}: Utilização apropriada de locks, estruturas de dados concorrentes e thread pools para garantir correção em ambiente multi-threaded.
\item \textbf{Trade-offs de Desempenho}: Compreensão das relações não-lineares entre carga de entrada, throughput e latência, especialmente em aproximação à saturação.
\item \textbf{Análise Quantitativa}: Aplicação de métodos estatísticos (intervalos de confiança, análise de variância) para avaliação rigorosa de sistemas estocásticos.
\end{itemize}
\subsection{Limitações e Trabalho Futuro}
\textbf{Limitações identificadas:}
\begin{itemize}
\item Rotas estáticas: Veículos não podem desviar-se em resposta a congestionamento dinâmico
\item Capacidade infinita de filas: Sistema não modela bloqueios físicos por falta de espaço
\item Ausência de prioridades: Todos os veículos são tratados igualmente (sem veículos de emergência)
\item Modelo de semáforos simplificado: Não considera fases de amarelo ou coordenação entre cruzamentos adjacentes
\end{itemize}
\textbf{Melhorias propostas:}
\begin{enumerate}
\item Implementação de encaminhamento adaptativo baseado em aprendizagem por reforço
\item Modelação de capacidades finitas com backpressure entre cruzamentos
\item Coordenação de semáforos através de ``ondas verdes'' para corredores prioritários
\item Integração de eventos externos (acidentes, obras, eventos especiais)
\item Visualização 3D da malha viária com animação de veículos em movimento
\end{enumerate}
\subsection{Conclusão Final}
O projeto cumpre integralmente os requisitos da unidade curricular, demonstrando competências na conceção, implementação e avaliação de sistemas distribuídos. Os resultados quantitativos obtidos através das análises multi-execução fornecem insights valiosos sobre o comportamento do sistema sob diferentes condições de carga, validando a abordagem arquitetural adotada.
A experiência adquirida na resolução de desafios de sincronização, gestão de concorrência e análise de desempenho constitui uma base sólida para o desenvolvimento de sistemas distribuídos de maior complexidade em contextos profissionais futuros.
% ============================================================
% BIBLIOGRAFIA
% ============================================================
\begin{thebibliography}{9}
\bibitem{tanenbaum2017}
Tanenbaum, A. S., \& Van Steen, M. (2017).
\textit{Distributed Systems: Principles and Paradigms} (3rd ed.).
Pearson.
\bibitem{coulouris2011}
Coulouris, G., Dollimore, J., Kindberg, T., \& Blair, G. (2011).
\textit{Distributed Systems: Concepts and Design} (5th ed.).
Addison-Wesley.
\bibitem{banks2009}
Banks, J., Carson, J. S., Nelson, B. L., \& Nicol, D. M. (2009).
\textit{Discrete-Event System Simulation} (5th ed.).
Pearson.
\bibitem{oracle2024java}
Oracle. (2024).
\textit{Java Platform, Standard Edition Documentation} (Version 17).
\url{https://docs.oracle.com/en/java/javase/17/}
\bibitem{goetz2006}
Goetz, B., Peierls, T., Bloch, J., Bowbeer, J., Holmes, D., \& Lea, D. (2006).
\textit{Java Concurrency in Practice}.
Addison-Wesley.
\bibitem{oracle2024javafx}
Oracle. (2024).
\textit{JavaFX Documentation} (Version 17).
\url{https://openjfx.io/javadoc/17/}
\bibitem{google2024gson}
Google. (2024).
\textit{Gson User Guide}.
\url{https://github.com/google/gson/blob/master/UserGuide.md}
\bibitem{law2000}
Law, A. M., \& Kelton, W. D. (2000).
\textit{Simulation Modeling and Analysis} (3rd ed.).
McGraw-Hill.
\end{thebibliography}
\vspace{1cm}
\noindent\textbf{Nota:} Este relatório foi elaborado com base na análise do código-fonte do projeto e nos resultados experimentais obtidos através de múltiplas execuções da simulação. Todos os valores estatísticos apresentados foram extraídos dos ficheiros de análise gerados pelo sistema (\texttt{analysis/LOW\_LOAD\_*.txt}, \texttt{analysis/MEDIUM\_LOAD\_*.txt}, \texttt{analysis/HIGH\_LOAD\_*.txt}).
\end{document}

View File

@@ -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 (Cr1Cr5) 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 E1E3
* **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
Everythings working traffic lights, routing, vehicles, stats.
Ready to go distributed next.

198
TODO.md
View File

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

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,1836,348,18.95,75.91,72.28,37.96,151.82
2,1728,663,38.37,52.10,49.52,26.05,104.21
3,1747,539,30.85,116.39,112.54,58.19,232.78
4,1769,149,8.42,89.64,85.89,44.82,179.29
5,1827,1097,60.04,90.49,86.93,45.25,180.98
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 1836 348 18.95 75.91 72.28 37.96 151.82
3 2 1728 663 38.37 52.10 49.52 26.05 104.21
4 3 1747 539 30.85 116.39 112.54 58.19 232.78
5 4 1769 149 8.42 89.64 85.89 44.82 179.29
6 5 1827 1097 60.04 90.49 86.93 45.25 180.98

View File

@@ -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-08 08:20:40
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 1781.40 Desvio Padrão: 48.09
Mediana: 1769.00 IC 95%: [1712.97, 1849.83]
Mín: 1728.00 Máx: 1836.00
Veículos Completados:
Média: 559.20 Desvio Padrão: 358.22
Mediana: 539.00 IC 95%: [49.44, 1068.96]
Mín: 149.00 Máx: 1097.00
Taxa de Conclusão (%):
Média: 31.33 Desvio Padrão: 19.70
Mediana: 30.85 IC 95%: [3.30, 59.36]
Mín: 8.42 Máx: 60.04
Tempo Médio no Sistema (segundos):
Média: 84.91 Desvio Padrão: 23.46
Mediana: 89.64 IC 95%: [51.52, 118.29]
Mín: 52.10 Máx: 116.39
Tempo Médio de Espera (segundos):
Média: 81.43 Desvio Padrão: 23.02
Mediana: 85.89 IC 95%: [48.68, 114.19]
Mín: 49.52 Máx: 112.54
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 111.60 Desvio Padrão: 69.43
Mediana: 105.00 IC 95%: [12.80, 210.40]
Mín: 29.00 Máx: 215.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 78.89 Desvio Padrão: 20.87
Mediana: 89.97 IC 95%: [49.20, 108.59]
Mín: 49.27 Máx: 98.23
--- LIGHT ---
Contagem de Veículos:
Média: 333.80 Desvio Padrão: 221.25
Mediana: 332.00 IC 95%: [18.95, 648.65]
Mín: 90.00 Máx: 669.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 83.83 Desvio Padrão: 24.74
Mediana: 86.14 IC 95%: [48.63, 119.03]
Mín: 51.94 Máx: 120.26
--- HEAVY ---
Contagem de Veículos:
Média: 113.80 Desvio Padrão: 68.36
Mediana: 102.00 IC 95%: [16.53, 211.07]
Mín: 30.00 Máx: 213.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 76.79 Desvio Padrão: 21.46
Mediana: 81.20 IC 95%: [46.26, 107.33]
Mín: 43.10 Máx: 102.14
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 221.40 Desvio Padrão: 226.21
Mediana: 128.00 IC 95%: [-100.50, 543.30]
Mín: 61.00 Máx: 616.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 3.60 Desvio Padrão: 5.90
Mediana: 2.00 IC 95%: [-4.79, 11.99]
Mín: 0.00 Máx: 14.00
Tamanho Médio da Fila:
Média: 3.60 Desvio Padrão: 5.90
Mediana: 2.00 IC 95%: [-4.79, 11.99]
Mín: 0.00 Máx: 14.00
Veículos Processados:
Média: 228.60 Desvio Padrão: 211.41
Mediana: 126.00 IC 95%: [-72.24, 529.44]
Mín: 93.00 Máx: 593.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 1.20 Desvio Padrão: 2.68
Mediana: 0.00 IC 95%: [-2.62, 5.02]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 1.20 Desvio Padrão: 2.68
Mediana: 0.00 IC 95%: [-2.62, 5.02]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 263.80 Desvio Padrão: 240.18
Mediana: 128.00 IC 95%: [-77.98, 605.58]
Mín: 57.00 Máx: 604.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: 95.00 Desvio Padrão: 78.43
Mediana: 62.00 IC 95%: [-16.60, 206.60]
Mín: 43.00 Máx: 231.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 2.80 Desvio Padrão: 3.63
Mediana: 1.00 IC 95%: [-2.37, 7.97]
Mín: 0.00 Máx: 9.00
Tamanho Médio da Fila:
Média: 2.80 Desvio Padrão: 3.63
Mediana: 1.00 IC 95%: [-2.37, 7.97]
Mín: 0.00 Máx: 9.00
Veículos Processados:
Média: 207.60 Desvio Padrão: 166.31
Mediana: 139.00 IC 95%: [-29.06, 444.26]
Mín: 76.00 Máx: 493.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 559.20 Desvio Padrão: 358.22
Mediana: 539.00 IC 95%: [49.44, 1068.96]
Mín: 149.00 Máx: 1097.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-high.properties]:
Gerados: 1836, Completados: 348 (19.0%)
Tempo Médio no Sistema: 75.91s
Tempo Médio de Espera: 72.28s
Execução #2 [simulation-high.properties]:
Gerados: 1728, Completados: 663 (38.4%)
Tempo Médio no Sistema: 52.10s
Tempo Médio de Espera: 49.52s
Execução #3 [simulation-high.properties]:
Gerados: 1747, Completados: 539 (30.9%)
Tempo Médio no Sistema: 116.39s
Tempo Médio de Espera: 112.54s
Execução #4 [simulation-high.properties]:
Gerados: 1769, Completados: 149 (8.4%)
Tempo Médio no Sistema: 89.64s
Tempo Médio de Espera: 85.89s
Execução #5 [simulation-high.properties]:
Gerados: 1827, Completados: 1097 (60.0%)
Tempo Médio no Sistema: 90.49s
Tempo Médio de Espera: 86.93s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,5 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,354,228,64.41,40.36,36.75,20.18,80.72
2,373,261,69.97,40.61,36.87,20.30,81.21
3,353,235,66.57,32.63,29.04,16.32,65.27
4,350,269,76.86,37.39,33.42,18.70,74.78
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 354 228 64.41 40.36 36.75 20.18 80.72
3 2 373 261 69.97 40.61 36.87 20.30 81.21
4 3 353 235 66.57 32.63 29.04 16.32 65.27
5 4 350 269 76.86 37.39 33.42 18.70 74.78

View File

@@ -0,0 +1,204 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-low.properties
Número de Execuções: 4
Data da Análise: 2025-12-08 08:13:57
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 357.50 Desvio Padrão: 10.47
Mediana: 353.50 IC 95%: [340.84, 374.16]
Mín: 350.00 Máx: 373.00
Veículos Completados:
Média: 248.25 Desvio Padrão: 19.82
Mediana: 248.00 IC 95%: [216.71, 279.79]
Mín: 228.00 Máx: 269.00
Taxa de Conclusão (%):
Média: 69.45 Desvio Padrão: 5.44
Mediana: 68.27 IC 95%: [60.79, 78.11]
Mín: 64.41 Máx: 76.86
Tempo Médio no Sistema (segundos):
Média: 37.75 Desvio Padrão: 3.71
Mediana: 38.87 IC 95%: [31.85, 43.65]
Mín: 32.63 Máx: 40.61
Tempo Médio de Espera (segundos):
Média: 34.02 Desvio Padrão: 3.68
Mediana: 35.08 IC 95%: [28.16, 39.88]
Mín: 29.04 Máx: 36.87
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 52.25 Desvio Padrão: 8.58
Mediana: 54.50 IC 95%: [38.60, 65.90]
Mín: 40.00 Máx: 60.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 43.39 Desvio Padrão: 19.65
Mediana: 39.45 IC 95%: [12.12, 74.65]
Mín: 25.04 Máx: 69.63
--- LIGHT ---
Contagem de Veículos:
Média: 151.25 Desvio Padrão: 8.34
Mediana: 152.50 IC 95%: [137.98, 164.52]
Mín: 141.00 Máx: 159.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 28.92 Desvio Padrão: 5.34
Mediana: 28.76 IC 95%: [20.43, 37.42]
Mín: 23.35 Máx: 34.82
--- HEAVY ---
Contagem de Veículos:
Média: 44.75 Desvio Padrão: 6.29
Mediana: 44.00 IC 95%: [34.74, 54.76]
Mín: 39.00 Máx: 52.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 43.02 Desvio Padrão: 13.73
Mediana: 47.91 IC 95%: [21.18, 64.86]
Mín: 22.83 Máx: 53.43
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 0.25 Desvio Padrão: 0.50
Mediana: 0.00 IC 95%: [-0.55, 1.05]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.25 Desvio Padrão: 0.50
Mediana: 0.00 IC 95%: [-0.55, 1.05]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 105.50 Desvio Padrão: 10.66
Mediana: 103.50 IC 95%: [88.54, 122.46]
Mín: 95.00 Máx: 120.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 1.75 Desvio Padrão: 2.87
Mediana: 0.50 IC 95%: [-2.82, 6.32]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 1.75 Desvio Padrão: 2.87
Mediana: 0.50 IC 95%: [-2.82, 6.32]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 119.00 Desvio Padrão: 11.17
Mediana: 122.50 IC 95%: [101.24, 136.76]
Mín: 103.00 Máx: 128.00
--- Cr3 ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 114.75 Desvio Padrão: 15.88
Mediana: 119.00 IC 95%: [89.48, 140.02]
Mín: 93.00 Máx: 128.00
--- Cr4 ---
Tamanho Máximo da Fila:
Média: 1.25 Desvio Padrão: 0.50
Mediana: 1.00 IC 95%: [0.45, 2.05]
Mín: 1.00 Máx: 2.00
Tamanho Médio da Fila:
Média: 1.25 Desvio Padrão: 0.50
Mediana: 1.00 IC 95%: [0.45, 2.05]
Mín: 1.00 Máx: 2.00
Veículos Processados:
Média: 63.00 Desvio Padrão: 11.75
Mediana: 62.00 IC 95%: [44.31, 81.69]
Mín: 50.00 Máx: 78.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 4.50 Desvio Padrão: 2.89
Mediana: 4.50 IC 95%: [-0.09, 9.09]
Mín: 1.00 Máx: 8.00
Tamanho Médio da Fila:
Média: 4.50 Desvio Padrão: 2.89
Mediana: 4.50 IC 95%: [-0.09, 9.09]
Mín: 1.00 Máx: 8.00
Veículos Processados:
Média: 123.00 Desvio Padrão: 24.18
Mediana: 116.50 IC 95%: [84.53, 161.47]
Mín: 103.00 Máx: 156.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 248.25 Desvio Padrão: 19.82
Mediana: 248.00 IC 95%: [216.71, 279.79]
Mín: 228.00 Máx: 269.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-low.properties]:
Gerados: 354, Completados: 228 (64.4%)
Tempo Médio no Sistema: 40.36s
Tempo Médio de Espera: 36.75s
Execução #2 [simulation-low.properties]:
Gerados: 373, Completados: 261 (70.0%)
Tempo Médio no Sistema: 40.61s
Tempo Médio de Espera: 36.87s
Execução #3 [simulation-low.properties]:
Gerados: 353, Completados: 235 (66.6%)
Tempo Médio no Sistema: 32.63s
Tempo Médio de Espera: 29.04s
Execução #4 [simulation-low.properties]:
Gerados: 350, Completados: 269 (76.9%)
Tempo Médio no Sistema: 37.39s
Tempo Médio de Espera: 33.42s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,368,329,89.40,78.34,74.19,39.17,156.67
2,368,218,59.24,60.44,56.64,30.22,120.89
3,349,235,67.34,53.51,49.44,26.76,107.03
4,332,243,73.19,69.63,65.50,34.82,139.27
5,322,221,68.63,47.52,43.77,23.76,95.05
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 368 329 89.40 78.34 74.19 39.17 156.67
3 2 368 218 59.24 60.44 56.64 30.22 120.89
4 3 349 235 67.34 53.51 49.44 26.76 107.03
5 4 332 243 73.19 69.63 65.50 34.82 139.27
6 5 322 221 68.63 47.52 43.77 23.76 95.05

View File

@@ -0,0 +1,215 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-low.properties
Número de Execuções: 5
Data da Análise: 2025-12-08 08:19:33
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 347.80 Desvio Padrão: 20.81
Mediana: 349.00 IC 95%: [318.18, 377.42]
Mín: 322.00 Máx: 368.00
Veículos Completados:
Média: 249.20 Desvio Padrão: 45.76
Mediana: 235.00 IC 95%: [184.08, 314.32]
Mín: 218.00 Máx: 329.00
Taxa de Conclusão (%):
Média: 71.56 Desvio Padrão: 11.17
Mediana: 68.63 IC 95%: [55.66, 87.46]
Mín: 59.24 Máx: 89.40
Tempo Médio no Sistema (segundos):
Média: 61.89 Desvio Padrão: 12.34
Mediana: 60.44 IC 95%: [44.33, 79.45]
Mín: 47.52 Máx: 78.34
Tempo Médio de Espera (segundos):
Média: 57.91 Desvio Padrão: 12.21
Mediana: 56.64 IC 95%: [40.54, 75.28]
Mín: 43.77 Máx: 74.19
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 48.20 Desvio Padrão: 12.38
Mediana: 47.00 IC 95%: [30.59, 65.81]
Mín: 36.00 Máx: 68.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 51.22 Desvio Padrão: 16.62
Mediana: 46.02 IC 95%: [27.56, 74.87]
Mín: 40.06 Máx: 80.31
--- LIGHT ---
Contagem de Veículos:
Média: 151.00 Desvio Padrão: 22.64
Mediana: 146.00 IC 95%: [118.78, 183.22]
Mín: 133.00 Máx: 189.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 58.33 Desvio Padrão: 11.58
Mediana: 53.58 IC 95%: [41.85, 74.80]
Mín: 45.31 Máx: 74.17
--- HEAVY ---
Contagem de Veículos:
Média: 50.00 Desvio Padrão: 13.77
Mediana: 47.00 IC 95%: [30.41, 69.59]
Mín: 35.00 Máx: 72.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 60.73 Desvio Padrão: 34.92
Mediana: 44.79 IC 95%: [11.04, 110.42]
Mín: 40.26 Máx: 122.51
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 5.00 Desvio Padrão: 4.47
Mediana: 4.00 IC 95%: [-1.36, 11.36]
Mín: 0.00 Máx: 12.00
Tamanho Médio da Fila:
Média: 5.00 Desvio Padrão: 4.47
Mediana: 4.00 IC 95%: [-1.36, 11.36]
Mín: 0.00 Máx: 12.00
Veículos Processados:
Média: 87.00 Desvio Padrão: 29.01
Mediana: 93.00 IC 95%: [45.72, 128.28]
Mín: 56.00 Máx: 123.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 95.20 Desvio Padrão: 24.86
Mediana: 100.00 IC 95%: [59.82, 130.58]
Mín: 61.00 Máx: 125.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 91.40 Desvio Padrão: 28.68
Mediana: 103.00 IC 95%: [50.58, 132.22]
Mín: 56.00 Máx: 126.00
--- Cr4 ---
Tamanho Máximo da Fila:
Média: 0.80 Desvio Padrão: 0.84
Mediana: 1.00 IC 95%: [-0.39, 1.99]
Mín: 0.00 Máx: 2.00
Tamanho Médio da Fila:
Média: 0.80 Desvio Padrão: 0.84
Mediana: 1.00 IC 95%: [-0.39, 1.99]
Mín: 0.00 Máx: 2.00
Veículos Processados:
Média: 63.00 Desvio Padrão: 21.11
Mediana: 62.00 IC 95%: [32.96, 93.04]
Mín: 38.00 Máx: 87.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 2.20 Desvio Padrão: 2.59
Mediana: 1.00 IC 95%: [-1.48, 5.88]
Mín: 0.00 Máx: 5.00
Tamanho Médio da Fila:
Média: 2.20 Desvio Padrão: 2.59
Mediana: 1.00 IC 95%: [-1.48, 5.88]
Mín: 0.00 Máx: 5.00
Veículos Processados:
Média: 126.40 Desvio Padrão: 45.39
Mediana: 111.00 IC 95%: [61.81, 190.99]
Mín: 86.00 Máx: 203.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 249.20 Desvio Padrão: 45.76
Mediana: 235.00 IC 95%: [184.08, 314.32]
Mín: 218.00 Máx: 329.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-low.properties]:
Gerados: 368, Completados: 329 (89.4%)
Tempo Médio no Sistema: 78.34s
Tempo Médio de Espera: 74.19s
Execução #2 [simulation-low.properties]:
Gerados: 368, Completados: 218 (59.2%)
Tempo Médio no Sistema: 60.44s
Tempo Médio de Espera: 56.64s
Execução #3 [simulation-low.properties]:
Gerados: 349, Completados: 235 (67.3%)
Tempo Médio no Sistema: 53.51s
Tempo Médio de Espera: 49.44s
Execução #4 [simulation-low.properties]:
Gerados: 332, Completados: 243 (73.2%)
Tempo Médio no Sistema: 69.63s
Tempo Médio de Espera: 65.50s
Execução #5 [simulation-low.properties]:
Gerados: 322, Completados: 221 (68.6%)
Tempo Médio no Sistema: 47.52s
Tempo Médio de Espera: 43.77s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,891,202,22.67,69.75,66.09,34.87,139.50
2,871,340,39.04,68.73,64.73,34.37,137.46
3,953,541,56.77,68.64,65.24,34.32,137.28
4,888,501,56.42,60.85,57.48,30.42,121.69
5,869,387,44.53,58.29,55.37,29.15,116.58
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 891 202 22.67 69.75 66.09 34.87 139.50
3 2 871 340 39.04 68.73 64.73 34.37 137.46
4 3 953 541 56.77 68.64 65.24 34.32 137.28
5 4 888 501 56.42 60.85 57.48 30.42 121.69
6 5 869 387 44.53 58.29 55.37 29.15 116.58

View File

@@ -0,0 +1,209 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-medium.properties
Número de Execuções: 5
Data da Análise: 2025-12-08 08:20:05
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 894.40 Desvio Padrão: 34.20
Mediana: 888.00 IC 95%: [845.73, 943.07]
Mín: 869.00 Máx: 953.00
Veículos Completados:
Média: 394.20 Desvio Padrão: 134.99
Mediana: 387.00 IC 95%: [202.11, 586.29]
Mín: 202.00 Máx: 541.00
Taxa de Conclusão (%):
Média: 43.89 Desvio Padrão: 14.12
Mediana: 44.53 IC 95%: [23.80, 63.97]
Mín: 22.67 Máx: 56.77
Tempo Médio no Sistema (segundos):
Média: 65.25 Desvio Padrão: 5.28
Mediana: 68.64 IC 95%: [57.73, 72.77]
Mín: 58.29 Máx: 69.75
Tempo Médio de Espera (segundos):
Média: 61.78 Desvio Padrão: 4.97
Mediana: 64.73 IC 95%: [54.71, 68.86]
Mín: 55.37 Máx: 66.09
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 83.60 Desvio Padrão: 28.80
Mediana: 88.00 IC 95%: [42.62, 124.58]
Mín: 42.00 Máx: 112.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 64.62 Desvio Padrão: 9.80
Mediana: 65.07 IC 95%: [50.67, 78.57]
Mín: 53.82 Máx: 77.73
--- LIGHT ---
Contagem de Veículos:
Média: 234.80 Desvio Padrão: 86.82
Mediana: 221.00 IC 95%: [111.26, 358.34]
Mín: 119.00 Máx: 328.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 60.49 Desvio Padrão: 4.15
Mediana: 61.41 IC 95%: [54.58, 66.39]
Mín: 53.78 Máx: 65.19
--- HEAVY ---
Contagem de Veículos:
Média: 75.80 Desvio Padrão: 21.70
Mediana: 78.00 IC 95%: [44.93, 106.67]
Mín: 41.00 Máx: 101.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 62.90 Desvio Padrão: 13.27
Mediana: 63.80 IC 95%: [44.01, 81.79]
Mín: 42.19 Máx: 78.56
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 2.00 Desvio Padrão: 2.55
Mediana: 1.00 IC 95%: [-1.63, 5.63]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 2.00 Desvio Padrão: 2.55
Mediana: 1.00 IC 95%: [-1.63, 5.63]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 106.20 Desvio Padrão: 62.26
Mediana: 72.00 IC 95%: [17.60, 194.80]
Mín: 56.00 Máx: 208.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 1.40 Desvio Padrão: 3.13
Mediana: 0.00 IC 95%: [-3.05, 5.85]
Mín: 0.00 Máx: 7.00
Tamanho Médio da Fila:
Média: 1.40 Desvio Padrão: 3.13
Mediana: 0.00 IC 95%: [-3.05, 5.85]
Mín: 0.00 Máx: 7.00
Veículos Processados:
Média: 123.60 Desvio Padrão: 90.00
Mediana: 102.00 IC 95%: [-4.47, 251.67]
Mín: 49.00 Máx: 275.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 102.60 Desvio Padrão: 50.09
Mediana: 104.00 IC 95%: [31.32, 173.88]
Mín: 55.00 Máx: 181.00
--- Cr4 ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 68.80 Desvio Padrão: 27.10
Mediana: 60.00 IC 95%: [30.24, 107.36]
Mín: 47.00 Máx: 113.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 1.20 Desvio Padrão: 2.17
Mediana: 0.00 IC 95%: [-1.89, 4.29]
Mín: 0.00 Máx: 5.00
Tamanho Médio da Fila:
Média: 1.20 Desvio Padrão: 2.17
Mediana: 0.00 IC 95%: [-1.89, 4.29]
Mín: 0.00 Máx: 5.00
Veículos Processados:
Média: 125.80 Desvio Padrão: 51.69
Mediana: 96.00 IC 95%: [52.24, 199.36]
Mín: 84.00 Máx: 193.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 394.20 Desvio Padrão: 134.99
Mediana: 387.00 IC 95%: [202.11, 586.29]
Mín: 202.00 Máx: 541.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-medium.properties]:
Gerados: 891, Completados: 202 (22.7%)
Tempo Médio no Sistema: 69.75s
Tempo Médio de Espera: 66.09s
Execução #2 [simulation-medium.properties]:
Gerados: 871, Completados: 340 (39.0%)
Tempo Médio no Sistema: 68.73s
Tempo Médio de Espera: 64.73s
Execução #3 [simulation-medium.properties]:
Gerados: 953, Completados: 541 (56.8%)
Tempo Médio no Sistema: 68.64s
Tempo Médio de Espera: 65.24s
Execução #4 [simulation-medium.properties]:
Gerados: 888, Completados: 501 (56.4%)
Tempo Médio no Sistema: 60.85s
Tempo Médio de Espera: 57.48s
Execução #5 [simulation-medium.properties]:
Gerados: 869, Completados: 387 (44.5%)
Tempo Médio no Sistema: 58.29s
Tempo Médio de Espera: 55.37s
================================================================================
FIM DO RELATÓRIO
================================================================================

169
main/graphing.py Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -29,6 +29,18 @@
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- JavaFX for UI -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>17.0.2</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>17.0.2</version>
</dependency>
</dependencies>
<build>
@@ -39,7 +51,16 @@
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>sd.Entry</mainClass>
<mainClass>sd.dashboard.Launcher</mainClass>
</configuration>
</plugin>
<!-- JavaFX Maven Plugin -->
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>sd.dashboard.Launcher</mainClass>
</configuration>
</plugin>
<plugin>
@@ -55,7 +76,7 @@
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>sd.Entry</mainClass>
<mainClass>sd.dashboard.Launcher</mainClass>
</transformer>
</transformers>
</configuration>

View File

@@ -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)");
}
}

View File

@@ -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).
* <p>
* Este processo atua como o sumidouro da rede de filas. A sua função primária é
* a <b>coleta de telemetria final</b>. 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.
* <p>
* <b>Arquitetura de Concorrência:</b>
* 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<VehicleType, Integer> vehicleTypeCount;
/** Latência acumulada por categoria. */
private final Map<VehicleType, Double> 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.
* <p>
* 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.
* <p>
* <b>Secção Crítica:</b> 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<VehicleType, Integer> typeCounts = new HashMap<>();
Map<VehicleType, Long> 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);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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.
* <p>
* 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<SimulationRunResult> 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.
* <p>
* O relatório inclui:
* <ul>
* <li>Métricas globais (throughput, tempos de espera, tempos no sistema).</li>
* <li>Análise segmentada por tipo de veículo ({@link VehicleType}).</li>
* <li>Análise de gargalos por interseção (tamanhos de fila).</li>
* <li>Resumos brutos das execuções individuais.</li>
* </ul>
*
* @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<String> 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.
* <p>
* 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<Double> 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.
* <p>
* 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<Double> extractValues(java.util.function.Function<SimulationRunResult, Double> extractor) {
List<Double> 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.
* <p>
* 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.
* <p>
* 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()
);
}
}
}
}

View File

@@ -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.
* <p>
* 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<VehicleType, Integer> vehicleCountByType;
private final Map<VehicleType, Double> avgSystemTimeByType;
private final Map<VehicleType, Double> avgWaitTimeByType;
// Per-intersection metrics
private final Map<String, Integer> maxQueueSizeByIntersection;
private final Map<String, Double> avgQueueSizeByIntersection;
private final Map<String, Integer> 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<VehicleType, Integer> 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<VehicleType, Double> 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<VehicleType, Double> 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<String, Integer> 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<String, Double> 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<String, Integer> 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
);
}
}

View File

@@ -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.
* <p>
* 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<Double> 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).
* <p>
* 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<Double> 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.
* <p>
* 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<Double> 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).
* <p>
* 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<Double> 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<Double> values) {
if (values == null || values.isEmpty()) {
return 0.0;
}
return Collections.max(values);
}
/**
* Calcula a mediana da amostra.
* <p>
* <b>Nota de Desempenho:</b> 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<Double> values) {
if (values == null || values.isEmpty()) {
return 0.0;
}
List<Double> 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.
* <p>
* Ú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<Double> 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()
);
}
}

View File

@@ -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.
* <p>
* 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 <i>classpath</i>.
* <p>
* 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<IntersectionConfig> intersections;
public List<IntersectionConfig> 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<String> lights;
private Map<String, String> 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<String> getLights() {
return lights;
}
/** @return Mapa de roteamento definindo destinos alcançáveis e seus próximos saltos. */
public Map<String, String> getRoutes() {
return routes;
}
}
/**
* Inicializa o gestor de configuração carregando propriedades do caminho especificado.
* * <p>Implementa uma estratégia de carregamento em cascata (fallback) para garantir robustez
* em diferentes ambientes de execução (IDE, JAR, Docker):
* <ol>
* <li><b>Sistema de Ficheiros Direto:</b> Tenta carregar do caminho absoluto ou relativo.</li>
* <li><b>Classpath (Contexto):</b> Tenta carregar via {@code Thread.currentThread().getContextClassLoader()},
* normalizando prefixos como "src/main/resources" ou "classpath:".</li>
* <li><b>Classpath (Classe):</b> Tenta carregar via {@code SimulationConfig.class.getResourceAsStream},
* útil para recursos na raiz do JAR.</li>
* </ol>
*
* @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 {
// List to track all attempted paths for better error reporting
List<String> attemptedPaths = new ArrayList<>();
IOException fileSystemException = null;
// Strategy 1: Try to load directly from file system
try (InputStream input = new FileInputStream(filePath)) {
properties.load(input);
return; // carregado com sucesso a partir do caminho fornecido
}
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);
}
// Strategy 2: Try to load from classpath with path normalization
String resourcePath = filePath;
// 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);
}
}
// Try loading from classpath using thread context class loader
InputStream resourceStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath);
attemptedPaths.add("Classpath (context): " + resourcePath);
if (resourceStream == null) {
//como último recurso, tentar com um leading slash
resourceStream = SimulationConfig.class.getResourceAsStream('/' + resourcePath);
// 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".
* <p>
* 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();
}
}
if (lastException != null) throw lastException;
/**
* 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.
* <ul>
* <li>0.0: Execução instantânea (DES puro, velocidade máxima).</li>
* <li>1.0: Tempo real (1 segundo simulado = 1 segundo real).</li>
* <li>0.01: Acelerado 100x.</li>
* </ul>
* @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.
* <p>
* 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.
* <p>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.
* <p>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);

View File

@@ -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.
* <p>
* Este processo atua como o "cérebro" da simulação, sendo responsável por:
* <ol>
* <li><b>Orquestração DES:</b> Gerir o relógio global ({@link SimulationClock}) e a fila de eventos prioritária.</li>
* <li><b>Geração de Carga:</b> Injetar veículos na malha viária seguindo distribuições estocásticas (Poisson) ou determinísticas.</li>
* <li><b>Encaminhamento Dinâmico:</b> Decidir as rotas dos veículos com base na política ativa (Random, Shortest Path, Least Congested).</li>
* <li><b>Sincronização:</b> Garantir que todos os nós (Interseções e Dashboard) operem em uníssono.</li>
* </ol>
*/
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<String, SocketClient> 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.
* <p>
* 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<String, Integer> 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).
* <p>
* 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<String, SocketClient> 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<String, SocketClient> 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
}
}
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* É 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.
* <p>
* <b>Protocolo de Envio (Length-Prefix Framing):</b>
* <ol>
* <li>Serializa o objeto {@link Message} para um array de bytes.</li>
* <li>Calcula o tamanho (N) do array.</li>
* <li>Escreve um cabeçalho de 4 bytes contendo N (Big-Endian).</li>
* <li>Escreve os N bytes do payload (corpo da mensagem).</li>
* <li>Realiza flush no stream para forçar o envio imediato do pacote TCP.</li>
* </ol>
* 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).
* <p>
* 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());
}
}

View File

@@ -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).
* <p>
* 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:
* <ol>
* <li>Orquestrar o ciclo de vida dos processos de simulação (start/stop/wait).</li>
* <li>Coletar métricas estatísticas de cada execução.</li>
* <li>Agregar resultados usando o {@link MultiRunAnalyzer}.</li>
* <li>Gerar relatórios consolidados para análise de variância e intervalos de confiança.</li>
* </ol>
* 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<Integer> 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<Integer> 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<Integer> runsSpinner = (Spinner<Integer>) dialog.getScene().lookup("#runsSpinner");
Spinner<Integer> durationSpinner = (Spinner<Integer>) 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);
});
}
}

View File

@@ -0,0 +1,193 @@
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.
* <p>
* 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:
* <ul>
* <li><b>Geração de Carga:</b> Alternância entre modelos estocásticos (Poisson) e determinísticos.</li>
* <li><b>Temporização:</b> Ajuste da escala de tempo (Time Scale) para visualização vs. performance pura.</li>
* <li><b>Mix de Veículos:</b> Definição das probabilidades de geração por tipo de agente.</li>
* </ul>
*/
public class ConfigurationDialog {
/**
* Exibe o diálogo de configuração avançada e captura as intenções do utilizador.
* <p>
* 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<ButtonType> 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));
content.setStyle("-fx-background-color: #2b2b2b;");
// 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; -fx-text-fill: white;");
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:");
modelLabel.setStyle("-fx-text-fill: white;");
ComboBox<String> 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]:");
rateLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> 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]:");
intervalLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> 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; -fx-text-fill: white;");
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]:");
durationLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> 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):");
scaleLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> 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]:");
drainLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> 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; -fx-text-fill: white;");
GridPane vehicleGrid = new GridPane();
vehicleGrid.setHgap(10);
vehicleGrid.setVgap(10);
vehicleGrid.setPadding(new Insets(10));
Label bikeLabel = new Label("Bicicletas/Motos [%]:");
bikeLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> 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 [%]:");
lightLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> 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 [%]:");
heavyLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> 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: #aaaaaa;");
// 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);
}
}

View File

@@ -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.
* <p>
* Esta classe implementa o padrão <i>Thread-per-Client</i>. 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.
* <p>
* As suas principais funções são:
* <ol>
* <li>Manter a conexão persistente com o nó remoto.</li>
* <li>Desserializar mensagens de protocolo recebidas.</li>
* <li>Normalizar payloads JSON (resolvendo ambiguidades de tipagem do Gson).</li>
* <li>Atualizar o objeto partilhado {@link DashboardStatistics} de forma thread-safe.</li>
* </ol>
*/
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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<sd.model.VehicleType, Integer> counts = stats.getVehicleTypeCounts();
Map<sd.model.VehicleType, Long> 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);
}
}

View File

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

View File

@@ -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.
* <p>
* 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<String, IntersectionStats> intersectionStats;
private final Map<VehicleType, AtomicInteger> vehicleTypeCount;
private final Map<VehicleType, AtomicLong> 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.
* <p>
* 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<String, IntersectionStats> getAllIntersectionStats() {
return new HashMap<>(intersectionStats);
}
public long getLastUpdateTime() {
return lastUpdateTime;
}
/**
* Obtém um snapshot dos tamanhos atuais das filas de todas as interseções.
* <p>
* 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<String, Integer> getCurrentQueueSizes() {
Map<String, Integer> queueSizes = new HashMap<>();
for (Map.Entry<String, IntersectionStats> 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());
}
}
}

View File

@@ -0,0 +1,620 @@
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.
* <p>
* Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão
* <i>Observer</i> (via polling) para refletir o estado do modelo {@link DashboardStatistics}
* nos componentes visuais.
* <p>
* <b>Aspetos Técnicos Relevantes:</b>
* <ul>
* <li><b>Concorrência de UI:</b> Utiliza um {@link ScheduledExecutorService} para buscar dados
* em background e {@link Platform#runLater(Runnable)} para injetar atualizações na
* <i>JavaFX Application Thread</i>, evitando exceções de "Not on FX application thread".</li>
* <li><b>Data Binding:</b> Utiliza {@link TableView} com classes internas (DTOs) para
* renderização tabular eficiente de tipos de veículos e interseções.</li>
* <li><b>Controlo de Processos:</b> Integra com {@link SimulationProcessManager} para orquestrar
* o ciclo de vida (spawn/kill) dos processos externos da simulação.</li>
* </ul>
*/
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<VehicleTypeRow> vehicleTypeTable;
// Intersection Table
private TableView<IntersectionRow> intersectionTable;
// Update scheduler (Background Thread)
private ScheduledExecutorService updateScheduler;
// Configuration controls
private ComboBox<String> 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");
root.setStyle("-fx-background-color: #2b2b2b;");
// 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<String> 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<VehicleTypeRow, String> typeCol = new TableColumn<>("Vehicle Type");
typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType"));
TableColumn<VehicleTypeRow, Integer> countCol = new TableColumn<>("Count");
countCol.setCellValueFactory(new PropertyValueFactory<>("count"));
TableColumn<VehicleTypeRow, String> 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<IntersectionRow, String> idCol = new TableColumn<>("Intersection ID");
idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId"));
TableColumn<IntersectionRow, Integer> arrivalsCol = new TableColumn<>("Total Arrivals");
arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals"));
TableColumn<IntersectionRow, Integer> departuresCol = new TableColumn<>("Total Departures");
departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures"));
TableColumn<IntersectionRow, Integer> 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<String, DashboardStatistics.IntersectionStats> 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; }
}
}

View File

@@ -0,0 +1,7 @@
package sd.dashboard;
public class Launcher {
public static void main(String[] args) {
DashboardUI.main(args);
}
}

View File

@@ -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.
* <p>
* Esta classe atua como um supervisor (Process Manager), responsável pelo <i>bootstrapping</i>
* e <i>teardown</i> das múltiplas Java Virtual Machines (JVMs) que compõem o sistema.
* <p>
* Funcionalidades principais:
* <ul>
* <li><b>Isolamento:</b> Cada nó (Interseção, Coordinator, ExitNode) corre no seu próprio processo OS.</li>
* <li><b>Ordem de Arranque:</b> Garante que os servidores (Interseções) estão online antes dos clientes (Coordenador).</li>
* <li><b>Gestão de Logs:</b> Redireciona stdout/stderr de cada processo filho para ficheiros temporários para facilitar o debug.</li>
* </ul>
*/
public class SimulationProcessManager {
private final List<Process> 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.
* <p>
* A ordem de inicialização é crítica para evitar <i>Race Conditions</i> na conexão TCP:
* <ol>
* <li><b>Workers (Interseções):</b> Iniciam os ServerSockets.</li>
* <li><b>Sink (Exit Node):</b> Prepara-se para receber métricas finais.</li>
* <li><b>Delay de Estabilização:</b> Pausa de 1s para garantir que os sockets estão em LISTENING.</li>
* <li><b>Source (Coordinator):</b> Inicia a geração de carga e conecta-se aos nós.</li>
* </ol>
* * @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.
* <p>
* 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.
* <p>
* 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());
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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);
}
}

View File

@@ -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.
* <p>
* 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 <b>atualizações parciais</b>
* (Sparse Updates):
* <ul>
* <li>Campos globais inicializados com {@code -1} indicam "sem alteração" (no-op). O Dashboard
* deve ignorar estes campos e manter o valor acumulado anterior.</li>
* <li>Campos de interseção ({@code arrivals}, {@code departures}) representam deltas ou snapshots
* específicos do nó remetente.</li>
* </ul>
* 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<VehicleType, Integer> vehicleTypeCounts;
/** Tempos de espera acumulados por tipo de veículo. */
private Map<VehicleType, Long> 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<VehicleType, Integer> getVehicleTypeCounts() {
return vehicleTypeCounts;
}
public Map<VehicleType, Long> 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<VehicleType, Integer> vehicleTypeCounts) {
this.vehicleTypeCounts = vehicleTypeCounts;
return this;
}
public StatsUpdatePayload setVehicleTypeWaitTimes(Map<VehicleType, Long> 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);
}
}

View File

@@ -0,0 +1,39 @@
package sd.des;
/**
* Tipos de eventos discretos da simulação.
*
* <p>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
}

View File

@@ -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.
*
* <p>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.</p>
*/
public class EventQueue {
private final PriorityQueue<SimulationEvent> queue;
private final List<SimulationEvent> 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<SimulationEvent> 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());
}
}

View File

@@ -0,0 +1,71 @@
package sd.des;
/**
* Gere o tempo de simulação para Simulação de Eventos Discretos.
*
* <p>
* No DES, o tempo avança em saltos discretos de evento para evento,
* não de forma contínua como o tempo real.
* </p>
*
* <p>
* Esta classe garante que todos os processos no sistema distribuído
* mantêm uma visão sincronizada do tempo de simulação.
* </p>
*/
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());
}
}

View File

@@ -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).
* <p>
* 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.
* <p>
* Características principais:
* <ul>
* <li><b>Ordenação Temporal:</b> Implementa {@link Comparable} para ser armazenado numa Fila de
* Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.</li>
* <li><b>Distribuído:</b> Implementa {@link Serializable} para permitir que eventos gerados num nó
* (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).</li>
* <li><b>Polimórfico:</b> Transporta um {@code payload} genérico, permitindo associar qualquer
* entidade (Veículo, Sinal, etc.) ao evento.</li>
* </ul>
*/
public class SimulationEvent implements Comparable<SimulationEvent>, 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.
* <p>
* <b>Lógica de Ordenação:</b>
* <ol>
* <li><b>Primária (Tempo):</b> Eventos com menor timestamp ocorrem primeiro.</li>
* <li><b>Secundária (Determinismo):</b> 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).</li>
* </ol>
*
* @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;
}
}

View File

@@ -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.
* <p>
* Este objeto atua como o <i>payload</i> 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);
}
}

View File

@@ -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<Event> eventQueue;
/**
* A map storing all intersections in the simulation, keyed by their ID (e.g., "Cr1").
*/
private final Map<String, Intersection> 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<String, Intersection> getIntersections() {
return new HashMap<>(intersections);
}
/**
* Gets the statistics collector instance.
* @return The {@link StatisticsCollector}.
*/
public StatisticsCollector getStatisticsCollector() {
return statisticsCollector;
}
}

View File

@@ -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.
* <p>
* Implementa o padrão <i>Singleton</i> para garantir um ponto centralizado de registo.
* Utiliza o padrão <i>Producer-Consumer</i> 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).
* <p>
* <b>Garantias:</b>
* <ul>
* <li>Non-blocking writes (para a thread chamadora, na maioria dos casos).</li>
* <li>Ordering cronológico aproximado (FIFO na fila).</li>
* <li>Graceful Shutdown (flush de logs pendentes ao terminar).</li>
* </ul>
*/
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<LogEntry> 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;
}
}
}

View File

@@ -0,0 +1,60 @@
package sd.logging;
/**
* Taxonomia oficial de eventos para o subsistema de logging centralizado.
* <p>
* Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo:
* <ul>
* <li>Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).</li>
* <li>Análise estatística post-mortem (parsear logs para calcular latências).</li>
* <li>Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).</li>
* </ul>
*/
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;
}
}

View File

@@ -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.
* <p>
* Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se
* na perspetiva do <b>agente</b>. 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).
* <p>
* <b>Funcionalidades:</b>
* <ul>
* <li>Análise forense de percursos individuais.</li>
* <li>Validação de tempos de espera e travessia por nó.</li>
* <li>Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).</li>
* </ul>
*/
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<String, VehicleTrace> 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).
* <p>
* Este método também desencadeia a escrita do <b>Sumário de Viagem</b> 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);
}
}
}

View File

@@ -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<Event>, 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);
}
}

View File

@@ -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
}

View File

@@ -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.
*
* <p>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}).</p>
*
* <p>Responsabilidades principais:</p>
* <ul>
* <li>Manter um {@link TrafficLight} para cada direção (Norte, Este, etc.)</li>
* <li>Gerir uma tabela de encaminhamento que mapeia destinos para direções</li>
* <li>Receber veículos e colocá-los na fila do semáforo correto</li>
* <li>Acompanhar estatísticas agregadas do tráfego</li>
* </ul>
*/
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<String, TrafficLight> 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<String, String> 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,53 @@ 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.
*
* @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").
* <p>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."</p>
*
* @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.
* 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 The {@link Vehicle} arriving at the intersection.
* @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++;
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 +116,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<TrafficLight> 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() {

View File

@@ -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).
* <p>
* 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).
* <p>
* 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 <b>Broadcast</b>.
*/
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.
* <p>
* Evita a necessidade de casts explícitos e supressão de warnings no código cliente.
*
* @param <T> The expected payload type
* @return The payload cast to type T
* @throws ClassCastException if the payload is not of type T
* @param <T> 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> T getPayloadAs(Class<T> 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]",

View File

@@ -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,
ROUTING_POLICY_CHANGE,
/**
* Error message to report problems in the distributed system.
* Payload: Error description and context
*/
ERROR
}

View File

@@ -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.
*
* <p>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.</p>
*
* <p><strong>Thread-safety:</strong> Usa locks para permitir acesso concorrente seguro entre
* a thread de processamento de eventos e as threads de I/O de rede.</p>
*/
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<Vehicle> 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<String, Double> 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.
* Coloca um veículo na fila deste semáforo.
*
* @param vehicle The {@link Vehicle} to add.
* 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.
* Remove um veículo da fila para travessia.
*
* @return The {@link Vehicle} at the front of the queue, or {@code null}
* if the light is RED or the queue is empty.
* <p>Só remove se:</p>
* <ul>
* <li>O semáforo estiver VERDE</li>
* <li>Existir pelo menos um veículo na fila</li>
* </ul>
*
* <p>Atualiza automaticamente as estatísticas de tempo de espera do veículo.</p>
*
* @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 lights 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.
* Muda o estado do semáforo.
*
* @param newState The {@link TrafficLightState} to set.
* @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
id, direction, getState(), getQueueSize()
);
}
}

View File

@@ -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
}

View File

@@ -5,93 +5,84 @@ import java.util.ArrayList;
import java.util.List;
/**
* Represents a single vehicle moving through the simulation.
* Representa um veículo que se move pela rede de interseções.
*
* 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).
* <p>
* Esta classe é o "gémeo digital" de um carro, mota ou camião.
* Mantém toda a informação necessária:
* </p>
* <ul>
* <li>Identificação e tipo do veículo</li>
* <li>Rota completa a percorrer</li>
* <li>Métricas de tempo (espera, travessia, total)</li>
* </ul>
*
* <p>
* O objeto é serializado e enviado pela rede à medida que o veículo
* se move entre processos distribuídos.
* </p>
*/
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<String> 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.
* Cria um novo veículo pronto para se fazer à estrada.
*
* @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"]).
* @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<String> 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.
* Move o GPS interno do veículo para o próximo destino.
*
* @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.
* 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<String> 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
);
id, type, getCurrentDestination(), route);
}
}

View File

@@ -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.
*
* <p>Cada tipo pode ter propriedades diferentes como tempo de travessia
* e probabilidade de geração, definidas na {@link sd.config.SimulationConfig}.</p>
*/
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
}

View File

@@ -0,0 +1,45 @@
package sd.protocol;
import java.io.Serializable;
import sd.model.MessageType;
/**
* Contrato para todas as mensagens trocadas no simulador.
*
* <p>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.
*
* <p>Tipo depende do MessageType:
* <ul>
* <li>VEHICLE_TRANSFER → objeto Vehicle
* <li>STATS_UPDATE → objeto de estatísticas
* </ul>
*
* @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();
}

View File

@@ -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.
* <p>
* Esta classe abstrai a complexidade da API nativa {@link java.net.Socket}, oferecendo:
* <ol>
* <li><b>Resiliência:</b> 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.</li>
* <li><b>Framing:</b> Implementação transparente do protocolo "Length-Prefix" (4 bytes de tamanho + payload),
* resolvendo o problema de fragmentação de stream TCP.</li>
* <li><b>Serialização:</b> Integração direta com a camada de serialização JSON.</li>
* </ol>
*/
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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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();
}
}

View File

@@ -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.
*
* <p>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.</p>
*
* <p>Objetivo: Distribuir o tráfego pela rede, evitando bottlenecks e
* minimizando o tempo de espera total.</p>
*
* <p><strong>Algoritmo:</strong></p>
* <ol>
* <li>Para cada rota possível, calcula a carga total (soma das filas)</li>
* <li>Escolhe a rota com menor carga total</li>
* <li>Em caso de empate ou falta de informação, usa a rota mais curta</li>
* </ol>
*/
public class LeastCongestedRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1 */
private final List<List<String>> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2 */
private final List<List<String>> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3 */
private final List<List<String>> 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<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
List<List<String>> 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<String> bestRoute = null;
int minLoad = Integer.MAX_VALUE;
for (List<String> 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<String> route, Map<String, Integer> 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<String> selectShortestRoute(List<List<String>> routes) {
List<String> shortest = routes.get(0);
for (List<String> 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<List<String>> 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;
}
}
}

View File

@@ -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).
*
* <p>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.</p>
*
* <p>As rotas são organizadas por ponto de entrada (E1, E2, E3) e cada rota
* tem uma probabilidade de seleção associada.</p>
*/
public class RandomRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1 */
private final List<RouteWithProbability> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2 */
private final List<RouteWithProbability> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3 */
private final List<RouteWithProbability> 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<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
// Ignora queueSizes - seleção aleatória não depende do estado da rede
List<RouteWithProbability> 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<RouteWithProbability> 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<String> route;
final double probability;
RouteWithProbability(List<String> route, double probability) {
this.route = route;
this.probability = probability;
}
}
}

View File

@@ -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.
*
* <p>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.).</p>
*/
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<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes);
}

View File

@@ -0,0 +1,36 @@
package sd.routing;
/**
* Enumeração que define as políticas de roteamento disponíveis para a simulação.
*
* <p>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.</p>
*
* <ul>
* <li><strong>RANDOM:</strong> Seleção aleatória de rotas baseada em probabilidades predefinidas</li>
* <li><strong>SHORTEST_PATH:</strong> Escolhe sempre a rota com o menor número de cruzamentos</li>
* <li><strong>LEAST_CONGESTED:</strong> Escolhe a rota evitando cruzamentos mais congestionados</li>
* </ul>
*/
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
}

View File

@@ -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.
*
* <p>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).</p>
*
* <p>Objetivo: Minimizar a distância teórica percorrida pelos veículos.</p>
*/
public class ShortestPathRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1, ordenadas por comprimento */
private final List<List<String>> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2, ordenadas por comprimento */
private final List<List<String>> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3, ordenadas por comprimento */
private final List<List<String>> 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<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
// Ignora queueSizes - política baseada apenas no comprimento do caminho
List<List<String>> availableRoutes = getRoutesForEntryPoint(entryPoint);
// Retorna a rota mais curta (primeira da lista)
List<String> 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<List<String>> 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;
}
}
}

View File

@@ -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.
* <p>
* 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:
* <ul>
* <li><b>Legibilidade:</b> O formato de texto facilita a depuração (sniffing de rede) sem ferramentas especializadas.</li>
* <li><b>Interoperabilidade:</b> Permite futura integração com componentes não-Java (ex: Dashboards web em JS).</li>
* <li><b>Segurança:</b> Reduz a superfície de ataque para execução remota de código (RCE), pois não desserializa classes arbitrárias, apenas dados.</li>
* </ul>
* <p>
* <b>Thread-Safety:</b> 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.
* <p>
* 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> T deserialize(byte[] data, Class<T> clazz) throws SerializationException {
if (data == null) {
@@ -95,18 +111,16 @@ 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;

View File

@@ -1,47 +1,48 @@
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.
* <p>
* Esta abstração permite desacoplar a camada de transporte (Sockets TCP) da camada de
* apresentação de dados. Ao implementar o padrão <b>Strategy</b>, 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.
* <p>
* <b>Requisitos para Implementações:</b>
* <ul>
* <li><b>Thread-Safety:</b> As implementações devem ser seguras para uso concorrente, dado que
* instâncias únicas podem ser partilhadas por múltiplos <i>ClientHandlers</i>.</li>
* <li><b>Robustez:</b> Falhas de parsing devem resultar em exceções tipificadas ({@link SerializationException}),
* nunca em falhas silenciosas ou estados inconsistentes.</li>
* </ul>
* * @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 <T> 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 <T> 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> T deserialize(byte[] data, Class<T> 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();

View File

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

View File

@@ -1,39 +1,38 @@
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.
* <p>
* 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);

View File

@@ -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}.
* <p>
* 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.
* <p>
* <b>Exemplo de Uso:</b>
* <pre>
* 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.
* <p>
* 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,19 +45,18 @@ 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);

View File

@@ -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.
* <p>
* Esta classe fornece primitivas para geração de números pseudo-aleatórios, abstraindo
* a complexidade de distribuições estatísticas.
* <p>
* <b>Funcionalidades Principais:</b>
* <ul>
* <li><b>Modelagem de Poisson:</b> Geração de tempos entre chegadas usando distribuição exponencial inversa.</li>
* <li><b>Amostragem Uniforme:</b> Geração de inteiros e doubles em intervalos fechados/abertos.</li>
* <li><b>Decisão Probabilística:</b> Avaliação de eventos booleanos baseados em pesos (Bernoulli trials).</li>
* <li><b>Determinismo:</b> Suporte a sementes (seeds) manuais para reprodutibilidade exata de cenários de teste.</li>
* </ul>
*/
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.
* <p>
* Este método implementa o algoritmo de <i>Inverse Transform Sampling</i> 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.
* <p>
* <b>Fórmula Matemática:</b> {@code T = -ln(1 - U) / λ}
* <br>Onde:
* <ul>
* <li>{@code U}: Variável aleatória uniforme no intervalo [0, 1).</li>
* <li>{@code λ (lambda)}: Taxa média de eventos por unidade de tempo (ex: veículos/segundo).</li>
* </ul>
*
* @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.
* <p>
* 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 <T> 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 <T> 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> 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.
* <p>
* <b>Importância Crítica:</b> 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);

View File

@@ -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<String, Double> vehicleArrivalTimes;
/**
* Tracks the sequence of intersections a vehicle has visited.
* Key: Vehicle ID (String)
* Value: List of Intersection IDs (String)
*/
private final Map<String, List<String>> 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<VehicleType, Integer> vehicleTypeCount;
/**
* Tracks the total waiting time, broken down by vehicle type.
* Key: {@link VehicleType}
* Value: Total Wait Time (Double)
*/
private final Map<VehicleType, Double> vehicleTypeWaitTime;
// --- Per-intersection statistics ---
/**
* A map to hold statistics objects for each intersection.
* Key: Intersection ID (String)
* Value: {@link IntersectionStats} object
*/
private final Map<String, IntersectionStats> 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<String> 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<String, Intersection> 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<String, Intersection> 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<String, Intersection> 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<String, Intersection> 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;
}
}

View File

@@ -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.
* <p>
* Esta classe atua como uma fábrica estocástica de veículos, sendo responsável por:
* <ol>
* <li><b>Modelagem Temporal:</b> Determinar os instantes de chegada (Inter-arrival times)
* usando processos de Poisson (estocástico) ou intervalos determinísticos.</li>
* <li><b>Caracterização da Entidade:</b> Atribuir tipos de veículo (Bike, Light, Heavy)
* baseado numa Distribuição de Probabilidade Cumulativa (CDF).</li>
* <li><b>Inicialização Espacial:</b> Distribuir a carga uniformemente entre os pontos de entrada (E1-E3).</li>
* <li><b>Atribuição de Rota:</b> Delegar a escolha do percurso à estratégia {@link RouteSelector} ativa.</li>
* </ol>
*/
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<RouteWithProbability> e1Routes;
/** Routes starting from entry point E2. */
private final List<RouteWithProbability> e2Routes;
/** Routes starting from entry point E3. */
private final List<RouteWithProbability> 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.
* <p>
* Se o modelo for "POISSON", utiliza a técnica de <i>Inverse Transform Sampling</i>
* (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.
* <p>
* O processo de criação segue um pipeline:
* <ol>
* <li>Seleção de Tipo (Roda da Fortuna / CDF).</li>
* <li>Seleção de Entrada (Uniforme).</li>
* <li>Cálculo de Rota (Delegado ao Strategy).</li>
* </ol>
*
* @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<String, Integer> queueSizes) {
VehicleType type = selectVehicleType();
List<String> route = selectRandomRoute();
String entryPoint = selectRandomEntryPoint();
List<String> 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.
* <p>
* 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<String> selectRandomRoute() {
// Step 1: Randomly select an entry point (E1, E2, or E3)
double entryRandom = Math.random();
List<RouteWithProbability> 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).
* <p>
* 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<String> 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<String> route, double probability) {
this.route = route;
this.probability = probability;
}
}
}

View File

@@ -0,0 +1,147 @@
/* Global Styles */
.root {
-fx-background-color: #2b2b2b;
-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: #1e1e1e;
-fx-background-radius: 8;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 2);
-fx-padding: 0;
}
.card-header {
-fx-background-color: #3a3a3a;
-fx-background-radius: 8 8 0 0;
-fx-padding: 10 15;
-fx-border-color: #555555;
-fx-border-width: 0 0 1 0;
}
.card-title {
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-text-fill: white;
}
.card-content {
-fx-padding: 15;
}
/* Statistics Grid */
.stat-label {
-fx-font-size: 14px;
-fx-text-fill: #cccccc;
}
.stat-value {
-fx-font-size: 20px;
-fx-font-weight: bold;
-fx-text-fill: #4ca1af;
}
/* Tables */
.table-view {
-fx-background-color: #1e1e1e;
-fx-border-color: transparent;
}
.table-view .column-header-background {
-fx-background-color: #3a3a3a;
-fx-border-color: #555555;
-fx-border-width: 0 0 1 0;
}
.table-view .column-header .label {
-fx-text-fill: white;
-fx-font-weight: bold;
}
.table-row-cell {
-fx-background-color: #1e1e1e;
-fx-border-color: transparent;
-fx-text-fill: white;
}
.table-row-cell:odd {
-fx-background-color: #252525;
}
.table-row-cell:selected {
-fx-background-color: #4ca1af;
-fx-text-fill: white;
}
.table-cell {
-fx-text-fill: white;
}
/* Footer */
.footer {
-fx-background-color: #34495e;
-fx-padding: 10 20;
}
.footer-text {
-fx-text-fill: #ecf0f1;
-fx-font-size: 12px;
}

View File

@@ -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"
}
}
]
}

View File

@@ -0,0 +1,75 @@
# Configuração de rede
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.host=localhost
exit.port=9001
dashboard.host=localhost
dashboard.port=9000
# Configuração da simulação
# Cenário de carga alta - tráfego pesado, teste de stress do sistema
simulation.duration=1800
simulation.arrival.model=POISSON
simulation.arrival.rate=1.0
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, verde mais longo
trafficlight.Cr1.South.green=45.0
trafficlight.Cr1.South.red=45.0
trafficlight.Cr1.East.green=45.0
trafficlight.Cr1.East.red=45.0
# Cruzamento 2 - hub principal, gargalo crítico, tempos máximos de verde
trafficlight.Cr2.South.green=50.0
trafficlight.Cr2.South.red=50.0
trafficlight.Cr2.East.green=60.0
trafficlight.Cr2.East.red=40.0
trafficlight.Cr2.West.green=50.0
trafficlight.Cr2.West.red=50.0
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=40.0
trafficlight.Cr3.South.red=45.0
trafficlight.Cr3.West.green=45.0
trafficlight.Cr3.West.red=40.0
# Cruzamento 4
trafficlight.Cr4.East.green=45.0
trafficlight.Cr4.East.red=45.0
trafficlight.Cr4.North.green=45.0
trafficlight.Cr4.North.red=45.0
# Cruzamento 5 - perto da saída, gargalo principal
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
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
vehicle.travel.time.base=1.0
vehicle.travel.time.bike.multiplier=0.5
vehicle.travel.time.heavy.multiplier=4.0
statistics.update.interval=10.0

View File

@@ -0,0 +1,75 @@
# Configuração de rede
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.host=localhost
exit.port=9001
dashboard.host=localhost
dashboard.port=9000
# Configuração da simulação
# Cenário de carga baixa - tráfego leve para testar o sistema sem congestionamento
simulation.duration=1800
simulation.arrival.model=POISSON
simulation.arrival.rate=0.2
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=30.0
trafficlight.Cr1.South.red=30.0
trafficlight.Cr1.East.green=30.0
trafficlight.Cr1.East.red=30.0
# Cruzamento 2 - hub principal
trafficlight.Cr2.South.green=30.0
trafficlight.Cr2.South.red=30.0
trafficlight.Cr2.East.green=30.0
trafficlight.Cr2.East.red=30.0
trafficlight.Cr2.West.green=30.0
trafficlight.Cr2.West.red=30.0
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=30.0
trafficlight.Cr3.South.red=30.0
trafficlight.Cr3.West.green=30.0
trafficlight.Cr3.West.red=30.0
# Cruzamento 4
trafficlight.Cr4.East.green=30.0
trafficlight.Cr4.East.red=30.0
trafficlight.Cr4.North.green=30.0
trafficlight.Cr4.North.red=30.0
# Cruzamento 5 - perto da saída
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
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
vehicle.travel.time.base=1.0
vehicle.travel.time.bike.multiplier=0.5
vehicle.travel.time.heavy.multiplier=4.0
statistics.update.interval=10.0

View File

@@ -0,0 +1,75 @@
# Configuração de rede
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.host=localhost
exit.port=9001
dashboard.host=localhost
dashboard.port=9000
# Configuração da simulação
# Cenário de carga média - tráfego normal com algum congestionamento
simulation.duration=1800
simulation.arrival.model=POISSON
simulation.arrival.rate=0.5
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=35.0
trafficlight.Cr1.South.red=35.0
trafficlight.Cr1.East.green=35.0
trafficlight.Cr1.East.red=35.0
# Cruzamento 2 - hub principal, gargalo crítico
trafficlight.Cr2.South.green=40.0
trafficlight.Cr2.South.red=40.0
trafficlight.Cr2.East.green=45.0
trafficlight.Cr2.East.red=35.0
trafficlight.Cr2.West.green=40.0
trafficlight.Cr2.West.red=40.0
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=35.0
trafficlight.Cr3.South.red=40.0
trafficlight.Cr3.West.green=40.0
trafficlight.Cr3.West.red=35.0
# Cruzamento 4
trafficlight.Cr4.East.green=35.0
trafficlight.Cr4.East.red=35.0
trafficlight.Cr4.North.green=35.0
trafficlight.Cr4.North.red=35.0
# Cruzamento 5 - perto da saída, possível gargalo
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
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
vehicle.travel.time.base=1.0
vehicle.travel.time.bike.multiplier=0.5
vehicle.travel.time.heavy.multiplier=4.0
statistics.update.interval=10.0

View File

@@ -1,13 +1,4 @@
# =========================================================
# Traffic Simulation Configuration
# ---------------------------------------------------------
# All parameters controlling network layout, timing,
# and simulation behavior.
# =========================================================
# === NETWORK CONFIGURATION ===
# Intersections (each with its host and port)
# Configuração de rede
intersection.Cr1.host=localhost
intersection.Cr1.port=8001
intersection.Cr2.host=localhost
@@ -19,95 +10,60 @@ 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 (3600 = 1 hour)
simulation.duration=60.0
# Vehicle arrival model: FIXED or POISSON
# Configuração da simulação
simulation.duration=300
simulation.time.scale=0.01
simulation.arrival.model=POISSON
# λ (lambda): average arrival rate (vehicles per second)
simulation.arrival.rate=0.5
# Fixed interval between arrivals (only used if model=FIXED)
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=RANDOM
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=35.0
trafficlight.Cr1.South.red=35.0
trafficlight.Cr1.East.green=35.0
trafficlight.Cr1.East.red=35.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
# Cruzamento 2 - hub principal
trafficlight.Cr2.South.green=40.0
trafficlight.Cr2.South.red=40.0
trafficlight.Cr2.East.green=40.0
trafficlight.Cr2.East.red=40.0
trafficlight.Cr2.West.green=40.0
trafficlight.Cr2.West.red=40.0
# 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
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=35.0
trafficlight.Cr3.South.red=40.0
trafficlight.Cr3.West.green=40.0
trafficlight.Cr3.West.red=35.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
# Cruzamento 4
trafficlight.Cr4.East.green=35.0
trafficlight.Cr4.East.red=35.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
# Cruzamento 5 - perto da saída
trafficlight.Cr5.East.green=35.0
trafficlight.Cr5.East.red=35.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 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)
# Configuração de veículos
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.5
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
# === STATISTICS ===
vehicle.travel.time.base=1.0
vehicle.travel.time.bike.multiplier=0.5
vehicle.travel.time.heavy.multiplier=4.0
# Interval between dashboard updates (seconds)
statistics.update.interval=10.0
statistics.update.interval=0.1

View File

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

View File

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