Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Sign in to follow this  
Quasar

TryRunTics is not aware of screenwipe delay

Recommended Posts

It seems like every time there is a screenwipe, TryRunTics decides that it is really far behind in running the game and decides to run a few dozen gametics all at once.

This normally isn't noticeable except when you transition from playing a map back to the demo sequence (for example, F7 during a demo to drop back to the titlescreen) - then suddenly you notice that the titlescreen lasts about a half or 2/3rds the amount of time that it would normally.

This bug is ancient, going all the way back to the foundations of the engine, and in fact I first noticed it while working on Chocolate Strife.

I think ZDoom fixes this; my question is how precisely its fix works and if it can be adapted into Eternity.

Share this post


Link to post

I've got zero experience with the software renderer but I would guess the reason this happens is due to rendering multiple frames while the game timer is still progressing. Naturally a wipe requires quite a few frames so if your game timer is still counting up during the process you're going to get a large delta come next simulation time. Why is the playsim thinking during a screenwipe in any case?

Is your game loop fully decoupled from rendering, or does it block waiting on completion of a "sharp" tic (i.e., 1/35)? There should be both upper and lower bounds on the maximum tics to simulate per cycle.

Here's a good article on the general subject of decoupling the game loop: http://gafferongames.com/game-physics/fix-your-timestep/

Share this post


Link to post

Does this bug work in vanilla or does it just happen on modern systems? I blasted myself against the walls in MAP32 for a few minutes to produce the wipe screen tons of times in succession yet when I ended the game the title screen seemed to last about normal.

Share this post


Link to post

Hmm... I had an experience or two with that, when trying to get the wiper to work with the rest of the stuff. I always thought that the way it worked was letting at least one frame of the game run (so that there is something to transition to) and then trying to run the wipe in a fixed amount of time, if possible, or skip ahead the wipe itself if it's taking too long.

I didn't really take notice if the tics used in the wiper "count" towards gameplay too, or if it sort of "freezes" the actual game until the wipe is over, also because the wiper "traps" the game in a loop of its own and uses just I_GetTime()...hmm it's worth sorting out.

The solution would be resetting I_GetTime() to whatever value it was before the wiper, or using a separate timer for the wiper altogether, or monitor the "just wiped" condition and hack TryRunTics itself.

Share this post


Link to post
myk said:

Does this bug work in vanilla or does it just happen on modern systems? I blasted myself against the walls in MAP32 for a few minutes to produce the wipe screen tons of times in succession yet when I ended the game the title screen seemed to last about normal.

Just a misunderstanding, as the amount of lag-behind does not accumulate between multiple wipes. The only way that would happen is if the wipes ran back-to-back without interruption of even a single main loop iteration, which never happens due to how the engine is programmed (it must see wipegamestate change values before it'll wipe again).

The amount of lag is always small, and is slightly variable, since the asynchronous timer will increment by a slighly different amount every time, depending on such uncontrollable factors as precision drift, process switching, cache misses, disk IO, etc.

Maes said:

The solution would be resetting I_GetTime() to whatever value it was before the wiper, or using a separate timer for the wiper altogether, or monitor the "just wiped" condition and hack TryRunTics itself.

Right and I think that is what ZDoom might be doing with its I_FreezeTimer function, but, as with a lot of stuff in ZDoom, the comments only mention the what and not the why, and it's not exactly self-explanatory code.

Share this post


Link to post

As I said; don't progress game time during a screenwipe. You should be able to still perform a game loop iteration if necessary without having the playsim think. Obviously this has implications for any other asynchronous threads like audio (but then thats easily dealt with by deferring music playback to begin on the first tic after the wipe).

Share this post


Link to post

The standard way that I_GetTime() works in most ports is as an "absolute" time from an arbitrary time reference point (usually when the whole source port is first fired up and important stuff is initialized).

Whenever it's queried, it simply does a "currentTime() minus storedTime" calculation and returns the equivalent time in tics.

"Freezing" it would require specifically altering the value of storedTime somehow so that the next time I_GetTime() gets called it starts counting tics from zero all over again or just resumes the tic count from where it was "freezed", as you can't really stop currentTime() from progressing (it's a system specific function).

Share this post


Link to post

Perhaps I'm simply too used to Doomsday's architecture which already has these mechanisms in place...

Yes you would definitely need to either freeze or run another timer in parallel. You don't need to freeze "real time", its the game time you need to freeze. If the latter is defined entirely as the offset between two real time samples then essentially you don't have a decoupled game loop at all, you have a glorified spin lock.

Share this post


Link to post
DaniJ said:

If the latter is defined entirely as the offset between two real time samples then essentially you don't have a decoupled game loop at all, you have a glorified spin lock.


This very aspect caused a problem with Mocha Doom and some older Athlon Dual Cores, when using the high-precision System.nanoTime() function as the "currentTime()" function: its output was core dependant and could even end up locking the wiper in-place or give fucked up timing problems in other programs, too.

The solution in that case was to either download a patch from AMD that definitively fixed the problem system-wide, or try and come up with some fucked up heuristics that tried to understand whether you were running in an "unstable timer" environment.

Interestingly, using the standard System.getTimeMillis() function (which on Windows maps directly to Win32's GetTickCount, if I recall correctly) caused no such problems, since it was not core-specific, but then you'd be limited to the fugly Win32 15-16 ms timer resolution.

Share this post


Link to post

MBF had a fix for pausing during the demo in G_Ticker, by creating "basetic":

if (paused && demoplayback)
{
  // For revenant tracers and RNG -- we must maintain sync
  basetic++;
}
I think this could be adapted in a way to also allow you to fix TryRunTics without putting a multi-level demo out-of-sync. You could fudge around with gametic, similarly to what basetic does, possibly by creating "realgametic", but you might have an awful lot of testing to do. You'd have to make the simulation think nothing has changed, and at the same time make TryRunTics think that a wipe didn't occur.

Maybe look at it from another angle: What happens if you just disable the wipe temporarily? Does a multi-level demo actually go out of sync?

An interesting find!

Share this post


Link to post

No, this isn't like the basetic problem at all.

This problem stems from I_GetTime and TryRunTics thinking that it needs to run a lot of gametics in order to catch up with real time - the same as if the game were running slowly due to having too many monsters - it would run a lot of tics and then the game would only render as often as it could manage between them, which means you are experiencing an FPS drop.

This is not about the gametic counting; it's about the measurement of real time.

Share this post


Link to post
Quasar said:

No, this isn't like the basetic problem at all.
This is not about the gametic counting; it's about the measurement of real time.

Well, you could take the wipe loop out of D_Display, and inhibit processing events while the wipe is active. That would keep the tics rolling, but, you'd have to disable this mechanism for old demo sync.

Essentially, those accumulated tics *should be occurring* during the wipe, but the wipe loop temporarily hijacks that ability. So, the trick is to either:

A. Fudge the timing by throwing away those tics.
B. Allow the tics to run while the wipe occurs.

Edit:
I don't think I notice this problem much, becuase I have a setting that prevents new demos from playing for a couple of seconds while menu/function keys are being pressed. I always found it annoying to be messing with options, and have a 2 second delay while waiting for the wipe to occur.

Share this post


Link to post

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  
×