Car
A single car, a driver, and red traffic light in the middle of the night.
Red Light, Matthias Ripp (CC BY 2.0)
Let’s start with a very simple model. The example demonstrates the main mode of operation, the core API and the component process model implemented in kalasim
. We want to build a simulation where a single car is driving around for a some time before stopping in front of a red traffic light.
////Cars.kts
import org.kalasim.*
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
class Driver : Resource()
class TrafficLight : State<String>("red")
class Car : Component() {
val trafficLight = get<TrafficLight>()
val driver = get<Driver>()
override fun process() = sequence {
request(driver) {
hold(30.minutes, description = "driving")
wait(trafficLight, "green")
}
}
}
createSimulation {
enableComponentLogger()
dependency { TrafficLight() }
dependency { Driver() }
Car()
}.run(5.hours)
For each (active) component we (can) define a type such as:
class Car : Component()
The class inherits from org.kalasim.Component
.
Our car depends on a state TrafficLight
and resource Driver
for operation. To implement that, we first declare these dependencies with dependency{}
in the main body of the simulation, and secondly inject them into our car with get<T>
. Note, we could also directly inject states and resources with dependency {State("red")}
without sub-classing.
Although it is possible to define other processes within a class,
the standard way is to define a generator function called process
in the class.
A generator is a function that returns Sequence<Component>
. Within these process definitions we use suspend
able interaction function calls as a signal to give control to the centralized event loop.
In this example,
hold(1.0)
suspends execution control and comes back after 1 time unit (referred to as tick). Apart from hold
, kalasim
supports a rich vocabulary of interaction methods including passivate
, request
, wait
and component
.
The main body of every kalasim
model usually starts with:
createSimulation{
...
}
Car()
. It automatically is assigned the name Car.0.
As there is a generator function called process
in Car
, this process description will be activated (by default at time now
, which is 0
by default at the beginning of a simulation). It is possible to start a process later, but this is by far the most common way to start a process.
With
run(5.0)
we start the simulation and get back control after 5 ticks. A component called main is defined under the hood to get access to the main process.
When we run this program, we get the following output (displayed as table for convenience):
time current receiver action info
------ -------- --------- ---------------------------------- -------------------
.00 main Created
.00 main
.00 Driver.1 Created capacity=1
.00 Car.1 Created
.00 activate scheduled for .00
.00 main run +5.00 scheduled for 5.00
.00 Car.1 Car.1
.00 Requesting 1.0 from Driver.1
.00 Claimed 1.0 from 'Car.1'
.00 Request honor Driver.1 scheduled for .00
.00
.00 hold +1.00 scheduled for 1.00
1.00
1.00 entering waiters of TrafficLight.1
1.00 wait scheduled for <inf>
5.00 main main
Process finished with exit code 0
There are plenty of other more advanced (that is more fun!) examples listed in examples chapter.