Jump to the Clay Package Index
Nodes often have other nodes embedded within them (e.g. an avoid_obstacle node typically has a detect_obstacle node embedded within it). The embedding is specified at initialization time using the node's constructor. Here is an example of how we'd embed one node in another:
detect_obstacles = new va_Obstacles_r(abstract_robot); avoid_obstacles = new v_Avoid_va(2.0, 1.0, detect_obstacles);In this example, a detect_obstacles node is created using the va_Obstacles_r class (the va_Obstacle_r class knows how to query the robot hardware for information about obstacles). Next an avoid_obstacles node is generated by embedding the detect_obstacles node in a v_Avoid_va object. The lower-case letters before and after the node names refer to the input and output types of the node; these hints are helpful when configuring nodes. More on that later.
Note that the embedding provides for code re-use. We could, for instance, avoid robots instead by embedding detect_robots versus detect_obstacles in the v_Avoid_va node. It is also possible to re-use instantiated nodes by embedding them in several other nodes. In this next example, detect_obstacle is imbedded in an avoid_obstacle node and a swirl_obstacle node:
detect_obstacles = new va_Obstacles_r(abstract_robot); avoid_obstacles = new v_Avoid_va(2.0, 1.0, detect_obstacles); swirl_obstacles = new v_Swirl_va(2.0, 1.0, detect_obstacles, heading);
Nodes are combined or blended by embedding them in a blending node. v_StaticWeightedSum_va is an example blending node class. If you are familiar with motor schema theory, this type node is used for the "sum and normalize" step of schema and assemblage combining. It takes an array of Nodes and an array of weights as input at configuration time. At runtime, it multiplies the output of each embedded node by the associated weight or gain, then sums them. The following statements generate a new node, avoid_n_swirl, that is the average of its two embedded nodes:
avoid_n_swirl = new v_StaticWeightedSum_va(); avoid_n_swirl.embedded[0] = avoid_obstacles; avoid_n_swirl.weights[0] = 0.5; avoid_n_swirl.embedded[1] = swirl_obstacles; avoid_n_swirl.weights[1] = 0.5;
At each timestep the configuration is activated through a call to configuration.Value(). configuration.Value() implicitly activates any embedded nodes through calls to their Value() methods, and so a heirarchical top-down chain of calls is initiated. How do we avoid a computational explosion? Several mechanisms are included in Clay to address this potential problem.
First all the Value() methods take a timestamp as a parameter. They remember the last time they were called, and if the timestamp has not increased, they return the last computed value. Thus if a node is reused by embedding in several other nodes, it will only go to the trouble of computing its value once per timestep. To ensure this, all nodes call embedded nodes with the timestamp they were passed. The timestamp is never incremented by a node. It is set at the highest level by the object calling the configuration (either JavaBotHard or JavaBotSim).
Second, Clay includes some node types that select rather than blend. In a selection node, only one embedded sub-node is activated at a time, thus fan-out calls are eliminated.
As mentioned above, input types for a node are specified by the constructor declaration. The output type is specified by its Value() definition. You should implement your node by extending one of the following classes, based on the desired output type for your node:
{output type}_{name}_{embedded node 1 type}{embedded node 2 type}{...}The name is the name you give your node. Variations that provide slightly different outputs or use many instead of one input are distinguished by the output and embedded type prefix and suffixes. Note that a node can only have one output type. The types are given by one or two letter abbreviations as follows:
i | int |
ia | int[] |
d | double |
da | double[] |
v | Vec2 |
va | Vec2[] |
b | boolean |
ba | boolean[] |
s | Scalar |
sa | Scalar[] |
r | robot - only valid as an input. |