Software Engineering Institute Carnegie Mellon

Actors

Chapter 8. Actors, Neighbors and Simulations
8.1.1. actor: mutable type; 	
An actor is any autonomous participant of a program or simulation. The actors of a simulation are of equal status and are independent of their parents (i.e. an actor may outlive its ancestors).
Actors may have any number of author defined formal parameters and attributes. Each actor also has four private attributes:
	# the scheduler controlling this actor
	sim :: simulation := ?;   
	
	# the position of the start of the currently executing frame
	lenv :: int := ?;         
	
	# the top of the expression stack
	tos :: int := ?;          
	
	# the top of the mark stack
	mstos :: int := ?;        
	
	# the next actor in whatever queue the actor is in currently	
	next :: actor := ?;       
When an actor is created, it executes in preference to its creator. When the created actor executes the first scheduling operation (wait, wait_mouse, wait_event, delay, when, etc.), or the first requeue, control is returned to the creator.
Commentary
By parent, we mean the actor or subroutine that called new on the actor type. There is no explicit relationship between child actors and their parents - e.g. there is no way for a child to find out who its parent is, and there is no way for a parent to find its children unless it keeps track of who they are (by maintaining a list of children, etc.)
# STATUS: current

child: actor type is
	for x: each 1..10 do
		wait 30.0;

parent: actor type is
	for each 1..10 do
		null new child;
		wait 1.0;

main(): action is
	s :: simulation := new simulation;
	null new(s, parent);
	wait s;
	
main();
Here the child actors will continue executing long after the parent actor has terminated - notice that each child lives for 300 cycles, while the parent only lives for 10 cycles.
Lists of actors are often used as the control type of an iterative statement (see 7.4).
# STATUS: current

family: simulation type is
	members :: list := new list any;
	
child(n: int): actor type is
	i :: int := n;
	output("--- Newborn child ", n, " has i of ", i, "\cr");
	for x: every sim.members do
		x.i := x.i + 1000;
		output("Now child ", n, " has i of ", i, "\cr");
		wait 3.0;

parent: actor type is
	output "in parent\cr";
	for n: each 1..10 do
		push(sim.members, new(sim, child n));
		wait 1.0;
		
main(): action is
	s :: family := new family;
	null new(s, parent);
	wait s;
main();	     
Here the child is using every sim.members as the control type of a for loop.
8.1.2. new(actor type): the type;	
New creates a new actor. This operation is a special case of the new operation of 6.8.3. Each actor is created, its attributes initialized, a reference to the actor is returned to its parent, and only then are evaluation of its parent and of the remainder of its body continued in parallel. Actors are evaluated in parallel with each other in either real or simulated time, and with concurrent or interleaved semantics depending on their scheduling regime.
Commentary
# STATUS: current

child(n: int): actor type is
	for j: each 1..10 do
		output("Child " , n, " ", j, "\cr");
		wait 3.0;

parent(n: int): actor type is
	for m: each 1..10 do
		null new(sim, child m);
	for j: each 1..10 do
		output("Parent " , n, j, "\cr");
		wait 3.0;

main(): action is
	s :: simulation := new simulation;
	for n: each 1..10 do
		null new(s, parent n);
	wait s;
main();
In this example, the ten children print out the "Child" lines in parallel with the parent's printing out the "Parent" lines.
Actors are evaluated in parallel with each other in either real or simulated time, and with concurrent or interleaved semantics depending on their scheduling regime.
Actors are initialized prior to return from the call on new without the intervening execution of any other actors.
# STATUS: current

network: simulation type is
	nodes: list node := new list node;
	
node(id: int): actor type is
	x:: int := rand(1, 1000);
	requeue();
	for n: each sim.nodes do
		if n.x > 500 then
			outln "found a far-out node";

N: network := new network;
for i: each 1..5 do
	push(N.nodes, new(N, node i));
wait N;
In this example five nodes examine the x attributes of the nodes in the network. Without the requeue after attribute initialization, there would be no guarantee that the x attributes would be initialized before being referenced.
Occasionally more complex initialization is required.
# STATUS: current

network: simulation type is
	nodes: list node := new list node;
	
node(id: int): actor type is
	x::int := rand(1, 1000);
	y:: int := ?;
	
	if id > 3 & x > 200 then
		y := 0;
	else
		y := 1;
		
	for n: each sim.nodes do
		if n.y = 0 then
			outln "found a funny node";

N:: network := new network;
for i: each 1..5 do
	push(N.nodes, new(N, node i));
wait N;

This example may not give the intended results, because there is no guarantee that y will be initialized before it is referenced. This can be handled by wrapping the initialization in a function call:
# STATUS: current

network: simulation type is
	nodes: list node := new list node;
	
initialize(id: int, x: int): int is
	y :: int;
	if id > 3 & x > 200 then
		y := 0;
	else
		y := 1;
	return y;
		
node(id: int): actor type is
	x:: int := rand(1, 1000);
	y:: int := initialize(id, x);
	
	for n: each sim.nodes do
		if n.y = 0 then
			outln "found a funny node";

N: network := new network;
for i: each 1..5 do
	push(N.nodes, new(N, node i));
wait N;

Alternatively, explicit synchronization using a when operator may be used. This technique is illustrated in the routing table example of 6.8.3.
Note that new returns a reference to the actor. This reference can be assigned to an attribute if needed, or discarded by means of the null operation if it is not.
8.1.3. new(simulation, actor type): (the actor) type;	
This form of the new operation creates a reference to an actor in the specified simulation. (If the one-parameter form is used, the actor is created in the current simulation.)
Commentary
# STATUS: current

boring: actor type is
	for every true do
		output "Yawn\cr";
null new(new simulation, boring);
This creates a boring new actor in an anonymous new simulation.
8.1.4. pronoun self: actor;	
Any actor may reference itself using the pronoun self. Self always refers to the dynamically current actor.
Commentary
# STATUS: current

log(msg: string): action is
	output(msg, ": n = ", self.n, "\cr");

child(n: int): actor type is
	for j: each 1..10 do
		log("Child");
		wait 3.0;

parent(n: int): actor type is
	for m: each 1..10 do
		null new(sim, child m);
	for j: each 1..10 do
		log("Parent");
		wait 3.0;

s :: simulation := new simulation;
for n: each 1..10 do
	null new(s, parent n);
wait s;
Here the log procedure uses the self pronoun to access the parameter "n" child and parent actors. Note that these parameters and attributes are not directly visible within log.
Restrictions
Reference to the private fields of the actor (lenv, tos, etc.) has not been implemented.
8.1.5. terminate(): action;	
Terminate immediately and permanently discontinue execution of the current actor. Any existing references to the actor will however remain valid. Every execution sequence within the body of an actor must include a call on the parameterless terminate procedure. A call on terminate is however implicit at the syntactic end of each actor body.
Commentary
# STATUS: current

child(n: int): actor type is
	for every true do
		output("Child " , n, "\cr");
		if rand(0, 500) > 400 then
			output("Child ", n, "terminating.\cr");
			terminate();
		wait 3.0;

parent(n: int): actor type is
	for m: each 1..10 do
		null new(sim, child m);
	for j: each (1..100) do
		wait 3.0;

main(): action is
	s: simulation := new simulation;
	for n: each 1..10 do
		null new(s, parent n);
	wait s;
main();
In this example, each parent creates 10 children, each of whom would live forever except for the random check, which will cause them to terminate.
Note that the parent can still access the children even after they have terminated, as in the following:
# STATUS: current

star(n: int): actor type is
	k :: int := n + 10;
	dead :: boolean := false;
	output( "Star ", n, " has been born and now is dead.\cr");
	dead := true;

main(): action is
	galaxy :: simulation := new simulation;
	s :: star := new(galaxy, star 1);
	wait galaxy;
	confirm s.k = 11;
	confirm s.dead;
main();
Here main creates a star, which prints a message and then terminates. Even after it terminates, its attributes can be referenced.
Every execution sequence within the body of an actor must include a call on terminate, but there is an implicit call at the syntactic end of each actor body.
# STATUS: current

child(n: int): actor type is
   if (n > 999) then
      terminate();
   else
      output n;
   terminate(); # Unnecessary
Here the second call on terminate is unnecessary because it is equivalent to the implicit call automatically inserted by the Easel translator.
Commentary
The following example uses most of the constructs described in 8 . It illustrates the use of actor, new, indivisible, terminate, self, wait. It also illustrates the use of can which is defined in 8.2
# STATUS: current

river: simulation type is
	v: view := ?;
	fishies: list Salmon := new list Salmon;

# The life cycle of a salmon
states: type is enum(egg, fish, dead);

# Only when a bear is near a salmon can it eat the salmon
# bear & near(it, salmon) can eat(salmon);	 # neighbor relation

eat(S: Salmon): action is 
	# Set the salmon's state to dead, and its location to off-screen
	S.State := dead;
	S.location := 10000;
	
	# Increase the eater's satisfaction
	self.satisfaction := self.satisfaction + 2;
	
# Utility function for moving along the river
move(d: Length): action is
	self.location := self.location + d;
	if self.location > 600 then
		self.location := 600;
	if self.location < 20 then
		self.location := 20;
			
# Define a salmon's properties
Salmon(): actor type is 

	# Initial state is egg
	State :: states := egg;

	# At birth, place the salmon egg at a random
	# location along the river
	location :: Length := random(uniform, 0.0, 600.0); 
	State := fish;
	
	# Use yellow circles for salmon
	depict(sim.v, var offset_by(paint(circle(0, 0, 5), yellow), var location, 200));
	
	# In life, the salmon keeps moving until it dies
	for every State != dead do
		move random(uniform, -5, 5);
		wait 1;


# Define a bear's properties
bear(): actor type is
	
	# At birth, place the bear at a random location
	# along the river
	location :: Length := random(uniform, 0.0, 600.0); 

	# On average, a bear moves 5 m per time interval
	d: Length := 5;
	
	# The bear's satisfaction is the inverse of its hunger
	satisfaction: int := 0;
	
	# Use a brown circle for the bear
	depict(sim.v, var offset_by(paint(circle(0, 0, 8), brown), var location, 200));
	
	# Life cycle of a bear: move up river eating salmon
	for every true do
		
		# The bear keeps moving (self = current actor = the bear)
		move d * random(uniform, 1, 5);
		
		# If the bear's not satisfied, he may change direction
		if satisfaction <= 0 then
			if rand(1, 20) > 15 then
				d := -d;
				
		# Satisfaction wanes over time
		satisfaction := satisfaction - 1;
		
		wait 2;

		# When the bear is near a salmon, 
		# the salmon is eaten 
		# This eat is only legal if the neighbor relation
		# efined at the top of the program is
		# satisfied for some salmon
		for fish: each sim.fishies do
			if +(location - fish.location) < 5 then
				eat fish;

# Create the river simulation
Clarion: river := new river;

# Use a blue line for the river
Clarion.v := new view(Clarion, "Clarion River", forestgreen, group(paint(polyline(12, 20, 200, 600, 200), blue)));
null make_window(Clarion.v, 0);

# Generate a new bear and 20 new salmon
null new (Clarion, bear());
for each 1..20 do
	push(Clarion.fishies, new (Clarion, Salmon()));
	
# Wait until the actors in the river are finished
wait Clarion;

Neighbor operations are used to describe the relationship among actor and between actors and props. The neighbor operations of this section allow the relationships among actor to be described as operations that can be performed by one actor, the perpetrator, that change the state of another, the victim. By defining such operations within the victim, the can operation may be used to limit which actors may be perpetrators and the private specification to limit which attribute may be referenced. The effect is that there is no global visibility among actors except as explicitly allowed by actor descriptions.
A simulation defines a world of interactions among examples of many different types. It is a belief system about some real or imagined world. Types may be defined local to a simulation and must be self consistent and consistent with other types of that simulation, but need not be consistent with any enclosed, parallel or enclosing simulation. Each simulation constitutes the limit of the visibility scope for all types and operations defined within that simulation. Each simulation has its own simulated time regime, scheduler and simulation controls.
Commentary
Here is an example of creating actors in simulations.
# STATUS: current

ant_hill: simulation type is
	ant_count :: int := 0;
ant: actor type is
	sim.ant_count := sim.ant_count + 1;
	for each (1..rand(1, 50)) do
		wait random(uniform, 1.0, 100.0);
	sim.ant_count := sim.ant_count - 1;
main(): action is
	AH :: ant_hill := new ant_hill;
	for each (1..20) do
		null new(AH, ant);
	wait AH;
main();
Here AH is declared as a new example of an ant hill simulation. 20 ants are created in the ant hill; each ant lives for a random amount of time and then dies. The simulation global ant_count keeps track of the number of ants currently living in the ant hill.
Multiple simulations may be executed concurrently. Here is an example:
# STATUS: current

family: simulation type is 
	members :: list := new list any; 
	
child(i: int, n: int): actor type is
	output("This is child ", n, " of parent ", i, " \cr");    
	wait 5.0; 

parent(i: int): actor type is
	for n: every (1..10) do       
		push(sim.members, new(sim, child(i, n)));
		wait 1.0;      

main(): action is 
	myfamily :: family := new family;    
	yourfamily :: family := new family;    
	null new(myfamily, parent 100); 
	null new(yourfamily, parent 200); 
	wait myfamily;
	wait yourfamily;

main();
Here we are running two independent simulations, myfamily and yourfamily, in parallel. Each simulation creates 10 children. In the output, the trace messages from both simulations will be interleaved.
8.3.1. 
scheduler: type is
	scale:: scale_type := uniform_scale;	
	update_interval :: number := 0.1;
	speed :: number := infinity; 	
	clock :: number := 0.0;
	_anchor_time :: number := 0.0;
	_time_q :: all := ?;
	_cond_q :: actor := ?;
	_wait_sim_q :: actor := ?;
	_runq_count :: int := nti 0;
	_blocked_count :: int := nti 0;
	_conditions_checked :: int := nti 0;
	_last_update_tick :: number := 0.0;
	_actor_count :: int := nti 0;
	 
	 
Schedulers are used to control simulations. Their fields may be referenced through the skdr attribute of simulations (see 8.3.15).
8.3.2. update_interval :: number := 0.1; 	
The update_interval attribute is a simulation parameter that controls the interval between graphic updates. (To be precise, it is the minimum time between the end of one update and the beginning of the next.) To increase simulation performance, this attribute may be set to values greater than 0.0. The unit is seconds.
Commentary
In the following example, the depictions within the orbiter simulation will be updated only every 5 seconds.
# STATUS: current

orbiter :: simulation := new simulation;
(orbiter.skdr).update_interval := 5.0;
confirm (orbiter.skdr).update_interval = 5.0;
8.3.3. speed :: number := infinity; 	
The speed attribute is a simulation parameter that controls the real time speed of the simulation. Speed is an upper bound on the ratio between simulated and real time. Speed affects the depiction but not the functional results of a simulation. The speed is initialized to infinity which guarantees that the simulation will run as fast as possible within the processing capabilities of the system.
Commentary
In the following example, the speed of the simulation is set to 0.5, ensuring that every second of simulated time will take at least two seconds of real time. In other words, the simulation will unfold in slow motion at half the speed of the events being simulated (assuming adequate processing power).
# STATUS: current

orbiter :: simulation := new simulation;
(orbiter.skdr).speed := 5.0;
confirm (orbiter.skdr).speed = 5.0;
8.3.4. _clock :: Time := ?;	
Clock is an attribute of every simulation. The value of clock is the amount of simulated time between the start of the simulation and the current simulated time. Outside of a simulation, clock has the same value as the real time clock (see 8.3.13).
Commentary
# STATUS: current

a(): actor type is
	for every true do
		wait 10.0;
		output(sim.skdr.clock, "\cr");       #    prints 10, 20 ...

null new(new simulation, a());
Note: this attribute is for internal use only; authors should access the clock using the clock() function (see 8.3.12).
8.3.5. _anchor_time :: Time := ?;	
Anchor_time is an internal attribute used to implement simulation scaling and speed. It is nominally the time at which the simulation started, but adjusted depending for speed and scaling. If the speed is always 1.0, and the scale is absolute scale, anchor_time will always be the starting time.
Commentary
# STATUS: current

a(): actor type is
	for every 1..3 do
		wait 10.0;
		output(sim.skdr._anchor_time, "\cr"); 

null new(new simulation, a());
8.3.6. _time_q :: all := ?;
The time queue is a queue of all actors waiting for a time to elapse (see 8.6.2).
8.3.7. _cond_q :: actor := ?;
The time queue is a queue of all actors waiting for specified conditions to become true (see 8.6.5).
8.3.8. _wait_sim_q :: actor := ?;
The simulation wait queue is a queue of all actors waiting for a given simulation to terminate (see 8.7.1).
8.3.9. _conditions_checked :: int := 0;
This is an internal attribute used to implement waitin on simulations.
8.3.10. _last_update_tick :: number := 0.0;
This is an internal attribute used to implement update_interval.
8.3.11. _actor_count :: int := nti 0;
This is an internal attribute used to implement waiting on simulations.
8.3.12. clock(): number;
The clock function returns the current simulation time, or the real time when the caller is not within a simulation. The clock cycles approximately once every 77.67 hours (i.e., every 2^23 1/60th's of a second).
Commentary
# STATUS: current

a(): actor type is
	for every true do
		wait 10.0;
		output(clock(), "\cr");       #    prints 10, 20 ...

null new(new simulation, a());
8.3.13. rtc(): Time;
The real time clock function returns the time in seconds since the Easel system started. The same operations apply to real time as to simulated time (see 8.5). The real time clock cycles approximately once every 77.67 hours (i.e., every 2^23 1/60th's of a second).
8.3.14. set_speed(number): action;	
The set_speed operator sets the simulation speed to the specified value and re-computes the anchor of the current simulation to ensure that the relationships among the simulation clock, the real-time clock, and the simulation speed are maintained.
Commentary
If the simulation clock is ever assigned to, set_speed should be called with the current speed so that the anchor is reset properly:
# STATUS: current

sim.skdr.clock := 100;
set_speed(sim.skdr.speed); # adjust the achor, etc.
8.3.15. simulation: mutable type is 
	skdr :: scheduler := new scheduler; 
Each simulation is an actor of the program or parent simulation. The specified return time of a simulation must be "simulation". Actors within a simulation have concurrent semantics.
8.3.16. new(simulation type): the type;	
New creates a new simulation.
Commentary
See 8.1.3 for an example of calling new on a simulation type.
8.3.17. pronoun sim: simulation;
Sim (see 8.3.17) is a pronoun used to reference the most global scope within the current simulation. Both the built-in simulation scheduler (the attribute skdr) and author-defined attributes of a particular simulation type may be referenced by dot qualification on sim.
Defines made anywhere within a simulation type are directly visible in a scope that is global to the body of the simulation, and indirectly visible to the actors within that simulation through the use of the simulation pronoun (see 8.3.17).
Each simulation scope hides all attributes and defines of all more global simulations, so that each simulation acts as an independent belief system. All system level definitions, including built-in language defines, however, are visible throughout every simulation. Language defined attributes are visible in any scope in which it is not hidden by a attribute or define of the same name.
Commentary
# STATUS: current

corporation: simulation type is
	employees :: int := 40000;
	virtue: type is enum(market_share, innovation, social_welfare);
	greatest_good :: virtue := ?;
	
	output("simulation started with ", employees, "\cr");
	
company: actor type is
	if sim.greatest_good = market_share then
		output "I'm a money grubber\cr";
	else if sim.greatest_good = social_welfare then
		output "I'm a tree hugger\cr";
	else if sim.greatest_good = innovation then
		output "I love newness\cr";
Macro_soft :: corporation := new corporation;
Macro_soft.greatest_good := market_share;
Werd :: company := new (Macro_soft, company);

Peaches :: corporation := new corporation;
Peaches.greatest_good := innovation;
Pipod :: company := new (Peaches, company);
wait Macro_soft;
wait Peaches;
Here we model the belief systems of two corporations. The belief that market share is the greatest good is shared by the Macrosoft Corporation and by its Werd subsidiary, while the conflicting belief that innovation is the greatest good is shared by Peaches and Pipod.
Here is another example:
# STATUS: current

cheesy: simulation type is
	cheese: type is enum(swiss, american, gorgonzola, cheddar, parmesan);
	variety :: cheese := rand(swiss, parmesan);
	max_time :: number := random(uniform, 0.0, 10.0);
	
cheese_lover():  actor type is
	for every true do
		# wait sim.max_time;
		wait 1.0;
		output("I believe the moon is made out of " ,
			sim.variety, " cheese\cr");
            
main(): action is
	s :: simulation := ?;
	for each (1..4) do
		s := new cheesy;
		for each 1..10 do
			null new(s, cheese_lover());
	wait s;
	
main();
In the above example, cheese, variety and max_time are local to each of the simulations. Within each of the 10 cheesy simulations, all the cheese_lovers share a single belief about the composition of the moon.
Time is a base dimension of any simulation or description of the real world. Time may be used to measure either duration or absolute time since the beginning of the simulation. See 2.10.4 for the definition of time.
8.6.1. control "take"(Time, action): action;	
Take specifies the amount of time required to execute an action. Within a simulation this becomes the actual time. Outside a simulation take has no effect other than to report the amount of time actually taken.
Commentary
The "take" operation leaves unspecified exactly where time is spent during the action.
# STATUS: current

run_one_km(): action is
	self.remaining := self.remaining - 1000.0;

ten_k_runner(id: int): actor type is
	remaining :: number := 10000.0;
	for every (remaining > 0.0) do
		take 4.0 to
			output("Runner ", id, "has ", remaining, " left to run.\cr");
			run_one_km();

take_example(): action is
	s :: simulation := new simulation;
		
	for i: each (1..40) do
		null new(s, ten_k_runner i);
		
take_example();
Here each runner runs a kilometer in 4.0 time units; nothing is said about how those 4.0 time units are distributed over the running of the kilometer.
8.6.2. wait(interval: Time): action;	
The wait interval operation suspends the current actor's execution for the specified interval. Wait works in either simulated or real-time actors.
Commentary
Normally, waits should always be used in preference to delays, so that code may be moved from real-time actors to simulation actors and vice versa.
Unlike the take operation, the wait interval operation specifies that nothing is going on within the actor during the wait.
# STATUS: current

ten_k_runner(id: int): actor type is
	remaining :: number := 10000.0;
	for every (remaining > 0.0) do
		output("Runner ", id, "has ", remaining, " left to run.\cr");
		remaining := remaining - 1000.0;
		wait 4.0;

wait_interval_example(): action is
	s :: simulation := new simulation;
	for i: each (1..40) do
		null new(s, ten_k_runner i);
	wait s;

wait_interval_example();
Here each runner runs a kilometer in zero time, then waits for 4.0 time units.
8.6.3. delay(Time): action;	
The delay operation is a real-time delay that suspends the current actor's execution for the specified real-time interval.
Commentary
The only purpose of delays is to provide real-time delays in simulated actors. In real-time actors, delays are the same as waits, but waits are preferred so that code can be moved from real-time actors to simulated actors and vice versa.
# STATUS: current

ten_k_runner(id: int): actor type is
	remaining :: number := 10000.0;
	for every (remaining > 0.0) do
		output("Runner ", id, "has ", remaining, " left to run.\cr");
		remaining := remaining - 1000.0;
		blocked delay 4.0;

delay_interval_example(): action is
	s :: simulation := new simulation;
	for i: each (1..40) do
		null new(s, ten_k_runner i);
	wait s;

delay_interval_example();
8.6.4. until(Time): Time;
The time until operation converts absolute time to a duration from the current time. The time until operation might be called in the argument to a wait interval operation to wait until an absolute time.
Commentary
# STATUS: current

ten_k_runner(): actor type is
	remaining: number := 10000.0;
	for every (remaining > 0.0) do
		outln("At ", clock(), ", ", remaining, " remaining");
		remaining := remaining - 100.0;
		wait until clock() + 4.0;

s: simulation := new simulation;
for each (1..4) do
	null new(s, ten_k_runner());
wait s;
8.6.5. control when(boolean): action;	
The when operation suspends the current actor's execution until the condition becomes true. If the value of the boolean changes from false to true and back to false between clock increments the true condition may or may not be detected by the when. If the condition is initially true, the actor will not be suspended, but at the implementation level will be requeued fifo-by-priority.
Commentary
# STATUS: current

race: simulation type is
	start :: boolean := false;
	
starter(): actor type is
	wait 110.0;
	sim.start := true;

racer(id: int): actor type is
	for each 1 .. rand(1, 100)  do
		output("Racer ", id, " is getting ready\cr");
	when sim.start;
	output("Racer ", id, " is starting");

R :: race := new race;
null new(R, starter());
for i: each 1..10 do
	null new(R, racer i);
wait R;
In this example, even though the ten racers take varying amounts of time to prepare for the race, they all start at the same time because they wait for the starter to signal the start of the race.
8.6.6. requeue(): action;	
Moves the current actor to the end of the run queue.
8.6.7. control blocked(action): action;
The blocked operator specifies that its parameter is to be executed as a blocked operation - that is, that it should prevent the simulation time from advancing.
Commentary
Consider the following program:
# STATUS: current

include "::Libraries:Metric.easel";
procrastinator(): actor type is
	for every 1 .. 3 do
		outln("Procrastinator procrastinating");
		delay 5.0 s;
		wait 0.0 s;
ant(): actor type is
	for each 1 .. 10 do
		outln("Ant delaying");
		wait 0.0 s;
	outln "Ant done!!";

blocked_test:: simulation := new simulation;
null new (blocked_test, procrastinator());
null new(blocked_test, ant());
wait blocked_test;
In this program, it will take about 15 seconds for the ant to finish, because the procrastinator is executing 3 delays of 5 seconds each. Since delay is a blocking operation, the ant cannot continue processing until the delays are completed. Contrast that with the following:
# STATUS: current

include "::Libraries:Metric.easel";
procrastinator(): actor type is
	for every 1 .. 3 do
		outln("Procrastinator procrastinating");
		unblocked delay 5.0 s;
		wait 0.0 s;
ant(): actor type is
	for each 1 .. 10 do
		outln("Ant delaying");
		wait 0.0 s;
	outln "Ant done!!";

blocked_test: simulation := new simulation;
null new (blocked_test, procrastinator());
null new(blocked_test, ant());
wait blocked_test;
Here, because the procrastinator does unblocked delays, the ant can continue processing, and so finishes almost immediately.
Observers are special actors whose activities are not bound by private and neighbor specifications. An observer may be used to collect information, interrogate simulations, or save simulation states. An observer can reference any attribute and apply any function without regard to private and neighbor restrictions.
Facilitators are special actors whose activities are not bound by private and neighbor specifications. A facilitator may be used to initialize and control simulations. A facilitator has the capabilities of an observer but may also assign to any attribute without regard to private and neighbor restrictions. The body of each simulation is a facilitator with respect to that and all embedded simulations. The body of a program is a facilitator with respect to all simulations of that program.
Commentary
Facilitators and Observers are conceptual entities; nothing special needs to be done to create them in Easel programs.
8.7.1. wait(simulation): action;	
Only observers can wait for simulations to complete. Wait simulation suspends the simulation until all actors of the simulation are terminated.
Commentary
Here is a simple producer-consumer example implemented using events.
# STATUS: current

Economy(): simulation type is
	product: type;
	warehouse: event product := new event product;

Producer(): actor type is
	for every true do
		post(sim.warehouse, new product);
		wait random(uniform, 1.0, 100.0);

Consumer(): actor type is
	for every true do
		output("Retrieved ", wait sim.warehouse, " from warehouse.\cr");
		wait random(uniform, 1.0, 100.0);
		
Main(): action	is
	e :: Economy := new Economy;
	null new(e, Producer());
	null new(e, Consumer());
8.8.1. event: type is list ;
An event is something that happens, an occurrence. An event may occur any number of times. Each occurrence of an event may have associated state information of type t.
8.8.2. new(event type): event;
New returns a reference to a new example of the event type. It does not create any occurrences of the event.
8.8.3. post(e: event, e.t): action;
One occurrence of an event is indicated by each call on post. Posting indicates that the event occurred at the current time. The parameter to post is the event information. Posting does not cause waiting. System defined events or posted by the system and do not generally occur in application programs.
8.8.4. wait(e: event): e.t;
Waiting on an event immediately returns the event state information for the earliest occurrence of that event that has not yet been processed (i.e., by a call on wait or get). If all previous occurrences have been processed, waiting suspends the current actor until an occurrence of the event is posted.
8.8.5. get(e: event): (e.t | nil) ;
Get event is similar to wait except that it returns immediately with value nil when no occurrence of the event is pending.
8.9.1. os_event: immutable type 
The type os_event is an internal, low-level mechanism for handling Operating System events such as mouse clicks and for manipulating scheduler queues. It is not intended for use at the user level.
8.9.2. event_kind: type is enum(update_event, run_event, mouse_event, print_event);
An internal type used to identify the class of os_event being handled.
8.9.3. wait_event(event_kind, time_out: Time): action;
An internal operator used to queue the actor that calls it to wait on the event_queue of that type until an os_event of the type occurs or the time_out time expires.

For more information, contact SEI Customer Relations, customer-relations@sei.cmu.edu

return to top   |   Easel Language Reference Manual and Users' Guide
Easel main page