Synchronized list and map data structure modules for BlitzMax. Just a simple mutex-locked wrapper over the BRL types to make operations atomic. Should be compatible with old code.
Prebuilt for Windows with source. Built using 1.33rc5, but 1.32 should also work. Linux and Mac versions of BRL.Threading have a problem that will hopefully be resolved soon.
To install, extract the zip under mod/otus.mod and rebuild docs.
Download: Synchronized v1.01 (zip, 71KB) License: None/Public Domain
I threw together a parser for a simple data description language in BlitzMax. The syntax is something like that in Python or Lua, but not quite. Whitespace is always insignificant and there are no fatal syntax errors: the whole file is always parsed, and non-conforming markup is ignored (silently by default).
Sample structures:
num = 1.234e-56 str = 'Hello "World"' list = [1,2,3] map = {1=2, 3=4} array = (0,1,2)
Download: otus.dataparser.zip
Install: Extract to BlitzMax/mod, build modules, rebuild docs.
I've come to like the expressiveness of the Python language, though I dislike some of the other features. Here's a 50 line raytracer that renders scenes of colored spheres to a PNG file:
import math from PIL import Image
def sub(v1, v2): return [x-y for x,y in zip(v1, v2)] def dot(v1, v2): return sum([x*y for x,y in zip(v1, v2)]) def norm(v): return [x/math.sqrt(dot(v,v)) for x in v]
def trace_sphere(r, s, tmin, color): sr = sub(s[0], r[0]) dsr = dot(sr, r[1])
d = dsr*dsr - dot(sr,sr) + s[1] if d < 0: return d = math.sqrt(d)
t1 = dsr + d if t1 < 0: return
t2 = dsr - d if t2 > tmin[0]: return
if t2 < 0: tmin[0] = t1 else: tmin[0] = t2 color[:] = [s[2]]
def trace_ray(ray, spheres): color = [(0, 0, 0)] tmin = [100000] for s in spheres: trace_sphere(ray, s, tmin, color) return color[0]
red = (255,0,0) blue = (0,0,255) spheres = ( ((0,0,100), 100.0, red), ((0,-5,50), 20.0, blue) )
w, h = 200, 200
image = Image.new("RGB", (w, h)) pix = image.load()
for x in range(w): for y in range(h): ray = ( (0,0,0), norm(((x-w/2.0)/w, (y-h/2.0)/w, 1)) ) pix[x,y] = trace_ray(ray, spheres)
image.save("out.png")
Ok, it's more like 30 significant likes to tell the truth.
This tutorial attempts to show the basics of working with C in your BlitzMax programs. BlitzMax manual/help has a section titled "Interfacing with C and other languages", but I find it short and confusing. A PDF version (PDF, 142KB) of this tutorial is available, as are sources for samples (ZIP, 3KB).
You should know the basics of C to understand what this is all about. If you know Java or some other language that has a C-like syntax, you should be able to follow. I recommend the WikiBook on C, if you need to know more about the language itself.
To compile C on Windows you need to install MinGW as detailed elsewhere. On other platforms GCC is likely already installed, so you don't have to do anything.
On BlitzMax side you should understand pointers: BlitzMax has two kinds of by-reference variable types: the Ptr-types and the Var-types. With the former you need to explicitly create the pointer using VarPtr or a cast, with Var-types the conversion is done implicitly by the compiler. Both are useful for different purposes. See the docs for syntax.
I always use SuperStrict and Framework. SuperStrict is important when you reference C programs, because Strict- and non-Strict-mode assume that all functions return an Int. Framework just makes compiling a lot faster, so there's no reason not to use it.
(I use BRL.StandardIO for command line programs, MaxGUI.MaxGUI for GUI programs and BRL.Max2D for graphics. That's just to make sure I can import files across projects.)
Let's begin with the following C code that you wish to call from your BlitzMax program:
void Say(char *str)
{
printf(str);
}
To use the function from within BlitzMax you need to import the file. C files are imported just like BlitzMax files, which is rather handy. However, Import only tells BMK to link the file - you still need to declare the function in BlitzMax using Extern. Here's how:
Import "tutorial1.c"
Extern
Function Say(str:Byte Ptr)
End Extern
Say "Hello World!".ToCString()
Note the case! Unlike BlitzMax, in C case matters. Since the C-function was named Say, the declaration in Extern must match that. Later on in your BMX-code you don't have to worry about it anymore.
Ok, that works, but it's messy to convert the string to C-format and a real program would need to MemFree the string after use. Thankfully there's an undocumented feature in BlitzMax that helps:
Extern
Function Say(str$z)
End Extern
Say "Hello World!"
That type tag $z works just as $ or :String would on the BlitzMax side, but the compiler converts the string to C-format before passing it to the function. It also automagically handles memory management, so you don't need to worry about leaks from unfreed memory.
Here's the final program:
' tutorial1.bmx
SuperStrict
Framework BRL.StandardIO
Import "tutorial1.c"
Extern
Function Say(str$z)
End Extern
Say "Hello World!"
// tutorial1.c
void Say(char *str)
{
printf(str);
}
Next, suppose you need to call a BlitzMax function from C. To keep things simple we'll create a function that works just like printf, so we can just replace the call in the above example:
Function PrintCString(s:Byte Ptr)
Print String.FromCString(s)
End Function
Next, let's modify the C-code to call that function. For that we need to declare the function in C. Again, case is important. The declaration would normally go to a header file (.h), but here we do it in the same file:
void bb_PrintCString(char *s);
void Say(char *str)
{
bb_PrintCString(str);
}
Note the bb_ -prefix. That is added to main program functions by the Blitz compiler to prevent name clashes. If you need to call a function from a module, you need to use something like brl_standardio_Print instead.
(Calling the "main" part of Blitz files is also possible using _bb_main for the main program or __bb_standardio_standardio for a module. Perhaps more usefully MemFree and MemAlloc can be called using bbMemFree and bbMemAlloc.)
Now the program should work just as before, but with Print instead of printf doing the job. Notice that I've decided to call the Say function SaySomething instead to show the syntax:
' tutorial2.bmx
SuperStrict
Framework BRL.StandardIO
Import "tutorial2.c"
Extern
Function SaySomething(str$z)="Say"
End Extern
Function PrintCString(s:Byte Ptr)
Print String.FromCString(s)
End Function
SaySomething "Hello World!"
// tutorial2.c
void bb_PrintCString(char *s);
void Say(char *str)
{
bb_PrintCString(str);
}
Ok, now let's attempt to do something remotely useful for a change. Using math functions in BlitzMax can be slow compared to C, due to the lack of compiler inlining. This isn't usually a big problem, but if you need several Sqrs and trigonometric functions over a large set of data, you can do it faster with C.
To keep the code simple we'll just need to take a vector of the form (x,y,z), normalize it and return the length. (This is way too simple to actually require C, though.)
Here's the C code:
float Normalize(float *x, float *y, float *z)
{
float length = sqrt( x[0]*x[0] + y[0]*y[0] + z[0]*z[0] );
float m = 1 / length;
x[0] *= m;
y[0] *= m;
z[0] *= m;
return length;
}
Simple enough: the notation x[0] means the data immediately at location x - exactly as in BlitzMax. Now the straightforward way would be to declare the arguments as Float Ptr on BlitzMax side, but that would require the use of VarPtr or something similar when passing variables. Instead we'll use Float Var:
Extern
Function Normalize:Float(x:Float Var, y:Float Var,..
z:Float Var)
End Extern
Now the variables can be passed like to any function (but note that they have to be variables, literal numbers or expressions won't do):
Local x:Float = 11.9, y:Float = -3.5, z:Float = 0.1
Print Normalize(x,y,z)
Print "("+x+","+y+","+z+")"
And here's the whole thing:
' tutorial3.bmx
SuperStrict
Framework BRL.StandardIO
Import "tutorial3.c"
Extern
Function Normalize:Float(x:Float Var, y:Float Var,..
z:Float Var)
End Extern
Local x:Float = 11.9, y:Float = -3.5, z:Float = 0.1
Print Normalize(x,y,z)
Print "("+x+","+y+","+z+")"
// tutorial3.c
float Normalize(float *x, float *y, float *z)
{
float length = sqrt( x[0]*x[0] + y[0]*y[0] + z[0]*z[0] );
float m = 1 / length;
x[0] *= m;
y[0] *= m;
z[0] *= m;
return length;
}
There are several different use cases with objects and Blitz/C interface. In the simplest case you only need to pass an object to the C side, maybe to return it again later, but without the need to access the data.
Passing an object from BlitzMax works just like passing a primitive. To pass and accept Blitz-strings (strings are objects) you could declare external functions like this:
Extern
Function StringTaker(str:String)
Function StringGiver:String()
End Extern
Nothing special there yet. To accept it on the other side, your C functions must be declared as using a byte (char) pointer:
void StringTaker(char *str);
char * StringGiver();
However, this approach does not let you do anything much with the object. Sure, you can store it somewhere and return it again from elsewhere, but that's about it. (Note: always make sure the object is not garbage collected by also retaining a reference on Blitz side.)
Instead, you can pass a pointer to the object's data by casting the object to a byte pointer in Blitz. Let's create a simple vector type to test this:
Type TVector
Field x:Float
Field y:Float
Field z:Float
End Type
Extern
Function NormalizeVector:Float(v:Byte Ptr)
End Extern
Ok, now the object is implicitly converted by the Blitz compiler to a pointer to the first field when passed. And we don't even have to cast explicitly, since Object->Byte Ptr casts are automatic. So we can just use it like this:
Local v:TVector = New TVector
v.x = 4.2
v.y = 2.4
v.z = 42.0
Print NormalizeVector(v)
Print "("+v.x+","+v.y+","+v.z+")"
Now, how about the implementation? We can almost copy the Normalize function used earlier. We just need to remember that y and z fields are offset from the x field. Like so:
float NormalizeVector(float *v)
{
float length = sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );
float m = 1 / length;
v[0] *= m;
v[1] *= m;
v[2] *= m;
return length;
}
I used a float pointer instead of a byte pointer, since both are just memory addresses. This is a good example of how little the compilers hold your hand when doing cross-language stuff: no warnings are emitted even though function declarations differ in type.
That's it. The whole program follows. As always with pointers, you must be careful not to access out of bounds. Also, as I mentioned above, you must make sure the object is not garbage collected by retaining a Blitz reference:
' tutorial4.bmx
SuperStrict
Framework BRL.StandardIO
Import "tutorial4.c"
Type TVector
Field x:Float
Field y:Float
Field z:Float
End Type
Extern
Function NormalizeVector:Float(v:Byte Ptr)
End Extern
Local v:TVector = New TVector
v.x = 4.2
v.y = 2.4
v.z = 42.0
Print NormalizeVector(v)
Print "("+v.x+","+v.y+","+v.z+")"
// tutorial4.c
float NormalizeVector(float *v)
{
float length = sqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] );
float m = 1 / length;
v[0] *= m;
v[1] *= m;
v[2] *= m;
return length;
}
I created a BlitzMax program to filter* and compress images using either LZMA SDK or zlib. Here are the results for three test images I picked up from Wikipedia: Mona Lisa (painting), The Gunk (pixel art) and Grad1 (gradient). All compression was done with level 9 (smallest size).
Image Size (BMP) Size (zlib) Size (LZMA)
Painting 612 KB 347 KB = 56.7% 287 KB = 46.9% (17.3% better)
Pixel art 93 KB 13 KB = 13.4% 11 KB = 11.4% (15.2% better)
Gradient 63 KB 28 KB = 44.9% 23 KB = 36.3% (18.9% better)
Image Compress (zlib) Compress (LZMA)
Painting - ~160ms ~630ms
Pixel art - ~70ms ~90ms
Gradient - ~45ms ~75ms
Image Uncompress (zlib) Uncompress (LZMA)
Painting - ~40ms ~40ms
Pixel art - ~1ms ~1s
Times are not very exact for such small operations, but the results seem to suggest that LZMA compresses more and slower, but uncompresses as fast as zlib. That makes LZMA a good choice for precompressed data, while zlib may be better for real time compression.
Note: Filtering times are not included.
*The filter uses Paeth prediction similar to PNG.