[.net] Plug in Tom's Render Loop in a few seconds!

Started by
30 comments, last by fchivu 16 years, 3 months ago
Hey My garbage collection timeline is now lovely and flat :) I was too lazy to put the whole of Tom's render loop in, so I'll describe my simpler way. It's just as good as Tom's original because DoEvents only ends up getting called like 5 times during the life of the application (obviously if you move your window about, this will increase, but to no worse than before) So this was my old render loop:

while (mainWindow.Created)
{
	FullRender();
	System.Windows.Forms.Application.DoEvents();
}
And this is my new render loop, done in just a few seconds:

while (mainWindow.Created)
{
	while (AppStillIdle)
	{
		FullRender();
	}
	System.Diagnostics.Debug.WriteLine("Calling DoEvents.");
	System.Windows.Forms.Application.DoEvents();
}

private bool AppStillIdle
{
	get
	{
		SafeNativeMethods.Message msg;
		return !SafeNativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
	}
}
So simple to plug in! You could do it right now...I'll be kind and give you the native methods, it wasn't so easy to complete these, I had to have a look at the SDK framework to find WindowMessage:

internal static class SafeNativeMethods
{
	/// <summary>Window messages</summary>
	public enum WindowMessage : uint
	{
		// Misc messages
		Destroy = 0x0002,
		Close = 0x0010,
		Quit = 0x0012,
		Paint = 0x000F,
		SetCursor = 0x0020,
		ActivateApplication = 0x001C,
		EnterMenuLoop = 0x0211,
		ExitMenuLoop = 0x0212,
		NonClientHitTest = 0x0084,
		PowerBroadcast = 0x0218,
		SystemCommand = 0x0112,
		GetMinMax = 0x0024,

		// Keyboard messages
		KeyDown = 0x0100,
		KeyUp = 0x0101,
		Character = 0x0102,
		SystemKeyDown = 0x0104,
		SystemKeyUp = 0x0105,
		SystemCharacter = 0x0106,

		// Mouse messages
		MouseMove = 0x0200,
		LeftButtonDown = 0x0201,
		LeftButtonUp = 0x0202,
		LeftButtonDoubleClick = 0x0203,
		RightButtonDown = 0x0204,
		RightButtonUp = 0x0205,
		RightButtonDoubleClick = 0x0206,
		MiddleButtonDown = 0x0207,
		MiddleButtonUp = 0x0208,
		MiddleButtonDoubleClick = 0x0209,
		MouseWheel = 0x020a,
		XButtonDown = 0x020B,
		XButtonUp = 0x020c,
		XButtonDoubleClick = 0x020d,
		MouseFirst = LeftButtonDown, // Skip mouse move, it happens a lot and there is another message for that
		MouseLast = XButtonDoubleClick,

		// Sizing
		EnterSizeMove = 0x0231,
		ExitSizeMove = 0x0232,
		Size = 0x0005,

	}

	[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
	public struct Message
	{
		public System.IntPtr hWnd;
		public WindowMessage msg;
		public System.IntPtr wParam;
		public System.IntPtr lParam;
		public uint time;
		public System.Drawing.Point p;
	}

	[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
	[System.Runtime.InteropServices.DllImport("User32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
	public static extern bool PeekMessage(out Message msg, System.IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
}

Hope that helps!
Advertisement
Well messages are sent whenever the mouse moves or a key/button is pressed, so DoEvents is called a lot more than 5 times.
Note that it really needs to be:
while (AppStillIdle && mainWindow.Created) otherwise your app might not exit when exceptions are thrown from within.

True, but it's still better than before :) And using WndProc is not my idea of fun :p, but thanks for that feedback...I'll have to test it more and see.
I hypothesize but unless mouse messages are sent at 70 times per second it's still probably a benenfit...but yes, more testing.
Application.DoEvents() suffers from a significant memory leak when run in a loop. Not to be negative, but Tom's way is better because it does not suffer from this leak, hence his efforts in trying to find an ideal render loop approach in the first place.

Cheers,
Graham
Use Spy++ to see just what messages are being sent and processed.

:D
If anyone has a link to the evidence for the DoEvents 'leak' I would love to see it. Everything Tom and Rick talked about (http://www.thezbuffer.com/articles/185.aspx for the history) talked about excessive memory allocations, but no leak.

I chatted to Graham in IRC and he thinks the leak was blogged about but I can't find any reference and the sample code I have using DoEvents doesn't show any sign of a leak in perfmon, though you can see the increase in allocations and GC0's). I would love to add evidence for a leak to my history, or show that theres isn't one before one more rumour about bad event loops is created. (though to be honest, why bother - just use the one that everyone agrees is best)

I found one reasonable claim of a leak http://groups.google.com/group/microsoft.public.dotnet.general/browse_thread/thread/e16a97db50bfdbbf though there's no follow up from MS and its only under certain conditions.
ZMan
I think the term Memory leak is quite misused regarding DoEvents() . Everyone, and I as well, sometimes use Memory leak when pointing to continuous memory allocations caused by DoEvents(). In my recent thread there's quite nice discussion about this topic. Of course, just to sum up things, DoEvents() do not leak the memory, rather keeps allocating memory resources during the life of the game(loop), althought it frees the memory at the end.
Yes, to clarify, managed applications can only "leak" memory (in the standard sense) if you are consuming legacy components or doing unmanaged interop. So essentially, the managed app itself isn't leaking. Managed apps can "leak" memory, but that memory is reclaimed by the GC when the application closes.

On the other hand, the leak DoEvents() ties with is a constant 4kb allocation every 5 seconds. And since DoEvents() is generally called in a render loop, this allocation happens a lot. Aside from the "leak" it's also very bad practice to call DoEvents() to force any kind of event processing (including control refreshing\invalidating). So regardless of the classification of problems behind that method, it is advisable to to look to alternate solutions (such as the Application.Idle event).

Remember kids: "Application.DoEvents() is pure EVIL" :)

~Graham
Quote:Original post by gwihlidal

Remember kids: "Application.DoEvents() is pure EVIL" :)

~Graham



Just out of curiousity, what would you recommend using inside of a tight loop(in an app, not a render / game loop) where you want to force the app's message pump? Using .Net 2.0 /SQL Server 2000 I've found that if I don't call Application.DoEvents() inside of a tight loop where I'm executing a SQL statement on each iteration of the loop, my SQL performance drops significantly. The apps I'm doing this in are more utility apps that are pretty much every once in a while type things, and don't run for more than a few hours continuously, so memory loss in these instances isn't that big of a deal. After reading your thoughts on DoEvents, I'm more curious about an alternative than anything.
Well, the whole problem behind that situation is that the loop is causing the message pump to not relieve other messages (like SQL). In .NET 1.1 you would use the asynchronous programming model (like BeginInvoke), and while you can do this in 2.0, you can also use the new BackgroundWorker class for it.

~Graham

This topic is closed to new replies.

Advertisement