Friday, April 18, 2008

Moving windows, forks, shares, and ARG!

I just spent the last 3 days working on a problem that SHOULD have been incredibly simple. Take a few windows, slide them to their new positions. The devil was, however, in the details.

Each of these windows is owned by a loadable module. Each module has a binding that translates requests form the loader into commands that the API used by the binding can use. This allows multiple graphics engines to run side by side and opens up all sorts of cross platform capabilities in the future.

Now, each module is loaded by the, well, loader, then forked off with a block of anonymous shared memory between them for message passing. This means that the loader has no way to execute commands directly in the process space of the module. Now comes the tricky part.

Moving a window around. xlib has a simple call for this, move window. The bindings do not expose the Window reference directly though, instead they take a 'move' signal. Each module is runing in it's own space and is thus subject to the scheduling whims of the kernel. So if you do something like:

for(modules module)
module->move(x,y);

you will get a whole mess of modules all moving themselves in the order and timing of the kernel's liking. Unfortunately these are low level graphics modules, which means they do not get the nice automatic refresh that you would get on a desktop. End result? Artifacts as they slide over eachother.

Now, a way to deal with this would be to have each one call the other modules and say 'hey, I drew over you, refresh yourslf' with the problem that they are all running at different speeds and thus no messages can get across quickly enough. The loader can in theory coordinate them but at best it can only suggest when to draw, it can't control the execution dirrectly.

So what I ended up trying to do was using a set of mutexes to let the loader control when each one drew. Essentially:

for(modules module)
module->lock();

for(frames)
{
for(modules module)
module->unlock()
sched_yield()
module->lock()

for(modules module)
module->unlock()
sched_yeild()
module->lock()
}
for(modules module)
module->unlock()

And on the module side:

while(!done)
Lock()
Move(one_frame_x,one_frame_y)
Unlock()
sched_yeild()
Lock()
Redraw()
Unlock()


So each module has it's mutex released in turn, gets to move it'self, then each module in turn gets to draw it'self.

Oh, that should have worked, but didn't. It turns out (and this took forever to track down) under the 2.4 kernel semaphores can't be shared between processes (threads are ok, but forked processes fail) and mutexs just quietly don't work.

Well, you learn something new every day.
Ugh. The things that should be simple.

No comments: