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