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.
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.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.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.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.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.
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.
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());
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