@@ -41,12 +41,14 @@ public class SimulationEngine {
private final PriorityQueue < Event > eventQueue ;
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 ;
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 ;
private final VehicleGenerator vehicleGenerator ;
@@ -70,7 +72,7 @@ public class SimulationEngine {
* Constructs a new SimulationEngine.
* Constructs a new SimulationEngine.
*
*
* @param config The {@link SimulationConfig} object containing all
* @param config The {@link SimulationConfig} object containing all
* simulation parameters.
* simulation parameters.
*/
*/
public SimulationEngine ( SimulationConfig config ) {
public SimulationEngine ( SimulationConfig config ) {
this . config = config ;
this . config = config ;
@@ -82,6 +84,26 @@ public class SimulationEngine {
this . vehicleCounter = 0 ;
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:
* Initializes the simulation. This involves:
* 1. Creating all {@link Intersection} and {@link TrafficLight} objects.
* 1. Creating all {@link Intersection} and {@link TrafficLight} objects.
@@ -108,9 +130,9 @@ public class SimulationEngine {
* and adds their corresponding traffic lights.
* and adds their corresponding traffic lights.
*/
*/
private void setupIntersections ( ) {
private void setupIntersections ( ) {
String [ ] intersectionIds = { " Cr1 " , " Cr2 " , " Cr3 " , " Cr4 " , " Cr5 " } ;
String [ ] intersectionIds = { " Cr1 " , " Cr2 " , " Cr3 " , " Cr4 " , " Cr5 " } ;
// Note: "North" is commented out, so it won't be created.
// Note: "North" is commented out, so it won't be created.
String [ ] directions = { /*"North",*/ " South " , " East " , " West " } ;
String [ ] directions = { /* "North", */ " South " , " East " , " West " } ;
for ( String id : intersectionIds ) {
for ( String id : intersectionIds ) {
Intersection intersection = new Intersection ( id ) ;
Intersection intersection = new Intersection ( id ) ;
@@ -121,11 +143,10 @@ public class SimulationEngine {
double redTime = config . getTrafficLightRedTime ( id , direction ) ;
double redTime = config . getTrafficLightRedTime ( id , direction ) ;
TrafficLight light = new TrafficLight (
TrafficLight light = new TrafficLight (
id + " - " + direction ,
id + " - " + direction ,
direction ,
direction ,
greenTime ,
greenTime ,
redTime
redTime ) ;
) ;
intersection . addTrafficLight ( light ) ;
intersection . addTrafficLight ( light ) ;
}
}
@@ -137,7 +158,8 @@ public class SimulationEngine {
/**
/**
* Configures how vehicles should be routed between intersections.
* Configures how vehicles should be routed between intersections.
* This hardcoded logic defines the "map" of the city.
* 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
* "at intersection Cr1, any vehicle whose *next* destination is Cr2
* should be sent to the 'East' traffic light queue."
* should be sent to the 'East' traffic light queue."
*/
*/
@@ -156,12 +178,12 @@ public class SimulationEngine {
intersections . get ( " Cr3 " ) . configureRoute ( " S " , " South " ) ; // "S" is the exit
intersections . get ( " Cr3 " ) . configureRoute ( " S " , " South " ) ; // "S" is the exit
// Cr4 routing
// Cr4 routing
//intersections.get("Cr4").configureRoute("Cr1", "North");
// intersections.get("Cr4").configureRoute("Cr1", "North");
intersections . get ( " Cr4 " ) . configureRoute ( " Cr5 " , " East " ) ;
intersections . get ( " Cr4 " ) . configureRoute ( " Cr5 " , " East " ) ;
// Cr5 routing
// Cr5 routing
//intersections.get("Cr5").configureRoute("Cr2", "North");
// intersections.get("Cr5").configureRoute("Cr2", "North");
//intersections.get("Cr5").configureRoute("Cr4", "West");
// intersections.get("Cr5").configureRoute("Cr4", "West");
intersections . get ( " Cr5 " ) . configureRoute ( " S " , " East " ) ; // "S" is the exit
intersections . get ( " Cr5 " ) . configureRoute ( " S " , " East " ) ; // "S" is the exit
}
}
@@ -186,9 +208,10 @@ public class SimulationEngine {
* Creates and schedules a new {@link EventType#TRAFFIC_LIGHT_CHANGE} event.
* Creates and schedules a new {@link EventType#TRAFFIC_LIGHT_CHANGE} event.
* The event is scheduled to occur at {@code currentTime + delay}.
* The event is scheduled to occur at {@code currentTime + delay}.
*
*
* @param light The {@link TrafficLight} that will change state.
* @param light The {@link TrafficLight} that will change state.
* @param intersectionId The ID of the intersection where the light is located.
* @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 ) {
private void scheduleTrafficLightChange ( TrafficLight light , String intersectionId , double delay ) {
double changeTime = currentTime + delay ;
double changeTime = currentTime + delay ;
@@ -200,7 +223,8 @@ public class SimulationEngine {
* Schedules the next {@link EventType#VEHICLE_GENERATION} event.
* Schedules the next {@link EventType#VEHICLE_GENERATION} event.
* The time of the next arrival is determined by the {@link VehicleGenerator}.
* 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 ) {
private void scheduleNextVehicleGeneration ( double baseTime ) {
// Get the absolute time for the next arrival.
// Get the absolute time for the next arrival.
@@ -258,7 +282,8 @@ public class SimulationEngine {
/**
/**
* Main event processing logic.
* 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.
* @param event The {@link Event} to be processed.
*/
*/
@@ -288,13 +313,14 @@ public class SimulationEngine {
* at its first destination intersection.
* at its first destination intersection.
* 4. Schedules the *next* {@link EventType#VEHICLE_GENERATION} event.
* 4. Schedules the *next* {@link EventType#VEHICLE_GENERATION} event.
* (Note: This line is commented out in the original, which might be a bug,
* (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 ( ) {
private void handleVehicleGeneration ( ) {
Vehicle vehicle = vehicleGenerator . generateVehicle ( " V " + ( + + vehicleCounter ) , currentTime ) ;
Vehicle vehicle = vehicleGenerator . generateVehicle ( " V " + ( + + vehicleCounter ) , currentTime ) ;
System . out . printf ( " [t=%.2f] Vehicle %s generated (type=%s, route=%s)%n " ,
System . out . printf ( " [t=%.2f] Vehicle %s generated (type=%s, route=%s)%n " ,
currentTime , vehicle . getId ( ) , vehicle . getType ( ) , vehicle . getRoute ( ) ) ;
currentTime , vehicle . getId ( ) , vehicle . getType ( ) , vehicle . getRoute ( ) ) ;
// Register with statistics collector
// Register with statistics collector
statisticsCollector . recordVehicleGeneration ( vehicle , currentTime ) ;
statisticsCollector . recordVehicleGeneration ( vehicle , currentTime ) ;
@@ -302,8 +328,8 @@ public class SimulationEngine {
// Schedule arrival at first intersection
// Schedule arrival at first intersection
String firstIntersection = vehicle . getCurrentDestination ( ) ;
String firstIntersection = vehicle . getCurrentDestination ( ) ;
if ( firstIntersection ! = null & & ! firstIntersection . equals ( " S " ) ) {
if ( firstIntersection ! = null & & ! firstIntersection . equals ( " S " ) ) {
// Assume minimal travel t ime to first intersection (e.g., 1-3 seconds)
double travelT ime = calculateTravelTime ( vehicle . getType ( ) ) ;
double arrivalTime = currentTime + 1 . 0 + Math . random ( ) * 2 . 0 ;
double arrivalTime = currentTime + travelTime ;
Event arrivalEvent = new Event ( arrivalTime , EventType . VEHICLE_ARRIVAL , vehicle , firstIntersection ) ;
Event arrivalEvent = new Event ( arrivalTime , EventType . VEHICLE_ARRIVAL , vehicle , firstIntersection ) ;
eventQueue . offer ( arrivalEvent ) ;
eventQueue . offer ( arrivalEvent ) ;
}
}
@@ -324,7 +350,8 @@ public class SimulationEngine {
* current intersection using {@link Intersection#receiveVehicle(Vehicle)}.
* current intersection using {@link Intersection#receiveVehicle(Vehicle)}.
* 5. Attempts to process the vehicle immediately if its light is green.
* 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 ) {
private void handleVehicleArrival ( Event event ) {
Vehicle vehicle = ( Vehicle ) event . getData ( ) ;
Vehicle vehicle = ( Vehicle ) event . getData ( ) ;
@@ -337,7 +364,7 @@ public class SimulationEngine {
}
}
System . out . printf ( " [t=%.2f] Vehicle %s arrived at %s%n " ,
System . out . printf ( " [t=%.2f] Vehicle %s arrived at %s%n " ,
currentTime , vehicle . getId ( ) , intersectionId ) ;
currentTime , vehicle . getId ( ) , intersectionId ) ;
// Record arrival time (used to calculate waiting time later)
// Record arrival time (used to calculate waiting time later)
statisticsCollector . recordVehicleArrival ( vehicle , intersectionId , currentTime ) ;
statisticsCollector . recordVehicleArrival ( vehicle , intersectionId , currentTime ) ;
@@ -359,7 +386,8 @@ public class SimulationEngine {
return ;
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 ) ;
intersection . receiveVehicle ( vehicle ) ;
// Try to process the vehicle immediately if its light is already green
// Try to process the vehicle immediately if its light is already green
@@ -370,18 +398,18 @@ public class SimulationEngine {
* Checks if a newly arrived vehicle (or a vehicle in a queue
* Checks if a newly arrived vehicle (or a vehicle in a queue
* that just turned green) can start crossing.
* that just turned green) can start crossing.
*
*
* @param vehicle The vehicle to process.
* @param vehicle The vehicle to process.
* @param intersection The intersection where the vehicle is.
* @param intersection The intersection where the vehicle is.
*/
*/
private void tryProcessVehicle ( Vehicle vehicle , Intersection intersection ) { //FIXME
private void tryProcessVehicle ( Vehicle vehicle , Intersection intersection ) { // FIXME
// Find the direction (and light) this vehicle is queued at
// Find the direction (and light) this vehicle is queued at
// This logic is a bit flawed: it just finds the *first* non-empty queue
// This logic is a bit flawed: it just finds the *first* non-empty queue
// A better approach would be to get the light from the vehicle's route
// A better approach would be to get the light from the vehicle's route
String direction = intersection . getTrafficLights ( ) . stream ( )
String direction = intersection . getTrafficLights ( ) . stream ( )
. filter ( tl - > tl . getQueueSize ( ) > 0 )
. filter ( tl - > tl . getQueueSize ( ) > 0 )
. map ( TrafficLight : : getDirection )
. map ( TrafficLight : : getDirection )
. findFirst ( )
. findFirst ( )
. orElse ( null ) ;
. orElse ( null ) ;
if ( direction ! = null ) {
if ( direction ! = null ) {
TrafficLight light = intersection . getTrafficLight ( direction ) ;
TrafficLight light = intersection . getTrafficLight ( direction ) ;
@@ -403,7 +431,7 @@ public class SimulationEngine {
* 1. Calculates and records the vehicle's waiting time.
* 1. Calculates and records the vehicle's waiting time.
* 2. Schedules an immediate {@link EventType#CROSSING_START} event.
* 2. Schedules an immediate {@link EventType#CROSSING_START} event.
*
*
* @param vehicle The {@link Vehicle} that is crossing.
* @param vehicle The {@link Vehicle} that is crossing.
* @param intersection The {@link Intersection} it is crossing.
* @param intersection The {@link Intersection} it is crossing.
*/
*/
private void scheduleCrossing ( Vehicle vehicle , Intersection intersection ) {
private void scheduleCrossing ( Vehicle vehicle , Intersection intersection ) {
@@ -431,7 +459,7 @@ public class SimulationEngine {
double crossingTime = getCrossingTime ( vehicle . getType ( ) ) ;
double crossingTime = getCrossingTime ( vehicle . getType ( ) ) ;
System . out . printf ( " [t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n " ,
System . out . printf ( " [t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n " ,
currentTime , vehicle . getId ( ) , intersectionId , crossingTime ) ;
currentTime , vehicle . getId ( ) , intersectionId , crossingTime ) ;
// Schedule the *end* of the crossing
// Schedule the *end* of the crossing
double endTime = currentTime + crossingTime ;
double endTime = currentTime + crossingTime ;
@@ -443,7 +471,8 @@ public class SimulationEngine {
* Handles {@link EventType#CROSSING_END}.
* Handles {@link EventType#CROSSING_END}.
* 1. Updates intersection and vehicle statistics.
* 1. Updates intersection and vehicle statistics.
* 2. Checks the vehicle's *next* destination.
* 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
* 4. Otherwise, schedule a {@link EventType#VEHICLE_ARRIVAL} event at the
* *next* intersection, after some travel time.
* *next* intersection, after some travel time.
*
*
@@ -463,14 +492,15 @@ public class SimulationEngine {
vehicle . addCrossingTime ( crossingTime ) ;
vehicle . addCrossingTime ( crossingTime ) ;
System . out . printf ( " [t=%.2f] Vehicle %s finished crossing at %s%n " ,
System . out . printf ( " [t=%.2f] Vehicle %s finished crossing at %s%n " ,
currentTime , vehicle . getId ( ) , intersectionId ) ;
currentTime , vehicle . getId ( ) , intersectionId ) ;
// Decide what to do next
// Decide what to do next
String nextDest = vehicle . getCurrentDestination ( ) ;
String nextDest = vehicle . getCurrentDestination ( ) ;
if ( nextDest ! = null & & ! nextDest . equals ( " S " ) ) {
if ( nextDest ! = null & & ! nextDest . equals ( " S " ) ) {
// Route to the *next* intersection
// Route to the *next* intersection
// Assume 5-10 seconds travel time between intersections
// Travel time varies by vehicle type: tmoto = 0.5 × tcarro, tcaminhão = 4 ×
double travelTime = 5 . 0 + Math . random ( ) * 5 . 0 ;
// tmoto
double travelTime = calculateTravelTime ( vehicle . getType ( ) ) ;
double arrivalTime = currentTime + travelTime ;
double arrivalTime = currentTime + travelTime ;
Event arrivalEvent = new Event ( arrivalTime , EventType . VEHICLE_ARRIVAL , vehicle , nextDest ) ;
Event arrivalEvent = new Event ( arrivalTime , EventType . VEHICLE_ARRIVAL , vehicle , nextDest ) ;
eventQueue . offer ( arrivalEvent ) ;
eventQueue . offer ( arrivalEvent ) ;
@@ -488,9 +518,9 @@ public class SimulationEngine {
*/
*/
private void handleVehicleExit ( Vehicle vehicle ) {
private void handleVehicleExit ( Vehicle vehicle ) {
System . out . printf ( " [t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n " ,
System . out . printf ( " [t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n " ,
currentTime , vehicle . getId ( ) ,
currentTime , vehicle . getId ( ) ,
vehicle . getTotalWaitingTime ( ) ,
vehicle . getTotalWaitingTime ( ) ,
vehicle . getTotalTravelTime ( currentTime ) ) ;
vehicle . getTotalTravelTime ( currentTime ) ) ;
// Record the exit for final statistics calculation
// Record the exit for final statistics calculation
statisticsCollector . recordVehicleExit ( vehicle , currentTime ) ;
statisticsCollector . recordVehicleExit ( vehicle , currentTime ) ;
@@ -499,7 +529,8 @@ public class SimulationEngine {
/**
/**
* Handles {@link EventType#TRAFFIC_LIGHT_CHANGE}.
* Handles {@link EventType#TRAFFIC_LIGHT_CHANGE}.
* 1. Toggles the light's state (RED to GREEN or GREEN to RED).
* 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.
* to process any waiting vehicles.
* 3. Schedules the *next* state change for this light based on its
* 3. Schedules the *next* state change for this light based on its
* green/red time duration.
* green/red time duration.
@@ -512,13 +543,13 @@ public class SimulationEngine {
// Toggle state
// Toggle state
TrafficLightState newState = ( light . getState ( ) = = TrafficLightState . RED )
TrafficLightState newState = ( light . getState ( ) = = TrafficLightState . RED )
? TrafficLightState . GREEN
? TrafficLightState . GREEN
: TrafficLightState . RED ;
: TrafficLightState . RED ;
light . changeState ( newState ) ;
light . changeState ( newState ) ;
System . out . printf ( " [t=%.2f] Traffic light %s changed to %s%n " ,
System . out . printf ( " [t=%.2f] Traffic light %s changed to %s%n " ,
currentTime , light . getId ( ) , newState ) ;
currentTime , light . getId ( ) , newState ) ;
// If changed to GREEN, process waiting vehicles
// If changed to GREEN, process waiting vehicles
if ( newState = = TrafficLightState . GREEN ) {
if ( newState = = TrafficLightState . GREEN ) {
@@ -530,8 +561,8 @@ public class SimulationEngine {
// Schedule the *next* state change for this same light
// Schedule the *next* state change for this same light
double nextChangeDelay = ( newState = = TrafficLightState . GREEN )
double nextChangeDelay = ( newState = = TrafficLightState . GREEN )
? light . getGreenTime ( )
? light . getGreenTime ( )
: light . getRedTime ( ) ;
: light . getRedTime ( ) ;
scheduleTrafficLightChange ( light , intersectionId , nextChangeDelay ) ;
scheduleTrafficLightChange ( light , intersectionId , nextChangeDelay ) ;
}
}
@@ -546,7 +577,7 @@ public class SimulationEngine {
* processes the entire queue "instantaneously" at the moment
* processes the entire queue "instantaneously" at the moment
* the light turns green.
* the light turns green.
*
*
* @param light The {@link TrafficLight} that just turned green.
* @param light The {@link TrafficLight} that just turned green.
* @param intersection The {@link Intersection} where the light is.
* @param intersection The {@link Intersection} where the light is.
*/
*/
private void processGreenLight ( TrafficLight light , Intersection intersection ) {
private void processGreenLight ( TrafficLight light , Intersection intersection ) {
@@ -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.
* @param type The type of vehicle.
* @return The crossing time in seconds.
* @return The crossing time in seconds.
@@ -603,6 +635,7 @@ public class SimulationEngine {
/**
/**
* Gets the current simulation time.
* Gets the current simulation time.
*
* @return The time in virtual seconds.
* @return The time in virtual seconds.
*/
*/
public double getCurrentTime ( ) {
public double getCurrentTime ( ) {
@@ -612,6 +645,7 @@ public class SimulationEngine {
/**
/**
* Gets a map of all intersections in the simulation.
* Gets a map of all intersections in the simulation.
* Returns a copy to prevent external modification.
* Returns a copy to prevent external modification.
*
* @return A {@link Map} of intersection IDs to {@link Intersection} objects.
* @return A {@link Map} of intersection IDs to {@link Intersection} objects.
*/
*/
public Map < String , Intersection > getIntersections ( ) {
public Map < String , Intersection > getIntersections ( ) {
@@ -620,6 +654,7 @@ public class SimulationEngine {
/**
/**
* Gets the statistics collector instance.
* Gets the statistics collector instance.
*
* @return The {@link StatisticsCollector}.
* @return The {@link StatisticsCollector}.
*/
*/
public StatisticsCollector getStatisticsCollector ( ) {
public StatisticsCollector getStatisticsCollector ( ) {