Copyright 2011,
Ralph Rönnquist.
The GORITE concept identifies two kinds of data aspects appearing in a goal execution:
A developer is in full control of the data modelling. In particular, the application will provide the particular Dynamic Context Data object to each goal execution, and it will refer freely to any Lexical Context Data within the task goals.
The following is a discussion about the nitty-gritty of Data, divided into three section. The first section discusses the Dynamic Context Data, and the different aspects of the GORITE Data class. The next section is devoted to Lexical Context Data, and how to interface this from goals. The final section discusses the GORITE Action class, which implements an abstraction for "performer actions".
The Dynamic Context Data is understood as a table of Named Data Elements, each holding some number of values. The Data object passed in to a goal execution will be passed along to all the goals involved, and each goal can access and add to that Data object. The same object is passed along through the whole goal execution, across all teams and performers involved, and they all will share in the task of maintaining the Dynamic Context Data object. To this end, there are a few main concerns to consider:
A Named Data Element is a holder of a value or a collection of values. It may be thought of as a push-down stack where the action to set a new value in fact pushes down the previous values and make it a multi-valued element. The values of a Data Element are also accessible as an array, where index 0 is the most recent assigment, index 1 the previous assignment, etc.
The push-down function can be turned off by setting the "goldfish" flag in the Dynamic Context Data object.
Named Data Elements are usually accessed by means of accessor methods in the Dynamic Context Data object; one of the following:
Object getValue(String name) Data setValue(String name,Object value) Data replaceValue(String name,Object value) Data restoreValue(String name,Object value)
Note that changing the value of a Named Data Element does not release the previous value, which rather is kept as a previous value. Applications should therefore be designed to avoid this, and rather use fields of Named Data Element values for that which changes a lot.
A more refined access to Data Elements are suported by the following suite of methods:
Data.Element create(String name) Data.Element find(String name) boolean hasElement(String name) boolean pending(String name) void ready(String name) int size(String name)
The above methods allow an application to create a Named Data Element without a value, and to investigate the element state. An element that is not created or not ready is considered pending, and it gets ready by getting a value, or explicitly being marked as ready.
The refined methods are needed for parallel intentions where one produces values for the other. In that case, the element would be created before the fork into parallel intentions so that they both can access a common data element. Thereafter the intentions may use the element readiness to synchronise between producer and consumer.
Further, to access past values or values of a multi-valued data, the application would typically use "create" to access the Data Element, and thereafter its "get(int i)" method to obtain the i:th value.
The Dynamic Context Data is linked in to Plan context processing by means of moving values between the Ref objects of a Plan context Query and the Data Elements named the same. The Data class includes a suite of utility methods for this, as follows:
void get(Query q,boolean keep) void get(Ref... refs) void get(Vector refs,boolean keep) void set(Query q) void set(Vector refs,boolean keep)The "get" methods transfer values from the Data Elements to the Ref objects, and the "set" methods do the opposite. The "keep" flag tells how the Data Element values are affected; a "get" action may consume the values or not, and a "set" action may replace the values or not.
Note that the application generally does not need to use any of these methods explicitly. The goal execution machinery uses the methods to accomplish the define from Plan context data transfers.
A Plan context is a Query which results in values assigned to its Ref objects to make the predicate true. Different such bindings to the Ref objects define different Plan variants, and when a Plan variant is choosen, its Ref object values are mapped into the Dynamic Context Data before the plan is executed as a goal. If the plan execution fails, the Data Elements concerned are restored to their previous state before a next Plan variant is attempted, or the orginating goal fails. If the plan succeeds, then the Data Elements retain their values for any subsequent goal execution.
The table of Named Data Elements is associated with the ongoing goal execution by using its trace name as "thread name". Any lookup of Data Elements always relate to the current goal execution, allowing parallel goal execution to have different Data Elements under the same name. The goal execution machinery takes care of forking the table of Named Data Elements as part of forming parallel intentions, and it takes care of joining the parallel tables as the executions join. The Data objects contains the following methods to support this function:
String setThreadName(String thread) void link(String thread) void fork(String name,int i,Goal.Instance instance) void join(String except, HashSeth, Goal.Instance instance) Data.Bindings getBindings() void dropBindings(String thread) Data.Element getLocalElement(String thread,String name)
The application should avoid using these methods explicitly.
The local table of Named Data Elements for a parallel intention will contain all data elements created on that intention. The Named Data Elements existing before the parallel intentions came up are shared between them. However, if a parallel intention sets a value for a data element, it will result in creating a local data element for the new value. That intention will see the new value, while other parallel intentions will only see the prior value.
On the other hand, if a parallel intention replaces the value of a shared data element, then that will affect the shared data element rather than creating a local data element.
The Data object also contains a rendezvous type synchronization for goal execution, to allow the execution thread to suspend at a Java level until it should be woken up by a triggering event. The following methods are used for this purpose:
void setFlag() void waitFlag()
The goal execution machinery will use the waitFlag method when it finds that all intentions are blocked. This suspends the Java thread in waiting for a notification, which is done by the setFlag method. Thus, the application code relating to the event should include a call to setFlag so as to ensure that any intention observing the event will come into play.
By lexical context data, we refer to data structures accessible to a goals execute method other than via the Data object passed in to it. These data structures represent "performer beliefs", and they contain the situation state of the application.
There are few, if any, limitations on how the lexical context data is designed. Generally all access and manipulation is done by application code from within the execute method of goals, with the only exception being any interfacing from Plan context. The following is an illustration of using a Lexical Context Data.
public Befriending extends Capability {
// Keeps track of current friends
public HashSet<String> friends = new HashSet<String>();
public Befriending() {
// In: who Out: -
addGoal( new Goal( "is friend?" ) {
public States execute(Data d) {
String who = (String) d.getValue( "who" );
if ( friends.contains( who ) )
return PASSED;
return FAILED;
}
} );
// In: who Out: -
addGoal( new Goal( "remember friend" ) {
public States execute(Data d) {
String who = (String) d.getValue( "who" );
friends.add( who );
return PASSED;
}
} );
// In: - Out: who*
addGoal( new Goal( "who all friends are" ) {
public States execute(Data d) {
for ( Iterator<String> i = friends.iterator(); i.hasNext(); ) {
d.setValue( "who", i.next() );
}
return PASSED;
}
} );
}
}
A Plan context is a Query, which represents a predicate over the situation state. The Query includes some Ref objects, and as a predicate, it is true for particular value assignments to these Ref objects. Procedurally, the execution machinery will call the "next" method of the Query repeatedly, asking it to change the Ref object values to new combinations that make the predicate true.
GORITE includes a Relation class that can be used to implement relational data models. The Relation class has a get method, which returns a Query and is suitable for use in Plan context expressions.
GORITE includes an Action class which is intended for modelling of "actions" that performers can do. This is suitable e.g. for equipment modelling, as illustrated by the code outline below, which would model a robot arm (a Puma robot).
public class Puma extends Performer {
Action loadA = new Action( "load A", "hw" ) {
public States execute(
boolean reentry,Data.Element [] ins,Data.Element [] outs) {
// makes the Puma to pick an A part and place
// in the carrier at the loading station
...
}
}
Action loadB = new Action( "load B", "hw" ) {
public States execute(
boolean reentry,Data.Element [] ins,Data.Element [] outs) {
// makes the Puma to pick a B part and place on top of an
// A part in the carrier at the loading station
...
}
}
Action unload = new Action( "unload", "hw" ) {
public States execute(
boolean reentry,Data.Element [] ins,Data.Element [] outs) {
// makes the Puma to unload the assembly from the carrier
// unit at the loading station.
...
}
}
public Puma(String name) {
super( name );
addGoal( loadA.create( null, null ) );
addGoal( loadB.create( null, null ) );
addGoal( unload.create( null, null ) );
}
}
The Puma performer includes three Action instances, to represent the three kinds of action it can perform: to load a type A part from the input conveyor into the carrier at the loading station, to load a type B part (onto the type A part) from the input conveyor into the carrier at the loading station, and to unload an AB assembly from the loading station to the output conveyor. By the modelling, all action goals are associated with the "hw" todo group for the performer, which renders the effect that they are serialised; i.e., the performer will complete each action before attending to the next.
We note that the execute method for an Action has a "reentry" flag, which tells whether the invocation is an initial goal invocation, or a subsequent invocation following a BLOCKED or STOPPED method return. In other words, the execute method may return BLOCKED or STOPPED while in progress similar to a goal execute method, and for the same reason: that the method is waiting for something "external" before it can progress to PASSED or FAILED state. If that external condition is expected to trigger the goal execution (as discussed above), then the execute method should return BLOCKED rather than STOPPED. However, if the external condition does not trigger goal execution, then this method would return STOPPED. In that case the goal execution will not block, but will stay in a busy-loop that repeatedly, with some regularity, reenters the action execution.
Obviously, a well-behaved application will always use triggering.
As illustrated in the outline above, the Action.create method is used in order to create a goal object to be associated with an action. This goal object may be told a collection of input data elements that the action requires, and a collection of output data elements that the action produces. These are given as null in the example, which means that the action goals don't use or produce data elements. An action goal with input elements will block until all input elements are ready, and any output elements named will be marked as ready when the action finishes.