mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
Compare commits
6 Commits
v0.7.5
...
d28a77b6a4
| Author | SHA1 | Date | |
|---|---|---|---|
| d28a77b6a4 | |||
| 173d9e54ce | |||
| 5202032471 | |||
| 46d148c9d5 | |||
| 0d85d010bf | |||
| 906e958729 |
69
.github/workflows/maven.yml
vendored
69
.github/workflows/maven.yml
vendored
@@ -1,8 +1,9 @@
|
|||||||
name: Java CI with Maven
|
name: Java CI with Maven
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: [ "dev", "cleanup" ]
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -11,51 +12,93 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
cache: maven
|
cache: maven
|
||||||
|
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn -B package
|
run: mvn -B package
|
||||||
working-directory: main
|
working-directory: main
|
||||||
|
|
||||||
- name: Upload built JAR
|
- name: Upload built JAR
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: package
|
name: package
|
||||||
path: main/target/*.jar
|
path: main/target/*.jar
|
||||||
|
|
||||||
- name: Generate dependency graph
|
- name: Generate dependency graph
|
||||||
run: mvn -B -f main/pom.xml com.github.ferstl:depgraph-maven-plugin:4.0.1:graph
|
run: mvn -B -f main/pom.xml com.github.ferstl:depgraph-maven-plugin:4.0.1:graph
|
||||||
|
|
||||||
- name: Upload dependency graph artifact
|
- name: Upload dependency graph artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dependency-graph
|
name: dependency-graph
|
||||||
path: main/target/**
|
path: main/target/**
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build with Maven (Skip Tests)
|
||||||
|
run: mvn -B package -DskipTests
|
||||||
|
working-directory: main
|
||||||
|
- name: Create JPackage App Image
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
New-Item -ItemType Directory -Force -Path "dist"
|
||||||
|
jpackage --name "DTSS" `
|
||||||
|
--input main/target `
|
||||||
|
--main-jar main-1.0-SNAPSHOT.jar `
|
||||||
|
--dest dist `
|
||||||
|
--type app-image `
|
||||||
|
--win-console
|
||||||
|
- name: Inject java.exe
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$javaPath = (Get-Command java).Source
|
||||||
|
Copy-Item -Path $javaPath -Destination "dist/DTSS/runtime/bin/"
|
||||||
|
- name: Zip Windows Release
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "dist/DTSS" -DestinationPath "dist/DTSS-Windows.zip"
|
||||||
|
- name: Upload Windows Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-package
|
||||||
|
path: dist/DTSS-Windows.zip
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build, build-windows]
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download built JAR
|
- name: Download Linux JAR
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: package
|
name: package
|
||||||
path: main/target/
|
path: main/target/
|
||||||
|
- name: Download Windows Zip
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-package
|
||||||
|
path: windows-dist/
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: main/target/*.jar
|
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
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>3.1.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>sd.dashboard.DashboardUI</mainClass>
|
<mainClass>sd.dashboard.Launcher</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- JavaFX Maven Plugin -->
|
<!-- JavaFX Maven Plugin -->
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<artifactId>javafx-maven-plugin</artifactId>
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
<version>0.0.8</version>
|
<version>0.0.8</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>sd.dashboard.DashboardUI</mainClass>
|
<mainClass>sd.dashboard.Launcher</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>sd.dashboard.DashboardUI</mainClass>
|
<mainClass>sd.dashboard.Launcher</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
</transformers>
|
</transformers>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public class IntersectionProcess {
|
|||||||
private final ExecutorService trafficLightPool;
|
private final ExecutorService trafficLightPool;
|
||||||
|
|
||||||
private ScheduledExecutorService statsExecutor;
|
private ScheduledExecutorService statsExecutor;
|
||||||
|
private ScheduledExecutorService departureExecutor;
|
||||||
|
|
||||||
private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras
|
private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras
|
||||||
// threads veem a mudança imediatamente.
|
// threads veem a mudança imediatamente.
|
||||||
@@ -86,6 +87,7 @@ public class IntersectionProcess {
|
|||||||
this.connectionHandlerPool = Executors.newCachedThreadPool();
|
this.connectionHandlerPool = Executors.newCachedThreadPool();
|
||||||
this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions
|
this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions
|
||||||
this.statsExecutor = Executors.newSingleThreadScheduledExecutor();
|
this.statsExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
this.departureExecutor = Executors.newScheduledThreadPool(4);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation
|
this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation
|
||||||
this.currentGreenDirection = null;
|
this.currentGreenDirection = null;
|
||||||
@@ -270,6 +272,25 @@ public class IntersectionProcess {
|
|||||||
public void sendVehicleToNextDestination(Vehicle vehicle) {
|
public void sendVehicleToNextDestination(Vehicle vehicle) {
|
||||||
String nextDestination = vehicle.getCurrentDestination();
|
String nextDestination = vehicle.getCurrentDestination();
|
||||||
|
|
||||||
|
// Calculate travel time
|
||||||
|
double baseTime = config.getBaseTravelTime();
|
||||||
|
double multiplier = 1.0;
|
||||||
|
switch (vehicle.getType()) {
|
||||||
|
case BIKE -> multiplier = config.getBikeTravelTimeMultiplier();
|
||||||
|
case HEAVY -> multiplier = config.getHeavyTravelTimeMultiplier();
|
||||||
|
default -> multiplier = 1.0;
|
||||||
|
}
|
||||||
|
double travelTime = baseTime * multiplier;
|
||||||
|
long travelTimeMs = (long) (travelTime * 1000);
|
||||||
|
|
||||||
|
System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n",
|
||||||
|
intersectionId, vehicle.getId(), nextDestination, travelTime);
|
||||||
|
|
||||||
|
// Record departure immediately as it leaves the intersection
|
||||||
|
recordVehicleDeparture();
|
||||||
|
|
||||||
|
// Schedule the arrival at the next node
|
||||||
|
departureExecutor.schedule(() -> {
|
||||||
try {
|
try {
|
||||||
// Get or create connection to next destination
|
// Get or create connection to next destination
|
||||||
SocketConnection connection = getOrCreateConnection(nextDestination);
|
SocketConnection connection = getOrCreateConnection(nextDestination);
|
||||||
@@ -284,11 +305,8 @@ public class IntersectionProcess {
|
|||||||
|
|
||||||
connection.sendMessage(message);
|
connection.sendMessage(message);
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
|
System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() +
|
||||||
" to " + nextDestination);
|
" arrived at " + nextDestination + " (msg sent)");
|
||||||
|
|
||||||
// Record departure for statistics
|
|
||||||
recordVehicleDeparture();
|
|
||||||
|
|
||||||
// Note: vehicle route is advanced when it arrives at the next intersection
|
// Note: vehicle route is advanced when it arrives at the next intersection
|
||||||
|
|
||||||
@@ -296,6 +314,7 @@ public class IntersectionProcess {
|
|||||||
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
|
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
|
||||||
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
|
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
}, travelTimeMs, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -540,6 +559,9 @@ public class IntersectionProcess {
|
|||||||
if (statsExecutor != null && !statsExecutor.isShutdown()) {
|
if (statsExecutor != null && !statsExecutor.isShutdown()) {
|
||||||
statsExecutor.shutdownNow();
|
statsExecutor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
if (departureExecutor != null && !departureExecutor.isShutdown()) {
|
||||||
|
departureExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Wait briefly for termination (don't block forever)
|
// 3. Wait briefly for termination (don't block forever)
|
||||||
try {
|
try {
|
||||||
@@ -552,6 +574,9 @@ public class IntersectionProcess {
|
|||||||
if (statsExecutor != null) {
|
if (statsExecutor != null) {
|
||||||
statsExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
statsExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
if (departureExecutor != null) {
|
||||||
|
departureExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ public class SimulationConfig {
|
|||||||
* @return The multiplier for heavy vehicle travel time.
|
* @return The multiplier for heavy vehicle travel time.
|
||||||
*/
|
*/
|
||||||
public double getHeavyTravelTimeMultiplier() {
|
public double getHeavyTravelTimeMultiplier() {
|
||||||
return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "2.0"));
|
return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "4.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Statistics ---
|
// --- Statistics ---
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ public class DashboardUI extends Application {
|
|||||||
updateScheduler = Executors.newSingleThreadScheduledExecutor();
|
updateScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
updateScheduler.scheduleAtFixedRate(() -> {
|
updateScheduler.scheduleAtFixedRate(() -> {
|
||||||
Platform.runLater(this::updateUI);
|
Platform.runLater(this::updateUI);
|
||||||
}, 0, 5, TimeUnit.SECONDS);
|
}, 0, 100, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private void updateUI() {
|
||||||
|
|||||||
7
main/src/main/java/sd/dashboard/Launcher.java
Normal file
7
main/src/main/java/sd/dashboard/Launcher.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
public class Launcher {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
DashboardUI.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,18 +95,24 @@ public class SimulationProcessManager {
|
|||||||
builder = new ProcessBuilder(javaBin, "-cp", classpath, className);
|
builder = new ProcessBuilder(javaBin, "-cp", classpath, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a linux thing - not sure about windows
|
// 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";
|
String logName = className.substring(className.lastIndexOf('.') + 1) + (arg != null ? "-" + arg : "") + ".log";
|
||||||
File logFile = new File("/tmp/" + logName);
|
|
||||||
|
// use the (File parent, String child) constructor to handle slash/backslash
|
||||||
|
// automatically
|
||||||
|
File logFile = new File(tempDir, logName);
|
||||||
|
|
||||||
builder.redirectOutput(logFile);
|
builder.redirectOutput(logFile);
|
||||||
builder.redirectError(logFile);
|
builder.redirectError(logFile);
|
||||||
|
|
||||||
Process process = builder.start();
|
Process process = builder.start();
|
||||||
runningProcesses.add(process);
|
runningProcesses.add(process);
|
||||||
System.out.println("Started " + className + (arg != null ? " " + arg : ""));
|
System.out.println("Started " + className + (arg != null ? " " + arg : ""));
|
||||||
}
|
// print where the logs are actually going
|
||||||
|
System.out.println("Logs redirected to: " + logFile.getAbsolutePath());
|
||||||
public boolean isSimulationRunning() {
|
|
||||||
return !runningProcesses.isEmpty() && runningProcesses.stream().anyMatch(Process::isAlive);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ dashboard.port=9000
|
|||||||
# === SIMULATION CONFIGURATION ===
|
# === SIMULATION CONFIGURATION ===
|
||||||
|
|
||||||
# Total duration in seconds (3600 = 1 hour)
|
# Total duration in seconds (3600 = 1 hour)
|
||||||
simulation.duration=60.0
|
simulation.duration=3600
|
||||||
|
|
||||||
# Vehicle arrival model: FIXED or POISSON
|
# Vehicle arrival model: FIXED or POISSON
|
||||||
simulation.arrival.model=POISSON
|
simulation.arrival.model=POISSON
|
||||||
@@ -47,44 +47,33 @@ simulation.arrival.fixed.interval=2.0
|
|||||||
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
|
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
|
||||||
|
|
||||||
# Intersection 1 (Entry point - balanced)
|
# Intersection 1 (Entry point - balanced)
|
||||||
trafficlight.Cr1.South.green=20.0
|
trafficlight.Cr1.South.green=60.0
|
||||||
trafficlight.Cr1.South.red=40.0
|
trafficlight.Cr1.South.red=5.0
|
||||||
trafficlight.Cr1.East.green=20.0
|
trafficlight.Cr1.East.green=60.0
|
||||||
trafficlight.Cr1.East.red=40.0
|
trafficlight.Cr1.East.red=5.0
|
||||||
trafficlight.Cr1.West.green=20.0
|
|
||||||
trafficlight.Cr1.West.red=40.0
|
|
||||||
|
|
||||||
# Intersection 2 (Main hub - shorter cycles, favor East-West)
|
# Intersection 2 (Main hub - shorter cycles, favor East-West)
|
||||||
trafficlight.Cr2.South.green=12.0
|
trafficlight.Cr2.South.green=60.0
|
||||||
trafficlight.Cr2.South.red=36.0
|
trafficlight.Cr2.South.red=5.0
|
||||||
trafficlight.Cr2.East.green=18.0
|
trafficlight.Cr2.East.green=60.0
|
||||||
trafficlight.Cr2.East.red=30.0
|
trafficlight.Cr2.East.red=5.0
|
||||||
trafficlight.Cr2.West.green=18.0
|
trafficlight.Cr2.West.green=60.0
|
||||||
trafficlight.Cr2.West.red=30.0
|
trafficlight.Cr2.West.red=5.0
|
||||||
|
|
||||||
# Intersection 3 (Path to exit - favor East)
|
# Intersection 3 (Path to exit - favor East)
|
||||||
trafficlight.Cr3.South.green=15.0
|
trafficlight.Cr3.South.green=60.0
|
||||||
trafficlight.Cr3.South.red=30.0
|
trafficlight.Cr3.South.red=5.0
|
||||||
trafficlight.Cr3.East.green=20.0
|
trafficlight.Cr3.West.green=60.0
|
||||||
trafficlight.Cr3.East.red=25.0
|
trafficlight.Cr3.West.red=5.0
|
||||||
trafficlight.Cr3.West.green=15.0
|
|
||||||
trafficlight.Cr3.West.red=30.0
|
|
||||||
|
|
||||||
# Intersection 4 (Favor East toward Cr5)
|
# Intersection 4 (Favor East toward Cr5)
|
||||||
trafficlight.Cr4.South.green=15.0
|
trafficlight.Cr4.East.green=60.0
|
||||||
trafficlight.Cr4.South.red=30.0
|
trafficlight.Cr4.East.red=5.0
|
||||||
trafficlight.Cr4.East.green=20.0
|
|
||||||
trafficlight.Cr4.East.red=25.0
|
|
||||||
trafficlight.Cr4.West.green=15.0
|
|
||||||
trafficlight.Cr4.West.red=30.0
|
|
||||||
|
|
||||||
# Intersection 5 (Near exit - favor East)
|
# Intersection 5 (Near exit - favor East)
|
||||||
trafficlight.Cr5.South.green=15.0
|
trafficlight.Cr5.East.green=60.0
|
||||||
trafficlight.Cr5.South.red=30.0
|
trafficlight.Cr5.East.red=5.0
|
||||||
trafficlight.Cr5.East.green=22.0
|
|
||||||
trafficlight.Cr5.East.red=23.0
|
|
||||||
trafficlight.Cr5.West.green=15.0
|
|
||||||
trafficlight.Cr5.West.red=30.0
|
|
||||||
|
|
||||||
# === VEHICLE CONFIGURATION ===
|
# === VEHICLE CONFIGURATION ===
|
||||||
# Probability distribution for vehicle types (must sum to 1.0)
|
# Probability distribution for vehicle types (must sum to 1.0)
|
||||||
@@ -99,13 +88,13 @@ vehicle.crossing.time.heavy=4.0
|
|||||||
|
|
||||||
# Travel times between intersections (in seconds)
|
# Travel times between intersections (in seconds)
|
||||||
# Base time for light vehicles (cars)
|
# Base time for light vehicles (cars)
|
||||||
vehicle.travel.time.base=8.0
|
vehicle.travel.time.base=1.0
|
||||||
# Bike travel time = 0.5 × car travel time
|
# Bike travel time = 0.5 × car travel time
|
||||||
vehicle.travel.time.bike.multiplier=0.5
|
vehicle.travel.time.bike.multiplier=0.5
|
||||||
# Heavy vehicle travel time = 4 × bike travel time
|
# Heavy vehicle travel time = 4.0 x base travel time
|
||||||
vehicle.travel.time.heavy.multiplier=2.0
|
vehicle.travel.time.heavy.multiplier=4.0
|
||||||
|
|
||||||
# === STATISTICS ===
|
# === STATISTICS ===
|
||||||
|
|
||||||
# Interval between dashboard updates (seconds)
|
# Interval between dashboard updates (seconds)
|
||||||
statistics.update.interval=1.0
|
statistics.update.interval=0.1
|
||||||
|
|||||||
159
main/src/test/java/TravelTimeTest.java
Normal file
159
main/src/test/java/TravelTimeTest.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
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.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.Timeout;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import sd.IntersectionProcess;
|
||||||
|
import sd.model.Message;
|
||||||
|
import sd.model.MessageType;
|
||||||
|
import sd.model.Vehicle;
|
||||||
|
import sd.model.VehicleType;
|
||||||
|
import sd.protocol.SocketConnection;
|
||||||
|
|
||||||
|
public class TravelTimeTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
private Path configFile;
|
||||||
|
private IntersectionProcess intersectionProcess;
|
||||||
|
private Thread serverThread;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
configFile = tempDir.resolve("test-simulation.properties");
|
||||||
|
String configContent = """
|
||||||
|
intersection.Cr1.host=localhost
|
||||||
|
intersection.Cr1.port=19001
|
||||||
|
intersection.Cr2.host=localhost
|
||||||
|
intersection.Cr2.port=19002
|
||||||
|
|
||||||
|
# Base travel time = 1.0s for testing
|
||||||
|
vehicle.travel.time.base=1.0
|
||||||
|
vehicle.travel.time.bike.multiplier=0.5
|
||||||
|
vehicle.travel.time.heavy.multiplier=4.0
|
||||||
|
|
||||||
|
# Dummy values for others
|
||||||
|
dashboard.host=localhost
|
||||||
|
dashboard.port=19100
|
||||||
|
exit.host=localhost
|
||||||
|
exit.port=19099
|
||||||
|
""";
|
||||||
|
Files.writeString(configFile, configContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
if (intersectionProcess != null) {
|
||||||
|
intersectionProcess.shutdown();
|
||||||
|
}
|
||||||
|
if (serverThread != null) {
|
||||||
|
try {
|
||||||
|
serverThread.join(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(10)
|
||||||
|
public void testVariableTravelTimes() throws IOException, InterruptedException {
|
||||||
|
// Start Intersection Cr1
|
||||||
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
|
// Mock network config for Cr1 to know about Cr2
|
||||||
|
// Since we can't easily inject network config without file, we rely on
|
||||||
|
// IntersectionProcess
|
||||||
|
// using the properties file we created. But wait, IntersectionProcess loads
|
||||||
|
// network_config.json
|
||||||
|
// from classpath. This might be an issue if we need custom routing.
|
||||||
|
// However, sendVehicleToNextDestination just looks up host/port from
|
||||||
|
// properties.
|
||||||
|
// We need to ensure getOrCreateConnection works.
|
||||||
|
|
||||||
|
// Let's manually inject the connection or just rely on properties.
|
||||||
|
// The properties file has intersection.Cr2.host/port, so it should work.
|
||||||
|
|
||||||
|
// Start a "fake" Cr2 server to receive the vehicle
|
||||||
|
BlockingQueue<Long> arrivalTimes = new LinkedBlockingQueue<>();
|
||||||
|
ServerSocket fakeCr2 = new ServerSocket(19002);
|
||||||
|
Thread cr2Thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Socket socket = fakeCr2.accept();
|
||||||
|
SocketConnection conn = new SocketConnection(socket);
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
conn.receiveMessage();
|
||||||
|
arrivalTimes.offer(System.currentTimeMillis());
|
||||||
|
} catch (Exception e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// End
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cr2Thread.start();
|
||||||
|
|
||||||
|
// Send vehicles from Cr1
|
||||||
|
// We need to call sendVehicleToNextDestination directly.
|
||||||
|
// But we need to initialize Cr1 first (at least the executor).
|
||||||
|
// We can't easily call initialize() because it tries to connect to dashboard
|
||||||
|
// etc.
|
||||||
|
// But the constructor initializes the executors!
|
||||||
|
|
||||||
|
// 1. Light Vehicle (Base = 1.0s)
|
||||||
|
Vehicle lightVehicle = new Vehicle("V_LIGHT", VehicleType.LIGHT, 0, Arrays.asList("Cr2"));
|
||||||
|
long startLight = System.currentTimeMillis();
|
||||||
|
intersectionProcess.sendVehicleToNextDestination(lightVehicle);
|
||||||
|
|
||||||
|
Long arrivalLight = arrivalTimes.poll(2000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull(arrivalLight, "Light vehicle should arrive");
|
||||||
|
long durationLight = arrivalLight - startLight;
|
||||||
|
System.out.println("Light Duration: " + durationLight + "ms");
|
||||||
|
assertTrue(durationLight >= 1000, "Light vehicle should take at least 1000ms");
|
||||||
|
assertTrue(durationLight < 1500, "Light vehicle should be close to 1000ms");
|
||||||
|
|
||||||
|
// 2. Bike (0.5 * 1.0 = 0.5s)
|
||||||
|
Vehicle bikeVehicle = new Vehicle("V_BIKE", VehicleType.BIKE, 0, Arrays.asList("Cr2"));
|
||||||
|
long startBike = System.currentTimeMillis();
|
||||||
|
intersectionProcess.sendVehicleToNextDestination(bikeVehicle);
|
||||||
|
|
||||||
|
Long arrivalBike = arrivalTimes.poll(2000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull(arrivalBike, "Bike should arrive");
|
||||||
|
long durationBike = arrivalBike - startBike;
|
||||||
|
System.out.println("Bike Duration: " + durationBike + "ms");
|
||||||
|
assertTrue(durationBike >= 500, "Bike should take at least 500ms");
|
||||||
|
assertTrue(durationBike < 1000, "Bike should be close to 500ms");
|
||||||
|
|
||||||
|
// 3. Heavy (4.0 * 1.0 = 4.0s)
|
||||||
|
Vehicle heavyVehicle = new Vehicle("V_HEAVY", VehicleType.HEAVY, 0, Arrays.asList("Cr2"));
|
||||||
|
long startHeavy = System.currentTimeMillis();
|
||||||
|
intersectionProcess.sendVehicleToNextDestination(heavyVehicle);
|
||||||
|
|
||||||
|
Long arrivalHeavy = arrivalTimes.poll(5000, TimeUnit.MILLISECONDS);
|
||||||
|
assertNotNull(arrivalHeavy, "Heavy vehicle should arrive");
|
||||||
|
long durationHeavy = arrivalHeavy - startHeavy;
|
||||||
|
System.out.println("Heavy Duration: " + durationHeavy + "ms");
|
||||||
|
assertTrue(durationHeavy >= 4000, "Heavy vehicle should take at least 4000ms");
|
||||||
|
assertTrue(durationHeavy < 4500, "Heavy vehicle should be close to 4000ms");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
fakeCr2.close();
|
||||||
|
cr2Thread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,8 +133,8 @@ public class TrafficLightCoordinationTest {
|
|||||||
List<TrafficLight> lights = intersectionProcess.getIntersection().getTrafficLights();
|
List<TrafficLight> lights = intersectionProcess.getIntersection().getTrafficLights();
|
||||||
boolean[] hasBeenGreen = new boolean[lights.size()];
|
boolean[] hasBeenGreen = new boolean[lights.size()];
|
||||||
|
|
||||||
// Monitor for 60 seconds (enough time for all lights to cycle: 18+18+12 = 48s)
|
// Monitor for 10 seconds (enough time for all lights to cycle: 18+18+12 = 48s)
|
||||||
long endTime = System.currentTimeMillis() + 60000;
|
long endTime = System.currentTimeMillis() + 10000;
|
||||||
|
|
||||||
while (System.currentTimeMillis() < endTime) {
|
while (System.currentTimeMillis() < endTime) {
|
||||||
for (int i = 0; i < lights.size(); i++) {
|
for (int i = 0; i < lights.size(); i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user