Bryan Horling

Scripting Overview Construction Examples Development

Simulation Scripting

The simulator, and the agents connected to it to a certain extent, support a wide range of scripting options. A script is meant to help both the scenario designer and tester easily simulate sophisticated behaviors and responses, as well as automate the execution of those scenarios. With the scripting services provided, you may do something as mundane as automatically start the simulation once certain agents have connected, or as complicated as remotely modify the internal beliefs of a specific agent.

This document is meant to offer a description of how our scripting system functions, both from a usage and development standpoint. We will start with an overview of the structure of the scripting system, and how it behaves. Following this we will cover how scripts are defined and used, along with a brief list of many of the existing scripting components. The last part of the paper will go over the technical details needed to extend the scripting system to suit your needs (if it doesn't already).

Scripting Environment Overview

All script activity is controlled by the simulation controller. Scripts are gathered from two locations: a local configuration file and from any connecting agent. The structure for the scripts (defined in the next section) is the same, regardless of the source.

The source of local script definitions is indicated by the SCRIPTS tag which can be placed in the simulator.cfg file (e.g. SCRIPTS: scripts.cfg). The scripts file is read once upon start-up, and all scripts are imported and installed into the current simulation session.

On the agent side in JAF is a Scripter component, which reads from a similar file located in the extended entry data file (indicated by the ScriptData property). This file is read on agent startup, and the information automatically transferred to the simulator when connected. While the config file is read just once, scripts can be added (or removed) at any time by other components in the agent. So, if you want dynamic scripting to occur, just add that capability to your problem solver. The submission format (for Scripter.addScript) is the same as the normal script text file, just stuffed into a StringBuffer.

Grammar event scripting is handled in a similar manner as regular agent event scripting. The current objective TAEMS structure is first searched for Reactions. As reactions are found, their activation traits and reactions are encoded into the simulator scripting format, and are then added with the Scripter component as any script would be. Thus, dynamic task structures (as discussed here) are accomplished by converting them to the simulator scripting format. The reactions to these scripts are handled by the simulator, which communicates to the agent the ID of a reaction when it is to take place. When the ID is received, task structure regeneration should take place as normal. Reaction conversion, addition and detection are all handled automatically by the AsggOrgDesign component.

Once in the simulation environment, scripts are periodically checked to determine if they should be started. Right now, scripts are checked during each of three defined phases: PERIODIC, PREEVENTS, and POSTEVENTS. These correspond to the time when the simulator is paused, before the event queue is processed and after the event queue is processed, respectively. If a script's activation requirements are found to be true, the script is immediately activated.

Script Construction

Each script in the simulation environment is composed of three parts: Lets look at a simple script to see how these work in practice:
Script
AndScript
Assertions
Time > 5
ExecuteEventActive method-4
Reactions
QuitSim
This script is an instance of AndScript, which specifies that the results of the assertions are ANDed together to determine if the script should fire. In plain terms, this means that all the assertions must be true for a reaction to be produced. The script has two assertions: one that says that the current time must be greater than five, and another that says that method-4 must be active. So, we now know that this script will fire any time after pulse five when method-4 is being executed. The result of this script, as specified with the QuitSim Reaction, is fairly straightforward - the simulation will end. One might use this kind of script when testing some sort of behavioral technique or gathering data. The designer in this case knew that the quality being studied or quantified would have taken place by this time, so the simulator can automatically quit.

Thus, each script is composed of a number of parts which say when it should fire, and other parts that specify what should happen when it does fire. While simple in construction, this technique actually allows for quite a bit of flexibility and versatility, when combined with a rich set of Assertions and Reactions. It is not only the type of element in the script which has bearing on its behavior, however. As seen above, we used general classes of component (Time and ExecuteEventActive) and specialized them to our needs by adding instance variables (> 5 and method-4). All the components, Script, Assertion and Reaction, are customizable in this way. Depending on their structure and function, they may take external data given at runtime which affects the way they will behave. You can think of this external data as being parameters to a function, or items in a configuration file. In generating our scripting components, we have tried to enforce a convention that configuration parameters be enumerated and described in the header comments on the object in question, so if you look at the API documents for the scripting objects, you should be able to determine how each object may be customized to your needs.

The file format used both in the configuration files and as a parameter to the Scripter's addScript function is fairly simple. Each element in the file is prepended with a type name, indicating what sort of item is defined on that line. A Script line will begin with "Script", an Assertion with "Assertion", and so forth. Individual items in a line are separated by commas (",").

A script is started by specifying a Script class name (e.g. AndScript, OrScript) to give the script assertion checking properties. The script may be given a textual name, which can be used to identify it later if needed, and any parameters the designer chooses to specify. Here's an example:

Script, AndScript, My First Script, Fire:10

In the above line, we have specified an AndScript, named "My First Script", with a runtime parameter named "Fire" having the value of ten. This latter property specifies that the script may only fire ten times during the lifetime of the script. As you can see, items in the data/parameter part of the line are specified using a Name:Value pair, so the physical ordering of the parameters is irrelevant.

Following the script definition is a list of one or more assertions, one per line. Each assertion is specified using its class name, followed by parameters encoded in the same format as was used in the Script line. Here's a couple examples:

Assertion, Time, Op: >, Value:5
Assertion, ExecuteEventActive, Method:method-4

Here we see the two assertions that we discussed earlier - a Time > 5 assertion and another which is true when method-4 is active. Nothing fancy here.

Reactions are specified in the same way as Assertions - a class name followed by data. Here's the simple QuitSim reaction we used above (it uses no external parameters):

Reaction, QuitSim

So, the four lines given above encode the script described in the beginning of this section. Following the rules given here, you can make your script as complex as the situation calls for, by just using an appropriate combination of Script, Assertion and Reaction objects.

Look at this link to see an example script config file. Note this file isn't meant to do anything constructive; it just serves as sort of a tangible example of what a script config file might look like.

Some Example Objects

In this section, we'll briefly go over some of the scripting objects which have already been written. Many of the existing objects are covered here, but not all. For a more complete listing, and more details on an object's behavior and parameters, see the API documentation for the MASS simulator.

Scripts

AndScript
The semantics of this script dictate that all assertions must be true for the reactions to be realized. It also supports a ShortCircuit flag which controls weather or not a short-circuit AND is used, and a Fire variable controlling the number of times the script may fire. Using a short-circuit check will allow you to decrease script checking time through clever ordering of assertions (i.e. an early false will halt further checking).
OrScript
This is essentially the same as AndScript, with the exception that the script will fire if any one of the assertions is true.
ExpressionScript
This script type allows the user to specify (in the Expression parameter) an arbitrary boolean expressions showing how the assertions are to be checked. If the expression as indicated evaluates to true, the script will fire. See the API documentation for this script for more details on its usage.

Assertions

True
Trivial assertion which is always true
False
I'll give you three guesses what this does.
Time
This assertion returns true if the simulator time matches with the supplied operator and value (e.g. ">= 2" or "!= 10", etc.)
Timer
Allows the user to specify a range of time, optionally delayed and/or repeating, during which the assertion is true.
AgentConnected
Will return true if an agent with a matching name is connected to the simulator.
ExecuteEventActive
As seen above, this will be true if a matching method is currently executing.
ExecuteEventStarting or ExecuteEventCompleting
Returns true if the method will start or terminate during the current time slot.
ExecuteEventDimension
This assertion allows the script designer to react to the behavior of a given method. Using this, one can probe a method's current quality, cost, duration and resource usage using all the standard binary operators.
Probability
This assertion will be true a specified percentage of the time. This can be used to add an element of randomness to your scripts.
ResourceLevel
The current level of a resource is checked here. You can also explicitly determine if the level is at its maximum or minimum value.
ScriptPulseType
This will be true if the assertion is being checked during any of the specified pulse types. This may be used to ensure that a script will only fire before or after the event queue has been processed, for instance.
ScriptStarted
This assertion determines if the given script (matching the script's textual name) has been activated since it was last checked.
SensorQuery
A simulator or agent defined sensor may be queried with this assertion.

Reactions

Write
This trivial reaction simply writes out a line of text using the simulator's logging facilities.
AdjustResourceLevel or SetResourceLevel
These reactions allow a script to adjust any resource's current level by a certain amount over time or set it to a certain value immediately.
SetResourceMax or SetResourceMin
Enables the script to specify new maximum or minimum values for a given resource.
DisconnectAgent
Disconnects an agent from the simulator.
GrammarEvent
The functional element of the dynamic task structure mechanism, this sends a message to an agent indicating that a particular grammar reaction has been caused.
StartScript or ResetScript
Starts or resets a given script in the simulator, by calling its start or reset methods.
ResetSim, RunSim, StepSim, StopSim, QuitSim
These reactions control the behavior of the simulator as their name indicates (they generally correspond to the control buttons on the simulator display).
SendMessage
Sends an arbitrary KQML message to an agent connected to the simulator.
SetSensorDeviation
Adjusts a simulator-controlled sensor deviation description.

Developing New Script Objects

Note that you only have to read this section if you've gone over all the above material and the complete list of existing scripting objects, and decided that you still can't create a script that meets your needs.

As you might have deduced from the structure given above, the scripting mechanism was designed to be very modular and extensible, thus allowing users to develop new script objects if the existing set fail to meet a particular need. Any of the three scripting object types can be easily derived to produce new behaviors or functionality by following the construction guidelines that will be covered in this section.

All three types of objects share a few features in common. During construction, they are passed the raw textual description of the parameters, exactly as encoded in the configuration file. This data is parsed by a common method, and the resulting data stored in a Hashtable accessible by the getData() function in the object (parameters are stored in String form, keyed by their property names). This process is done automatically. Each object also has an init() method, called just after construction, during which parameter conversion is typically performed (e.g. converting the String data in the Hashtable to a more suitable representation). The JAF function agent.simplest.State#reTypeProperty() may be useful here. As stated earlier, we expect that users defining new scripting objects will enumerate and describe any parameters used by their objects in the object's header comments, using the Javadoc comment style.

Script has essentially two functions which are important to customize: pulse() and reset(). The first, pulse, is called by the script engine periodically to allow the Script to determine if it should fire. Thus, the primary purpose of pulse is to check its assertions and determine if the script should fire. A script may fire with the start() method. The reset method is responsible for resetting any internal structures maintained by the Script back to their original values when it is reset.

The Assertion structure is very simple. The only method of interest here is check(). The check() method should determine, by whatever means necessary, weather or not the assertion is true. If the assertion is false, or an error occurs, the check function should return false.

As with Assertion, Reaction also has just one method to define in derived classes: realize(). This method is responsible for producing the behaviors defined by the Reaction. Realize should return true if the action was successfully performed, or false if an error occurred.

The structure of the objects as shown is quite simple. The only complicated part of creating new objects is how to hook your Assertions and Reactions into the simulation environment - you may need to know how you muddle around in the guts of the simulator to achieve your goals. This may be quite daunting to users, especially if you do not have the simulator source code available to you (but feel free to write us if you need guidance with your object). One alternative is to make use of a preexisting object to produce your behavior. For instance, the GrammarEvent object is a trivial subclass of SendMessage which just fills in the appropriate parameters with the data needed to send a GrammarEvent. For some types of objects this may be the easiest method of construction.