Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Don't forget to read Tuesday's email newsletter for your chance to win a free copy of Construct 2!


C# Timers, having issues


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
15 replies to this topic

#1 Chris_F   Members   -  Reputation: 2438

Like
0Likes
Like

Posted 29 October 2010 - 03:47 PM

I need to use a timer to call the Draw method of my graphics engine which draws the next frame to the screen buffer and calls Invalidate() to refresh the form. I'd like to have a steady framerate, and 60FPS would be ideal. The problem is I've tried every type of timer (that I know of) and none of them are reliable enough.

If I use the following code (paraphrased a bit), which draws a large triangle on the screen. It rotates the triangle every frame and displays the frame rate as well:


Thread drawLoop = new Thread(new ThreadStart(Draw));
drawLoop.Start()

...

void Draw()
{
for(;;)
{
graphics.Clear();
graphics.Draw();
this.Invalidate();
Thread.Sleep(16); // has to be integer value, 16ms = 62.5fps
}
}


It show's a very solid 66.7FPS on the screen and the animation is very smooth.

However, when I tried to implement this as a timer and EventHandler, e.g. :


var timer = new System.Timers.Timer(1000.0f / 60.0f); //use 60FPS (also tried 16ms as before)
timer.Elapsed += new ElapsedEventHandler(Draw);
timer.Start()

void Draw(object source, ElapsedEventArgs e)
{
graphics.Clear();
graphics.Draw();
this.Invalidate();
}


Besides System.Timers.Timer, I've also used System.Windows.Forms.Timer and System.Threading.Timer and they all yield identical results; slow and jerky animation with frame rates that jump around +/- 50% and are otherwise lower than they should be (e.g. 16ms should be ~62FPS but you get closer to 40FPS).

This is extremely frustrating and I hope someone here has an answer as to what I should do.



Sponsor:

#2 Nypyren   Crossbones+   -  Reputation: 4498

Like
0Likes
Like

Posted 29 October 2010 - 03:55 PM

Most games use a loop like the first one to ensure that they have more control over the framerate. Why do you need to use a timer? The Windows events can be pumped, or you can use the Application.Idle trick to run game-related processing as fast as possible.

#3 Chris_F   Members   -  Reputation: 2438

Like
0Likes
Like

Posted 29 October 2010 - 04:44 PM

Should I maybe use a BackgroundWorker?

#4 keinmann   Members   -  Reputation: 122

Like
0Likes
Like

Posted 29 October 2010 - 08:42 PM

NEVER use Thread.Sleep(int) to try to keep something at a constant beat or synchronize threads. Not only is it not very accurate, but if you use it for thread synchronization, you're shooting yourself in the foot. It's ok if you want to add something like Thread.Sleep(1) to just let it rest for a little sliver of time, but never ever ever try to use this as a time keeping or synchronization mechanism.

You need to implement a "GameClock" like you can see in the SlimDX Samples (I also don't think you mentioned what API you're using). Basically, you're going to use System.Diagnostics.Stopwatch to do your work. You use Stopwatch.GetTimeStamp() to get the current amount of system ticks (keep in mind, you aren't instantiating a Stopwatch, but using the static methods/properties). When your GameClock object updates again, you get the current timestamp and find the amount of ticks which elapsed since the last update. You can convert that to milliseconds by getting the Stopwatch.Frequency property, which tells you how many ticks are in a second on the local system.

Using such a mechanism, your game loop will use an accumulator-like mechanism to keep update and render calls properly synced.

EDIT: If you like, I could post my implementation for you to learn from. Just don't take my code verbatim and stick it in your project. Read it, and write one that suits your needs properly. Otherwise, you're cheating yourself out of the experience and fun! :)

#5 Koen   Members   -  Reputation: 511

Like
0Likes
Like

Posted 29 October 2010 - 10:35 PM

I have heard of similar "problems" using the .net timer class. The explanation was that when you keep no reference to the timer object, the garbage collector might collect the timer before the specified time has passed and your function has been called. Maybe that can explain your strange results?

#6 Chris_F   Members   -  Reputation: 2438

Like
0Likes
Like

Posted 30 October 2010 - 08:13 AM

@keinmann

I'm not using any APIs (other than GDI+) since this is just a simple software rendering exercise I'm doing. I'd like to see your code, or maybe a link to something similar.

I have a problem when I use a sleep() that's lower than 2ms, I draw the frame rate to the top left corner and it doesn't display if it's running too fast.

#7 Chris_F   Members   -  Reputation: 2438

Like
0Likes
Like

Posted 31 October 2010 - 11:15 AM

bump

#8 EJH   Members   -  Reputation: 314

Like
0Likes
Like

Posted 01 November 2010 - 05:06 AM

Here's a timer class I used for a C# game server. Create an instance of a TimeClock and then Update() it in your main program update loop.


using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

public class TimeClock
{
private Stopwatch stopWatch;

// time elapsed since the previous update
public float elapsedMS = 0f;
private float currentTime = 0f;
private float oldTime = 0f;


public TimeClock()
{
stopWatch = new Stopwatch();
stopWatch.Reset();
stopWatch.Start();
}


// calculate elapsed ms since update() was last called
public void Update()
{
oldTime = currentTime;
currentTime = (float)stopWatch.ElapsedMilliseconds;
elapsedMS = currentTime - oldTime;
}
}



In your code somewhere you'd do something like this:


// timeclock and a time variable
float elapsedMilliseconds = 0;
TimeClock timeClock = new TimeClock();

// do this somewhere in your update routine
YourApp.Update()
{

// update the elapsed time
timeClock.Update();
elapsedMilliseconds += timeClock.elapsedMS;


// check if 16 ms have passed
// if so, reset the timer and fire the event
if(elapsedMilliseconds >= 16f)
{
elapsedMillisecond %= 16f;
DoSomething();
}
}









#9 keinmann   Members   -  Reputation: 122

Like
0Likes
Like

Posted 01 November 2010 - 05:41 AM

The best way is NOT to instantiate the Stopwatch class and use Stop/Start/Reset. You should use the GetTimeStamp() method to get system ticks and the Frequency property to calculate real elapsed time. That's the most accurate way.

Check your pm box, and I'm going to give you a copy of the original file, documentation included. Here I'm going to post it with no documentation for readability's sake, since you can't collapse things in the forums' text boxes.


#region Using Statements
/* System References :: */
using System;
using System.Diagnostics;

/* SlimDX References :: */

/* Engine References :: */

#endregion

internal sealed class GameClock
{
/*-----------------------------------------------------------------------------
* CONSTRUCTORS & INITIALIZATION ::
*-----------------------------------------------------------------------------*/


internal GameClock()
{
Reset();
}

/*-----------------------------------------------------------------------------
* FIELDS & PROPERTIES ::
*-----------------------------------------------------------------------------*/


long count;
const long nsfactor = 10000000L;

public bool IsRunning { get { return isrunning; } }
bool isrunning = false;

internal long Frequency { get { return Stopwatch.Frequency; } }

internal bool isHighResolution { get { return Stopwatch.IsHighResolution; } }

internal static long Timestamp { get { return Stopwatch.GetTimestamp(); } }

internal TimeSpan Elapsed { get { return elapsed; } }
TimeSpan elapsed;

internal TimeSpan Total { get { return total; } }
TimeSpan total;

/*-----------------------------------------------------------------------------
* METHODS & IMPLEMENTATIONS ::
*-----------------------------------------------------------------------------*/


internal void Start()
{
isrunning = true;
count = Timestamp;
}

internal void Stop()
{
isrunning = false;
}

internal void Reset()
{
count = Timestamp;
elapsed = TimeSpan.Zero;
total = TimeSpan.Zero;
isrunning = false;
}

internal void Restart()
{
Reset();
Start();
}

internal void Step()
{
if (isrunning)
{

long last = count;
count = Timestamp;
long offset = count - last;
elapsed = DeltaToTimeSpan(offset);
total += elapsed;
}
}

private TimeSpan DeltaToTimeSpan(long delta)
{
return TimeSpan.FromTicks((delta * nsfactor) / Frequency);
}

/*-----------------------------------------------------------------------------*
*******************************************************************************
*-----------------------------------------------------------------------------*/


};



Very simple yet effective mechanism, and performance is excellent.

#10 voguemaster   Members   -  Reputation: 179

Like
0Likes
Like

Posted 01 November 2010 - 07:40 AM

Hi,

Although the proper solution was already given by keinmann, just for the sake of knowledge, here are a couple of points to consider with C# timers:

1. System.Windows.Forms timers are driven by Win32's windows message loop. Basically, a WM_TIMER message is sent to the window procedure and dispatched to the event handler.
This is a low-frequency timer and it also suffers because many other windows messages can be present in the message queue for the main UI thread when it is posted, causing a delay until it actually reaches the application.

2. System.Timers.Timer should be a better solution but it is activated by sending an event from a thread in a ThreadPool provided by OS. How you can draw when you get a callback from a thread that is not the main UI event handling thread is beyond me.

The same applies for System.Threading.Timer, it is called from a ThreadPool and not on the UI thread.

3. You also allocated a thread for drawing. It operates on a graphics object but what thread was the control from which that graphics is derived from ? (unless you're doing something else entirely). Usually you can't do stuff like that if you allocated a Control in thread #1 and you try to change its state from thread #2.

I don't know how it even worked, unless there's something I'm missing.

#11 keinmann   Members   -  Reputation: 122

Like
0Likes
Like

Posted 01 November 2010 - 08:49 AM

In Windows.Forms programming, you can always use Form.Invoke(delegate) to execute something on the proper UI thread; which is a MUST with Winforms. You can't touch a control from any other thread. See the "MethodInvoker" class which can be very helpful in this scenario. Basically, the thread is asking the main UI thread to do { whatever(); } on the correct thread so you can access controls without violating the rules. You can also check "InvokeRequired" to know if this is necessary or if you can access controls as normal. To me: overkill. Just do it. There is a way to get around this single-threaded UI rule, but I won't even say how because it's a pretty dirty thing to do, imho. Do it right.

Still, stray away from that where you can. My timer above will work fine, even if there is a little delay in the message loop. I let it sit idle for HUGE periods of time before calling Step() again and it still kept the correct time. So it won't suffer that, and the resolution/frequency is as good as the underlying hardware can provide. You can't even hurt it from calling Step too fast; it still stays on track. I tested nearly every case I could think of, and she works fine! :)

@voguemaster: Thanks, and your additional info will probably help many!

#12 Chris_F   Members   -  Reputation: 2438

Like
0Likes
Like

Posted 01 November 2010 - 10:14 AM

Quote:
Original post by voguemaster
3. You also allocated a thread for drawing. It operates on a graphics object but what thread was the control from which that graphics is derived from ? (unless you're doing something else entirely). Usually you can't do stuff like that if you allocated a Control in thread #1 and you try to change its state from thread #2.

I don't know how it even worked, unless there's something I'm missing.


I defined a Graphics member in my Form1 class and initialized it in Form1_Load, the bitmap that the graphics object draws to is set to the form's background image.



#13 voguemaster   Members   -  Reputation: 179

Like
0Likes
Like

Posted 01 November 2010 - 09:40 PM

hi,

Keinmann, since you touched on the Invoke delegate issue, I want to also add that it can be quite expensive in certain circumstances. Sometimes passing objects via those delegates will cause them to be serialized and de-serialized so they can be passed to the UI thread, an expensive operation to be sure.

Anyway, I took a look at the example code you posted, quite nice indeed :)

#14 keinmann   Members   -  Reputation: 122

Like
0Likes
Like

Posted 02 November 2010 - 09:47 AM

@ voguemaster:

Well, you taught me something new. Never considered the performance consequences of Form.Invoke(delegate) before. Reason being that I only use it in non-game Winform applications. As a general rule of thumb, I try to avoid having a thread that needs to tamper with Control objects; sometimes tough though when you need to update a status bar in a long operation. I don't want to hijack the OP's thread, but does the dilemma you describe apply with this sort of usage?


Action action = new Action(() =>
{
DoSomethingOnUIThread();
UpdateData();
Whatever();
});
this.Invoke(action);



...assuming "this" is a Winform? That's about as complicated as I ever let things get with another thread "talking" to controls/UI thread; just for the sake of design and cleanliness.

And thanks for your positive feedback on my code! That's straight production code from my (incomplete) SlimDX/D3D11 engine. That's how much of it looks: clean, simple, fast and loosely coupled with other implementations (or totally decoupled in this case). I like to build complex systems from simple, maintainable parts. I hope that little class can help folks as much as you have helped by sharing some knowledge with us!

Interlocked.Increment(ref your_rating); :)

#15 voguemaster   Members   -  Reputation: 179

Like
0Likes
Like

Posted 02 November 2010 - 10:59 PM

Actually, yes. Thread marshalling will be used when you invoke a delegate on a control like you described. This happens also when switching between AppDomains for example.

What this really mean is that arguments to the delegate function will have to be serialized and de-serialized. There is no way around it in this case.

If the code is not performance critical in any way or such an invocation doesn't occur often, don't think of it. I just wanted to alert it to anyone that may be interested :-)


#16 keinmann   Members   -  Reputation: 122

Like
0Likes
Like

Posted 03 November 2010 - 11:07 AM

Well thanks again! That's nice to know in any case. :)

I have a few more questions about it, just for the sake of learning more. But I'll take it up with you via pm.

@ Chris:

I hope you got my pm with the original GameClock.cs file, and that you have resolved your issues!




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS