Jump to content
  • Advertisement
Sign in to follow this  
nPawn

Dealing with Latency...?

This topic is 4873 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok, I followed the "Targetting" tutorial on Gamedev for my little spaceship game (think subspace clone), and i'm still getting a little bit jaggedy results (it improved quite a bit, but not all the way). What i'm doing: Before I send out the player update which holds their position and velocity info, I look at a latency variable which gets filled every few seconds (this variable holds my guess at latency by sending out a packet to the server and seeing how long it takes to get a response and dividing that time in half) then I move the ship to where it will be in that in the future after that amount of latency time elapses to make up for the lag from player to server, and send off that packet to the server. The server gets the packet and sends it to all the other players. The other player recieves the original player's update, and looks at it's own latency to the server (obtained in the same way I mentioned first), and advances the ships update position even further according to that latency. Looking at the latency results, it appears one client has around a 15ms latency, the other client has around a 29ms latency. How can I improve upon this implementation to get even smoother results? Right now this is all on a LAN, and i'm kinda surprised the latency is enough to even make the movement jaggedy without interpolation of some sort in the first place. Hopefully my description of how i'm doing it right now made some sense.

Share this post


Link to post
Share on other sites
Advertisement
You should constantly be computing round trip time using a filtered mean-deviation estimator:

RTT calculation.
More info here.

Part of the extra delay could be due to your simulation rate and network update rate. If you are running at 60Hz, there will be an average extra delay of 1/60*.5, or 16.667*.5 = 8.33ms.

See Dead reckoning and Ghosts for networked object motion (smoothly interpolate between object's and their ghosts).
See here for more information.
Smoothing motion.

Share this post


Link to post
Share on other sites
Can you explain the formulas from your first link a bit?

mean’ = (7/8)*mean + (1/8)*(rtt)
sdev’ = (3/4)*sdev + (1/4)*abs(rtt-mean)


mainly, what are the variables 'mean', 'rtt', 'sdev', and how do I get them?

Share this post


Link to post
Share on other sites
I would assume, from context, that those variables are:

rtt: raw round-trip-time sample, as measured on a single packet round-trip
mean: average round-trip-time, as used by the system
sdev: standard deviation of the round trip time

This just runs simple statistics on the round trip time in an attempt to smoothly adapt the program to changing networking conditions.

Share this post


Link to post
Share on other sites
Ok thanks for clarifying it. I know how to get the average, but I forget how to get the standard deviation of a set of numbers. And then what do I do with the results of those formulas? Do I use the mean' or sdev' as the guessed timeframe for moving the object?

Still trying to wrap my head around the cubic spline tutorial, the description of how to implement the formulas didn't make a lot of sense.

Share this post


Link to post
Share on other sites
Quote:
Original post by nPawn
Ok thanks for clarifying it. I know how to get the average, but I forget how to get the standard deviation of a set of numbers. And then what do I do with the results of those formulas? Do I use the mean' or sdev' as the guessed timeframe for moving the object?


See Jacobson's paper for a complete description of the algorithm. The concepts are important, and are a good starting point. However, feel free to modify and change the design once you get an idea for how it works with your particular application. A real-time graph showing network history is extremely valuable in tuning a custom protocol.

An example Graphical Trend Analyzer (if there is any interest, I can provide the rest of the code after E3):


struct TrendAnalyzer {
int historyLength;
FIRfilterDynamic filter;
FIRfilterDynamic filterMin;
FIRfilterDynamic filterMax;
DynArrayNCD<float> sampledHistory;
DynArrayNCD<float> filteredHistory;
float absoluteMin,absoluteMax;
float filteredMin,filteredMax;
float filteredMinMaxRate;
float RANGE_INIT;
int head;
TrendAnalyzer() : sampledHistory(0), filteredHistory(0), historyLength(0) {
RANGE_INIT = 1e6;
filteredMinMaxRate = 1.f/(60.f*2.f);
reset();
}
~TrendAnalyzer() {
free();
}
void free(void) {
sampledHistory.free();
filteredHistory.free();
} // free
void init(int _historyLength) {
free();
historyLength = _historyLength;
sampledHistory.setSizeMaxCount(historyLength);
filteredHistory.setSizeMaxCount(historyLength);
filter.init(_historyLength);
filterMin.init(_historyLength);
filterMax.init(_historyLength);
reset();
} // init
void reset(void) {
for (int i=0; i < historyLength; i++) {
sampledHistory = 0.f;
filteredHistory = 0.f;
} // for
absoluteMin = RANGE_INIT;
absoluteMax = -absoluteMin;
filteredMin = 0.f;
filteredMax = 0.f;
head = 0;
filter.reset();
filterMin.reset();
filterMax.reset();
} // reset
void update(float sample) {
if (sample < absoluteMin) absoluteMin = sample;
if (sample > absoluteMax) absoluteMax = sample;
sampledHistory[head] = sample;
filteredHistory[head] = filter.updateAndGetFilteredSample(sample);
if (++head == historyLength) head = 0;

float currentMin = RANGE_INIT;
float currentMax = -currentMin;

int i;
for (i=0;i < historyLength; i++) {
float sample = sampledHistory;
if (sample < currentMin) currentMin = sample;
if (sample > currentMax) currentMax = sample;
sample = filteredHistory;
} // for

float filteredMinT = filterMin.updateAndGetFilteredSample(currentMin);
float filteredMaxT = filterMax.updateAndGetFilteredSample(currentMax);
filteredMin = rLerp(filteredMinMaxRate,filteredMin,filteredMinT);
filteredMax = rLerp(filteredMinMaxRate,filteredMax,filteredMaxT);

} // update

virtual void line2D(float x1,float y1,float x2,float y2,ColorARGB color)=0;
virtual void rect2D(float x,float y,float width,float height,ColorARGB color)=0;
virtual void text2D(const char * s,float x,float y,ColorARGB color)=0;
virtual float textPixelWidth(const char * s)=0;
virtual float textPixelHeight(const char * s)=0;

virtual void render(float cx,float cy,
float width,float height,
float borderWidth,
ColorARGB backgroundColor,
ColorARGB historyColor,
ColorARGB filteredHistoryColor,
ColorARGB minColor, // Min value line
ColorARGB maxColor, // Max value line
float minRange,float maxRange, // When both are -1, use dynamic range.
float scale, // Scale the rendered values (scale = frameRate*8/1000, scale*bytesPerFrame = KBits/sec).
const char * units, // f/s, KBit/s, etc.
const char * title) {

rect2D(cx,cy,width,height,backgroundColor);

width -= borderWidth*2.f;
height -= borderWidth*2.f;
float maxOffsetY = height-1.f;

float startX = cx + borderWidth;
float startY = cy + borderWidth + maxOffsetY; // Drawing from bottom up

float scaleX = (width-1.f)/float(historyLength-1);
float scaleY;

float sampleOffsetY,rangeY;

if (minRange == -1.f && maxRange == -1.f) {
rangeY = filteredMax - filteredMin;
sampleOffsetY = filteredMin;
} else {
rangeY = maxRange - minRange;
sampleOffsetY = minRange;
} // if

if (rangeY > 0.f) scaleY = maxOffsetY/rangeY;
else scaleY = 0.f;

float lastX;
float lastY;

// Draw sampledHistory
int pos = head; // Start at oldest sample, move toward newest sample
int i;
for (i=0; i < historyLength; i++) {
float x = startX + float(i)*scaleX;
float biasedY = sampledHistory[pos]-sampleOffsetY; // Bias so min is near zero
float y = startY - clamp(biasedY*scaleY,0.f,maxOffsetY); // Drawing up
if (i > 0) {
line2D(lastX,lastY,x,y,historyColor);
} // if
lastX = x;
lastY = y;
if (++pos == historyLength) pos = 0;
} // for

// Draw filteredHistory over sampledHistory
pos = head; // Start at oldest sample, move toward newest sample
for (i=0; i < historyLength; i++) {
float x = startX + float(i)*scaleX;
float biasedY = filteredHistory[pos]-sampleOffsetY; // Bias so min is near zero
float y = startY - clamp(biasedY*scaleY,0.f,maxOffsetY); // Drawing up
if (i > 0) {
line2D(lastX,lastY,x,y,filteredHistoryColor);
} // if
lastX = x;
lastY = y;
if (++pos == historyLength) pos = 0;
} // for

rangeY = absoluteMax - absoluteMin;
if (rangeY > 0.f) scaleY = maxOffsetY/rangeY;
else scaleY = 0.f;

// Render title
float stW = startX + width - 1.f;
float stYMin = startY-height;
float stX = startX;
text2D(title,stX,stYMin,ColorARGB(127,127,240,240));

// Render average (last filtered history value)
int newestIndex = head > 0 ? head-1 : historyLength-1;
char buff[64];
sprintf(buff,"AVE %3.3f %s",filteredHistory[newestIndex]*scale,units);
text2D(buff,stX,startY-borderWidth,ColorARGB(127,200,200,100));

// Render Absolute Max
sprintf(buff,"AMAX %.3f",absoluteMax*scale);
stX = stW - textPixelWidth(buff);
text2D(buff,stX,stYMin,ColorARGB(127,192,192,192));
stYMin += textPixelHeight(buff)+1.f;

// Render Absolute Min
sprintf(buff,"AMIN %.3f",absoluteMin*scale);
stX = startX + width - textPixelWidth(buff) - 1.f;
float stYMax = startY-borderWidth;
text2D(buff,stX,stYMax,ColorARGB(127,192,192,192));
stYMax -= textPixelHeight(buff)+1.f;

// Render filteredMax line
float lineY = startY-filteredMax*scaleY; // Drawing up
lineY = clamp(lineY,stYMin,stYMax);
line2D(startX,lineY,lastX,lineY,maxColor);
sprintf(buff,"FMAX %3.3f",filteredMax*scale);
float fmaxWidth = textPixelWidth(buff);
stX = stW - fmaxWidth;
text2D(buff,stX,lineY,ColorARGB(127,200,100,100));
float minLineY = lineY + textPixelHeight(buff) - 1.f;

// Render filteredMin line
lineY = startY-filteredMin*scaleY; // Drawing up
lineY = clamp(lineY,stYMin,stYMax);
line2D(startX,lineY,lastX,lineY,minColor);
sprintf(buff,"FMIN %3.3f",filteredMin*scale);
stX = stW - textPixelWidth(buff);
if (lineY < minLineY) stX -= fmaxWidth+textPixelWidth(" ");

text2D(buff,stX,lineY,ColorARGB(127,100,200,100));

} // render

};




Quote:
Original post by nPawn
Still trying to wrap my head around the cubic spline tutorial, the description of how to implement the formulas didn't make a lot of sense.


Skip the spline method for now. To start, just interpolate between the two positions/orientations.

This method adds some positional delay (lag), but it's easy to implement:

alpha = 1.f/15.f // arbitrary. Set to suit desired behavior.

localPosition = lerp(alpha,localPosition,ghostPosition) // vectors
localRotation = slerp(alpha,localRotation,ghostRotation) // quats
(You can do the same for the velocities).

The above is an IIR filter (Infinite Impulse Response filter), and is also a low-pass filter (high frequency motion will be smoothed: this also results in some delay).

Share this post


Link to post
Share on other sites
I assume Lerp is just finding the midpoint between the two points? then we multiply that by the alpha?

Share this post


Link to post
Share on other sites
Quote:
Original post by nPawn
I assume Lerp is just finding the midpoint between the two points? then we multiply that by the alpha?


lerp = Linear Interpolation. Example for a vector:


inline Vec3 lerp(const flt a,const Vec3 & lo,const Vec3 & hi) {
return lo + (hi - lo)*a;
} // Vec3 lerp





http://catb.org/~esr/jargon/html/L/LERP.html
http://ggt.sourceforge.net/html/group__Interp.html

If your game is 2D, you won't need slerp (for 3D rotations). For 2D, just lerp the rotation angles.

See www.google.com and search for lerp, slerp, etc., for more info.

Share this post


Link to post
Share on other sites
using the Lerp is the smoothest i've seen yet. It's really smooth on one computer, but another computer still gets spurts of extra speed (like it's zooming to catch up), but the jerkiness/jumping for it is all but gone. Gonna play with things until I can hopefully get the "catch up" fixed.

Would you mind explaining what the Lerp is doing? Linear Interpolation doesn't mean anything to me, i've heard the term but dunno what it means. Sorry for all the questions but i'm really new with this topic so i'm not sure what is being accomplished other than a smoothing of sorts. Thanks for all your help.

Share this post


Link to post
Share on other sites
The problem with using that alpha is that, if you want to move from point 0 to point 1 with an alpha of 10 (say), you will move:

In the first timestep, from 0.0 to 0.1
In the second timestep, from 0.1 to 0.19
In the third timestep, from 0.19 to 0.271
...

I e, you'll start out going fast, and "ease off" towards the goal.

There are better interpolators/extrapolators pointed at from the Forum FAQ.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!