Event Driven Programming - Part 2

by Brad Moore

Home

Maintaining checkbox states
Tidbits from the community
Liberty Basic 4
New Alternate Forum
Using the Tsunami Database
Wizard Framework
Links to LB Sites
Update on 10th Anniversary Contest
Extracting Icons And Saving Them As Bitmaps
Applying Symbolic Logic
QuadClicks
Simple Math For Moving Objects
Event Driven Programming - Part 2
The Beginners Series - Part 1

 

More about what it means to be Event Driven

The basic idea behind event-driven programming is that some events determine the flow of the application. Liberty Basic depends a lot on event-driven programming techniques. This is unlike implementations of Basic in DOS, which foster mainly top/down program design. In top/down programming, an input statement waits for input at that location in the code, then processes that input and moves on to the next statement. Event driven programs tend to be designed in a fashion that gets a user interface out there, then waits for the user to do something, at which point they may execute any one of many different code segments. Not only can the user create events, but the system can also create its own events which the program may need to process. The flow of the program is not from top to bottom, but from top outward in many directions. It is also not necessary for the program to return to the place it was when the event occurred unless there is some unfinished processing to complete. It can simply start waiting for another event as soon as it has completed processing the current event. As we said earlier - events dictate the flow of the application.

The nature of an event driven program is one of waiting. Most Windows programs spend most of their time waiting for events to occur. Once an event occurs a handler is invoked within the code. The handler has lines of code which are executed in response to specific events. Most programs are not written with code that responds to ALL Windows events, as most events do not relate to the program, so they are ignored.

As an example - when the user clicks one of the mouse buttons an event occurs. A message describing the event is sent to along a chain of windows processes and eventually makes its way to the window that has current focus and perhaps even to a control (an object) on that window. If it is a window created by Liberty Basic - the Liberty Basic virtual machine sees the message and sends the message on to your code as an event. (Figure 1 shows this message handling process) If you have written a trap for the event - that code will be executed. One other detail - you must actually intercept the event using a SCAN, INPUT or WAIT statement.

Figure 1 - Message Handling Process

Messages for everyone

There are many low level processes running in windows which create and consume messages. Many of these never send the messages further up the message chain. Other messages may be used many times. A lower level process may use it, and knowing it is important to higher level processes it will send the message on up. Many people do not realize how many processes are running on their system. Figure 2 shows a screen shot of running processes on a fairly static NT box. These are just the upper level processes - many of which are message consumers. Windows provides a method of actually reaching and intercepting these messages for your programs using several API calls. The process is called Subclassing - but it does not work with Liberty Basic because the Liberty Basic virtual machine is already consuming messages and your attempt to subclass from within the message consumer creates havoc for everyone.

Figure 2 - Processes on an NT Box

Messages (and thus events) are serialized - each message is handled only after the previous one has been processed (or ignored). When an program is executing some code (that is, it is not waiting for an event), it can not intercept an event if it occurs, and will not process the event until a WAIT, INPUT or SCAN command is encountered. Fortunate for us, the Liberty Basic virtual machine does not allow Windows messages to pile up waiting for us, but rather continues to forward these on to other processes that may need them - queuing up certain events that we may be interested in. I am uncertain how much of a buffer the Liberty Basic virtual machine maintains, but I suspect that after a certain point old messages are flushed as newer ones are added to the LB message queue (Carl I am sure can supply this information if it is required).

Getting the message

As the programmer writing an event driven program we are very interested in getting our messages and acting on them as they become available. Anytime we issue a WAIT, SCAN or INPUT command, we are allowing the program to check the message queue to see if there are any events that the user has caused waiting for us. If there is an event then Liberty Basic will search to see if we have set up a trap for that event and provided an event handler (explained in part one of Event Drive Programming). This is easy when our program does a couple things then simply waits. We issue a WAIT command and all is well. The big problem that we can run into is: How do we continue to handle events when we have some bone crushing number crunching to do?

Consider the following code:

for x = 1 to 10000
   for y = 1 to 1000
      for j = 1 to 250
         if globalLoc(x,y) = playerPos(j) then
            playerPos(j) = 0 
            player(j) = 0
         end if
      next j
   next y
next x

That is a very tight loop that will run for several seconds (on my PC anyway). What if the user has been pressing the “Cancel” or “Quit” button because they are done. Unfortunately we would be deep in the center of determining whether player(j) lives or dies and there would be no way to act on that request by the user.

Enter SCAN

What is required is a SCAN command. An often misunderstood command, the SCAN command can be the programmers best friend. It is just like the WAIT command (which is simply a synonym for the INPUT command), except that it does not wait. What it does do is peak quickly at the message stack that the Liberty Basic virtual machine is maintaining and checks for waiting events. If there is one then execution quits where we are and the program pointer is transferred to the event handler code (provided there is an event handler for this event).

Here is what the helpfile says about the scan command:

The SCAN statement causes Liberty BASIC to stop what it is doing for a moment and process Windows keyboard and mouse messages. This is useful for any kind of routine that needs to run continuously but which still needs to process button clicks and other actions. In this way, SCAN can be used as an INPUT statement that doesn't stop and wait.

So we need a SCAN statement in our code - but where? It is something that requires a little bit of thought. We have three nested loops. There is the X loop which runs 10,000 times. There is the Y loop which runs 10,000,000 times and finally there is the J loop which runs 2,500,000,000 times.

If we were to put the SCAN command into the J loop it would be executed over two billion times. That seems like over kill. Then again - if we put the SCAN command in the X loop we would have to wait for the program to evaluate the player positions a quarter of a million times before we checked for the next event. That might be OK if your system were fairly fast. Personally I would try the Y loop first and then compare the response with the response you get when the SCAN command is in the X loop.

My new code would look like so:

for x = 1 to 10000
   for y = 1 to 1000
      scan
      for j = 1 to 250
         if globalLoc(x,y) = playerPos(j) then
            playerPos(j) = 0 
            player(j) = 0
         end if
      next j
   next y
next x

Now that might be too many SCANs to deal with. If things seem to bog down - move it to the outer loop. Unfortunately we have introduced an new potential for a bug. What if the user triggered an event, but it was not the QUIT event? The game must go on. But we have trapped the event and routed program execution from the analysis of the player positions to some other area. These cases must be handled using some simple Boolean switches that keep track of what is suppose to be happening at any given point.

For instance just prior to evaluating the player positions the game play ended for the human player. We could set a flag that says it is not their turn.

PlayTurn = 0

If they try to interrupt the evaluation of the player positions and attempt another play we could verify that the play is valid in the handler:

If PlayTurn = 0 then
   Notice "It is not your turn - Sorry"
   goto [PositionEval]
else
   'It is the players turn - go on...

Likewise we must know whether we have completed the evaluation should the program be interrupted by an event. We could set a Boolean switch at the beginning and end of evaluation and then check the status of that switch later before game play advances. Something like:

[PositionEval]
Evaluate = 1
for x = 1 to 10000
   for y = 1 to 1000
      scan
      for j = 1 to 250
         if globalLoc(x,y) = playerPos(j) then
            playerPos(j) = 0 
            player(j) = 0
         end if
      next j
   next y
next x
Evaluate = 0

...
'Elsewhere in the code
...
[MainLoop]
'Make sure everything got done before proceeding with player turn
if Evaluate = 1 then
   'looks like evaluate did not finish - lets do it again
   goto [PositionEval]
end if
 

Our need to have a more responsive user experience and to handle events when they arrive has created a need for some extra checking on the process, but to be honest, if you are writing a program like this, you probably are already doing some of this type of checking of Boolean type switches. It is just a good way to insure that everything that needed to be done got done.

I hope that the technical stuff has not been too much for people. You can really take it or leave it. It is not critical to know the exact fundamentals about how a specific message got to Liberty Basic and triggered an event. What is important to know id that events happen - and you need to plan to handle them. In part one we discussed handling them with the WAIT statement. This month we focused on the SCAN statement. SCAN is not a statement to be afraid of. Early on there was a lot of apprehension regarding its use. People believed it used significant processor time and some people used SCAN when a simple INPUT (this was in pre-WAIT command days) would have done the job.

SCAN performance

Why do people think that SCAN is a CPU hog? Well it is because the put it into a tight loop (like our loop) or even more troubling a loop like:

j = 1
while j <> 0 
   scan
wend

This is not necessary. A WAIT statement will do the same thing and the rest of the system will be happier. But what about our processing loop above? It consumed CPU before the SCAN statement was inserted. It consumes CPU because it is a tight loop and by definition is CPU intensive. SCAN itself takes a scant nanosecond to perform its work and it is out of the loops way, adding virtually nothing to the load on the CPU.

So - use it correctly, use it when you need it and don't forget to use it when it is called for. SCAN: its not just for breakfast anymore...

by Brad Moore, © copyright 2002, all rights reserved.

Home

Maintaining checkbox states
Tidbits from the community
Liberty Basic 4
New Alternate Forum
Using the Tsunami Database
Wizard Framework
Links to LB Sites
Update on 10th Anniversary Contest
Extracting Icons And Saving Them As Bitmaps
Applying Symbolic Logic
QuadClicks
Simple Math For Moving Objects
Event Driven Programming - Part 2
The Beginners Series - Part 1