Dec 29
Foray Embedding Pythong
I was bored today and decided to poke around with Boost.Python, because I’ve never used it before. I didn’t want to write a trivial “hello world” application, I so instead decided to write a cellular automata framework which reads in a Python script for the atomic cell update stuff.
Turns out that it’s actually really nice (once it’s working, lawds), to expose a function there’s some sickening hacks that allow this beautiful syntax -
int getCell( int x, int y ) {
return *getCell_( x, y );
}
void setCell( int x, int y, int v ) {
*getCell_( x, y ) = v;
}
BOOST_PYTHON_MODULE( CA ) {
def( "getCell", getCell );
def( "setCell", setCell );
}
Loading the python script from file wasn’t too hard. The tutorial code I was using was a little bit off – the tutorial code needs to pass in the namespace for both the global and local variable dictionaries otherwise the interpreter shits all over itself (at least it did for me, running Python2.5 etc). Anyway, the code to actually create the main namespace for the interpreter and load in the script from a file looks like this -
object main_module, main_namespace;
try {
main_module = import( "__main__" );
main_namespace = main_module.attr( "__dict__" );
exec_file( argv[4], main_namespace, main_namespace );
}
catch( error_already_set const& ) {
PyErr_Print();
running = false;
}
Which gets used to (in the debugger) load the following Python script:
import CA import random g_neighbors = [] g_width = 0 g_height = 0 def init( width, height ): globals()["g_width"] = width globals()["g_height"] = height for y in xrange( 0, height ): for x in xrange( 0, width ): CA.setCell( x, y, random.randint( 0, 1 ) ) globals()["g_neighbors"].append( 0 ) def precalc(): for y in xrange( 0, g_height ): for x in xrange( 0, g_width ): g_neighbors[ y * g_width + x ] = 0 for y in xrange( 0, g_height ): for x in xrange( 0, g_width ): if CA.getCell( x, y ): for iy in xrange( -1, 2 ): for ix in xrange( -1, 2 ): if ( ix or iy ): g_neighbors[ ( ( y + iy ) % g_height ) * g_width + ( ( x + ix ) % g_width )] += 1 def update( x, y ): #count = 0 #for iy in xrange( -1, 2 ): # for ix in xrange( -1, 2 ): # if ( ix or iy ): # count += CA.getCell( x + ix, y + iy ) count = g_neighbors[ y * g_width + x ] if CA.getCell( x, y ): if count < 2: return 0 elif count > 3: return 0 else: return 1 else: if count == 3: return 1 else: return 0
Calling the functions from within the C++ application is fairly straightforward, because the generic PyObject wrapper that Boost.Python provides defines an overloaded operator(), which makes calling the grabbed functions really easy:
object init = main_namespace["init"]; init( screen_width, screen_height );
Despite the ease with which I was able to embed Python into my application with Boost.Python, there was a significant performance overhead for doing so. The Python implementation of Conway’s Life performed approximately 4x slower than the C++ implementation. While for many applications the advantages of writing the logic in Python may far outweigh performance losses, for cellular automata it just isn’t feasible, since the logic is the primary bottleneck for the simulation.
Fun to tinker with, nonetheless. The source and binary can be obtained here (165KB).
Tagged with: boost, conway's life, python, sepples
2 comments
2 Comments so far
Leave a comment

Look into the global statement to avoid those global()['something'] hacks.
Thanks for the suggestion! Someone on #gamedev actually mentioned the same thing (in addition to using some better sugar to operate on the list itself). Ultimately, I really should go back and spend some more time learning the more fundamental language features :)