2024-11-21, 16:11 *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
Pages: [1]
  Print  
Author Topic: Fixing the Q3 server bug (A Tutorial)  (Read 17873 times)
0 Members and 1 Guest are viewing this topic.
Phoenix
Bird of Fire
 

Team Member
Elite (7.5k+)
*********
Posts: 8814

WWW
« on: 2005-09-14, 20:43 »

If a server is left running for close to 24 hours, certain undesired behaviors begin to manifest - shaders get stuck, bobbing items start to get choppy, explosion graphics go out of sync with their timers, etc.  The problem is caused by a variable the server depends on to increment time.  I will explain how to fix this problem.  This little tutorial assumes you have the full Q3 engine source.  Line numbers will be approximations, and I'll provide the surrounding contextual code so you should be able to find it without trouble, and I'll show changed code remarked out instead of removed.

The problem is in serverStatic_t, the time value (referred to svs.time) is used for all server-to-client transactions.  This not only includes timestamps on packets, but also snapshots.  Since it always increments, this is bad because animated shaders, etc, need to multiply the provided timer by some value in their functions.  This causes the resultant value to exceed the maximum positive value for a signed 32 bit integer, which results in a negative value, causing animapped shaders to freeze on frame 0, and bobbing items to get jerky.  What we need to do is create a new timer value that does not constantly increment, but rather resets every time the level changes so our integers stay sane.

First up, we need to define our timer.  While serverStatic_t does not clear, server_t is reset every level change, so we want to use that.  Go into server/server.h and look at Ln 81.  You should see this:

Code:
	playerState_t	*gameClients;
int    gameClientSize;  // will be > sizeof(playerState_t) due to game private data

int    restartTime;
} server_t;

Add a line to make it look like this:

Code:
	playerState_t	*gameClients;
int    gameClientSize;  // will be > sizeof(playerState_t) due to game private data

int    restartTime;
// Phoenix - bugfix
int    time;
} server_t;

Henceforth this will be referred to as sv.time.  Now we're going to replace all instances of svs.time that can affect gameplay, but we don't want to mess with packet timestamps or connection-specific code.

Open up server/sv_ccmds.c  Head down to Ln 276.  You should be in a function called SV_MapRestart_f, and it should look something like this:

Code:
	SV_RestartGameProgs();

// run a few frames to allow everything to settle
for ( i = 0;i < 3; i++ ) {
  VM_Call( gvm, GAME_RUN_FRAME, svs.time );
  svs.time += 100;

}

We want to add our sv.time integer, but we want to increment it alongside svs.time, not replace the svs.time value.  However, we want to pass sv.time - not svs.time - into the vm.  Make your code look like this:

Code:
	SV_RestartGameProgs();

// run a few frames to allow everything to settle
for ( i = 0;i < 3; i++ ) {
// Phoenix - bugfix
  VM_Call( gvm, GAME_RUN_FRAME, sv.time );
  sv.time += 100;
  //VM_Call( gvm, GAME_RUN_FRAME, svs.time );
  svs.time += 100;

}

In the same function, around Ln 321 at the end of the function, look for this:

Code:
	// run another frame to allow things to look at all the players
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
svs.time += 100;
}

Add these lines so it looks like this:

Code:
// run another frame to allow things to look at all the players
// Phoenix - bugfix
VM_Call( gvm, GAME_RUN_FRAME, sv.time );
sv.time += 100;
//VM_Call( gvm, GAME_RUN_FRAME, svs.time );
svs.time += 100;
}

Now we want to look at server/sv_game.c, Head to Ln 909.  You should see the following, at the end of SV_InitGameVM:

Code:
VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart );
}

Again we want to pass sv.time, not svs.time, into the vm:

Code:
// Phoenix - bugfix
VM_Call( gvm, GAME_INIT, sv.time, Com_Milliseconds(), restart );
//VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart );
}

Next, open up server/sv_init.c, and look for Ln 442.  Look at SV_SpawnServer, and find this segment:

Code:
	// run a few frames to allow everything to settle
for ( i = 0;i < 3; i++ ) {
  VM_Call( gvm, GAME_RUN_FRAME, svs.time );
  SV_BotFrame( svs.time );
  svs.time += 100;
}

We're going to use sv.time for both the vm call and the botframe run.  Change it to look like this:

Code:
	// run a few frames to allow everything to settle
for ( i = 0;i < 3; i++ ) {
// Phoenix - bugfix
  VM_Call( gvm, GAME_RUN_FRAME, sv.time );
  SV_BotFrame( sv.time );
  sv.time += 100;
  //VM_Call( gvm, GAME_RUN_FRAME, svs.time );
  //SV_BotFrame( svs.time );
  svs.time += 100;
}

Again, in the same function, head to Ln 502 and locate this:

Code:
	// run another frame to allow things to look at all the players
VM_Call( gvm, GAME_RUN_FRAME, svs.time );
SV_BotFrame( svs.time );
svs.time += 100;

Give it the same treatment as follows:

Code:
	// run another frame to allow things to look at all the players
// Phoenix - bugfix
VM_Call( gvm, GAME_RUN_FRAME, sv.time );
SV_BotFrame( sv.time );
sv.time += 100;
// VM_Call( gvm, GAME_RUN_FRAME, svs.time );
// SV_BotFrame( svs.time );
svs.time += 100;

Let's head over to server/sv_main.c and jump to Ln 779.  You should be in SV_Frame.

Code:
	sv.timeResidual += msec;

if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );

This needs to be changed to this:

Code:
	sv.timeResidual += msec;

// Phoenix - bugfix?
if (!com_dedicated->integer) SV_BotFrame( sv.time + sv.timeResidual );
//if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual );

Now in the same function, locate this portion:

Code:
	// update ping based on the all received frames
SV_CalcPings();

if (com_dedicated->integer) SV_BotFrame( svs.time );

// run the game simulation in chunks
while ( sv.timeResidual >= frameMsec ) {
  sv.timeResidual -= frameMsec;
  svs.time += frameMsec;

  // let everything in the world think and move
  VM_Call( gvm, GAME_RUN_FRAME, svs.time );
}

We need to pass sv.time into SV_BotFrame, increment sv.time by frameMsec, and pass it into the VM.  Here's how it should look when we're done:

   
Code:
// update ping based on the all received frames
SV_CalcPings();

// Phoenix - bugfix?
if (com_dedicated->integer) SV_BotFrame( sv.time );
//if (com_dedicated->integer) SV_BotFrame( svs.time );

// run the game simulation in chunks
while ( sv.timeResidual >= frameMsec ) {
  sv.timeResidual -= frameMsec;
  svs.time += frameMsec;
// Phoenix - bugfix?
  sv.time += frameMsec;

  // let everything in the world think and move
// Phoenix - bugfix?
  VM_Call( gvm, GAME_RUN_FRAME, sv.time );
  //VM_Call( gvm, GAME_RUN_FRAME, svs.time );
}

Our last change is in server/sv_snapshot.c, on Ln 164.  Look at SV_WriteSnapshotToClient and find this:

Code:
	MSG_WriteLong (msg, sv.time);

That's not good, since this is what creates cg.time on the client.  We need to do this instead:

Code:
// Phoenix - bugfix
MSG_WriteLong (msg, sv.time);
//MSG_WriteLong (msg, svs.time);

This way cg.time only increments from level start.  That's fine since the client's vm is shut down every level change anyway.

These are the ONLY changes that should be done in reference to svs.time.  Other references (like client->nextSnapshotTime) have to do with the client's connection to the server, and should never run backwards or be cleared or bad things might happen (like booting the client off the server).

Note that this fix is 100% server-side.  There are no changes required to the client, nor to the .qvm's or renderer.  I've already tested this with a server that's been running over 60 hours and I've had no detrimental effects.  Gameplay is smooth, no shaders are malfunctioning, level changes work fine and bots remain connected when they should and behave properly.  Demos also record properly.

Limitations
If a map has been running on a server for over 24 hours, sv.time will cause the same problem behavior.  This is unavoidable.  To correct this, just change maps.  Most Q3 servers would be sitting at a scoreboard at this point anyway, so unless someone's having a 48 hour frag-a-thon on the same map it will be self-correcting after a level change.

That's it.  If you use this bugfix credit is always nice, but I won't insist on it.  I just want this problem GONE from Q3 servers, so by all means implement this fix if you can![/color]
« Last Edit: 2007-07-10, 15:35 by Tabun » Logged


I fly into the night, on wings of fire burning bright...
Orbital-S2D
 

Shambler
*****
Posts: 100

RnR Boss

« Reply #1 on: 2014-08-22, 02:17 »

when did they add this to ioquake3?
Logged

GET THAT GUY GET THAT GUY, PEW PEW PEW!
Phoenix
Bird of Fire
 

Team Member
Elite (7.5k+)
*********
Posts: 8814

WWW
« Reply #2 on: 2014-08-23, 02:04 »

It's been in there probably since I posted this in 2005.  I had posted the same source code on quakesrc.org, and ioquake3 picked it up from there.  I don't think I was ever credited for the code by the ioquake3 team, but that's OK.  I posted it public so that anyone could use it.

Quakesrc.org is dead now, sadly.  Some kind of server crash nuked all the forum content a few years back and that was that.  The forum content can't be accessed on the Internet Archive either.  There was a LOT of good coding information on that site.  Bad loss when it went down.
Logged


I fly into the night, on wings of fire burning bright...
MadTux
 

Ogre
**
Posts: 54

*Personal* text? No way!

« Reply #3 on: 2014-08-23, 07:33 »

Somehow, code is always the most painful thing to lose. You put so much time in it, and so much effort per time. I once accidentally *deleted* a file I had just spent hours on  Slipgate - Shouting and that was bad enough. Anyway, good thing that this was found and used for ioquake3. And posted here.
Logged

Oooh look -- a signature!
Phoenix
Bird of Fire
 

Team Member
Elite (7.5k+)
*********
Posts: 8814

WWW
« Reply #4 on: 2014-08-24, 01:01 »

I maintain meticulous backups of everything I do, so I've not lost any code that I've written myself, or any media.  I had a system crash once that tore up my hard drive, but I only lost maybe 3 hours worth of model work from that.
Logged


I fly into the night, on wings of fire burning bright...
MadTux
 

Ogre
**
Posts: 54

*Personal* text? No way!

« Reply #5 on: 2014-08-24, 10:18 »

I'm a lot better with backups, now that my 500G backup HDD broke, that contained my old Ubuntu system with files I still needed. And a backup of a USB stick I lost. Well, now I back up to two hard disks regularly. Anyway, this is getting off topic ...
* MadTux has been hanging around too much on the off-topic thread Slipgate - Wink
Logged

Oooh look -- a signature!
Pages: [1]
  Print  
 
Jump to: