BlitzMax overview: |
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.
First up, let's have a look at what Blitz3D's Delete command actually does. It really serves 2 purposes:
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 imageYou can therefore think of Release as being similar to Blitz3D's FreeImage (or FreeSound, FreeThis, FreeThat etc).
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 deadAfter 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.'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
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.
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 TypeThis is one way of composing objects. Part2 'contains' a Part1, via the p1 field.Type Part2 Field p1.Part1 Field p,q,r End Type
Function UpdatePart1( t:Part1 ) 'Do something clever with a Part1. End Function
Local t:Part2=New Part2 t.p1=New Part1 'need to create it! UpdatePart1 t.p1OO inheritance provides a different (not always better!) way to do this:
Type Base Field x,y,z End TypeOk, 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.Type Derived Extends Base Field p,q,r End Type
Function UpdateBase( t:Base ) 'Do something clever with a Base object. End Function
Local t:Derived=New DerivedAnother 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.t.x=100 'x inherited from Base - thanks Base, you rock!
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.
Type Actor Field x,y,z End TypeBut still, you can think of Actor as the base type, and Player as the derived type - a Player is derived from an Actor.Type Player Extends Actor Field lives,score End Type
Type Actor Field x,y,z End TypeSo, 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.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
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 MethodWhew, that's a fair bit of work! Fortunately, BlitzMax can simplify things a bit for us in this case.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
Type TUpperStream Extends TStreamWrapperOne 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.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
'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.CloseThis 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.'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
Type TUpperStreamFactory Extends TStreamFactoryAnd a demo...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
'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