Menu:

Download

Gnofract 4D Internals

This section explains how Gnofract 4D is structured. You don't need to know any of this to use the program, but it may come in handy if you want to change it or contribute to its development (which you're heartily encouraged to do).

Gnofract 4D is implemented primarily in Python, with some C++ extensions. Extensive use is made of Python unittest framework to keep everything working - each Python file foo.py is accompanied by test_foo.py, which contains unit tests for that file's features. 'test.py' for each folder runs all of the tests.

Source Code Layout

The important directories in the source are:

DirectoryContents

fract4d

This contains all the non-GUI-related, relatively platform-independent parts of the code. This is in case it ever needs to be ported to another environment (eg run on a server without a GUI as part of a cluster). Most of the files here are parts of the compiler (see below). The main class which represents a fractal is in fractal.py. This holds references to the compiled code, the formula and colorfunc definitions, the parameters and the colormap. It also handles loading and saving information from a .fct file, and provides wrappers for some of the gnarlier C++ extension functions.

fract4d/c

This contains the C++ extension code which is compiled to produce fract4dc.so. This is divided into a set of classes which communicate primaily via interfaces. The main responsibility of this code is to call the 'pointFunc' (the function which calculates a single pixel) once for each point on the image. This code also does the bulk of the '4D' manipulation - vectors.h contains code for 4-vectors and 4x4 matrix math. This library also handles multi-threaded calculations, parcelling out the work to multiple MTFractWorkers via the queue in threadpool.h

fract4dgui

This contains the python code which implements the GUI. It uses PyGTK as the GUI toolkit. The earliest PyGTK we support is 1.99, which has some annoying incompatibilities with newer PyGTK's like 2.4. I need to work out whether to ditch the older library altogether or try to come up with some wrappers to hide the differences. Basically there's one class per dialog or custom control, and a few other for utility purposes. The central class is gtkfractal, which wraps a fractal and displays the results of the calculation in a window.

Compiler

The most complicated part of Gnofract 4D is the compiler. This takes as input an UltraFractal or Fractint formula file, and produces C code. We then invoke a C compiler (eg gcc) to produce a shared library containing code to generate the fractal which we dynamically load.

The UltraFractal manual is the best current description of the formula file format, though there are some UltraFractal features which are not yet supported. You can download it from here.

The implementation is based on the outline in Modern Compiler Implementation in ML: basic techniques (Appel 1997, Cambridge). It doesn't do any optimization at this point, leaving that to the C compiler used as a back-end. It would be worthwhile to do some simple optimization (eg constant-folding, removing multiplication by 1.0) because the C compiler refuses to do this to floating point numbers.

Overall structure: The PLY package is used to do lexing and SLR parsing - it's in lex.py and yacc.py. fractlexer.py and fractparser.py are the lexer and parser definitions, respectively. They produce as output an abstract syntax tree (defined in the Absyn module). The Translate module type-checks the code, maintains the symbol table (symbol.py) and converts it into an intermediate form (ir.py). Canon performs several simplifying passes on the IR to make it easier to deal with, then codegen converts it into a linear sequence of simple C instructions. stdlib.py contains the 'standard library' of mathematical functions, like cosh(z). It's at this point that complex and hypercomplex variables are expanded out into pairs of floating point numbers - the C code is oblivious to the complex numbers. Finally we invoke the C compiler to convert to a native code shared library.

At runtime the different phases happen at different times. First, the entire .frm file is lexed and parsed. Then when a particular formula is selected, it's translated and syntax-checked. The actual code is only generated just before the fractal is drawn. This phase is repeated whenever the function parameters are changed (eg @fn1 is set to 'cosh').

Probably the ugliest part of the code is the handling of parameters. Numeric parameters like floats are passed in as an array, and the C++ code and Python code need to collaborate to work out which indices into this array correspond to which params- this is done by sorting them into alphabetic order. In general this area is a bit of a mess.

Threading

One of the weirder parts of the code is how we deal with threading. Basically we want the calculation of the fractal to happen on a different thread (or multiple threads for SMP) from the main UI, so you can interrupt at any point. This is complicated by the fact that Python only allows a single thread in the Global Interpreter Lock, and that PyGTK is often compiled by Linux distribution vendors without thread support, meaning this lock is not released when running the GTK main loop.

The way out of this is that the additional threads live only in the C++ code, where they are invisible to the Python code and GTK. When pycalc is called with asynchronous=True, it spawns a thread to do the calculation, which may in turn spawn more workers if we want multiple threads. These all write to the image buffer and report back what they're doing by writing messages into a pipe. This pipe is added to the list of things the GTK main loop monitors, so whenever a new message appears we get a callback into the gtkfractal code, interleaved with the normal GTK events. We can interrupt a calculation in progress by setting a var which the calculation threads check frequently - they then abandon their work and quit.

Warning

Multiple threads and C++ exceptions do not coexist well, at least on some of the libstdc++'s that Gnofract 4D runs with. So the C++ code can't throw exceptions or very odd things including crashes will happen.