Plans and discrete actions

Ian Horswill 4/12/00
 
 

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.

Defining primitive actions

Primitive actions are specially flagged behaviors whose activation level is controlled by a register.  Primitive actions may also optionally take arguments.  They are defined with the define-action form:
(define-action (action-name (arg-name  initial-value) ...)

  (initially-running? boolean)
  (trigger-when condition)
  (terminate-when condition)
  motor-vector)
Macro.  Defines a new global variable, action-name, and binds it to a behavior whose action level is controlled by a trigger that is callable by a plan sequencer.  The initial-values, condition, and motor-vector may be arbitrary signal expressions.   The condition and motor-vector expressions are evaluated within the scope of the arguments (Ph.D. talk for saying you can use the arguments in them).  If the initially-running? clause is specified and is true, then the action will be triggered when the program is started.  If the trigger-when clause is specified, then the action can be triggered, either by being explicitly called, or by having the condition specified in the clause be true.  The action will then continue to run until termination, regardless of whether condition continues to be true.  If the terminate-when clause is specified, then the action can be terminated either by being explicitly stopped by some plan, or by having the specified condition be true.
(action (action-name (arg-name  initial-value) ...)

  (initially-running? boolean)
  (trigger-when condition)
  (terminate-when condition)
  motor-vector)
Macro.  Returns a behavior whose action level is controlled by a trigger that is callable by a plan sequencer.  The initial-values, condition, and motor-vector may be arbitrary signal expressions.   The condition and motor-vector expressions are evaluated within the scope of the arguments (Ph.D. talk for saying you can use the arguments in them).   The behavior will automatically drop its activation level when condition is true.


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.

Defining plans

Plans are compound actions.  They are callable in the same way as normal actions, however, they compile into finite state machines.

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.

(define-plan (action-name (arg-name   initial-value) ...)

  (initially-running? boolean)
  (trigger-when condition)
  (terminate-when condition)
  plan-expression ...)
Defines a new global variable, action-name, and binds it to transducer that implements a state-machine to run the plan-expressions in order.  The sequencer is idle until triggered.  Once triggered, it remains active until explicitly stopped or until it finishes the plan.
(plan ((arg-name   initial-value) ...)

  (initially-running? boolean)
  (trigger-when condition)
  (terminate-when condition)
  plan-expression ...)
Creates a transducer that implements a state-machine to run the plan-expressions in order.  The sequencer is idle until triggered.  Once triggered, it remains active until explicitly stopped or until it finishes the plan.


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.

Plan expressions

A plan fis specified by a plan expression, which can be any of the following:
 
(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.
(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
    consequent-plan)

(when signal-expression
  consequent-plan  ...)

(unless signal-expression
  consequent-plan  ...)

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)   ...)
  plan-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)

Miscellaneous

(call-when condition  action  args  ....)
Signal procedure.  Calls action with args whenever condition is true.
(call  action  args  ....)
Signal procedure.  An accumulator that calls action with args whenever it is driven with #t.  Probably useful only in rulesets and the on-activation/on-termination clauses of frames.
(in-progress? action)
Signal procedureAction can be a primitive action or a plan.   Returns true or if the action is running.
(done? action)
Signal procedureAction can be a primitive action or a plan.   Returns true or if the action is has terminated (or has never been started).

Internals

Ultimately, a call to a plan or discrete action is compiled into state machien state with the following object code:
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))
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.

Signal properties

Information about actions (plan and primitive actions) is stored in the following properties of the property list.  You can make other kinds of objects act like actions (i.e. be callable) by filling these in appropriately.
action?
True if the signal is an action, that is, if it is callable by the normal action calling sequence.
action-trigger
The action's trigger (a signal which is a shared register).
action-args
The action's a list of the registers that hold the action's arguments, in order.

Low-level procedures

(action? signal)
Procedure.  Returns #t if the signal is an action.
(assert-action! signal)
Procedure.  Throws an error is signal is not an action.   Otherwise returns without doing anything.
(action-trigger action)
Procedure.  Returns the trigger register of action.
(action-args action)
Procedure.  Returns the argument registers of action.