Files
upo-valpre/src/main/java/net/berack/upo/valpre/sim/Simulation.java

215 lines
6.6 KiB
Java

package net.berack.upo.valpre.sim;
import java.util.List;
import java.util.PriorityQueue;
import net.berack.upo.valpre.rand.Rng;
import net.berack.upo.valpre.sim.stats.Result;
/**
* Process an entire run of the simulation.
*/
public final class Simulation {
public final Rng rng;
public final long timeStartedNano;
public final EndCriteria[] criterias;
public final long seed;
private final ServerNodeState[] states;
private final PriorityQueue<Event> fel;
private double time = 0.0d;
private long eventProcessed = 0;
/**
* Creates a new simulation for the given network.
* The random number generator is used to generate random numbers for the
* simulation.
* The simulation will end when the given criteria are met.
* NOTE: the network passed is only used to create the initial states of the
* nodes, so the simulation is not affected by changes to the network after
* the creation of this object.
*
* @param net The network to simulate.
* @param rng The random number generator to use.
* @param criterias when the simulation has to end.
*/
public Simulation(Net net, Rng rng, EndCriteria... criterias) {
this.timeStartedNano = System.nanoTime();
this.states = net.buildNodeStates();
this.fel = new PriorityQueue<>();
this.criterias = criterias;
this.seed = rng.getSeed();
this.rng = rng;
boolean hasLimit = false;
for (var state : this.states) {
var node = state.node;
// check for ending criteria in simulation
if (node.spawnArrivals != Integer.MAX_VALUE)
hasLimit = true;
// Initial arrivals (if spawned)
this.addToFel(state.spawnArrivalIfPossilbe(0.0d));
}
if (!hasLimit && (criterias == null || criterias.length == 0))
throw new IllegalArgumentException("At least one end criteria is needed!");
}
/**
* Runs the simulation until a given criteria is met.
*
* @return The final statistics the network.
*/
public Result run() {
while (!this.hasEnded())
this.processNextEvent();
return this.endSimulation();
}
/**
* Processes the next event in the future event list.
* This method will throw NullPointerException if there are no more events.
* You should check if the simulation has ended before calling this method.
*
* @see #hasEnded()
* @throws NullPointerException if there are no more events to process.
*/
public void processNextEvent() {
var event = fel.poll();
if (event == null)
throw new NullPointerException("No more events to process!");
var state = this.states[event.nodeIndex];
this.time = event.time;
this.eventProcessed += 1;
switch (event.type) {
case AVAILABLE -> {
state.updateAvailable(time);
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
}
case ARRIVAL -> {
state.updateArrival(time);
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
}
case DEPARTURE -> {
state.updateDeparture(time);
// Spawn unavailability if has unavailable time
this.addToFel(state.spawnUnavailableIfPossible(time, this.rng));
// Spawn departure if has requests and server is available
this.addToFel(state.spawnDepartureIfPossible(time, this.rng));
// Spawn arrival to self if is source node
this.addToFel(state.spawnArrivalIfPossilbe(time));
// Spawn arrival to child node if queue is not full otherwise drop
var ev = state.spawnArrivalToChild(time, this.rng);
if (ev != null && !this.states[ev.nodeIndex].isQueueFull())
this.addToFel(ev);
}
}
}
/**
* Ends the simulation and returns the statistics of the network.
*
* @return The statistics of the network.
*/
public Result endSimulation() {
var elapsed = System.nanoTime() - this.timeStartedNano;
var builder = new Result.Builder();
for (var i = 0; i < this.states.length; i++) {
var state = this.states[i];
builder.addNode(state.node.name, state.stats);
}
return builder.seed(this.seed).times(this.time, elapsed * 1e-6).build();
}
/**
* Get the current time.
*
* @return a double representing the current time of the simulation.
*/
public double getTime() {
return this.time;
}
/**
* Get the number of events processed.
*
* @return the number of events processed.
*/
public long getEventsProcessed() {
return this.eventProcessed;
}
/**
* Get the list of future events.
* This method returns a copy of the list, so the original list is not modified.
*
* @return a list of future events.
*/
public List<Event> getFutureEventList() {
return List.copyOf(this.fel);
}
/**
* Get the node requested by the name passed as a string.
*
* @param node the name of the node
* @return the node
* @throws NullPointerException if the node does not exist.
*/
public ServerNode getNode(String node) {
return this.getNodeState(node).node;
}
/**
* Get the node state requested by the name passed as a string.
*
* @param node the name of the node
* @return the current state of the node
* @throws NullPointerException if the node does not exist.
*/
public ServerNodeState getNodeState(String node) {
for (var state : this.states) {
if (state.node.name.equals(node))
return state;
}
throw new NullPointerException("Node not found: " + node);
}
/**
* Add an arrival event to the future event list if the event is not null,
* otherwise do nothing.
*
* @param e the event to add
*/
public void addToFel(Event e) {
if (e != null)
this.fel.add(e);
}
/**
* Determines if the simulation has finshed based on the given criteria.
*
* @return True if the simulation should end, false otherwise.
*/
public boolean hasEnded() {
if (fel.isEmpty()) {
return true;
}
for (var c : this.criterias) {
if (c.shouldEnd(this)) {
return true;
}
}
return false;
}
}