[Closed] Garbage Collection issue with dotNet Timer
I am writing a player for 3ds Max and am using a dotNet Timer to playback the video.
Unfortunately for some reason the timer is getting garbage collected when I run the function to capture the video.
When the capture function runs and I try to play it back I get this error
No property “start” in dotNetObject:System.Windows.Forms.Timer
i tried to avoid this initially by running:
dotNet.setLifeTimeControl theTimer #dotNet
But it is still being Garbage Collected.
Now here is the really confusing part, to test this I added 2 print statements to the function that was causing the issue.
Here is an example of the function:
fn captureImg start end w h=
(
for t = start to end do
(
sliderTime = t
redrawViews()
img=(dotNetObject "System.Drawing.Bitmap" w h)
gfx=(DotNetClass "system.drawing.Graphics").fromImage img
screenCap=gfx.CopyFromScreen pos.x pos.y 0 0 (dotNetObject "System.Drawing.Size" w h)
append imageAr img
)
formatProps theTimer
print theTimer
)
now the capture runs and then this is what print out to the listener. (oh the formatProps function prints out all properties, methods, events and constructors for the dotNet object specified)
Properties:
Methods:
Events:
Constructors:
dotNetObject:System.Windows.Forms.Timer
so the timer is defined but all of it’s properties are gone!
Then I tried creating the timer after the capture function runs and it plays back fine but then I can’t stop the timer because the “stop” property doesn’t exist!!
here is the event function for the play button.
fn playClick sender arg=--Event For PLAY button
(
theStuct.createTimer theStuct.fps
print sender.name
if sender.name == "play" then
(
theStuct.theTimer.start()
sender.name = "stop"
)else
(
theStuct.theTimer.stop()
theStuct.theTimer.dispose()
sender.name = "play"
)
),
I do not know what is causing this and am losing my mind trying to figure it out.
Please Help.
Thank You
Steve
This looks like something similar to what I posted last week:
http://forums.cgsociety.org/showthread.php?f=98&t=910111
I was getting errors like:
[left]
>> MAXScript Rollout Handler Exception: -- Unable to convert: dotNetObject:System.Windows.Forms.Label to type: System.Windows.Forms.Control <<
[/left]
Then I would do ( (classof controlThatWorks) == (classof controlThatDoesntWork)) and get true. Yet the one that doesn’t work had no properties like in your example.
I worked around PART of the problem by moving the setLifetimeControl and addEventHandler calls out of the function that creates the control, and then I created a second function that adds events to the controls AFTER they’re in the form and the form is visible. That works when I run it from the listener, or from anywhere except where I want to run it from: from a button pressed event inside a custom attribute rollout. When I build the same form from the same function from a button pressed event inside a custom attribute, it builds the form but the events don’t stick, I guess they’re being garbage collected.
Thanks for the reply Rotu. I tried adding the events and setLifetime… after the rollout was built and visible but still the same issue.
Here is my stripped down code that is causing the problem:
(I should have posted this initially sorry)
struct theStruct
(
instTheRollout = false,
form = undefined,
bt = undefined,
imageAr = #(),
contAr = #(),
imageIndex = 1,
theTimer = undefined,
viewportPos=undefined,
viewWidth=undefined,
viewHeight=undefined,
pos = [10,90],
width = 200,
height = 200,
playBt = undefined,
captBt = undefined,
pBox = undefined,
theTimer = undefined,
theImg = undefined,
/*---------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------*/
fn formatProps dn=
(
if classOf dn==dotNetObject or classOf dn==dotNetClass then
(
clearListener()
format "Properties:
"
showProperties dn
format "
Methods:
"
showMethods dn
format "
Events:
"
showEvents dn
format "
Constructors:
"
dotNet.showConstructors dn
)else
(
format "% is not a dotNetObject or dotNetClass
" dn
)
),
/*-----------------------------------------------------------------------------------------------------------------------
Function to Capture Images
-----------------------------------------------------------------------------------------------------------------------*/
fn captureImg start end w h=--capture images and stores them in an Array (imageArray)
(
for t = start to end do
(
sliderTime = t
redrawViews()
img=(dotNetObject "System.Drawing.Bitmap" w h)
gfx=(DotNetClass "system.drawing.Graphics").fromImage img
screenCap=gfx.CopyFromScreen pos.x pos.y 0 0 (dotNetObject "System.Drawing.Size" w h)
append imageAr img
)
theStruct.formatProps theStruct.theTimer
print theStruct.theTimer
),
fn createForm w h=
(
form=dotNetObject "maxCustomControls.maxForm" -- creates Form
sysPointer=dotNetObject "system.intPtr" (windows.getMaxHWND())--Sets to look like max
maxHandle=dotNetObject "maxCustomControls.win32HandleWrapper" sysPointer
-- form.show maxHandle -- Shows the form
-- form.backColor=(dotNetClass "system.drawing.color").fromARGB 82 82 82
form.bounds=dotNetObject "system.drawing.rectangle" 10 90 w (h+170) -- Sets Position and scale of form
form.text="Hok - Viewport Preview"
),
fn UIaddButton obj w h xpos ypos =
(
bt = dotNetObject "System.Windows.Forms.Button"
bt.bounds = dotNetObject "system.drawing.rectangle" xpos ypos w h
obj.controls.add bt
bt.flatStyle = bt.flatStyle.popUp
),
fn UIplayBackBt=
(
theStruct.UIaddButton form 60 30 10 30
playbt = bt
playBt.text = "play"
),
fn UIcaptureBt=
(
theStruct.UIaddButton form 60 30 75 30
captBt = bt
captBt.text = "Capture"
),
/*----------------------------------------------------------------------------------------------------------------------------------
END UI
----------------------------------------------------------------------------------------------------------------------------------*/
fn makePBox w h=
(
pBox=dotNetObject "pictureBox" --"system.windows.forms.pictureBox"
pBox.bounds=dotNetObject "System.drawing.rectangle" 0 65 w h--Set the size and location of the pictureBox
pBox.backColor=(dotNetClass "system.drawing.color").black--Set the back ground color
form.controls.add pBox--Add the pictureBox to the form
),
fn pBoxPaint sender arg=--Event For Paint
(
g=arg.graphics
if theStruct.imageAr.count > 0 do
(
g.DrawImageUnscaled theStruct.imageAr[theStruct.imageIndex] 0 0 theStruct.width theStruct.height
)
),
fn refreshFn=
(
print "tick"
if theStruct.imageIndex <= theStruct.imageAr.count then
(
theStruct.form.refresh()
theStruct.imageIndex += 1
)else
(
theStruct.imageIndex = 1
)
),
fn createTimer inter=
(
theStruct.theTimer = dotNetObject "System.Windows.Forms.Timer"
theStruct.theTimer.interval = 1000/inter
theStruct.addEventHandlers theStruct.theTimer "tick" refreshFn
),
fn playClick sender arg=--Event For PLAY button
(
print sender.name
if sender.text == "play" then
(
theStruct.theTimer.start()
sender.text = "stop"
)else
(
theStruct.theTimer.stop()
theStruct.theTimer.dispose()
sender.text = "play"
)
),
fn CaptureClick sender arg=--Event For Capture Button
(
theStruct.captureImg 1 20 theStruct.width theStruct.height
theStruct.form.refresh()
),
fn addEventHandlers cont arg Func=--Function to Add EventHandlers
(
dotNet.addEventHandler cont arg Func
dotNet.setLifeTimeControl cont #dotNet
),
fn buildUI w h=
(
theStruct.createForm theStruct.width theStruct.height
theStruct.UIplayBackBt()
theStruct.UIcaptureBt()
theStruct.createTimer 30
theStruct.makePBox theStruct.width theStruct.height
theStruct.addEventHandlers theStruct.playBt "click" playClick
theStruct.addEventHandlers theStruct.pBox "paint" pBoxPaint
theStruct.addEventHandlers theStruct.captBt "click" captureClick
),
fn run=
(
if instTheRollout==false then
(
theStruct.BuildUI width height
theStruct.form.show maxHandle
instTheRollout=true
)else
(
theStruct.form.close()
instTheRollout = false
)
)
)
theStruct = theStruct()
theStruct.run()
Any help would be greatly appreciated.
Thanks
this snippet shows that the an included in a structure timer can stay alive and not garbage collected:
struct LifeTimer
(
enabled = on,
interval = 1000,
timer = dotnetObject "Timer",
ticks = 0,
starttime,
endtime,
fn onTick s a = if keyboard.escpressed then s.stop() else
(
format "%
" (s.tag.value.ticks += 1) [i]-- shows that the timer is alive
[/i] ),
fn init =
(
dotnet.removeAllEventHandlers timer[i] -- it's not really necessary[/i]
timer.interval = interval
dotnet.addeventhandler timer "Tick" onTick
),
fn start =
(
starttime = timestamp()
timer.start()
),
fn stop =
(
timer.stop()
endtime = timestamp()
),
on create do timer.tag = dotnetmxsvalue this[i] -- store pointer to the instance[/i]
)
global tt = LifeTimer()
tt.init()
tt.start()
Thank you for the reply Denis.
I understand that a timer can be put into a struct and not be gc’d.
The issue I am having is that the timer stops working only after the capture button is pressed… If you run the script and push play without pressing capture first, the timer works correctly. I prints “tick” until you press stop (play again). But for some reason the capture button is causing the timer to lose all of it’s properties, methods and events.
you code is too big for debugging. Cut 90% of the code, isolate the problem, and show what is not working. In this case I’ll be happy to help you.
Thanks Denis. I trimmed the code down to just the bare minimum and highlighted the one function that is causing the issue.
If you press the play button the timer starts and when you hit it again it stops. When you press the capture button which calls the captureImg Function and then hit play you will get an error that says the property “start” is undefined in the timer.
struct theStruct
(
instTheRollout = false,
form = undefined,
bt = undefined,
imageAr = #(),
imageIndex = 1,
theTimer = undefined,
pos = [10,90],
width = 200,
height = 200,
playBt = undefined,
captBt = undefined,
pBox = undefined,
theTimer = undefined,
theImg = undefined,
/*----------------------------------------------------------------------------------------------------------------------
bUILD ui
-----------------------------------------------------------------------------------------------------------------------*/
fn createForm w h=
(
form=dotNetObject "maxCustomControls.maxForm" -- creates Form
sysPointer=dotNetObject "system.intPtr" (windows.getMaxHWND())--Sets to look like max
maxHandle=dotNetObject "maxCustomControls.win32HandleWrapper" sysPointer
form.bounds=dotNetObject "system.drawing.rectangle" 10 90 200 200
form.text="Hok - Viewport Preview"
),
fn UIaddButton obj w h xpos ypos =
(
bt = dotNetObject "System.Windows.Forms.Button"
bt.bounds = dotNetObject "system.drawing.rectangle" xpos ypos w h
obj.controls.add bt
bt.flatStyle = bt.flatStyle.popUp
),
fn UIplayBackBt=
(
theStruct.UIaddButton form 60 30 10 30
playbt = bt
playBt.text = "play"
),
fn UIcaptureBt=
(
theStruct.UIaddButton form 60 30 75 30
captBt = bt
captBt.text = "Capture"
),
/*----------------------------------------------------------------------------------------------------------------------------------
END UI
----------------------------------------------------------------------------------------------------------------------------------*/
/*______________________________________________________________________________
--THIS IS THE FUNCTION THAT IS CAUSING THE PROBLEM--------------------------------------------------
**********************************************************************************************************
_______________________________________________________________________________*/
fn captureImg =--capture images and stores them in an Array (imageArray)
(
for t = 0 to 20 do
(
sliderTime = t
redrawViews()
img=(dotNetObject "System.Drawing.Bitmap" 200 200)
gfx=(DotNetClass "system.drawing.Graphics").fromImage img
screenCap=gfx.CopyFromScreen pos.x pos.y 0 0 (dotNetObject "System.Drawing.Size" 200 200)
append imageAr img
)
print theStruct.theTimer
),
/*______________________________________________________________________________
**********************************************************************************************************
_______________________________________________________________________________*/
fn refreshFn=
(
print "tick"
),
fn createTimer inter=
(
theStruct.theTimer = dotNetObject "System.Windows.Forms.Timer"
theStruct.theTimer.interval = 1000/inter
),
fn playClick sender arg=--Event For PLAY button
(
print sender.name
if sender.text == "play" then
(
theStruct.theTimer.start()
sender.text = "stop"
)else
(
theStruct.theTimer.stop()
sender.text = "play"
)
),
fn CaptureClick sender arg=--Event For Capture Button
(
theStruct.captureImg()
),
fn addEventHandlers cont arg Func=--Function to Add EventHandlers
(
dotNet.addEventHandler cont arg Func
dotNet.setLifeTimeControl cont #dotNet
),
fn buildUI =
(
theStruct.createForm 200 200
theStruct.UIplayBackBt()
theStruct.UIcaptureBt()
theStruct.createTimer 30
theStruct.addEventHandlers theStruct.playBt "click" playClick
theStruct.addEventHandlers theStruct.captBt "click" captureClick
theStruct.addEventHandlers theStruct.theTimer "tick" refreshFn
),
fn run=
(
if instTheRollout==false then
(
theStruct.BuildUI()
theStruct.form.show maxHandle
instTheRollout=true
)else
(
theStruct.form.close()
instTheRollout = false
)
)
)
theStruct = theStruct()
theStruct.run()
I cleaned up a little your code, removed some things those don’t make sense for me, and add some “key” things colored in YELLOW.
struct theStruct
(
instTheRollout = false,
form = undefined,
bt = undefined,
imageAr = #(),
imageIndex = 1,
theTimer = undefined,
pos = [10,90],
width = 200,
height = 200,
playBt = undefined,
captBt = undefined,
pBox = undefined,
theTimer = undefined,
theImg = undefined,
/*----------------------------------------------------------------------------------------------------------------------
bUILD ui
-----------------------------------------------------------------------------------------------------------------------*/
fn createForm w h=
(
form=dotNetObject "maxCustomControls.maxForm" -- creates Form
sysPointer=dotNetObject "system.intPtr" (windows.getMaxHWND())--Sets to look like max
maxHandle=dotNetObject "maxCustomControls.win32HandleWrapper" sysPointer
form.bounds=dotNetObject "system.drawing.rectangle" 10 90 200 200
form.text="Hok - Viewport Preview"
),
fn UIaddButton obj w h xpos ypos =
(
bt = dotNetObject "System.Windows.Forms.Button"
bt.bounds = dotNetObject "system.drawing.rectangle" xpos ypos w h
obj.controls.add bt
bt.flatStyle = bt.flatStyle.popUp
),
fn UIplayBackBt=
(
UIaddButton form 60 30 10 30
playbt = bt
playBt.text = "play"
),
fn UIcaptureBt=
(
UIaddButton form 60 30 75 30
captBt = bt
captBt.text = "Capture"
),
/*----------------------------------------------------------------------------------------------------------------------------------
END UI
----------------------------------------------------------------------------------------------------------------------------------*/
/*_________________________________________________ _____________________________
--THIS IS THE FUNCTION THAT IS CAUSING THE PROBLEM--------------------------------------------------
*********************************************** ******************************************** ***
__________________________________________________ _____________________________*/
fn captureImg =--capture images and stores them in an Array (imageArray)
(
for t = 0 to 20 do
(
sliderTime = t
redrawViews()
img=(dotNetObject "System.Drawing.Bitmap" 200 200)
gfx=(DotNetClass "system.drawing.Graphics").fromImage img
screenCap=gfx.CopyFromScreen pos.x pos.y 0 0 (dotNetObject "System.Drawing.Size" 200 200)
append imageAr img
)
-- print theTimer
),
/*_________________________________________________ _____________________________
*********************************************** ******************************************** ***
__________________________________________________ _____________________________*/
fn refreshFn=
(
print "tick"
),
fn createTimer inter=
(
theTimer = dotNetObject "System.Windows.Forms.Timer"
theTimer.interval = 1000/inter
playbt.tag = theTimer
),
fn playClick sender arg=--Event For PLAY button
(
print sender.name
if sender.text == "play" then
(
sender.tag.start()
sender.text = "stop"
)else
(
sender.tag.stop()
sender.text = "play"
)
),
fn CaptureClick sender arg=--Event For Capture Button
(
captureImg()
),
fn addEventHandlers cont arg Func=--Function to Add EventHandlers
(
dotNet.addEventHandler cont arg Func
dotNet.setLifeTimeControl cont #dotNet
),
fn buildUI =
(
createForm 200 200
UIplayBackBt()
UIcaptureBt()
createTimer 30
addEventHandlers playBt "click" playClick
addEventHandlers captBt "click" captureClick
addEventHandlers theTimer "tick" refreshFn
),
fn run=
(
if instTheRollout==false then
(
BuildUI()
form.show maxHandle
instTheRollout=true
)else
(
form.close()
instTheRollout = false
)
)
)
global st = theStruct()
st.run()
in playBt’s event handler you used the pointer to instance (theTimer) that doesn’t exist at the moment of structure initialization.
Thanks you very much Denis. It worked perfectly.
I really appreciated your help on this,
Steve
by the way… are you positive that the timer is the right solutions… have you looked at the time changed event?
I haven’t looked into the time changed event. I am using the timer so I can change the intervals to what ever I want.
I’m sure you can do that with the time change but I have never heard of it so i went with the timer.
If you don’t mind what is the difference between the two? Why timeChanged over Timer?
Steve
Coming from pre-python Maya, I had gotten into the habit of making all my functions and shared variables global. I had worked around some of the issues I had mentioned earlier in the thread, but it still felt hacky and ugly, so I tried to structify my code like the examples posted.
What I’m experiencing is that the following code isn’t pointing the event at the current instance of the struct, but at it’s definition instead.
dotNet.setLifetimeControl b[i] #dotnet
dotNet.addEventHandler b[i] "click" xxx_btnEvent
Therefore any references to variables or other functions in the event handler within the struct give me this error:
>> MAXScript dotNet event handler Exception: -- Runtime error: Struct member access requires instance: xxx_checkNodeExist <<
But when I try to point the event handler at the current instance with a “this.” prefix, it gets garbage collected.
dotNet.setLifetimeControl b[i] #dotnet
dotNet.addEventHandler b[i] "click" this.xxx_btnEvent
And the event never fires on click.
I haven’t tried to embed the struct instance into the control’s tag property yet since I’m using tag for other data currently.
Any help would be appreciated…
…
rich
The error you are getting is because you are trying to reference a control that resides with in the struct with out referencing the instance of the struct first.
When you add the eventHandler you need to create an instance of the control in order for it to work.
Here is an example of what I am talking about:
struct theStruct
(
form = undefined,
bt = undefined,
fn createForm =
(
form=dotNetObject "maxCustomControls.maxForm"
form.bounds=dotNetObject "system.drawing.rectangle" 10 90 400 400
bt = dotNetObject "System.Windows.Forms.Button"
bt.bounds = dotNetObject "system.drawing.rectangle" 50 50 100 30
bt.text = "The Button"
form.controls.add bt
),
fn btClick sender arg=
(
print sender.text
),
fn addEvent=
(
dotnet.addEventHandler theStruct.bt "click" btClick
dotNet.setLifeTimeControl theStruct.bt #dotNet
),
fn run=
(
theStruct.createform()
theStruct.addEvent()
theStruct.form.show()
dotNet.setLifeTimeControl theStruct.form #dotNet
)
)
theStruct = theStruct()
theStruct.run()
Hope this helps.