And Events Shall Be Raised Uninterruptible

It is reasonable to say that the flow of control in LSL is determined, at the top level, by the recognition of events. LSL code executes only when something happens. Of course, there is a variety of ways that something can happen in LSL. The obvious methods are by overt actions such as touch() or collision(). You can catch an event when some parameter of the object has changed(). There are events that trigger on ostensibly linguistic communication: listen(), link_message(), and email(). There are events raised when a system request is fulfilled, such as timer() or dataserver(). There are also what I call the administrative events, state_entry() and state_exit(). There’s even an event available when someone gives you money(). When events are triggered, the respective LSL code is performed.

But is it that simple? The pattern of code execution in LSL programs can be confusing: on the one hand, event code appears to be triggered when the action occurs, according to the interrupt-driven model; yet on the other hand, reliance on interrupt-driven coding techniques simply stall the program.

That’s because event-driven does not mean interrupt-driven, at least not in LSL. The reality is that LSL event code is not executed directly when the event is recognized. The paradigm is not interrupt-driven at the level of the script.

Here’s what does occur: when an event is raised, a request is placed in that task’s event queue, and it stays there until the program is in a position to service it. The program is in that position when it has completed any currently executing event code. The program then services event requests in order of the request.

This means that the coding techniques of interrupt-driven applications cannot be considered. Perhaps the most basic of these is the “busy-wait,” where the program loops until an interrupting event sets a flag. This would seem to be intuitive, because you “wait for this, and then do that,” hoping to confine these actions within one routine.

Let’s take a trivial example where an announcement is made upon the first touch of the object:

[lsl]
// A misapplied example of busy-wait.
// This will not work in LSL!

integer g_flag = FALSE;

default  {
   state_entry()  {
      while (g_flag == FALSE);    // Wait for a touch.
                                  // g_flag is now TRUE.
      llSay (0, "I've been touched!");
   }

   touch_start (integer numDetected)  {
      g_flag = TRUE;
   }
}
[/lsl]

This protocol would require that, upon touch, program execution be taken from the middle of the state_entry() routine and given to touch_start(); whereupon, after touch_start() completes, execution would return to wherever state_entry() left off. This is how the interrupt-driven paradigm works. This just does not happen in LSL.

But notice: if this had worked, then I would have effectively coded the action of a touch*() event in the state_entry() event. This is likely not the most philosophically elegant of situations; nevertheless, as you will see later, it may be expedient to do exactly this. File that away while I make my current point: event code cannot be interrupted by other event code.

Now, the first stab at a working LSL program for the above is quite simple:

[lsl]
default  {
   touch_start (integer numDetected)  {
      llSay (0, "I've been touched!");
   }
}
[/lsl]

Of course, these examples are not quite equivalent. In the former implementation, the object would speak only once; in the latter, it will speak every time a group of touches (in the amount of numDetected) has been recognized.

If you really want to ensure that the object speaks only once as originally intended, then you can disable the announcement and/or change state. This can be coded in a few different ways depending on your personal style or, more likely, the arbitrary whim of your immediate supervisor. Notice how the following examples are functionally equivalent:

[lsl]
integer g_flag = FALSE;

default  {
   touch_start (integer numDetected)  {
      if (! g_flag)  {
         llSay (0, "I've been touched!");
         g_flag = TRUE;
      }
   }
}
[/lsl]
[lsl]
default  {
   touch_end (integer numDetected)  {
      llSay (0, "I've been touched!");
      state TOUCHED;
   }
}

// ------------------------------------------------

state TOUCHED  {
   state_entry()  {
   }
}
[/lsl]
[lsl]
default  {
   touch_end (integer numDetected)  {
      state TOUCHED;
   }

   state_exit()  {
      llSay (0, "I've been touched!");
   }
}

// ------------------------------------------------

state TOUCHED  {
   state_entry()  {
   }
}
[/lsl]
[lsl]
default  {
   touch_end (integer numDetected)  {
      state TOUCHED;
   }
}

// ------------------------------------------------

state TOUCHED  {
   state_entry()  {
      llSay (0, "I've been touched!");
   }
}
[/lsl]

The example with the state_exit() event I find particularly interesting; others might find it particularly obtuse if they haven’t used state_exit() before. Just as state_entry() is performed, if it is defined, upon state entry, state_exit() will be performed, if it is defined, upon transition to another state.

That example sets up a cascade of events:

  1. Wait for a touch;
  2. Explicitly request a change of state;
  3. Automatically call state_exit() in this state;
  4. Perform the state change;
  5. Automatically call state_entry() in the new state.

And in these last two examples, the action intended to be performed upon touch–that is, the annoucement “I’ve been touched!”–is not placed in the touch event itself, but downstream in the cascade. The placement of the announcement itself is not critical in this example, as long as it occurs after the touch. This distribution, this separation, this organizational distance of consequence from its cause is a useful technique in the design and topological sorting of event cascades in LSL.

Not even the delays implicit in certain function calls will free up resources to allow other events to play out. Calling llInstantMessage() will delay the script for two seconds; calling llEmail() will delay the script for 20; and you can specify your own explicit delay with llSleep().

Nothing at the LSL code level happens during these delays. The rest of the event routine is on hold, and no other code is executed. Event requests will be queued during these delays, but not serviced. Only when the delay finishes, and event code is resumed and completes execution, are any other events, waiting in the event queue, serviced.

Code these delays carefully, however. The event queue holds up to 64 requests, after which events cannot be queued and are therefore effectively ignored.

I’ll continue this discussion in a future blog entry, which I will likely entitle A Series of [Un]Requested Events.

Originally posted 21 Dec 2008