Configurators
Actors are not simply constructed. Instead, they are configured, and later, the configuration is spawned. The evolution from source statement to spawned actor can be described in four steps:
- Generate -- compile the source to kernel code that creates actor records
- Construct -- execute the kernel code to create a configurator
- Configure -- invoke the configurator to create a configuration
- Spawn -- spawn a concurrent process using the configuration
Generate and Construct
HelloWorld
In this section, we compile the following HelloWorld
source into kernel code that, when executed, constructs an actor record containing a configurator.
01 actor HelloWorld(init_name) in
02 import system.Cell
03 var my_name = Cell.new(init_name)
04 handle tell {'name': name} in
05 my_name := name
06 end
07 handle ask 'hello' in
08 'Hello, World! My name is ' + @my_name + '.'
09 end
10 end
Compiling HelloWorld
generates the following kernel code with code omitted in certain places to keep the example concise. Omissions are noted using <<...>>
.
01 local $actor_cfgtr in
02 $create_actor_cfgtr(proc (init_name, $r) in // free vars: $import, $respond
03 local Cell, my_name, $v0, $v6 in
04 $import('system', ['Cell'])
05 $select_apply(Cell, ['new'], init_name, my_name)
06 $create_proc(proc ($m) <<...>> in end, $v0)
07 $create_proc(proc ($m) <<...>> in end, $v6)
08 $create_tuple('handlers'#[$v0, $v6], $r)
09 end
10 end, $actor_cfgtr)
11 $create_rec('HelloWorld'#{'cfg': $actor_cfgtr}, HelloWorld)
12 end
When executed, the kernel code creates the actor record 'HelloWorld'#{'cfg': $actor_cfgtr}
at line 11
. All actor records conform to the pattern 'ACTORNAME'#{'cfg': $actor_cfgtr}
where ACTORNAME
is the name used in the source code and $actor_cfgtr
is a configurator compiled from the actor body source.
Next, we discuss how configurators are used to produce configurations.
Configure and Spawn
Configuring and spawning an actor involves two special Java classes: ActorCfgtr
and ActorCfg
.
The configurator discussed previously is an instance of ActorCfgtr
, and when invoked, produces an instance of ActorCfg
. The essence of both Java classes are shown below.
public class ActorCfgtr implements Proc {
private final Closure handlersCtor;
public ActorCfgtr(Closure handlersCtor) {
this.handlersCtor = handlersCtor;
}
@Override
public final void apply(List<CompleteOrIdent> ys, Env env, Machine machine) throws WaitException {
List<Complete> resArgs = new ArrayList<>(ys.size());
for (int i = 0; i < ys.size() - 1; i++) {
CompleteOrIdent y = ys.get(i);
Complete yRes = y.resolveValue(env).checkComplete();
resArgs.add(yRes);
}
CompleteOrIdent target = ys.get(ys.size() - 1);
ValueOrVar targetRes = target.resolveValueOrVar(env);
ActorCfg actorCfg = new ActorCfg(resArgs, handlersCtor);
targetRes.bindToValue(actorCfg, null);
}
}
public final class ActorCfg implements Obj {
private final List<Complete> args;
private final Closure handlersCtor;
public ActorCfg(List<Complete> args, Closure handlersCtor) {
this.args = args;
this.handlersCtor = handlersCtor;
}
}
PerformHelloWorld
In this section, we compile PerformHelloWorld
and discuss how a parent actor configures and spawns its nested actor HelloWorld
discussed previously.
01 actor PerformHelloWorld() in
02 actor HelloWorld(init_name) in
03 import system.Cell
04 var my_name = Cell.new(init_name)
05 handle tell {'name': name} in
06 my_name := name
07 end
08 handle ask 'hello' in
09 'Hello, World! My name is ' + @my_name + '.'
10 end
11 end
12 handle ask 'perform' in
13 var hello_world = spawn(HelloWorld.cfg('Bob'))
14 var hello_bob = hello_world.ask('hello')
15 hello_world.tell({'name': 'Bobby'})
16 var hello_bobby = hello_world.ask('hello')
17 [hello_bob, hello_bobby]
18 end
19 end
Compiling PerformHelloWorld
generates the following kernel code containing our nested actor HelloWorld
. Again, omissions are noted using <<...>>
. Note the two invocations of $create_actor_cfgtr
at lines 02
and 04
, which create configurators for PerformHelloWorld
and HelloWorld
, respectively.
01 local $actor_cfgtr in
02 $create_actor_cfgtr(proc ($r) in // free vars: $import, $respond, $spawn
03 local HelloWorld, $actor_cfgtr, $v9, $v15 in
04 $create_actor_cfgtr(proc (init_name, $r) in // free vars: $import, $respond
05 local Cell, my_name, $v0, $v6 in
06 $import('system', ['Cell'])
07 $select_apply(Cell, ['new'], init_name, my_name)
08 $create_proc(proc ($m) in <<...>> end, $v0)
09 $create_proc(proc ($m) in <<...>> end, $v6)
10 $create_tuple('handlers'#[$v0, $v6], $r)
11 end
12 end, $actor_cfgtr)
13 $create_rec('HelloWorld'#{'cfg': $actor_cfgtr}, HelloWorld)
14 $create_proc(proc ($m) in // free vars: $respond, $spawn, HelloWorld
15 local $else in
16 $create_proc(proc () in <<...>> end, $else)
17 case $m of 'perform' then
18 local $v12, hello_world, hello_bob, hello_bobby in
19 local $v13 in
20 $select_apply(HelloWorld, ['cfg'], 'Bob', $v13)
21 $spawn($v13, hello_world)
22 end
23 $select_apply(hello_world, ['ask'], 'hello', hello_bob)
24 local $v14 in
25 $bind({'name': 'Bobby'}, $v14)
26 $select_apply(hello_world, ['tell'], $v14)
27 end
28 $select_apply(hello_world, ['ask'], 'hello', hello_bobby)
29 $create_tuple([hello_bob, hello_bobby], $v12)
30 $respond($v12)
31 end
32 else
33 $else()
34 end
35 end
36 end, $v9)
37 $create_proc(proc ($m) in <<...>> end, $v15)
38 $create_tuple('handlers'#[$v9, $v15], $r)
39 end
40 end, $actor_cfgtr)
41 $create_rec('PerformHelloWorld'#{'cfg': $actor_cfgtr}, PerformHelloWorld)
42 end
The nested HelloWorld
actor is defined between lines 04
and 13
. At run time, PerformHelloWorld
configures and spawns a HelloWorld
child as part of its handle ask 'perform' in <<...>> end
kernel code defined between lines 19
and 22
. The HelloWorld
configurator (an instance of ActorCfgtr
) is invoked at line 20
to instantiate an ActorCfg
and bind it to variable $v13
. Subsequently, the $spawn
instruction launches a concurrent actor using the ActorCfg
bound to $v13
. The result of the $spawn
instruction is an ActorRef
bound to the hello_world
variable. Later, the hello_world
variable is used to send messages to the new child actor.
Native Configurators
In this section, we present a timer example that uses a native actor. The Torq program below configures and spawns a timer at line 05
as part of stream expression.
01 actor IterateTimerTicks() in
02 import system[Cell, Stream, Timer, ValueIter]
03 handle ask 'iterate' in
04 var tick_count = Cell.new(0)
05 var timer_stream = Stream.new(spawn(Timer.cfg(1, 'microseconds')),
06 'request'#{'ticks': 5})
07 for tick in ValueIter.new(timer_stream) do
08 tick_count := @tick_count + 1
09 end
10 @tick_count
11 end
12 end
The Java program below runs the example from above and validates that the stream generated five timer ticks.
ActorRef actorRef = Actor.builder()
.setAddress(Address.create(getClass().getName() + "Actor"))
.setSource(source)
.spawn()
.actorRef();
Object response = RequestClient.builder()
.setAddress(Address.create("IterateTimerTicksClient"))
.send(actorRef, Str.of("iterate"))
.awaitResponse(100, TimeUnit.MILLISECONDS);
assertEquals(Int32.of(5), response);
The timer implementation is provided as a TimerPack
.
final class TimerPack {
public static final Ident TIMER_IDENT = Ident.create("Timer");
private static final int TIMER_CFGTR_ARG_COUNT = 3;
private static final CompleteProc TIMER_CFGTR = TimerPack::timerCfgtr;
public static final CompleteRec TIMER_ACTOR = createTimerActor();
private static CompleteRec createTimerActor() {
return CompleteRec.singleton(Actor.CFG, TIMER_CFGTR);
}
private static void timerCfgtr(List<CompleteOrIdent> ys, Env env, Machine machine) throws WaitException {
if (ys.size() != TIMER_CFGTR_ARG_COUNT) {
throw new InvalidArgCountError(TIMER_CFGTR_ARG_COUNT, ys, "timerCfgtr");
}
Num period = (Num) ys.get(0).resolveValue(env);
Str timeUnit = (Str) ys.get(1).resolveValue(env);
TimerCfg config = new TimerCfg(period, timeUnit);
ys.get(2).resolveValueOrVar(env).bindToValue(config, null);
}
private static final class Timer extends AbstractActor {
// <<...>>
}
private static final class TimerCfg extends OpaqueValue implements NativeActorCfg {
final Num periodNum;
final Str timeUnitStr;
TimerCfg(Num periodNum, Str timeUnitStr) {
this.periodNum = periodNum;
this.timeUnitStr = timeUnitStr;
}
@Override
public final ActorRef spawn(Address address, ActorSystem system, boolean trace) {
return new Timer(address, system, trace, periodNum, timeUnitStr);
}
}
}
Note the following TimerPack
elements and how they correlate to compiled Torq:
TIMER_ACTOR
is a Torq record, which is the same structure produced when a source statementactor <<...>> end
is compiled.TIMER_CFGTR
is a configurator and part of theTIMER_ACTOR
record.TimerCfg
is an instance ofNativeActorConfig
At run time, LocalActor
recognizes the difference between Torq and Java configurations and spawns appropriately.
ActorRefObj childRefObj;
if (config instanceof ActorCfg actorCfg) {
childRefObj = spawnActorCfg(actorCfg);
} else {
childRefObj = spawnNativeActorCfg((NativeActorCfg) config);
}
Spawning an actor from Java
The previous sections describe how Torq and native actors are spawned from Torq. Here, we briefly discuss how to spawn actors from Java and obtain an instance of ActorRef
to send the actor messages.
To configure an instance of a Torq actor, begin with a fluent builder instance using ActorBuilder.builder()
. Actor builders are used throughout this book to run examples.
To configure an instance of a native actor, you must use the elements it provides. For example, the Timer
actor above is held privately in the TimerPack
, so you must access its configurator from its actor record.