Galaxy Communicator Tutorial:

Creating a UI Server

License / Documentation home / Help and feedback

Up to this point, we haven't talked much about the engineering model behind the Galaxy Communicator infrastructure, and we haven't needed to. However, in order to learn how to integrate UI elements into a Communicator-compliant system, it's important to understand how things happen in the infrastructure, and how to control how things happen. This lesson is more about computer science than anything else, and we won't look at any code; but at its end, you should understand enough about how the Communicator infrastructure works to integrate a UI aspects into a Communicator-compliant server.


The Communicator object model

There are several object types involved in flow of control in the Galaxy Communicator infrastructure. The first Galaxy Communicator object we came across in looking at server code was the call environment. In the last lesson, on brokering, we encountered the broker object. We briefly encountered the server object when discussing the server initialization function. The one object we haven't encountered yet is the connection object, which underlies both ends of Hub-server connections and broker connections. (Broker proxies aren't on this list because they're just object wrappers around brokers.) Here's the complete object model:

The reason this object model is important is because the server, connection, and broker objects are all associated with separate communications channels, and these channels are monitored by the Galaxy Communicator infrastructure using an event-driven programming model. This model is at the heart of the flexibility of the Galaxy Communicator infrastructure, and it's worth spending some time understanding it better.


Event-driven programming

There are three major components to an event-driven programming model: In a communications system like Galaxy Communicator, some of the events are typically the availability of data to read from or write to a communications channel; we'll call these events data events. Another type of event is a timer event, which marks the conclusion of a previously-determined time interval. These events can be used to schedule activities (for example, inspect the state of a connection every 10 milliseconds).

For each type of object in the Galaxy Communicator object model, there's a corresponding callback. The server object can establish a new connection when it has a request to do so; the connection object can invoke a dispatch function when it has message data; the outbound broker can feed data to its connected clients; and the inbound broker can read typed data when it's available.

So we can see now that the data callbacks for the broker clients we learned about in the last lesson are actually part of this larger model. They're not exactly the low-level data callbacks we're talking about here, since the data has been read and digested long before the broker callback sees it; rather, it's one of a set of higher-level callbacks based on what we'll call logical events that the Galaxy Communicator infrastructure supports. In general, it's these logical events which are visible to the average developer, rather than the lower-level data and timer events. Not surprisingly, dispatch functions also count as a type of logical callback:

There are a number of other logical events which the various objects support, some of which are a direct result of communications events (for instance, when a server creates a new connection object), and some of which are under programmer control (for instance, when an outbound or inbound broker is created). The full set of logical events can be shown as follows:

The programmer can associate callbacks with any of these logical events; in fact, we saw a brief example of this behavior when our target server associated a data completion callback with its broker client.

The power of the event-driven programming model is embodied in its extremely conservative assumptions about ordering and scheduling. Because events can occur randomly, the appropriate behavior in response to any event must be carefully encapsulated. This means that the resulting system is maximally embeddable in other software, because all the required behavior is completely separate from the scheduling and flow of control.

In other words, the event monitor is the least important dimension of the model; you can replace it with another event monitor without any sacrifice in functionality. In fact, we provide an example in the distribution which substitutes a different event monitor for the default Galaxy Communicator event monitor. Furthermore, the Python and Allegro Common Lisp programming language bindings use their own event monitors, but invoke the same underlying C callbacks as the default C implementation. So even before we look at UI programming, we can derive some significant benefits from this model.


The event monitor

There are two strategies for implementing event monitors we'll look at: threads and event loops. We're only going to talk about threads in passing, but it's a valuable conceptual step to the event loop.

In the abstract, a system like this has a number of parallel threads of execution going on. Some of these events are events which happen at particular intervals, and others are events which happen when data is available on a particular communications channel. These threads are conceptually separate. You could think of a callback which is fired every 10 milliseconds as one which is governed by an infinite loop which fires the callback, sleeps for 10 milliseconds, and repeats. Similarly, you could think of each data callback as governed by an infinite loop which constantly tries to read data from the communications channel, and succeeds only when data is available (a so-called blocking read).

In a multithreaded programming situation, this is exactly what happens. Each logical monitor is assigned to a programming thread. Examples of this are when a Communicator C server is run in threaded mode (which we won't talk about in this tutorial), or in the Java or Allegro Common Lisp programming language bindings. In these latter cases, the threaded model is the conceptually natural model for the language.

If you don't use threads (and the default C server doesn't), life is a little more complicated. Since you can only have one infinite loop, these conceptually independent threads of execution have to be collapsed into one:

Obviously, in this case we can't sleep or do blocking reads; if we block on one channel, we'll miss events on another channel. So we have to use an event loop. Periodically, the event loop checks the communications channels it's been told to monitor, and checks the expiration time of the timers it's been told to monitor. When it discovers that data is available, or that a timer has expired, it fires the appropriate callback. When the callback finishes, the event loop continues. The default Galaxy Communicator main loop is an event loop.


Adding UI elements

When you add a UI element, what you're doing, essentially, is adding another type of event to monitor. You may be monitoring standard input, or perhaps input from audio:
In cases like this, the appropriate thing to do is to add these events to the Galaxy Communicator main loop:

A well-designed main loop will allow the programmer to add and remove callbacks for timer and data events at will; the Galaxy Communicator event loop provides these capabilities. In fact, there's even a utility in the Galaxy Communicator distribution which allows you to add a standard input monitor to the Galaxy Communicator main loop fairly easily. This is the utility which monitors the carriage returns in the Audio server.

However, the reason this is simple to do is that monitoring standard input, or an audio device, usually doesn't come with its own main loop. There are many other common tools, such as CORBA and windowing systems, which have their own main loop. For instance, you'd encounter this problem if you wanted to build a Communicator-compliant Motif or Tcl/Tk server:

This is exactly the same problem we faced with multiple parallel threads of execution: unless you're actually using threads, you can't have more than one main loop running. So in these cases, either the Galaxy Communicator main loop has to go, or the windowing main loop has to go.

Fortunately, almost all of these main loops are well-designed, in that they support the ability to add and remove timer and data event callbacks. And since we know that Galaxy Communicator is designed so that all the work is done in the callbacks, and we have no idea whether the same is true for these other systems, the appropriate thing to do is to toss out the Galaxy Communicator main loop and monitor all the appropriate events in the other main loop:

This is how the unit tester works, actually; it monitors the Communicator events in the Python/Tk main window loop.

For external main loops implemented in C, there's a well-defined way of doing this embedding of the Galaxy Communicator callbacks, called external loop records. The example in the distribution which substitutes a different main loop for the Galaxy Communicator main loop shows how this mechanism works.


Summary

This lesson has been fairly abstract, out of necessity; an appropriately detailed examination of relevant code examples is far beyond the scope of this tutorial. We believe, though, that the Galaxy Communicator distribution contains enough examples and documentation to allow you to learn the details on your own.

This brings us to the end of our discussion of the structure of servers. We still have three more lessons to go. These lessons will address more details in the Hub which will finally allow you to assemble an entire end-to-end system.

Next: Setting up the Hub to log your interactions


License / Documentation home / Help and feedback
Last updated September 28, 2001