Sign in to follow this  
Ratmil

C# on idle

Recommended Posts

Hi. I am developing a C# game using GDI+ (it´s a very simple board game). I am using a timer for animations, but that´s pretty bad. Somewhere I heard about an OnIdle event that´s called when the program is, well, idle. But I can´t find on MSDN. Is there something like that in .NET? Or do I have to use a thread? Thanks.

Share this post


Link to post
Share on other sites
It's the Application.Idle event, available from the Windows Forms API.

Note that it's fired when the application becomes idle, not when the application is idle. You generally want to spin in a loop in the idle handler making a call to a P/Invoke'd PeekMessage() to see if you should stop.

Share this post


Link to post
Share on other sites
Using PickMessage would make my game windows dependant. I tried something like this:

public partial class GameForm : Form
{
public GameForm()
{
InitializeComponent();
gameApp = new GameApp(this);
lastUpdate = DateTime.Now;
closing = false;
timerMain.Enabled = true;
}

private void timerMain_Tick(object sender, EventArgs e)
{
timerMain.Enabled = false; // Disable timer at first call
while (!closing)
{
DateTime currentTime = DateTime.Now;
TimeSpan ellapsed = currentTime - lastUpdate;
lastUpdate = currentTime;
gameApp.Play(ellapsed);
Application.DoEvents();
}
}
}

What do you think?

Share this post


Link to post
Share on other sites
Your game is Windows-dependent by virtue of using Windows.Forms to begin with. Granted you might have a shot with the Mono implementation, but honestly I'd just suggest writing at least partially separate code per OS.

Share this post


Link to post
Share on other sites
You are right, the game is Windows-dependent from the moment I created the project as Windows application.
So, it won´t run on Mono, right?
Anyway, my solution isn´t that bad, is it?

Share this post


Link to post
Share on other sites
Mono definitely won't like you invoking User32.dll. And I don't like Tom Miller's solution; he breaks platform independence for a performance boost that has questionable significance.

Considering that DirectX is a Windows API, it doesn't surprise me that his solution is in the DirectX SDK.

Mono's support for Windows.Forms is lousy (just like everything Mono has support for), but it does have support for it, and with a bit of twisting you can get it working (if you avoid using Application.DoEvents() at all costs!). You don't have any chance at getting a User32.dll pinvoke working though, I suggest using this method.

http://www.csharp-home.com/index/tiki-read_article.php?articleId=146&page=2

You won't even notice the difference.

Share this post


Link to post
Share on other sites
I'm using this:

/// <summary>Control that automatically updates itself when the application is idle</summary>
public partial class AutoRefreshingControl : Control {

/// <summary>Initializes a new auto-updating control</summary>
public AutoRefreshingPageControl() {
this.refreshPageDelegate = new MethodInvoker(refreshPage);
this.onApplicationIdleDelegate = new EventHandler(onApplicationIdle);
}

/// <summary>
/// Wird aufgerufen wenn die Seite sich automatisch aktualisieren kann
/// </summary>
protected virtual void OnAutoRefresh() { }

/// <summary>Immediately releases all resource owned by the instance</summary>
/// <param name="calledManually">
/// Whether Dispose() is being called manually (not by the GC)
/// </param>
protected override void Dispose(bool calledManually) {
if (calledManually) {
if(this.autoRefreshEnabled) {
stopAutoRefresh();
}
}

base.Dispose(calledManually);
}

/// <summary>Whether automatic refreshing is currently enabled</summary>
protected bool AutoRefreshEnabled {
get {
return this.autoRefreshEnabled;
}
set {
if(value == this.autoRefreshEnabled) {
return;
}

if(value) {
startAutoRefresh();
} else {
stopAutoRefresh();
}
}
}

/// <summary>Called when the control is created</summary>
protected override void OnCreateControl() {
if(this.autoRefreshEnabled) {
if(this.autoRefreshPostponed) {
this.autoRefreshPostponed = false;
startAutoRefresh();
}
}
}

/// <summary>Begins the auto-refresh cycle</summary>
private void startAutoRefresh() {
if(Created) {
// The ignition uses a BeginInvoke() call - the application will when send itself
// a window message, meaning it won't be idle during the first call to
// refreshPage(). That means we can use the Application.Idle event for the
// next call and vice versa.
// Since we're not looping in Application.Idle, multiple controls can update
// themselves using the application's idle time.
this.refreshPageAsyncResult = BeginInvoke(this.refreshPageDelegate);
Application.Idle += this.onApplicationIdleDelegate;
} else {
// Control hasn't been created yet. Ignition needs to be deferred until the
// Control has been created.
this.autoRefreshPostponed = true;
}

this.autoRefreshEnabled = true;
}

/// <summary>Stoppt die automatische Steuerelementaktualisierung</summary>
private void stopAutoRefresh() {
Application.Idle -= this.onApplicationIdleDelegate;

// Also reset autoRefreshPostponed zurücksetzen. While a second ignition would be
// caught, this is way cleaner because no useless code execution happens in the first place.
this.autoRefreshPostponed = false;
this.autoRefreshEnabled = false;
}

/// <summary>Runs the automatic control update</summary>
private void refreshPage() {

// Was this call caused by BeginInvoke()? If so, we need to call
// EndInvoke() and leave the next call up to the Application.Idle event
if(this.refreshPageAsyncResult != null) {
EndInvoke(this.refreshPageAsyncResult);
this.refreshPageAsyncResult = null;
} else {
this.refreshPageAsyncResult = BeginInvoke(this.refreshPageDelegate);
}

// Perform the automatic refresh
OnAutoRefresh();

}

/// <summary>Called when the application becomes idle</summary>
/// <param name="sender">Not used</param>
/// <param name="arguments">Not used</param>
private void onApplicationIdle(object sender, EventArgs arguments) {
refreshPage();
}

/// <summary>Delegate for the refreshPage() method</summary>
private MethodInvoker refreshPageDelegate;
/// <summary>Delegate for the onApplicationIdle() method</summary>
private EventHandler onApplicationIdleDelegate;

/// <summary>AsyncResult returned by the updatePage() call in progress.</summary>
private volatile IAsyncResult refreshPageAsyncResult;
/// <summary>Whether automatic refreshing is currently enabled</summary>
private volatile bool autoRefreshEnabled;
/// <summary>
/// Is set when the ignition of the auto refresh cycle wasn't possible because
/// the control hadn't been created yet.
/// </summary>
private volatile bool autoRefreshPostponed;

}



It uses Application.Idle together with BeginInvoke() to allow for any number of controls making use of idle refresh (looping in Application.Idle with PeekMessage() works too, but will halt any other controls using the same technique - as I found out, XNA does it this way ;))

Share this post


Link to post
Share on other sites
Hi, Thevenin.
I tested the OnPaint solution (http://www.csharp-home.com/index/tiki-read_article.php?articleId=146&page=2) and it consumes a few more megabytes of memory than calling Application.DoEvents.
Maybe the difference will be seen after the game has been some time running.
What do you think?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this