BlitzMax tutorials and articles

There are currently 4 tutorials available:

BlitzMax Memory management - or, "where did Delete go?!?"

As many users from a Blitz3D or BlitzPlus background have noticed, BlitzMax does not include a 'Delete' command.

This appears to have caused a great deal of bemusement and head scratching in the Blitz community, which this tutorial will attempt to clear up.

Note that any references to Blitz3D in the rest of this document also apply to BlitzPlus.

The BASICs

First up, let's have a look at what Blitz3D's Delete command actually does. It really serves 2 purposes:
  1. The object is removed from its 'type list'.
  2. The memory used by the object is returned to the system - this is called 'deallocating' the object.
Step 1 is necessary due to Blitz3D's type list system, which automatically adds all New-ed objects to an internal global type list.

Step 2 is necessary to prevent memory leaks. A memory leak is when your program has allocated a chunk of memory, used it for something, and then forgotten to deallocate it when it is no longer useful. Since computers have a finite amount of memory, this will eventually cause problems if it happens often enough!

The first thing about these 2 steps to note is that the global type list system is no longer present in BlitzMax, so step 1 is now completely up to the user - if you have added an object to a linked list (or lists), it is up to you to remove it.

If all you are wanting to achieve is a system similar to Blitz3D's type list system, then this is as simple as using ListAddLast immediately after you have created an object using New, and using ListRemove immediately before deleting it using Delete - oops, there is no Delete! Ok, using ListRemove *instead* of Delete!

This still leaves the question of deallocating the object's memory, and this is done in BlitzMax using a 'garbage collector'. A garbage collector automatically scans for 'dead objects' every now and then and releases the memory they occupied.

Note that the issue of deallocating objects is completely separate from the issue of type lists. Yes, if you forget to remove an object from a list it will remain 'live', but this is the same as forgetting to delete an object in Blitz3D!

One final complicating issue: BlitzMax includes a feature that allows you to assign objects to integers. This feature exists mainly to simplify things for beginners so, instead of...
Local image:TImage=LoadImage( "somepic.png" ) 'normal object to object assignment...
...you can just go...
Local image=LoadImage( "somepic.png" ) 'assigning an object to an int! What gives?
However, if you are using this feature in your programs, you *must* later 'free' the object yourself using Release...
Release image
You can therefore think of Release as being similar to Blitz3D's FreeImage (or FreeSound, FreeThis, FreeThat etc).

All-in-all, it's probably best to avoid object-to-int assignments altogther if you are concerned about memory leaks.

And that's it! Well, not quite, but the above should be enough information for you to be able to write leak-free programs with. I'll go into some of the nitty gritty details below...

The nitty gritty part 1 : dead objects

Ok, lets have a look at the question of 'what exactly is a dead object'? Well, a dead object is an object that is no longer referred to by any live objects. In other words, dead objects are objects your program can no longer 'see' - they are therefore completely useless to your program and can be safely deallocated.

Here are a few examples of when objects become 'dead':
'example 1
Local p:MyType=New MyType 'allocate object 1
p=Null 'object 1 is now dead

'example 2
Local p:MyType=New MyType 'allocate object 1
Local q:MyType=New MyType 'allocate object 2
p=q 'object 1 is now dead, object 2 is still alive

'example 3
Local p:MyType[1] 'allocate object 1 (an array object)
p[0]=New MyType 'allocate object 2
p=Null 'object 1 and object 2 both now dead

'example 4
Local p:MyType=New MyType 'allocate object 1
p.my_type=New MyType 'allocate object 2
p.my_type=New MyType 'allocate object 3 - object 2 now dead
Local q:MyType=p.my_type
p=Null 'object 1 and object 2 dead, object 3 still alive thanks to the q assignment above
After seeing this sort of thing, there is a tendancy for people to stick '=Null' code all over the place in an attempt to 'force' objects to become dead - but this is generally not necessary.

Sooner or later, all variables will either be overwritten by new values, or (in the case of Locals and Fields) simply go out of scope. Both of these actions are enough to ensure that dead objects will be correctly detected and deallocated.

Probably the only good reason to use '=Null' is in the case of global variables, when you know that a global variable will not be modified for a long time, but it also happens to be keeping a large chunk of memory alive that is no longer useful to your program. In this case, an '=Null' is probably the right choice.

BlitzMax also provides the GCMemAlloced() function to determine the amount of memory currently used by your program.


Crash course in OO programming for Blitz coders

Welcome to a crash course in OO programming for Blitz coders!

OO stands for 'Object Oriented'. OO is a programming paradigm or 'way of thinking' that has taken on almost mythical proportions over the years yet, somewhat unusually for the world of computing, is actually quite useful!

*Disclaimer* - OO coding means different things to different people. This article is strongly swayed by my own experience with OO coding and what I like and find useful about it. The actual 'meaning' of OO is almost as fun to argue about as whether the universe is infinite or not.

First up, OO coding has spawned a bunch of initially confusing but actually quite sensible buzzwords, including such gems as:

Inheritance
Polymorphism
Virtual
Abstract
Concrete
Type
Class
Object
Instance
Base Class
Derived Class
Super Class
Sub Class

Scary stuff!

Well, not really. Never forget that behind the scenes its all just a bunch of bits and bytes and Ifs and Adds and Gotos.

As always, its easier than the guys who get paid megabucks to do it would have us think.

Part 1 : Inheritance

Inheritance is a way of 'composing' objects from other objects.

This is already possible in procedural Blitz. Consider the following code...
Type Part1
Field x,y,z
End Type

Type Part2
Field p1.Part1
Field p,q,r
End Type

Function UpdatePart1( t:Part1 )
'Do something clever with a Part1.
End Function
This is one way of composing objects. Part2 'contains' a Part1, via the p1 field.

If you've got a Part2 object and want to call UpdatePart1(), you can grab the p1 field and use it in the function call, for example:
Local t:Part2=New Part2
t.p1=New Part1 'need to create it!
UpdatePart1 t.p1
OO inheritance provides a different (not always better!) way to do this:
Type Base
Field x,y,z
End Type

Type Derived Extends Base
Field p,q,r
End Type

Function UpdateBase( t:Base )
'Do something clever with a Base object.
End Function
Ok, we've already hit our first OO buzzwords - Base and Derived. These are not special keywords, they are just names I chose for these types. Bear with me and hopefully you'll get some idea of why I chose them.

First up, The Base type is just the same as the procedural Part1 type - no problem there.

The Derived type, however, introduces us to the Extends keyword, which is used to indicate inheritance.

Extends always appears after 'Type whatever', and means that the type you are describing extends a different, existing type. So the type you are describing is composed not only of the fields and stuff you declare, but also the fields and stuff of the type you are extending!

This means that the Derived type above actually has the additional fields x,y,z - these have been 'inherited' from the Base type. It is then perfectly legal for you to code up something like:
Local t:Derived=New Derived

t.x=100 'x inherited from Base - thanks Base, you rock!
Another way to think of it is the Derived type 'is a' Base type, but with some extra stuff. In fact, in OO terminology 'IsA' is often used to indicate inheritance or inheritance-like properties.

Now it gets a bit interesting. Since our Derived type 'is a' Base type, its perfectly legal to use it where ever a Base type is expected - for example, the UpdateBase() function:
Local MyDerived:Derived=New Derived 'fine...
UpdateBase MyDerived 'What?!?
Initially, this looks *wrong* - 'Type mismatch error' looms! But in OO code its fine. Since Derived extends Base, its OK to use a Derived where ever a Base is expected - even in assignments:
Local MyBase:Base=New Derived 'Okay, because Derived extends (or 'Is a') Base.
In addition, there could be many types extending Base, and each of these could be used interchangeably with Base.

Hopefully the terms 'Base' and 'Derived' now mean something. The Derived type is derived from, or 'comes from', the Base type.

Remember that Base and Derived are just names I chose for this example. In real world coding, you'd use useful names, for example:
Type Actor
Field x,y,z
End Type

Type Player Extends Actor
Field lives,score
End Type
But still, you can think of Actor as the base type, and Player as the derived type - a Player is derived from an Actor.

Its also perfectly legal (and very useful!) to extend extended types, for example:
Type Actor
Field x,y,z
End Type

Type Player Extends Actor
Field lives,score
End Type

Type Enemy Extends Actor
Field mood
End Type

Type Troll Extends Enemy
Field grudge_quotient
End Type

Type Ghoul Extends Enemy
Field lust_for_life
End Type
So, if you've got a function like UpdateActor( t:Actor ), you can pass it any of the above types as they are all ultimately derived from Actor.

Similarly, a RethinkMood( t:Enemy ) function could be used with any of the bad guys (Enemy, Troll or Ghoul) - but not with players, as Player is not derived from Enemy.

This is what is known as a 'class hierarchy' - a 'tree' of types.

Stay tuned for part 2...!


The Nehe OpenGL tutorials

The NeHe tutorials are a popular collection of OpenGL programming tutorials.

Here are the first 5 NeHe tutorials converted to BlitzMax: For more information on these tutorials and to learn lots about OpenGL, check out the Nehe website at http://nehe.gamedev.net.


Writing custom stream handlers

BlitzMax's OO design allows you to add your own kinds of streams to the system.

In this tutorial we will concentrate on creating a custom stream that simply converts any text written to or read from the stream to uppercase. Not very useful, but a good starting point for your own experiments!

The source code for this tutorial can be found here.

To create a custom stream, you will need to create a new user type that extends the TStream type. Your type will also need to implement at least the following methods:
Type TMyStream extends TStream
	Method Eof()
		'code to return end-of-file status here...
	End Method

	Method Pos()
		'code to return current stream position here...
	End Method

	Method Size()
		'code to return size of stream here...
	End Method

	Method Seek( pos )
		'code to seek to a position in a stream here...
	End Method

	Method Flush()
		'code to flush stream output here...
	End Method

	Method Close()
		'code to close stream here...
	End Method

	Method Read( buf:Byte Ptr,count )
		'code to read from stream here...
	End Method

	Method Write( buf:Byte Ptr,count )
		'code to write to a stream here...
	End Method
End Type
Whew, that's a fair bit of work! Fortunately, BlitzMax can simplify things a bit for us in this case.

Many types of stream really just modify the behaviour of an existing stream. For example, the BlitzMax endian stream works by modifying the behaviour of an existing stream - in a sense, it 'wraps' an existing stream. Our goal of creating an 'uppercase' stream also applies here.

BlitzMax provides the TStreamWrapper type for exactly this situtation. TStreamWrapper extends TStream, but also maintains an internal 'underlying' stream. Whenever you call a method of TStreamWrapper, it will simply 'forward' the method call to the underlying stream.

This is ideal for situations where you only want to modify the behaviour of a few of the stream methods. In our case, we want to modify the Read and Write methods to convert text to uppercase - and here's how it's done:
Type TUpperStream Extends TStreamWrapper

	Method Read( buf:Byte Ptr,count )
		'Read data from the underlying stream
		count=Super.Read( buf,count )
		'Convert the data to uppercase
		For Local i=0 Until count
			If buf[i]>=Asc("a") And Buf[i]<=Asc("z")
				buf[i]=buf[i]-Asc("a")+Asc("A")
			EndIf
		Next
		'Done!
		Return count
	End Method

	Method Write( buf:Byte Ptr,count )
		'Copy the data to a new buffer, converting to uppercase as we go
		Local tmp:Byte[count]
		For Local i=0 Until count
			If buf[i]>=Asc("a") And buf[i]<=Asc("z")
				tmp[i]=buf[i]-Asc("a")+Asc("A")
			Else
				tmp[i]=buf[i]
			EndIf
		Next
		'Write the data to the underlying stream
		Return Super.Write( tmp,count )
	End Method

	Function Create:TUpperStream( stream:TStream )
		Local t:TUpperStream=New TUpperStream
		'SetStream is a TStreamWrapper method that sets the underlying stream.
		t.SetStream stream
		Return t
	End Function

End Type
One important note: The Read method modifies the data 'in place'. However, the Write method makes a copy of the data. This is because, although we know it's safe to modify the read buffer - in fact, it will already be modified by the time we get to it - we can't be sure it's safe to modify the write buffer. The caller of 'Write' may well be expecting the contents of the buffer to remain unchanged.

Now let's see it in action:
'Create a tmp file and write some text to it.
Local tmp:TStream=WriteStream( "tmp" )
tmp.WriteLine "A little example..."
tmp.WriteLine "of our cool TUpperStream!"
tmp.Close

'Open tmp file again, and wrap it with a TUpperStream
tmp:TStream=ReadStream( "tmp" ) 
Local upperizer:TUpperStream=TUpperStream.Create( tmp )

'Dump file contents
While Not upperizer.Eof()
	Print upperizer.ReadLine()
Wend

'Close files
upperizer.Close
tmp.Close
This demo creates a text file and reads it back through a TUpperStream, which converts the file contents to uppercase while it's being read. If you inspect the tmp file, it's still actually in mixed case - but it's just as easy to write through the TUpperCase stream and end up with an uppercase file.

Finally, the icing on the cake: adding our TUpperStream to the system so that it can be used transparently:
Type TUpperStreamFactory Extends TStreamFactory

	Method CreateStream:TUpperStream( url:Object,proto$,path$,readable,writeable )
		If proto$<>"uppercase" Return
		Local stream:TStream=OpenStream( path,readable,writeable )
		If stream Return TUpperStream.Create( stream )
	End Method
	
End Type

New TUpperStreamFactory
And a demo...
'Open a TUpperStream tmp file using WriteStream
Local tmp2:TStream=WriteStream( "uppercase::tmp" )
tmp2.WriteLine "Another little example..."
tmp2.WriteLine "of our even cooler TUpperStream!"
tmp2.Close

'Read back and dump tmp file
tmp2:TStream=ReadStream( "tmp" )
While Not tmp2.Eof()
	Print tmp2.ReadLine()
Wend
tmp2.Close