Confusion of memory management with Locals

BlitzMax Forums/BlitzMax Programming/Confusion of memory management with Locals

ImaginaryHuman(Posted 2005) [#1]
I have been tracking down a bug in my code for a couple of days and finally realized that it seems the memory management doesn't pay any attention to the existence of Local references to information.

For example, I'd created a Local TPixmap variable in a main piece of code. Then I called a Method which loaded an image into a TPixmap and stored the handle to the pixmap in a Field variable as part of the type. Since that Field variable is permanent, it kept the reference to the pixmap. If, within the method, I then make that Field=Null, and do a flushmem, then my Local reference to that pixmap outside of the method is now also Null, even if I was wanting to still use it.

The whole problem was then fixed by changing the local into a Global, because then it seems like the memory manager `sees` that there is a second reference to the object and doesn't destroy it with flushmem. Things then behaved as I expected them to.

So, not saying this is a bug, just something to bear in mind, a lesson learned. If you want to keep your object references, you probably have to put them in non-Local variables, or at least know that doing a flushmem will also eliminate any local references if those are the only ones left.

Does this sound about accurate? Seems to be the case.


Beaker(Posted 2005) [#2]
This code seems to disprove your theory:
Type blah
	Field img:Tpixmap

	Method killImage()
		img = Null
	End Method	
End Type

Graphics 640,480
Local img:Tpixmap = LoadPixmap("fltkwindow.png")

b:blah = New blah
b.img:Tpixmap = LoadPixmap("fltkwindow.png")

b.killImage
FlushMem

If img = Null Then Print "NO IMAGE in LOCAL"
If b.img = Null Then Print "NO IMAGE in TYPE FIELD"

End

Give it a try.


Robert(Posted 2005) [#3]
If you are experiencing that problem AD, then it is a bug. It is supposed to work as in Beaker's code above.

Generally speaking, it should not be necessary to worry about memory management in your programs. Just create New objects when you need them and let BlitzMAX do the rest.


ImaginaryHuman(Posted 2005) [#4]
Your code above looks sensible enough so I'm not sure why the difference. For me it would not work. If I defined `img` as a Local, in my version of code, it would become Null and lose the image.

I agree I shouldn't need to be concerned about the memory management.


Dreamora(Posted 2005) [#5]
Have you checked for strict - non-strict?

Locals behave very different in non-strict and its usage isn't recommended for OO programming, just for procedural one!


ImaginaryHuman(Posted 2005) [#6]
Here is some code extracted straight from my actual program that I'm working on, containing the elements needed to reproduce the situation I described.

The basic idea is this: I have a custom `Bitmap` type which I am using to process my version of pixmap's in main memory, for use with my own blitting routines and such. The program should load a regular PNG image which must be 256x200 pixels in RGBA format, into the Bitmap object. To do this the PNG is loaded into a Pixmap and then the Bitmap object `cludges` onto the pixmap memory with a static bank, and stores the pixmap handle in one of the type's Fields for safekeeping. Then, more or less the same file, only in a RAW RGBA data version, also 256x200, is loaded into the same Bitmap object, the pixmap reference is made Null, and a Flushmem is performed. What SHOULD happen, you'd think, is that the other handle on that pixmap - the Local variable TestPX:TPixmap, should still point to the pixmap and it should still be drawable with DrawPixmap(). However, it isn't. When TestPX is a Local, the pixmap is not drawn at all. When TestPX is changed to a Global, the pixmap is drawn.

So what is going on here, if it's not a memory management bug/issue/feature? I'd like to know if this is a bug, or if its working how it's meant to but I'm just not understanding it right.

Strict

Type Bitmap
	'Object to define the dimensions and properties of a `bitmap` containing 4-byte-per-pixel (RGBA) data
	
	Field DataBankPtr:Byte Ptr		'Byte-Pointer to the actual memory reserved in the bank
	Field DataBankIntPtr:Int Ptr		'Int-Pointer to the memory in the bank
	Field DataBankLongPtr:Long Ptr		'Long-Pointer to the memory in the bank
	Field DataBank:TBank		'Pointer to a TBank object to store data
	Field Width:Int		'Total width of the bitmap in terms of pixels or units of representation
	Field Height:Int		'Total height of the bitmap in terms of pixels or units of representation
	Field RowBytes:Int		'How many total bytes of data are allocated in each row of pixels or units (Width*4)
	Field TotalBytes:Int		'How many bytes in total does the bitmap data take up
	Field FromPixmap:TPixmap		'Pointer to a Pixmap object which may have been used for loading data from an image, used as a static bank
	Field MaskValue:Int		'RGBA color or 4-byte value, where all pixels in the Bitmap of that color/value are transparent/non-copied when copying in Mode 2 (ValueMask mode)

	Method New()
		'Executed when a new instance of `Bitmap` is created
		DataBankPtr=Null
		DataBankIntPtr=Null
		DataBankLongPtr=Null
		DataBank=Null
		Width=0
		Height=0
		RowBytes=0
		TotalBytes=0
		FromPixmap=Null
		MaskValue=0
	End Method
	
	Method Initialize(MWidth:Int,MHeight:Int)
		'To create and populate this `Bitmap` instance with memory space and valid variables
		Width=MWidth
		Height=MHeight
		RowBytes=MWidth Shl 2		'4 Bytes per pixel
		TotalBytes=MWidth*MHeight Shl 2		'4 Bytes per pixel		
		DataBank=CreateBank(TotalBytes)
		DataBankPtr=BankBuf(DataBank)
		DataBankIntPtr=Int Ptr(DataBankPtr)
		DataBankLongPtr=Long Ptr(DataBankPtr)
		FromPixmap=Null
		MaskValue=0		'Default black or $0000
		FlushMem
	End Method

	Method LoadRAWData(MFilePath:String)
		'To load RAW data from a file into the memory bank
		'You should call Initialize() first
		If DataBank=Null
			Print "Bitmap was not initilized prior to trying to load RAW file "+MFilePath+" into Bitmap!"
			End '!!!!
		EndIf
		Local MStream:TStream
		MStream=ReadStream(MFilePath)		'For reading only, pre-exists
		If MStream
			MStream=BigEndianStream(MStream)		'To read data in order, left to right
			MStream.Read(DataBankPtr,TotalBytes)
			CloseStream(MStream)
		Else
			Print "Couldn't load RAW file "+MFilePath+" into Bitmap!"
			End '!!!!
		EndIf
		FromPixmap=Null
		FlushMem
	End Method

	Method LoadImageData(MFilePath:String)
		'To load an image-format file (png/jpg etc) and convert it into the `Bitmap`s memory bank to represent 4-byte-per-pixel values
		Local TempPixmap:TPixmap=New TPixmap
		TempPixmap=LoadPixmap(MFilePath)
		If TempPixmap=Null
			Print "There was a problem loading the file "+MFilePath+" as an image into a Bitmap object. File not found or error with file or wrong file type!"
			End '!!!!
		EndIf
		If PixmapFormat(TempPixmap)<>PF_RGBA8888 Then TempPixmap=ConvertPixmap(TempPixmap,PF_RGBA8888)
		DataBankPtr=PixmapPixelPtr(TempPixmap,0,0)
		DataBankIntPtr=Int Ptr(DataBankPtr)
		DataBankLongPtr=Long Ptr(DataBankPtr)
		Width=PixmapWidth(TempPixmap)
		Height=PixmapHeight(TempPixmap)
		RowBytes=PixmapPitch(TempPixmap)
		If RowBytes Mod 4>0		'Mod 4 due to PF_RGBA8888 which is 4 bytes per pixel
			Print "Image being loaded as Color "+MFilePath+" has width that is not multiple of 4 bytes (ie has skip pixels per row) even after ConvertPixmap!"
			End '!!!!!
		EndIf
		TotalBytes=(Width*Height) Shl 2		'4 Bytes per pixel
		DataBank=CreateStaticBank(DataBankPtr,TotalBytes)		'Bank's memory pointer = pixmap's memory pointer
		FromPixmap=TempPixmap		'Store
		FlushMem
	End Method
End Type

Graphics 640,480,0
Cls
Flip
Cls
Flip

Local Test:Bitmap=New Bitmap

Cls
Test.Initialize(256,200)
Test.LoadImageData("TestImages/Green256x200.png")
Local TestPX:TPixmap=Test.FromPixmap		'Must be Global otherwise when the only other Global FromPixmap is freed, the pixmap is lost
DrawPixmap TestPX,320,16
Test.LoadRAWData("TestImages/SavedRAWData-Green256x200.raw")
DrawPixmap TestPX,320,16
Flip;WaitKey

End



marksibly(Posted 2005) [#7]
Hi,

Not 100% sure what's its meant to do, but note that the Test.LoadRAWData call is overwriting the contents of the pixmap.

This is because you create a static bank from the pixmap, which means that the bank shares exactly the same block of memory as the pixmap. So when Test.LoadRAWData loads stuff into the bank, it's also loading into the pixmap.

I tend to think the TBank is unnecessary, as are all the ptrs. Why not just store a pixmap and work from that?

I also get exactly the same result with or without any flushmems.


ImaginaryHuman(Posted 2005) [#8]
The Bitmap object is meant to be retained the the image data is meant to be overwritten by the LoadRAWData() call. That's how it's designed to work. The only call I have that destroys the existing pixmap AND Fields is the loading of the PNG image. Everything else should retain the dimensions etc and just load/save raw data. Reason being that I am going to be loading part of a raw data file into a rectangular area within the existing bitmap image.

Do you get the same result with a Local TestPX variable as with a Global one? With a Local, for me, the pixap does not draw (or draws black).

Also the reason I'm not using pixmaps is because they are more generic than I need and, additionally, the Bitmap object isn't always going to contain an image that came from an image file, hence most of the time data is going to be loaded from raw and that FromPixmap field will be Null. So I would then have to have separate routines for when there is a pixmap to work from and when there isn't, if I were to make use of the pixmap routines. So I am globally doing all of the work myself. I will also be having fields that pixmap's don't have and it will relate to the rest of the system in a way that pixmap's don't, so it has to be custom.


ImaginaryHuman(Posted 2005) [#9]
Sooooooooo............. does anyone know why the above code doesn't show the pixmap when the TestPX variable is a Local, but it does when it's a Global?


Beaker(Posted 2005) [#10]
I get the same error either way - Local or Global:
"Couldn't load RAW file SavedRAWData-Green256x200.raw into Bitmap!"


Beaker(Posted 2005) [#11]
BUT, if I create my own SavedRAWData-Green256x200.raw file (copied the png and renamed it) then it works in both instances.


ImaginaryHuman(Posted 2005) [#12]
Okay.

Just to make sure I'm not nuts or making it up, I re-compiled it here again, with and without debug, and it continues to exhibit the same behavior.

When I create Local TestPX:TPixmap the DrawPixmap command which should display it on the screen, actually display a black/empty pixmap. I drew a white rect behind it and it does blit the pixmap, it's just black rather than the image.

When I change it to Global TestPX:TPixmap the image, as loaded, appears.

In both instances, the same files are being loaded with the same routines.

Now, when I remove both of the FlushMem's, the program works correctly. Question is, why does flushmem cause the image to not be loaded correctly? the pixmap is stored in the FromPixmap field before flushing, so it should still be retained.


Ideas?