Monday, July 11, 2011

Using dlopen() for fast engine prototyping...

I recently became tired of the cycle of hacking on the renderer, starting the whole engine, checking where and how it's failing, exiting the engine and the pointless time wasted during this process. Typically it's only a specific area I'm working on, thus perhaps one or two C source code files are actually changed, so it doesn't make sense to reload the entire engine and all media assets.

I started working on a smaller project for rapid-fire prototyping, μEngine. Currently it's a pretty basic project. It provides the basic framework for detecting that a shared object has changed, unloading the current version from memory, loading the new library, resolving the required symbols, and continuing the "rendering" loop (which actually doesn't perform any OpenGL rendering yet.)

Here's an example of the engine running:

~/uengine/src (master) $ make && ./uengine 
make: Nothing to be done for `all'.
... .libs/libuengine.so
0xdeadbeef
0xdeadbeef
0xdeadbeef
0xdeadbeef
... .libs/libuengine.so
0xcafebabe
0xcafebabe
0xcafebabe
0xcafebabe
... .libs/libuengine.so
0x0
$ 

While the engine was running, I edited libuengine.c, which is compiled by Automake into a shared object:

$ sed -i 's/0xdeadbeef/0xcafebabe/' libuengine.c && make
  CC     libuengine.lo
  CCLD   libuengine.la

Now I want to terminate the program (and I'm too lazy to have written a signal handler yet) so μEngine's main-loop is setup to terminate when dlsym_main returns a zero value.

$ sed -i 's/0xcafebabe/0x0/' libuengine.c && make 
  CC     libuengine.lo
  CCLD   libuengine.la

μEngine also links with libengine (which provides all of the math/geometry/etc functionality for Trinity), libdecl (which provides access to id Software style declaration files), and liblwo2 (which, well, loads LWO2 model files.)

This little experiment is far from complete. I still need to setup an OpenGL and GLX context, handle X11 input, and keep track of any ARB fp/vp or GLSL programs loaded. GPU programs will be reloaded in much the same way as the shared object itself.

There is of course a few frames delay between invocation of make and the new library being loaded, because should stat fail to obtain the file modification time (because it's still being written out) μEngine just continues with the old shared library until stat succeeds. The modification time is checked every "frame."

I think this should help with faster prototyping, since μEngine has the ability to access all of the assets and libengine library functions that Trinity can, but without the requirement of loading all the assets for a full world map into memory at start up.