1 // globals.cxx -- Global state that needs to be shared among the sim modules
2 //
3 // Written by Curtis Olson, started July 2000.
4 //
5 // Copyright (C) 2000 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 Foundation,
19 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 //
21 // $Id$
22
23 #include <config.h>
24
25 #include <algorithm>
26
27 #include <osgViewer/Viewer>
28 #include <osgDB/Registry>
29
30 #include <simgear/structure/commands.hxx>
31 #include <simgear/structure/exception.hxx>
32 #include <simgear/misc/sg_path.hxx>
33 #include <simgear/misc/sg_dir.hxx>
34 #include <simgear/timing/sg_time.hxx>
35 #include <simgear/ephemeris/ephemeris.hxx>
36 #include <simgear/structure/subsystem_mgr.hxx>
37 #include <simgear/structure/event_mgr.hxx>
38
39 #include <simgear/misc/ResourceManager.hxx>
40 #include <simgear/props/propertyObject.hxx>
41 #include <simgear/props/props_io.hxx>
42 #include <simgear/props/AtomicChangeListener.hxx>
43 #include <simgear/scene/model/modellib.hxx>
44 #include <simgear/package/Root.hxx>
45
46 #include <Add-ons/AddonResourceProvider.hxx>
47 #include <Aircraft/controls.hxx>
48 #include <Airports/runways.hxx>
49 #include <Autopilot/route_mgr.hxx>
50 #include <Navaids/navlist.hxx>
51
52 #include <GUI/gui.h>
53 #include <Main/sentryIntegration.hxx>
54 #include <Viewer/viewmgr.hxx>
55
56 #include <Scenery/scenery.hxx>
57 #include <Scenery/tilemgr.hxx>
58 #include <Viewer/renderer.hxx>
59 #include <GUI/FGFontCache.hxx>
60 #include <GUI/MessageBox.hxx>
61
62 #include <simgear/sound/soundmgr.hxx>
63 #include <simgear/scene/material/matlib.hxx>
64
65 #include "globals.hxx"
66 #include "locale.hxx"
67
68 #include "fg_props.hxx"
69 #include "fg_io.hxx"
70
71 class AircraftResourceProvider : public simgear::ResourceProvider
72 {
73 public:
AircraftResourceProvider()74 AircraftResourceProvider() :
75 simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
76 {
77 }
78
resolve(const std::string & aResource,SGPath &) const79 virtual SGPath resolve(const std::string& aResource, SGPath&) const
80 {
81 string_list pieces(sgPathBranchSplit(aResource));
82 if ((pieces.size() < 3) || (pieces.front() != "Aircraft")) {
83 return SGPath(); // not an Aircraft path
84 }
85
86 // test against the aircraft-dir property
87 const char* aircraftDir = fgGetString("/sim/aircraft-dir");
88 string_list aircraftDirPieces(sgPathBranchSplit(aircraftDir));
89 if (!aircraftDirPieces.empty() && (aircraftDirPieces.back() == pieces[1])) {
90 // current aircraft-dir matches resource aircraft
91 SGPath r(aircraftDir);
92 for (unsigned int i=2; i<pieces.size(); ++i) {
93 r.append(pieces[i]);
94 }
95
96 if (r.exists()) {
97 return r;
98 } else {
99 // Stop here, otherwise we could end up returning a resource that
100 // belongs to an unrelated version of the same aircraft (from a
101 // different aircraft directory).
102 return SGPath();
103 }
104 }
105
106 // try each aircraft dir in turn
107 std::string res(aResource, 9); // resource path with 'Aircraft/' removed
108 const PathList& dirs(globals->get_aircraft_paths());
109 PathList::const_iterator it = dirs.begin();
110 for (; it != dirs.end(); ++it) {
111 SGPath p(*it);
112 p.append(res);
113 if (p.exists()) {
114 return p;
115 }
116 } // of aircraft path iteration
117
118 return SGPath(); // not found
119 }
120 };
121
122 class CurrentAircraftDirProvider : public simgear::ResourceProvider
123 {
124 public:
CurrentAircraftDirProvider()125 CurrentAircraftDirProvider() :
126 simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
127 {
128 }
129
resolve(const std::string & aResource,SGPath &) const130 virtual SGPath resolve(const std::string& aResource, SGPath&) const
131 {
132 SGPath p = SGPath::fromUtf8(fgGetString("/sim/aircraft-dir"));
133 p.append(aResource);
134 return p.exists() ? p : SGPath();
135 }
136 };
137
138 class CompositorEffectsProvider : public simgear::ResourceProvider {
139 public:
CompositorEffectsProvider()140 CompositorEffectsProvider() :
141 simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {
142 }
resolve(const std::string & aResource,SGPath &) const143 SGPath resolve(const std::string &aResource, SGPath&) const override {
144 const SGPath p = globals->get_fg_root() / "Compositor" / aResource;
145 return p.exists() ? p : SGPath();
146 }
147 };
148
149 ////////////////////////////////////////////////////////////////////////
150 // Implementation of FGGlobals.
151 ////////////////////////////////////////////////////////////////////////
152
153 // global global :-)
154 FGGlobals *globals = NULL;
155
156
157 // Constructor
FGGlobals()158 FGGlobals::FGGlobals() :
159 renderer( new FGRenderer ),
160 subsystem_mgr( new SGSubsystemMgr ),
161 event_mgr( new SGEventMgr ),
162 sim_time_sec( 0.0 ),
163 fg_root( "" ),
164 fg_home( "" ),
165 time_params( NULL ),
166 commands( SGCommandMgr::instance() ),
167 channel_options_list( NULL ),
168 initial_waypoints( NULL ),
169 channellist( NULL ),
170 haveUserSettings(false)
171 {
172 SGPropertyNode* root = new SGPropertyNode;
173 props = SGPropertyNode_ptr(root);
174 locale = new FGLocale(props);
175
176 auto resMgr = simgear::ResourceManager::instance();
177 resMgr->addProvider(new AircraftResourceProvider());
178 resMgr->addProvider(new CurrentAircraftDirProvider());
179 resMgr->addProvider(new flightgear::addons::ResourceProvider());
180 #ifdef ENABLE_COMPOSITOR
181 resMgr->addProvider(new CompositorEffectsProvider());
182 #endif
183 initProperties();
184 }
185
initProperties()186 void FGGlobals::initProperties()
187 {
188 simgear::PropertyObjectBase::setDefaultRoot(props);
189
190 positionLon = props->getNode("position/longitude-deg", true);
191 positionLat = props->getNode("position/latitude-deg", true);
192 positionAlt = props->getNode("position/altitude-ft", true);
193
194 viewLon = props->getNode("sim/current-view/viewer-lon-deg", true);
195 viewLat = props->getNode("sim/current-view/viewer-lat-deg", true);
196 viewAlt = props->getNode("sim/current-view/viewer-elev-ft", true);
197
198 orientPitch = props->getNode("orientation/pitch-deg", true);
199 orientHeading = props->getNode("orientation/heading-deg", true);
200 orientRoll = props->getNode("orientation/roll-deg", true);
201
202 }
203
204 // Destructor
~FGGlobals()205 FGGlobals::~FGGlobals()
206 {
207 // save user settings (unless already saved)
208 saveUserSettings();
209
210 // stop OSG threading first, to avoid thread races while we tear down
211 // scene-graph pieces
212 // there are some scenarios where renderer is already gone.
213 osg::ref_ptr<osgViewer::Viewer> vw;
214 if (renderer) {
215 vw = renderer->getViewer();
216 if (vw) {
217 // https://code.google.com/p/flightgear-bugs/issues/detail?id=1291
218 // explicitly stop trheading before we delete the renderer or
219 // viewMgr (which ultimately holds refs to the CameraGroup, and
220 // GraphicsContext)
221 vw->stopThreading();
222 }
223 }
224
225 subsystem_mgr->shutdown();
226 subsystem_mgr->unbind();
227
228 // don't cancel the pager until after shutdown, since AIModels (and
229 // potentially others) can queue delete requests on the pager.
230 if (vw && vw->getDatabasePager()) {
231 vw->getDatabasePager()->cancel();
232 vw->getDatabasePager()->clear();
233 }
234
235 osgDB::Registry::instance()->clearObjectCache();
236 if (subsystem_mgr->get_subsystem(FGScenery::staticSubsystemClassId())) {
237 subsystem_mgr->remove(FGScenery::staticSubsystemClassId());
238 }
239
240 // renderer touches subsystems during its destruction
241 set_renderer(nullptr);
242
243 FGFontCache::shutdown();
244 fgCancelSnapShot();
245
246 delete subsystem_mgr;
247 subsystem_mgr = nullptr; // important so ::get_subsystem returns NULL
248 vw = nullptr;
249 set_matlib(NULL);
250
251 delete time_params;
252 delete channel_options_list;
253 delete initial_waypoints;
254 delete channellist;
255
256 // delete commands before we release the property root
257 // this avoids crash where commands might be storing a propery
258 // ref/pointer.
259 // see https://sentry.io/organizations/flightgear/issues/1890563449
260 delete commands;
261 commands = nullptr;
262
263 simgear::PropertyObjectBase::setDefaultRoot(NULL);
264 simgear::SGModelLib::resetPropertyRoot();
265 delete locale;
266 locale = NULL;
267
268 cleanupListeners();
269
270 props.clear();
271
272 delete simgear::ResourceManager::instance();
273 }
274
275 // set the fg_root path
set_fg_root(const SGPath & root)276 void FGGlobals::set_fg_root (const SGPath &root) {
277 SGPath tmp(root);
278 fg_root = tmp.realpath();
279
280 // append /data to root if it exists
281 tmp.append( "data" );
282 tmp.append( "version" );
283 if ( tmp.exists() ) {
284 fgGetNode("BAD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
285 fg_root.append("data");
286 fgGetNode("GOOD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
287 SG_LOG(SG_GENERAL, SG_ALERT, "***\n***\n*** Warning: changing bad FG_ROOT/--fg-root to '"
288 << fg_root << "'\n***\n***");
289 }
290
291 // deliberately not a tied property, for fgValidatePath security
292 // write-protect to avoid accidents
293 SGPropertyNode *n = fgGetNode("/sim", true);
294 n->removeChild("fg-root", 0);
295 n = n->getChild("fg-root", 0, true);
296 n->setStringValue(fg_root.utf8Str());
297 n->setAttribute(SGPropertyNode::WRITE, false);
298
299 simgear::ResourceManager::instance()->addBasePath(fg_root,
300 simgear::ResourceManager::PRIORITY_DEFAULT);
301 }
302
303 // set the fg_home path
set_fg_home(const SGPath & home)304 void FGGlobals::set_fg_home (const SGPath &home)
305 {
306 fg_home = home.realpath();
307 }
308
set_texture_cache_dir(const SGPath & textureCache)309 void FGGlobals::set_texture_cache_dir(const SGPath &textureCache)
310 {
311 texture_cache_dir = textureCache.realpath();
312 auto node = fgGetNode("/sim/rendering/texture-cache/dir", true);
313 node->setAttribute(SGPropertyNode::WRITE, true);
314 node->setStringValue(textureCache.utf8Str());
315 node->setAttribute(SGPropertyNode::WRITE, false);
316 }
317
318
get_data_paths() const319 PathList FGGlobals::get_data_paths() const
320 {
321 PathList r(additional_data_paths);
322 r.push_back(fg_root);
323 r.insert(r.end(), _dataPathsAfterFGRoot.begin(), _dataPathsAfterFGRoot.end());
324 return r;
325 }
326
get_data_paths(const std::string & suffix) const327 PathList FGGlobals::get_data_paths(const std::string& suffix) const
328 {
329 PathList r;
330 for (SGPath p : get_data_paths()) {
331 p.append(suffix);
332 if (p.exists()) {
333 r.push_back(p);
334 }
335 }
336
337 return r;
338 }
339
append_data_path(const SGPath & path,bool afterFGRoot)340 void FGGlobals::append_data_path(const SGPath& path, bool afterFGRoot)
341 {
342 if (!path.exists()) {
343 SG_LOG(SG_GENERAL, SG_WARN, "adding non-existant data path:" << path);
344 }
345
346 using RM = simgear::ResourceManager;
347 auto resManager = RM::instance();
348 if (afterFGRoot) {
349 _dataPathsAfterFGRoot.push_back(path);
350 // after FG_ROOT
351 resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT - 10));
352 } else {
353 additional_data_paths.push_back(path);
354 // after NORMAL prioirty, but ahead of FG_ROOT
355 resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT + 10));
356 }
357 }
358
findDataPath(const std::string & pathSuffix) const359 SGPath FGGlobals::findDataPath(const std::string& pathSuffix) const
360 {
361 for (SGPath p : additional_data_paths) {
362 p.append(pathSuffix);
363 if (p.exists()) {
364 return p;
365 }
366 }
367
368 SGPath rootPath(fg_root);
369 rootPath.append(pathSuffix);
370 if (rootPath.exists()) {
371 return rootPath;
372 }
373
374 for (SGPath p : _dataPathsAfterFGRoot) {
375 p.append(pathSuffix);
376 if (p.exists()) {
377 return p;
378 }
379 }
380
381 SG_LOG(SG_IO, SG_WARN, "not found in any data path: '" << pathSuffix << "'");
382 return SGPath{};
383 }
384
append_fg_scenery(const PathList & paths)385 void FGGlobals::append_fg_scenery (const PathList &paths)
386 {
387 for (const SGPath& path : paths) {
388 append_fg_scenery(path);
389 }
390 }
391
append_fg_scenery(const SGPath & path)392 void FGGlobals::append_fg_scenery (const SGPath &path)
393 {
394 SGPropertyNode* sim = fgGetNode("/sim", true);
395
396 // find first unused fg-scenery property in /sim
397 int propIndex = 0;
398 while (sim->getChild("fg-scenery", propIndex) != NULL) {
399 ++propIndex;
400 }
401
402 SGPath abspath(path.realpath());
403 if (!abspath.exists()) {
404 SG_LOG(SG_GENERAL, SG_WARN, "scenery path not found:" << abspath);
405 return;
406 }
407
408 // check for duplicates
409 PathList::const_iterator ex = std::find(fg_scenery.begin(), fg_scenery.end(), abspath);
410 if (ex != fg_scenery.end()) {
411 SG_LOG(SG_GENERAL, SG_INFO, "skipping duplicate add of scenery path:" << abspath);
412 return;
413 }
414
415 // tell the ResouceManager about the scenery path
416 // needed to load Models from this scenery path
417 simgear::ResourceManager::instance()->addBasePath(abspath, simgear::ResourceManager::PRIORITY_DEFAULT);
418
419 fg_scenery.push_back(abspath);
420 extra_read_allowed_paths.push_back(abspath);
421
422 // make scenery dirs available to Nasal
423 SGPropertyNode* n = sim->getChild("fg-scenery", propIndex++, true);
424 n->setStringValue(abspath.utf8Str());
425 n->setAttribute(SGPropertyNode::WRITE, false);
426
427 // temporary fix so these values survive reset
428 n->setAttribute(SGPropertyNode::PRESERVE, true);
429 }
430
append_read_allowed_paths(const SGPath & path)431 void FGGlobals::append_read_allowed_paths(const SGPath &path)
432 {
433 SGPath abspath(path.realpath());
434 if (!abspath.exists()) {
435 SG_LOG(SG_IO, SG_DEBUG, "read-allowed path not found:" << abspath);
436 return;
437 }
438 extra_read_allowed_paths.push_back(abspath);
439 }
440
clear_fg_scenery()441 void FGGlobals::clear_fg_scenery()
442 {
443 fg_scenery.clear();
444 fgGetNode("/sim", true)->removeChildren("fg-scenery");
445 }
446
447 // The 'path' argument to this method must come from trustworthy code, because
448 // the method grants read permissions to Nasal code for many files beneath
449 // 'path'. In particular, don't call this method with a 'path' value taken
450 // from the property tree or any other Nasal-writable place.
set_download_dir(const SGPath & path)451 void FGGlobals::set_download_dir(const SGPath& path)
452 {
453 SGPath abspath(path.realpath());
454 download_dir = abspath;
455
456 append_read_allowed_paths(abspath / "Aircraft");
457 append_read_allowed_paths(abspath / "AI");
458 append_read_allowed_paths(abspath / "Liveries");
459 // If in use, abspath / TerraSync will be added to 'extra_read_allowed_paths'
460 // by FGGlobals::append_fg_scenery(), as any scenery path.
461
462 SGPropertyNode *n = fgGetNode("/sim/paths/download-dir", true);
463 n->setAttribute(SGPropertyNode::WRITE, true);
464 n->setStringValue(abspath.utf8Str());
465 n->setAttribute(SGPropertyNode::WRITE, false);
466 }
467
468 // The 'path' argument to this method must come from trustworthy code, because
469 // the method grants read permissions to Nasal code for all files beneath
470 // 'path'. In particular, don't call this method with a 'path' value taken
471 // from the property tree or any other Nasal-writable place.
set_terrasync_dir(const SGPath & path)472 void FGGlobals::set_terrasync_dir(const SGPath &path)
473 {
474 if (terrasync_dir.realpath() != SGPath(fgGetString("/sim/terrasync/scenery-dir")).realpath()) {
475 // if they don't match, /sim/terrasync/scenery-dir has been set by something else
476 SG_LOG(SG_GENERAL, SG_WARN, "/sim/terrasync/scenery-dir is no longer stored across runs: if you wish to keep using a non-standard Terrasync directory, use --terrasync-dir or the launcher's settings");
477 }
478 SGPath abspath(path.realpath());
479 terrasync_dir = abspath;
480 // deliberately not a tied property, for fgValidatePath security
481 // write-protect to avoid accidents
482 SGPropertyNode *n = fgGetNode("/sim/terrasync/scenery-dir", true);
483 n->setAttribute(SGPropertyNode::WRITE, true);
484 n->setStringValue(abspath.utf8Str());
485 n->setAttribute(SGPropertyNode::WRITE, false);
486 // don't add it to fg_scenery yet, as we want it ordered after explicit --fg-scenery
487 }
488
489
490
set_catalog_aircraft_path(const SGPath & path)491 void FGGlobals::set_catalog_aircraft_path(const SGPath& path)
492 {
493 catalog_aircraft_dir = path;
494 }
495
get_aircraft_paths() const496 PathList FGGlobals::get_aircraft_paths() const
497 {
498 PathList r;
499 if (!catalog_aircraft_dir.isNull()) {
500 r.push_back(catalog_aircraft_dir);
501 }
502
503 r.insert(r.end(), fg_aircraft_dirs.begin(), fg_aircraft_dirs.end());
504 return r;
505 }
506
append_aircraft_path(const SGPath & path)507 void FGGlobals::append_aircraft_path(const SGPath& path)
508 {
509 SGPath dirPath(path);
510 if (!dirPath.exists()) {
511 SG_LOG(SG_GENERAL, SG_WARN, "aircraft path not found:" << path);
512 return;
513 }
514
515 SGPath acSubdir(dirPath);
516 acSubdir.append("Aircraft");
517 if (acSubdir.exists()) {
518 SG_LOG(
519 SG_GENERAL,
520 SG_WARN,
521 "Specified an aircraft-dir with an 'Aircraft' subdirectory:" << dirPath
522 << ", will instead use child directory:" << acSubdir
523 );
524 dirPath = acSubdir;
525 }
526
527 fg_aircraft_dirs.push_back(dirPath.realpath());
528 extra_read_allowed_paths.push_back(dirPath.realpath());
529 }
530
append_aircraft_paths(const PathList & paths)531 void FGGlobals::append_aircraft_paths(const PathList& paths)
532 {
533 for (unsigned int p = 0; p<paths.size(); ++p) {
534 append_aircraft_path(paths[p]);
535 }
536 }
537
resolve_aircraft_path(const std::string & branch) const538 SGPath FGGlobals::resolve_aircraft_path(const std::string& branch) const
539 {
540 return simgear::ResourceManager::instance()->findPath(branch);
541 }
542
resolve_maybe_aircraft_path(const std::string & branch) const543 SGPath FGGlobals::resolve_maybe_aircraft_path(const std::string& branch) const
544 {
545 return simgear::ResourceManager::instance()->findPath(branch);
546 }
547
resolve_resource_path(const std::string & branch) const548 SGPath FGGlobals::resolve_resource_path(const std::string& branch) const
549 {
550 return simgear::ResourceManager::instance()
551 ->findPath(branch, SGPath(fgGetString("/sim/aircraft-dir")));
552 }
553
554 FGRenderer *
get_renderer() const555 FGGlobals::get_renderer () const
556 {
557 return renderer;
558 }
559
set_renderer(FGRenderer * render)560 void FGGlobals::set_renderer(FGRenderer *render)
561 {
562 if (render == renderer) {
563 return;
564 }
565
566 delete renderer;
567 renderer = render;
568 }
569
570 SGSubsystemMgr *
get_subsystem_mgr() const571 FGGlobals::get_subsystem_mgr () const
572 {
573 return subsystem_mgr;
574 }
575
576 SGSubsystem *
get_subsystem(const char * name) const577 FGGlobals::get_subsystem (const char * name) const
578 {
579 if (!subsystem_mgr) {
580 return NULL;
581 }
582
583 return subsystem_mgr->get_subsystem(name);
584 }
585
586 void
add_subsystem(const char * name,SGSubsystem * subsystem,SGSubsystemMgr::GroupType type,double min_time_sec)587 FGGlobals::add_subsystem (const char * name,
588 SGSubsystem * subsystem,
589 SGSubsystemMgr::GroupType type,
590 double min_time_sec)
591 {
592 subsystem_mgr->add(name, subsystem, type, min_time_sec);
593 }
594
595 SGEventMgr *
get_event_mgr() const596 FGGlobals::get_event_mgr () const
597 {
598 return event_mgr;
599 }
600
601 SGGeod
get_aircraft_position() const602 FGGlobals::get_aircraft_position() const
603 {
604 return SGGeod::fromDegFt(positionLon->getDoubleValue(),
605 positionLat->getDoubleValue(),
606 positionAlt->getDoubleValue());
607 }
608
609 SGVec3d
get_aircraft_position_cart() const610 FGGlobals::get_aircraft_position_cart() const
611 {
612 return SGVec3d::fromGeod(get_aircraft_position());
613 }
614
get_aircraft_orientation(double & heading,double & pitch,double & roll)615 void FGGlobals::get_aircraft_orientation(double& heading, double& pitch, double& roll)
616 {
617 heading = orientHeading->getDoubleValue();
618 pitch = orientPitch->getDoubleValue();
619 roll = orientRoll->getDoubleValue();
620 }
621
622 SGGeod
get_view_position() const623 FGGlobals::get_view_position() const
624 {
625 return SGGeod::fromDegFt(viewLon->getDoubleValue(),
626 viewLat->getDoubleValue(),
627 viewAlt->getDoubleValue());
628 }
629
630 SGVec3d
get_view_position_cart() const631 FGGlobals::get_view_position_cart() const
632 {
633 return SGVec3d::fromGeod(get_view_position());
634 }
635
treeDumpRefCounts(int depth,SGPropertyNode * nd)636 static void treeDumpRefCounts(int depth, SGPropertyNode* nd)
637 {
638 for (int i=0; i<nd->nChildren(); ++i) {
639 SGPropertyNode* cp = nd->getChild(i);
640 if (SGReferenced::count(cp) > 1) {
641 SG_LOG(SG_GENERAL, SG_INFO, "\t" << cp->getPath() << " refcount:" << SGReferenced::count(cp));
642 }
643
644 treeDumpRefCounts(depth + 1, cp);
645 }
646 }
647
treeClearAliases(SGPropertyNode * nd)648 static void treeClearAliases(SGPropertyNode* nd)
649 {
650 if (nd->isAlias()) {
651 nd->unalias();
652 }
653
654 for (int i=0; i<nd->nChildren(); ++i) {
655 SGPropertyNode* cp = nd->getChild(i);
656 treeClearAliases(cp);
657 }
658 }
659
660 void
resetPropertyRoot()661 FGGlobals::resetPropertyRoot()
662 {
663 delete locale;
664
665 // avoid a warning when we processOptions after reset
666 terrasync_dir = SGPath{};
667
668 cleanupListeners();
669
670 // we don't strictly need to clear these (they will be reset when we
671 // initProperties again), but trying to reduce false-positives when dumping
672 // ref-counts.
673 positionLon.clear();
674 positionLat.clear();
675 positionAlt.clear();
676 viewLon.clear();
677 viewLat.clear();
678 viewAlt.clear();
679 orientPitch.clear();
680 orientHeading.clear();
681 orientRoll.clear();
682
683 // clear aliases so ref-counts are accurate when dumped
684 treeClearAliases(props);
685
686 SG_LOG(SG_GENERAL, SG_INFO, "root props refcount:" << props.getNumRefs());
687 treeDumpRefCounts(0, props);
688
689 //BaseStackSnapshot::dumpAll(std::cout);
690
691 props = new SGPropertyNode;
692 initProperties();
693 locale = new FGLocale(props);
694
695 // remove /sim/fg-root before writing to prevent hijacking
696 SGPropertyNode *n = props->getNode("/sim", true);
697 n->removeChild("fg-root", 0);
698 n = n->getChild("fg-root", 0, true);
699 n->setStringValue(fg_root.utf8Str());
700 n->setAttribute(SGPropertyNode::WRITE, false);
701 }
702
autosaveName()703 static std::string autosaveName()
704 {
705 std::ostringstream os;
706 string_list versionParts = simgear::strutils::split(VERSION, ".");
707 if (versionParts.size() < 2) {
708 return "autosave.xml";
709 }
710
711 os << "autosave_" << versionParts[0] << "_" << versionParts[1] << ".xml";
712 return os.str();
713 }
714
autosaveFilePath(SGPath userDataPath) const715 SGPath FGGlobals::autosaveFilePath(SGPath userDataPath) const
716 {
717 if (userDataPath.isNull()) {
718 userDataPath = get_fg_home();
719 }
720
721 return simgear::Dir(userDataPath).file(autosaveName());
722 }
723
deleteProperties(SGPropertyNode * props,const string_list & blacklist)724 static void deleteProperties(SGPropertyNode* props, const string_list& blacklist)
725 {
726 const std::string path(props->getPath());
727 auto it = std::find_if(blacklist.begin(), blacklist.end(), [path](const std::string& black)
728 { return simgear::strutils::matchPropPathToTemplate(path, black); });
729 if (it != blacklist.end()) {
730 SGPropertyNode* pr = props->getParent();
731 pr->removeChild(props);
732 return;
733 }
734
735 // recurse
736 for (int c=0; c < props->nChildren(); ++c) {
737 deleteProperties(props->getChild(c), blacklist);
738 }
739
740 }
741
742 using VersionPair = std::pair<int, int>;
743
operator <(const VersionPair & a,const VersionPair & b)744 static bool operator<(const VersionPair& a, const VersionPair& b)
745 {
746 if (a.first == b.first) {
747 return a.second < b.second;
748 }
749
750 return a.first < b.first;
751 }
752
tryAutosaveMigration(const SGPath & userDataPath,SGPropertyNode * props)753 static void tryAutosaveMigration(const SGPath& userDataPath, SGPropertyNode* props)
754 {
755 const string_list versionParts = simgear::strutils::split(VERSION, ".");
756 if (versionParts.size() < 2) {
757 return;
758 }
759
760 simgear::Dir userDataDir(userDataPath);
761 SGPath migratePath;
762 VersionPair foundVersion(0, 0);
763 const VersionPair currentVersion(simgear::strutils::to_int(versionParts[0]),
764 simgear::strutils::to_int(versionParts[1]));
765
766 for (auto previousSave : userDataDir.children(simgear::Dir::TYPE_FILE, ".xml")) {
767 const std::string base = previousSave.file_base();
768 VersionPair v;
769 // extract version from name
770 const int matches = ::sscanf(base.c_str(), "autosave_%d_%d", &v.first, &v.second);
771 if (matches != 2) {
772 continue;
773 }
774
775 if (currentVersion < v) {
776 // ignore autosaves from more recent versions; this happens when
777 // running unsable and stable at the same time
778 continue;
779 }
780
781 if (v.first < 2000) {
782 // ignore autosaves from older versions, too much change to deal
783 // with.
784 continue;
785 }
786
787 if (foundVersion < v) {
788 foundVersion = v;
789 migratePath = previousSave;
790 }
791 }
792
793 if (!migratePath.exists()) {
794 return;
795 }
796
797 SG_LOG(SG_GENERAL, SG_INFO, "Migrating old autosave:" << migratePath);
798 SGPropertyNode oldProps;
799
800 try {
801 readProperties(migratePath, &oldProps, SGPropertyNode::USERARCHIVE);
802 } catch (sg_exception& e) {
803 SG_LOG(SG_GENERAL, SG_WARN, "failed to read previous user settings:" << e.getMessage()
804 << "(from " << e.getOrigin() << ")");
805 return;
806 }
807
808 // read migration blacklist
809 string_list blacklist;
810 SGPropertyNode_ptr blacklistNode = fgGetNode("/sim/autosave-migration/blacklist", true);
811 for (auto node : blacklistNode->getChildren("path")) {
812 blacklist.push_back(node->getStringValue());
813 }
814
815 // apply migration filters for each property in turn
816 deleteProperties(&oldProps, blacklist);
817
818 // manual migrations
819 // migrate pre-2019.1 sense of /sim/mouse/invert-mouse-wheel
820 if (foundVersion.first < 2019) {
821 SGPropertyNode_ptr wheelNode = oldProps.getNode("/sim/mouse/invert-mouse-wheel");
822 if (wheelNode) {
823 wheelNode->setBoolValue(!wheelNode->getBoolValue());
824 }
825 }
826 // copy remaining props out
827 copyProperties(&oldProps, props);
828
829 // we can't inform the user yet, becuase embedded resources and the locale
830 // are not done. So we set a flag and check it once those things are done.
831 fgSetBool("/sim/autosave-migration/did-migrate", true);
832 }
833
834 // Load user settings from the autosave file (normally in $FG_HOME)
835 void
loadUserSettings(SGPath userDataPath)836 FGGlobals::loadUserSettings(SGPath userDataPath)
837 {
838 if (userDataPath.isNull()) {
839 userDataPath = get_fg_home();
840 }
841
842 // Remember that we have (tried) to load any existing autosave file
843 haveUserSettings = true;
844
845 SGPath autosaveFile = autosaveFilePath(userDataPath);
846 SGPropertyNode autosave;
847 if (autosaveFile.exists()) {
848 SG_LOG(SG_INPUT, SG_INFO,
849 "Reading user settings from " << autosaveFile);
850 try {
851 readProperties(autosaveFile, &autosave, SGPropertyNode::USERARCHIVE);
852 } catch (sg_exception& e) {
853 SG_LOG(SG_INPUT, SG_WARN, "failed to read user settings:" << e.getMessage()
854 << "(from " << e.getOrigin() << ")");
855 }
856 } else {
857 tryAutosaveMigration(userDataPath, &autosave);
858 }
859 /* Before 2020-03-10, we could save portions of the /ai/models/ tree, which
860 confuses things when loaded again. So delete any such items if they have
861 been loaded. */
862 SGPropertyNode* ai = autosave.getNode("ai");
863 if (ai) {
864 ai->removeChildren("models");
865 }
866 copyProperties(&autosave, globals->get_props());
867 }
868
869 // Save user settings to the autosave file.
870 //
871 // When calling this method, make sure the value of 'userDataPath' is
872 // trustworthy. In particular, make sure it can't be influenced by Nasal code,
873 // not even indirectly via a Nasal-writable place such as the property tree.
874 //
875 // Note: the default value, which causes the autosave file to be written to
876 // $FG_HOME, is safe---if not, it would be a bug.
877 void
saveUserSettings(SGPath userDataPath)878 FGGlobals::saveUserSettings(SGPath userDataPath)
879 {
880 if (userDataPath.isNull()) userDataPath = get_fg_home();
881
882 // only save settings when we have (tried) to load the previous
883 // settings (otherwise user data was lost)
884 if (!haveUserSettings)
885 return;
886
887 if (fgGetBool("/sim/startup/save-on-exit")) {
888 // don't save settings more than once on shutdown
889 haveUserSettings = false;
890
891 SGPath autosaveFile = autosaveFilePath(userDataPath);
892 autosaveFile.create_dir( 0700 );
893 SG_LOG(SG_IO, SG_INFO, "Saving user settings to " << autosaveFile);
894 try {
895 writeProperties(autosaveFile, globals->get_props(), false, SGPropertyNode::USERARCHIVE);
896 } catch (const sg_exception &e) {
897 guiErrorMessage("Error writing autosave:", e);
898 }
899 SG_LOG(SG_INPUT, SG_DEBUG, "Finished Saving user settings");
900 }
901 }
902
get_warp() const903 long int FGGlobals::get_warp() const
904 {
905 return fgGetInt("/sim/time/warp");
906 }
907
set_warp(long int w)908 void FGGlobals::set_warp( long int w )
909 {
910 fgSetInt("/sim/time/warp", w);
911 }
912
get_warp_delta() const913 long int FGGlobals::get_warp_delta() const
914 {
915 return fgGetInt("/sim/time/warp-delta");
916 }
917
set_warp_delta(long int d)918 void FGGlobals::set_warp_delta( long int d )
919 {
920 fgSetInt("/sim/time/warp-delta", d);
921 }
922
get_scenery() const923 FGScenery* FGGlobals::get_scenery () const
924 {
925 return get_subsystem<FGScenery>();
926 }
927
get_viewmgr() const928 FGViewMgr *FGGlobals::get_viewmgr() const
929 {
930 return get_subsystem<FGViewMgr>();
931 }
932
get_current_view() const933 flightgear::View* FGGlobals::get_current_view () const
934 {
935 FGViewMgr* vm = get_viewmgr();
936 return vm ? vm->get_current_view() : 0;
937 }
938
set_matlib(SGMaterialLib * m)939 void FGGlobals::set_matlib( SGMaterialLib *m )
940 {
941 matlib = m;
942 }
943
get_controls() const944 FGControls *FGGlobals::get_controls() const
945 {
946 return get_subsystem<FGControls>();
947 }
948
addListenerToCleanup(SGPropertyChangeListener * l)949 void FGGlobals::addListenerToCleanup(SGPropertyChangeListener* l)
950 {
951 _listeners_to_cleanup.push_back(l);
952 }
953
cleanupListeners()954 void FGGlobals::cleanupListeners()
955 {
956 SGPropertyChangeListenerVec::iterator i = _listeners_to_cleanup.begin();
957 for (; i != _listeners_to_cleanup.end(); ++i) {
958 delete *i;
959 }
960 _listeners_to_cleanup.clear();
961
962 simgear::AtomicChangeListener::clearPendingChanges();
963 }
964
packageRoot()965 simgear::pkg::Root* FGGlobals::packageRoot()
966 {
967 return _packageRoot.get();
968 }
969
setPackageRoot(const SGSharedPtr<simgear::pkg::Root> & p)970 void FGGlobals::setPackageRoot(const SGSharedPtr<simgear::pkg::Root>& p)
971 {
972 _packageRoot = p;
973 }
974
is_headless()975 bool FGGlobals::is_headless()
976 {
977 return flightgear::isHeadlessMode();
978 }
979
set_headless(bool mode)980 void FGGlobals::set_headless(bool mode)
981 {
982 flightgear::setHeadlessMode(mode);
983 }
984
985 // end of globals.cxx
986