@@ -28,49 +28,51 @@ import sd.util.VehicleGenerator;
* - The {@link StatisticsCollector} for tracking metrics.
*/
public class SimulationEngine {
/**
* Holds all simulation parameters loaded from the properties file.
*/
private final SimulationConfig config ;
/**
* The core of the discrete event simulation. Events are pulled from this
* queue in order of their timestamp.
*/
private final PriorityQueue < Event > eventQueue ;
/**
* A map storing all intersections in the simulation, keyed by their ID (e.g., "Cr1").
* 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 ;
/**
* Collects and calculates statistics throughout the simulation.
*/
private final StatisticsCollector statisticsCollector ;
/**
* The current time in the simulation (in virtual seconds).
* This time advances based on the timestamp of the event being processed.
*/
private double currentTime ;
/**
* A simple counter to generate unique IDs for vehicles.
*/
private int vehicleCounter ;
/**
* Constructs a new SimulationEngine.
*
* @param config The {@link SimulationConfig} object containing all
* simulation parameters.
* simulation parameters.
*/
public SimulationEngine ( SimulationConfig config ) {
this . config = config ;
@@ -81,7 +83,27 @@ public class SimulationEngine {
this . currentTime = 0 . 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:
* 1. Creating all {@link Intersection} and {@link TrafficLight} objects.
@@ -91,53 +113,53 @@ public class SimulationEngine {
*/
public void initialize ( ) {
System . out . println ( " Initializing simulation... " ) ;
setupIntersections ( ) ;
setupRouting ( ) ;
// Schedule initial events to "bootstrap" the simulation
scheduleTrafficLightEvents ( ) ;
scheduleNextVehicleGeneration ( 0 . 0 ) ;
scheduleStatisticsUpdates ( ) ;
System . out . println ( " Simulation initialized with " + intersections . size ( ) + " intersections " ) ;
}
/**
* Creates all intersections defined in the configuration
* and adds their corresponding traffic lights.
*/
private void setupIntersections ( ) {
String [ ] intersectionIds = { " Cr1 " , " Cr2 " , " Cr3 " , " Cr4 " , " Cr5 " } ;
String [ ] intersectionIds = { " Cr1 " , " Cr2 " , " Cr3 " , " Cr4 " , " Cr5 " } ;
// 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 ) {
Intersection intersection = new Intersection ( id ) ;
// Add traffic lights for each configured direction
for ( String direction : directions ) {
double greenTime = config . getTrafficLightGreenTime ( id , direction ) ;
double redTime = config . getTrafficLightRedTime ( id , direction ) ;
TrafficLight light = new TrafficLight (
id + " - " + direction ,
direction ,
greenTime ,
redTime
) ;
id + " - " + direction ,
direction ,
greenTime ,
redTime ) ;
intersection . addTrafficLight ( light ) ;
}
intersections . put ( id , intersection ) ;
}
}
/**
* Configures how vehicles should be routed between intersections.
* This hardcoded logic defines the "map" of the city.
* * For example, `intersections.get("Cr1").configureRoute("Cr2", "East");` means
* * 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."
*/
@@ -145,26 +167,26 @@ public class SimulationEngine {
// Cr1 routing
intersections . get ( " Cr1 " ) . configureRoute ( " Cr2 " , " East " ) ;
intersections . get ( " Cr1 " ) . configureRoute ( " Cr4 " , " South " ) ;
// Cr2 routing
intersections . get ( " Cr2 " ) . configureRoute ( " Cr1 " , " West " ) ;
intersections . get ( " Cr2 " ) . configureRoute ( " Cr3 " , " East " ) ;
intersections . get ( " Cr2 " ) . configureRoute ( " Cr5 " , " South " ) ;
// Cr3 routing
intersections . get ( " Cr3 " ) . configureRoute ( " Cr2 " , " West " ) ;
intersections . get ( " Cr3 " ) . configureRoute ( " S " , " South " ) ; // "S" is the exit
// Cr4 routing
//intersections.get("Cr4").configureRoute("Cr1", "North");
// intersections.get("Cr4").configureRoute("Cr1", "North");
intersections . get ( " Cr4 " ) . configureRoute ( " Cr5 " , " East " ) ;
// Cr5 routing
//intersections.get("Cr5").configureRoute("Cr2", "North");
//intersections.get("Cr5").configureRoute("Cr4", "West");
// intersections.get("Cr5").configureRoute("Cr2", "North");
// intersections.get("Cr5").configureRoute("Cr4", "West");
intersections . get ( " Cr5 " ) . configureRoute ( " S " , " East " ) ; // "S" is the exit
}
/**
* Schedules the initial {@link EventType#TRAFFIC_LIGHT_CHANGE} event
* for every traffic light in the simulation.
@@ -181,38 +203,40 @@ public class SimulationEngine {
}
}
}
/**
* Creates and schedules a new {@link EventType#TRAFFIC_LIGHT_CHANGE} event.
* The event is scheduled to occur at {@code currentTime + delay}.
*
* @param light The {@link TrafficLight} that will change state.
* @param 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 ;
Event event = new Event ( changeTime , EventType . TRAFFIC_LIGHT_CHANGE , light , intersectionId ) ;
eventQueue . offer ( event ) ;
}
/**
* Schedules the next {@link EventType#VEHICLE_GENERATION} event.
* The time of the next arrival is determined by the {@link VehicleGenerator}.
*
* @param baseTime The time from which to calculate the next arrival (usually {@code currentTime}).
* @param baseTime The time from which to calculate the next arrival (usually
* {@code currentTime}).
*/
private void scheduleNextVehicleGeneration ( double baseTime ) {
// Get the absolute time for the next arrival.
double nextArrivalTime = vehicleGenerator . getNextArrivalTime ( baseTime ) ;
// Only schedule the event if it's within the simulation's total duration.
if ( nextArrivalTime < config . getSimulationDuration ( ) ) {
Event event = new Event ( nextArrivalTime , EventType . VEHICLE_GENERATION , null , null ) ;
eventQueue . offer ( event ) ;
}
}
/**
* Schedules all periodic {@link EventType#STATISTICS_UPDATE} events
* for the entire duration of the simulation.
@@ -220,13 +244,13 @@ public class SimulationEngine {
private void scheduleStatisticsUpdates ( ) {
double interval = config . getStatisticsUpdateInterval ( ) ;
double duration = config . getSimulationDuration ( ) ;
for ( double time = interval ; time < duration ; time + = interval ) {
Event event = new Event ( time , EventType . STATISTICS_UPDATE , null , null ) ;
eventQueue . offer ( event ) ;
}
}
/**
* Runs the main simulation loop.
* The loop continues as long as there are events in the queue and
@@ -240,46 +264,47 @@ public class SimulationEngine {
public void run ( ) {
System . out . println ( " Starting simulation... " ) ;
double duration = config . getSimulationDuration ( ) ;
while ( ! eventQueue . isEmpty ( ) & & currentTime < duration ) {
// Get the next event in chronological order
Event event = eventQueue . poll ( ) ;
// Advance simulation time to this event's time
currentTime = event . getTimestamp ( ) ;
// Process the event
processEvent ( event ) ;
}
System . out . println ( " \ nSimulation completed at t= " + String . format ( " %.2f " , currentTime ) + " s " ) ;
printFinalStatistics ( ) ;
}
/**
* Main event processing logic.
* Delegates the event to the appropriate handler method based on its {@link EventType}.
* Delegates the event to the appropriate handler method based on its
* {@link EventType}.
*
* @param event The {@link Event} to be processed.
*/
private void processEvent ( Event event ) {
switch ( event . getType ( ) ) {
case VEHICLE_GENERATION - > handleVehicleGeneration ( ) ;
case VEHICLE_ARRIVAL - > handleVehicleArrival ( event ) ;
case TRAFFIC_LIGHT_CHANGE - > handleTrafficLightChange ( event ) ;
case CROSSING_START - > handleCrossingStart ( event ) ;
case CROSSING_END - > handleCrossingEnd ( event ) ;
case STATISTICS_UPDATE - > handleStatisticsUpdate ( ) ;
default - > System . err . println ( " Unknown event type: " + event . getType ( ) ) ;
}
}
/**
* Handles {@link EventType#VEHICLE_GENERATION}.
* 1. Creates a new {@link Vehicle} using the {@link #vehicleGenerator}.
@@ -288,32 +313,33 @@ 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 ) ;
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
statisticsCollector . recordVehicleGeneration ( vehicle , currentTime ) ;
// Schedule arrival at first intersection
String firstIntersection = vehicle . getCurrentDestination ( ) ;
if ( firstIntersection ! = null & & ! firstIntersection . equals ( " S " ) ) {
// Assume minimal travel t ime to first intersection (e.g., 1-3 seconds)
double arrivalTime = currentTime + 1 . 0 + Math . random ( ) * 2 . 0 ;
double travelT ime = calculateTravelTime ( vehicle . getType ( ) ) ;
double arrivalTime = currentTime + travelTime ;
Event arrivalEvent = new Event ( arrivalTime , EventType . VEHICLE_ARRIVAL , vehicle , firstIntersection ) ;
eventQueue . offer ( arrivalEvent ) ;
}
// Schedule next vehicle generation
// This was commented out in the original file.
// For a continuous simulation, it should be enabled:
scheduleNextVehicleGeneration ( currentTime ) ;
}
/**
* Handles {@link EventType#VEHICLE_ARRIVAL} at an intersection.
* 1. Records the arrival for statistics.
@@ -324,65 +350,67 @@ 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 ( ) ;
String intersectionId = event . getLocation ( ) ;
Intersection intersection = intersections . get ( intersectionId ) ;
if ( intersection = = null ) {
System . err . println ( " Unknown intersection: " + intersectionId ) ;
return ;
}
System . out . printf ( " [t=%.2f] Vehicle %s arrived at %s%n " ,
currentTime , vehicle . getId ( ) , intersectionId ) ;
currentTime , vehicle . getId ( ) , intersectionId ) ;
// Record arrival time (used to calculate waiting time later)
statisticsCollector . recordVehicleArrival ( vehicle , intersectionId , currentTime ) ;
// Advance the vehicle's route to the *next* stop
// (it has now arrived at its *current* destination)
boolean hasNext = vehicle . advanceRoute ( ) ;
if ( ! hasNext ) {
// This was the last stop
handleVehicleExit ( vehicle ) ;
return ;
}
String nextDestination = vehicle . getCurrentDestination ( ) ;
if ( nextDestination = = null | | " S " . equals ( nextDestination ) ) {
// Next stop is the exit
handleVehicleExit ( vehicle ) ;
return ;
}
// Add vehicle to the appropriate traffic light queue based on its next destination
// Add vehicle to the appropriate traffic light queue based on its next
// destination
intersection . receiveVehicle ( vehicle ) ;
// Try to process the vehicle immediately if its light is already green
tryProcessVehicle ( vehicle , intersection ) ;
}
/**
* Checks if a newly arrived vehicle (or a vehicle in a queue
* that just turned green) can start crossing.
*
* @param vehicle The vehicle to process.
* @param vehicle The vehicle to process.
* @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
// This logic is a bit flawed: it just finds the *first* non-empty queue
// A better approach would be to get the light from the vehicle's route
String direction = intersection . getTrafficLights ( ) . stream ( )
. filter ( tl - > tl . getQueueSize ( ) > 0 )
. map ( TrafficLight : : getDirection )
. findFirst ( )
. orElse ( null ) ;
. filter ( tl - > tl . getQueueSize ( ) > 0 )
. map ( TrafficLight : : getDirection )
. findFirst ( )
. orElse ( null ) ;
if ( direction ! = null ) {
TrafficLight light = intersection . getTrafficLight ( direction ) ;
// If the light is green and it's the correct one...
@@ -396,26 +424,26 @@ public class SimulationEngine {
}
}
}
/**
* Schedules the crossing for a vehicle that has just been dequeued
* from a green light.
* 1. Calculates and records the vehicle's waiting time.
* 2. Schedules an immediate {@link EventType#CROSSING_START} event.
*
* @param vehicle The {@link Vehicle} that is crossing.
* @param vehicle The {@link Vehicle} that is crossing.
* @param intersection The {@link Intersection} it is crossing.
*/
private void scheduleCrossing ( Vehicle vehicle , Intersection intersection ) {
// Calculate time spent waiting at the red light
double waitTime = currentTime - statisticsCollector . getArrivalTime ( vehicle ) ;
vehicle . addWaitingTime ( waitTime ) ;
// Schedule crossing start event *now*
Event crossingStart = new Event ( currentTime , EventType . CROSSING_START , vehicle , intersection . getId ( ) ) ;
processEvent ( crossingStart ) ; // Process immediately
}
/**
* Handles {@link EventType#CROSSING_START}.
* 1. Determines the crossing time based on vehicle type.
@@ -427,23 +455,24 @@ public class SimulationEngine {
private void handleCrossingStart ( Event event ) {
Vehicle vehicle = ( Vehicle ) event . getData ( ) ;
String intersectionId = event . getLocation ( ) ;
double crossingTime = getCrossingTime ( vehicle . getType ( ) ) ;
System . out . printf ( " [t=%.2f] Vehicle %s started crossing at %s (duration=%.2fs)%n " ,
currentTime , vehicle . getId ( ) , intersectionId , crossingTime ) ;
currentTime , vehicle . getId ( ) , intersectionId , crossingTime ) ;
// Schedule the *end* of the crossing
double endTime = currentTime + crossingTime ;
Event crossingEnd = new Event ( endTime , EventType . CROSSING_END , vehicle , intersectionId ) ;
eventQueue . offer ( crossingEnd ) ;
}
/**
* Handles {@link EventType#CROSSING_END}.
* 1. Updates intersection and vehicle statistics.
* 2. Checks the vehicle's *next* destination.
* 3. If the next destination is the exit ("S"), call {@link #handleVehicleExit(Vehicle)}.
* 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.
*
@@ -452,25 +481,26 @@ public class SimulationEngine {
private void handleCrossingEnd ( Event event ) {
Vehicle vehicle = ( Vehicle ) event . getData ( ) ;
String intersectionId = event . getLocation ( ) ;
// Update stats
Intersection intersection = intersections . get ( intersectionId ) ;
if ( intersection ! = null ) {
intersection . incrementVehiclesSent ( ) ;
}
double crossingTime = getCrossingTime ( vehicle . getType ( ) ) ;
vehicle . addCrossingTime ( crossingTime ) ;
System . out . printf ( " [t=%.2f] Vehicle %s finished crossing at %s%n " ,
currentTime , vehicle . getId ( ) , intersectionId ) ;
currentTime , vehicle . getId ( ) , intersectionId ) ;
// Decide what to do next
String nextDest = vehicle . getCurrentDestination ( ) ;
if ( nextDest ! = null & & ! nextDest . equals ( " S " ) ) {
// Route to the *next* intersection
// Assume 5-10 seconds travel time between intersections
double travelTime = 5 . 0 + Math . random ( ) * 5 . 0 ;
// 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 ) ;
@@ -479,7 +509,7 @@ public class SimulationEngine {
handleVehicleExit ( vehicle ) ;
}
}
/**
* Handles a vehicle exiting the simulation.
* Records final statistics for the vehicle.
@@ -488,18 +518,19 @@ public class SimulationEngine {
*/
private void handleVehicleExit ( Vehicle vehicle ) {
System . out . printf ( " [t=%.2f] Vehicle %s exited the system (wait=%.2fs, travel=%.2fs)%n " ,
currentTime , vehicle . getId ( ) ,
vehicle . getTotalWaitingTime ( ) ,
vehicle . getTotalTravelTime ( currentTime ) ) ;
currentTime , vehicle . getId ( ) ,
vehicle . getTotalWaitingTime ( ) ,
vehicle . getTotalTravelTime ( currentTime ) ) ;
// Record the exit for final statistics calculation
statisticsCollector . recordVehicleExit ( vehicle , currentTime ) ;
}
/**
* Handles {@link EventType#TRAFFIC_LIGHT_CHANGE}.
* 1. Toggles the light's state (RED to GREEN or GREEN to RED).
* 2. If the light just turned GREEN, call {@link #processGreenLight(TrafficLight, Intersection)}
* 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.
@@ -509,17 +540,17 @@ public class SimulationEngine {
private void handleTrafficLightChange ( Event event ) {
TrafficLight light = ( TrafficLight ) event . getData ( ) ;
String intersectionId = event . getLocation ( ) ;
// Toggle state
TrafficLightState newState = ( light . getState ( ) = = TrafficLightState . RED )
? TrafficLightState . GREEN
: TrafficLightState . RED ;
TrafficLightState newState = ( light . getState ( ) = = TrafficLightState . RED )
? TrafficLightState . GREEN
: TrafficLightState . RED ;
light . changeState ( newState ) ;
System . out . printf ( " [t=%.2f] Traffic light %s changed to %s%n " ,
currentTime , light . getId ( ) , newState ) ;
currentTime , light . getId ( ) , newState ) ;
// If changed to GREEN, process waiting vehicles
if ( newState = = TrafficLightState . GREEN ) {
Intersection intersection = intersections . get ( intersectionId ) ;
@@ -527,15 +558,15 @@ public class SimulationEngine {
processGreenLight ( light , intersection ) ;
}
}
// Schedule the *next* state change for this same light
double nextChangeDelay = ( newState = = TrafficLightState . GREEN )
? light . getGreenTime ( )
: light . getRedTime ( ) ;
double nextChangeDelay = ( newState = = TrafficLightState . GREEN )
? light . getGreenTime ( )
: light . getRedTime ( ) ;
scheduleTrafficLightChange ( light , intersectionId , nextChangeDelay ) ;
}
/**
* Processes vehicles when a light turns green.
* It loops as long as the light is green and there are vehicles in the queue,
@@ -546,7 +577,7 @@ public class SimulationEngine {
* processes the entire queue "instantaneously" at the moment
* 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.
*/
private void processGreenLight ( TrafficLight light , Intersection intersection ) {
@@ -559,7 +590,7 @@ public class SimulationEngine {
}
}
}
/**
* Handles {@link EventType#STATISTICS_UPDATE}.
* Calls the {@link StatisticsCollector} to print the current
@@ -570,9 +601,10 @@ public class SimulationEngine {
statisticsCollector . printCurrentStatistics ( intersections , currentTime ) ;
System . out . println ( ) ;
}
/**
* 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.
@@ -585,7 +617,7 @@ public class SimulationEngine {
default - > 2 . 0 ;
} ; // Default fallback
}
/**
* Prints the final summary of statistics at the end of the simulation.
*/
@@ -593,33 +625,36 @@ public class SimulationEngine {
System . out . println ( " \ n " + " = " . repeat ( 60 ) ) ;
System . out . println ( " FINAL SIMULATION STATISTICS " ) ;
System . out . println ( " = " . repeat ( 60 ) ) ;
statisticsCollector . printFinalStatistics ( intersections , currentTime ) ;
System . out . println ( " = " . repeat ( 60 ) ) ;
}
// --- Public Getters ---
/**
* Gets the current simulation time.
*
* @return The time in virtual seconds.
*/
public double getCurrentTime ( ) {
return currentTime ;
}
/**
* Gets a map of all intersections in the simulation.
* Returns a copy to prevent external modification.
*
* @return A {@link Map} of intersection IDs to {@link Intersection} objects.
*/
public Map < String , Intersection > getIntersections ( ) {
return new HashMap < > ( intersections ) ;
}
/**
* Gets the statistics collector instance.
*
* @return The {@link StatisticsCollector}.
*/
public StatisticsCollector getStatisticsCollector ( ) {