From d28a77b6a4196f8c1f17bceddc970c8011637d8a Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sat, 29 Nov 2025 00:07:53 +0000 Subject: [PATCH] small fixes + debug --- .../src/main/java/sd/IntersectionProcess.java | 75 ++++++--- .../main/java/sd/config/SimulationConfig.java | 2 +- .../main/java/sd/dashboard/DashboardUI.java | 2 +- main/src/main/resources/simulation.properties | 59 +++---- main/src/test/java/TravelTimeTest.java | 159 ++++++++++++++++++ 5 files changed, 235 insertions(+), 62 deletions(-) create mode 100644 main/src/test/java/TravelTimeTest.java diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index bdcb74f..437b962 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -49,6 +49,7 @@ public class IntersectionProcess { private final ExecutorService trafficLightPool; private ScheduledExecutorService statsExecutor; + private ScheduledExecutorService departureExecutor; private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras // threads veem a mudança imediatamente. @@ -86,6 +87,7 @@ public class IntersectionProcess { this.connectionHandlerPool = Executors.newCachedThreadPool(); this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions this.statsExecutor = Executors.newSingleThreadScheduledExecutor(); + this.departureExecutor = Executors.newScheduledThreadPool(4); this.running = false; this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation this.currentGreenDirection = null; @@ -270,32 +272,49 @@ public class IntersectionProcess { public void sendVehicleToNextDestination(Vehicle vehicle) { String nextDestination = vehicle.getCurrentDestination(); - try { - // Get or create connection to next destination - SocketConnection connection = getOrCreateConnection(nextDestination); - - // Create and send message using Message class - MessageProtocol message = new Message( - MessageType.VEHICLE_TRANSFER, - intersectionId, - nextDestination, - vehicle, - System.currentTimeMillis()); - - connection.sendMessage(message); - - System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() + - " to " + nextDestination); - - // Record departure for statistics - recordVehicleDeparture(); - - // Note: vehicle route is advanced when it arrives at the next intersection - - } catch (IOException | InterruptedException e) { - System.err.println("[" + intersectionId + "] Failed to send vehicle " + - vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); + // 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 { + // Get or create connection to next destination + SocketConnection connection = getOrCreateConnection(nextDestination); + + // Create and send message using Message class + MessageProtocol message = new Message( + MessageType.VEHICLE_TRANSFER, + intersectionId, + nextDestination, + vehicle, + System.currentTimeMillis()); + + connection.sendMessage(message); + + System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() + + " arrived at " + nextDestination + " (msg sent)"); + + // Note: vehicle route is advanced when it arrives at the next intersection + + } catch (IOException | InterruptedException e) { + System.err.println("[" + intersectionId + "] Failed to send vehicle " + + vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); + } + }, travelTimeMs, TimeUnit.MILLISECONDS); } /** @@ -540,6 +559,9 @@ public class IntersectionProcess { if (statsExecutor != null && !statsExecutor.isShutdown()) { statsExecutor.shutdownNow(); } + if (departureExecutor != null && !departureExecutor.isShutdown()) { + departureExecutor.shutdownNow(); + } // 3. Wait briefly for termination (don't block forever) try { @@ -552,6 +574,9 @@ public class IntersectionProcess { if (statsExecutor != null) { statsExecutor.awaitTermination(1, TimeUnit.SECONDS); } + if (departureExecutor != null) { + departureExecutor.awaitTermination(1, TimeUnit.SECONDS); + } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/main/src/main/java/sd/config/SimulationConfig.java b/main/src/main/java/sd/config/SimulationConfig.java index b974495..6c11b66 100644 --- a/main/src/main/java/sd/config/SimulationConfig.java +++ b/main/src/main/java/sd/config/SimulationConfig.java @@ -373,7 +373,7 @@ public class SimulationConfig { * @return The multiplier for heavy vehicle travel time. */ 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 --- diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java index cb4cfcf..3ddd536 100644 --- a/main/src/main/java/sd/dashboard/DashboardUI.java +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -333,7 +333,7 @@ public class DashboardUI extends Application { updateScheduler = Executors.newSingleThreadScheduledExecutor(); updateScheduler.scheduleAtFixedRate(() -> { Platform.runLater(this::updateUI); - }, 0, 5, TimeUnit.SECONDS); + }, 0, 100, TimeUnit.MILLISECONDS); } private void updateUI() { diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties index 825476d..9ac81cc 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -31,7 +31,7 @@ dashboard.port=9000 # === SIMULATION CONFIGURATION === # Total duration in seconds (3600 = 1 hour) -simulation.duration=60.0 +simulation.duration=3600 # Vehicle arrival model: FIXED or POISSON simulation.arrival.model=POISSON @@ -47,44 +47,33 @@ simulation.arrival.fixed.interval=2.0 # Format: trafficlight...= # Intersection 1 (Entry point - balanced) -trafficlight.Cr1.South.green=20.0 -trafficlight.Cr1.South.red=40.0 -trafficlight.Cr1.East.green=20.0 -trafficlight.Cr1.East.red=40.0 -trafficlight.Cr1.West.green=20.0 -trafficlight.Cr1.West.red=40.0 +trafficlight.Cr1.South.green=60.0 +trafficlight.Cr1.South.red=5.0 +trafficlight.Cr1.East.green=60.0 +trafficlight.Cr1.East.red=5.0 # Intersection 2 (Main hub - shorter cycles, favor East-West) -trafficlight.Cr2.South.green=12.0 -trafficlight.Cr2.South.red=36.0 -trafficlight.Cr2.East.green=18.0 -trafficlight.Cr2.East.red=30.0 -trafficlight.Cr2.West.green=18.0 -trafficlight.Cr2.West.red=30.0 +trafficlight.Cr2.South.green=60.0 +trafficlight.Cr2.South.red=5.0 +trafficlight.Cr2.East.green=60.0 +trafficlight.Cr2.East.red=5.0 +trafficlight.Cr2.West.green=60.0 +trafficlight.Cr2.West.red=5.0 # Intersection 3 (Path to exit - favor East) -trafficlight.Cr3.South.green=15.0 -trafficlight.Cr3.South.red=30.0 -trafficlight.Cr3.East.green=20.0 -trafficlight.Cr3.East.red=25.0 -trafficlight.Cr3.West.green=15.0 -trafficlight.Cr3.West.red=30.0 +trafficlight.Cr3.South.green=60.0 +trafficlight.Cr3.South.red=5.0 +trafficlight.Cr3.West.green=60.0 +trafficlight.Cr3.West.red=5.0 # Intersection 4 (Favor East toward Cr5) -trafficlight.Cr4.South.green=15.0 -trafficlight.Cr4.South.red=30.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 +trafficlight.Cr4.East.green=60.0 +trafficlight.Cr4.East.red=5.0 # Intersection 5 (Near exit - favor East) -trafficlight.Cr5.South.green=15.0 -trafficlight.Cr5.South.red=30.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 +trafficlight.Cr5.East.green=60.0 +trafficlight.Cr5.East.red=5.0 + # === VEHICLE CONFIGURATION === # 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) # 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 vehicle.travel.time.bike.multiplier=0.5 -# Heavy vehicle travel time = 4 × bike travel time -vehicle.travel.time.heavy.multiplier=2.0 +# Heavy vehicle travel time = 4.0 x base travel time +vehicle.travel.time.heavy.multiplier=4.0 # === STATISTICS === # Interval between dashboard updates (seconds) -statistics.update.interval=1.0 +statistics.update.interval=0.1 diff --git a/main/src/test/java/TravelTimeTest.java b/main/src/test/java/TravelTimeTest.java new file mode 100644 index 0000000..d5d54bc --- /dev/null +++ b/main/src/test/java/TravelTimeTest.java @@ -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 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(); + } +}