6.3 Understanding KSL Processes and Entities
Entities can experience many processes. Thus, there needs to be a mechanism to activate the processes and to cleanup after the processes have completed. The ProcessModel
class facilitates the modeling of entities experiencing processes. A ProcessModel
has inner classes (Entity,
EntityGenerator,
etc.) that can be used to describe entities and the processes that they experience.
As noted in Figure 6.5 a ProcessModel
can activate KSL processes, can start entity process sequences, can dispose of entities, and can perform some after replication cleanup. One of the activities that a process model must perform is to terminate any processes that are still suspended after the replication is completed. In addition, it ensures that no entity terminates its process while still having allocations to a resource. Thus, a ProcessModel
is needed to manage the entities and processes that it represents. This is why in the pharmacy code example, the pharmacy model is a subclass of ProcessModel.
class DriveThroughPharmacy(
parent: ModelElement,
numPharmacists: Int = 1,
ad: RandomIfc = ExponentialRV(1.0, 1),
sd: RandomIfc = ExponentialRV(0.5, 2),
name: String? = null
) : ProcessModel(parent, name)
This provides the modeler with access to the inner classes e.g. Entity
that are inherited by the subclass for use in process modeling.
The key inner class is Entity,
which has a function process()
that uses a builder to describe the entity’s process in the form of a coroutine. An entity can have many processes described that it may follow based on different modeling logic. A process model facilitates the running of a sequence of processes that are stored in an entity’s processSequence
property. An entity can experience only one process at a time. After completing the process, the entity will try to use its sequence to run the next process (if available). Individual processes can be activated for specific entities. But, again, an entity instance may only be activated to experience 1 process at a time, even if it has many defined processes. The entity experiences processes sequentially.
An Entity
instance is something that can experience processes and as such may wait in queues. Entity
is a subclass of QObject.
Thus, statistics can be automatically collected on entities if they experience waiting. The general approach to defining a process for an entity is to use the process()
function to define a process that a subclass of Entity can follow. Entity instances may use resources, signals, hold queues, etc. as shared mutable state. Entities may follow a process sequence if defined. An entity can have many properties that define different processes that it might experience. The user can store the processes in data structures. In fact, there is a processSequence
property for this purpose that defines a list of processes that the entity will follow. As previously mentioned, the process()
function automatically adds each defined process (in the order of definition via the class body) to the processSequence
property unless told not to do so as an optional argument to the process()
function. The following code defines a process and assigns the function to the property pharmacyProcess.
This property is of type KSLProcess.
Because there were no arguments to the process()
function, the process is automatically added to the list of processes for this entity found in the processSequence
property. Each process can also be provided a string name via an argument of the process()
function. The name of a process can be useful in tracing and debugging process code.
KSLProcess
is an interface that provides a limited view of a KSL process instance. The interface allows the user to check the state of the process, get its identification (id, and name) and access the entity that is associated with the process. Underneath, the instance of a KSLProcess
is mapped onto a specially constructed Kotlin coroutine.
interface KSLProcess {
val id: Int
val name: String
val isCreated: Boolean
val isSuspended: Boolean
val isTerminated: Boolean
val isCompleted: Boolean
val isRunning: Boolean
val isActivated: Boolean
val entity: ProcessModel.Entity
}
The process()
function is a special builder function related to a KSLProcessBuilder.
A KSLProcessBuilder
provides the functionality for describing a process. A process is an instance of a coroutine that can be suspended and resumed. The methods of the KSLProcessBuilder
are the suspending functions that are allowed within the process modeling paradigm of the KSL. The various suspending functions have an optional string name parameter to identify the name of the suspension point. While not required for basic modeling, identifying the suspension point can be useful for more advanced modeling involving the cancellation or interrupting of a process. A unique name can be used to determine which suspension point is suspending the process when the process has many suspension points. We will examine the functionality of a KSLProcessBuilder
later in this section.
First and foremost, process modeling starts with understanding and using instances of the Entity
class.
Figure 6.5 indicates that there is an inner class called EntityState
within an Entity. As seen in Figure 6.6, EntityState
serves as the base class for defining the legal states for an entity and the legal state transitions using the state pattern as show in Figure 6.7.
CreatedState
- The entity is placed into this state when it is created. From the created state, the entity can be scheduled to be active.Scheduled
- The scheduled state indicates that the entity is associated with an event that is pending to occur in the event calendar. The entity is scheduled to be active at some future time. The underlying process associated with the entity is suspended.Active
- This is the state that indicates that the entity is executing an underlying process. It is executing non-suspending code within its process. It is the active entity. From the active state, the entity can be suspended for a number of reasons, each mapped to various states.WaitingForResource
- This state indicates that the entity’s process is suspended because the entity is waiting for units of a resource.WaitingForSignal
- This state indicates that the entity’s process is suspended because the entity is waiting on an arbitrary signal to be sent.BlockedSending
- This state indicates that the entity’s process is suspended because the entity is trying to send an item via a shared blocking queue and there is not space for the item in the queue. Blocking queues can be used to communicate between processes.BlockedReceiving
- This state indicates that the entity’s process is suspended because the entity is trying to receive items from a blocking queue and there are no items to receive. This is the other end of the communication channel formed by a blocking queue.InHoldQueue
- This state indicates that the entity’s process is suspended because the entity is an arbitrary queue that holds entities until they are removed and re-activated.WaitForProcess
- An entity may activate another process. This state indicates that the entity’s process is suspended because the entity is waiting for the process to complete before proceeding.
The entity states are mapped onto the lower level coroutine via an internal inner class (ProcessCoroutine
). This class defines the legal states of the coroutine shown in Figure 6.8.
Again, these states define the legal states of the underlying process coroutine. In general, users will not care about these internal details, but a basic understanding of what are legal transitions, shown in Figure 6.9 can be helpful. The following are the process coroutine states:
- Created - The process coroutine is placed in this state when it is instantiated.
- Running - The coroutine is running after it is started. This occurs when the entity activates the associated process. Processes are started by scheduling an event that invokes the coroutine code at the appropriate simulated time. The entity moves through its process and when the entity is between suspension points the process is considered to be in the running state.
- Suspended - The underlying process coroutine is suspended using Kotlin’s coroutine suspension functionality. Suspension is mapped to the various suspension states associated with an entity.
- Completed - The process coroutine has exited normally from the process routine or reached the end of the process routine. Once completed the coroutine is finished.
- Terminated - The process coroutine has exited abnormally via an error or exception or the user has directly terminated the process. Once terminated the coroutine is finished.
When a process coroutine exits normally the process coroutine is placed in the completed state. Then, the ProcessModel
checks to see if there are additional processes to execute within the entity’s process sequence. If there are, then the next process is automatically started. If there are no additional processes in the sequence, the entity is disposed.
If the entity is executing a process and the process is suspended, then the process routine may be terminated. This causes the currently suspended process to exit, essentially with an error condition. No further programming statements within the process coroutine will execute. The process ends (placed in the terminated state). All resources that the entity has allocated will be deallocated. If the entity was waiting in a queue, the entity is removed from the queue and no statistics are collected on its queueing. If the entity is experiencing a delay, then the event associated with the delay is cancelled. If the entity has additional processes in its process sequence they are not automatically executed. If the user requires specific behavior to occur for the entity after termination, then the user should override the Entity’s handleTerminatedProcess()
function to supply specific logic. Termination happens immediately, with no time delay.
The following section will illustrate through some simple models some of the functionality enabled by KSLProcessBuilder.