]> git.mxchange.org Git - flightgear.git/blob - src/Time/TimeManager.cxx
Potential fix for bug #471 (crash in checkspeedadjustment).
[flightgear.git] / src / Time / TimeManager.cxx
1 // TimeManager.cxx -- simulation-wide time management
2 //
3 // Written by James Turner, started July 2010.
4 //
5 // Copyright (C) 2010  Curtis L. Olson  - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "TimeManager.hxx"
26
27 #ifdef _WIN32
28 #  define WIN32_LEAN_AND_MEAN
29 #  include <windows.h> // for Sleep()
30 #else
31 #  include <unistd.h> // for usleep()
32 #endif
33
34 #include <simgear/timing/sg_time.hxx>
35 #include <simgear/structure/event_mgr.hxx>
36 #include <simgear/misc/sg_path.hxx>
37 #include <simgear/timing/lowleveltime.h>
38 #include <simgear/structure/commands.hxx>
39
40 #include <Main/fg_props.hxx>
41 #include <Main/globals.hxx>
42 #include <Time/sunsolver.hxx>
43
44 using std::string;
45
46 static bool do_timeofday (const SGPropertyNode * arg)
47 {
48     const string &offset_type = arg->getStringValue("timeofday", "noon");
49     int offset = arg->getIntValue("offset", 0);
50     TimeManager* self = (TimeManager*) globals->get_subsystem("time");
51     if (offset_type == "real") {
52     // without this, setting 'real' time is a no-op, since the current
53     // wrap value (orig_warp) is retained in setTimeOffset. Ick.
54         fgSetInt("/sim/time/warp", 0);
55     }
56     
57     self->setTimeOffset(offset_type, offset);
58     return true;
59 }
60
61 TimeManager::TimeManager() :
62   _inited(false),
63   _impl(NULL)
64 {
65   SGCommandMgr::instance()->addCommand("timeofday", do_timeofday);
66 }
67
68 void TimeManager::init()
69 {
70   if (_inited) {
71     // time manager has to be initialised early, so needs to be defensive
72     // about multiple initialisation 
73     return; 
74   }
75   
76   _firstUpdate = true;
77   _inited = true;
78   _dtRemainder = 0.0;
79   _adjustWarpOnUnfreeze = false;
80   
81   _maxDtPerFrame = fgGetNode("/sim/max-simtime-per-frame", true);
82   _clockFreeze = fgGetNode("/sim/freeze/clock", true);
83   _timeOverride = fgGetNode("/sim/time/cur-time-override", true);
84   _warp = fgGetNode("/sim/time/warp", true);
85   _warp->addChangeListener(this);
86   
87   _warpDelta = fgGetNode("/sim/time/warp-delta", true);
88   
89   _longitudeDeg = fgGetNode("/position/longitude-deg", true);
90   _latitudeDeg = fgGetNode("/position/latitude-deg", true);
91   
92   SGPath zone(globals->get_fg_root());
93   zone.append("Timezone");
94   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
95   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
96   
97   _impl = new SGTime(lon, lat, zone.str(), _timeOverride->getLongValue());
98   
99   _warpDelta->setIntValue(0);
100   
101   globals->get_event_mgr()->addTask("updateLocalTime", this,
102                             &TimeManager::updateLocalTime, 30*60 );
103   updateLocalTime();
104   
105   _impl->update(lon, lat, _timeOverride->getLongValue(),
106                _warp->getIntValue());
107   globals->set_time_params(_impl);
108     
109   // frame-rate / worst-case latency / update-rate counters
110   _frameRate = fgGetNode("/sim/frame-rate", true);
111   _frameLatency = fgGetNode("/sim/frame-latency-max-ms", true);
112   _lastFrameTime = 0;
113   _frameLatencyMax = 0.0;
114   _frameCount = 0;
115 }
116
117 void TimeManager::postinit()
118 {
119   initTimeOffset();
120 }
121
122 void TimeManager::reinit()
123 {
124   shutdown();
125   init();
126   postinit();
127 }
128
129 void TimeManager::shutdown()
130 {
131   _warp->removeChangeListener(this);
132   
133   globals->set_time_params(NULL);
134   delete _impl;
135   _impl = NULL;
136   _inited = false;
137   globals->get_event_mgr()->removeTask("updateLocalTime");
138 }
139
140 void TimeManager::valueChanged(SGPropertyNode* aProp)
141 {
142   if (aProp == _warp) {
143     if (_clockFreeze->getBoolValue()) {
144     // if the warp is changed manually while frozen, don't modify it when
145     // un-freezing - the user wants to unfreeze with exactly the warp
146     // they specified.
147       _adjustWarpOnUnfreeze = false;
148     }
149     
150     double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
151     double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
152     _impl->update(lon, lat,
153                    _timeOverride->getLongValue(),
154                    _warp->getIntValue());
155   }
156 }
157
158 void TimeManager::computeTimeDeltas(double& simDt, double& realDt)
159 {
160   // Update the elapsed time.
161   if (_firstUpdate) {
162     _lastStamp.stamp();
163     _firstUpdate = false;
164     _lastClockFreeze = _clockFreeze->getBoolValue();
165   }
166
167   bool scenery_loaded = fgGetBool("sim/sceneryloaded");
168   bool wait_for_scenery = !(scenery_loaded || fgGetBool("sim/sceneryloaded-override"));
169   
170   if (!wait_for_scenery) {
171     throttleUpdateRate();
172   }
173   else
174   {
175       // suppress framerate while initial scenery isn't loaded yet (splash screen still active) 
176       _lastFrameTime=0;
177       _frameCount = 0;
178   }
179   
180   SGTimeStamp currentStamp;
181   currentStamp.stamp();
182   double dt = (currentStamp - _lastStamp).toSecs();
183   if (dt > _frameLatencyMax)
184       _frameLatencyMax = dt;
185
186 // Limit the time we need to spend in simulation loops
187 // That means, if the /sim/max-simtime-per-frame value is strictly positive
188 // you can limit the maximum amount of time you will do simulations for
189 // one frame to display. The cpu time spent in simulations code is roughly
190 // at least O(real_delta_time_sec). If this is (due to running debug
191 // builds or valgrind or something different blowing up execution times)
192 // larger than the real time you will no longer get any response
193 // from flightgear. This limits that effect. Just set to property from
194 // your .fgfsrc or commandline ...
195   double dtMax = _maxDtPerFrame->getDoubleValue();
196   if (0 < dtMax && dtMax < dt) {
197     dt = dtMax;
198   }
199   
200   int model_hz = fgGetInt("/sim/model-hz");
201   
202   SGSubsystemGroup* fdmGroup = 
203     globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
204   fdmGroup->set_fixed_update_time(1.0 / model_hz);
205   
206 // round the real time down to a multiple of 1/model-hz.
207 // this way all systems are updated the _same_ amount of dt.
208   dt += _dtRemainder;
209   int multiLoop = long(floor(dt * model_hz));
210   multiLoop = SGMisc<long>::max(0, multiLoop);
211   _dtRemainder = dt - double(multiLoop)/double(model_hz);
212   dt = double(multiLoop)/double(model_hz);
213
214   realDt = dt;
215   if (_clockFreeze->getBoolValue() || wait_for_scenery) {
216     simDt = 0;
217   } else {
218     simDt = dt;
219   }
220   
221   _lastStamp = currentStamp;
222   globals->inc_sim_time_sec(simDt);
223
224 // These are useful, especially for Nasal scripts.
225   fgSetDouble("/sim/time/delta-realtime-sec", realDt);
226   fgSetDouble("/sim/time/delta-sec", simDt);
227 }
228
229 void TimeManager::update(double dt)
230 {
231   bool freeze = _clockFreeze->getBoolValue();
232   time_t now = time(NULL);
233   
234   if (freeze) {
235     // clock freeze requested
236     if (_timeOverride->getLongValue() == 0) {
237       _timeOverride->setLongValue(now);
238       _adjustWarpOnUnfreeze = true;
239     }
240   } else {
241     // no clock freeze requested
242     if (_lastClockFreeze) {
243       if (_adjustWarpOnUnfreeze) {
244       // clock just unfroze, let's set warp as the difference
245       // between frozen time and current time so we don't get a
246       // time jump (and corresponding sky object and lighting
247       // jump.)
248         int adjust = _timeOverride->getLongValue() - now;
249         SG_LOG(SG_GENERAL, SG_INFO, "adjusting on un-freeze:" << adjust);
250         _warp->setIntValue(_warp->getIntValue() + adjust);
251       }
252       _timeOverride->setLongValue(0);
253     }
254     
255     int warpDelta = _warpDelta->getIntValue();
256     if (warpDelta != 0) {
257       _warp->setIntValue(_warp->getIntValue() + warpDelta);
258     }
259   }
260
261   _lastClockFreeze = freeze;
262   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
263   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
264   _impl->update(lon, lat,
265                _timeOverride->getLongValue(),
266                _warp->getIntValue());
267
268   computeFrameRate();
269 }
270
271 void TimeManager::computeFrameRate()
272 {
273   // Calculate frame rate average
274   if ((_impl->get_cur_time() != _lastFrameTime)) {
275     _frameRate->setIntValue(_frameCount);
276     _frameLatency->setDoubleValue(_frameLatencyMax*1000);
277     _frameCount = 0;
278     _frameLatencyMax = 0.0;
279   }
280   
281   _lastFrameTime = _impl->get_cur_time();
282   ++_frameCount;
283 }
284
285 void TimeManager::throttleUpdateRate()
286 {
287   double throttle_hz = fgGetDouble("/sim/frame-rate-throttle-hz", 0.0);
288   SGTimeStamp currentStamp;
289   
290   // common case, no throttle requested
291   if (throttle_hz <= 0.0) {
292     return; // no-op
293   }
294   
295   double frame_us = 1000000.0 / throttle_hz;
296 #define FG_SLEEP_BASED_TIMING 1
297 #if defined(FG_SLEEP_BASED_TIMING)
298   // sleep based timing loop.
299   //
300   // Calling sleep, even usleep() on linux is less accurate than
301   // we like, but it does free up the cpu for other tasks during
302   // the sleep so it is desirable.  Because of the way sleep()
303   // is implemented in consumer operating systems like windows
304   // and linux, you almost always sleep a little longer than the
305   // requested amount.
306   //
307   // To combat the problem of sleeping too long, we calculate the
308   // desired wait time and shorten it by 2000us (2ms) to avoid
309   // [hopefully] over-sleep'ing.  The 2ms value was arrived at
310   // via experimentation.  We follow this up at the end with a
311   // simple busy-wait loop to get the final pause timing exactly
312   // right.
313   //
314   // Assuming we don't oversleep by more than 2000us, this
315   // should be a reasonable compromise between sleep based
316   // waiting, and busy waiting.
317
318   // sleep() will always overshoot by a bit so undersleep by
319   // 2000us in the hopes of never oversleeping.
320   frame_us -= 2000.0;
321   if ( frame_us < 0.0 ) {
322       frame_us = 0.0;
323   }
324   
325   currentStamp.stamp();
326
327   double elapsed_us = (currentStamp - _lastStamp).toUSecs();
328   if ( elapsed_us < frame_us ) {
329     double requested_us = frame_us - elapsed_us;
330  
331 #ifdef _WIN32
332     Sleep ((int)(requested_us / 1000.0)) ;
333 #else
334     usleep(requested_us) ;
335 #endif
336   }
337 #endif
338
339   // busy wait timing loop.
340   //
341   // This yields the most accurate timing.  If the previous
342   // ulMilliSecondSleep() call is omitted this will peg the cpu
343   // (which is just fine if FG is the only app you care about.)
344   currentStamp.stamp();
345   SGTimeStamp next_time_stamp = _lastStamp;
346   next_time_stamp += SGTimeStamp::fromSec(1e-6*frame_us);
347   while ( currentStamp < next_time_stamp ) {
348       currentStamp.stamp();
349   }
350 }
351
352 // periodic time updater wrapper
353 void TimeManager::updateLocalTime() 
354 {
355   SGPath zone(globals->get_fg_root());
356   zone.append("Timezone");
357   
358   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
359   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
360   
361   SG_LOG(SG_GENERAL, SG_INFO, "updateLocal(" << lon << ", " << lat << ", " << zone.str() << ")");
362   _impl->updateLocal(lon, lat, zone.str());
363 }
364
365 void TimeManager::initTimeOffset()
366 {
367
368   int offset = fgGetInt("/sim/startup/time-offset");
369   string offset_type = fgGetString("/sim/startup/time-offset-type");
370   setTimeOffset(offset_type, offset);
371 }
372
373 void TimeManager::setTimeOffset(const std::string& offset_type, int offset)
374 {
375   // Handle potential user specified time offsets
376   int orig_warp = _warp->getIntValue();
377   time_t cur_time = _impl->get_cur_time();
378   time_t currGMT = sgTimeGetGMT( gmtime(&cur_time) );
379   time_t systemLocalTime = sgTimeGetGMT( localtime(&cur_time) );
380   time_t aircraftLocalTime = 
381       sgTimeGetGMT( fgLocaltime(&cur_time, _impl->get_zonename() ) );
382     
383   // Okay, we now have several possible scenarios
384   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
385   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
386   int warp = 0;
387   
388   if ( offset_type == "real" ) {
389       warp = 0;
390   } else if ( offset_type == "dawn" ) {
391       warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 90.0, true ); 
392   } else if ( offset_type == "morning" ) {
393      warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 75.0, true ); 
394   } else if ( offset_type == "noon" ) {
395      warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 0.0, true ); 
396   } else if ( offset_type == "afternoon" ) {
397     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 60.0, false );  
398   } else if ( offset_type == "dusk" ) {
399     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 90.0, false );
400   } else if ( offset_type == "evening" ) {
401     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 100.0, false );
402   } else if ( offset_type == "midnight" ) {
403     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 180.0, false );
404   } else if ( offset_type == "system-offset" ) {
405     warp = offset;
406     orig_warp = 0;
407   } else if ( offset_type == "gmt-offset" ) {
408     warp = offset - (currGMT - systemLocalTime);
409     orig_warp = 0;
410   } else if ( offset_type == "latitude-offset" ) {
411     warp = offset - (aircraftLocalTime - systemLocalTime);
412     orig_warp = 0;
413   } else if ( offset_type == "system" ) {
414     warp = offset - (systemLocalTime - currGMT) - cur_time;
415   } else if ( offset_type == "gmt" ) {
416       warp = offset - cur_time;
417   } else if ( offset_type == "latitude" ) {
418       warp = offset - (aircraftLocalTime - currGMT)- cur_time; 
419   } else {
420     SG_LOG( SG_GENERAL, SG_ALERT,
421           "TimeManager::setTimeOffset: unsupported offset: " << offset_type );
422      warp = 0;
423   }
424   
425   _warp->setIntValue( orig_warp + warp );
426
427   SG_LOG( SG_GENERAL, SG_INFO, "After TimeManager::setTimeOffset(): warp = " 
428             << _warp->getIntValue() );
429 }