The previous entries in this series have dealt almost exclusively with issues regarding the GEM VDI graphics library, but finally we get to GEM AES.
AES stands for Application Environment Services, which mostly translates into “handling the user interface stuff“. There are a few other things in there, but mostly it’s responsible for drawing windows and other user interface objects on screen and for managing the user’s interaction with them.
Like VDI, GEM’s graphics library, AES has its share of design issues. Overall I think it’s probably better off in that regard than VDI, but there are a few particular areas where the design had significant flaws. In contrast to VDI, however, it’s not always clear what the better solution might have been, or how big a difference it would have made.
This article will focus on the AES event processing model and how it tied into GEM’s support for cooperative multitasking.
Also, before we dive in, I’d like to say for the record that we’re talking about vanilla GEM here, version one point oh. I don’t care if somebody added more features to some post-Atari version of GEM in 1997 or something like that. I generally don’t even talk about the stuff Atari added for the TT030 or Falcon030.
GUI Event Processing
Since the earliest examples of the genre, Graphic User Interface systems all have a certain similarity in their basic structure. While they may have significant differences from one to the next in regards to what sort of controls or widgets they support, in terms of how they work, they’re all fundamentally wrapped around the idea of event processing. Some event occurs, and the application responds. The event in question is usually some action the user has performed which interacts with something on screen, like moving the mouse, clicking a mouse button, or pressing a key on the keyboard. When something like happens, the GUI manager captures the event and then it lets the application know about it so that the appropriate response can take place.
Let’s compare the event processing methods for Windows and GEM. Not that Windows is necessarily the best thing around, but it’s been around for about as long as GEM, whereas other examples like the current version of Apple’s OS X or something like Java Swing, are much newer.
If you’re not familiar with programming either system, some things may not make sense at first but we’ll try to fill in the details as we go.
Also, be aware that we’re talking about the basic, original, low-level structure of Windows event processing. If your experience with Windows programming is mainly using class libraries and frameworks like MFC, Windows Forms, or WPF, this is how they get things done internally.
A Look At Windows Event Processing
Generally, when a Windows applications starts, it performs whatever basic initialization is needed, registers at least one window class with the system, then opens a window.
Some specialized types of program may not open a window at startup, like screen savers, or programs that hook into the system tray, or something like a patch installer that doesn’t need to interact with the user. However, we’re concentrating on the more typical example of a standard program that is going to be interacting with the user using the graphic user interface.
After opening the main window, it likely does something like specify a menu bar, or a tool bar, and whatever other configuration might be required. Eventually, the startup code exits and control returns to Windows. Now the application sits idle until an event occurs. This could be a keypress, mouse click, window redraw message, or any number of other things.
Once an event occurs, Windows looks at what UI element was involved, figures out the window class that element belongs to, then it calls thvent handler routine which was specified when the window class was registered. One of the parameters would be a window handle, a unique ID which would indicate which specific instance of that UI element was referenced.
If the window class for that element was defined as part of your program, then it would regain control at the entry point to the event handler function. If the window class was created elsewhere and only referenced by your program, then that code would be called instead, and your program wouldn’t even know an event had taken place.
An event handler can decline to process an event and mark it as handled. When that happens, Windows finds the original element’s parent object and gives it a chance to process the event. The event bubbles up the tree of UI elements until it gets processed, or it runs into the top most object.
If some of this seems unfamiliar, keep in mind that modern application frameworks like Windows Forms or WPF do a lot of work to simplify the event handling process so that your application only has to worry about creating functions to handle the specific events it thinks are important.
Once the handler is done processing the event, it exits, returning control back to Windows. Now the program once again sits idle until Windows has another event to be handled. In the meantime, Windows is passing control to other applications as needed to handle their events. The result is that everything generally looks nice and responsive and coexisting peacefully.
For now, anyway. But we’ll come back to that.
And GEM’s Version Of That Is?
When a GEM application starts, it normally starts by loading its primary resource file, which specifies definitions for menu bars, dialog boxes, icons, etc. This information can also be embedded into the main executable, which is how Windows usually does it, but this is uncommon under GEM.
Next, it probably turns on the menu bar using one of the resource trees that was in the loaded resource file. Now it might open a window, or maybe not. GEM puts the menu bar at the top of the overall screen, not inside a window, so it’s not necessary for GEM applications to open a window right off the bat.
Alternately, if the application is loaded as a GEM desk accessory, then instead of activating a menu bar, it registers a menu item in the system menu, along with the other loaded desk accessories.
Just like with our Windows example, this represents the typical mode of operation for a typical program that interacts with the user using the graphic user interface. And as with Windows, specialized types of programs can be different,
Finally, after whatever initialization is performed, GEM applications go into their primary event loop. This normally starts with the program calling the AES evnt_multi() function. This asks GEM to look in the event queue for any events that require processing, but it also passes control back to GEM.
Note: GEM AES actually has a variety of functions for requesting specific types of event, but to simplify things we’re going to refer only to evnt_multi(), which can request multiple types of event at the same time.
When evnt_multi() is called and AES gets control back from the application, it looks in the system’s event queue for the oldest unprocessed event it can find. The event may belong to the same application that was just in control, or it could be a different application. The original version of GEM could have a main application and up to six desk accessories, giving us up to seven programs running together in cooperative multitasking harmony. In theory, that is.
Once AES figures out which application the oldest event in the queue belongs to, it looks at what other events might be waiting in the queue for the same application. In some cases, multiple events can be handled at once. Once it figures out all the events it can pass to the application, it returns back from that application’s call to evnt_multi() so the event can be processed.
If there are multiple events waiting in the queue for the same application, then evnt_multi() will attempt to combine them to a certain degree so that as many events can be processed at once as possible. However, there are limits to what events can be combined. For example, only one message event at a time can be processed because of the way the information is returned to the application.
At this point, the application takes whatever actions are required to process the events, then ultimately it calls evnt_multi() again so that the whole process can happen all over again.
Sounds Pretty Similar, But Is It?
At first glance, Windows and GEM sound pretty similar in their approach to event processing, but note that they’re actually approaching the problem from opposite sides.
Windows remains in control until an event needs processing, then it temporarily gives control to the application while that is done. This means that idle time always belongs to Windows.
With GEM, it’s the application that stays in control until it decides to give the system a chance to process events. Although it looks like it’s only asking GEM for its own events, it’s actually giving GEM the opportunity to let other applications take control for awhile, if they have pending events to be processed. So, idle time can belong to either GEM or the application, depending on how the application decides to poll for events.
Another big difference is that Windows processes one event at a time, while GEM can process several at once. At first, you might think that means GEM is more efficient, but the problem is that this means events can be processed in a different order than they occurred. The information returned back from evnt_multi() tells the application which events occurred, but it doesn’t say anything about which one happened first. What if you get a message event for a window redraw and also a mouse click event at the same time? Maybe in some cases it makes no difference, but in other cases maybe it does.
The other issue is that Windows offers a much finer granularity over the processing of events. For example, when GEM returns a mouse-click event to an application, it’s up to the application to figure out if that click happened inside a button, or an editable field, etc., and respond accordingly. All events happen at the application level and it’s up to the application to figure out how they should be trickled down to individual UI elements.
Except… windows. Message events for windows are different in that they specify the handle for the specific window. So when you get a message saying a window was minimized, for example, so you know which one. However, this only applies to the message events about things happening with the window frame. Except for a window redraw message, events that occur within the window’s client area aren’t really tied to the window in any way.
For example, if you get a mouse click event, AES tells the application the screen coordinates where the mouse was at when it happened. It doesn’t tell you which window, if any, or anything else. Just “Here’s a mouse click. Deal with it.” It’s then up to the application to figure out if that was inside a window, and if so, which one.
With Windows, the application only owns the events that occur within the windows, not in the desktop area. And everything the application draws ultimately tracks back to a UI element with a window class and event handler. There’s never any ambiguity about what was supposed to receive the event.
For example, if a button gets clicked, it’s sent a mouse-click event message. The button’s event handler is called, where it has the option of either processing the event, or ignoring it. If it ignores the event, then Windows looks for whatever object contains the button and passes the event to it. This process repeats until one of the event handlers claims responsibility. Thus, the event can be handled at the lowest-level, or the highest level.
This also means that with Windows, you have the ability to create custom controls that are completely self-contained and responsible for their own behavior, without needing the main application to do stuff for them. For example, you could have a button which changes border color when the mouse enters or exits it. In GEM this would be impossible without the application figuring out that the mouse moved over the button, or out of it, and then calling some function in the button code. It can be done but it’s a lot more work.
Even when you’re not using customized controls, with GEM if you wanted to have a non-modal window with UI widgets like buttons or editable fields in it, your event handling becomes a lot more complicated because of the need to manage them. With Windows, it’s much more straightforward.
To summarize, with Windows, events are something that happen with individual objects that can be combined to make more complex objects. With GEM, events happen at the application level, and it’s up to the application to figure out how to process them in a way that makes sense for whatever happens to be on screen at the moment.
The First AES Design Flaw
So, the first major design flaw of GEM AES is that event processing doesn’t have enough granularity built into the basic setup. Developers have to write a fair amount of code to make up the difference, and some things like self-contained custom controls just aren’t really possible.
The Problem With Cooperative Multitasking Is…
The event processing model of a GUI system is intimately tied in with the concept of cooperative multitasking. We already discussed how GEM gives applications a chance to respond to user input and events, but we kind of skipped past something that can be a major concern.
What happens when your application needs to do some sort of processing that takes a lot longer than your basic event handling?
In a modern application, an application is expected to create a worker thread when some sort of heavy lifting is required. The event handler would create the worker thread and start it, and return control to Windows. The worker thread could then run in the background. Your user interface design might include a progress bar, and certain function might need to be disabled while the background task was running, but in general your user interface, and that of other programs, would remain responsive to user input.
However, the technique of using a worker thread was not available in the heyday of cooperative multitasking. In those days, under systems such as GEM/Atari or Windows 3.1, everything, except interrupt handlers, ran in a single thread. Worker threads simply weren’t an option in those days.
What would normally end up happening is that applications would simply do whatever heavy lifting they needed to do, in direct response to the user input that requested it, forgetting about event processing in the meantime. Of course, while they were doing whatever processing they needed to do, other applications weren’t getting a chance to do anything. The result was often that the system looked like things had locked up, at least momentarily.
This wasn’t a big issue if the processing only took a few seconds or less, but for bigger jobs where the system was unresponsive for longer periods, users might think the system had crashed.
Avoiding The Lockup
With Windows, there wasn’t really any easy way to avoid the problem, since the only way to let event processing occur was to exit from the event handler and return control back to the system. The best option was to divide your processing up into small chunks that could be done a little at a time in response to a timer event. Otherwise you pretty much just had to let the UI freeze up for a bit while you did your thing.
GEM arguably had things a bit better. You could theoretically avoid the “freeze” if you spent some extra effort. While doing your long processing task, you could periodically call a subroutine that would call evnt_multi() with timer events enabled, and a timer period of zero. This would give other applications a chance to do their own processing, but the timer event would ensure the system returned control to your application as soon as possible, even if no other events were pending. Once the evnt_multi() call returned, the subroutine could process any pending events for your own application and then return back to your long processing task.
Most likely, if you did this, your application would have many functions disabled while the long processing task was going on, but things like screen redraws would get processed in a timely fashion. You had to make sure that processing events didn’t step on what you were otherwise doing, or vice versa, but with a little care you could get it to work.
The main problem with this technique is that the application really has no way to know when there are events that need to be processed, either for itself or for others. If it wants to perform its processing in the shortest time possible, it doesn’t want to waste time passing off control to the system when there aren’t any events to worry about.
The Second AES Design Flaw
It’s a shame that GEM AES didn’t offer a better means for an application to perform long processing tasks without locking up the UI. Sprinkling your code with calls to an event processing subroutine did basically work, but it wasn’t ideal and it certainly wasn’t elegant. Every time I suggested the idea to a developer, they always kind of looked at me like they were thinking, “Seriously?”.
There are probably better ideas I haven’t thought of, but maybe what GEM really needed was a better way of polling the status of the event queue. They could have had a function, let’s call it evnt_peek(), which would have simply looked at the event queue and returned a flag indicating if events were pending for any application. As long as no events were pending, the program could continue processing without worrying about it looking like the system locked up.
At least we can take solace in the fact that Windows had it worse back in those days.
Next time around, we’ll talk about how GEM AES was inconsistent about how it defined and managed UI elements, and the repercussions of that.