mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
Compare commits
4 Commits
v0.6.5
...
dev-ui-tem
| Author | SHA1 | Date | |
|---|---|---|---|
| ce7f642246 | |||
| 8f97aab836 | |||
|
|
86c0c4b5b3 | ||
| 6fdcf376b2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -48,3 +48,6 @@ build/
|
||||
# Other
|
||||
*.swp
|
||||
*.pdf
|
||||
|
||||
# JAR built pom file
|
||||
dependency-reduced-pom.xml
|
||||
21
main/pom.xml
21
main/pom.xml
@@ -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>
|
||||
@@ -42,6 +54,15 @@
|
||||
<mainClass>sd.Entry</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.DashboardUI</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import sd.config.SimulationConfig;
|
||||
import sd.coordinator.SocketClient;
|
||||
import sd.dashboard.StatsUpdatePayload;
|
||||
import sd.model.Message;
|
||||
import sd.model.MessageType;
|
||||
import sd.model.Vehicle;
|
||||
@@ -38,6 +39,9 @@ public class ExitNodeProcess {
|
||||
/** Flag para controlar a execução do processo (volatile para visibilidade entre threads) */
|
||||
private volatile boolean running;
|
||||
|
||||
/** Simulation start time (milliseconds) to calculate relative times */
|
||||
private long simulationStartMillis;
|
||||
|
||||
/** Counter de veículos que completaram a rota */
|
||||
private int totalVehiclesReceived;
|
||||
|
||||
@@ -161,9 +165,10 @@ public class ExitNodeProcess {
|
||||
int port = config.getExitPort();
|
||||
serverSocket = new ServerSocket(port);
|
||||
running = true;
|
||||
simulationStartMillis = System.currentTimeMillis();
|
||||
|
||||
System.out.println("Exit node started on port " + port);
|
||||
System.out.println("Waiting for vehicles...\n");
|
||||
System.out.println("Waiting for vehicles...\\n");
|
||||
|
||||
while (running) {
|
||||
try {
|
||||
@@ -186,28 +191,54 @@ public class ExitNodeProcess {
|
||||
* @param clientSocket Socket da ligação estabelecida com a interseção
|
||||
*/
|
||||
private void handleIncomingConnection(Socket clientSocket) {
|
||||
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||
String clientAddress = clientSocket.getInetAddress().getHostAddress();
|
||||
System.out.println("New connection accepted from " + clientAddress);
|
||||
|
||||
System.out.println("New connection accepted from " +
|
||||
clientSocket.getInetAddress().getHostAddress());
|
||||
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) {
|
||||
// Coordinator sends start time - use it instead of our local start
|
||||
simulationStartMillis = ((Number) message.getPayload()).longValue();
|
||||
System.out.println("[Exit] Simulation start time synchronized");
|
||||
} else if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||
Object payload = message.getPayload();
|
||||
System.out.println("[Exit] Payload type: " + payload.getClass().getName());
|
||||
|
||||
// Handle Gson LinkedHashMap
|
||||
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;
|
||||
}
|
||||
|
||||
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||
Vehicle vehicle = (Vehicle) message.getPayload();
|
||||
processExitingVehicle(vehicle);
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("Unknown message type received: " + e.getMessage());
|
||||
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("Connection error: " + e.getMessage());
|
||||
System.err.println("[Exit] Connection error from " + clientAddress + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,10 +257,14 @@ public class ExitNodeProcess {
|
||||
private synchronized void processExitingVehicle(Vehicle vehicle) {
|
||||
totalVehiclesReceived++;
|
||||
|
||||
double systemTime = vehicle.getTotalTravelTime(getCurrentTime());
|
||||
// Calculate relative simulation time (seconds since simulation start)
|
||||
double currentSimTime = (System.currentTimeMillis() - simulationStartMillis) / 1000.0;
|
||||
// System time = time vehicle spent in system (current time - entry time)
|
||||
double systemTime = currentSimTime - vehicle.getEntryTime();
|
||||
double waitTime = vehicle.getTotalWaitingTime();
|
||||
double crossingTime = vehicle.getTotalCrossingTime();
|
||||
|
||||
// Store times in seconds, will be converted to ms when sending to dashboard
|
||||
totalSystemTime += systemTime;
|
||||
totalWaitingTime += waitTime;
|
||||
totalCrossingTime += crossingTime;
|
||||
@@ -238,13 +273,12 @@ public class ExitNodeProcess {
|
||||
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)%n",
|
||||
vehicle.getId(), vehicle.getType(), systemTime, 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);
|
||||
|
||||
if (totalVehiclesReceived % 10 == 0) {
|
||||
// Send stats after every vehicle to ensure dashboard updates quickly
|
||||
sendStatsToDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém o tempo atual da simulação em segundos.
|
||||
@@ -273,32 +307,42 @@ public class ExitNodeProcess {
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalVehicles", totalVehiclesReceived);
|
||||
stats.put("avgSystemTime", totalVehiclesReceived > 0 ? totalSystemTime / totalVehiclesReceived : 0.0);
|
||||
stats.put("avgWaitingTime", totalVehiclesReceived > 0 ? totalWaitingTime / totalVehiclesReceived : 0.0);
|
||||
stats.put("avgCrossingTime", totalVehiclesReceived > 0 ? totalCrossingTime / totalVehiclesReceived : 0.0);
|
||||
// Create stats payload
|
||||
StatsUpdatePayload payload = new StatsUpdatePayload();
|
||||
|
||||
// Set global stats - convert seconds to milliseconds
|
||||
payload.setTotalVehiclesCompleted(totalVehiclesReceived);
|
||||
payload.setTotalSystemTime((long)(totalSystemTime * 1000.0)); // s -> ms
|
||||
payload.setTotalWaitingTime((long)(totalWaitingTime * 1000.0)); // s -> ms
|
||||
|
||||
// Set vehicle type stats
|
||||
Map<VehicleType, Integer> typeCounts = new HashMap<>();
|
||||
Map<VehicleType, Long> typeWaitTimes = new HashMap<>();
|
||||
|
||||
Map<String, Integer> typeCounts = new HashMap<>();
|
||||
Map<String, Double> typeAvgWait = new HashMap<>();
|
||||
for (VehicleType type : VehicleType.values()) {
|
||||
int count = vehicleTypeCount.get(type);
|
||||
typeCounts.put(type.name(), count);
|
||||
if (count > 0) {
|
||||
typeAvgWait.put(type.name(), vehicleTypeWaitTime.get(type) / count);
|
||||
typeCounts.put(type, vehicleTypeCount.get(type));
|
||||
typeWaitTimes.put(type, (long)(vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms
|
||||
}
|
||||
}
|
||||
stats.put("vehicleTypeCounts", typeCounts);
|
||||
stats.put("vehicleTypeAvgWait", typeAvgWait);
|
||||
|
||||
Message message = new Message(MessageType.STATS_UPDATE, "ExitNode", "Dashboard", stats);
|
||||
payload.setVehicleTypeCounts(typeCounts);
|
||||
payload.setVehicleTypeWaitTimes(typeWaitTimes);
|
||||
|
||||
// Send message
|
||||
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, totalWaitingTime / totalVehiclesReceived);
|
||||
totalVehiclesReceived, avgWait);
|
||||
|
||||
} catch (SerializationException | IOException e) {
|
||||
System.err.println("Failed to send stats to dashboard: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
System.err.println("[Exit] Failed to send stats to dashboard: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,17 @@ import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import sd.config.SimulationConfig;
|
||||
import sd.coordinator.SocketClient;
|
||||
import sd.dashboard.StatsUpdatePayload;
|
||||
import sd.engine.TrafficLightThread;
|
||||
import sd.model.Intersection;
|
||||
import sd.model.Message;
|
||||
import sd.model.MessageType;
|
||||
import sd.model.TrafficLight;
|
||||
import sd.model.Vehicle;
|
||||
import sd.protocol.MessageProtocol;
|
||||
import sd.protocol.SocketConnection;
|
||||
import sd.serialization.SerializationException;
|
||||
|
||||
/**
|
||||
* Main class for an Intersection Process in the distributed traffic simulation.
|
||||
@@ -59,6 +63,12 @@ public class IntersectionProcess {
|
||||
*/
|
||||
private volatile String currentGreenDirection;
|
||||
|
||||
private SocketClient dashboardClient;
|
||||
private long simulationStartMillis;
|
||||
private volatile int totalArrivals = 0;
|
||||
private volatile int totalDepartures = 0;
|
||||
private long lastStatsUpdateTime;
|
||||
|
||||
/**
|
||||
* Constructs a new IntersectionProcess.
|
||||
*
|
||||
@@ -82,6 +92,35 @@ public class IntersectionProcess {
|
||||
System.out.println("=".repeat(60));
|
||||
}
|
||||
|
||||
// Main entry point for running an intersection process
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: java IntersectionProcess <intersectionId> [configFile]");
|
||||
System.err.println("Example: java IntersectionProcess Cr1");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String intersectionId = args[0];
|
||||
String configFile = args.length > 1 ? args[1] : "src/main/resources/simulation.properties";
|
||||
|
||||
try {
|
||||
IntersectionProcess process = new IntersectionProcess(intersectionId, configFile);
|
||||
process.initialize();
|
||||
process.start();
|
||||
|
||||
// Add shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
System.out.println("\nShutdown signal received...");
|
||||
process.shutdown();
|
||||
}));
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to start intersection process: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
|
||||
|
||||
@@ -89,9 +128,36 @@ public class IntersectionProcess {
|
||||
|
||||
configureRouting();
|
||||
|
||||
connectToDashboard();
|
||||
|
||||
System.out.println("[" + intersectionId + "] Initialization complete.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes connection to the dashboard server for statistics reporting.
|
||||
*/
|
||||
private void connectToDashboard() {
|
||||
try {
|
||||
String dashboardHost = config.getDashboardHost();
|
||||
int dashboardPort = config.getDashboardPort();
|
||||
|
||||
System.out.println("[" + intersectionId + "] Connecting to dashboard at " +
|
||||
dashboardHost + ":" + dashboardPort + "...");
|
||||
|
||||
dashboardClient = new SocketClient(intersectionId, dashboardHost, dashboardPort);
|
||||
dashboardClient.connect();
|
||||
|
||||
System.out.println("[" + intersectionId + "] Connected to dashboard.");
|
||||
lastStatsUpdateTime = System.currentTimeMillis();
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("[" + intersectionId + "] Failed to connect to dashboard: " +
|
||||
e.getMessage());
|
||||
System.err.println("[" + intersectionId + "] Will continue without dashboard reporting.");
|
||||
dashboardClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates traffic lights for this intersection based on its physical
|
||||
* connections.
|
||||
@@ -222,17 +288,22 @@ public class IntersectionProcess {
|
||||
// Get or create connection to next destination
|
||||
SocketConnection connection = getOrCreateConnection(nextDestination);
|
||||
|
||||
// Create and send message
|
||||
MessageProtocol message = new VehicleTransferMessage(
|
||||
// Create and send message using Message class
|
||||
MessageProtocol message = new Message(
|
||||
MessageType.VEHICLE_TRANSFER,
|
||||
intersectionId,
|
||||
nextDestination,
|
||||
vehicle);
|
||||
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) {
|
||||
@@ -374,14 +445,42 @@ public class IntersectionProcess {
|
||||
try {
|
||||
MessageProtocol message = connection.receiveMessage();
|
||||
|
||||
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||
Vehicle vehicle = (Vehicle) message.getPayload();
|
||||
// Handle simulation start time synchronization
|
||||
if (message.getType() == MessageType.SIMULATION_START) {
|
||||
simulationStartMillis = ((Number) message.getPayload()).longValue();
|
||||
System.out.println("[" + intersectionId + "] Simulation start time synchronized");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Accept both VEHICLE_TRANSFER and VEHICLE_SPAWN (from coordinator)
|
||||
if (message.getType() == MessageType.VEHICLE_TRANSFER ||
|
||||
message.getType() == MessageType.VEHICLE_SPAWN) {
|
||||
// Cast payload to Vehicle - handle Gson deserialization
|
||||
Vehicle vehicle;
|
||||
Object payload = message.getPayload();
|
||||
if (payload instanceof Vehicle) {
|
||||
vehicle = (Vehicle) payload;
|
||||
} else if (payload instanceof java.util.Map) {
|
||||
// Gson deserialized as LinkedHashMap - re-serialize and deserialize as Vehicle
|
||||
com.google.gson.Gson gson = new com.google.gson.Gson();
|
||||
String json = gson.toJson(payload);
|
||||
vehicle = gson.fromJson(json, Vehicle.class);
|
||||
} else {
|
||||
System.err.println("[" + intersectionId + "] Unknown payload type: " + payload.getClass());
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("[" + intersectionId + "] Received vehicle: " +
|
||||
vehicle.getId() + " from " + message.getSourceNode());
|
||||
|
||||
// Advance vehicle to next destination in its route
|
||||
vehicle.advanceRoute();
|
||||
|
||||
// Add vehicle to appropriate queue
|
||||
intersection.receiveVehicle(vehicle);
|
||||
|
||||
// Record arrival for statistics
|
||||
recordVehicleArrival();
|
||||
}
|
||||
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
@@ -394,6 +493,13 @@ public class IntersectionProcess {
|
||||
System.err.println("[" + intersectionId + "] Unknown message type received: " +
|
||||
e.getMessage());
|
||||
break; // Invalid message, close connection
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
System.err.println("[" + intersectionId + "] Failed to deserialize message: " +
|
||||
e.getMessage());
|
||||
e.printStackTrace(); // For debugging - maybe change//remove later
|
||||
}
|
||||
break; // Connection error, close connection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +565,11 @@ public class IntersectionProcess {
|
||||
outgoingConnections.clear();
|
||||
}
|
||||
|
||||
// 5. Close dashboard connection
|
||||
if (dashboardClient != null) {
|
||||
dashboardClient.close();
|
||||
}
|
||||
|
||||
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
||||
System.out.println("============================================================\n");
|
||||
}
|
||||
@@ -473,6 +584,73 @@ public class IntersectionProcess {
|
||||
return intersection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that a vehicle has arrived at this intersection.
|
||||
*/
|
||||
public void recordVehicleArrival() {
|
||||
totalArrivals++;
|
||||
checkAndSendStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that a vehicle has departed from this intersection.
|
||||
*/
|
||||
public void recordVehicleDeparture() {
|
||||
totalDepartures++;
|
||||
checkAndSendStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it's time to send statistics to the dashboard and sends them if needed.
|
||||
*/
|
||||
private void checkAndSendStats() {
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsed = now - lastStatsUpdateTime;
|
||||
|
||||
// Send stats every 5 seconds
|
||||
if (elapsed >= 5000) {
|
||||
sendStatsToDashboard();
|
||||
lastStatsUpdateTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends current statistics to the dashboard server.
|
||||
*/
|
||||
private void sendStatsToDashboard() {
|
||||
if (dashboardClient == null || !dashboardClient.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Calculate current queue size
|
||||
int currentQueueSize = intersection.getTrafficLights().stream()
|
||||
.mapToInt(TrafficLight::getQueueSize)
|
||||
.sum();
|
||||
|
||||
StatsUpdatePayload payload = new StatsUpdatePayload()
|
||||
.setIntersectionArrivals(totalArrivals)
|
||||
.setIntersectionDepartures(totalDepartures)
|
||||
.setIntersectionQueueSize(currentQueueSize);
|
||||
|
||||
// Send StatsUpdatePayload directly as the message payload
|
||||
sd.model.Message message = new sd.model.Message(
|
||||
MessageType.STATS_UPDATE,
|
||||
intersectionId,
|
||||
"Dashboard",
|
||||
payload
|
||||
);
|
||||
|
||||
dashboardClient.send(message);
|
||||
|
||||
System.out.printf("[%s] Sent stats to dashboard (arrivals=%d, departures=%d, queue=%d)%n",
|
||||
intersectionId, totalArrivals, totalDepartures, currentQueueSize);
|
||||
|
||||
} catch (SerializationException | IOException e) {
|
||||
System.err.println("[" + intersectionId + "] Failed to send stats to dashboard: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inner class for Vehicle Transfer Messages ---
|
||||
|
||||
/**
|
||||
|
||||
@@ -227,6 +227,32 @@ public class SimulationConfig {
|
||||
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.heavy", "4.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base travel time between intersections for light vehicles.
|
||||
* @return The base travel time in seconds.
|
||||
*/
|
||||
public double getBaseTravelTime() {
|
||||
return Double.parseDouble(properties.getProperty("vehicle.travel.time.base", "8.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the travel time multiplier for bike vehicles.
|
||||
* Bike travel time = base time × this multiplier.
|
||||
* @return The multiplier for bike travel time.
|
||||
*/
|
||||
public double getBikeTravelTimeMultiplier() {
|
||||
return Double.parseDouble(properties.getProperty("vehicle.travel.time.bike.multiplier", "0.5"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the travel time multiplier for heavy vehicles.
|
||||
* Heavy vehicle travel time = base time × this multiplier.
|
||||
* @return The multiplier for heavy vehicle travel time.
|
||||
*/
|
||||
public double getHeavyTravelTimeMultiplier() {
|
||||
return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "2.0"));
|
||||
}
|
||||
|
||||
// --- Statistics ---
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import sd.config.SimulationConfig;
|
||||
import sd.dashboard.StatsUpdatePayload;
|
||||
import sd.model.Message;
|
||||
import sd.model.MessageType;
|
||||
import sd.model.Vehicle;
|
||||
@@ -24,6 +25,7 @@ public class CoordinatorProcess {
|
||||
private final SimulationConfig config;
|
||||
private final VehicleGenerator vehicleGenerator;
|
||||
private final Map<String, SocketClient> intersectionClients;
|
||||
private SocketClient dashboardClient;
|
||||
private double currentTime;
|
||||
private int vehicleCounter;
|
||||
private boolean running;
|
||||
@@ -75,6 +77,9 @@ public class CoordinatorProcess {
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// Connect to dashboard first
|
||||
connectToDashboard();
|
||||
|
||||
System.out.println("Connecting to intersection processes...");
|
||||
|
||||
String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"};
|
||||
@@ -108,6 +113,9 @@ public class CoordinatorProcess {
|
||||
System.out.println("Duration: " + duration + " seconds");
|
||||
System.out.println();
|
||||
|
||||
// Send simulation start time to all processes for synchronization
|
||||
sendSimulationStartTime();
|
||||
|
||||
nextGenerationTime = vehicleGenerator.getNextArrivalTime(currentTime);
|
||||
final double TIME_STEP = 0.1;
|
||||
|
||||
@@ -132,6 +140,9 @@ public class CoordinatorProcess {
|
||||
System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n",
|
||||
currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute());
|
||||
|
||||
// Send generation count to dashboard
|
||||
sendGenerationStatsToDashboard();
|
||||
|
||||
if (vehicle.getRoute().isEmpty()) {
|
||||
System.err.println("ERROR: Vehicle " + vehicle.getId() + " has empty route!");
|
||||
return;
|
||||
@@ -201,4 +212,77 @@ public class CoordinatorProcess {
|
||||
System.out.println("\nStop signal received...");
|
||||
running = false;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package sd.dashboard;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.Map;
|
||||
|
||||
import sd.model.MessageType;
|
||||
import sd.protocol.MessageProtocol;
|
||||
@@ -71,12 +72,22 @@ public class DashboardClientHandler implements Runnable {
|
||||
|
||||
System.out.println("[Handler] Received STATS_UPDATE from: " + senderId);
|
||||
|
||||
if (payload instanceof StatsUpdatePayload stats) {
|
||||
updateStatistics(senderId, stats);
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
private void updateStatistics(String senderId, StatsUpdatePayload stats) {
|
||||
@@ -88,14 +99,30 @@ public class DashboardClientHandler implements Runnable {
|
||||
statistics.updateVehiclesCompleted(stats.getTotalVehiclesCompleted());
|
||||
}
|
||||
|
||||
// Exit Node sends cumulative totals, so we SET rather than ADD
|
||||
if (stats.getTotalSystemTime() >= 0) {
|
||||
statistics.addSystemTime(stats.getTotalSystemTime());
|
||||
statistics.setTotalSystemTime(stats.getTotalSystemTime());
|
||||
}
|
||||
|
||||
if (stats.getTotalWaitingTime() >= 0) {
|
||||
statistics.addWaitingTime(stats.getTotalWaitingTime());
|
||||
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,
|
||||
|
||||
@@ -22,13 +22,29 @@ public class DashboardServer {
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
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 {
|
||||
// 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);
|
||||
@@ -52,6 +68,7 @@ public class DashboardServer {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DashboardServer(SimulationConfig config) {
|
||||
this.port = config.getDashboardPort();
|
||||
|
||||
@@ -68,11 +68,21 @@ public class DashboardStatistics {
|
||||
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);
|
||||
|
||||
378
main/src/main/java/sd/dashboard/DashboardUI.java
Normal file
378
main/src/main/java/sd/dashboard/DashboardUI.java
Normal file
@@ -0,0 +1,378 @@
|
||||
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.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TitledPane;
|
||||
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.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.stage.Stage;
|
||||
import sd.config.SimulationConfig;
|
||||
import sd.model.VehicleType;
|
||||
|
||||
/**
|
||||
* JavaFX-based Dashboard UI for displaying real-time simulation statistics.
|
||||
* Provides a graphical interface with auto-updating statistics panels.
|
||||
*/
|
||||
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
|
||||
private ScheduledExecutorService updateScheduler;
|
||||
|
||||
@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
|
||||
server.start();
|
||||
|
||||
// Build UI
|
||||
BorderPane root = new BorderPane();
|
||||
root.setStyle("-fx-background-color: #f5f5f5;");
|
||||
|
||||
// Header
|
||||
VBox header = createHeader();
|
||||
root.setTop(header);
|
||||
|
||||
// Main content
|
||||
VBox mainContent = createMainContent();
|
||||
root.setCenter(mainContent);
|
||||
|
||||
// Footer
|
||||
HBox footer = createFooter();
|
||||
root.setBottom(footer);
|
||||
|
||||
// Create scene
|
||||
Scene scene = new Scene(root, 1200, 800);
|
||||
primaryStage.setTitle("Traffic Simulation Dashboard - Real-time Statistics");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
|
||||
// Start periodic updates
|
||||
startPeriodicUpdates();
|
||||
|
||||
// Handle window close
|
||||
primaryStage.setOnCloseRequest(event -> {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
showErrorAlert("Failed to start Dashboard Server", e.getMessage());
|
||||
Platform.exit();
|
||||
}
|
||||
}
|
||||
|
||||
private VBox createHeader() {
|
||||
VBox header = new VBox(10);
|
||||
header.setPadding(new Insets(20));
|
||||
header.setStyle("-fx-background-color: linear-gradient(to right, #2c3e50, #3498db);");
|
||||
|
||||
Label title = new Label("DISTRIBUTED TRAFFIC SIMULATION DASHBOARD");
|
||||
title.setFont(Font.font("Arial", FontWeight.BOLD, 28));
|
||||
title.setTextFill(Color.WHITE);
|
||||
|
||||
Label subtitle = new Label("Real-time Statistics and Monitoring");
|
||||
subtitle.setFont(Font.font("Arial", FontWeight.NORMAL, 16));
|
||||
subtitle.setTextFill(Color.web("#ecf0f1"));
|
||||
|
||||
header.getChildren().addAll(title, subtitle);
|
||||
header.setAlignment(Pos.CENTER);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private VBox createMainContent() {
|
||||
VBox mainContent = new VBox(15);
|
||||
mainContent.setPadding(new Insets(20));
|
||||
|
||||
// Global Statistics Panel
|
||||
TitledPane globalStatsPane = createGlobalStatisticsPanel();
|
||||
|
||||
// Vehicle Type Statistics Panel
|
||||
TitledPane vehicleTypePane = createVehicleTypePanel();
|
||||
|
||||
// Intersection Statistics Panel
|
||||
TitledPane intersectionPane = createIntersectionPanel();
|
||||
|
||||
mainContent.getChildren().addAll(globalStatsPane, vehicleTypePane, intersectionPane);
|
||||
|
||||
return mainContent;
|
||||
}
|
||||
|
||||
private TitledPane createGlobalStatisticsPanel() {
|
||||
GridPane grid = new GridPane();
|
||||
grid.setPadding(new Insets(15));
|
||||
grid.setHgap(20);
|
||||
grid.setVgap(15);
|
||||
grid.setStyle("-fx-background-color: white; -fx-border-radius: 5;");
|
||||
|
||||
// Initialize labels
|
||||
lblVehiclesGenerated = createStatLabel("0");
|
||||
lblVehiclesCompleted = createStatLabel("0");
|
||||
lblVehiclesInTransit = createStatLabel("0");
|
||||
lblAvgSystemTime = createStatLabel("0.00 ms");
|
||||
lblAvgWaitingTime = createStatLabel("0.00 ms");
|
||||
|
||||
// Add labels with descriptions
|
||||
addStatRow(grid, 0, "Total Vehicles Generated:", lblVehiclesGenerated);
|
||||
addStatRow(grid, 1, "Total Vehicles Completed:", lblVehiclesCompleted);
|
||||
addStatRow(grid, 2, "Vehicles In Transit:", lblVehiclesInTransit);
|
||||
addStatRow(grid, 3, "Average System Time:", lblAvgSystemTime);
|
||||
addStatRow(grid, 4, "Average Waiting Time:", lblAvgWaitingTime);
|
||||
|
||||
TitledPane pane = new TitledPane("Global Statistics", grid);
|
||||
pane.setCollapsible(false);
|
||||
pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
private TitledPane createVehicleTypePanel() {
|
||||
vehicleTypeTable = new TableView<>();
|
||||
vehicleTypeTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
vehicleTypeTable.setPrefHeight(200);
|
||||
|
||||
TableColumn<VehicleTypeRow, String> typeCol = new TableColumn<>("Vehicle Type");
|
||||
typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType"));
|
||||
typeCol.setPrefWidth(200);
|
||||
|
||||
TableColumn<VehicleTypeRow, Integer> countCol = new TableColumn<>("Count");
|
||||
countCol.setCellValueFactory(new PropertyValueFactory<>("count"));
|
||||
countCol.setPrefWidth(150);
|
||||
|
||||
TableColumn<VehicleTypeRow, String> avgWaitCol = new TableColumn<>("Avg Wait Time");
|
||||
avgWaitCol.setCellValueFactory(new PropertyValueFactory<>("avgWaitTime"));
|
||||
avgWaitCol.setPrefWidth(150);
|
||||
|
||||
vehicleTypeTable.getColumns().addAll(typeCol, countCol, avgWaitCol);
|
||||
|
||||
TitledPane pane = new TitledPane("Vehicle Type Statistics", vehicleTypeTable);
|
||||
pane.setCollapsible(false);
|
||||
pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
private TitledPane createIntersectionPanel() {
|
||||
intersectionTable = new TableView<>();
|
||||
intersectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
intersectionTable.setPrefHeight(250);
|
||||
|
||||
TableColumn<IntersectionRow, String> idCol = new TableColumn<>("Intersection ID");
|
||||
idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId"));
|
||||
idCol.setPrefWidth(200);
|
||||
|
||||
TableColumn<IntersectionRow, Integer> arrivalsCol = new TableColumn<>("Total Arrivals");
|
||||
arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals"));
|
||||
arrivalsCol.setPrefWidth(150);
|
||||
|
||||
TableColumn<IntersectionRow, Integer> departuresCol = new TableColumn<>("Total Departures");
|
||||
departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures"));
|
||||
departuresCol.setPrefWidth(150);
|
||||
|
||||
TableColumn<IntersectionRow, Integer> queueCol = new TableColumn<>("Current Queue");
|
||||
queueCol.setCellValueFactory(new PropertyValueFactory<>("queueSize"));
|
||||
queueCol.setPrefWidth(150);
|
||||
|
||||
intersectionTable.getColumns().addAll(idCol, arrivalsCol, departuresCol, queueCol);
|
||||
|
||||
TitledPane pane = new TitledPane("Intersection Statistics", intersectionTable);
|
||||
pane.setCollapsible(false);
|
||||
pane.setFont(Font.font("Arial", FontWeight.BOLD, 16));
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
private HBox createFooter() {
|
||||
HBox footer = new HBox(10);
|
||||
footer.setPadding(new Insets(10, 20, 10, 20));
|
||||
footer.setStyle("-fx-background-color: #34495e;");
|
||||
footer.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Label statusLabel = new Label("Status:");
|
||||
statusLabel.setTextFill(Color.WHITE);
|
||||
statusLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
|
||||
|
||||
Circle statusIndicator = new Circle(6);
|
||||
statusIndicator.setFill(Color.LIME);
|
||||
|
||||
Label statusText = new Label("Connected and Receiving Data");
|
||||
statusText.setTextFill(Color.WHITE);
|
||||
statusText.setFont(Font.font("Arial", 12));
|
||||
|
||||
lblLastUpdate = new Label("Last Update: --:--:--");
|
||||
lblLastUpdate.setTextFill(Color.web("#ecf0f1"));
|
||||
lblLastUpdate.setFont(Font.font("Arial", 12));
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
footer.getChildren().addAll(statusLabel, statusIndicator, statusText, spacer, lblLastUpdate);
|
||||
|
||||
return footer;
|
||||
}
|
||||
|
||||
private Label createStatLabel(String initialValue) {
|
||||
Label label = new Label(initialValue);
|
||||
label.setFont(Font.font("Arial", FontWeight.BOLD, 20));
|
||||
label.setTextFill(Color.web("#2980b9"));
|
||||
return label;
|
||||
}
|
||||
|
||||
private void addStatRow(GridPane grid, int row, String description, Label valueLabel) {
|
||||
Label descLabel = new Label(description);
|
||||
descLabel.setFont(Font.font("Arial", FontWeight.NORMAL, 14));
|
||||
descLabel.setTextFill(Color.web("#34495e"));
|
||||
|
||||
grid.add(descLabel, 0, row);
|
||||
grid.add(valueLabel, 1, row);
|
||||
}
|
||||
|
||||
private void startPeriodicUpdates() {
|
||||
updateScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
updateScheduler.scheduleAtFixedRate(() -> {
|
||||
Platform.runLater(this::updateUI);
|
||||
}, 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
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 ms", statistics.getAverageSystemTime()));
|
||||
lblAvgWaitingTime.setText(String.format("%.2f ms", statistics.getAverageWaitingTime()));
|
||||
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 ms", avgWait)));
|
||||
}
|
||||
|
||||
// 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()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
// Inner classes for TableView data models
|
||||
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; }
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,14 @@ public class SimulationEngine {
|
||||
private final PriorityQueue<Event> eventQueue;
|
||||
|
||||
/**
|
||||
* A map storing all intersections in the simulation, keyed by their ID (e.g., "Cr1").
|
||||
* 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.
|
||||
* Responsible for creating new vehicles according to the configured arrival
|
||||
* model.
|
||||
*/
|
||||
private final VehicleGenerator vehicleGenerator;
|
||||
|
||||
@@ -82,6 +84,26 @@ public class SimulationEngine {
|
||||
this.vehicleCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the travel time between intersections based on vehicle type.
|
||||
*
|
||||
* @param vehicleType The type of the vehicle.
|
||||
* @return The travel time in seconds.
|
||||
*/
|
||||
private double calculateTravelTime(VehicleType vehicleType) {
|
||||
double baseTime = config.getBaseTravelTime();
|
||||
|
||||
switch (vehicleType) {
|
||||
case BIKE:
|
||||
return baseTime * config.getBikeTravelTimeMultiplier();
|
||||
case HEAVY:
|
||||
return baseTime * config.getHeavyTravelTimeMultiplier();
|
||||
case LIGHT:
|
||||
default:
|
||||
return baseTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the simulation. This involves:
|
||||
* 1. Creating all {@link Intersection} and {@link TrafficLight} objects.
|
||||
@@ -124,8 +146,7 @@ public class SimulationEngine {
|
||||
id + "-" + direction,
|
||||
direction,
|
||||
greenTime,
|
||||
redTime
|
||||
);
|
||||
redTime);
|
||||
|
||||
intersection.addTrafficLight(light);
|
||||
}
|
||||
@@ -137,7 +158,8 @@ public class SimulationEngine {
|
||||
/**
|
||||
* 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
|
||||
* * 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."
|
||||
*/
|
||||
@@ -188,7 +210,8 @@ public class SimulationEngine {
|
||||
*
|
||||
* @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.
|
||||
* @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;
|
||||
@@ -200,7 +223,8 @@ public class SimulationEngine {
|
||||
* 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}).
|
||||
* @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.
|
||||
@@ -258,7 +282,8 @@ public class SimulationEngine {
|
||||
|
||||
/**
|
||||
* Main event processing logic.
|
||||
* Delegates the event to the appropriate handler method based on its {@link EventType}.
|
||||
* Delegates the event to the appropriate handler method based on its
|
||||
* {@link EventType}.
|
||||
*
|
||||
* @param event The {@link Event} to be processed.
|
||||
*/
|
||||
@@ -288,7 +313,8 @@ public class SimulationEngine {
|
||||
* 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.)
|
||||
* as it implies only one vehicle is ever generated. It should likely be
|
||||
* active.)
|
||||
*/
|
||||
private void handleVehicleGeneration() {
|
||||
Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime);
|
||||
@@ -302,8 +328,8 @@ public class SimulationEngine {
|
||||
// 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;
|
||||
double travelTime = calculateTravelTime(vehicle.getType());
|
||||
double arrivalTime = currentTime + travelTime;
|
||||
Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, firstIntersection);
|
||||
eventQueue.offer(arrivalEvent);
|
||||
}
|
||||
@@ -324,7 +350,8 @@ public class SimulationEngine {
|
||||
* 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.
|
||||
* @param event The arrival event, containing the {@link Vehicle} and
|
||||
* intersection ID.
|
||||
*/
|
||||
private void handleVehicleArrival(Event event) {
|
||||
Vehicle vehicle = (Vehicle) event.getData();
|
||||
@@ -359,7 +386,8 @@ public class SimulationEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add vehicle to the appropriate traffic light queue based on its next destination
|
||||
// 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
|
||||
@@ -443,7 +471,8 @@ public class SimulationEngine {
|
||||
* 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)}.
|
||||
* 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.
|
||||
*
|
||||
@@ -469,8 +498,9 @@ public class SimulationEngine {
|
||||
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;
|
||||
// Travel time varies by vehicle type: tmoto = 0.5 × tcarro, tcaminhão = 4 ×
|
||||
// tmoto
|
||||
double travelTime = calculateTravelTime(vehicle.getType());
|
||||
double arrivalTime = currentTime + travelTime;
|
||||
Event arrivalEvent = new Event(arrivalTime, EventType.VEHICLE_ARRIVAL, vehicle, nextDest);
|
||||
eventQueue.offer(arrivalEvent);
|
||||
@@ -499,7 +529,8 @@ public class SimulationEngine {
|
||||
/**
|
||||
* 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)}
|
||||
* 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.
|
||||
@@ -572,7 +603,8 @@ public class SimulationEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to get the configured crossing time for a given {@link VehicleType}.
|
||||
* 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.
|
||||
@@ -603,6 +635,7 @@ public class SimulationEngine {
|
||||
|
||||
/**
|
||||
* Gets the current simulation time.
|
||||
*
|
||||
* @return The time in virtual seconds.
|
||||
*/
|
||||
public double getCurrentTime() {
|
||||
@@ -612,6 +645,7 @@ public class SimulationEngine {
|
||||
/**
|
||||
* 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() {
|
||||
@@ -620,6 +654,7 @@ public class SimulationEngine {
|
||||
|
||||
/**
|
||||
* Gets the statistics collector instance.
|
||||
*
|
||||
* @return The {@link StatisticsCollector}.
|
||||
*/
|
||||
public StatisticsCollector getStatisticsCollector() {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
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.
|
||||
* This class implements {@link MessageProtocol} which extends Serializable for network transmission.
|
||||
*/
|
||||
public class Message implements Serializable {
|
||||
public class Message implements MessageProtocol {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -132,6 +133,17 @@ public class Message implements Serializable {
|
||||
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]",
|
||||
|
||||
@@ -19,6 +19,12 @@ public enum MessageType {
|
||||
*/
|
||||
STATS_UPDATE,
|
||||
|
||||
/**
|
||||
* Message to synchronize simulation start time across all processes.
|
||||
* Payload: Start timestamp (long milliseconds)
|
||||
*/
|
||||
SIMULATION_START,
|
||||
|
||||
/**
|
||||
* Message to synchronize traffic light states between processes.
|
||||
* Payload: TrafficLight state and timing information
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
@@ -94,6 +96,12 @@ public class TrafficLight {
|
||||
*/
|
||||
private int totalVehiclesProcessed;
|
||||
|
||||
/**
|
||||
* Track when vehicles arrive at this light for wait time calculation.
|
||||
* Maps vehicle ID to arrival timestamp (milliseconds).
|
||||
*/
|
||||
private final Map<String, Long> vehicleArrivalTimes;
|
||||
|
||||
/**
|
||||
* Constructs a new TrafficLight.
|
||||
*
|
||||
@@ -115,6 +123,7 @@ public class TrafficLight {
|
||||
|
||||
this.greenTime = greenTime;
|
||||
this.redTime = redTime;
|
||||
this.vehicleArrivalTimes = new HashMap<>();
|
||||
this.totalVehiclesProcessed = 0;
|
||||
}
|
||||
|
||||
@@ -128,6 +137,7 @@ public class TrafficLight {
|
||||
lock.lock(); // Acquire the lock
|
||||
try {
|
||||
queue.offer(vehicle); // Add vehicle to queue
|
||||
vehicleArrivalTimes.put(vehicle.getId(), System.currentTimeMillis());
|
||||
vehicleAdded.signalAll(); // Signal (for concurrent models)
|
||||
} finally {
|
||||
lock.unlock(); // Always release the lock
|
||||
@@ -152,6 +162,13 @@ public class TrafficLight {
|
||||
Vehicle vehicle = queue.poll(); // Remove vehicle from queue
|
||||
if (vehicle != null) {
|
||||
totalVehiclesProcessed++;
|
||||
|
||||
// Calculate wait time (time spent in queue)
|
||||
Long arrivalTime = vehicleArrivalTimes.remove(vehicle.getId());
|
||||
if (arrivalTime != null) {
|
||||
double waitTimeSeconds = (System.currentTimeMillis() - arrivalTime) / 1000.0;
|
||||
vehicle.addWaitingTime(waitTimeSeconds);
|
||||
}
|
||||
}
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
@@ -172,8 +172,8 @@ public class SocketConnection implements Closeable {
|
||||
byte[] data = new byte[length];
|
||||
dataIn.readFully(data);
|
||||
|
||||
// Deserialize do JSON
|
||||
return serializer.deserialize(data, MessageProtocol.class);
|
||||
// Deserialize do JSON - use concrete Message class, not interface
|
||||
return serializer.deserialize(data, sd.model.Message.class);
|
||||
|
||||
} catch (SerializationException e) {
|
||||
throw new IOException("Failed to deserialize message", e);
|
||||
|
||||
@@ -46,54 +46,54 @@ simulation.arrival.fixed.interval=2.0
|
||||
# === TRAFFIC LIGHT TIMINGS ===
|
||||
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
|
||||
|
||||
# Intersection 1
|
||||
trafficlight.Cr1.North.green=30.0
|
||||
trafficlight.Cr1.North.red=30.0
|
||||
trafficlight.Cr1.South.green=30.0
|
||||
trafficlight.Cr1.South.red=30.0
|
||||
trafficlight.Cr1.East.green=30.0
|
||||
trafficlight.Cr1.East.red=30.0
|
||||
trafficlight.Cr1.West.green=30.0
|
||||
trafficlight.Cr1.West.red=30.0
|
||||
# Intersection 1 (Entry point - balanced)
|
||||
trafficlight.Cr1.North.green=20.0
|
||||
trafficlight.Cr1.North.red=40.0
|
||||
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
|
||||
|
||||
# Intersection 2
|
||||
trafficlight.Cr2.North.green=25.0
|
||||
trafficlight.Cr2.North.red=35.0
|
||||
trafficlight.Cr2.South.green=25.0
|
||||
trafficlight.Cr2.South.red=35.0
|
||||
trafficlight.Cr2.East.green=35.0
|
||||
trafficlight.Cr2.East.red=25.0
|
||||
trafficlight.Cr2.West.green=35.0
|
||||
trafficlight.Cr2.West.red=25.0
|
||||
# Intersection 2 (Main hub - shorter cycles, favor East-West)
|
||||
trafficlight.Cr2.North.green=12.0
|
||||
trafficlight.Cr2.North.red=36.0
|
||||
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
|
||||
|
||||
# Intersection 3
|
||||
trafficlight.Cr3.North.green=30.0
|
||||
# Intersection 3 (Path to exit - favor East)
|
||||
trafficlight.Cr3.North.green=15.0
|
||||
trafficlight.Cr3.North.red=30.0
|
||||
trafficlight.Cr3.South.green=30.0
|
||||
trafficlight.Cr3.South.green=15.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.East.green=20.0
|
||||
trafficlight.Cr3.East.red=25.0
|
||||
trafficlight.Cr3.West.green=15.0
|
||||
trafficlight.Cr3.West.red=30.0
|
||||
|
||||
# Intersection 4
|
||||
trafficlight.Cr4.North.green=30.0
|
||||
# Intersection 4 (Favor East toward Cr5)
|
||||
trafficlight.Cr4.North.green=15.0
|
||||
trafficlight.Cr4.North.red=30.0
|
||||
trafficlight.Cr4.South.green=30.0
|
||||
trafficlight.Cr4.South.green=15.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.East.green=20.0
|
||||
trafficlight.Cr4.East.red=25.0
|
||||
trafficlight.Cr4.West.green=15.0
|
||||
trafficlight.Cr4.West.red=30.0
|
||||
|
||||
# Intersection 5
|
||||
trafficlight.Cr5.North.green=30.0
|
||||
# Intersection 5 (Near exit - favor East)
|
||||
trafficlight.Cr5.North.green=15.0
|
||||
trafficlight.Cr5.North.red=30.0
|
||||
trafficlight.Cr5.South.green=30.0
|
||||
trafficlight.Cr5.South.green=15.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.East.green=22.0
|
||||
trafficlight.Cr5.East.red=23.0
|
||||
trafficlight.Cr5.West.green=15.0
|
||||
trafficlight.Cr5.West.red=30.0
|
||||
|
||||
# === VEHICLE CONFIGURATION ===
|
||||
@@ -103,11 +103,19 @@ vehicle.probability.light=0.6
|
||||
vehicle.probability.heavy=0.2
|
||||
|
||||
# Average crossing times (in seconds)
|
||||
vehicle.crossing.time.bike=1.5
|
||||
vehicle.crossing.time.bike=1.0
|
||||
vehicle.crossing.time.light=2.0
|
||||
vehicle.crossing.time.heavy=4.0
|
||||
|
||||
# Travel times between intersections (in seconds)
|
||||
# Base time for light vehicles (cars)
|
||||
vehicle.travel.time.base=8.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
|
||||
|
||||
# === STATISTICS ===
|
||||
|
||||
# Interval between dashboard updates (seconds)
|
||||
statistics.update.interval=10.0
|
||||
statistics.update.interval=1.0
|
||||
|
||||
60
main/start.sh
Executable file
60
main/start.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# Distributed Traffic Simulation Startup Script
|
||||
|
||||
# kill java
|
||||
echo "-> Cleaning up existing processes..."
|
||||
pkill -9 java 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# build
|
||||
echo "-> Building project..."
|
||||
cd "$(dirname "$0")"
|
||||
mvn package -DskipTests -q
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "XXX Build failed! XXX"
|
||||
exit 1
|
||||
fi
|
||||
echo "-> Build complete"
|
||||
echo ""
|
||||
|
||||
# start gui
|
||||
echo "-> Starting JavaFX Dashboard..."
|
||||
mvn javafx:run &
|
||||
DASHBOARD_PID=$!
|
||||
sleep 3
|
||||
|
||||
# acho que é assim idk
|
||||
echo "-> Starting 5 Intersection processes..."
|
||||
for id in Cr1 Cr2 Cr3 Cr4 Cr5; do
|
||||
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.IntersectionProcess $id > /tmp/$(echo $id | tr '[:upper:]' '[:lower:]').log 2>&1 &
|
||||
echo "[SUCCESS] Started $id"
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# exit
|
||||
echo "-> Starting Exit Node..."
|
||||
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.ExitNodeProcess > /tmp/exit.log 2>&1 &
|
||||
sleep 1
|
||||
|
||||
# coordinator
|
||||
echo "-> Starting Coordinator..."
|
||||
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.coordinator.CoordinatorProcess > /tmp/coordinator.log 2>&1 &
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
echo "-> All processes started!"
|
||||
echo ""
|
||||
echo "-> System Status:"
|
||||
ps aux | grep "java.*sd\." | grep -v grep | wc -l | xargs -I {} echo " {} Java processes running"
|
||||
echo ""
|
||||
echo " IMPORTANT: Keep the JavaFX Dashboard window OPEN for 60+ seconds"
|
||||
echo " to see live updates! The simulation runs for 60 seconds."
|
||||
echo ""
|
||||
echo "-> Logs available at:"
|
||||
echo " Dashboard: Check JavaFX window (live updates)"
|
||||
echo " Intersections: /tmp/cr*.log"
|
||||
echo " Exit Node: /tmp/exit.log"
|
||||
echo " Coordinator: /tmp/coordinator.log"
|
||||
echo ""
|
||||
echo "-> To stop all processes: pkill -9 java"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user