Eventus ObscuricusPosted: February 9, 2012
There always comes a time in a programmer’s life when they must deal with an “event loop”. Since the dawn of the teletype, programmers have had to deal with the age old question “should I poll for events, or should I be notified asynchronously”. This very question is baked deeply into our CPU architectures with things like interrupts and queues. It’s simply unavoidable. So, here I sit, at the precipice of unification (2D and 3D) and the question arises, should I poll, or should I be notified.
Well, I’m actually in favor of asynchronous processing because that’s the way the world works in general. Sure, we drive towards things, but more often than not, we are responding to something in our environment. I figure the same should be true of a program. Yes, it has some goals in mind, but largely it should be responsive to activities that occur on the periphery.
HeadsUp is the 2D aspect of BanateCAD. This is where events like mouse and keyboard interaction enter the system. Up until recently, I dealt with these events in a synchronous sort of way, with a typical event loop. Now, I do a hybrid. Yes, there is a fundamental message pump that pulls messages out of the system’s message queue, but then that pump turns around and stuffs them into an asynchronous queue, which is handled by a coroutine (cooperative multitasking). That’s handy for a couple of reasons.
First of all, the message pump does not have to be completely synchronous with the rest of the program. It can just pull messages out of the pipe, and stick them into the queue, rinse and repeat. That makes the message pump very simple, and easily separable from the rest of the program. Second, the part of the program that deals with processing messages does not have to know anything about the mechanics of the message pump. All it knows it that it will be notified when there are messages to be processed. It can then go pull as many messages from the async queue as it likes, and deal with them at its leisure.
Perhaps it’s only a point an asynchronous programmer could love.
Now that there’s a generic queue that can process commands, I want to make everything into commands. So, here’s an example of a very typical case. When I want to draw a line, there is typically some interface connected to the graphics API that looks like this:
That will talk to the rendering engine directly, and a line will magically appear. But, what if under the covers, what was really happening was this:
void Graphics:DrawLine(pt1, pt2)
local cmd = Graphics:CreateDrawLine(pt1, pt2)
From the programmer’s perspective, there’s nothing different in their conceptual model. They call DrawLine(), and that’s the end of it. They assume a line is drawn, and it will be drawn, eventually.
From the system’s perspective, a whole world of possibilities just opened up. Now that the command is packaged up and stuck into a queue, it could be immediately removed from the queue, and really executed against the graphics system, or it could be stored off in some persistent store somewhere, possibly on a distant part of the planet. And, as long as you’ve got commands all packaged up and ready to go, you could ship them to other renderers while you’re at it. That might be interesting and useful.
At any rate, it’s a fairly fundamental question to deal with. Should you poll, or be notified, along with, should you call directly, or issue commands. In the case of BanateCAD, there is a hybrid of poll/notify, and commands are definitely the way to go.