1 /*
2
3 *************************************************************************
4
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000 Manuel Moos (manuel@moosnet.de)
7
8 **************************************************************************
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 ***************************************************************************
25
26 */
27
28 #include "tMemManager.h"
29 #include "eTimer.h"
30 #include "eNetGameObject.h"
31 #include "nSimulatePing.h"
32 #include "tRecorder.h"
33 #include "tMath.h"
34 #include "tConfiguration.h"
35 #include "eLagCompensation.h"
36
37 eTimer *se_mainGameTimer=NULL;
38
39 // from nNetwork.C; used to sync time with the server
40 //extern REAL sn_ping[MAXCLIENTS+2];
41
eTimer()42 eTimer::eTimer():nNetObject(), startTimeSmoothedOffset_(0){
43 //con << "Creating own eTimer.\n";
44
45 speed = 1.0;
46
47 Reset(0);
48
49 if (se_mainGameTimer)
50 delete se_mainGameTimer;
51 se_mainGameTimer=this;
52 if (sn_GetNetState()==nSERVER)
53 RequestSync();
54
55 averageSpf_.Reset();
56 averageSpf_.Add(1/60.0,5);
57
58 creationSystemTime_ = tSysTimeFloat();
59 }
60
eTimer(nMessage & m)61 eTimer::eTimer(nMessage &m):nNetObject(m), startTimeSmoothedOffset_(0){
62 //con << "Creating remote eTimer.\n";
63
64 speed = 1.0;
65
66 Reset(0);
67
68 if (se_mainGameTimer)
69 delete se_mainGameTimer;
70 se_mainGameTimer=this;
71
72 averageSpf_.Reset();
73 averageSpf_.Add(1/60.0,5);
74
75 creationSystemTime_ = tSysTimeFloat();
76
77 lastStartTime_ = lastRemoteTime_ = 0;
78
79 // assume VERY strongly the clocks are in sync
80 startTimeDrift_.Add( 0, 10000 );
81 }
82
~eTimer()83 eTimer::~eTimer(){
84 //con << "Deleting eTimer.\n";
85 se_mainGameTimer=NULL;
86 }
87
88 // see if a client supports lag compensation
89 static nVersionFeature se_clientLagCompensation( 14 );
90
91 // old clients need a default lag compensation
92 static REAL se_lagOffsetLegacy = 0.0f;
93 static tSettingItem< REAL > se_lagOffsetLegacyConf( "LAG_OFFSET_LEGACY", se_lagOffsetLegacy );
94
WriteSync(nMessage & m)95 void eTimer::WriteSync(nMessage &m){
96 nNetObject::WriteSync(m);
97 REAL time = Time();
98
99 if ( SyncedUser() > 0 && !se_clientLagCompensation.Supported( SyncedUser() ) )
100 time += se_lagOffsetLegacy;
101
102 m << time;
103 m << speed;
104 //std::cerr << "syncing:" << currentTime << ":" << speed << '\n';
105
106 // plan next sync
107 const REAL stopFast = 3;
108 const REAL maxFast = 3;
109 if ( time < stopFast )
110 {
111 nextSync_ = smoothedSystemTime_ + maxFast/(stopFast+maxFast-time);
112 }
113 else
114 {
115 nextSync_ = smoothedSystemTime_ + 1;
116 }
117 }
118
119 static REAL se_timerStartFudge = 0.0;
120 static tSettingItem<REAL> se_timerStartFudgeConf("TIMER_SYNC_START_FUDGE",se_timerStartFudge);
121 static REAL se_timerStartFudgeStop = 2.0;
122 static tSettingItem<REAL> se_timerStartFudgeStopConf("TIMER_SYNC_START_FUDGE_STOP",se_timerStartFudgeStop);
123
ReadSync(nMessage & m)124 void eTimer::ReadSync(nMessage &m){
125 nNetObject::ReadSync(m);
126
127 bool checkDrift = true;
128
129 //std::cerr << "Got sync:" << remote_currentTime << ":" << speed << '\n';
130
131 // determine the best estimate of the start time offset that reproduces the sent remote
132 // time and its expected quality.
133 REAL remoteStartTimeOffset = 0;
134 REAL remoteTimeNonQuality = 0;
135 {
136 //REAL oldTime=currentTime;
137 REAL remote_currentTime, remoteSpeed;
138 m >> remote_currentTime; // read in the remote time
139 m >> remoteSpeed;
140
141 // forget about earlier syncs if the speed changed
142 if ( fabs( speed - remoteSpeed ) > 10 * EPS )
143 {
144 qualityTester_.Timestep( 100 );
145 startTimeOffset_.Reset();
146 checkDrift = false;
147 }
148
149 speed = remoteSpeed;
150
151 // determine ping
152 nPingAverager & averager = sn_Connections[m.SenderID()].ping;
153 // pingAverager_.Add( rawAverager.GetPing(), remote_currentTime > .01 ? remote_currentTime : .01 );
154 REAL ping = averager.GetPing();
155
156 // add half our ping (see Einsteins SRT on clock syncronisation)
157 REAL real_remoteTime=remote_currentTime+ping*speed*.5;
158
159 // and the normal time including ping charity
160 REAL min_remoteTime=remote_currentTime+ping*speed-
161 sn_pingCharityServer*.001;
162
163 if (real_remoteTime<min_remoteTime)
164 remote_currentTime=min_remoteTime;
165 else
166 remote_currentTime=real_remoteTime;
167
168 // HACK: warp time into the future at the beginning of the round.
169 // I can't explain why this is required; without it, the timer is late.
170 if ( remote_currentTime < se_timerStartFudgeStop )
171 {
172 remote_currentTime += ping * ( se_timerStartFudgeStop - remote_currentTime ) * se_timerStartFudge;
173 checkDrift = false;
174 }
175
176 // determine quality from ping variance
177 remoteTimeNonQuality = averager.GetFastAverager().GetAverageVariance();
178
179 // determine start time
180 remoteStartTimeOffset = smoothedSystemTime_ - startTime_ - remote_currentTime;
181
182 // calculate drift
183 if( checkDrift )
184 {
185 REAL timeDifference = remote_currentTime - lastRemoteTime_;
186 REAL drift = lastStartTime_ - remoteStartTimeOffset;
187
188 if ( timeDifference > 0 )
189 {
190 REAL driftAverage = startTimeDrift_.GetAverage();
191 // con << "Drift: " << driftAverage << "\n";
192 REAL driftDifference = drift/timeDifference;
193
194 timeDifference = timeDifference > 1 ? 1 : timeDifference;
195 startTimeDrift_.Timestep( timeDifference * .1 );
196 startTimeDrift_.Add( driftDifference + driftAverage, timeDifference );
197 }
198 }
199
200 lastStartTime_ = remoteStartTimeOffset;
201 lastRemoteTime_ = remote_currentTime;
202
203 // let the averagers decay faster at the beginning
204 if ( remote_currentTime < 0 )
205 {
206 qualityTester_.Timestep( .2 );
207 startTimeOffset_.Timestep( .2 );
208 }
209 }
210
211 // try to get independend quality measure: get the variance of the received start times
212 qualityTester_.Add( remoteStartTimeOffset );
213
214 // add the variance to the non-quality, along with an offset to avoid division by zero
215 remoteTimeNonQuality += 0.00001 + 4 * qualityTester_.GetAverageVariance();
216
217 // add the offset to the statistics, weighted by the quality
218 startTimeOffset_.Add( remoteStartTimeOffset, 1/remoteTimeNonQuality );
219 }
220
221 static nNOInitialisator<eTimer> eTimer_init(210,"eTimer");
222
CreatorDescriptor() const223 nDescriptor &eTimer::CreatorDescriptor() const{
224 return eTimer_init;
225 }
226
227 // determines whether time should be smoothed
se_SmoothTime()228 static bool se_SmoothTime()
229 {
230 bool smooth = 0;
231
232 // try to get value from recording
233 char const * section = "SMOOTHTIME";
234 if ( !tRecorder::PlaybackStrict( section, smooth ) )
235 {
236 // get value by OS type
237 smooth = !tTimerIsAccurate();
238 }
239 // archive the decision
240 tRecorder::Record( section, smooth );
241
242 return smooth;
243 }
244
Time()245 REAL eTimer::Time()
246 {
247 return ( smoothedSystemTime_ - startTime_ ) - startTimeSmoothedOffset_ + eLag::Current();
248 }
249
SyncTime()250 void eTimer::SyncTime(){
251 // get current system time
252 {
253 double newTime=tSysTimeFloat();
254
255 static bool smooth = se_SmoothTime();
256
257 // recheck if no recording/playback is running
258 if ( !tRecorder::IsRunning() )
259 {
260 smooth = !tTimerIsAccurate();
261 }
262
263 if ( !smooth )
264 {
265 // take it
266 smoothedSystemTime_ = newTime;
267 }
268 else
269 {
270 // smooth it
271 REAL smoothDecay = .2f;
272 smoothedSystemTime_ = ( smoothedSystemTime_ + smoothDecay * newTime )/( 1 + smoothDecay );
273 }
274 }
275
276 // update timers
277 REAL timeStep=smoothedSystemTime_ - lastTime_;
278 lastTime_ = smoothedSystemTime_;
279
280 #ifdef DEBUG
281 #ifndef DEDICATED
282 // maximum effective timestep in SP debug mode: .1s
283 if (timeStep > .1f && sn_GetNetState() == nSTANDALONE && !tRecorder::IsRunning() )
284 {
285 startTime_ += timeStep - .1f;
286 timeStep = .1f;
287 }
288 #endif
289 #endif
290
291 // update lag compensation
292 eLag::Timestep( timeStep );
293
294 // store and average frame times
295 spf_ = timeStep;
296 if ( timeStep > 0 && speed > 0 )
297 {
298 averageSpf_.Add( timeStep );
299 averageSpf_.Timestep( timeStep );
300 }
301
302 // let averagers decay
303 startTimeOffset_.Timestep( timeStep * .1 );
304 qualityTester_ .Timestep( timeStep * .3 );
305 // pingAverager_ .Timestep( timeStep * .05);
306
307 // if we're not running at default speed, update the virtual start time
308 startTime_ += ( 1.0 - speed - startTimeDrift_.GetAverage() ) * timeStep;
309
310 // smooth time offset
311 {
312 REAL startTimeOffset = startTimeOffset_.GetAverage();
313
314 // correct huge deviations (compared to variance) faster
315 REAL deviation = startTimeSmoothedOffset_ - startTimeOffset;
316 REAL extraSmooth = deviation * deviation / ( startTimeOffset_.GetAverageVariance() + .0001 );
317
318 // allow for faster smoothing at the beginning of the round
319 REAL time = Time();
320 if ( time < 0 )
321 extraSmooth -= 4 * time;
322
323 REAL smooth = timeStep * ( .5 + extraSmooth );
324 startTimeSmoothedOffset_ = ( startTimeSmoothedOffset_ + startTimeOffset * smooth )/(1 + smooth);
325
326 if ( !finite( startTimeSmoothedOffset_ ) )
327 {
328 // emergency, smoothing the timer produced infinite results
329 st_Breakpoint();
330 startTimeSmoothedOffset_ = startTimeOffset;
331 }
332 }
333
334 // request syncs
335 if (sn_GetNetState()==nSERVER && smoothedSystemTime_ >= nextSync_ )
336 {
337 #ifdef nSIMULATE_PING
338 RequestSync(); // ack.
339 #else
340 RequestSync(false); // NO ack.
341 #endif
342 }
343 }
344
Reset(REAL t)345 void eTimer::Reset(REAL t){
346 if (sn_GetNetState()!=nCLIENT)
347 speed=1;
348
349 // reset start time
350 smoothedSystemTime_ = tSysTimeFloat();
351 startTime_ = smoothedSystemTime_ - t;
352
353 // reset averagers
354 startTimeOffset_.Reset();
355 startTimeOffset_.Add(100,EPS);
356 startTimeOffset_.Add(-100,EPS);
357
358 qualityTester_.Reset();
359 static const REAL qual = sqrt(1/EPS);
360 qualityTester_.Add(qual,EPS);
361 qualityTester_.Add(-qual,EPS);
362
363 // reset times of actions
364 lastTime_ = nextSync_ = smoothedSystemTime_;
365 startTimeSmoothedOffset_ = startTimeOffset_.GetAverage();
366 }
367
IsSynced() const368 bool eTimer::IsSynced() const
369 {
370 // allow non-synced status only during the first ten seconds of a connection
371 if ( smoothedSystemTime_ - creationSystemTime_ > 10 || sn_GetNetState() != nCLIENT )
372 return true;
373
374 // the quality of the start time offset needs to be good enough (to .1 s, variance is the square of the expected)
375 bool synced = startTimeOffset_.GetAverageVariance() < .01 &&
376 fabs( startTimeOffset_.GetAverage() - startTimeSmoothedOffset_ ) < .01;
377
378 static char const * section = "TIMER_SYNCED";
379
380 if ( tRecorder::IsPlayingBack() ? tRecorder::Playback( section ) : synced )
381 {
382 tRecorder::Record( section );
383
384 // and make sure the next calls also return true.
385 creationSystemTime_ = smoothedSystemTime_ - 11;
386 return true;
387 }
388
389 return false;
390 }
391
pause(bool p)392 void eTimer::pause(bool p){
393 if (p){
394 if(speed!=0){
395 speed=0;
396 if (sn_GetNetState()==nSERVER)
397 RequestSync();
398 }
399 }
400 else{
401 if (speed!=1){
402 speed=1;
403 //Reset(currentTime_);
404
405 if (sn_GetNetState()==nSERVER)
406 RequestSync();
407 }
408 }
409 }
410
se_GameTime()411 REAL se_GameTime(){
412 if (se_mainGameTimer)
413 return se_mainGameTimer->Time();
414 else
415 return 0;
416 }
se_GameTimeNoSync()417 REAL se_GameTimeNoSync(){
418 if (se_mainGameTimer)
419 return se_mainGameTimer->TimeNoSync();
420 else
421 return 0;
422 }
423
se_SyncGameTimer()424 void se_SyncGameTimer(){
425 if (se_mainGameTimer)
426 se_mainGameTimer->SyncTime();
427 }
428
se_MakeGameTimer()429 void se_MakeGameTimer(){
430 tNEW(eTimer);
431 }
432
se_KillGameTimer()433 void se_KillGameTimer(){
434 if (se_mainGameTimer)
435 delete se_mainGameTimer;
436 se_mainGameTimer=NULL; // to make sure
437 }
438
439
se_ResetGameTimer(REAL t)440 void se_ResetGameTimer(REAL t){
441 if (se_mainGameTimer)
442 se_mainGameTimer->Reset(t);
443 }
444
se_PauseGameTimer(bool p)445 void se_PauseGameTimer(bool p){
446 if (se_mainGameTimer && sn_GetNetState()!=nCLIENT)
447 se_mainGameTimer->pause(p);
448 }
449
se_AverageFrameTime()450 REAL se_AverageFrameTime(){
451 return 1/se_AverageFPS();
452 }
453
se_AverageFPS()454 REAL se_AverageFPS(){
455 if (se_mainGameTimer)
456 return se_mainGameTimer->AverageFPS();
457 else
458 return (.2);
459 }
460
se_PredictTime()461 REAL se_PredictTime(){
462 return se_AverageFrameTime()*.5;
463 }
464
465
466
467
468