Confusion of memory management with Locals
BlitzMax Forums/BlitzMax Programming/Confusion of memory management with Locals
| ||
| 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. |
| ||
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"
EndGive it a try. |
| ||
| 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. |
| ||
| 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. |
| ||
| 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! |
| ||
| 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
|
| ||
| 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. |
| ||
| 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. |
| ||
| 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? |
| ||
| I get the same error either way - Local or Global: "Couldn't load RAW file SavedRAWData-Green256x200.raw into Bitmap!" |
| ||
| BUT, if I create my own SavedRAWData-Green256x200.raw file (copied the png and renamed it) then it works in both instances. |
| ||
| 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? |