Understanding the Run Loop

Since the 1980’s, operating systems have used a mechanism called a run loop to coordinate the response to mouse clicks, keyboard presses, and other events that require attention. Panorama is designed to interact with the run loop to handle events that are related to windows that belong to Panorama, and any events that the system decides are related to Panorama in general. Normally all of the details are taken care of for you, and you don’t have to worry about any of this – handling of events just works the way you would expect it to. Even if you are a programmer writing complex code, as long as your programs are triggered by normal menus and buttons, everything just works (though it turns out that Panorama is doing much more than you suspected behind the scenes.)

However, for advanced programmers it can be very valuable to understand how Panorama code interacts with the run loop system. In addition to regular programs triggered by menus and buttons, Panorama allows you to set up special code that is triggered automatically by some normal Panorama action, for example entering data, switching to a different record, or displaying information on a form. This is called implicit triggering and there are several help pages explaining how to set this up, including Implicitly Triggered Procedures and Automatic Field Code. When code is triggered implicitly, that code is usually limited in the types of operations that code can perform. Many Panorama opererations, including most that switch to different window or update the display, cannot be perfomed in an implicitly triggered procedure. Attempting to perform an operation that is not allowed can result in failure or even in a crash. Panorama doesn’t have guardrails in this area, so it’s up to you to make sure you only use statements that will work in the context the code is being run.

### Normal Run Loop Operation

Before looking at the special cases, you need to understand how the run loop works normally. Reducing to the simplest view, the run loop simply waits for events to happen (mouse clicks, etc.), decides what program needs to handle the event, and then passes the event to that program so it can be handled. This diagram shows the run loop if there are three applications currently running: Safari, Panorama X and Pages.

You can clearly see why it’s called a “run loop”, the flow circles back to the start in a loop after each event is processed.

From now on for simplification we’ll ignore that the run loop includes all running apps, and just include Panorama in the diagrams.

In addition to actual physical events, the system also automatically generates events when some or all of a window needs to be displayed. For example, suppose a window is partially hidden and then brought to the front. The system automatically generates a special “update display” event, which is passed to the program that owns the window so that the display will be refreshed.

A similar process occurs when a program needs to update its own windows after some operation is performed. For example, to select a data subset and display the results requires two trips thru the run loop. The first loop (the inner loop in this diagram) performs the actual select operation. The second (outer) loop updates the display. The loops are labeled 1 and 2 to help show the order in which they occur.

You may wonder why it’s done this way – couldn’t Panorama just update the display immediately as part of the select operation? It turns out that the operating system doesn’t allow that – the display must be performed as a separate operation. This is required so that the operating system can optimize the way the display is updated.

### Procedure Code and Run Loop Operation

When you are working with Panorama’s tools and menus the run loop never goes around more than twice for a particular operation – once to perform the operation, and then a second time to update the display. But a Panorama procedure can string together many operations in a row, with many updates of the display. As you’ll see, this can cause many turns through the run loop.

Let’s start with a very simple procedure that doesn’t update the display. All this procedure does is use the let statement to create three local variables and assign values to them.

    let x = 3
    let y = 5
    let z = sqr(x^2+y^2)

When you run this code, the run loop only loops once, with each let statement executing in turn. When the last let statement is finished, there’s nothing to display, so the run loop simply goes back to waiting for something else to happen.

Now let’s consider a program that does update the display.

    field State
    sortup
    field City
    sortupwithin

When you run this code, the run loop circles not just once, but multiple times.

Whenever a statement is performed that updates the display, Panorama temporarily pauses the code and goes back through the run loop, so that the display operation can be performed. Panorama then resumes the procedure from where it left off. This pause-display-resume cycle happens every time the display needs to be updated. Fortunately, you usually don’t have to worry about this, Panorama takes care of all this for you automatically. (By the way, the diagram above is actually simplified. The field statement also causes the display to update, so in reality the diagram above should show that the run loop circles 8 times to run this code, not 4.)

You can use the noshow statement to temporarily suspend these automatic pause-display-resume cycles. When you do this, the run loop is bypassed until you use the endnoshow statement. Here’s a modified version of the code above that only updates the display once, at the end.

    noshow
    field State
    sortup
    field City
    sortupwithin
    endnoshow
    showpage

With this modification, the run loop only circles twice, instead of 8 times.

Updating data is not the only situation that requires extra passes thru the run loop. Any operation that changes the window configuration causes Panorama to pause, update the windows, then resume the code. This includes using the window statement, opening or closing a window (for example openform or closewindow), opening an alert or dialog (message, alertsheet, rundialog etc.), and opening or closing a file (opendatabase, etc.). For example, this simple one line program:

    openform "Order Form"

will require two trips around the run loop, as shown by this diagram.

Updating the display isn’t the only task that can require an additional pause-resume cycle through the run loop. This is also required for operations that read or write the database to the disk, for example the save statement.

Note: This extra pause-resume cycle is only for database read/write operations. File operations on individual files, for example fileload( and filesave, are synchronous and do not cause the program to cycle through the run loop. Because of this, these operations can be included in atomic code (see below).

### Atomic Code (No Run Loop Pauses)

As you have seen, Panorama typically doesn’t execute a program all at once. Instead the code is broken up into chunks, with pauses in between each chunk to update the display. However, there are some situations where pausing is not possible – situations where all the code must run atomically, all at once with no pauses.

One such situation is the execute( function. This function can be used in a formula to run code to calculate the result. The program needs to run immediately to calculate the answer, the formula is waiting for the result. Panorama doesn’t allow this code to pause and come back later to finish up, there is no logic to allow that. If the code contains any statements that need to pause to display, like the sortup statement, that operation cannot happen. If you include statements that need to update the display it will put Panorama into an unpredictable state. You simply cannot perform those types of operations in an execute( function.

Here is an example of code that is ok in an execute( function. This example checks to see if a Text Editor SuperObject is currently active, and if so, returns the currently selected text in that object.

    execute(|||
        if info("activesuperobject")=""  // is there any object being edited?
            functionvalue ""   // no, return nothing
            rtn
        endif
        local theSelectedText
        activesuperobject "GetSelectedText",theSelectedText
        functionvalue theSelectedText
    |||)

This code performs a number of operations, but none of them require the display to be updated, or the window configuration to change, or document disk i/o. So this means that the code can be run atomically, all in one chunk, so it is fine for use in the execute( function.

Of course the execute( function isn’t the only situation that requires atomic code. A similar situation is the call( and callwithin( functions, both of which require atomic code.

#### Using Atomic Code in Text Editor Programming

When configuring a Text Editor Object you have the option of using atomic code:

Whenever possible, we recommend enabling this option, it eliminates potential conflicts if other code is running. If you need to open windows or display data, you can still do so using executeasap or wait, as described below in Triggering Non-Atomic Code from Atomic Code.

#### Checking for Atomic Code

If you’re not sure whether or not atomic code is required in a certain situation, you can use the info(“runningatomic”) function to check. This code sorts up by the City field, but only if the code was triggered in a way that allows non-atomic code.

    if not info("runningatomic")
        field City
        sortup
    endif

#### Handler vs. Atomic Code

Previous versions of Panorama called atomic code handler code, and you may still see references to that in documentation and panels. Handler code and atomic code are the exact same thing, just a different nomenclature.

### Semi-Atomic Code

There are a few special cases of Implicitly Triggered Procedures where non-atomic code is allowed, but operations beyond the current record are not allowed. For example, consider the `.CurrentRecord` procedure, which is triggered when the database shifts to a different record. Obviously it would be a problem if this procedure responded by changing to another different record. So it’s ok for this procedure to change the value of a field, but it should not do something like sorting the database or searching for a different record, or even moving down a record. The same restrictions apply to the `.ModifyRecord`, `.NewRecord`, and `.DeleteRecord` procedures, and to code triggered by data entry in a cell (see Automatic Field Code).

### Triggering Non-Atomic Code from Atomic Code

If you absolutely need to perform a non-atomic operation when it’s not allowed, the easiest way to do that is with the executeasap statement. This statement allows you to designate code that will run the next time Panorama gets back to a normal run loop state.

For example, suppose you want a dialog sheet to appear asking for a name every time a new record is added to a database. You can’t normally do this with the `.NewRecord` procedure, because that procedure is not allowed to open new windows with a statement like gettextdialog (see Semi-Atomic Code above). But by using the executeasap statement, you can postpone the operation of the gettextdialog for a fraction of a second, until after Panorama has finished creating the new record. At that point it is safe to do whatever you want.

    executeasap {gettextdialog Name,
        "Prompt","Enter this person's name",
        "Sheet","YES",
        "Button","Enter","AbortButton","Cancel",
        "Width",300}

This diagram shows how the executeasap statement moves the dialog code from the restriction context into the regular run loop, where it has no problem opening a dialog.

Keep in mind that the executeasap statement has no access to the local variables in the procedure it is a part of. The best way to to pass a local value into this code is to build it in as part of the formula that creates the code.

Though the executeasap statement makes it safe to do whatever you want, we recommend that you think long and hard about whether you really want your database to work in this unusual way. If you need to use the executeasap statement to accomplish your task, you are probably creating a very non-standard user interface that probably goes against the best modern practices in UI design.

Also if you’re not careful, the executeasap statement could get you into an endless loop that can only be cut short by forcing Panorama to quit. For example, suppose you put the code above into an execute( formula in a text display object. Simply displaying the object would trigger the dialog, then closing the dialog would display the object again, opening the dialog again, ad-infiniteum. So look out for this situation.

#### Using the Wait Statement

An alternate way to trigger non-atomic code is with the wait statement. Everything above the wait statement must be atomic, but everything below is allowed to be non-atomic.

    wait 0
    gettextdialog Name,
        "Prompt","Enter this person's name",
        "Sheet","YES",
        "Button","Enter","AbortButton","Cancel",
        "Width",300

You may find this method, easier to read and write, and it has the advantage that local variables are maintained from the atomic to the non-atomic section of the code. However, this method cannot be used with code that is embedded into a formula with the execute(, call( or callwithin( functions. When embedding code into a formula, the only way to trigger non-atomic code is with executeasap, as described above.

### Timer Code

If you’ved used timer code (see StartTimer) you may wonder how that code fits into the run loop. Essentially, the timer system magically inserts the timer code into the run loop at the specified times in the future. Once it’s placed into the run loop it’s just regular code, so it runs without restrictions. In other words, timer code does NOT have to be atomic. In fact, the executeasap and wait statements mentioned above actually are built using timers that repeat only once.

See Also

- Automatic Field Code -- running a short program when data is entered into a field.
- changetimer -- modifies a repeating task that will be performed periodically when Panorama is not otherwise busy.
- endnoshow -- resumes the output of text and graphics after it has been disabled with the noshow statement.
- executeasap -- executes the specified code at the first possible opportunity, non-atomically.
- Implicitly Triggered Procedures -- Procedures that are triggered automatically when the user performs some normal Panorama action are said to be "implicitly triggered."
- info("computername") -- returns the name of the computer (as set up in the System Preferences Sharing panel).
- info("noshow") -- returns true if noshow is currently turned on, false if it is not.
- info("runningatomic") -- returns true if the current procedure is running as an "atomic" procedure.
- noshow -- temporarily disables the output of text and graphics.
- resumeaftertask -- resumes after a waitfortask statement.
- setwaitinglocal -- assigns a value to a local variable in the parent procedure of an asynchronous task, allowing the asynchronouse code to pass a value back to the code that spawned it.
- starttimer -- sets up and starts a repeating task that will be performed periodically when Panorama is not otherwise busy.
- Timer Workshop -- provides a tool for monitoring timers and testing timer configurations.
- timerexists( -- checks to see whether a timer exists.
- Timers -- executing recurring periodic background code at fixed intervals.
- wait -- pauses the program for a specified period of time.
- waitfortask -- temporarily pauses a procedure until the specified task is complete.

History

Version | Status | Notes
10.0 | Updated | Carried over from Panorama 6.0.