1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 /**
14  * GameTime:
15  *  Manages the network time.
16  *      Time is stored as microseconds since the epoch.
17  */
18 
19 // the top dog
20 #include "common.h"
21 
22 // interface header
23 #include "GameTime.h"
24 
25 // system headers
26 #include <time.h>
27 #ifndef _WIN32
28 #  include <sys/time.h>
29 #endif
30 #include <list>
31 
32 // common headers
33 #include "Pack.h"
34 #include "bzfio.h"
35 
36 
37 // type definitions
38 typedef uint16_t u16;
39 typedef uint32_t u32;
40 #ifndef WIN32
41 typedef int64_t s64;
42 #else
43 typedef __int64 s64;
44 #endif
45 typedef struct
46 {
47     s64 netTime;
48     s64 localTime;
49 } TimeRecord;
50 
51 // local constants
52 static const double filterTime = 10.0;
53 static const unsigned int maxRecords = 1024; // safety
54 static const unsigned int maxRecordAge = 120 * 1000000;
55 static const s64 maxTime = 2345678;
56 static const double minRate = 0.50;
57 static const double maxRate = 2.00;
58 
59 // local variables
60 static std::list<TimeRecord> timeRecs;
61 static double stepSecs = 0.0;
62 static s64 stepTime = 0;
63 static double avgRate = 1.0;
64 static TimeRecord avgPoint = {0, 0};
65 
66 
67 /******************************************************************************/
68 
getRawTime()69 static s64 getRawTime()
70 {
71 #ifndef _WIN32
72 
73     struct timeval tv;
74     gettimeofday(&tv, NULL);
75     return ((s64)tv.tv_sec * (s64)1000000) + (s64)tv.tv_usec;
76 
77 #else //_WIN32
78 
79     FILETIME ft;
80     LARGE_INTEGER li;
81 
82     GetSystemTimeAsFileTime(&ft);
83     li.LowPart  = ft.dwLowDateTime;
84     li.HighPart = ft.dwHighDateTime;
85 
86     /* QuadPart is in 100-nanosecond intervals */
87     return (li.QuadPart / 10);
88 
89 #endif //_WIN32
90 }
91 
92 
93 /******************************************************************************/
94 
calcAvgRate()95 static void calcAvgRate()
96 {
97     // FIXME - this is weak
98     const int count = timeRecs.size();
99     if (count <= 0)
100     {
101         avgRate = 1.0;
102         avgPoint.netTime = 0;
103         avgPoint.localTime = 0;
104         return;
105     }
106     else if (timeRecs.size() == 1)
107     {
108         const TimeRecord& tr = *timeRecs.begin();
109         avgRate = 1.0;
110         avgPoint = tr;
111         return;
112     }
113     else
114     {
115         const TimeRecord& last = *timeRecs.begin();
116         const TimeRecord& first = *timeRecs.rbegin();
117         const s64 netDiff = last.netTime - first.netTime;
118         const s64 locDiff = last.localTime - first.localTime;
119         if (locDiff != 0.0)
120         {
121             avgRate = ((double)netDiff / (double)locDiff);
122             avgPoint = last;
123         }
124         else
125         {
126             // don't update
127         }
128     }
129     return;
130 }
131 
132 
133 /******************************************************************************/
134 
reset()135 void GameTime::reset()
136 {
137     stepSecs = 0.0;
138     stepTime = 0;
139     avgRate = 1.0;
140     avgPoint.netTime = 0;
141     avgPoint.localTime = 0;
142     timeRecs.clear();
143     return;
144 }
145 
146 
resetToRecord(const TimeRecord & record)147 static void resetToRecord(const TimeRecord& record)
148 {
149     avgRate = 1.0;
150     avgPoint = record;
151     stepTime = record.netTime;
152     TimeRecord copy = record;
153     timeRecs.clear();
154     timeRecs.push_front(copy);
155     return;
156 }
157 
158 
update()159 void GameTime::update()
160 {
161     unsigned int count = timeRecs.size();
162     if (count == 0)
163     {
164         const TimeRecord tr = {0,0};
165         resetToRecord(tr);
166     }
167     else if (count == 1)
168     {
169         const TimeRecord& tr = *timeRecs.begin();
170         resetToRecord(tr);
171     }
172     else
173     {
174         calcAvgRate();
175         const TimeRecord& tr = *timeRecs.begin();
176         const s64 diffTime = stepTime - tr.netTime;
177         if ((diffTime < -maxTime) || (diffTime > +maxTime) ||
178                 (avgRate < minRate) || (avgRate > maxRate))
179         {
180             logDebugMessage(4,"GameTime: discontinuity: usecs = %lli, rate = %f\n",
181                             diffTime, avgRate);
182             resetToRecord(tr);
183         }
184     }
185 
186     logDebugMessage(4,"GameTime: count = %zu, rate = %f\n", timeRecs.size(), avgRate);
187 
188     return;
189 }
190 
191 
192 /******************************************************************************/
193 
setStepTime()194 void GameTime::setStepTime()
195 {
196     static s64 lastStep = 0;
197     const s64 thisStep = getRawTime();
198     if (timeRecs.empty())
199         stepTime = thisStep;
200     else
201     {
202         // long term prediction
203         const double diffLocal = (double)(thisStep - avgPoint.localTime);
204         const double longPred = (double)avgPoint.netTime + (diffLocal * avgRate);
205         // short term prediction
206         const double skipTime = (double)(thisStep - lastStep);
207         const double shortPred = (double)stepTime + (skipTime * avgRate);
208         // filtering
209         const double c = (skipTime * 1.0e-6) / filterTime;
210         const double a = (c > 0.0) && (c < 1.0) ? c : 0.5;
211         const double b = 1.0 - a;
212         stepTime = (s64)((a * longPred) + (b * (double)shortPred));
213     }
214     stepSecs = (double)stepTime * 1.0e-6;
215     lastStep = thisStep;
216 
217     return;
218 }
219 
220 
getStepTime()221 double GameTime::getStepTime()
222 {
223     return stepSecs;
224 }
225 
226 
227 /******************************************************************************/
228 
packSize()229 int GameTime::packSize()
230 {
231     return (2 * sizeof(u32));
232 }
233 
234 
pack(void * buf,float lag)235 void* GameTime::pack(void *buf, float lag)
236 {
237     double halfLag;
238     if ((lag <= 0.0f) || (lag > 10.0f))
239     {
240         // assume a 150ms delay
241         halfLag = 0.075;
242     }
243     else
244         halfLag = (double)(lag * 0.5f);
245     const s64 nowTime = getRawTime() + (s64)(halfLag * 1.0e6);
246     buf = nboPackUInt(buf, (u32)(nowTime >> 32));     // msb's
247     buf = nboPackUInt(buf, (u32)(nowTime & 0xFFFFFFFF));  // lsb's
248     return buf;
249 }
250 
251 
unpack(const void * buf)252 const void* GameTime::unpack(const void *buf)
253 {
254     u32 msb, lsb;
255     buf = nboUnpackUInt(buf, msb);
256     buf = nboUnpackUInt(buf, lsb);
257     const s64 netTime = ((s64)msb << 32) + (s64)lsb;
258 
259     // store the value
260     const TimeRecord tr = { netTime, getRawTime() };
261     timeRecs.push_front(tr);
262 
263     // clear oversize entries
264     while (timeRecs.size() > maxRecords)
265         timeRecs.pop_back();
266 
267     // clear the aged entries
268     if (!timeRecs.empty())
269     {
270         s64 nowTime = getRawTime();
271         while (!timeRecs.empty())
272         {
273             TimeRecord back = *timeRecs.rbegin();
274             if ((nowTime - back.localTime) < maxRecordAge)
275                 break;
276             timeRecs.pop_back();
277         }
278     }
279 
280     return buf;
281 }
282 
283 
284 /******************************************************************************/
285 
286 
287 // Local Variables: ***
288 // mode: C++ ***
289 // tab-width: 4 ***
290 // c-basic-offset: 4 ***
291 // indent-tabs-mode: nil ***
292 // End: ***
293 // ex: shiftwidth=4 tabstop=4
294