Jump to content
Sign in to follow this  
sbsmac

Release: In-game FPS counter

Recommended Posts

The discussion of infinite loops got me tinkering. The following script ended up being a bit of a test-bed for a number of techniques including 'auto-load' of an action and associated code from a single file.

Just add this to your players initialization field:-

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">s=[] execVM "fps.sqf"

The should then find that your player has a new action called "Toggle FPS" which turns the counter on and off. I've tested this against FRAPS and it seems to give consistent results.

The original file without the crappy CODE reformatting and _much_ easier to read is here.

Top tip - it looks an awful lot nicer with syntax highlighting turned on. Treating it as a 'C' file seems to give the best results in emacs.

If you can't see the link above, the content (with unreadably mangled format) is below...

FILE: fps.sqf

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">

// In-game FPS (Frames Per Second) counter

//

// Version 1.0, 28 dec 06

// Created by sbsmac

//

// To use this script add the following to the player initialization field:-

//

// s=[] execVM "fps.sqf"

//

// Note that 's' is a dummy variable to keep the parser happy.

//

// You should then find that your action menu contains 'Toggle FPS'.

// Select this option to toggle fps counting on and off

if (count _this ==0 ) then {

//This is the code that is run at initialisation. It sets up

//the required variables and functions and adds the 'toggle' action.

fpsState = "stopped"; //global variable to control state of counter

//the counter function- this runs continuously until halted

startFps = {

fpsState="running";

//build in a little bit of smoothing by averaging the last n frames

_numFrames = 10;

_frameIndex=0;

_frameTimes=[];

//clear out the buffer (needs to be one larger than num frames)

for "_i" from 0 to (_numFrames) do {_frameTimes set [_i,0];};

//use a double loop here because of the ArmA limitation that

//loops terminate after 10000 iterations

while {fpsState == "running"} do {

while {fpsState == "running"} do {

_frameTimes set [_frameIndex,time];

_frameIndex = (_frameIndex +1) % (_numFrames+1);

hint format ["fps %1 (over last %2 frames)",

round((_numframes)/(time-(_frameTimes select _frameIndex))),

_numFrames];

//delay a little bit - this forces a wait until

//the next frame

Sleep 0.001;

};

};

hint ""; //clear the hint box

fpsState = "stopped";

};

//function to request that the counter be stopped

stopFps = {

fpsState = "stopping";

};

//having set up the functions, add an action to control them

//the exact string passed to the script is actually irrelevant

player addAction ["Toggle FPS","fps.sqf",["toggle"]];

} else {

//We've been called from the action menu so toggle fps counting.

//fpsState has 3 states to guard against the (very narrow) race

//condition where a user can hit the toggle action very quickly

//twice in succession. This could cause two instances of the counter

//to run if a simple boolean state was used

switch (fpsState) do {

case "running" : { call stopFps;};

case "stopped" : { call startFps;};

case "stopping" : {}; //do nothing

};

};

Please, no complaints that the double while-loop should be a 'waitUntil' wink_o.gif

*Edit* reformatted CODE section following Sickboy's 'no-tabs' suggestion

Share this post


Link to post
Share on other sites

Very Nice script!

But isn't it lighter to have a program like fraps running, as it reads the frames from directX instead of a loop in the engine?

ergo, it seems best to have as low as possible scripts/loops running within the engine?

Share this post


Link to post
Share on other sites
Quote[/b] ]But isn't it lighter to have a program like fraps running, as it reads the frames from directX instead of a loop in the engine?

As I said, I developed this mainly to test out different techniques - I particularly like the fact that it all runs from a single file. Whether it's actually useful to anyone else is debatable biggrin_o.gif

To actually address your question, I've got nothing against FRAPS but I think it's unlikely that the overhead from a small script running in the game engine will be higher than that of an external process. Of course, having a second core to run FRAPS on or discovering that the ArmA parser was extraordinarily slow might change my opinion. I doubt the effect in either case is noticeable.

I think the more practical use of a script like this would be to is monitor fps in-game so that other scripts could dynamically adjust their own processing load. Eg, if the player has a high number of fps a ballistics simulation script could afford to use a finer step size.

Share this post


Link to post
Share on other sites
...

Sweet! Very well thought of. I love it in 1 script aswell, especially with the functions that disable/enable it again etc, as said before, nice job!

And ur right, fraps indeed probably uses more resources than a loop ingame biggrin_o.gif

One thing though, if while loops exceed or hit 10.000 and they get terminated, don't they terminate the whole script, and not just only that while? I thought it would kill the whole script and as such ppl where using a counter < 9999.

btw, if you wish to make it more readable in [ code ] stuff, try replacing tabs by 4-8 spaces or so, not in ur script itself of coarse but just in what you post smile_o.gif

Share this post


Link to post
Share on other sites
Quote[/b] ]One thing though, if while loops exceed or hit 10.000 and they get terminated, don't they terminate the whole script, and not just only that while?

Fair question, but no, it seems that the iteration count for a loop is a property of that loop and is reset upon entry to the loop. I tested this by adding this code just before the call to Sleep.

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">

_cnt = 0;

while {fpsState == "running"} do {_cnt=_cnt+1;};

hint format ["cnt %1 %2",_cnt,time];

Obviously this loop will hit 10000 pretty quickly and would cause the entire counter to exit if the iteration count was a property of the script. As it happens, it keeps on running fine. The hint line shows _cnt always at 10000 with the time ticking along as expected.

BTW, you can see why it is important for loops to relinquish control and be 'cooperative;' by considering that adding this inner loop reduces my average fps from 28 to 11 !

smile_o.gif

*Edit*

Quote[/b] ]btw, if you wish to make it more readable

Good tip - thanks.

smile_o.gif

Share this post


Link to post
Share on other sites

It's nice.

Suggestions:

Formatting suggestions: Instead of going step by step, I figured I'd just do a quick reformat. You're sharp, so you'll probably pick up on what all I did pretty dang quick. These are only suggestions though, so take it however you wish.

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">// In-game FPS (Frames Per Second) counter

//

// Version 1.0, 28 dec 06

// Created by sbsmac

//

// To use this script add the following to the player initialization field:-

//

// s=[] execVM "fps.sqf"

//

// Note that 's' is a dummy variable to keep the parser happy.

//

// You should then find that your action menu contains 'Toggle FPS'.

// Select this option to toggle fps counting on and off

if (count _this ==0 ) then

{

//This is the code that is run at initialisation. It sets up

//the required variables and functions and adds the 'toggle' action.

fpsState = "stopped"; //global variable to control state of counter

//the counter function- this runs continuously until halted

startFps =

{

fpsState = "running";

//build in a little bit of smoothing by averaging the last n frames

_numFrames = 10;

_frameIndex = 0;

_frameTimes = [];

//clear out the buffer (needs to be one larger than num frames)

for "_i" from 0 to (_numFrames) do {_frameTimes set [_i,0];};

//use a double loop here because of the ArmA limitation that

//loops terminate after 10000 iterations

while {fpsState == "running"} do

{

while {fpsState == "running"} do

{

_frameTimes set [_frameIndex,time];

_frameIndex = (_frameIndex +1) % (_numFrames+1);

hint format

[

"fps %1 (over last %2 frames)",

round((_numframes)/(time-(_frameTimes select _frameIndex))),

_numFrames

];

//delay a little bit - this forces a wait until

//the next frame

Sleep 0.001;

};

};

hint ""; //clear the hint box

fpsState = "stopped";

};

//function to request that the counter be stopped

stopFps =

{

fpsState = "stopping";

};

//having set up the functions, add an action to control them

//the exact string passed to the script is actually irrelevant

player addAction ["Toggle FPS","fps.sqf",["toggle"]];

}

else

{

//We've been called from the action menu so toggle fps counting.

//fsp_state has 3 states to guard against the (very narrow) race

//condition where a user can hit the toggle action very quickly

//twice in succession. This could cause two instances of the counter

//to run if a simple boolean state was used

switch (fpsState) do

{

case "running" : {call stopFps;};

case "stopped" : {call startFps;};

case default : {}; //do nothing

};

};

Case structures:

Try

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">

switch (fpsState) do

{

case "running" : {call stopFps;};

case "stopped" : {call startFps;};

case default : {}; //do nothing

};

in place of

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">

switch (fpsState) do

{

case "running" : { call stopFps;};

case "stopped" : { call startFps;};

case "stopping" : {}; //do nothing

};

The default accounts for every other contigency, other than running and stopped. I suppose it doesn't really matter in this case though, since it's do nothing anyways. It's just a form thing I guess. Generally, in programming, you'll always use the default in a case structure, even if it's only to throw an error.

Nested loops:

That loop will run out after 27.7777 hours. I'd suggest you nest it one deeper. That'll make it 277,777.777 hours instead. Although in 99.999% of cases, that wouldn't be an issue, you may end up with an insane mission designer using it, and an even more insane player playing it.

I'm sure there's a way to optimize that loop a little too, although I'd prefer to wait till I've slept to make any suggestions there.

Edit: something was wrong there, so I removed it.

Share this post


Link to post
Share on other sites

Colonel, thanks for the comments. A little about me - I've been writing code for over 20 years including embedded kernels, pre-emptive schedulers and real-time device-drivers in parallel-processing environments so you'll forgive me if I have my own opinion about how things should look tounge2.gif

In the switch statement I prefer explicit case checks because I prefer to know when something has gone wrong (undefined state) earlier rather than later. To be really correct the case statement should actually look as it currently does but with a default clause to throw an error (so we're agreed there) but this is only a script... ;-)

By my calculation the counter will run for 100 million iterations (10000 x 10000). Since it runs once per frame it will run for 1 million seconds (around 13 days) even at 100fps and if you have a machine that will run ArmA at 100fps please let me know the specs - I want to get one ! biggrin_o.gif

Share this post


Link to post
Share on other sites
Quote[/b] ]I'm sure there's a way to optimize that loop a little too

Several actually:-

* Using a power-of-two array size is the obvious one (masking is generally much cheaper than the modulo operation).

* Only perform the hint if the number of fps has changed since the last hint (or else a certain time has elapsed). (Display and format operations are likely to be relatively expensive.)

* Cache the time instead of re-reading it when recalculating fps. (Calling a function is likely more expensive than reading a variable.)

* Change the number of frames to a power of two, allowing the division to be replaced by a logical shift right by a fixed number of bits (shifting is cheaper than division). *Edit* Didn't explain this very well. Some architectures have a cheap 'inverse' instruction for 1/x divisions. If numFrames is a power of two you can then shift the result rather than multiply it. Unlikely to apply to ArmA though so I'm reaching a bit with this one wink_o.gif

But... the inner loop is executed once per frame. After seeing the interpreter run 10K iterations of a simple loop in less than measurable time, it seems pointless to optimise the code at th expense of readability or correctness.

Note there is one potential area of failure for the script. I've made the assumption that the game engine runs each script once per frame. Therefore any delay in a script will cause it to be descheduled and to wait until the next frame. It's just about possible that the game-engine might run this script, deschedule it, then run another that runs for > 0.001 seconds. It might then check back for waiting scripts and find this one ready to run. I think it's unlikely though.

smile_o.gif

Share this post


Link to post
Share on other sites
Colonel, thanks for the comments. A little about me - I've been writing code for over 20 years including embedded kernels, pre-emptive schedulers and real-time device-drivers in parallel-processing environments so you'll forgive me if I have my own opinion about how things should look tounge2.gif

Cool, I'm a programmer too, and did recognize it as a standard formatting scheme. I just always suggest the scheme presented above, whenever the oppurtunity presents. I think I really just like how the braces are more symetrical in my format wink_o.gif.

In the switch statement I prefer explicit case checks because I prefer to know when something has gone wrong (undefined state) earlier rather than later. To be really correct the case statement should actually look as it currently does but with a default clause to throw an error (so we're agreed there) but this is only a script... ;-)

You could always try the throw command, but yeah, it's not a big deal really.

By my calculation the counter will run for 100 million iterations (10000 x 10000). Since it runs once per frame it will run for 1 million seconds (around 13 days) even at 100fps and if you have a machine that will run ArmA at 100fps please let me know the specs - I want to get one ! biggrin_o.gif

Well, you're right. Pretty much a bonehead mistake. Although I do get 300 fps in max zoom in on the map in some areas, that's still nearly 4 days straight. So yeah, it's cool too probably.

As far as optimizing the loop, I was thinking more along the lines of something else. More towards taking advantage of the way the game executes.

Like I said though, I'm tired. I've been up a while, and that's why I made that bone head mistake above.

Share this post


Link to post
Share on other sites
Quote[/b] ]Like I said though, I'm tired. I've been up a while, and that's why I made that bone head mistake above.

No sweat - we've all been there. I've lost count of the number of times I've woken up in the early hours thinking 'damn - 'why the hell did I write _that_?' smile_o.gif

Share this post


Link to post
Share on other sites

i just woke up, so my mind isn't very clear, but i got this feeling and i had to write it somewhere, sorry smile_o.gif

What if the script (ok, not exactly this one, but altered one to be run on ded) is run on a dedicated server?

...not for the reason of measuring it's FPS, but maybe for some other reasons like checking the workload?

What is the average FPS for an average ded server?

...i am sorry if this question belongs to somewhere else.

Share this post


Link to post
Share on other sites

Soemthing usefull that this script could be used for is graphical settings negotiation.

Anyone who was around in the ofp days will remember where there where multiplayer scripts to negotiate view distance settings.

This script could probably be able to do something like that pretty succesfully.

Share this post


Link to post
Share on other sites
Quote[/b] ]What if the script (ok, not exactly this one, but altered one to be run on ded) is run on a dedicated server?

Interesting idea. I guess in that case the 'frame-rate' would probably tell you how frequently the server engine was looping round its control loop. Presumably that looks something like this:-

while (1)

{

get updates from clients

send updates to clients

run core game engine

run server triggers, scripts etc

}

I'd be very surprised if that loop wasn't throttled (so as to avoid over-frequent network traffic) so most of the time I'd expect the 'framerate' to be constant. However, any major dip would indicate the server being overloaded. One might then take appropriate action to refuse to allow new players to join, reduce the view-distance (as ColonelSanders suggests) etc

Mind you this is all speculation - anyone want to try it ?

biggrin_o.gif

Share this post


Link to post
Share on other sites

Looking at this closely now that I've slept:

First, while it's not a set in stone rule, it's really best to register a unique tag and use it in front of all global variables and scripts. This is a community initiative to prevent conflicts between multiple scripts by different authors. Suppose a mission maker uses two scripts with a variable called SquadAStatus. It would break both scripts... For more information on this, check here for more info.

Second, I noticed you accidently commented out the hint lines on the version you posted in your top message (not the one that's offsite).

Third, I was playing with the startFPS block, optimizing the loop, like I thought could be done yesterday, and I came up with this:

<table border="0" align="center" width="95%" cellpadding="0" cellspacing="0"><tr><td>Code Sample </td></tr><tr><td id="CODE">

startFps =

{

fpsState = "running";

_precision = 15;

_startTime = time;

_nowTime = time;

//use a double loop here because of the ArmA limitation that

//loops terminate after 10000 iterations

while {fpsState == "running"} do

{

while {fpsState == "running"} do

{

if (_nowTime != _startTime) then

{

hint format ["fps %1 (over last %2 frames)", round(_precision / (_nowTime - _startTime)), _precision];

};

_startTime = time;

_i = 0;

waitUntil {_i = _i + 1; _i > _precision};

_nowTime = time;

};

};

hint ""; //clear the hint box

fpsState = "stopped";

};

This is just the startFps routine, it's identical otherwise. It executes fewer instructions per second like this.

Assuming 30 FPS, your's executes roughly 13*30=390 commands per second. The one above executes roughly 3*30+10*2=110 commands per second. I don't know how many commands are in the bis functions, so I counted them as one. Although if you tallied those up too, mine uses less of those too, so that would help my case a bit.

Higher FPS will mean higher differences too. Supposing you have "Das Uber Machine" that can run 200 FPS in the forest biggrin_o.gif:

Original: 13*200=2600

Suggested: 3*200+10*100=1600

Not that it really matters much anyways. Well, in processor intense situations, maybe. Like if there's a huge firefight going on. Keeping in mind that the thing arma uses the most of after vid power is processor, it may on large missions.

This one also uses less variables, although again, that really probably doesn't matter much either, since new systems are throwing between 1 and 4 gigs or ram at the game on average...

Share this post


Link to post
Share on other sites
Quote[/b] ]First, while it's not a set in stone rule, it's really best to register a unique tag and use it in front of all global variables and scripts.

That's a good idea - thanks for the link.

Quote[/b] ]Second, I noticed you accidently commented out the hint lines on the version you posted in your top message (not the one that's offsite).

Oops - ta. I've got the Perforce RCS installed on my PC but haven't yet started using it for scripts. That's a strong hint I should do so biggrin_o.gif

Quote[/b] ]Third, I was playing with the startFPS block, optimizing the loop,

A few observations:-

* I hadn't realised that waitUntil would have that behaviour. Have you tried it ? Unfortunately I'm away from my Arma machine so can't do. If it does then I would tend to agree that using a simple waitUntil should be the canonical version of 'Sleep (0)' in other environments.

* You've subtly altered the calculation of the fps number. I was quite deliberate about calculating a rolling average and updating it every frame for viewer satisfaction (hence the circular buffer) but your version calculates a step-wise average and updates it every 15 frames. Not that it really makes much difference and I agree that the newer version is an improvement for the kinds of applications we talked about earlier in the post. smile_o.gif

* You can drop the conditional round the hint by forcing starttime to be nowtime-1 or just a small number.

* Better to soft-code the '15' constant. I used a variable for this but a define would have been better if I could have been bothered to try to get it work!

* I generally prefer stronger conditions so '==16' would be preferred as '>15'. No different in practice but saves a bit of head-scratching when things go wrong and you start questioning whether _i could somehow have been corrupted to the point that it skipped the value of 16 altogether !

All in all I think this version is definitely better for load-monitoring and rather usefully points out the value of code-reviews smile_o.gif

Share this post


Link to post
Share on other sites

A few observations:-

* I hadn't realised that waitUntil would have that behaviour. Have you tried it ? Unfortunately I'm away from my Arma machine so can't do. If it does then I would tend to agree that using a simple waitUntil should be the canonical version of 'Sleep (0)' in other environments.

Yes, the script runs and produces correct results with the modifications presented.

* You've subtly altered the calculation of the fps number. I was quite deliberate about calculating a rolling average and updating it every frame for viewer satisfaction (hence the circular buffer) but your version calculates a step-wise average and updates it every 15 frames. Not that it really makes much difference and I agree that the newer version is an improvement for the kinds of applications we talked about earlier in the post. smile_o.gif

It produces an average of the frame rate over the past 15 frames as is, since it figures out the time it took to render 15 frames. The actual behaviour from a user's point of view is nearly identical. Only with very intimate knowledge of both formulas, can you tell the difference at all, even then, it's slight.

* You can drop the conditional round the hint by forcing starttime to be nowtime-1 or just a small number.

Unfourtunatly, no you can't. Otherwise, it errors if you tab out, or escape to the menu. Doing it like this, takes care of both the first loop, and the error. This is the simplest and most efficient method of doing it that I found in just the short period of time I messed with it.

* Better to soft-code the '15' constant. I used a variable for this but a define would have been better if I could have been bothered to try to get it work!

* I generally prefer stronger conditions so '==16' would be preferred as '>15'. No different in practice but saves a bit of head-scratching when things go wrong and you start questioning whether _i could somehow have been corrupted to the point that it skipped the value of 16 altogether !

You are absolutely correct on both counts. I just did that because it was the most expedient way of showing you the methodology.

The version I posted above is now edited to do the soft coded constant and use the better condition.

Edit:

Actually, it doesn't error when you tab out without the conditional (at least in windowed mode, it still may if you tab out in full screen mode), but it *does* error if you go to the ingame menu. I imagine this is because it's still rendering, but no longer processing commands (they're on hold). I'm not really sure as to the *exact* cause though, and it would take a bunch of eperimentation to figure it out. So, I treated the symptom, instead of the illness.

Share this post


Link to post
Share on other sites

Any picture of it? I don't have Armed Assault yet, so I can't try it out confused_o.gif

Share this post


Link to post
Share on other sites
Quote[/b] ]Any picture of it?

biggrin_o.gif It's not exactly very exciting but if you really want to look...

fps.JPG

*Edit* Note that the yellow number in the top right is the frame-rate as measured by FRAPS. The number in the hint bar at the top left is what the script produces.

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×