Copyright © 1998, 2001 by Ian Horswill, see comment within this file.
The plans package provides GRL definitions for compiling a language of plan expressions into sequences implemented as GRL transducers. These transducers can them be compiled as a part of a normal GRL program. Since all plan sequencers compile into transducers, they are all just finite state machines internally. The good news is that this means they can run in parallel without needing an underlying thread system in the host operating system. The bad news is that it means none of the sequencers have a stack, and so you can't write recursive or mutually recursive plans. Fortunately, such things seem relatively infrequent.
NB: Plans are implemented as statically parallel finite-state machines and so are not reentrant. Attempting to write recursive or mutually recursive plans can lead to strange results, including deadlock and livelock.
NB: This macro will not properly handle redefinition. In particular, the sequence:
(define-signal a (action
(arg 0) ...))
(define-plan (p)
(a)
(a))
(define-signal a (action
(arg 0) ...))
will leave the plan p with a pointer to the old version of a. Use define-action when you want to be able to redefine an action. or talk to Ian about how to write macros that handle redefinition properly.
Note that primitive actions are behaviors. They should be combined as usual with whatever other behaviors you wish to support, and then passed to whatever motor controller you use. Plans, however, are not behaviors in the GRL sense of having an activation level and a motor vector. Plans do all their word by turning on and off actions and other plans. As far as the GRL compiler is concerned, an action is of type behavior, meaning it is a group consisting of an activation level and a motor vector. A plan is a signal whose type is void, i.e. it doesn't have a real value and the compiler simply compiles it for its side effects.
NB: as with the action macro,
this does not support redefinition properly. You should use define-plan
when you need to bind the resulting plan to a global variable that will
be periodically redefined.
| (scheme scheme-expression) | Executes the scheme code verbatim |
| (action signal-expression ...) | Triggers action and sets its arguments to the current values
of their respective signal-expressions, then waits for the action
to terminate.
NB: this operation is unsynchronized. If action is already running, it will overwrite action's arguments. To synchronize it, prefix it with (wait-action action). |
| (start (action signal-expression ...)) | Triggers action and sets its arguments to the current
values of their respective signal-expressions, but does not wait
for the action to terminate.
NB: this operation is unsynchronized. If action is already running, it will overwrite action's arguments. To synchronize it, prefix it with (wait-action action) |
| (stop action) | Stops the execution of action, if it is currently running. |
| (wait-action action) | Waits for action to terminate. |
| (sleep milliseconds) | Pauses execution of the plan for the specified number of milliseconds. |
| (wait signal-expression) | Waits until signal-expression is true |
| (set! register signal-expression) | Sets register to the current value of signal-expression. |
| (begin plan-expression ...) | Executes each plan-expression in sequence. |
| (cobegin plan-expression ...) | Executes each plan-expression in parallel. |
| (while signal-expression plan-expression ...) | A normal while loop: the body is executed repeatedly until signal-expression is false. The test is only performed at the top of the loop - it is not evaluated continuously. |
| (if signal-expression
consequent-plan alternative-plan) (if signal-expression
(when signal-expression
(unless signal-expression
|
A normal conditional: tests signal-expression, then executes either consequent-plan or alternative-plan (or nothing), depending on the value. |
| (let ((variable
signal-expression) ...)
plan-expression ...) (let* ((variable signal-expression)
...)
|
Binding construct for local variables, as in LISP.
Caveat: let-bound variables in plans, while they have local scope, are ultimately implemented as global variables with inifinite lifetimes, thus plans with large numbers of local variables will require (somewhat) large amounts of space, even though only a few of those variables are "in use" at any given time. |
Primitive actions created with define-action are behaviors; they do their work by returning a motor-vector and an activation level. Plans do their work through side-effects: they modify registers, call other actions (including plans), and/or they invoke low-level scheme or C code. Rule actions are a third type of action. As with plans, they do their work by affecting other components of the system directly, but unlike plans, their operation is controlled by stateless inference rules rather than by a program counter. For more information on rules, see the rule macro documentation.
(define-rule-action (name (arg-name
initial-value) ...)
options ... rules ...)define-action
or define-plan: action-when, terminate-when,
etc.Generic actions provide a data-directed programming mechanism similar to Common Lisp generic functions. They are similar to C++ virtual functions except that they allow run-time specialization over multiple arguments (so-called "multimethods"). When a generic action is called, all methods attempt to match themselves to the argument values in parallel. All matching methods run in parallel until one terminates. When a method terminates, it clears the shared trigger register, thus halting all methods until the next call.
Plan and action methods can be mixed and matched within a given generic action.
As with signals, the compiler compiles only those methods that are specified in the call to the compiler, or that are inputs to some signal in the call. This has the advantage that methods can be defined in a library and then configured as desired by the user simply by adjusting what methods are given in the compiler call. It has the disadvantage that the user has to remember to include all the desired methods.
There is also a mechanism for defining default methods that run if and only if not other method matches:
Generic actions are effectively implemented as vestigial actions with no motor vectors. That is, they have trigger and argument registers but no actual code to execute. Methods are implemented as normal actions or plans that don't have their own trigger and argument registers, but rather use the trigger and argument registers of the generic action.
There is also a macro facility available for cases where a procedural abstraction is desired but it cannot be phrased as a separate plan. The usual reason for this is that multiple concurrent instances of the procedure are needed. GRL plans aren't reentrant, so concurrent calls interfere with one another.
or.(assert! (goal t)), will activate the goal modality
for a binary signal t.or.(assert! (goal near-object?) patient), will make (near-object?
patient) a goal..or.(retract! (goal t)), will deactive the goal modality
for a binary signal t.assert! and retract! both work by creating
a register to hold the value being asserted and then setting the register to
drive the accumulator. So if some other signal drives the accumulator
directly, retract will not be able to override it.or.(retract! (goal near-object?) patient), will cancel
the goal (near-object? patient).assert! and retract! both work by creating
a register to hold the value being asserted and then setting the register to
drive the accumulator. So if some other signal drives the accumulator
directly, retract will not be able to override it.It is very unlikely that you actually want to use this stuff. There's generally a cleaner way to achieve the same ends.
This code would be situated inside of a case statement that is called once per cycle of the GRL control loop and that branches based on the value of my-state.State 0: (set! action-trigger #t) (set! action-arg1 value1) ... (set! action-argn valuen) (set! my-state 1)State 1: (when (not plan-trigger) (set! my-state 2))