[PREVIOUS] [NEXT] [UP]

Containers

In this chapter you will become acquainted with the container unit to encapsulate a circuit as a new unit.

How to create a container

A container is created like any other NST unit: drag the container icon from the folder window into the circuit window and specify the desired interface (i.e., the input and output connectors that the container shall have).

The left half of the dialog window allows you to specify up to 30 input connectors. Each line in this half which starts with the leftmost button set to "inp" specifies the first pin (or scalar pin group, cf. below) of a new connector. If the leftmost button is instead set to "-", the line specifies an additional pin (or scalar pin group) for the current connector.

The pin type is selected with the next button, its dimension with the next input field. If the type is float and the dimension>1, then the entry will specify an entire group of consecutive scalar float pins. If the dimension is <0, this is treated as the specification of a float[] vector pin whose dimension is the absolute value of the entered (negative) dimension value.

In all other cases, the entry will specify a single (vector) pin of the given dimension (with one of the types float[], int[] or char[] for float, int or byte vector). If the vector dimension is 0 in one of these three cases, the pin will be a dynamic vector pin.

Finally, "string" and "special" select the string pin type (with the dimension specifying an initial minimal size, which, however, will be dynamically expanded as required [and always be rounded to a multiple of an internally used block size]) and the user-defined pin type (this will ignore the dimension).

In an analogous fashion, the right side allows to specify the output connectors. After "OK" a container unit according to the given specification is made. A second parameter dialog allows you to choose a label for the created unit. Create some example containers with different types of connectors!

How to modify a container

To modify a container, select its 'm' field. You will get an almost-copy of the container's create dialog box. However, at the bottom there are some additional buttons to allow you to change the container's label ("relabel") or to insert new or delete old connector or pin entries without having to re-type the part below the insertion/deletion point. To delete a particular pin or connector, select its dimension entry with the mouse pointer (use left mouse button), the select button "DelPin" or "DelFld". To insert a new pin or connector, first select the dimension entry directly below the desired insertion point, then select "InsPin" or "InsFld" to create a gap in which you may enter your specification. When a deleted or to-be-modified pin is connected to wires, the change may be refused (in this case, you first have to disconnect the pin before you can make the change). Note also that there is no CANCEL option in this case: you can only accept your changes or undo them manually!

How to use a container

The purpose of a container is to "encapsulate" a visual program so that it appears as a single object (with input and output connectors) from the "outside". Therefore, each container has a 'o' symbol which you can click at to "open" the container. The circuit window will then display the interior of the container, with any connectors of the container appearing at the left (for inputs) or the right (for outputs) side. Here is an example#1. (it draws the famous "Feigenbaum graph", which is the attractor of the logistic equation x(t+1)=a*x(t)*(1-x(t)) as a function of the "gain parameter" a). To get back to the outer level, press the right mouse button. When you are inside the container, any newly created NST units will become "subunits" of the container, i.e., will be located inside. Inputs and outputs of such subunits can be made accessible from the outer level by connecting them to suitable input or output connectors of the enclosing container (following the same type matching rules as explained previously with the only change that now inputs can only be connected with inputs, and outputs only with outputs). Executing a container has the effect of executing all of its subunits (the container itself has no operations -- except for possible type conversions at its connectors -- associated with it). This is the basis for structuring large visual programs in a hierarchical manner.
 

How to use the return_unit

The return_unit provides a construct similar to a (conditional) return statement for a subprogram: when a return_unit is inside a container AND the return_unit's input is non-zero when it is executed, execution will jump back to the next outer level and continues with the first unit after the container that encloses the return_unit (if the container is the last unit in another container, execution jumps again to the next outer level, and so on, until a next unit is found, or the current "Step" is completed).
 

How to call named subunits of a container

You can call (e.g., with the use_method unit) named subunits of a container "from outside", if the enclosing container has a name, too. In this case, the call requires that the two names are concatenated (with a colon ':' as separator) into a "path name", such as "mycontainer:mysubunit". This scheme generalizes to deeper calls into multiply nested named containers. Here is an example#2, in which a named container "foo" has a named subunit "foo" (which is an output_window instance), and a named container "bar", containing another named subunit "deep" (again an output_window instance) one nesting level deeper. The other three units at the top level are two use_method units and a use_named unit that call the subunits "foo:bar" and "foo:bar:box" by their path names (click at their 'x'-field to see the effect).

Note that also the use_method unit automatically "mirrors" the interface of the referenced subunit. In the present case, we do not wish to change any of the current inputs of the referenced units, therefore, the use_method units are instantiated with transport of input data restricted to "connected inputs only" (the inputs at our use_method units are unconnected, no data will be transported, as desired). The use_named unit comes without any connectors, so we don't need to care about suppressing data transport (if we wish to have data transport, we must specify the to-be-accessed connectors in the use_named creation dialog window).

Class containers

A container with a collection of named subunits with related functions resembles in many ways the concept of a class: each subunit offers a particular "method" that can be invoked via the unit's path name from outside. Therefore, in NST such a construction is called a "class container" and the subunits are referred to as its "method subunits" . Usually, it is not desireable that execution of the class container executes all of its methods; therefore, a class container usually contains as its first subunit a return_unit that protects
the remaining method subunits from being executed with the class container (they are then only executable from outside via their path names, by means of a use_method, use_named or suitably programmed prog_unit).

Here is an example#3 with a class container named "vec". It offers three methods: "vec:plot" plots a given vector in graphics window that will open when the method subunit is executed. "vec:print" will show the values of a given vector in a window that will open when the method subunit is executed, and finally "vec:end" will close any window(s) opened by any of the other two methods. The first method is implemented with three units inside a container unit, the second method is just the print_vec unit, and the third method is the ctrl_op unit that sends -- when invoked -- a control call to the other two units to close their windows, if they are open. To test the example, the top-level circuit contains three use_named units to invoke the methods and a rnd_gen instance as an example data source.
(click at the 'x'-fields of the method invokers to see the effect).

How to create class containers with the prog_unit

A particularly simple way to make a class container is offered by the prog_unit: whenever you declare in the prog_unit code a parameterles routine with the non-standard "return type" public (which has the same semantics as "void"), the prog_unit will become a class container with each such declared routine appearing as a method subunit (the protective return_unit is automatic in this case and will not be shown by Neo). Public routines can have static variables declared with the special keywords INP and OUT (which have the same semantics as "static"). Such variables will then appear as input or output connectors for the associated method subunit, analogously as for the main program. Example definition of a "mini-method" with two input and one output connector, each of a
single float:
      ...
      public sum() {
           INP float x; INP float y;  OUT float z;
           z = x+y;
      }
      ...
Since all public routines inside a prog_unit instance will share any global variables, this is a very convenient scheme for the rapid prototyping of class containers with methods that operate on a shared set of data. Many NST circuits are structured in this way: one or a few prog_units implement the main methods operating on one or a few central data structures (held in the prog_unit instance(s)). The remaining "machinery" (visualization and GUI units) are assembled around this functional core, using use_method or use_named units to invoke the methods implemented in the class containers. Example#4 illustrates this technique.

Later, when a compiled C or C++ implementation of the same class container is desired, NST offers the necessary means to wrap a corresponding C or C++ class suitably.

How to turn containers into operators by means of virtual units

We have already seen how to turn a prog_unit into an operator unit that acts on a number of operands, using the exec_opnds() function. The virtual_for_op unit offers a similar facility for a container unit: when  a virtual_for_op is placed inside a container, the entire container will appear for the outside circuit like a for_op unit with an adjustable scope rectangle. The operands in that scope rectangle (which are successor units to the container unit) will be executed at the moment when the virtual_for_op becomes executed inside the container.  Moreover, the virtual_for_op offers inside the container a second scope rectangle, where it can have additional operand units (this time its immediate successors inside the container). When the virtual_for_op is executed, it will first execute all of its direct
(inside the container) operands, then the operands in the outside scope (the successors to its enclosing container). This will happen
for each iteration, with the number of iterations specified in an analogous manner as for the usual for_op. Here is a simple example#5 where you can try out the possibilities.

There can be more than a single virtual_for_op inside a container, but then one should take care that the number of outer operands is for all virtual_for_ops the same (this is currently not automatically enforced and the scope rectangle will show only the scope of one of the virtual_for_ops).

The virtual_unit  is similar to the virtual_for_op, however, it just takes a single operand (identified by a positional parameter, with
values 1,2,.. selecting successor positions, and -1,-2,... selecting predecessor positions relative to the enclosing container) and executes it only once for each execution of the virtual_unit. Data exchange with the referenced unit can be made by specifying the interface positions of the connectors or pins to be accessed (this is similar as for the use_named unit, but the target unit need not be named). The action is true "operand style", i.e., there will be no double execution from a Step command.

One attractive use of virtual_unit's is the implementation of various "inspector" operators: these are just put in front of another unit to display some information about the operand (which, of course, must be of a type that can be handled by the inspector operator).


[PREVIOUS] [NEXT] [UP]