RAVE
|
A lot of work done in RAVE is number crunching in different ways.
Sometimes it can be re-projecting from one area to another, other times it's more demanding algorithms that must be performed.
So, why have we decided to go for C instead of high-level languages like Python and Java. The easiest answer is that we had an old legacy that we have refined, but fortunately there are other reasons as well. C is fast and since some algorithms will always be demanding it's better to prepared for the worst. Another reason is that it is fairly easy to add both Python and Java APIs on top of C by using the native interface support in those languages.
Let's take a look at the core of the transform library (<ravesrc>/librave/transform).
C might be a bit awkward to use since you need to be careful about memory management. We have tried to make parts of this processing more easily manageable by using a concept where we are using reference counters and objects instead of pure memory allocation. This is not completely true since you still will need to allocate memory at times, but we try to facilitate this.
First, we have rave_object.h. This file is essential for manipulating objects within RAVE. It is somewhat similar to how Python has implemented its object support.
There are five macros that are really necessary and will be used quite a lot.
PolarScan_t* scan = RAVE_OBJECT_NEW(&PolarScan_TYPE);
RAVE_OBJECT_RELEASE(scan);
RaveScan_t* copy = RAVE_OBJECT_COPY(scan);
RaveScan_t* clone = RAVE_OBJECT_CLONE(scan);
if (RAVE_OBJECT_CHECK_TYPE(scan, &PolarScan_TYPE))
NOTE: Regardless if you are using RAVE_OBJECT_NEW, RAVE_OBJECT_COPY or RAVE_OBJECT_CLONE you always should call RAVE_OBJECT_RELEASE when you are done with the object.
PolarScan_t* src = RAVE_OBJECT_NEW(&PolarScan_TYPE); // Create a new instance PolarScan_t* copy = RAVE_OBJECT_COPY(src); // same object as src (but reference count is increased) PolarScan_t* clone = RAVE_OBJECT_CLONE(copy); // identical copy but not same // if (src == copy) evaluates to true but // if (src == clone) will evaluate to false. RAVE_OBJECT_RELEASE(src); // decrease reference count RAVE_OBJECT_RELEASE(copy); // decreases reference count and object is destroyed RAVE_OBJECT_RELEASE(clone); // decreases reference count and object is destroyed
If you are uncertain if you have managed to release all objects when the application terminates or if you are curious on how many pending objects you have at a particular time, the function RaveCoreObject_printStatistics will print it out on stderr. If you want such statistics to be printed when the application terminates, add this call during initialization of your program.
if (atexit(RaveCoreObject_printStatistics) != 0) { fprintf(stderr, "Could not set atexit function"); }
If the Python API modules are used, this has already been done so you do not need to bother.
There are a few more macros that might be useful when wrapping the objects for other languages or similar situations but that usage must be used with caution.
RAVE_OBJECT_BIND(scan, boundobject);
RAVE_OBJECT_UNBIND(scan, boundobject);
if (RAVE_OBJECT_ISBOUND(scan))
PyPolarScan* this = (PyPolarScan*)RAVE_OBJECT_GETBINDING(scan);
We will investigate the bindings later on in the RAVE Python API documentation. But for now, just be careful if you decide to use it. By the way, RAVE_OBJECT_COPY will leave the binding as is but RAVE_OBJECT_CLONE will not pass it on.
This is the other API that provides some help when it comes to memory allocation. These macros basically debug all allocations you perform (if you use them). Since this is a quite heavy operation, we only debug memory if RAVE has been built with the compile-flag -DRAVE_MEMORY_DEBUG, otherwise it will basically be the same as calling the standard C-functions directly.
If you have enabled memory debugging, you will get notifications if you are accessing memory out of bounds, if you try to free memory != NULL that already has been freed, and so forth. However, you will still not get a dump of lost memory. To be able to get that, you will need to do one thing manually in your main program or module.
if (atexit(rave_alloc_print_statistics) != 0) { fprintf(stderr, "Could not set atexit function"); }
This little code segment will call the function rave_alloc_print_statistics when the program exits. This is automatically supported if you are using our Python modules, but if you prefer to write your own program using the transform library you need to do this.
The final header file that provides some useful macros is rave_debug.h. As it sounds, it contains some debug macros. Since we do not know if the system will be compiled with a compiler that can manage macros with variable argument lists, we have provided macros that can take up to four (4) arguments. If you need more, then you will need to create your own strings and pass them to an appropriate macro.
Some macros will always be enabled and other will only be available when compiling with -DDEBUG_RAVE. To be able to get the printouts you need to set a debug level by calling the function #Rave_setDebugLevel. The default level is always silent, meaning that nothing will be printed except CRITICAL errors. The levels that are possible to print out without enabling -DDEBUG_RAVE are:
Speaking of which, you are able to send all printouts to your own logger. This is done by using #Rave_setDebugFunction.
static void MY_debugFunction(char* filename, int lineno, Rave_Debug lvl, const char* fmt, ...) { if (lvl == RAVE_CRITICAL) { fprintf(stderr, "%s:%d caused a CRITICAL ERROR\n", filename, lineno); abort(); } } .... Rave_setDebugFunction(MY_debugFunction);
There is another thing that also can cause mischief related to the debug macros and that is the abuse of #RAVE_ASSERT. This macro is currently not possible to deactivate or disable in any way. By using this macro you have actually set up a contract on your code that says that unless the given criteria are fulfilled, I will crash the system.
Some people are against this type of behaviour since it forces a running system to dump instead of trying to recover or ignoring the recognized problem. The problem is actually that these macros can be used improperly and in that case they are really, really bad. If they are used with caution you might actually gain from them instead as they will give you an immediate information on where the problem occured instead of having a system that runs a bit further and then crashes for unknown reasons.
A good example on how to use the assert:
static int MyObject_setSize(MyObject_t* self, int sz) { RAVE_ASSERT((self != NULL), "self == NULL"); self->sz = sz; return 1; }
Here we say that if you pass in a self-pointer that is NULL, then there is something fundamentally wrong in your code.
A bad example on assert usage:
static int MyObject_setSize(MyObject_t* self, int sz) { RAVE_ASSERT((sz > 0 && sz < 10), "sz <= 0 or sz >= 10"); self->sz = sz; return 1; }
Here, on the other hand, we say that if you try to set a value that not is in the interval 1 - 9, you should crash the system. This might be a bit harsh, but, as usual, it's a matter of taste.
Personally, I would have implemented it like this:
static int MyObject_setSize(MyObject_t* self, int sz) { int result = 0; RAVE_ASSERT((self != NULL), "self == NULL"); if (sz > 0 && sz < 10) { self->sz = sz; result = 1; } return result; }
This way I would have ensured that nobody atempts to call the object without the object itself and if someone is trying to set a value outside 1 - 9, I will return 0 instead of 1.
With this knowledge, it is time to take a look at the RAVE objects that are currently implemented and that are therefore at your disposal.