The plans package provides definitions for behaviors that function as triggerable, discrete actions, as well as explicit sequencing of actions via plans. Actions has associated with them triggers for initiating them and, optionally, arguments that can be specified at the time the action is triggered. Actions can be triggered either by normal GRL code or by plans. Plans form compound actions that can be called and passed arguments by other plans.
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: 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-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
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.|
|(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.|
|A normal conditional: tests signal-expression, then executes either consequent-plan or alternative-plan (or nothing), depending on the value.|
(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.
(action args ...)
(set! register signal-expression)
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))