FlightGear next
TimeManager.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: TimeManager.cxx
3 * SPDX-FileComment: simulation-wide time management
4 * SPDX-FileCopyrightText: Written by James Turner, started July 2010.
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#ifdef HAVE_CONFIG_H
9# include "config.h"
10#endif
11
12#include "TimeManager.hxx"
13
14#include <simgear/misc/sg_path.hxx>
15#include <simgear/timing/lowleveltime.h>
16#include <simgear/structure/commands.hxx>
17#include <simgear/timing/sg_time.hxx>
18#include <simgear/math/SGMath.hxx>
19
20#include <Main/fg_props.hxx>
21#include <Main/globals.hxx>
22#include <Time/bodysolver.hxx>
23
24#include <algorithm>
25#include <chrono>
26#include <thread>
27
28
29static bool do_timeofday (const SGPropertyNode * arg, SGPropertyNode * root)
30{
31 const std::string &offset_type = arg->getStringValue("timeofday", "noon");
32 int offset = arg->getIntValue("offset", 0);
33 auto self = globals->get_subsystem<TimeManager>();
34 if (offset_type == "real") {
35 // without this, setting 'real' time is a no-op, since the current
36 // wrap value (orig_warp) is retained in setTimeOffset. Ick.
37 fgSetInt("/sim/time/warp", 0);
38 }
39
40 self->setTimeOffset(offset_type, offset);
41 return true;
42}
43
45 _inited(false),
46 _impl(NULL)
47{
48 globals->get_commands()->addCommand("timeofday", do_timeofday);
49}
50
52{
53 globals->get_commands()->removeCommand("timeofday");
54}
55
57{
58 if (_inited) {
59 // time manager has to be initialised early, so needs to be defensive
60 // about multiple initialisation
61 return;
62 }
63
64 _firstUpdate = true;
65 _inited = true;
66 _dtRemainder = 0.0;
67 _mpProtocolClock = _steadyClock = 0.0;
68 _adjustWarpOnUnfreeze = false;
69
70 _maxDtPerFrame = fgGetNode("/sim/max-simtime-per-frame", true);
71 _clockFreeze = fgGetNode("/sim/freeze/clock", true);
72 _timeOverride = fgGetNode("/sim/time/cur-time-override", true);
73 _warp = fgGetNode("/sim/time/warp", true);
74 _warp->addChangeListener(this);
75 _maxFrameRate = fgGetNode("/sim/frame-rate-throttle-hz", true);
76 _localTimeStringNode = fgGetNode("/sim/time/local-time-string", true);
77 _localTimeZoneNode = fgGetNode("/sim/time/local-timezone", true);
78 _warpDelta = fgGetNode("/sim/time/warp-delta", true);
79 _frameNumber = fgGetNode("/sim/frame-number", true);
80 _simFixedDt = fgGetNode("/sim/time/fixed-dt", true);
81
82 SGPath zone(globals->get_fg_root());
83 zone.append("Timezone");
84
85 _impl = new SGTime(globals->get_aircraft_position(), zone, _timeOverride->getLongValue());
86
87 _warpDelta->setDoubleValue(0.0);
88 updateLocalTime();
89
90 _impl->update(globals->get_aircraft_position(), _timeOverride->getLongValue(),
91 _warp->getIntValue());
92 globals->set_time_params(_impl);
93
94 // frame-rate / worst-case latency / update-rate counters
95 _frameRate = fgGetNode("/sim/frame-rate", true);
96 _frameLatency = fgGetNode("/sim/frame-latency-max-ms", true);
97 _frameRateWorst = fgGetNode("/sim/frame-rate-worst", true);
98 _lastFrameTime = 0;
99 _frameLatencyMax = 0.0;
100 _frameCount = 0;
101
102 _sceneryLoaded = fgGetNode("sim/sceneryloaded", true);
103 _modelHz = fgGetNode("sim/model-hz", true);
104 _timeDelta = fgGetNode("sim/time/delta-realtime-sec", true);
105 _simTimeDelta = fgGetNode("sim/time/delta-sec", true);
106 _mpProtocolClockNode = fgGetNode("sim/time/mp-clock-sec", true);
107 _steadyClockNode = fgGetNode("sim/time/steady-clock-sec", true);
108 _frameTimeOffsetNode = fgGetNode("sim/time/frame-time-offset-ms", true);
109 _dtRemainderNode = fgGetNode("sim/time/dt-remainder-sec", true);
110 _mpClockOffset = fgGetNode("sim/time/mp-clock-offset-sec", true);
111 _steadyClockDrift = fgGetNode("sim/time/steady-clock-drift-ms", true);
112 _computeDrift = fgGetNode("sim/time/compute-clock-drift", true);
113 _frameWait = fgGetNode("sim/time/frame-wait-ms", true);
114 _simTimeFactor = fgGetNode("/sim/speed-up", true);
115 // use pre-set value but ensure we get a sane default
116 if (!_simTimeDelta->hasValue()) {
117 _simTimeFactor->setDoubleValue(1.0);
118 }
119 if (!_mpClockOffset->hasValue()) {
120 _mpClockOffset->setDoubleValue(0.0);
121 }
122 _computeDrift->setBoolValue(true);
123
124 _simpleTimeEnabledPrev = false;
125 _simpleTimeEnabled = fgGetNode("/sim/time/simple-time/enabled", true);
126 _simpleTimeUtc = fgGetNode("/sim/time/simple-time/utc", true);
127 _simpleTimeFdm = fgGetNode("/sim/time/simple-time/fdm", true);
128 _simple_time_utc = 0;
129 _simple_time_fdm = 0;
130}
131
133{
134 _maxDtPerFrame.clear();
135 _clockFreeze.clear();
136 _timeOverride.clear();
137 _warp.clear();
138 _warpDelta.clear();
139 _frameRate.clear();
140 _frameLatency.clear();
141 _frameRateWorst.clear();
142 _frameWait.clear();
143 _maxFrameRate.clear();
144
145 _sceneryLoaded.clear();
146 _modelHz.clear();
147 _timeDelta.clear();
148 _simTimeDelta.clear();
149 _mpProtocolClockNode.clear();
150 _steadyClockNode.clear();
151 _frameTimeOffsetNode.clear();
152 _dtRemainderNode.clear();
153 _mpClockOffset.clear();
154 _steadyClockDrift.clear();
155 _computeDrift.clear();
156 _simTimeFactor.clear();
157}
158
160{
161 initTimeOffset();
162}
163
165{
166 shutdown();
167 init();
168 postinit();
169}
170
172{
173 _warp->removeChangeListener(this);
174
175 globals->set_time_params(NULL);
176 delete _impl;
177 _impl = NULL;
178 _inited = false;
179}
180
181void TimeManager::valueChanged(SGPropertyNode* aProp)
182{
183 if (aProp == _warp) {
184 if (_clockFreeze->getBoolValue()) {
185 // if the warp is changed manually while frozen, don't modify it when
186 // un-freezing - the user wants to unfreeze with exactly the warp
187 // they specified.
188 _adjustWarpOnUnfreeze = false;
189 }
190
191 _impl->update(globals->get_aircraft_position(),
192 _timeOverride->getLongValue(),
193 _warp->getIntValue());
194 }
195}
196
197// simple-time mode requires UTC time.
198//
199// SGTimeStamp() doesn't return UTC time on some systems, e.g. Linux with
200// _POSIX_TIMERS > 0 uses _POSIX_MONOTONIC_CLOCK if available.
201//
202// So we define our own time function here.
203//
204static double TimeUTC()
205{
206 auto t = std::chrono::system_clock::now().time_since_epoch();
207 typedef std::chrono::duration<double, std::ratio<1, 1>> duration_hz_fp;
208 auto ret = std::chrono::duration_cast<duration_hz_fp>(t);
209 return ret.count();
210}
211
212void TimeManager::computeTimeDeltasSimple(double& simDt, double& realDt)
213{
214 double t;
215 double fixed_dt = _simFixedDt->getDoubleValue();
216 static double fixed_dt_prev = 0.0;
217 if (fixed_dt)
218 {
219 // Always increase time by fixed amount, regardless of elapsed
220 // time. E.g. this can be used to generate high-quality videos.
221 t = _simple_time_fdm + fixed_dt;
222 fixed_dt_prev = fixed_dt;
223 }
224 else
225 {
226 t = TimeUTC();
227
228 if (fixed_dt_prev)
229 {
230 // We are changing from fixed-dt mode to normal mode; avoid bogus
231 // sleep to match _maxFrameRate, otherwise we can end up pausing
232 // for a long time.
233 _simple_time_fdm = _simple_time_utc = t - fixed_dt_prev;
234 fixed_dt_prev = 0.0;
235 }
236 }
237 double modelHz = _modelHz->getDoubleValue();
238 bool scenery_loaded = _sceneryLoaded->getBoolValue();
239
240 if (_firstUpdate) {
241 _firstUpdate = false;
242 _simple_time_utc = t;
243 _simple_time_fdm = t;
244 SGSubsystemGroup* fdmGroup = globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
245 fdmGroup->set_fixed_update_time(1.0 / modelHz);
246 }
247
248 // Sleep if necessary to respect _maxFrameRate. It's simpler to do this
249 // inline instead of calling throttleUpdateRate().
250 //
251 double sleep_time = 0;
252 if (scenery_loaded && !fixed_dt) {
253 double max_frame_rate = _maxFrameRate->getDoubleValue();
254 if (max_frame_rate != 0) {
255 double delay_end = _simple_time_utc + 1.0/max_frame_rate;
256 if (delay_end > t) {
257 sleep_time = delay_end - t;
258 std::this_thread::sleep_for(std::chrono::milliseconds((int) (sleep_time * 1000)));
259 t = delay_end;
260 }
261 }
262 }
263 else {
264 // suppress framerate while initial scenery isn't loaded yet (splash screen still active)
265 _lastFrameTime=0;
266 _frameCount = 0;
267 }
268
269 // Increment <_simple_time_fdm> by a multiple of the FDM interval, such
270 // that it is as close as possible, but not greater than, the current UTC
271 // time <t>.
272 //
273 double dt_fdm = floor( (t - _simple_time_fdm) * modelHz) / modelHz;
274 _simple_time_fdm += dt_fdm;
275 _frameLatencyMax = std::max(_frameLatencyMax, t - _simple_time_utc);
276 _simple_time_utc = t;
277
278 _simpleTimeUtc->setDoubleValue(_simple_time_utc);
279 _simpleTimeFdm->setDoubleValue(_simple_time_fdm);
280
281 // simDt defaults to dt_fdm, but is affected by whether we are paused or
282 // running the FDM at faster/slowe than normal.
283 if (_clockFreeze->getBoolValue() || !scenery_loaded) {
284 simDt = 0;
285 }
286 else {
287 simDt = dt_fdm * _simTimeFactor->getDoubleValue();
288 }
289 realDt = dt_fdm;
290 globals->inc_sim_time_sec(simDt);
291
292 _mpProtocolClock = _simple_time_fdm;
293 _mpProtocolClockNode->setDoubleValue(_mpProtocolClock);
294
295 // Not sure anyone calls getSteadyClockSec()?
296 _steadyClock = _simple_time_fdm;
297 _steadyClockNode->setDoubleValue(_steadyClock);
298
299 // These are used by Nasal scripts, e.g. when interpolating property
300 // values.
301 _timeDelta->setDoubleValue(realDt);
302 _simTimeDelta->setDoubleValue(simDt);
303
304 SG_LOG(SG_GENERAL, SG_DEBUG, ""
305 << std::setprecision(5)
306 << std::fixed
307 << std::setw(16)
308 << " " << ((simDt >= 1.0) ? "*" : " ")
309 << " simDt=" << simDt
310 << " realDt=" << realDt
311 << " sleep_time=" << sleep_time
312 << " _simple_time_utc=" << _simple_time_utc
313 << " _simple_time_fdm=" << _simple_time_fdm
314 << " utc-fdm=" << (_simple_time_utc - _simple_time_fdm)
315 << " _steadyClock=" << _steadyClock
316 << " _mpProtocolClock=" << _mpProtocolClock
317 );
318}
319
320void TimeManager::computeTimeDeltas(double& simDt, double& realDt)
321{
322 bool simple_time = _simpleTimeEnabled->getBoolValue();
323 if (simple_time != _simpleTimeEnabledPrev) {
324 _simpleTimeEnabledPrev = simple_time;
325 _firstUpdate = true;
326 }
327 if (simple_time) {
328 computeTimeDeltasSimple(simDt, realDt);
329 return;
330 }
331
332 const double modelHz = _modelHz->getDoubleValue();
333
334 // Update the elapsed time.
335 if (_firstUpdate) {
336 _lastStamp.stamp();
337
338 // Initialise the mp protocol / steady clock with the system clock.
339 // later, the clock follows steps of 1/modelHz (120 by default),
340 // so the MP clock remains aligned to these boundaries
341
342 _systemStamp.systemClockHoursAndMinutes();
343 const double systemStamp = _systemStamp.toSecs();
344 _steadyClock = floor(systemStamp * modelHz) / modelHz;
345
346 // add offset if defined
347 const double frameOffsetMsec = _frameTimeOffsetNode->getDoubleValue();
348 _steadyClock += frameOffsetMsec / 1000.0;
349
350 // initialize the remainder with offset from the system clock
351 _dtRemainder = systemStamp - _steadyClock;
352
353 _firstUpdate = false;
354 _lastClockFreeze = _clockFreeze->getBoolValue();
355 }
356
357 bool wait_for_scenery = !_sceneryLoaded->getBoolValue();
358 if (!wait_for_scenery) {
359 throttleUpdateRate();
360 } else {
361 // suppress framerate while initial scenery isn't loaded yet (splash screen still active)
362 _lastFrameTime=0;
363 _frameCount = 0;
364 }
365
366 SGTimeStamp currentStamp;
367 currentStamp.stamp();
368
369 // if asked, we compute the drift between the steady clock and the system clock
370
371 if (_computeDrift->getBoolValue()) {
372 _systemStamp.systemClockHoursAndMinutes();
373 double clockdrift = _steadyClock + (currentStamp - _lastStamp).toSecs()
374 + _dtRemainder - _systemStamp.toSecs();
375 _steadyClockDrift->setDoubleValue(clockdrift * 1000.0);
376 _computeDrift->setBoolValue(false);
377 }
378
379 // this dt will be clamped by the max sim time by frame.
380 double fixed_dt = _simFixedDt->getDoubleValue();
381 double dt = (fixed_dt) ? fixed_dt : (currentStamp - _lastStamp).toSecs();
382
383 // here we have a true real dt for a clock "real time".
384 double mpProtocolDt = dt;
385
386 if (dt > _frameLatencyMax)
387 _frameLatencyMax = dt;
388
389 // Limit the time we need to spend in simulation loops
390 // That means, if the /sim/max-simtime-per-frame value is strictly positive
391 // you can limit the maximum amount of time you will do simulations for
392 // one frame to display. The cpu time spent in simulations code is roughly
393 // at least O(real_delta_time_sec). If this is (due to running debug
394 // builds or valgrind or something different blowing up execution times)
395 // larger than the real time you will no longer get any response
396 // from flightgear. This limits that effect. Just set to property from
397 // your .fgfsrc or commandline ...
398 double dtMax = _maxDtPerFrame->getDoubleValue();
399 if (0 < dtMax && dtMax < dt) {
400 dt = dtMax;
401 }
402
403 SGSubsystemGroup* fdmGroup =
404 globals->get_subsystem_mgr()->get_group(SGSubsystemMgr::FDM);
405 fdmGroup->set_fixed_update_time(1.0 / modelHz);
406
407 // round the real time down to a multiple of 1/model-hz.
408 // this way all systems are updated the _same_ amount of dt.
409 dt += _dtRemainder;
410
411 // we keep the mp clock sync with the sim time, as it's used as timestamp
412 // in fdm state,
413 mpProtocolDt += _dtRemainder;
414 int multiLoop = long(floor(dt * modelHz));
415 multiLoop = SGMisc<long>::max(0, multiLoop);
416 _dtRemainder = dt - double(multiLoop)/modelHz;
417 dt = double(multiLoop)/modelHz;
418 mpProtocolDt -= _dtRemainder;
419
420 realDt = dt;
421 if (_clockFreeze->getBoolValue() || wait_for_scenery) {
422 simDt = 0;
423 } else {
424 // sim time can be scaled
425 simDt = dt * _simTimeFactor->getDoubleValue();
426 }
427
428 _lastStamp = currentStamp;
429 globals->inc_sim_time_sec(simDt);
430 _steadyClock += mpProtocolDt;
431 _mpProtocolClock = _steadyClock + _mpClockOffset->getDoubleValue();
432
433 _dtRemainderNode->setDoubleValue(_dtRemainder);
434 _steadyClockNode->setDoubleValue(_steadyClock);
435 _mpProtocolClockNode->setDoubleValue(_mpProtocolClock);
436
437 // These are useful, especially for Nasal scripts.
438 _timeDelta->setDoubleValue(realDt);
439 _simTimeDelta->setDoubleValue(simDt);
440}
441
442void TimeManager::update(double dt)
443{
444 _frameNumber->setIntValue(_frameNumber->getIntValue() + 1);
445 bool freeze = _clockFreeze->getBoolValue();
446 time_t now = time(NULL);
447
448 if (freeze) {
449 // clock freeze requested
450 if (_timeOverride->getLongValue() == 0) {
451 _timeOverride->setLongValue(now);
452 _adjustWarpOnUnfreeze = true;
453 }
454 } else {
455 // no clock freeze requested
456 if (_lastClockFreeze) {
457 if (_adjustWarpOnUnfreeze) {
458 // clock just unfroze, let's set warp as the difference
459 // between frozen time and current time so we don't get a
460 // time jump (and corresponding sky object and lighting
461 // jump.)
462 int adjust = _timeOverride->getLongValue() - now;
463 SG_LOG(SG_GENERAL, SG_DEBUG, "adjusting on un-freeze:" << adjust);
464 _warp->setIntValue(_warp->getIntValue() + adjust);
465 }
466 _timeOverride->setLongValue(0);
467 }
468
469 // account for speed-up in warp value. This implies when speed-up is not
470 // 1.0 we need to continually adjust warp, either forwards for speed-up,
471 // or backwards for a slow-down. Eg for a speed up of 4x, we want to
472 // incease warp by 3 additional seconds per elapsed real second.
473 // for a 1/2x factor, we want to decrease warp by half a second per
474 // elapsed real second.
475 double speedUp = _simTimeFactor->getDoubleValue() - 1.0;
476 if (speedUp != 0.0) {
477 double realDt = _timeDelta->getDoubleValue();
478 double speedUpOffset = speedUp * realDt;
479 _warp->setDoubleValue(_warp->getDoubleValue() + speedUpOffset);
480 }
481 } // of sim not frozen
482
483 // scale warp-delta by real-dt, so rate is constant with frame-rate,
484 // but warping works while paused
485 int warpDelta = _warpDelta->getIntValue();
486 if (warpDelta) {
487 _adjustWarpOnUnfreeze = false;
488 double warpOffset = warpDelta * _timeDelta->getDoubleValue();
489 _warp->setDoubleValue(_warp->getDoubleValue() + warpOffset);
490 }
491
492 const auto d2 = distSqr(_lastTimeZoneCheckPosition, globals->get_aircraft_position_cart());
493 const auto oneNmSqr = SG_NM_TO_METER * SG_NM_TO_METER;
494 if (d2 > oneNmSqr) {
495 updateLocalTime();
496 }
497
498 _lastClockFreeze = freeze;
499 _impl->update(globals->get_aircraft_position(),
500 _timeOverride->getLongValue(),
501 _warp->getIntValue());
502
503 updateLocalTimeString();
504 computeFrameRate();
505}
506
507void TimeManager::computeFrameRate()
508{
509 // Calculate frame rate average
510 if ((_impl->get_cur_time() != _lastFrameTime)) {
511 _frameRate->setIntValue(_frameCount);
512 _frameLatency->setDoubleValue(_frameLatencyMax*1000);
513 if (_frameLatencyMax>0)
514 _frameRateWorst->setIntValue(1/_frameLatencyMax);
515 _frameCount = 0;
516 _frameLatencyMax = 0.0;
517 }
518
519 _lastFrameTime = _impl->get_cur_time();
520 ++_frameCount;
521}
522
523void TimeManager::throttleUpdateRate()
524{
525 const double throttleHz = _maxFrameRate->getDoubleValue();
526 // no delay required.
527 if (throttleHz <= 0) {
528 _frameWait->setDoubleValue(0);
529 return;
530 }
531
532 const double modelHz = _modelHz->getDoubleValue();
533 SGTimeStamp frameWaitStart = SGTimeStamp::now();
534
535 // we want to sleep until just after the next ideal timestamp wanted, we will
536 // gain time from a 1/Hz step if the last timestamp was late.
537 const double t = (round(modelHz / throttleHz) / modelHz) - _dtRemainder;
538 SGTimeStamp::sleepUntil(_lastStamp + SGTimeStamp::fromSec(t));
539 _frameWait->setDoubleValue(frameWaitStart.elapsedMSec());
540}
541
543{
544 // force a zone check, next update()
545 _lastTimeZoneCheckPosition = SGVec3d::zeros();
546}
547
548// periodic time updater wrapper
549void TimeManager::updateLocalTime()
550{
551 _lastTimeZoneCheckPosition = globals->get_aircraft_position_cart();
552 _impl->updateLocal(globals->get_aircraft_position(), globals->get_fg_root() / "Timezone");
553 // synchronous update, since somebody might need that
554 updateLocalTimeString();
555}
556
557void TimeManager::updateLocalTimeString()
558{
559 time_t cur_time = _impl->get_cur_time();
560 if (!_impl->get_zonename()) {
561 return;
562 }
563
564 struct tm* aircraftLocalTime = fgLocaltime(&cur_time, _impl->get_zonename());
565 static char buf[16];
566 snprintf(buf, 16, "%.2d:%.2d:%.2d",
567 aircraftLocalTime->tm_hour,
568 aircraftLocalTime->tm_min, aircraftLocalTime->tm_sec);
569
570 // check against current string to avoid changes all the time
571 std::string s = _localTimeStringNode->getStringValue();
572 if (s != std::string(buf)) {
573 _localTimeStringNode->setStringValue(buf);
574 }
575
576 std::string zs = _localTimeZoneNode->getStringValue();
577 if (zs != std::string(_impl->get_description())) {
578 _localTimeZoneNode->setStringValue(_impl->get_description());
579 }
580}
581
582void TimeManager::initTimeOffset()
583{
584
585 long int offset = fgGetLong("/sim/startup/time-offset");
586 std::string offset_type = fgGetString("/sim/startup/time-offset-type");
587 setTimeOffset(offset_type, offset);
588}
589
590void TimeManager::setTimeOffset(const std::string& offset_type, long int offset)
591{
592 // Handle potential user specified time offsets
593 int orig_warp = _warp->getIntValue();
594 time_t cur_time = _impl->get_cur_time();
595 time_t currGMT = sgTimeGetGMT( gmtime(&cur_time) );
596 time_t systemLocalTime = sgTimeGetGMT( localtime(&cur_time) );
597 time_t aircraftLocalTime =
598 sgTimeGetGMT( fgLocaltime(&cur_time, _impl->get_zonename() ) );
599
600 // Okay, we now have several possible scenarios
601 SGGeod loc = globals->get_aircraft_position();
602 int warp = 0;
603
604 if ( offset_type == "real" ) {
605 warp = 0;
606 } else if ( offset_type == "dawn" ) {
607 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 90.0, true, true );
608 } else if ( offset_type == "morning" ) {
609 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 75.0, true, true );
610 } else if ( offset_type == "noon" ) {
611 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 0.0, true, true );
612 } else if ( offset_type == "afternoon" ) {
613 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 75.0, false, true );
614 } else if ( offset_type == "dusk" ) {
615 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 90.0, false, true );
616 } else if ( offset_type == "evening" ) {
617 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 100.0, false, true );
618 } else if ( offset_type == "midnight" ) {
619 warp = fgTimeSecondsUntilBodyAngle( cur_time, loc, 180.0, false, true );
620 } else if ( offset_type == "system-offset" ) {
621 warp = offset;
622 orig_warp = 0;
623 } else if ( offset_type == "gmt-offset" ) {
624 warp = offset - (currGMT - systemLocalTime);
625 orig_warp = 0;
626 } else if ( offset_type == "latitude-offset" ) {
627 warp = offset - (aircraftLocalTime - systemLocalTime);
628 orig_warp = 0;
629 } else if ( offset_type == "system" ) {
630 warp = offset - (systemLocalTime - currGMT) - cur_time;
631 } else if ( offset_type == "gmt" ) {
632 warp = offset - cur_time;
633 } else if ( offset_type == "latitude" ) {
634 warp = offset - (aircraftLocalTime - currGMT)- cur_time;
635 } else {
636 SG_LOG( SG_GENERAL, SG_ALERT,
637 "TimeManager::setTimeOffset: unsupported offset: " << offset_type );
638 warp = 0;
639 }
640
641 if( fgGetBool("/sim/time/warp-easing", false) && !fgGetBool("/devices/status/keyboard/ctrl", false)) {
642 double duration = fgGetDouble("/sim/time/warp-easing-duration-secs", 5.0 );
643 const std::string easing = fgGetString("/sim/time/warp-easing-method", "swing" );
644 SGPropertyNode n;
645 n.setDoubleValue( orig_warp + warp );
646 _warp->interpolate( "numeric", n, duration, easing );
647 } else {
648 _warp->setIntValue( orig_warp + warp );
649 }
650
651 SG_LOG(SG_GENERAL, SG_INFO, "After TimeManager::setTimeOffset(): " << offset_type << ", warp = " << _warp->getIntValue());
652}
653
655{
656 return _simTimeFactor->getDoubleValue();
657}
658
659// Register the subsystem.
660SGSubsystemMgr::Registrant<TimeManager> registrantTimeManager(
661 SGSubsystemMgr::INIT,
662 {{"FDM", SGSubsystemMgr::Dependency::HARD}});
static bool do_timeofday(const SGPropertyNode *arg, SGPropertyNode *root)
SGSubsystemMgr::Registrant< TimeManager > registrantTimeManager(SGSubsystemMgr::INIT, {{"FDM", SGSubsystemMgr::Dependency::HARD}})
static double TimeUTC()
time_t fgTimeSecondsUntilBodyAngle(time_t cur_time, const SGGeod &loc, double target_angle_deg, bool ascending, bool sun_not_moon)
Given the current unix time in seconds, calculate seconds to the specified body angle (relative to st...
void postinit() override
void setTimeOffset(const std::string &offset_type, long int offset)
virtual ~TimeManager()
void init() override
void shutdown() override
double getSimSpeedUpFactor() const
void computeTimeDeltas(double &simDt, double &realDt)
void computeTimeDeltasSimple(double &simDt, double &realDt)
void reinit() override
void valueChanged(SGPropertyNode *) override
void update(double dt) override
void reposition()
void unbind() override
long fgGetLong(const char *name, long defaultValue)
Get a long value for a property.
Definition fg_props.cxx:538
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
bool fgSetInt(const char *name, int val)
Set an int value for a property.
Definition fg_props.cxx:568
FGGlobals * globals
Definition globals.cxx:142
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27