AnsweredAssumed Answered

VRf-VEE_Control

Question asked by VRFuser on Jan 27, 1998
send q VRf-VEE_Control ww gandmmcc@ridgecrest.ca.us vrf

from: Greg Goebel / HP-MXD
      gvg@lvld.hp.com / 800-452-4844
      website:  ftp://fcext3.external.hp.com/dist/mxd/index.html
to:   VRF-Grant McCormick
date: Wednesday, 28 January 1998 1316 MST

> Return-Path: <gandmmcc@ridgecrest.ca.us>
> From: Grant McCormick <gandmmcc@ridgecrest.ca.us>
>
> to:      vrf
> name: Grant McCormick
> email: tel_telemetry-octagon@echo.chinalake.navy.mil
>
> 1. My program needs a "back" button in case the user needs to go back to
> the previous screen.
>     A trivial thing for most VEE programmers I'm sure. Please help.
>     also
> 2. Is it possible to have multiple "ok" buttons in the same User Object
> Panel? I try to have
>     multiple buttons, but pressing one "ok" effects the other "ok"
> buttons.
>
> thank you for helping

Sir:

Sounds like you're at the point where a core dump on VEE control principles
might be a good place to start.

[%%] regards -- gvg

--------------------------------- cut here ----------------------------------



[12.0] VEE Control Flow

* VEE control flow is a tricky subject; the propagation rules are a little
devious -- though when analyzed are usually perfectly logical -- and the only
way to get a realistic grasp of them is through walking through examples.

It is difficult for someone who is experienced in working with VEE to
remember how tricky the learning curve is; concepts that seem difficult to
the newcomer suddenly seem perfectly familiar, are taken for granted, and the
difficulty is forgotten.

VEE control flow can be understood by analogy with a number of different
concepts:  it has some similarities to digital logic, is in some ways like
the relay-ladder logic used in industrial controllers, is even to a degree
like laying out track for a model train set; but most of all it is still has
much in common with a normal sequential programming language -- in particular
in that (despite any initial impression that VEE might give) you can't do two
different things at the same time, and that operations will follow each other
in a sequence (though one which is not necessarily as deterministic as in a
conventional language).

To get started, here are some simple rules of VEE style:

% Construct a program whose control flow is clear and flows down in a
   hierarchical fashion.  If you can visualize the control flow easily, you
   normally won't have problems.

% If the execution order between objects is important but ambiguous, use
   sequence-in and sequence-out connections to ensure the execution order.
   And remember that control pins are "asynchronous" -- meaning,
   essentially, that they don't follow the rules for VEE propagation flow.

% Avoid feedback loops.  Novice VEE programmers will use feedback loops to
   accomplish iterations, only to quickly find out such constructs don't work
   the way they "should", and in fact lead to lockups and other problems.

% Don't use Start Buttons.  They are useful for debugging but they are
   useless in Runtime programs (as of VEE 4.X).

% Avoid parallel paths fed by a looping object.  It is difficult to
   determine which path will be executed, or if a path is executed at all.

One of the common remarks made by users having ghostly problems with VEE is:
"I deleted the object, replaced it with a new one of the same type, and then
it worked."

This is an strong indication of an error in control-flow design.  The reason
such a substitution can make the program work is because VEE's execution
order depends on the order in which objects were added if you haven't
explicitly forced the execution to operate in a specific sequence.

Similarly, some programs that worked perfectly well in VEE 3.X don't work at
all in VEE 4.x because the execution order has changed (while remaining legal
under VEE propagation rules ... there are also some peculiarities to VEE
execution under VEE 4.x compiled mode that are different from VEE 3.x).

Sometimes the sequence of execution between different objects doesn't matter;
sometimes it matters a great deal.  This chapter will provide you with
information on how to make that judgement and otherwise deal with
control-flow problems.

[%%]


[12.1] ELEMENTARY CONTROL FLOW

* Writing VEE programs requires a grasp of how the core control constructs
work in controlling programs.  The simplest control is in generating a simple
loop.  If you want to generate a simple count, you can use a For Count
object:

                        +----------------------+
                        | Logging Alphanumeric |
                        +----------------------+
   +---------------+    | 6                    |
   | For Count: 10 +--->| 7                    |
   +---------------+    | 8                    |
                        | 9                    |
                        +----------------------+

Note that For Count counts from 0 to 9, not 1 to 10.  Counter objects can be
nested, of course:

  +---------------+
  | For Count: 10 +---+
  +-------+-------+   |
          |           |
          |   +-------+-------+   +---------+
          |   | For Count: 10 +-->| Counter +-->
          |   +---------------+   +---------+
          |
      +---+---+
      | Beep  |
      +-------+

Note how the outer-loop counter generates a "beep" through its sequence-out
pin when it's done.  This illustrates an important feature of how such
looping objects work:  they don't generate a sequence-out pulse until
*after* everything they are driving has executed.

* If you want to generate an endless loop, you can use an Until Break object:

   +-------+
   | Until |
   | Break +----+
   +-------+    |        +-----------------------------+
                |        |        AlphaNumeric         |
            +---+---+    +-----------------------------+
            | now() +--->|   Sun 19/Feb/1995 13:19:16  |
            +-------+    +--------------+--------------+
                                        |
                                  +-----+-----+
                                  | Delay: 1  |
                                  +-----------+

I add a delay here so the AlphaNumeric won't update more often than once a
second; actually, an On Cycle would allow you to generate a container to
drive the "now()" object with any delay that you like and so would be
preferred in this case ... but I will be developing the use of Until Break in
following examples, and might as well be consistent.

You can set the time format though the "Edit Properties" entry in the
AlphaNumeric's object menu.  The number formats include a set of time formats
for date and time, date, or time.

But what if you want to bail out of this loop at some time?  This is a little
tricky.  One possible approach is to just add an OK button to drive a Break
object to break out of the loop:

   +-------+
   | Until |
   | Break +----+----------+
   +-------+    |          |        +-----------------------------+
                |          |        |        AlphaNumeric         |
                |      +---+---+    +-----------------------------+
                |      | now() +--->|   Sun 19/Feb/1995 13:19:16  |
                |      +-------+    +--------------+--------------+
                |                                  |
             +--+--+                         +-----+-----+
             | OK  +---+                     | Delay: 1  |
             +-----+   |                     +-----------+
                       |
                   +---+---+
                   | Break |
                   +-------+

However, the approach of using two parallel paths has a major drawback:
there is no guarantee that one path will be executed.  One path might be
executed all the time, or the other path might be executed all the time, or
execution might be balanced between the two.  There is nothing in the program
to determine what will happen.

This ambiguity would occasionally lead to troubles with VEE 3.X, but when VEE
4.0 came out, the troubles became blatant and obvious.  In reality, the same
effect can be obtained just by adding an OK Button and a Stop object as a
separate thread:

   +-------+           +------+
   | Until |           | Quit +------+
   | Break +--+        +------+      |
   +-------+  |                  +---+---+
              |                  | Stop  |
              |                  +-------+
              |
              |       +-----------------------------+ 
              |       |        AlphaNumeric         |
          +---+---+   +-----------------------------+
          | now() +-->|   Sun 19/Feb/1995 13:19:16  |
          +-------+   +--------------+--------------+
                                     |
                               +-----+-----+
                               | Delay: 1  |
                               +-----------+

But what if the program has other threads that can't be halted?  Then a
Toggle button can be wired into the program to cause it to break out:

   +-------+
   | Until |
   | Break +--+
   +-------+  |           +-------------------+
              |           |    If/Then/Else   |
         +----+----+      +---+--------+------+
      +->| Toggle  +--+-->| A | A == 1 | Then +---------------+
      |  +----+----+  |   +---+--------+------+               |
      +-------|-------+                                   +---+---+
              |                                           | Break |
              |        +-----------------------------+    +-------+
              |        |        AlphaNumeric         |
          +---+---+    +-----------------------------+
          | now() +--->|   Sun 19/Feb/1995 13:19:16  |
          +-------+    +--------------+--------------+
                                      |
                                +-----+-----+
                                | Delay: 1  |
                                +-----------+

The Toggle is wired back to its Reset pin.  If a user clicks on the Toggle
with a mouse, it is set to 1, and so the Break object is fired.  Otherwise,
control is passed on to the rest of the program.  (This particular trick is
used in examples in earlier chapters.)

The Toggle should be configured to initialize to a 0 value to ensure that the
program starts up correctly.  This program's actions are much more
predictable than those of the previous program.

* That said ... now let's mix things up a little.  Suppose you want to
generate a count over and over again; one user tried this trick:

   +-------+
   | Start |
   +---+---+
       |
       +--->+-----+
            | JCT +----+            +----------------------+
       +--->+-----+    |            | Logging Alphanumeric |
       |               |            +----------------------+
       |       +-------+-------+    | 6                    |
       |       | For Count: 10 +--->| 7                    |
       |       +-------+-------+    | 8                    |
       |               |            | 9                    |
       +---------------+            +----------------------+

However, this only counted from 0 to 9 once ... I was appalled at this since
I could take one look at it and know it wasn't likely to work; then I
realized I couldn't actually *say* why it wouldn't work.  It was just that
after having played with VEE for a long time I had a feel for what would work
and what wouldn't.  (This construct is actually illegal in VEE 4.X.)

What would work was of course the Until Break again:

   +-------+
   | Until |
   | Break +---+            +----------------------+
   +-------+   |            | Logging Alphanumeric |
               |            +----------------------+
       +-------+-------+    | 6                    |
       | For Count: 10 +--->| 7                    |
       +---------------+    | 8                    |
                            | 9                    |
                            +----------------------+

* Anyway, given these techniques I can now demonstrate how to create a
general architecture for interactive VEE programs.  Let's consider a simple
program where the user can select one of two actions or exit the program by
clicking on the appropriate Toggle buttons; this program has the form:

  +-------+
  | Until |
  | Break +---+
  +-------+   |          +--------------+
              |          | If/Then/Else |
          +---+---+      +-------+------+
       +->| Key 1 +--+-->| A==1  | Then +--+
       |  +---+---+  |   +-------+------+  |
       +------|------+                     |
              |                  +---------+----------+
              |                  |        Text        |
              |                  +--------------------+
              |                  | You pressed key 1! +--+
              |                  +--------------------+  |
              |                                          |
              |          +--------------+                |
              |          | If/Then/Else |                |
          +---+---+      +-------+------+                |
       +->| Key 2 +--+-->| A==1  | Then +--+             |
       |  +---+---+  |   +-------+------+  |             |
       +------|------+                     |             |
              |                  +---------+----------+  |
              |                  |        Text        |  +-->+-----+
              |                  +--------------------+      | JCT +--+
              |                  | You pressed key 2! +----->+-----+  |
              |                  +--------------------+               |
              |                                                       |
              |          +--------------+                             |
              |          | If/Then/Else |              +--------------+
          +---+---+      +-------+------+              |
       +->| Quit  +--+-->| A==1  | Then +--+           |  +--------------+
       |  +-------+  |   +-------+------+  |           |  | AlphaNumeric |
       +-------------+                     |           |  +--------------+
                                       +---+---+       +->|              |
                                       | Break |          +--------------+
                                       +-------+ 

The concept is simple:  each separate action is positioned in a ladder of
selections, and each action ends in a Next object to drive the next iteration
of the Until Break object.  You can add more actions as you need to.

Note the implication of this architecture:  the path that is executing has to
complete before another path can be executed.  If the path that is being
executed takes a long time to complete, you'll wait until it gets finished
before you do something else.

[%%]


[12.2] ADVANCED CONTROL FLOW

* It is not a great leap forward from the techniques outlined in the section
above to define how to handle more complicated VEE control tasks.

For an example, consider a VEE program that allows the user to select one of
several tasks, or have a task initiated by a service request (SRQ) from an
instrument.  The following program (see xsrqtest.vee for the source) does
this task:

   +-------+   +-----------+          +--------------+
   |  ID   |   |    Toggle |          | If/Then/Else |
   | 3478  |   |       +---+---+      +--------------+
   +---+---+   |    +->| Quit  +-+--->|     A==1     +--------+
       |       |    |  +---+---+ |    +--------------+        |
   +---+---+   |    +------|-----+                        +---+---+
   | Until |   |           |          +--------------+    | Break |
   | Break +---+    Toggle |          | If/Then/Else |    +-------+
   +-------+          +----+----+     +--------------+
                   +->| Task 1! +-+-->|     A==1     +--------+
                   |  +----+----+ |   +--------------+        |
                   +-------|------+                      +----+----+
                           |          +--------------+   | TASK 1! +--> T1
                    Toggle |          | If/Then/Else |   +---------+
                      +----+----+     +--------------+
                   +->| Task 2! +-+-->|     A==1     +--------+
                   |  +----+----+ |   +--------------+        |
                   +-------|------+                      +----+----+
                           |          +--------------+   | TASK 2! +--> T2
                           |          | If/Then/Else |   +---------+  
                    +------+------+   +--------------+  
    Interface Event | SRQ: hpib7  +-->|     A!=0     +-------+
                    +-------------+   +--------------+       |
                                                      +------+------+
                                         Device Event | Spoll: 3478 |
                                                      +------+------+
                                                             |
       T1  --->+-----+   +---------+                    +----+----+
       T2  --->| JCT +-->| Message |               Text |   SRQ!  +--> SRQ
       SRQ --->+-----+   |   Box   |                    +---------+
                         +---------+

The program is driven by an Until Break object; the Until Break object drives
the four parallel paths within the program -- controlled by three OK buttons
("Task 1", "Task 2", "Quit") and the Interface Event object ("SRQ:  hpib7")
-- and an outer loop that guarantees repeated execution of the paths.

The two paths defined by the "Task 1" and "Task 2" buttons simply display
appropriate text ("TASK 1!" and "TASK 2!") in the Message Box to the right,
while the path defined by the "Quit" button simply stops the program.

The interesting part of the program is the SRQ handling component, of course.
Note that a 3478 State Driver is used at the beginning of the program to set
a 3478 DMM's SRQ mask to 63; this allows you to assert an SRQ by pressing a
button on the front panel of the DMM.

This done, the path that handles the SRQ simply uses the Interface Event
object to wait for the SRQ; it is set as follows:

                   |
   +---------------+---------------+
   |           SRQ: hpib7          |
   +-----------------------+-------+
   |  Interface: [ hpib7 ] |       |
   |     Action: [NO WAIT] | event +-->
   |      Event: [  SRQ  ] |       |
   +-----------------------+-------+

When an SRQ occurs, the Interface Event object then fires off the Device
Event object to do a serial poll, which clears the SRQ on the 3478:

                     |
   +-----------------+------------------+
   |           SRQ: hpib7               |
   +---------------------------+--------+
   | Device: [ 3478 (hp3478) ] |        |
   |  Event: [     Spoll     ] | status +-->
   | Action: [    NO WAIT    ] |        |
   |   Mask: [      #H0      ] |        |
   +---------------------------+--------+

Since NO WAIT is set, this object does the serial poll and then proceeds
(firing off a text object to display that an SRQ has occurred); the mask
value is irrelevant.  (You can also do a WAIT SPOLL through a Direct I/O
object if desired.)

Operating the example is simple; you run it and click the "Task 1" or "Task
2" buttons to get the appropriate pop-up display -- then if you press the SRQ
button on the front of the 3478 , you get the "SRQ!" display output ... and
go back to perform another task.  The program stops when you click on the
"Quit" button.

* For another example along this line ... consider a user who wanted to
be able to execute one of three different UserFunctions -- FunctionA,
FunctionB, or FunctionC -- and then have his program continue to execute the
last selected action.  This could be done with the following program (see
xpgmfunc.vee for the source):

   +-------+
   | Until |                                +------+
   | Break +--+                             | Quit +--+
   +-------+  |                             +------+  |
              |                                       |
    +---------+--------+                          +---+---+
    | Execute Function +--+                       | Stop  |
    +------------------+  |                       +-------+
     OK Button            |                      
                  +-------+-------+
                  | Radio Buttons |   +-----------+  +-----------+
                  +---------------+   |  Formula  |  |           | 
                  | <*> FunctionB |   +-----------+  |   +-------+-------+
                  | < > FunctionB +-->| asText(A) +--|-->| Call Function |
                  | < > FunctionC |   +-----+-----+  |   +---------------+
                  +---------------+         |        |
                                            +--------+

This program calls three different functions -- FunctionA, FunctionB, and
FunctionC (all they do is pop up a message box indicating "Task A!", "Task
B!", or "Task C!").   The scheme is quite simple:  An OK button fires off
a Radio Buttons box that contains a list of the functions; the selected
entry is converted into text and then sent into a Call Function box.

* For another applied problem in control flow ... one of our HP support
people had a related problem where he wanted to generate a strip chart that
would be cleared every certain number of points, or whenever the user clicked
a button.  Nice idea, but doing it isn't intuitive.

The following program (see xclstrip.vee for the source) shows how it's
done:

   +-------+
   | Until +--+
   | Break |  |
   +-------+  |
              |
      +-------+-------+                                              |
      | Random Number +--------------------------------------------->|Trace 1]
      +-------+-------+                                              |
              |                                     +------+         |
              |                                     | Quit +--+      |
              |                                     +------+  |      |
              |                                               |      |
              |                                           +---+---+  |
              |                                           | Stop  |  |
              |                                           +-------+  |
      +-------|-------+                                              |
      |  +----+----+  |   +-----------------------------------+      |
      +->| Toggle  +--+   |           If/Then/Else            |      |
         +----+----+  |   +-------+--------------------+------+      |
      Reset   |       +-->|[A Any]|[(A==1) OR (B==100)]|[Then]+--+-->|[Clear]
              |           |       |                    |      |  |   |
     +--------+       +-->|[B Any]|                    |[Else]|  |
     |                |   +-------+--------------------+------+  |
     +-->+---------+  |                                          |
         | Counter +--+                                          |
     +-->+---------+                                             |
     | Clear                                                     |
     |                                                           |
     +-----------------------------------------------------------+

An Until Break object clocks the program; it triggers a sequence of random
numbers that are driven into the Strip Graph object (which is only shown as a
set of input pins at the right of the diagram).

The Strip Graph is reset every 100 times by a Counter driving an If/Then/Else
object; the output of the If/Then/Else is fed back to clear the Counter so
the count can begin over again.  It is also reset by the Toggle button. 

[%%]


[12.3] ERROR HANDLING

* One difficult concept to deal with in VEE is error handling -- that is, how
to perform an action and either repeat the action or continue after an error
occurs.

A simple way to show how to handle this is with a dialog box to represent an
action that can have different outcomes, as the following program (check
xerrdemo.vee for the source) shows:

   +-------+
   | Until |
   | Break +------------------------+
   +---+---+                        |
       |                        +---+---+
       |                        | Beep  |
       |                        +---+---+
       |                            |
       |       +--------------------+--------------------+
       |       |               Message Box               |
       |       +--------------------------------+--------+
       |       | Message [ I/O Error          ] | Abort  +-------------+
       |       | Symbol  [(!)] [ Exclamation  ] | Retry  +--+          |
       |       | Buttons [ Abort Retry Ignore ] | Ignore |  |          |
       |       | Default [       Abort        ] |        |  |          |
       |       +--------------------+-----------+--------+  |          |
       |                            |                       |          |
       |                        +---+---+               +---+---+  +---+---+
       +-------------+          | Break |               | Next  |  | Stop  |
                     |          +-------+               +-------+  +-------+
                     |
  +------------------+-----------------+
  |              Message Box           |
  +-------------------------------+----+
  | Message [ ALL DONE!         ] |    |
  | Symbol  [(i)] [ Information ] | OK |
  | Buttons [         OK        ] |    |
  | Default [         OK        ] |    |
  +-------------------------------+----+

This program pops up a Message Box to ask if you want to "Abort Retry
Ignore"; if you "Abort", a Stop object is executed to stop the program; if
you "Retry", you get a "Next" loop to try again; if you "Ignore", you get a
"Break" that turns off the Until Break that then sequences down to the
Message box that tells you the program is over.

Note how the element(s) to be executed sequentially after the "I/O" loop are
connected to the sequence-out pin of the Until Break -- they are *not*
connected to any of the loop elements.  Note also that you could connect the
Break object to the "Ignore" pin instead of the sequence-out pin and it would
work all the same.

* VEE 3.2 added a useful improvement to error handling ... in previous
versions of VEE, users who wanted to trap errors on objects through error
pins simply got an error number, not an error message.

To get around this limitation, we added a new function named "errorInfo()"
that allows a program to retrieve full details of the error; for example:

              +------------------+
              |      Formula     |
   +---+      +---+-----+--------+
   | 1 +----->| A | A/B | Result +--->
   +---+  +-->| B |     | error  +--+
          |   +---+-----+--------+  |          +--------------------------+
   +---+  |                         |          |        AlphaNumeric      |
   | 0 +--+                  +------+------+   +--------------------------+
   +---+                     | errorInfo() +-->| {511, "Divide/Mod by 0"} |
                             +-------------+   +--------------------------+

This example is also part of the file xerrdemo.vee ... of course this
file will not load in earlier versions of VEE.

* Beware of using error-handling as a standard practice, particularly with,
say, a Transaction Object whose transactions contain complicated math
formulas.  VEE will allocate memory to parse these formulas, and if an error
occurs during that parse activity, will lose that memory -- causing an
incremental memory leak.  This flaw is deeply rooted in the VEE architecture
and will not likely disappear any time soon.

[%%]


[12.4] PROPAGATION PROBLEMS

* VEE propagation problems can be very distressing to work with ... some
examples can help illuminate such problems.

* A user sent me a problem report ...  his idea was to get the time
interval between two events.  I did not understand exactly what was supposed
to be measured, but the program he gave me was clear enough:

       +-------+
       |  For  |
       | Count +---------+
       +-------+         |
                   +-----+-----+   +---------+
                   | Delay (1) +-->| Counter +--+
                   +-----------+   +---------+  |
                                                |
   +--------------------------------------------+
   |
   |   +------+ T  +-----------+-->+-------+ T
   +-->| A>3? +--->| Shift Reg |   | A!=B? +---+
   |   +------+    +-----------+-->+-------+   |
   |                                           +-->+-------+
   |                                               | Timer +-->
   |                                           +-->+-------+
   |   +------+ T  +-----------+-->+-------+ T |
   +-->| A>6? +--->| Shift Reg |   | A!=B? +---+
       +------+    +-----------+-->+-------+

In this diagram, the "Delay (1)" object is a "Delay" object set to 1 second,
and the four "conditional" ("A>3?", "A>6?", "A!=B?") represent "If/Then/Else"
objects, with "T" indicating the "True" output line.

The top part of the program is not important ... all it does is build up a
count.  The bottom section is where the problem occurs. 

The peculiar logic of the two branches of the bottom sections is clearly
intended to be able to trap any value above 3/6 but not generate any output
after that.  The two branches of the logic appeared to generate outputs to
the "Timer" object, but the "Timer" never generated an output of its own.
Tracing data values indicated that there was indeed data valid on the outputs
to the "Timer" but the second input of the "Timer" didn't have valid data.

On consulting with the lab engineers, I discovered that this was expected
behavior, and relates to how VEE handles looping constructs.  As a general
rule, values used by objects inside iteration loops are not retained between
iterations.

Since the problem was clearly one of synchronization of inputs, after
considerable tinkering I finally came up with a variation that avoids that
condition:

   |
   |   +------+ T  +-----------+-->+-------+ T
   +-->| A>3? +--->| Shift Reg |   | A!=B? +------+
   |   +------+    +-----------+-->+-------+      |
   |                                          +---+---+   +-----------------+
   |                                          | now() +-->| Set Global "t0" |
   |                                          +-------+   +-----------------+
   |
   |   +------+ T  +-----------+-->+-------+ T 
   +-->| A>6? +--->| Shift Reg |   | A!=B? +------+
       +------+    +-----------+-->+-------+      |
                                                  |
                                           +------+-----+
                                           | now() - t0 +-->
                                           +------------+

When the top branch fires, it stores the time in the global variable "t0";
when the second branch fires, it uses a "Formula" box to subtract "t0" from
the current time -- which gives the time interval.

* The same sort of problem came up a few days later through a report from the
HP Response Center; the user was trying to build a record of three waveforms
as follows:

                               +---------------+    +--------------------+
   +-----------+               | Demultiplexer |    |    Build Record    |
   | Function  |               +------+--------+    +---+------------+---+
   | Generator +-------------->| Data | Addr 0 +--->| A |   Output   |   |
   +-----------+               |      | Addr 1 +--->| B |   Shape:   | R +-->
                           +-->| Addr | Addr 2 +--->| C | [Array 1D] |   |
            +-----------+  |   +------+--------+    +---+------------+---+
            | For Range +--+
            |   (0:2)   |
            +-----------+

The "Build Record" object would never fire; this was because it only received
one input at a time, and each interation of the loop would invalidate the
inputs from the previous iteration.  (At first I thought that the
"Demultiplexer" would actually generate null outputs on the unselected pins,
but that didn't prove to be the case ... it makes no material difference,
however.)

After some more tinkering I came up with a solution (using a "Shift Register"
instead of a "Demultiplexer") where all three inputs would be valid
simultaneously:

      +-----------+
      | For Range |
      |   (0:2)   +------------+
      +-----+-----+            |
            |                  |
            +------------------|-----------------------+
                               |                       |
                       +-------+--------+    +---------+----------+
      +-----------+    | Shift Register |    |    Build Record    |
      | Function  |    +------+---------+    +---+------------+---+
      | Generator +--->| Data | Current +--->| A |   Output   |   |
      +-----------+    |      | 1 Prev  +--->| B |   Shape:   | R +-->
                       |      | 2 Prev  +--->| C | [Array 1D] |   |
                       +------+---------+    +---+------------+---+

This program clocks three waveforms into the "Shift Register" and then
strobes the "Build Record" to generate a record of them.  Note that this
could also be (more simply) done with a "Collector" if an array output is
desired instead; another approach would be to use the original scheme -- but
embed the circuit that feeds the Build Record into a UserObject; the outputs
of the UserObject will all be valid when it completes operation:

   +-------------------------------------------+
   |                 UserObject                |
   +---------------------------------------+---+
   |                   +---------------+   |   |    +--------------------+
   |  +-----------+    | Demultiplexer |   |   |    |    Build Record    |
   |  | Function  |    +------+--------+   |   |    +---+------------+---+
   |  | Generator +--->| Data | Addr 0 +-->| X +--->| A |   Output   |   |
   |  +-----------+    |      | Addr 1 +-->| Y +--->| B |   Shape:   | R +-->
   |                +->| Addr | Addr 2 +-->| Z +--->| C | [Array 1D] |   |
   |  +-----------+ |  +------+--------+   |   |    +---+------------+---+
   |  | For Range +-+                      |   |
   |  |   (0:2)   |                        |   |
   |  +-----------+                        |   |
   +---------------------------------------+---+

* For another example (related to Timers) ... a user reported to us what he
thought was a bug, demonstrated by the following program:

   +-------+
   | Until |
   | Break +---+----------+
   +---+---+   |          |
       |       |      +---+---+
       |       |      | Break |
       |       |      +-------+
       |       |
       |       |  +------------+
       |       |  |   Timer    |
       |       |  +------------+
       |       +->| T1         |
       +--------->| T2         |
                  +------------+

The user got an error that said T2 was being executed before T1, but the
error would come and go depending on how the customer wired up the program.

The error is actually logical.  What happens is that the Break object is
being executed before the T1 pin of the Timer object; this forces an
*immediate* exit of the Until Break loop, meaning the T1 pin is not pinged.

The Until Break fires its Sequence Out pin and pings the T2 pin of the Timer
object, forcing the error.

This is as per VEE operational specifications (and can be seen if the program
is traced).  Without constraints, there is no way to tell which object on the
output of the Until Break will be executed first.  That is why tinkering with
the wiring gets things to work (and that is, in fact, a good indication of a
propagation ambiguity like this one).

Many programs that worked in VEE 3.0 were dependent on a specific propagation
order that was actually undefined in practice.  VEE 4.0's compiled mode in
general follows the same rules of propagation, but that doesn't mean it will
choose the same arbitrary order of propagation when there is an ambiguity.

VEE Do objects can be used to resolve the ambiguity in control flow and
ensure proper Timer operation.  The operation of looping objects driving
parallel paths as in this example is very suspect in VEE 4.X (as opposed to
slightly suspect in VEE 3.X).

[<>]

Outcomes