Application continue processing when dragging

BlitzMax Forums/BlitzMax Programming/Application continue processing when dragging

Derron(Posted 2012) [#1]
I know... I had started a thread about this 4-5 years ago - but there is still no solution.

When moving a windowed application/game in Windows the content is not being redrawn and also not processing actions.

I tried the timer method from this forums to emit timer-tick-events but on Linux it stops responding after about 200 events.

While Not Ending
  WaitSystem
Wend


Function Hook:Object ( id:int , data:Object , context:Object )
  Local Event:TEvent = TEvent ( data )
  Select Event.source
    Case Null
      If Event.id = EVENT_APPTERMINATE then Ending = True
      If Event.id = EVENT_KEYDOWN And Event.data = KEY_ESCAPE then Ending = True
    Case Timer
      If Event.id = EVENT_TIMERTICK
        StopTimer Timer
        GameLoop()
		Timer = CreateTimer ( HERTZ )
      EndIf
  EndSelect
  Return data
EndFunction


Standard method works fine:
	Repeat
		If Not GameLoop() Then Exit
	Until AppTerminate() Or ExitGame = 1



GameLoop just updates AppLoop and custom EventManager (custom events, no derivates of TEvent)

	Method Loop()
		self.newTime		= MilliSecs()
		if self.oldTime = 0.0 then self.oldTime = self.newTime - 1
		self.secondGone		:+ (self.newTime - self.oldTime)
		self.loopTime		= (self.newTime - self.oldTime) / 1000.0
		self.oldTime		= self.newTime

		if self.secondGone >= 1000.0 'in ms
			self.secondGone = 0.0
			self.fps		= self.timesDrawn
			self.ups		= self.timesUpdated
			self.deltas = 0.0
			self.timesDrawn = 0
			self.timesUpdated = 0
		endif

		'fill time available for this loop
		self.loopTime = Min(0.25, self.loopTime)	'min 4 updates per seconds 1/4
		self.accumulator :+ self.loopTime

		'update gets deltatime - fraction of a second (speed = pixels per second)
		While self.accumulator >= self.deltaTime
			self.totalTime :+ self.deltaTime
			self.accumulator :- self.deltaTime
			self.timesUpdated :+ 1
			EventManager.triggerEvent( "App.onUpdate", TEventSimple.Create("App.onUpdate",null))
		Wend
		'how many % of ONE update are left - 1.0 would mean: 1 update missing
		self.tweenValue = self.accumulator / self.deltaTime

		'draw gets tweenvalue (0..1)
		self.timesDrawn :+1
		EventManager.triggerEvent( "App.onDraw", TEventSimple.Create("App.onDraw", string(self.tweenValue) ) )
		'Delay(1)
	End Method



Another thing I don't want is that the timer method limits frame rate to the timers hertz rate.



So what kind of possiblities do I have to process messages of my application even if its in background or being dragged around?

On Linux the app is processing while being dragging the window around... so I guess it is a Windows thing.

Graphical processing is not that important, but overall processing (physics, entity positions etc.) should be possible (as the app clients are able to control custom figures by AIscripts and so on).


Any ideas?


bye
Ron


ima747(Posted 2012) [#2]
I believe you can detect the actual dragging events... though they won't emit if it's standing still. Combined with tweening you may be able to fudge something together, but I don't think it will be optimal unless changes are made to the event manager on windows...

Another possibility could be to multithread the things that need continuous updates so they're unteathered from the main thread and therefore won't get suspended when the window is being moved... that might require a total overhaul of your code and opens up the possibility of threading bugs of course.

What is the root need for continuous updates while dragging the window? It seems to me that moving a window around generally means the user is not engaged with it's contents, but rather their user space, as a result holding the processing in that time would generally be a favorable experience. I can see how if you have time sensitive puzzles or something, where extra time looking at it would be bad then you don't want a free pause effect, but then if you tween the game time so they just loose whatever time they spent dragging it shouldn't be a problem... uncoupling logic/game time from draw is beneficial not just for performance adjustment but for maintaining control as well... Root cause of your issue might provide an easier solution than trying to resolve problems with Windows...


Derron(Posted 2012) [#3]
Drawing is decoupled from physics...
Network is decoupled from physics...

I did not know that multithreading could get the thing...

To your question of the need:
Every player in my game controls a figure, master controlls some more.
If a player figure reaches a certain spot (or clicks to an entity - which is no problem here) an event is emitted and a networkpacket is spread around informing that player figure is doing something important.
Although master is knowing such things too and can spread around the same info -- this info might be obsolete (player clicked -> informs master -> master informs others -> player packet reaches master -> master informs others).

If Game master is having a freezed window he is not able to send events for AI controlled figures or entities reaching a state where they have to sync with all clients (event based sync, not time based - to lower packet amount)
Next to figures there are other events which happen to certain "in game times".

what is more important: if no processing is done, client would drop out of the game after a certain amount of time (no response...).


bye
Ron

Last edited 2012


ima747(Posted 2012) [#4]
Re: processing drop out, give an allowance when the events are being blocked. You can detect the start/end of dragging so just restart the timers for player drop out after the release to prevent drags from timing them out.

If your drawing (has to be main thread) and networking (can be wherever) are decoupled then your networking should theoretically be possible to offload to another thread. You will need to manage some form of inter-thread communications (probably a mutex locked event/command queue) to safely talk with it, but that shouldn't be too difficult in theory, though it depends on how you're currently doing things... shoehorning multithreading into existing systems can be a nightmare if things don't line up. I think I posted some multithreading tutorials a while back in the tutorials forums, I would recommend you start there. Something like this should in theory fit from what you've described I think:

Main Thread:
Call function to post network message
Network post function locks mutex (which holds main thread if the mutex is in use)
Posts message to que
Unlocks mutex
function return

Networking thread:
At the top of each cycle attempts to lock the mutex
If it locks then copy the messages into a local que, clear the shared sue, unlock mutex so it's free to be posted to again
if it can't lock it (the main thread is currently using it) skip the que for this pass and do whever needs doing (look for new network messages, etc.)


I would advise using a que rather than locks for everything that might want to be changed on either side because a) it's a HELLUVA lot easier to keep track of what's going on and b) it allows both threads to run totally independently, no chance for race conditions, mutex fights, etc. That said the bmax multithreaded garbage collector has some gremlins so if you do a lot of thrashing especially on child threads things can get weird.

One more thing: If you're using 3rd party modules there's always a chance they might not be thread safe... additionally not all things can happen on child threads... Quite often if you keep things on a given thread they're fine even if they're not properly thread safe (don't cross the beams...) but again, threading can bring up some monsters from the deep.

Last edited 2012


ImaginaryHuman(Posted 2012) [#5]
You can make this work in MaxGUI. However, the way to make it work is to NOT use the standard widgets/titlebar but instead to implement your own generic widget that looks like a title bar, or a window scaling widget, and then I believe it was possible to have it not block the program from processing while you do stuff.


ima747(Posted 2012) [#6]
If there's a work around through some maxgui fumbling I would urge that over trying to cram in threading... unless you're really interested in threading and up for some weird headaches :0)


Derron(Posted 2012) [#7]
Currently I'm not able to try it.
I thought about locking an update mutex when updating and do only drawing/mainloop if mutex is not locked.

But as bruceys libxml crashes when having multiple instances of txmldoc in multithreading... I have to recode that first ;D
Edit: its not libxml its a "calling currentTime()"-thingy which is crashing when having loaded a xml file with links to other xml files in there (which are then getting parsed)...investigating
Edit2: as it crashes at random points (no mutexes or brl.threads used/imported, just compiling threaded) within the xml-processing I still think its there... but not meant as offense to brucey when I state it's libxml :D
Edit3: *blushing* ... seems noel cowers bufferedglmax really (as he wrote) isn't thread safe - replacing it with the default maxgl made the crashes go away (windows build uses normal opengl...and run fine)

bye
Ron

Last edited 2012


ImaginaryHuman(Posted 2012) [#8]
If you use the native widgets it title bar, resize gadget etc, the operating system will get all the cpu time until you let go of the widget. That's why you have to do your own widgets which you poll each frame.


Derron(Posted 2012) [#9]
Ok so I finally coded something which works ... ima747 was right about additional threads...

I hope I didn't coded something wrong, so feel free to give suggestions.
The following code contains delta timing functions, fps counter and a game loop using that things.
If compiled without threading, the singlethreaded loop will get compiled else the one for threading.

(instead of the emit of events just use your update/draw calls at that spots)
superstrict
?Threaded
Import Brl.threads
?
'class for smooth framerates, includes loop
Type TDeltaTimer
	field newTime:int 			= 0
	field oldTime:int 			= 0.0
	field loopTime:float			= 0.1
	field deltaTime:float			= 0.1		'10 updates per second
	field accumulator:float			= 0.0
	field tweenValue:float			= 0.0		'between 0..1 (0 = no tween, 1 = full tween)

	field fps:int 				= 0
	field ups:int				= 0
	field deltas:float 			= 0.0
	field timesDrawn:int 			= 0
	field timesUpdated:int 			= 0
	field secondGone:float 			= 0.0

	field totalTime:float 			= 0.0

	?Threaded
	Global UpdateThread:TThread
	Global drawMutex:TMutex 		= CreateMutex()
	Global useDeltaTimer:TDeltaTimer= null
	?

	Function Create:TDeltaTimer(physicsFps:int = 60)
		local obj:TDeltaTimer	= new TDeltaTimer
		obj.deltaTime		= 1.0 / float(physicsFps)
		obj.newTime		= MilliSecs()
		obj.oldTime		= 0.0
		return obj
	End Function

	?Threaded
	Function RunUpdateThread:Object(Input:Object)
		repeat
			useDeltaTimer.newTime		= MilliSecs()
			if useDeltaTimer.oldTime = 0.0 then useDeltaTimer.oldTime = useDeltaTimer.newTime - 1
			useDeltaTimer.secondGone	:+ (useDeltaTimer.newTime - useDeltaTimer.oldTime)
			useDeltaTimer.loopTime		= (useDeltaTimer.newTime - useDeltaTimer.oldTime) / 1000.0
			useDeltaTimer.oldTime		= useDeltaTimer.newTime

			if useDeltaTimer.secondGone >= 1000.0 'in ms
				useDeltaTimer.secondGone 	= 0.0
				useDeltaTimer.fps		= useDeltaTimer.timesDrawn
				useDeltaTimer.ups		= useDeltaTimer.timesUpdated
				useDeltaTimer.deltas		= 0.0
				useDeltaTimer.timesDrawn 	= 0
				useDeltaTimer.timesUpdated	= 0
			endif

			'fill time available for this loop
			useDeltaTimer.loopTime = Min(0.25, useDeltaTimer.loopTime)	'min 4 updates per seconds 1/4
			useDeltaTimer.accumulator :+ useDeltaTimer.loopTime

			if useDeltaTimer.accumulator >= useDeltaTimer.deltaTime
				'force lock as physical updates are crucial
				LockMutex(drawMutex)
				While useDeltaTimer.accumulator >= useDeltaTimer.deltaTime
					useDeltaTimer.totalTime		:+ useDeltaTimer.deltaTime
					useDeltaTimer.accumulator	:- useDeltaTimer.deltaTime
					useDeltaTimer.timesUpdated	:+ 1
					EventManager.triggerEvent( "App.onUpdate", TEventSimple.Create("App.onUpdate",null))
				Wend
				UnLockMutex(drawMutex)
			endif
			delay(1)
		forever
	End Function

	Method Loop()
		'init update thread
		if not self.UpdateThread OR not ThreadRunning(self.UpdateThread)
			useDeltaTimer = self
			print " - - - - - - - - - - - - "
			print "Start Updatethread: create thread"
			print " - - - - - - - - - - - - "
			self.UpdateThread = CreateThread(self.RunUpdateThread, Null)
		endif

		'if we get the mutex (not updating now) -> send draw event
		if TryLockMutex(drawMutex)
			'how many % of ONE update are left - 1.0 would mean: 1 update missing
			self.tweenValue = self.accumulator / self.deltaTime

			'draw gets tweenvalue (0..1)
			self.timesDrawn :+1
			EventManager.triggerEvent( "App.onDraw", TEventSimple.Create("App.onDraw", string(self.tweenValue) ) )
			UnlockMutex(drawMutex)
		endif
		delay(1)
	End Method
	?

	?not Threaded
	Method Loop()
		self.newTime		= MilliSecs()
		if self.oldTime = 0.0 then self.oldTime = self.newTime - 1
		self.secondGone		:+ (self.newTime - self.oldTime)
		self.loopTime		= (self.newTime - self.oldTime) / 1000.0
		self.oldTime		= self.newTime

		if self.secondGone >= 1000.0 'in ms
			self.secondGone 	= 0.0
			self.fps		= self.timesDrawn
			self.ups		= self.timesUpdated
			self.deltas		= 0.0
			self.timesDrawn 	= 0
			self.timesUpdated	= 0
		endif

		'fill time available for this loop
		self.loopTime = Min(0.25, self.loopTime)	'min 4 updates per seconds 1/4
		self.accumulator :+ self.loopTime

		'update gets deltatime - fraction of a second (speed = pixels per second)
		While self.accumulator >= self.deltaTime
			self.totalTime		:+ self.deltaTime
			self.accumulator	:- self.deltaTime
			self.timesUpdated	:+ 1
			EventManager.triggerEvent( "App.onUpdate", TEventSimple.Create("App.onUpdate",null))
		Wend
		'how many % of ONE update are left - 1.0 would mean: 1 update missing
		self.tweenValue = self.accumulator / self.deltaTime

		'draw gets tweenvalue (0..1)
		self.timesDrawn :+1
		EventManager.triggerEvent( "App.onDraw", TEventSimple.Create("App.onDraw", string(self.tweenValue) ) )
		'Delay(1)
	End Method
	?
	'tween value = oldposition*tween + (1-tween)*newPosition
	'so its 1 if no movement, 0 for full movement to new position
	'each drawing function has to take care of it by itself
	Method getTween:float()
		return self.tweenValue
	End Method

	'time between physical updates as fraction to a second
	'used to be able to give "velocity" in pixels per second (dx = 100 means 100px per second)
	Method getDeltaTime:float()
		return self.deltaTime
	End Method
End Type


What made me wonder is that on windows a debug+mt-build runs 15 graphical fps (thread 1) and <5 physical fps (thread 2) on my virtual machine - release build runs way faster (100 for graphics and the forced/limited 60 physical updates per second). On Linux there is nearly no difference between debug and release.


Edit: Singlethreaded I get (in a ingamescene) 250-270fps, multithreaded it drops to 170-190 - but that's because I need to "delay(1)" drawing and updates as else updates wont occour steady (30-65 instead 59-61) and therefor spritemovement is far away from smooth.
Ideas how to avoid the delay within the drawing loop so I can see where bottlenecks occour (later I can limit it the same way as I do within the updates loop) ? Without delay(1) in the main thread, i get 180-300fps so removing the updatepart from the first thread is able to speed up the app again (but multicore cpus are certainly able to run the app smooth enough :D).

bye
Ron

Last edited 2012


Zeke(Posted 2012) [#10]



Derron(Posted 2012) [#11]
thanks Zeke ... but although your code works it seems to have a disadvantage.

Above the "titleBar" I have black area (where I can drag the window by pressing the mouse button) - am I able to style it (custom "application bar")?

Edit: it only happens when using GLMax2dDriver instead of DX... using the default one like in your setup, the code works as expected.
Problem then is the displacement which have to be done for all objects - or to handle a "mouseover"-dropdown-widget.
So now the question would be: how to handle opengl so it does not have that black border?

And does somebody know how a Mac handles dragging and meanwhile updating?

bye
Ron

Last edited 2012