1
\$\begingroup\$

I know nothing of game development/this site, so I apologise if this is completely foobar.

Today I experimented with building a small game loop for a network game (think MW3, CSGO etc). I was wondering why they do not build in automatic rate adjustment based on server performance? Would it affect the client that much if the client knew this frame is based on this tickrate? Has anyone attempted this before?

Here is what my noobish C++ brain came up with earlier. It will improve the tickrate if it has been stable for x ticks. If it "lags", the tickrate will be reduced down by y amount:

// GameEngine.cpp : Defines the entry point for the console application.
//

#ifdef WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#include <ctime>
#endif
#include<iostream>
#include <dos.h>    
#include "stdafx.h"

using namespace std;

UINT64 GetTimeInMs()
{
    #ifdef WIN32
     /* Windows */
     FILETIME ft;
     LARGE_INTEGER li;

     /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
      * to a LARGE_INTEGER structure. */
     GetSystemTimeAsFileTime(&ft);
     li.LowPart = ft.dwLowDateTime;
     li.HighPart = ft.dwHighDateTime;

     UINT64 ret = li.QuadPart;
     ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */
     ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */

     return ret;
    #else
     /* Linux */
     struct timeval tv;

     gettimeofday(&tv, NULL);

     uint64 ret = tv.tv_usec;
     /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */
     ret /= 1000;

     /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */
     ret += (tv.tv_sec * 1000);

     return ret;
    #endif
}

int _tmain(int argc, _TCHAR* argv[])
{
    int sv_tickrate_max = 1000; // The maximum amount of ticks per second
    int sv_tickrate_min = 100; // The minimum amount of ticks per second
    int sv_tickrate_adjust = 10; // How much to de/increment the tickrate by
    int sv_tickrate_stable_before_increment = 1000; // How many stable ticks before we increase the tickrate again

    int sys_tickrate_current = sv_tickrate_max; // Always start at the highest possible tickrate for the best performance

    int counter_stable_ticks = 0; // How many ticks we have not lagged for


    UINT64 __startTime = GetTimeInMs();
    int ticks = 100000;

    while(ticks > 0)
    {
        int maxTimeInMs = 1000 / sys_tickrate_current; 

        UINT64 _startTime = GetTimeInMs();

        // Long code here...

        cout << ".";

        UINT64 _timeTaken = GetTimeInMs() - _startTime;

        if(_timeTaken < maxTimeInMs)
        {
            Sleep(maxTimeInMs - _timeTaken);
            counter_stable_ticks++;

            if(counter_stable_ticks >= sv_tickrate_stable_before_increment)
            {
                // reset the stable # ticks counter
                counter_stable_ticks = 0;

                // make sure that we don't go over the maximum tickrate
                if(sys_tickrate_current + sv_tickrate_adjust <= sv_tickrate_max)
                {
                    sys_tickrate_current += sv_tickrate_adjust;

                    // let me know in console #DEBUG
                    cout << endl << "Improving tickrate. New tickrate: " << sys_tickrate_current << endl;
                }
            }
        }
        else if(_timeTaken > maxTimeInMs)
        {
            cout << endl;
            if((sys_tickrate_current - sv_tickrate_adjust) > sv_tickrate_min)
            {
                sys_tickrate_current -= sv_tickrate_adjust;
            }
            else
            {
                if(sys_tickrate_current == sv_tickrate_min)
                {
                    cout << "Please reduce sv_tickrate_min..." << endl;
                }
                else{
                    sys_tickrate_current = sv_tickrate_min;
                }
            }

            // let me know in console #DEBUG
            cout << "The server has lag. Reduced tickrate to: " << sys_tickrate_current << endl;
        }
        ticks--;
    }

    UINT64 __timeTaken = GetTimeInMs() - __startTime;

    cout << endl << endl << "Total time in ms: " << __timeTaken;

    cout << endl << "Ending tickrate: " << sys_tickrate_current;

    char test;
    cin >> test;

    return 0;
}
\$\endgroup\$
2
  • \$\begingroup\$ it's mostly because of two reasons: physics simulations and client side prediction, hopefully other guys will answer your question or I'll answer it in two days in my poor English! meanwhile you can do a little research about those two yourself. \$\endgroup\$
    – Ali1S232
    Aug 29, 2012 at 21:53
  • \$\begingroup\$ You can read here about how Age of Empires used variable timesteps. gamasutra.com/view/feature/131503/… \$\endgroup\$
    – Matsemann
    Sep 3, 2012 at 8:07

1 Answer 1

1
\$\begingroup\$

There are a few reasons. Let's start by thinking about why the server has something called tickrate in the first place.

  • It allows scheduling of events. "send world state to clients every N ticks"
  • It allows iterative processing of game events
  • It makes the whole system synchronous, granted that all systems rely on same tick counter

So what happens if the server intentionally alters this depending on it's load:

  • Event scheduling gets somewhat messed up - server load can change rapidly and packets may be sent late or out of order. Both cause lag and jitter in multiplayer experience.
  • Iterative processing of physics for example lose precision, this can easily cause syncing problems between server and connected clients, which then require the server to send out world updates.
  • While the server itself still is within sync(if all the systems rely on the same tick counter), there can arise situations when server and client go out of sync network wise - packets arrive with higher delay and go out of order.

So, we bring in problems and probably some hard to debug bugs. Of course, all problems are easy to circumvent with some thought, but is this really the price to pay for less CPU load during times of low CPU load anyway? Of course something like this could be benefiting for server which are being ran on hardware with varying load(a shared server for example) but in the end it's the players online experience which takes a hit anyway. Simply put, players prefer servers which don't have performance issues, and dynamic tickrate adjustment wouldn't bring a cure anyway.

\$\endgroup\$
2
  • \$\begingroup\$ Let's not forget that "The Server" is mostly "A Whole Bunch of Boxes Running a Whole Bunch of Processes and Threads" and not just one box and so the question of keeping all those boxes working together smoothly together is a whole other can of worms. \$\endgroup\$ Sep 3, 2012 at 8:37
  • \$\begingroup\$ @PatrickHughes am I missing something or what exactly makes you imply distributed computing for hosting game servers? \$\endgroup\$
    – zxcdw
    Sep 3, 2012 at 9:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .