In the introduction to process-oriented simulation in jasima we already came across the waitFor/waitUntil methods that allow simulation processes to wait for some time or until some absolute point in time before their execution continues. Sometimes it is necessary to wait for some indefinite amount of time until a certain condition becomes true.

This is possible with the waitCondition method. This method always has to be used with ObervableValues. An observable value stores some value. There are get() and set(T) methods to retrieve or set its current value. What makes them special is, that they also offer a notification mechanism that can be used register a listener. The listener is notified whenever the stored value changes. This is used in waitCondition to be able to continue a process whenever a certain condition becomes true.

In addition to the ObservableValue<T> class itself there is also a class ObservableValues. It offers certain static helper function that can be used to create and combine ObservableValues more easily. Combining ObservableValues leads to new, derived ObservableValues.

Example

To demonstrate the use, let’s look at the HelloConditions.java example, available in package examples.simulation.process of project_template_standalone. The general setup of this class is similar to the hello world example from Getting Started: There is a main SimEntity with its lifecycle method and two simulation processes ("incrementer" and "printer"), activated by it. The class contains a counter callCounter implemented as an Observable, containing an Integer value. The counter is incremented by the simulation process "incrementer". The second process "printer" prints a message after 3 increments before printing some message (mathematically it waits until the counter modulus 3 is 0). Furthermore the main process also waits on a condition involving the counter. Only if it reaches 13 the simulation is ended. The example in full is shown below, explaining the most important lines.

Main process
 @Override
 protected void lifecycle() throws MightBlock {
 	// init
 	System.out.println("simulation main process started at: " + simTime());
 	callCounter = new ObservableValue<>(0); (1)
 	activate(this::incrementer);
 	activate(this::printer);

 	// main process waiting until counter reaches 13
 	ObservableValue<Boolean> counterEquals13 = ObservableValues.isEqual(callCounter, 13); (2)
 	waitCondition(counterEquals13); (3)

 	// end simulation
 	System.out.println("simulation main process finished at: " + simTime());
 	end();
 }
1 create a new observable value with an initial value of 0
2 create a derived observable that becomes true once callCounter is equal to 13
3 wait until the condition becomes true
Incrementer process
 void incrementer() throws MightBlock {
 	System.out.println("incrementor() starting at: " + simTime());
 	while (true) {
 		waitFor(3.0);

 		// increment counter
 		callCounter.set(callCounter.get() + 1); (1)
 		// instead of get/set we could also have used: callCounter.update(n -> n+1);

 		System.out.println("incrementor() executed at: " + simTime() + ", counter = " + callCounter.get());
 	}
 }
1 increment the counter by 1
Printer process
 void printer() throws MightBlock {
 	System.out.println("printer() starting at: " + simTime());
 	while (true) {
 		waitCondition(n -> n % 3 == 0, callCounter); (1)
 		System.out.println("printer() executed at: t=" + simTime() + ", counter = " + callCounter.get());
 		waitFor(5.0);
 	}
 }
1 wait on a condition derived from callCounter; the condition is a lambda expression taking an Integer as a parameter and returning a Boolean value

Running the example, the following output should be visible:

Output of HelloConditions.java
simulation main process started at: 0.0
incrementor() starting at: 0.0
printer() starting at: 0.0
printer() executed at: t=0.0, counter = 0
incrementor() executed at: 3.0
incrementor() executed at: 6.0
incrementor() executed at: 9.0
printer() executed at: t=9.0, counter = 3
incrementor() executed at: 12.0
incrementor() executed at: 15.0
incrementor() executed at: 18.0
printer() executed at: t=18.0, counter = 6
incrementor() executed at: 21.0
incrementor() executed at: 24.0
incrementor() executed at: 27.0
printer() executed at: t=27.0, counter = 9
incrementor() executed at: 30.0
incrementor() executed at: 33.0
incrementor() executed at: 36.0
printer() executed at: t=36.0, counter = 12
incrementor() executed at: 39.0
simulation main process finished at: 39.0