I got it mostly working… but have been unsucessful in tracking down the memory leak through trial and error. I thought it might be at first because I wasn’t copying enough… so as it is now everything that gets assigned is assigned a copy… but that makes things worse I think. Somewhere a bitmap isn’t getting freed from memory… based on how much of the bitmap system in maxscript I understand… probably every bitmap isn’t getting freed from memory
try(destroyDialog BoxDrawing)Catch()
global BoxDrawing,BoxShapes
fn getboxes =
(
ReturnArray = #()
struct BoxShape (pos,width,height,border,bordercolor,bgcolor,active,fg,nhandle,obj)
local Boxobjects = for o in $box* collect o
for o in BoxObjects do
(
append ReturnArray (BoxShape pos:[(o.min.x + (255/2))as integer,(o.max.y*(-1) + (255/2)) as integer] width:(o.width as integer) height:(o.length as integer) border:1 bordercolor:black bgcolor:white active:0 fg:1 nhandle:o.inode.handle obj:o )
)
return ReturnArray
)
fn updatepos boxarray =
(
for o in boxarray do
(
o.pos = [(o.obj.min.x + (255/2))as integer,(o.obj.max.y*(-1) + (255/2)) as integer]
)
return boxarray
)
BoxShapes = getboxes()
(
local bitmapX = bitmapY = 255
local WhiteBitmap = (bitmap bitmapx Bitmapy color:white)
local BlankBitmap = (bitmap bitmapx bitmapy color:(color 0 0 0 0))
local BackgroundBitmap = (bitmap bitmapX bitmapY color:white)
local MidgroundBitmap
local ForegroundBitmap = (bitmap bitmapx bitmapy color:(Color 0 0 0 0))
rollout BoxDrawing "DrawingBox"
(
BITMAP Canvas pos:[0,0] width: bitmapX height: bitmapY bitmap:WhiteBitmap
timer clock "maxscriptdemoclock" interval:33
fn drawbox canvas: pos: width: height: border:1 bordercolor:black bgcolor:white =
(
rightcrop = (canvas.width-pos.x-width)
if rightcrop > 0 do (rightcrop = 0)
if pos.x < 0 then (leftoffset = abs(pos.x)+1; pos.x = 0) else (leftoffset = 1)
local theBorderLine = for i = leftoffset to (width+rightcrop) collect bordercolor
local theBodyLine = for i = leftoffset to (width+rightcrop) collect bgcolor
for i = 1 to border do
(
if i >= leftoffset and width > rightcrop do
(
theBodyLine[i] = bordercolor
)
if (i-1) <= rightcrop and leftoffset < width do
(
theBodyLine[width+2-i-leftoffset] = bordercolor
)
)
for y = border to height-border-1 do
setPixels canvas [pos.x,pos.y+y] theBodyLine
for y = 0 to border-1 do
(
setPixels canvas [pos.x,pos.y+y] theBorderLine
setPixels canvas [pos.x,pos.y+height-1-y] theBorderLine
)
)
fn CompositeCanvas bgcanvas fgcanvas =
(
pasteBitmap fgcanvas bgcanvas (box2 0 0 fgcanvas.width fgcanvas.height) [0,0] type:#composite
)
fn drawbg shapearray canvas =
(
for b in shapearray do
(
if b.fg == 0 then
(
drawbox canvas:canvas pos:b.pos width:b.width height:b.height bgcolor:blue
)
)
)
fn drawfg shapearray canvas =
(
for b in shapearray do
(
if b.fg == 1 then
(
drawbox canvas:canvas pos:b.pos width:b.width height:b.height bgcolor:white
)
)
)
on clock tick do
(
BoxShapes = updatepos BoxShapes
ForegroundBitmap = copy BlankBitmap
drawfg boxshapes foregroundBitmap
MidgroundBitmap = copy BackgroundBitmap
compositeCanvas MidgroundBitmap ForegroundBitmap
Canvas.bitmap = copy Midgroundbitmap
)
on BoxDrawing open do
(
backgroundbitmap = copy Whitebitmap
drawbg boxshapes backgroundBitmap
Canvas.bitmap = copy BackgroundBitmap
)
)
createDialog BoxDrawing (bitmapX+30) (bitmapY+100)
)
they should be getting freed on a gc() as long as they’re not assigned to something still using them / have a file handle open.
That said, maybe there’s a reason you’re not using it, but… right now you’re creating a new copy of the bitmap each time. Instead, you should have a few bitmaps that you would re-use each time by copying the bitmap of interest into them.
e.g.
a = bitmap 16 16 -- create a bitmap
b = copy a -- create a copy
-- at a later time...
b = copy a -- creating another copy.
-- at a later time again...
b = copy a -- creating yet another copy.
or…
a = bitmap 16 16 -- create a bitmap
b = copy a -- create an initial copy
-- at a later time...
copy a b -- copy the original bitmap -into- the copy we created earlier. This doesn't actually create a new bitmap, it just sticks all the values in a into the memory space of b.
-- at a later time again...
copy a b -- keep re-using that bitmap's memory space.
Ahhhhhhh so just treat bitmaps as if they were nodes. They still exist beyond the variable they’re associated with got it.
Just like if you said
newnode = copy $node
you then have two nodes.
I was of the mindset that they were just like an array or other dataset.
That’s really helpful.
err no. The bitmaps reside in those variables. So you can’t do, say…
bitmap a = bitmap 16 16
copy a b
bitmap b = bitmap 16 16
as ‘b’ doesn’t exist at the point you’re copying the content of a into b. Similarly…
local backgroundBitmap = bitmap 16 16
fn someFunc = (
copy backgroundBitmap workBitmap
)
fn init = (
workBitmap = bitmap 16 16
)
init()
someFunc()
will fail because workBitmap only exists within the scope of the init function
local backgroundBitmap = bitmap 16 16
local workBitmap
fn someFunc = (
copy backgroundBitmap workBitmap
)
fn init = (
workBitmap = bitmap 16 16
)
init()
someFunc()
will work, however.
The main point was that once you’ve already created a bitmap, and allocated its memory for it, you can re-use that bitmap (and thus the memory allocation for it) by copying another bitmap into it (copy a b) – instead of creating a new copy (b = copy a) which would allocate additional memory).
I hope it makes more sense now
It kind of does but if you do
bitmapOne = bitmap 16 16
BitmapTwo = copy bitmapOne
setpixels bitmapTwo [1,2] #(black)
bitmapOne = undefined
Shouldn’t only one Bitmap be in memory now? BitmapTwo?
As far as I can tell (based on my memory leaks and what you say) that there are now (3?) bitmaps in memory? The bitmap created on line one. The bitmap that was created as a copy of BitmapOne and BitmapTwo.
nope – only after garbage collection does the memory get freed up. Basically, and putting this a bit simple, gc() goes over all memory, checks what variables the various areas of memory refer to, and whether or not those variables still refer back to that same memory or not. If they don’t, the memory is released.
So in your case, after “bitmapOne = undefined”, the actual memory used by bitmapOne, the bitmap, is not yet cleared up. Calling gc() afterwards should clear it up, however.
Bitmaps are a bit special – if there’s also a file handle open to it (i.e. you assigned the bitmap a filename and saved it), then it still doesn’t get cleared up; you’d have to call “close bitmapOne” first. But that doesn’t apply in your case
But GC() causes a pretty big performance hit. I tried executing a GC() on each tick but that caused some serious problems and slowed it wayyyy down.
But if I do
copy a b
It doesn’t leave anything in memory it actually overwrites the assigned memory block?
Bitmaps are weird.
Is this true of strings and stringstreams too?
Like if I do a big string:
bigstring = “(pagelongstring)” (2 instances in memory)?
bigstring2 = bigstring (3 instances in memory?)
bigstring= bigstring2 = undefined (3 instance in memory still?)
gc() (Now no strings in memory?)
What about inside of a function loop?
(
for i = 1 to 100 do
(
bigstring = "(pagelongstring)" (2 instances in memory)?
bigstring2 = bigstring (3 instances in memory?)
bigstring= bigstring2 = undefined (3 instance in memory still?)
)
)
Will I now have like 300 instances of a page long string in memory? Or does it garbage collect automatically when it leaves the scope of the loop?
I believe the answer is ‘yes’, but I’ll leave the details of when and how gc() gets called to Bobo or Larry.
For strings, specifically…
a = "Hello "
-- memory for "Hello " allocated and referenced by 'a'
b = "World "
-- memory for "World" allocated and referenced by 'b'
a = a + b
-- memory for "Hello World" allocated and referenced by 'a'
-- note: memory for "Hello " remains allocated but is no longer referenced. This will be collected upon the next gc() (presumably)
But since 3ds Max r5, you can also use append strA strB:
a = "Hello "
-- memory for "Hello " allocated and referenced by 'a'
b = "World "
-- memory for "World" allocated and referenced by 'b'
append a b
-- memory used by "Hello " cleared
-- memory for "Hello World" re-allocated and referenced by 'a'
( See also: “Append String Method Conserves Memory” in the help file )
Interesting.
So when I do:
Canvas.bitmap = CompositeBitmap
should I be doing
copy Canvas.Bitmap CompositeBitmap
as well? So that the assignment of the bitmap doesn’t leave a CompositeBitmap in memory? Or are Object.property = Bitmap assignments treated differently? I assume it’s treated the same and everywhere I want to assign a bitmap I should use the copy A B to prevent memory leaks.
Edit: To answer my question copy BITMAPUI.bitmap BGBitmap does nothing. So you have to define. But with the copy a b it doesn’t seem to be leaking memory like before. So problem solved?
GC() performs FULL garbage collection including scene nodes and clears the Undo buffer.
GC LIGHT:TRUE does not. It only clears MAXScript values like variables, bitmaps etc. but does not touch the scene content and is MUCH faster. For bitmaps, you should be using that.
‘Copy a b’ is actually not a real copy operation, it is a FULL PASTE operation. Like “take the pixels from a, scale them if necessary and paste them into bitmap b”. So “copy” is a misnomer. It does not perform memory copying to create a duplicate of a value, it does data pasting from a source into a target.
So the idea is to DECLARE all bitmaps you will ever need in the script in the beginning with the correct sizes, then just paste their contents around using the ‘copy a b’ method. This way, no duplication of memory will occur, only pixel data will be moved – it is much faster than looping with lines of setPixels()…