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