behaviorEngine
(For high level concepts about how to use Lua to write scripted tasks, please see VR-Forces User's Guide.)
This package implements a behavior engine aimed at supporting unit behaviors, or "commands." These commands are invoked and executed the same way all Lua task scripts are.
This file should be included in a behavior script with
require "behaviorEngine"
It provides several functions and implements the standard callbacks init(), tick(), saveState(), and loadState(), so they are not needed in the behavior script.
Usage
A task script that uses the behavior engine will generally be organized as follows:
- Define global constants
- Define utility functions
- Define roles in a Role List.
- Define a top level behavior node, with nested behavior nodes, that together define the command.
- Call defineBehavior, passing the Role List and Top Node as arguments.
Role List
Roles are defined in tables and combined in a list (table). Each role has elements as follows:
- name
- The name of the role. This will be matched with a role string in a parallel action in the behavior.
- types
- A list (table) of strings that define object types. Subordinate entity types must match one of these strings to be assigned this role.
- isCritical
- (Optional, default false.) A Boolean which, if true, means that the behavior fails if no subordinate can be matched to this role; and the behavior fails if a parallel action for this role fails.
- maxInstances
- (Optional, default 1.) The maximum number of subordinates that can execute this role.
- scoreFn
- (Optional, default score is 0.) A function that takes three arguments: a subordinate object, the index of the subordinate in the _subordinates table, and a string containing the function of the subordinate. Returns a score: < 0 if the subordinate should not be assigned to the role; 0 if it should be assigned only when no + scoring subordinate is available; and a value > 0 otherwise that indicates the desirability of the subordinate for the role.
Behavior Node
A Behavior Node is either a primitive action or a module with "child" nodes. Different module types define different ways that children are selected and executed. A behavior is a tree of nodes with primitive actions at the leaves.
A node can be one of the following types:
- sequence
- parallel
- selector
- role
- loop
- task (leaf)
- conditional (leaf)
- job (leaf)
- sync (leaf)
- send (leaf)
- receive (leaf)
- query (leaf)
Behavior Node Definition
Each node is defined in a table with key value pair entries. Every behavior node can have the following entries:
- nodeType
- (Required) A string defining the type of the node. Must be one of the types listed above.
- name
- A string that is useful mostly in debugging output. Required for sync nodes; all sync nodes that are synchronized with each other must have the same name.
- initFn
- A function that takes a Node as an argument. No return value. Run after the node is created, but before any other function is called.
- startWhen
- A function that takes a Node as an argument. Returns a Boolean. When it returns true, node execution can begin.
- endWhen
- A function that takes a Node as an argument. Returns a Boolean. If this function is true, the node stops any running actions and ends (with success status).
- failWhen
- A function that takes a Node as an argument. Returns a Boolean. If this function is true, the node stops any running actions and ends with an fail status.
- exitFn
- A function that takes a Node as an argument. No return value. Run after the node finishes running, either with success or failure.
In addition, some nodes have special entries:
- children
- sequence, parallel, and selector nodes-- a table containing node definitions.
- child
- loop, role nodes -- a node definition.
- doWhile
- loop nodes-- a function that takes a Node as an argument. Returns a Boolean.
- successPolicy
- parallel nodes -- a string, either "one" or "all". How many children have to complete with success for the parallel node to finish with success. The default value is "one".
- letAllFinish
- parallel nodes -- boolean. True indicates that the parallel node will not be be finished until all children have finished. False indicates that the parallel node can finish as soon as its completion status has been determined. The default value is True.
- role
- role nodes -- a string that matches a role name in the role list.
- task
- task nodes -- A function that takes a Node as an argument. Returns an integer that is normally a VRF task handle, i.e. the return value of a vrf:sendTask() or similar function. A value of -1 indicates an immediate failure; a value of 0 indicates an immediate success; and any other value indicates that the VRF task is running.
- condition
- conditional nodes -- a function that takes a Node as an argument. Returns a Boolean.
- job
- job nodes -- a function that takes a Node as an argument. Returns a Lua Async Job object. (See Async Job documentation.)
- mode
- selector nodes -- a string, either "reactive" or "fallback". Indicates whether the selector attempts to tick all children every tick (to implement prioritized reactive behaviors) or whether it starts by ticking the currently running child node (to implement fallback behaviors).
- messageType
- send and receive nodes -- a string specifying the type of message to send or receive. Either an object message type, or "text" for plain text messages. Receive nodes can also use "all" to accept all message types. Defaults to "generic-table".
- sendTo
- send node -- can be one of three things:
- simObject
- string. A string must have the value "unit" or "subordinates".
- function. Function takes a node for an argument and must return a table of simObjects.
- messageFn
- send node -- a function returning either a table (for object messages) or a string (for text messages). Takes a node as an argument.
- sender
- receive node -- Optional. can be one of three things:
- simObject
- string. A string must have the value "unit" or "subordinates".
- function. Function takes a node for an argument and must return a table of simObjects.
- acceptFn
- receive node -- a function for processing messages returning true, false, or nil. Generally processes a message using the node.result() function. True makes the node return "success", false makes the node return "fail", and nil leaves the node "running".
- query
- query nodes -- A table that specifies the query. The table must have the following elements:
- title
- A string that will be the title of the query.
- message
- A string that will be the query itself.
- options
- A table whose items are strings. These are the answers from which the user may select.
- displayStyle
- An integer specifying how the options will be displayed. 0: as series of buttons; 1: radio buttons; 2: a combo box.
- timeOut
- query and receive nodes -- A time duration to wait for the user to respond to the query. If there is no response in this time, the query is canceled (and the node returns success).
A maximum time after the receive node is first ticked or has received a message before the next message must be received. If this time is exceeded, the node returns "fail".
Behavior Node Functions
Several entries in the behavior node definitions are functions that take a node as an argument. A node has several member functions that can be called to get context information. These functions are called with the node parameter as a reference, e.g. node.subordinate().- subordinate()
- Returns a simObject -- the subordinate running this node; or nil if this node isn't under a role node.
- instanceN()
- Returns an integer -- the instance number in the role (above this node).
- setRoleStateVar(varName, value)
- varName is the name of a variable (string), and value is any Lua value. Sets the given state variable in the environment that is private to this role and this instance of that role. (See State Variables, below).
- getRoleStateVar(varName)
- Given the state variable name (a string), returns the value of that variable in the role-instance environment
- status()
- The current status of the node, which is "created", "waiting", "running", "success", or "fail". Generally, behavior code should not need to access the status.
- result()
- Job, query and receive nodes only. The result object returned by the async job or query. For jobs, the result will be nil until the job completes and the node goes to "success" status. For queries, the result is -1 until the user answers, and then it is an integer corresponding to the choice made. The first choice is 0. Fo receive nodes, this function returns the message received.
State Variables
There is a global table for state variables. These global state variables are accessible to any Activity Block or Action in the behavior. They are saved in a scenario save. To set and access them, use the functions setStateVar() and getStateVar() (see below).
Under each role node, there are additional tables set up that are private to that role, and to the instance of that role. Functions in nodes in these parallel actions can set and access variables using the functions setRoleStateVar() and getRoleStateVar() (above).
Graphical Status Output
If the notification level of a unit is set to Debug, then the behavior engine will write out an html file that graphically describes the state of the behavior. This file is located in the bin64 directory and has the name behaviorStatus_entity name.html, where the entity name has spaces replaced with underscores. Html output, and the file name, can be controlled with the enableHtmlOutput and disableHtmlOutput functions, below.
You can view this file in a browser at any time and see the status of the behavior. The page must be reloaded manually to see updates.
The output is color coded as follows:
- Nodes have different background colors corresponding to their type. The type is also written in the node box along with the node name.
- The node border is gray if it has not been created yet; blue if it is running; dashed-blue if it is waiting (startWhen is false); dashed-green if it is finished with status success; and dashed-red if it is finished with status fail.
- Edges between nodes are bold blue if they point to a running child node.
- Edges are dashed if they point to nodes running in parallel. This is the case for children of parallel nodes, and parallel instances under role nodes.
- Conditional nodes are ovals unless they are directly under role nodes (and thus display the local state).
- The simulation time when the output was updated is displayed in the top center of the output.
The output graph is generated as a graphviz file using the DOT language. See https://graphviz.gitlab.io/about/ . The graphviz file is written to the bin64 directory as
Function List
| createObjectMap () | Creates a table that can have simObjects as keys. |
| defineCommand (args) | Define the behavior command. |
| getStateVar (variable) | A convenience function to get the value of a state variable. |
| isRoleCritical (roleName) | Indicates whether the given role has been defined as critical. |
| makeObjectMapFromTable (objectTable) | Makes an objectMap from an existing table that has SimObjects as keys. |
| roleForSubordinate (subordinate) | Returns the name of the role that the given subordinate has been assigned. |
| setStateVar (variable, value) | Sets the entry "variable" in checkpointState. |
| subordinatesAssignedToRole (roleName) | Provides a list of subordinates that have been assigned to the given role. |
Table List
| _subordinateFunctions | A table with subordinates as keys and subordinate functions (strings) as values. |
| _subordinates | A list of the subordinates of this entity. |
Functions
- createObjectMap ()
-
Creates a table that can have simObjects as keys. Since simObjects are USER_DATA, when used as keys, a normal table only works properly if a lookup is made using the same USER_DATA-- i.e., address in memory-- as the key. USER_DATA will change on every rewind or scenario load. However, if createObjectMap is used to create the table, then UUIDs will actually be used as the keys, and table lookups will work even after a scenario reload. The table will have a field __isObjectMap in it set to true to mark it for scenario saves.
Usage:
checkpointState.objectLocations = createObjectMap()
checkpointState.objectLocations[this] = this:getLocation3D()Return value:
- A table that expects to have simObjects as keys.
- defineCommand (args)
-
Define the behavior command.
Parameters:
-
args: is a table with two elements, args.subordinateRoles and args.topNode. The first argument is a Role List, i.e. a table containing role definitions. The second argument is the description of the top level activity block. Note that using Lua's "{}" notation, this function can be called with key value pairs. Returns true if successful, false otherwise.
Usage:
defineCommand{subordinateRoles = myRoles, topNode = commandDefinition} -
- getStateVar (variable)
-
A convenience function to get the value of a state variable.
Parameters:
-
variable: A string, the name of the variable.
Return value:
- checkpointState[variable]
-
- isRoleCritical (roleName)
-
Indicates whether the given role has been defined as critical.
Parameters:
-
roleName: (string) A role name.
Return value:
- true if the role is critical.
-
- makeObjectMapFromTable (objectTable)
-
Makes an objectMap from an existing table that has SimObjects as keys. Prints an error and returns nil if any key is not a SimObject.
Parameters:
-
objectTable: A table with SimObjects as keys.
Return value:
- An objectMap with the same contents as the objectTable.
-
- roleForSubordinate (subordinate)
-
Returns the name of the role that the given subordinate has been assigned. If the behavior hasn't been assigned a role, or if the entity running this behavior doesn't have subordinates, then nil is returned.
Parameters:
-
subordinate: (simObject) The subordinate of interest.
Return value:
- role (string) The role assigned to the subordinate
-
- setStateVar (variable, value)
-
Sets the entry "variable" in checkpointState. The calling code could do this directly; however, using this function causes an update to the output used for the html Inspector.
Parameters:
-
variable: A string, the name of the variable. -
value: The value to set.
-
- subordinatesAssignedToRole (roleName)
-
Provides a list of subordinates that have been assigned to the given role. The subordinates are listed in order; i.e. the first one will be assigned instance 1 of that role, the second to instance 2, etc.
Parameters:
-
roleName: (string) A role name.
Return value:
- List (table) of subordinates (simObjects) that have been assigned to the given role.
-
