Notifications
Clear all

[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

16 Replies

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.

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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()
    
    	
    	
1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

see mxs help -> Time Change Callback Mechanism

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.

Page 1 / 2