Environments

A simulation environment manages the simulation time as well as the scheduling and processing of events. It also provides means to step through or execute the simulation.

The abstract type for all environments is AbstractEnvironment. “Normal” simulations usually use its subtype Environment.

Simulation control

SimJulia is very flexible in terms of simulation execution. You can run your simulation until there are no more events, until a certain simulation time is reached, or until a certain event is triggered. You can also step through the simulation event by event. Furthermore, you can mix these things as you like. For example, you could run your simulation until an interesting event occurs. You could then step through the simulation event by event for a while; and finally run the simulation until there are no more events left and your processes have all terminated.

The most important method in this section is run():

  • If you call it with only one argument run(env::Environment), it steps through the simulation until there are no more events left.

Warning

If your process function runs forever, e.g.

while true
  yield(Timeout(env, 1.0))
end

run(env) will never terminate unless you kill your script by pressing Ctrl-C.

  • In most cases it is advisable to stop your simulation when it reaches a certain simulation time. Therefore, you can pass the desired time via a second argument: run(env, 10.0). The simulation will then stop when the internal clock reaches 10.0 but will not process any events scheduled for time 10.0. This is similar to a new environment where the clock is 0.0 but (obviously) no events have yet been processed.

  • Instead of passing a floating point value as second argument, you can also pass any instance of a AbstractEvent to it. The function returns when this event has been processed. Assuming that the current time is 0.0, run(env, Timeout(env, 5.0)) is equivalent to run(env, 5.0). You can also pass other types of events (remember, that Process is a subtype of AbstractEvent).

    using SimJulia
    
    function my_proc(env::Environment)
      yield(Timeout(env, 1.0))
      return "Monty Python's Flying Circus"
    end
    
    env = Environment()
    proc = Process(env, my_proc)
    println(run(env, proc))
    

To step through the simulation event by event, the environment offers peek(env::Environment) and step(env::Environment):

  • peek(env::Environment) returns the time of the next scheduled event or Inf when no more events are scheduled.
  • step(env::Environment) processes the next scheduled event. It raises an EmptySchedule exception if no event is available.

In a typical use case, you use these methods in a loop like:

until = 10.0
while peek(env) < until
  step(env)
end

State access

The environment allows you to get the current simulation time via the function now(env::Environment). The simulation time is a floating point value without unit and is increased via timeout events.

By default, the constructor Environment() starts the simulation time at 0.0, but you can pass an initial value, Environment(initial_value::Float64) to use something else.

The function active_process(env::Environment) is comparable to Base.getpid() and returns the currently active Process. A process is active when its process function is being executed. It becomes inactive (or suspended) when it yields an event.

Thus, it only makes sense to call this function from within a process function or a function that is called by your process function, otherwise, a NullException is thrown:

using SimJulia

function subfunc(env::Environment)
  println("Active process: $(active_process(env))")
end

function my_proc(env::Environment)
  println("Active process: $(active_process(env))")
  yield(Timeout(env, 1.0))
  subfunc(env)
end

env = Environment()
Process(env, my_proc)
println("Time: $(peek(env))")
try
  println(active_process(env))
catch exc
  println("No active process")
end
step(env)
println("Time: $(peek(env))")
try
  println(active_process(env))
catch exc
  println("No active process")
end
step(env)
println("Time: $(peek(env))")
step(env)
println("Time: $(peek(env))")

A nice example of this function can be found in the resource system. When a process function calls the constructor Request(res::Resource) to generate a request event for a resource, the resource determines the requesting process via active_process(env).

Event creation

To create events, you normally have to use a constructor Event(env::AbstractEnvironment) to instantiate the Event type and pass a reference to the environment to it.

More details on what events do can be found in the next sections.

Miscellaneous

A process function can have a return value:

using SimJulia

function my_proc(env::Environment)
  yield(Timeout(env, 1.0))
  return 42
end

function other_proc(env::Environment)
  ret_val = yield(Process(env, my_proc))
  @assert(ret_val == 42)
end

env = Environment()
Process(env, other_proc)
run(env)

The simulation can be stopped by throwing a StopSimulation exception in a process function. To keep your code more readable, the function stop_simulation(env::AbstractEnvironment) does exactly this.