]> git.mxchange.org Git - flightgear.git/blob - src/Time/TimeManager.cxx
Clean-up cmake (linker) dependencies.
[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 #include <simgear/timing/sg_time.hxx>
28 #include <simgear/structure/event_mgr.hxx>
29 #include <simgear/misc/sg_path.hxx>
30 #include <simgear/timing/lowleveltime.h>
31 #include <simgear/structure/commands.hxx>
32
33 #include <Main/fg_props.hxx>
34 #include <Main/globals.hxx>
35 #include <Time/sunsolver.hxx>
36
37 using std::string;
38
39 static bool do_timeofday (const SGPropertyNode * arg)
40 {
41     const string &offset_type = arg->getStringValue("timeofday", "noon");
42     int offset = arg->getIntValue("offset", 0);
43     TimeManager* self = (TimeManager*) globals->get_subsystem("time");
44     if (offset_type == "real") {
45     // without this, setting 'real' time is a no-op, since the current
46     // wrap value (orig_warp) is retained in setTimeOffset. Ick.
47         fgSetInt("/sim/time/warp", 0);
48     }
49     
50     self->setTimeOffset(offset_type, offset);
51     return true;
52 }
53
54 TimeManager::TimeManager() :
55   _inited(false),
56   _impl(NULL),
57   _sceneryLoaded("sim/sceneryloaded"),
58   _sceneryLoadOverride("sim/sceneryloaded-override"),
59   _modelHz("sim/model-hz"),
60   _timeDelta("sim/time/delta-realtime-sec"),
61   _simTimeDelta("sim/time/delta-sec")
62 {
63   SGCommandMgr::instance()->addCommand("timeofday", do_timeofday);
64 }
65
66 void TimeManager::init()
67 {
68   if (_inited) {
69     // time manager has to be initialised early, so needs to be defensive
70     // about multiple initialisation 
71     return; 
72   }
73   
74   _firstUpdate = true;
75   _inited = true;
76   _dtRemainder = 0.0;
77   _adjustWarpOnUnfreeze = false;
78   
79   _maxDtPerFrame = fgGetNode("/sim/max-simtime-per-frame", true);
80   _clockFreeze = fgGetNode("/sim/freeze/clock", true);
81   _timeOverride = fgGetNode("/sim/time/cur-time-override", true);
82   _warp = fgGetNode("/sim/time/warp", true);
83   _warp->addChangeListener(this);
84   
85   _warpDelta = fgGetNode("/sim/time/warp-delta", true);
86   
87   _longitudeDeg = fgGetNode("/position/longitude-deg", true);
88   _latitudeDeg = fgGetNode("/position/latitude-deg", true);
89   
90   SGPath zone(globals->get_fg_root());
91   zone.append("Timezone");
92   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
93   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
94   
95   _impl = new SGTime(lon, lat, zone.str(), _timeOverride->getLongValue());
96   
97   _warpDelta->setIntValue(0);
98   
99   globals->get_event_mgr()->addTask("updateLocalTime", this,
100                             &TimeManager::updateLocalTime, 30*60 );
101   updateLocalTime();
102   
103   _impl->update(lon, lat, _timeOverride->getLongValue(),
104                _warp->getIntValue());
105   globals->set_time_params(_impl);
106     
107   // frame-rate / worst-case latency / update-rate counters
108   _frameRate = fgGetNode("/sim/frame-rate", true);
109   _frameLatency = fgGetNode("/sim/frame-latency-max-ms", true);
110   _frameRateWorst = fgGetNode("/sim/frame-rate-worst", true);
111   _lastFrameTime = 0;
112   _frameLatencyMax = 0.0;
113   _frameCount = 0;
114 }
115
116 void TimeManager::postinit()
117 {
118   initTimeOffset();
119 }
120
121 void TimeManager::reinit()
122 {
123   shutdown();
124   init();
125   postinit();
126 }
127
128 void TimeManager::shutdown()
129 {
130   _warp->removeChangeListener(this);
131   
132   globals->set_time_params(NULL);
133   delete _impl;
134   _impl = NULL;
135   _inited = false;
136   globals->get_event_mgr()->removeTask("updateLocalTime");
137 }
138
139 void TimeManager::valueChanged(SGPropertyNode* aProp)
140 {
141   if (aProp == _warp) {
142     if (_clockFreeze->getBoolValue()) {
143     // if the warp is changed manually while frozen, don't modify it when
144     // un-freezing - the user wants to unfreeze with exactly the warp
145     // they specified.
146       _adjustWarpOnUnfreeze = false;
147     }
148     
149     double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
150     double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
151     _impl->update(lon, lat,
152                    _timeOverride->getLongValue(),
153                    _warp->getIntValue());
154   }
155 }
156
157 void TimeManager::computeTimeDeltas(double& simDt, double& realDt)
158 {
159   // Update the elapsed time.
160   if (_firstUpdate) {
161     _lastStamp.stamp();
162     _firstUpdate = false;
163     _lastClockFreeze = _clockFreeze->getBoolValue();
164   }
165
166   bool wait_for_scenery = !(_sceneryLoaded || _sceneryLoadOverride);
167   if (!wait_for_scenery) {
168     throttleUpdateRate();
169   }
170   else
171   {
172       // suppress framerate while initial scenery isn't loaded yet (splash screen still active) 
173       _lastFrameTime=0;
174       _frameCount = 0;
175   }
176   
177   SGTimeStamp currentStamp;
178   currentStamp.stamp();
179   double dt = (currentStamp - _lastStamp).toSecs();
180   if (dt > _frameLatencyMax)
181       _frameLatencyMax = dt;
182
183 // Limit the time we need to spend in simulation loops
184 // That means, if the /sim/max-simtime-per-frame value is strictly positive
185 // you can limit the maximum amount of time you will do simulations for
186 // one frame to display. The cpu time spent in simulations code is roughly
187 // at least O(real_delta_time_sec). If this is (due to running debug
188 // builds or valgrind or something different blowing up execution times)
189 // larger than the real time you will no longer get any response
190 // from flightgear. This limits that effect. Just set to property from
191 // your .fgfsrc or commandline ...
192   double dtMax = _maxDtPerFrame->getDoubleValue();
193   if (0 < dtMax && dtMax < dt) {
194     dt = dtMax;
195   }
196     
197   SGSubsystemGroup* fdmGroup = 
198     globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
199   fdmGroup->set_fixed_update_time(1.0 / _modelHz);
200   
201 // round the real time down to a multiple of 1/model-hz.
202 // this way all systems are updated the _same_ amount of dt.
203   dt += _dtRemainder;
204   int multiLoop = long(floor(dt * _modelHz));
205   multiLoop = SGMisc<long>::max(0, multiLoop);
206   _dtRemainder = dt - double(multiLoop)/double(_modelHz);
207   dt = double(multiLoop)/double(_modelHz);
208
209   realDt = dt;
210   if (_clockFreeze->getBoolValue() || wait_for_scenery) {
211     simDt = 0;
212   } else {
213     simDt = dt;
214   }
215   
216   _lastStamp = currentStamp;
217   globals->inc_sim_time_sec(simDt);
218
219 // These are useful, especially for Nasal scripts.
220   _timeDelta = realDt;
221   _simTimeDelta = simDt;
222 }
223
224 void TimeManager::update(double dt)
225 {
226   bool freeze = _clockFreeze->getBoolValue();
227   time_t now = time(NULL);
228   
229   if (freeze) {
230     // clock freeze requested
231     if (_timeOverride->getLongValue() == 0) {
232       _timeOverride->setLongValue(now);
233       _adjustWarpOnUnfreeze = true;
234     }
235   } else {
236     // no clock freeze requested
237     if (_lastClockFreeze) {
238       if (_adjustWarpOnUnfreeze) {
239       // clock just unfroze, let's set warp as the difference
240       // between frozen time and current time so we don't get a
241       // time jump (and corresponding sky object and lighting
242       // jump.)
243         int adjust = _timeOverride->getLongValue() - now;
244         SG_LOG(SG_GENERAL, SG_INFO, "adjusting on un-freeze:" << adjust);
245         _warp->setIntValue(_warp->getIntValue() + adjust);
246       }
247       _timeOverride->setLongValue(0);
248     }
249     
250     int warpDelta = _warpDelta->getIntValue();
251     if (warpDelta != 0) {
252       _warp->setIntValue(_warp->getIntValue() + warpDelta);
253     }
254   }
255
256   _lastClockFreeze = freeze;
257   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
258   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
259   _impl->update(lon, lat,
260                _timeOverride->getLongValue(),
261                _warp->getIntValue());
262
263   computeFrameRate();
264 }
265
266 void TimeManager::computeFrameRate()
267 {
268   // Calculate frame rate average
269   if ((_impl->get_cur_time() != _lastFrameTime)) {
270     _frameRate->setIntValue(_frameCount);
271     _frameLatency->setDoubleValue(_frameLatencyMax*1000);
272     if (_frameLatencyMax>0)
273         _frameRateWorst->setIntValue(1/_frameLatencyMax);
274     _frameCount = 0;
275     _frameLatencyMax = 0.0;
276   }
277   
278   _lastFrameTime = _impl->get_cur_time();
279   ++_frameCount;
280 }
281
282 void TimeManager::throttleUpdateRate()
283 {
284   // common case, no throttle requested
285   double throttle_hz = fgGetDouble("/sim/frame-rate-throttle-hz", 0.0);
286   if (throttle_hz <= 0)
287     return; // no-op
288
289   // sleep for exactly 1/hz seconds relative to the past valid timestamp
290   SGTimeStamp::sleepUntil(_lastStamp + SGTimeStamp::fromSec(1/throttle_hz));
291 }
292
293 // periodic time updater wrapper
294 void TimeManager::updateLocalTime() 
295 {
296   SGPath zone(globals->get_fg_root());
297   zone.append("Timezone");
298   
299   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
300   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
301   
302   SG_LOG(SG_GENERAL, SG_INFO, "updateLocal(" << lon << ", " << lat << ", " << zone.str() << ")");
303   _impl->updateLocal(lon, lat, zone.str());
304 }
305
306 void TimeManager::initTimeOffset()
307 {
308
309   long int offset = fgGetLong("/sim/startup/time-offset");
310   string offset_type = fgGetString("/sim/startup/time-offset-type");
311   setTimeOffset(offset_type, offset);
312 }
313
314 void TimeManager::setTimeOffset(const std::string& offset_type, long int offset)
315 {
316   // Handle potential user specified time offsets
317   int orig_warp = _warp->getIntValue();
318   time_t cur_time = _impl->get_cur_time();
319   time_t currGMT = sgTimeGetGMT( gmtime(&cur_time) );
320   time_t systemLocalTime = sgTimeGetGMT( localtime(&cur_time) );
321   time_t aircraftLocalTime = 
322       sgTimeGetGMT( fgLocaltime(&cur_time, _impl->get_zonename() ) );
323     
324   // Okay, we now have several possible scenarios
325   double lon = _longitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
326   double lat = _latitudeDeg->getDoubleValue() * SG_DEGREES_TO_RADIANS;
327   int warp = 0;
328   
329   if ( offset_type == "real" ) {
330       warp = 0;
331   } else if ( offset_type == "dawn" ) {
332       warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 90.0, true ); 
333   } else if ( offset_type == "morning" ) {
334      warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 75.0, true ); 
335   } else if ( offset_type == "noon" ) {
336      warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 0.0, true ); 
337   } else if ( offset_type == "afternoon" ) {
338     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 75.0, false );  
339   } else if ( offset_type == "dusk" ) {
340     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 90.0, false );
341   } else if ( offset_type == "evening" ) {
342     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 100.0, false );
343   } else if ( offset_type == "midnight" ) {
344     warp = fgTimeSecondsUntilSunAngle( cur_time, lon, lat, 180.0, false );
345   } else if ( offset_type == "system-offset" ) {
346     warp = offset;
347     orig_warp = 0;
348   } else if ( offset_type == "gmt-offset" ) {
349     warp = offset - (currGMT - systemLocalTime);
350     orig_warp = 0;
351   } else if ( offset_type == "latitude-offset" ) {
352     warp = offset - (aircraftLocalTime - systemLocalTime);
353     orig_warp = 0;
354   } else if ( offset_type == "system" ) {
355     warp = offset - (systemLocalTime - currGMT) - cur_time;
356   } else if ( offset_type == "gmt" ) {
357       warp = offset - cur_time;
358   } else if ( offset_type == "latitude" ) {
359       warp = offset - (aircraftLocalTime - currGMT)- cur_time; 
360   } else {
361     SG_LOG( SG_GENERAL, SG_ALERT,
362           "TimeManager::setTimeOffset: unsupported offset: " << offset_type );
363      warp = 0;
364   }
365   
366   _warp->setIntValue( orig_warp + warp );
367
368   SG_LOG( SG_GENERAL, SG_INFO, "After TimeManager::setTimeOffset(): warp = " 
369             << _warp->getIntValue() );
370 }