"grayaii" <gr*****@gmail.comwrote in message
news:11**********************@n67g2000cwd.googlegr oups.com...
Thank you so much for the response. I will try using Threading.Timer
first thing tomorrow morning.
Okay. Hopefully you will wind up not needing it, but at least it will be
useful to know if it's a decent alternative for you.
My application is actually a game, which is why OnPaint has to be
called so often.
There are so many topics on the "ultimate c# game loop", but "they" say
the easiest way to have a high frame rate game is to call Invalidate at
the end of OnPaint
Easy is not necessary right. In fact, it almost never is. :)
You don't say what kind of game you have, but it may or may not be the case
that you need the highest frame rate. However, typically a game that
requires a high frame rate will not use the normal window messaging paradigm
at all. It will redraw its graphics via some other mechanism. Going
through User (the window message pump) and GDI (the invalidation/redraw
paradigm) is overhead that isn't acceptable when frame rate needs to be
maximized.
In addition, for many games the worst thing you can do with respect to frame
rate is to invalidate the entire window. Doing so (or the equivalent) makes
a lot of sense for 3D games where the camera is always in motion, because
the entire view is going to have to be redrawn every frame anyway. You
wouldn't waste time invalidating and waiting for a WM_PAINT message (which
is what underlies the .NET paint event mechanism). You'd just draw a new
frame as often as you could.
But if you don't have a 3D game, you're dealing with some sort of 2D
sprite-like graphics, most likely. In this case, often only small areas of
the screen are actually going to change at any given time. Even if you have
(for example) a grid playfield in which every other square contains an
animated icon, a) only half the window actually needs to be redrawn, and b)
it's unlikely you actually have icon animations rendered at as high a frame
rate as is typically possible (for example, animations may be stored at 15
or 30 frames per second, while the actual possible frame rate might be over
100 fps, especially for 2D stuff...that means most of the time, even the
animated parts of your screen aren't changing from one frame to the next).
As a general rule, simple 2D games work well simply by using the normal
Windows redraw mechanisms. Based on user input and game mechanics,
invalidate only the portion of the window that has actually changed, when it
changes, and let the paint event redraw it next chance it gets. Don't force
the entire window to be redrawn, because there's no point if nothing has
changed.
(And I should note here: typically, it's not so much that you avoid running
code to redraw a portion of the screen, as it is that the code that does run
winds up not having to actually move any data around, because drawing is
clipped to exclude any portion that hasn't been invalidated. It is
theoretically possible to inspect the clipping area in the OnPaint method,
and not draw stuff that's entirely outside the clipped area. But the
biggest gains comes simply from the clipping itself; unless you have very
simple, fast ways to avoid drawing altogether, it's faster to just run
through all of the drawing code normally, and let Windows save you the time
of actually drawing anything when it's outside the clipped area).
[...]
Interestingly enough the game works pretty well, in the sense that
MouseEvents and KeyEvents get trapped fine and all the animations are
drawn rather nicely. Why those events work and not the timer is beyond
my understanding :)
Probably because the message queue isn't strictly FIFO. Windows does some
prioritization on messages in the queue, allowing higher priority ones to
bypass lower priority ones.
I don't know the specifics off the top of my head, but it wouldn't surprise
me to find that the WM_TIMER message is low on the priority list, behind
WM_PAINT, WM_MOUSE, and WM_KEYDOWN/UP.
What I do believe is that I have is the classic
problem of distributing the workload to the appropriate places. For
example, my OnPaint function is currently doing way too much work: For
instance, the game lags significantly when a monster is performing a
"Shortest Path Algorithm" call on a target that is very far away, and
it's because that function call is inside OnPaint. The more OnPaint
does, the lower the framerate.
Well, one problem is that your OnPaint method should *only* be painting.
Game mechanics should be somewhere else. The exact mechanism for your game
mechanics will vary according to your needs, but it's a mistake to
intertwine the game mechanics with the game rendering. The two are
completely different things, and while they have to interact with each
other, they should remain isolated from each other in terms of where the
code is.
Another problem may be that your path finding algorithm is either flawed or
inappropriate. Even after you seperate drawing from calculating, your
monster can't go anywhere until it's calculated a path to travel. If you
insist on holding up the rest of the game until the monster's decided where
to go, then frame rate will suffer regardless. Not because you can't draw
that fast, but just because nothing's happened until the monster figures out
where to go.
Alternatives to that problem include:
* Multi-threading the game AI. This would allow your monster to think
on a different thread than that used for the rest of the game. Just as the
game can proceed without the human player doing anything (while the human
tries to figure out what to do), it can proceed without the monster doing
anything while it tries to figure out what to do. Using this method, the
monster could interact with the game engine in much the same way the user
does: there would be some sort of "monster input" API that the monster AI
could call to tell the game engine where and how it wants to move, once it
figures that out.
* Use a more efficient path-finding algorithm. This may or may not be a
possibility, depending on how you've done the algorithm at this point.
However, it's probably worth a look. Especially if the routine is used on a
regular basis, it behooves you to make sure it is absolutely as efficient as
possible.
* Pare the algorithm down so that it doesn't do so much work each frame.
This is especially appropriate if the monster will have to recalculate the
path the next frame anyway (in response, for example, to player input).
Don't waste time calculating the optimal path, since you're going to toss
out most of that work the next frame anyway...just do enough of the
calculation to get the monster moving in the right direction. Do that again
next frame. Keep doing that until the monster reaches its goal. Unless
your monster can move through the entire optimal path in the time it takes
to draw a single frame, then calculating that entire optimal path each frame
is a waste of time.
* You could even combine the first and third ideas above, by allowing
your algorithm to maintain state somewhere and tracking how much time it's
spent. Set an upper time limit, and have it save its state and yield
control to the rest of the program when it hits that upper time limit. This
sort of multi-threads the monster AI without actually putting it on another
thread. Personally, I think this would be overkill (if you really wanted
this approach, just do it via threading), but it's certainly a possibility.
In any case, there's just no reason to call Invalidate from within the
OnPaint method. If you need the absolute maximum rate with respect to
redrawing, just call the drawing code as often as you can without even
bothering to use Invalidate. Otherwise, use the Windows redraw mechanism as
it was designed, by calling Invalidate only to invalidate areas that you
know have actually changed, when those areas have actually changed (in
response to user input, game mechanics, and the like). And don't forget,
depending on what you're actually drawing, you may find that invalidating
the entire window is actually *slower* than invalidating smaller areas as
necessary.
So, to sum up:
* Don't do game AI when you ought to be drawing.
* Don't draw stuff when you ought to be doing game AI.
* Be smarter/more efficient about monster AI. Don't calculate things
that you will never use.
* Don't call Invalidate from within the OnPaint method.
* If you have to repaint the window as frequently as possible, just
repaint it every chance you get. Don't waste time messing around with the
Invalidate/paint-event cycle.
* Keep in mind that depending on your game, you may not have to repaint
the entire window at all. If that's the case, then *do* use the
Invalidate/paint-event cycle and don't invalidate the entire window unless
you know for sure the entire window has to be redrawn.
Argh. Your response only strengthens my resolve to re-think how my game
should be structured :)
Thank you again for you response!
You're welcome. Hope the above is also helpful.
Pete