1999-06-18 03:42:54 +00:00
|
|
|
// main.cxx -- top level sim routines
|
1998-04-21 17:02:27 +00:00
|
|
|
//
|
2002-04-05 00:38:55 +00:00
|
|
|
// Written by Curtis Olson, started May 1997.
|
1998-04-21 17:02:27 +00:00
|
|
|
//
|
2004-11-19 22:10:41 +00:00
|
|
|
// Copyright (C) 1997 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
|
1998-04-21 17:02:27 +00:00
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
2006-02-21 01:16:04 +00:00
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
1998-04-21 17:02:27 +00:00
|
|
|
//
|
|
|
|
// $Id$
|
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
#include <config.h>
|
1998-04-21 17:02:27 +00:00
|
|
|
|
2003-08-17 09:54:41 +00:00
|
|
|
#include <simgear/compiler.h>
|
2016-03-19 12:10:36 +00:00
|
|
|
#include <simgear/props/props_io.hxx>
|
2003-08-17 09:54:41 +00:00
|
|
|
|
2008-08-14 18:13:39 +00:00
|
|
|
#include <iostream>
|
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
#include <osg/Camera>
|
|
|
|
#include <osg/GraphicsContext>
|
2008-03-22 09:31:06 +00:00
|
|
|
#include <osgDB/Registry>
|
|
|
|
|
2014-01-18 14:50:31 +00:00
|
|
|
|
2005-03-09 21:56:00 +00:00
|
|
|
// Class references
|
2012-10-31 00:40:43 +00:00
|
|
|
#include <simgear/canvas/VGInitOperation.hxx>
|
2021-04-21 11:50:07 +00:00
|
|
|
#include <simgear/debug/logdelta.hxx>
|
|
|
|
#include <simgear/emesary/Emesary.hxx>
|
|
|
|
#include <simgear/emesary/notifications.hxx>
|
2010-10-23 19:37:26 +00:00
|
|
|
#include <simgear/io/raw_socket.hxx>
|
2012-05-12 21:27:57 +00:00
|
|
|
#include <simgear/math/SGMath.hxx>
|
2021-05-25 11:57:07 +00:00
|
|
|
#include <simgear/math/sg_random.hxx>
|
2014-01-27 17:27:12 +00:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
2021-04-21 11:50:07 +00:00
|
|
|
#include <simgear/nasal/NasalEmesaryInterface.hxx>
|
|
|
|
#include <simgear/props/AtomicChangeListener.hxx>
|
|
|
|
#include <simgear/props/props.hxx>
|
|
|
|
#include <simgear/scene/material/Effect.hxx>
|
|
|
|
#include <simgear/scene/material/matlib.hxx>
|
|
|
|
#include <simgear/scene/model/modellib.hxx>
|
|
|
|
#include <simgear/scene/tsync/terrasync.hxx>
|
2019-03-24 17:59:23 +00:00
|
|
|
#include <simgear/structure/commands.hxx>
|
2021-04-21 11:50:07 +00:00
|
|
|
#include <simgear/timing/sg_time.hxx>
|
2008-07-31 12:04:32 +00:00
|
|
|
|
Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably
AddonManager, Addon and AddonVersion. The AddonManager is used to
register add-ons. It relies on an std::map<std::string, AddonRef> to
hold the metadata of each registered add-on (keys of the std::map are
add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>).
Accessor methods are available for:
- retrieving the list of registered or loaded add-ons (terminology
explained in $FG_ROOT/Docs/README.add-ons);
- checking if a particular add-on has already been registered or
loaded;
- for each add-on, obtaining an Addon instance which can be queried
for its name, id, version, base path, the minimum and maximum
FlightGear versions it requires, its base node in the Property Tree,
its order in the load sequence, short and long description strings,
home page, etc.
The most important metadata is made accessible in the Property Tree
under /addons/by-id/<addon-id> and the property
/addons/by-id/<addon-id>/loaded can be checked or listened to, in
order to determine when a particular add-on is loaded. There is also a
Nasal interface to access add-on metadata in a convenient way.
In order to provide this metadata, each add-on must from now on have in
its base directory a file called 'addon-metadata.xml'.
All this is documented in much more detail in
$FG_ROOT/Docs/README.add-ons.
Mailing-list discussion:
https://sourceforge.net/p/flightgear/mailman/message/36146017/
2017-11-06 12:58:14 +00:00
|
|
|
#include <Add-ons/AddonManager.hxx>
|
2020-11-14 14:31:18 +00:00
|
|
|
#include <GUI/MessageBox.hxx>
|
|
|
|
#include <GUI/gui.h>
|
Integrate the EmbeddedResourceManager into FlightGear
${CMAKE_SOURCE_DIR}/src/EmbeddedResources/FlightGear-resources.xml
(currently empty) is automatically "compiled" into
${CMAKE_BINARY_DIR}/src/EmbeddedResources/FlightGear-resources.[ch]xx by
fgrcc inside the build directory. These files are incorporated into the
FlightGear build (FlightGear-resources.cxx is linked into FlightGear).
When the XML embedded resource declaration file added here,
FlightGear-resources.xml, is compiled, fgrcc is passed the
--root=${CMAKE_SOURCE_DIR} option, so that files referred to in
FlightGear-resources.xml are looked up relatively to the root directory
of the FlightGear repository. One could use a second XML embedded
resource declaration file compiled with a different --root option to
grab files from FGData, for instance. I would name such a file
FGData-resources.xml to be consistent with the current naming scheme.
Note: this --root option applies to the paths of real files. Don't
confuse it with the 'prefix' attribute of <qresource> elements
inside XML resource declaration files (such as
FlightGear-resources.xml), which applies to the virtual path of
each resource defined beneath.
The commands in src/Main/CMakeLists.txt ensure that
FlightGear-resources.xml is recompiled with fgrcc whenever it is
changed, and obviously also when FlightGear-resources.cxx or
FlightGear-resources.hxx is missing. However, CMake doesn't know how to
parse fgrcc XML resource declaration files, therefore when a resource is
modified but the XML file it is declared in is not (here,
FlightGear-resources.xml), you have to trigger yourself a recompilation
of the XML resource declaration file to see the new resource contents
inside FlightGear. The easiest ways to do so are:
- either update the timestamp of the XML resource declaration file;
- or remove one or both of the generated files
(FlightGear-resources.cxx and FlightGear-resources.hxx here).
The EmbeddedResourceManager is created in fgMainInit() just after
Options::processOptions() set the language that was either requested by
the user or obtained from the system (locales). Resources from
FlightGear-resources.cxx are added to it, after which
EmbeddedResourceManager::selectLocale() is called with the user's
preferred locale (obtained with FGLocale::getPreferredLanguage()).
Upon reset (fgStartNewReset()), EmbeddedResourceManager::selectLocale()
is called in a similar way after Options::processOptions(), however in
this case the EmbeddedResourceManager instance doesn't have to be
recreated.
2017-04-27 22:09:14 +00:00
|
|
|
#include <Main/locale.hxx>
|
2003-08-17 09:54:41 +00:00
|
|
|
#include <Model/panelnode.hxx>
|
2020-11-14 14:31:18 +00:00
|
|
|
#include <Navaids/NavDataCache.hxx>
|
2003-08-17 09:54:41 +00:00
|
|
|
#include <Scenery/scenery.hxx>
|
2012-09-17 11:36:38 +00:00
|
|
|
#include <Sound/soundmanager.hxx>
|
2010-07-13 20:50:44 +00:00
|
|
|
#include <Time/TimeManager.hxx>
|
2020-11-14 14:31:18 +00:00
|
|
|
#include <Viewer/GraphicsPresets.hxx>
|
2012-10-31 00:40:43 +00:00
|
|
|
#include <Viewer/WindowSystemAdapter.hxx>
|
2020-11-14 14:31:18 +00:00
|
|
|
#include <Viewer/renderer.hxx>
|
|
|
|
#include <Viewer/splash.hxx>
|
2020-08-24 13:32:22 +00:00
|
|
|
#include <flightgearBuildId.h>
|
2004-09-20 13:21:51 +00:00
|
|
|
|
Andy Ross:
The biggest and coolest patch adds mouse sensitivity to the 3D
cockpits, so we can finally work the radios. This ended up requiring
significant modifications outside of the 3D cockpit code. Stuff folks
will want to look at:
+ The list of all "3D" cockpits is stored statically in the
panelnode.cxx file. This is clumsy, and won't migrate well to a
multiple-aircraft feature. Really, there should be a per-model list
of 3D panels, but I couldn't find a clean place to put this. The
only handle you get back after parsing a model is a generic ssg
node, to which I obviously can't add panel-specific methods.
+ The aircraft model is parsed *very* early in the initialization
order. Earlier, in fact, than the static list of allowable command
bindings is built in fgInitCommands(). This is bad, as it means
that mouse bindings on the instruments can't work yet. I moved the
call to fgInitCommands, but someone should look carefully to see
that I picked the right place. There's a lot of initialization
code, and I got a little lost in there... :)
+ I added yet another "update" hook to the fgRenderFrame routine to
hook the updates for the 3D panels. This is only required for
"mouse press delay", and it's a fairly clumsy mechanism based on
frame rate instead of real time. There appears to be delay handling
already in place in the Input stuff, and there's a discussion going
on about different mouse behavior right now. Maybe this is a good
time to unify these two (now three) approaches?
2002-10-29 19:44:03 +00:00
|
|
|
#include "fg_commands.hxx"
|
2008-07-29 08:27:48 +00:00
|
|
|
#include "fg_init.hxx"
|
2021-02-09 16:56:12 +00:00
|
|
|
#include "fg_io.hxx"
|
2009-08-05 14:29:32 +00:00
|
|
|
#include "fg_os.hxx"
|
2012-04-25 21:28:00 +00:00
|
|
|
#include "fg_props.hxx"
|
2021-02-09 16:56:12 +00:00
|
|
|
#include "main.hxx"
|
|
|
|
#include "options.hxx"
|
2012-09-18 19:50:28 +00:00
|
|
|
#include "positioninit.hxx"
|
2013-12-09 00:13:44 +00:00
|
|
|
#include "screensaver_control.hxx"
|
2012-09-30 15:34:51 +00:00
|
|
|
#include "subsystemFactory.hxx"
|
2021-02-09 16:56:12 +00:00
|
|
|
#include "util.hxx"
|
|
|
|
#include <Main/ErrorReporter.hxx>
|
2020-04-07 08:38:54 +00:00
|
|
|
#include <Main/sentryIntegration.hxx>
|
2003-08-29 16:46:21 +00:00
|
|
|
|
Integrate the EmbeddedResourceManager into FlightGear
${CMAKE_SOURCE_DIR}/src/EmbeddedResources/FlightGear-resources.xml
(currently empty) is automatically "compiled" into
${CMAKE_BINARY_DIR}/src/EmbeddedResources/FlightGear-resources.[ch]xx by
fgrcc inside the build directory. These files are incorporated into the
FlightGear build (FlightGear-resources.cxx is linked into FlightGear).
When the XML embedded resource declaration file added here,
FlightGear-resources.xml, is compiled, fgrcc is passed the
--root=${CMAKE_SOURCE_DIR} option, so that files referred to in
FlightGear-resources.xml are looked up relatively to the root directory
of the FlightGear repository. One could use a second XML embedded
resource declaration file compiled with a different --root option to
grab files from FGData, for instance. I would name such a file
FGData-resources.xml to be consistent with the current naming scheme.
Note: this --root option applies to the paths of real files. Don't
confuse it with the 'prefix' attribute of <qresource> elements
inside XML resource declaration files (such as
FlightGear-resources.xml), which applies to the virtual path of
each resource defined beneath.
The commands in src/Main/CMakeLists.txt ensure that
FlightGear-resources.xml is recompiled with fgrcc whenever it is
changed, and obviously also when FlightGear-resources.cxx or
FlightGear-resources.hxx is missing. However, CMake doesn't know how to
parse fgrcc XML resource declaration files, therefore when a resource is
modified but the XML file it is declared in is not (here,
FlightGear-resources.xml), you have to trigger yourself a recompilation
of the XML resource declaration file to see the new resource contents
inside FlightGear. The easiest ways to do so are:
- either update the timestamp of the XML resource declaration file;
- or remove one or both of the generated files
(FlightGear-resources.cxx and FlightGear-resources.hxx here).
The EmbeddedResourceManager is created in fgMainInit() just after
Options::processOptions() set the language that was either requested by
the user or obtained from the system (locales). Resources from
FlightGear-resources.cxx are added to it, after which
EmbeddedResourceManager::selectLocale() is called with the user's
preferred locale (obtained with FGLocale::getPreferredLanguage()).
Upon reset (fgStartNewReset()), EmbeddedResourceManager::selectLocale()
is called in a similar way after Options::processOptions(), however in
this case the EmbeddedResourceManager instance doesn't have to be
recreated.
2017-04-27 22:09:14 +00:00
|
|
|
#include <simgear/embedded_resources/EmbeddedResourceManager.hxx>
|
|
|
|
#include <EmbeddedResources/FlightGear-resources.hxx>
|
|
|
|
|
2014-12-26 12:20:51 +00:00
|
|
|
#if defined(HAVE_QT)
|
|
|
|
#include <GUI/QtLauncher.hxx>
|
|
|
|
#endif
|
2013-12-09 00:13:44 +00:00
|
|
|
|
2019-08-12 16:52:53 +00:00
|
|
|
#ifdef __OpenBSD__
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#endif
|
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
using namespace flightgear;
|
|
|
|
|
2008-08-14 18:13:39 +00:00
|
|
|
using std::cerr;
|
2012-05-12 21:27:57 +00:00
|
|
|
using std::vector;
|
2008-08-14 18:13:39 +00:00
|
|
|
|
2006-08-07 15:16:46 +00:00
|
|
|
// The atexit() function handler should know when the graphical subsystem
|
2004-08-24 08:40:41 +00:00
|
|
|
// is initialized.
|
|
|
|
extern int _bootstrap_OSInit;
|
|
|
|
|
2012-11-18 15:35:54 +00:00
|
|
|
static SGPropertyNode_ptr frame_signal;
|
2019-06-11 11:44:59 +00:00
|
|
|
static SGPropertyNode_ptr nasal_gc_threaded;
|
|
|
|
static SGPropertyNode_ptr nasal_gc_threaded_wait;
|
2012-11-18 15:35:54 +00:00
|
|
|
|
2019-06-11 11:44:59 +00:00
|
|
|
#ifdef NASAL_BACKGROUND_GC_THREAD
|
|
|
|
extern "C" {
|
|
|
|
extern void startNasalBackgroundGarbageCollection();
|
|
|
|
extern void stopNasalBackgroundGarbageCollection();
|
|
|
|
extern void performNasalBackgroundGarbageCollection();
|
|
|
|
extern void awaitNasalGarbageCollectionComplete(bool can_wait);
|
|
|
|
}
|
|
|
|
#endif
|
2021-04-16 20:02:11 +00:00
|
|
|
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_begin(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Begin));
|
|
|
|
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_end(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::End));
|
|
|
|
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_started(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Started));
|
|
|
|
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_stopped(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Stopped));
|
|
|
|
static SGSharedPtr<simgear::Notifications::NasalGarbageCollectionConfigurationNotification> ngccn;
|
2019-06-11 11:44:59 +00:00
|
|
|
// This method is usually called after OSG has finished rendering a frame in what OSG calls an idle handler and
|
|
|
|
// is reposonsible for invoking all of the relevant per frame processing; most of which is handled by subsystems.
|
2011-11-20 15:20:05 +00:00
|
|
|
static void fgMainLoop( void )
|
2010-01-10 11:00:52 +00:00
|
|
|
{
|
2019-06-11 11:44:59 +00:00
|
|
|
//
|
|
|
|
// the Nasal GC will automatically run when (during allocation) it discovers that more space is needed.
|
|
|
|
// This has a cost of between 5ms and 50ms (depending on the amount of currently active Nasal).
|
|
|
|
// The result is unscheduled and unpredictable pauses during normal operation when the garbage collector
|
|
|
|
// runs; which typically occurs at intervals between 1sec and 20sec.
|
|
|
|
//
|
|
|
|
// The solution to this, which overall increases CPU load, is to use a thread to do this; as Nasal is thread safe
|
|
|
|
// so what we do is to launch the garbage collection at the end of the main loop and then wait for completion at the start of the
|
|
|
|
// next main loop.
|
|
|
|
// So although the overall CPU is increased it has little effect on the frame rate; if anything it is an overall benefit
|
|
|
|
// as there are no unscheduled long duration frames.
|
|
|
|
//
|
|
|
|
// The implementation appears to work fine without waiting for completion at the start of the frame - so
|
|
|
|
// this wait at the start can be disabled by setting the property /sim/nasal-gc-threaded-wait to false.
|
|
|
|
|
|
|
|
// first we see if the config has changed. The notification will return true from SetActive/SetWait when the
|
|
|
|
// value has been changed - and thus we notify the Nasal system that it should configure itself accordingly.
|
|
|
|
auto use_threaded_gc = nasal_gc_threaded->getBoolValue();
|
|
|
|
auto threaded_wait = nasal_gc_threaded_wait->getBoolValue();
|
|
|
|
bool notify_gc_config = false;
|
|
|
|
notify_gc_config = ngccn->SetActive(use_threaded_gc);
|
|
|
|
notify_gc_config |= ngccn->SetWait(threaded_wait);
|
|
|
|
if (notify_gc_config)
|
2021-04-16 20:02:11 +00:00
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(ngccn);
|
2019-06-11 11:44:59 +00:00
|
|
|
|
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_begin);
|
2016-07-20 13:03:15 +00:00
|
|
|
|
|
|
|
if (sglog().has_popup()) {
|
|
|
|
std::string s = sglog().get_popup();
|
2016-07-21 06:56:05 +00:00
|
|
|
flightgear::modalMessageBox("Alert", s, "");
|
2016-07-20 13:03:15 +00:00
|
|
|
}
|
|
|
|
|
2011-11-20 15:20:05 +00:00
|
|
|
frame_signal->fireValueChanged();
|
2010-01-10 11:00:52 +00:00
|
|
|
|
2018-01-29 10:46:08 +00:00
|
|
|
auto timeManager = globals->get_subsystem<TimeManager>();
|
2011-11-20 15:20:05 +00:00
|
|
|
// compute simulated time (allowing for pause, warp, etc) and
|
|
|
|
// real elapsed time
|
|
|
|
double sim_dt, real_dt;
|
2018-01-29 10:46:08 +00:00
|
|
|
timeManager->computeTimeDeltas(sim_dt, real_dt);
|
2011-11-20 15:20:05 +00:00
|
|
|
|
|
|
|
// update all subsystems
|
|
|
|
globals->get_subsystem_mgr()->update(sim_dt);
|
|
|
|
|
2019-03-24 17:59:23 +00:00
|
|
|
// flush commands waiting in the queue
|
|
|
|
SGCommandMgr::instance()->executedQueuedCommands();
|
2011-11-20 15:20:05 +00:00
|
|
|
simgear::AtomicChangeListener::fireChangeListeners();
|
2019-06-11 11:44:59 +00:00
|
|
|
|
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_end);
|
2009-11-30 14:24:16 +00:00
|
|
|
}
|
|
|
|
|
2013-11-14 16:48:14 +00:00
|
|
|
static void initTerrasync()
|
|
|
|
{
|
2014-01-15 22:01:02 +00:00
|
|
|
// add the terrasync root as a data path so data can be retrieved from it
|
|
|
|
// (even if we are in read-only mode)
|
2016-10-18 21:10:09 +00:00
|
|
|
SGPath terraSyncDir(globals->get_terrasync_dir());
|
2020-06-30 16:08:29 +00:00
|
|
|
globals->append_data_path(terraSyncDir, false /* = ahead of FG_ROOT */);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-11-14 16:48:14 +00:00
|
|
|
if (fgGetBool("/sim/fghome-readonly", false)) {
|
2021-01-22 12:57:02 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "initTerrasync() failing because /sim/fghome-readonly is true");
|
2013-11-14 16:48:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2016-08-18 15:23:39 +00:00
|
|
|
// make fg-root dir available so existing Scenery data can be copied, and
|
|
|
|
// hence not downloaded again.
|
|
|
|
fgSetString("/sim/terrasync/installation-dir", (globals->get_fg_root() / "Scenery").utf8Str());
|
|
|
|
|
2013-11-14 16:48:14 +00:00
|
|
|
simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync();
|
|
|
|
terra_sync->setRoot(globals->get_props());
|
2021-04-02 09:16:20 +00:00
|
|
|
globals->add_subsystem("terrasync", terra_sync, SGSubsystemMgr::GENERAL);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-11-14 16:48:14 +00:00
|
|
|
terra_sync->bind();
|
|
|
|
terra_sync->init();
|
2020-04-07 08:38:54 +00:00
|
|
|
|
|
|
|
if (fgGetBool("/sim/terrasync/enabled")) {
|
|
|
|
flightgear::addSentryTag("terrasync", "enabled");
|
|
|
|
}
|
2013-11-14 16:48:14 +00:00
|
|
|
}
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2016-03-19 12:10:36 +00:00
|
|
|
static void fgSetVideoOptions()
|
|
|
|
{
|
2020-04-13 15:03:37 +00:00
|
|
|
SGPath userDataPath = globals->get_fg_home();
|
|
|
|
SGPath autosaveFile = globals->autosaveFilePath(userDataPath);
|
|
|
|
if (autosaveFile.exists()) return;
|
|
|
|
|
2016-03-19 12:10:36 +00:00
|
|
|
std::string vendor = fgGetString("/sim/rendering/gl-vendor");
|
|
|
|
SGPath path(globals->get_fg_root());
|
|
|
|
path.append("Video");
|
|
|
|
path.append(vendor);
|
|
|
|
if (path.exists())
|
|
|
|
{
|
|
|
|
std::string renderer = fgGetString("/sim/rendering/gl-renderer");
|
2016-03-21 10:50:11 +00:00
|
|
|
size_t pos = renderer.find("x86/");
|
|
|
|
if (pos == std::string::npos) {
|
|
|
|
pos = renderer.find('/');
|
|
|
|
}
|
2016-03-19 12:10:36 +00:00
|
|
|
if (pos == std::string::npos) {
|
|
|
|
pos = renderer.find(" (");
|
|
|
|
}
|
|
|
|
if (pos != std::string::npos) {
|
|
|
|
renderer = renderer.substr(0, pos);
|
|
|
|
}
|
|
|
|
path.append(renderer+".xml");
|
|
|
|
if (path.exists()) {
|
2016-06-23 13:26:34 +00:00
|
|
|
SG_LOG(SG_INPUT, SG_INFO, "Reading video settings from " << path);
|
2016-03-19 12:10:36 +00:00
|
|
|
try {
|
|
|
|
SGPropertyNode *r_prop = fgGetNode("/sim/rendering");
|
2016-06-23 13:26:34 +00:00
|
|
|
readProperties(path, r_prop);
|
2016-03-19 12:10:36 +00:00
|
|
|
} catch (sg_exception& e) {
|
|
|
|
SG_LOG(SG_INPUT, SG_WARN, "failed to read video settings:" << e.getMessage()
|
|
|
|
<< "(from " << e.getOrigin() << ")");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-27 17:27:12 +00:00
|
|
|
static void checkOpenGLVersion()
|
|
|
|
{
|
2020-04-07 08:38:54 +00:00
|
|
|
flightgear::addSentryTag("gl-version", fgGetString("/sim/rendering/gl-version"));
|
|
|
|
flightgear::addSentryTag("gl-renderer", fgGetString("/sim/rendering/gl-vendor"));
|
|
|
|
flightgear::addSentryTag("gl-vendor", fgGetString("/sim/rendering/gl-renderer"));
|
|
|
|
|
2014-01-27 17:27:12 +00:00
|
|
|
#if defined(SG_MAC)
|
|
|
|
// Mac users can't upgrade their drivers, so complaining about
|
|
|
|
// versions doesn't help them much
|
|
|
|
return;
|
|
|
|
#endif
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2014-01-27 17:27:12 +00:00
|
|
|
// format of these strings is not standardised, so be careful about
|
|
|
|
// parsing them.
|
|
|
|
std::string versionString(fgGetString("/sim/rendering/gl-version"));
|
|
|
|
string_list parts = simgear::strutils::split(versionString);
|
|
|
|
if (parts.size() == 3) {
|
|
|
|
if (parts[1].find("NVIDIA") != std::string::npos) {
|
|
|
|
// driver version number, dot-seperared
|
|
|
|
string_list driverVersion = simgear::strutils::split(parts[2], ".");
|
|
|
|
if (!driverVersion.empty()) {
|
|
|
|
int majorDriverVersion = simgear::strutils::to_int(driverVersion[0]);
|
|
|
|
if (majorDriverVersion < 300) {
|
|
|
|
std::ostringstream ss;
|
|
|
|
ss << "Please upgrade to at least version 300 of the nVidia drivers (installed version is " << parts[2] << ")";
|
|
|
|
|
|
|
|
flightgear::modalMessageBox("Outdated graphics drivers",
|
|
|
|
"FlightGear has detected outdated drivers for your graphics card.",
|
|
|
|
ss.str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // of NVIDIA-style version string
|
|
|
|
} // of three parts
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:32:36 +00:00
|
|
|
namespace flightgear {
|
|
|
|
|
|
|
|
void registerMainLoop()
|
2012-11-18 16:20:11 +00:00
|
|
|
{
|
|
|
|
// stash current frame signal property
|
|
|
|
frame_signal = fgGetNode("/sim/signals/frame", true);
|
2019-06-11 11:44:59 +00:00
|
|
|
nasal_gc_threaded = fgGetNode("/sim/nasal-gc-threaded", true);
|
|
|
|
nasal_gc_threaded_wait = fgGetNode("/sim/nasal-gc-threaded-wait", true);
|
2021-04-21 11:50:07 +00:00
|
|
|
|
|
|
|
// init the Emesary receiver for Nasal
|
|
|
|
nasal::initMainLoopRecipient();
|
|
|
|
|
2012-11-18 16:20:11 +00:00
|
|
|
fgRegisterIdleHandler( fgMainLoop );
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:32:36 +00:00
|
|
|
void unregisterMainLoopProperties()
|
|
|
|
{
|
2021-06-17 14:54:02 +00:00
|
|
|
nasal::shutdownMainLoopRecipient();
|
2020-08-11 17:32:36 +00:00
|
|
|
frame_signal.reset();
|
|
|
|
nasal_gc_threaded.reset();
|
|
|
|
nasal_gc_threaded_wait.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace flightgear
|
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
// This is the top level master main function that is registered as
|
2011-05-22 16:28:42 +00:00
|
|
|
// our idle function
|
2007-04-29 09:58:37 +00:00
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
// The first few passes take care of initialization things (a couple
|
|
|
|
// per pass) and once everything has been initialized fgMainLoop from
|
|
|
|
// then on.
|
2007-04-29 09:58:37 +00:00
|
|
|
|
2013-11-16 12:10:32 +00:00
|
|
|
static int idle_state = 0;
|
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
static void fgIdleFunction ( void ) {
|
2012-09-17 11:41:42 +00:00
|
|
|
// Specify our current idle function state. This is used to run all
|
|
|
|
// our initializations out of the idle callback so that we can get a
|
|
|
|
// splash screen up and running right away.
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2008-08-01 15:57:29 +00:00
|
|
|
if ( idle_state == 0 ) {
|
2012-09-25 20:39:13 +00:00
|
|
|
if (guiInit())
|
|
|
|
{
|
2014-01-27 17:27:12 +00:00
|
|
|
checkOpenGLVersion();
|
2016-03-19 12:10:36 +00:00
|
|
|
fgSetVideoOptions();
|
2012-09-25 20:39:13 +00:00
|
|
|
idle_state+=2;
|
|
|
|
fgSplashProgress("loading-aircraft-list");
|
2016-07-21 11:55:31 +00:00
|
|
|
fgSetBool("/sim/rendering/initialized", true);
|
2008-08-01 15:57:29 +00:00
|
|
|
}
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2005-05-06 09:08:44 +00:00
|
|
|
} else if ( idle_state == 2 ) {
|
2013-11-14 16:48:14 +00:00
|
|
|
initTerrasync();
|
2005-05-04 21:28:42 +00:00
|
|
|
idle_state++;
|
2012-09-26 12:38:26 +00:00
|
|
|
fgSplashProgress("loading-nav-dat");
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2005-05-06 09:08:44 +00:00
|
|
|
} else if ( idle_state == 3 ) {
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-09-25 16:24:12 +00:00
|
|
|
bool done = fgInitNav();
|
|
|
|
if (done) {
|
|
|
|
++idle_state;
|
|
|
|
fgSplashProgress("init-scenery");
|
|
|
|
}
|
2005-05-06 09:08:44 +00:00
|
|
|
} else if ( idle_state == 4 ) {
|
2013-11-16 12:10:32 +00:00
|
|
|
idle_state++;
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2018-01-29 10:46:08 +00:00
|
|
|
globals->add_new_subsystem<TimeManager>(SGSubsystemMgr::INIT);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2005-05-04 21:28:42 +00:00
|
|
|
// Do some quick general initializations
|
|
|
|
if( !fgInitGeneral()) {
|
2013-10-15 21:16:50 +00:00
|
|
|
throw sg_exception("General initialization failed");
|
2005-05-04 21:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-08-23 19:33:23 +00:00
|
|
|
// now we have commands up
|
|
|
|
flightgear::delayedSentryInit();
|
|
|
|
|
2005-05-04 21:28:42 +00:00
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
// Initialize the property-based built-in commands
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
fgInitCommands();
|
2019-03-24 17:59:23 +00:00
|
|
|
fgInitSceneCommands();
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2012-09-30 15:34:51 +00:00
|
|
|
flightgear::registerSubsystemCommands(globals->get_commands());
|
|
|
|
|
2005-05-04 21:28:42 +00:00
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
// Initialize the material manager
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
globals->set_matlib( new SGMaterialLib );
|
2012-01-02 23:16:18 +00:00
|
|
|
simgear::SGModelLib::setPanelFunc(FGPanelNode::load);
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-11-16 12:10:32 +00:00
|
|
|
} else if (( idle_state == 5 ) || (idle_state == 2005)) {
|
|
|
|
idle_state+=2;
|
|
|
|
flightgear::initPosition();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2020-03-13 09:53:49 +00:00
|
|
|
simgear::SGModelLib::init(globals->get_fg_root().utf8Str(), globals->get_props());
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2018-01-29 10:46:08 +00:00
|
|
|
auto timeManager = globals->get_subsystem<TimeManager>();
|
2013-11-16 12:10:32 +00:00
|
|
|
timeManager->init();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2005-05-04 21:28:42 +00:00
|
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
// Initialize the TG scenery subsystem.
|
|
|
|
////////////////////////////////////////////////////////////////////
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2015-12-10 22:48:02 +00:00
|
|
|
globals->add_new_subsystem<FGScenery>(SGSubsystemMgr::DISPLAY);
|
2005-05-04 21:28:42 +00:00
|
|
|
globals->get_scenery()->init();
|
|
|
|
globals->get_scenery()->bind();
|
2016-07-30 12:26:45 +00:00
|
|
|
|
2012-09-21 17:36:25 +00:00
|
|
|
fgSplashProgress("creating-subsystems");
|
2013-11-16 12:10:32 +00:00
|
|
|
} else if (( idle_state == 7 ) || (idle_state == 2007)) {
|
|
|
|
bool isReset = (idle_state == 2007);
|
|
|
|
idle_state = 8; // from the next state on, reset & startup are identical
|
2012-09-18 19:29:36 +00:00
|
|
|
SGTimeStamp st;
|
|
|
|
st.stamp();
|
2021-06-24 14:26:47 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
fgCreateSubsystems(isReset);
|
|
|
|
} catch (std::exception& e) {
|
|
|
|
// attempt to trace location of illegal argument / invalid string
|
|
|
|
// position errors on startup
|
|
|
|
flightgear::sentryReportException(string{"Creating subsystems: caught:"} + e.what());
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
2012-09-18 19:29:36 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Creating subsystems took:" << st.elapsedMSec());
|
2012-09-21 17:36:25 +00:00
|
|
|
fgSplashProgress("binding-subsystems");
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-09-18 19:29:36 +00:00
|
|
|
} else if ( idle_state == 8 ) {
|
|
|
|
idle_state++;
|
|
|
|
SGTimeStamp st;
|
|
|
|
st.stamp();
|
|
|
|
globals->get_subsystem_mgr()->bind();
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO, "Binding subsystems took:" << st.elapsedMSec());
|
|
|
|
|
2012-09-21 17:36:25 +00:00
|
|
|
fgSplashProgress("init-subsystems");
|
2012-09-18 19:29:36 +00:00
|
|
|
} else if ( idle_state == 9 ) {
|
|
|
|
SGSubsystem::InitStatus status = globals->get_subsystem_mgr()->incrementalInit();
|
|
|
|
if ( status == SGSubsystem::INIT_DONE) {
|
|
|
|
++idle_state;
|
2012-09-21 17:36:25 +00:00
|
|
|
fgSplashProgress("finishing-subsystems");
|
2012-09-18 19:29:36 +00:00
|
|
|
} else {
|
2012-09-21 17:36:25 +00:00
|
|
|
fgSplashProgress("init-subsystems");
|
2003-04-15 14:10:47 +00:00
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-09-18 19:29:36 +00:00
|
|
|
} else if ( idle_state == 10 ) {
|
2012-12-15 15:25:45 +00:00
|
|
|
idle_state = 900;
|
2012-09-18 19:29:36 +00:00
|
|
|
fgPostInitSubsystems();
|
2012-12-15 15:25:45 +00:00
|
|
|
fgSplashProgress("finalize-position");
|
2012-09-18 19:29:36 +00:00
|
|
|
} else if ( idle_state == 900 ) {
|
2005-05-06 09:08:44 +00:00
|
|
|
idle_state = 1000;
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2003-04-15 14:10:47 +00:00
|
|
|
// setup OpenGL view parameters
|
2011-05-22 16:28:42 +00:00
|
|
|
globals->get_renderer()->setupView();
|
1998-07-06 02:42:02 +00:00
|
|
|
|
2004-09-20 13:21:51 +00:00
|
|
|
globals->get_renderer()->resize( fgGetInt("/sim/startup/xsize"),
|
|
|
|
fgGetInt("/sim/startup/ysize") );
|
2012-10-31 00:40:43 +00:00
|
|
|
WindowSystemAdapter::getWSA()->windows[0]->gc->add(
|
|
|
|
new simgear::canvas::VGInitOperation()
|
|
|
|
);
|
2003-09-17 10:02:36 +00:00
|
|
|
|
2009-10-24 09:22:20 +00:00
|
|
|
int session = fgGetInt("/sim/session",0);
|
|
|
|
session++;
|
|
|
|
fgSetInt("/sim/session",session);
|
2005-05-04 21:28:42 +00:00
|
|
|
}
|
|
|
|
|
2005-05-06 09:08:44 +00:00
|
|
|
if ( idle_state == 1000 ) {
|
2017-01-29 15:18:10 +00:00
|
|
|
sglog().setStartupLoggingEnabled(false);
|
|
|
|
|
2003-04-15 14:10:47 +00:00
|
|
|
// We've finished all our initialization steps, from now on we
|
|
|
|
// run the main loop.
|
2005-05-06 09:08:44 +00:00
|
|
|
fgSetBool("sim/sceneryloaded", false);
|
2020-08-11 17:32:36 +00:00
|
|
|
flightgear::registerMainLoop();
|
|
|
|
|
2019-06-11 11:44:59 +00:00
|
|
|
ngccn = new simgear::Notifications::NasalGarbageCollectionConfigurationNotification(nasal_gc_threaded->getBoolValue(), nasal_gc_threaded_wait->getBoolValue());
|
2021-04-16 20:02:11 +00:00
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(ngccn);
|
2019-06-11 11:44:59 +00:00
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_started);
|
2020-04-07 08:38:54 +00:00
|
|
|
|
|
|
|
flightgear::addSentryBreadcrumb("entering main loop", "info");
|
1998-07-06 02:42:02 +00:00
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-11-16 12:10:32 +00:00
|
|
|
if ( idle_state == 2000 ) {
|
2020-04-07 08:38:54 +00:00
|
|
|
flightgear::addSentryBreadcrumb("starting reset", "info");
|
2013-11-16 12:10:32 +00:00
|
|
|
fgStartNewReset();
|
|
|
|
idle_state = 2005;
|
|
|
|
}
|
1998-07-06 02:42:02 +00:00
|
|
|
}
|
|
|
|
|
2013-11-16 12:10:32 +00:00
|
|
|
void fgResetIdleState()
|
|
|
|
{
|
|
|
|
idle_state = 2000;
|
|
|
|
fgRegisterIdleHandler( &fgIdleFunction );
|
|
|
|
}
|
|
|
|
|
2018-02-03 13:44:26 +00:00
|
|
|
void fgInitSecureMode()
|
|
|
|
{
|
|
|
|
bool secureMode = true;
|
|
|
|
if (Options::sharedInstance()->isOptionSet("allow-nasal-from-sockets")) {
|
2020-09-06 13:57:58 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_MANDATORY_INFO, "\n!! Network connections allowed to use Nasal !!\n"
|
2018-02-03 13:44:26 +00:00
|
|
|
"Network connections will be allowed full access to the simulator \n"
|
|
|
|
"including running arbitrary scripts. Ensure you have adequate security\n"
|
|
|
|
"(such as a firewall which blocks external connections).\n");
|
|
|
|
secureMode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// it's by design that we overwrite any existing property tree value
|
|
|
|
// here - this prevents an aircraft or add-on setting the property
|
|
|
|
// value underneath us, eg in their -set.xml
|
|
|
|
SGPropertyNode_ptr secureFlag = fgGetNode("/sim/secure-flag", true);
|
|
|
|
secureFlag->setBoolValue(secureMode);
|
|
|
|
secureFlag->setAttributes(SGPropertyNode::READ |
|
|
|
|
SGPropertyNode::PRESERVE |
|
|
|
|
SGPropertyNode::PROTECTED);
|
|
|
|
}
|
2013-11-16 12:10:32 +00:00
|
|
|
|
2006-03-10 11:46:03 +00:00
|
|
|
static void upper_case_property(const char *name)
|
|
|
|
{
|
2009-07-17 12:54:12 +00:00
|
|
|
using namespace simgear;
|
2006-03-10 11:46:03 +00:00
|
|
|
SGPropertyNode *p = fgGetNode(name, false);
|
|
|
|
if (!p) {
|
|
|
|
p = fgGetNode(name, true);
|
|
|
|
p->setStringValue("");
|
|
|
|
} else {
|
2009-07-17 12:54:12 +00:00
|
|
|
props::Type t = p->getType();
|
|
|
|
if (t == props::NONE || t == props::UNSPECIFIED)
|
2006-03-10 11:46:03 +00:00
|
|
|
p->setStringValue("");
|
|
|
|
else
|
2009-07-17 12:54:12 +00:00
|
|
|
assert(t == props::STRING);
|
2006-03-10 11:46:03 +00:00
|
|
|
}
|
2013-12-04 09:11:26 +00:00
|
|
|
SGPropertyChangeListener* muc = new FGMakeUpperCase;
|
|
|
|
globals->addListenerToCleanup(muc);
|
|
|
|
p->addChangeListener(muc);
|
2006-03-10 11:46:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-12 10:13:14 +00:00
|
|
|
// this hack is needed to avoid weird viewport sizing within OSG on Windows.
|
|
|
|
// still required as of March 2017, sad times.
|
|
|
|
// see for example https://sourceforge.net/p/flightgear/codetickets/1958/
|
|
|
|
static void ATIScreenSizeHack()
|
|
|
|
{
|
|
|
|
osg::ref_ptr<osg::Camera> hackCam = new osg::Camera;
|
|
|
|
hackCam->setRenderOrder(osg::Camera::PRE_RENDER);
|
|
|
|
int prettyMuchAnyInt = 1;
|
|
|
|
hackCam->setViewport(0, 0, prettyMuchAnyInt, prettyMuchAnyInt);
|
|
|
|
globals->get_renderer()->addCamera(hackCam, false);
|
|
|
|
}
|
|
|
|
|
2016-09-27 22:16:46 +00:00
|
|
|
// Propose NVIDIA Optimus / AMD Xpress to use high-end GPU
|
2013-07-15 19:47:14 +00:00
|
|
|
#if defined(SG_WINDOWS)
|
|
|
|
extern "C" {
|
|
|
|
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
2016-09-27 22:16:46 +00:00
|
|
|
_declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
2013-07-15 19:47:14 +00:00
|
|
|
}
|
|
|
|
#endif
|
2013-02-08 11:43:51 +00:00
|
|
|
|
2020-06-26 09:13:13 +00:00
|
|
|
static void rotateOldLogFiles()
|
|
|
|
{
|
|
|
|
const int maxLogCount = 10;
|
|
|
|
const auto homePath = globals->get_fg_home();
|
|
|
|
|
|
|
|
for (int i = maxLogCount; i > 0; --i) {
|
|
|
|
const auto name = "fgfs_" + std::to_string(i - 1) + ".log";
|
|
|
|
SGPath curLogFile = homePath / name;
|
|
|
|
if (curLogFile.exists()) {
|
|
|
|
auto newName = "fgfs_" + std::to_string(i) + ".log";
|
|
|
|
curLogFile.rename(homePath / newName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SGPath p = homePath / "fgfs.log";
|
|
|
|
if (!p.exists())
|
|
|
|
return;
|
|
|
|
SGPath log0Path = homePath / "fgfs_0.log";
|
2020-10-29 13:44:52 +00:00
|
|
|
if (!p.rename(log0Path)) {
|
|
|
|
std::cerr << "Failed to rename " << p.str() << " to " << log0Path.str() << std::endl;
|
|
|
|
}
|
2020-06-26 09:13:13 +00:00
|
|
|
}
|
|
|
|
|
2020-06-25 10:01:43 +00:00
|
|
|
static void logToHome(const std::string& pri)
|
2013-02-08 11:43:51 +00:00
|
|
|
{
|
2020-06-25 10:01:43 +00:00
|
|
|
sgDebugPriority fileLogLevel = SG_INFO;
|
|
|
|
// https://sourceforge.net/p/flightgear/codetickets/2100/
|
|
|
|
if (!pri.empty()) {
|
|
|
|
try {
|
|
|
|
fileLogLevel = std::min(fileLogLevel, logstream::priorityFromString(pri));
|
2021-04-16 20:02:11 +00:00
|
|
|
} catch (std::exception& ) {
|
2020-06-25 10:01:43 +00:00
|
|
|
// let's not worry about this, and just log at INFO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-08 11:43:51 +00:00
|
|
|
SGPath logPath = globals->get_fg_home();
|
|
|
|
logPath.append("fgfs.log");
|
|
|
|
if (logPath.exists()) {
|
2020-06-26 09:13:13 +00:00
|
|
|
rotateOldLogFiles();
|
2013-02-08 11:43:51 +00:00
|
|
|
}
|
2020-06-25 10:01:43 +00:00
|
|
|
|
|
|
|
sglog().logToFile(logPath, SG_ALL, fileLogLevel);
|
2013-02-08 11:43:51 +00:00
|
|
|
}
|
|
|
|
|
2020-12-19 15:07:57 +00:00
|
|
|
struct SGLogDeltasListener : SGPropertyChangeListener
|
|
|
|
{
|
|
|
|
void valueChanged(SGPropertyNode* node) override
|
|
|
|
{
|
|
|
|
const char* value = node->getStringValue();
|
|
|
|
std::cerr << __FILE__ << ":" << __LINE__ << ": sglogdeltas value=" << value << "\n";
|
|
|
|
logDeltaSet(value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
static SGLogDeltasListener s_sglogdeltas_listener;
|
|
|
|
|
2002-11-16 20:17:11 +00:00
|
|
|
// Main top level initialization
|
2014-12-26 12:20:51 +00:00
|
|
|
int fgMainInit( int argc, char **argv )
|
|
|
|
{
|
2020-09-14 10:05:32 +00:00
|
|
|
sglog().setLogLevels( SG_ALL, SG_WARN );
|
2017-01-29 15:18:10 +00:00
|
|
|
sglog().setStartupLoggingEnabled(true);
|
2019-08-12 16:52:53 +00:00
|
|
|
|
2013-02-08 11:43:51 +00:00
|
|
|
globals = new FGGlobals;
|
2020-05-03 17:02:20 +00:00
|
|
|
auto initHomeResult = fgInitHome();
|
|
|
|
if (initHomeResult == InitHomeAbort) {
|
|
|
|
flightgear::fatalMessageBoxThenExit("Unable to create lock file",
|
|
|
|
"Flightgear was unable to create the lock file in FG_HOME");
|
2013-11-14 16:48:14 +00:00
|
|
|
}
|
2020-03-17 09:47:59 +00:00
|
|
|
|
2020-10-29 13:44:52 +00:00
|
|
|
std::cerr << "DidInitHome" << std::endl;
|
|
|
|
|
|
|
|
|
2020-03-17 09:47:59 +00:00
|
|
|
#if defined(HAVE_QT)
|
|
|
|
flightgear::initApp(argc, argv);
|
|
|
|
#endif
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2020-05-03 17:02:20 +00:00
|
|
|
// check if the launcher is requested, since it affects config file parsing
|
|
|
|
bool showLauncher = flightgear::Options::checkForArg(argc, argv, "launcher");
|
|
|
|
// an Info.plist bundle can't define command line arguments, but it can set
|
|
|
|
// environment variables. This avoids needed a wrapper shell-script on OS-X.
|
|
|
|
showLauncher |= (::getenv("FG_LAUNCHER") != nullptr);
|
|
|
|
|
2020-05-04 20:29:23 +00:00
|
|
|
#if defined(HAVE_QT)
|
2020-05-03 17:02:20 +00:00
|
|
|
if (showLauncher && (initHomeResult == InitHomeReadOnly)) {
|
|
|
|
// show this message early, if we can
|
|
|
|
auto r = flightgear::showLockFileDialog();
|
|
|
|
if (r == flightgear::LockFileReset) {
|
2020-09-06 13:57:58 +00:00
|
|
|
SG_LOG( SG_GENERAL, SG_MANDATORY_INFO, "Deleting lock file at user request");
|
2020-12-06 18:48:01 +00:00
|
|
|
flightgear::addSentryBreadcrumb("deleting lock-file at user request", "info");
|
2020-05-03 17:02:20 +00:00
|
|
|
fgDeleteLockFile();
|
|
|
|
fgSetBool("/sim/fghome-readonly", false);
|
2020-05-07 15:49:50 +00:00
|
|
|
} else if (r == flightgear::LockFileQuit) {
|
|
|
|
return EXIT_SUCCESS;
|
2020-05-03 17:02:20 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-04 20:29:23 +00:00
|
|
|
#endif
|
|
|
|
|
2020-12-19 15:07:57 +00:00
|
|
|
{
|
|
|
|
SGPropertyNode* sglogdeltas = globals->get_props()->getNode("/sim/sg-log-deltas", true /*create*/);
|
|
|
|
assert(sglogdeltas);
|
|
|
|
sglogdeltas->addChangeListener(&s_sglogdeltas_listener, false /*initial*/);
|
|
|
|
const char* sglogdeltas_value = getenv("SG_LOG_DELTAS");
|
|
|
|
if (sglogdeltas_value) {
|
|
|
|
sglogdeltas->setStringValue(sglogdeltas_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 18:03:10 +00:00
|
|
|
globals->get_props()->getNode("/sim", true /*create*/)->setAttribute(SGPropertyNode::VALUE_CHANGED_DOWN, true);
|
|
|
|
|
2021-05-22 10:56:22 +00:00
|
|
|
{
|
2021-05-26 09:16:04 +00:00
|
|
|
SGPropertyNode* active = globals->get_props()->getNode("/sim/property-locking/active", true /*create*/);
|
|
|
|
SGPropertyNode* verbose = globals->get_props()->getNode("/sim/property-locking/verbose", true /*create*/);
|
|
|
|
SGPropertyNode* timing = globals->get_props()->getNode("/sim/property-locking/timing", true /*create*/);
|
2021-06-02 18:03:43 +00:00
|
|
|
SGPropertyNode* parent_listeners = globals->get_props()->getNode("/sim/property-locking/parent_listeners", true /*create*/);
|
|
|
|
SGPropertyLockControl(active, verbose, timing, parent_listeners);
|
2021-05-22 10:56:22 +00:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:22:33 +00:00
|
|
|
const bool readOnlyFGHome = fgGetBool("/sim/fghome-readonly");
|
|
|
|
if (!readOnlyFGHome) {
|
2013-11-14 16:48:14 +00:00
|
|
|
// now home is initialised, we can log to a file inside it
|
2020-06-25 10:01:43 +00:00
|
|
|
const auto level = flightgear::Options::getArgValue(argc, argv, "--log-level");
|
|
|
|
logToHome(level);
|
2020-10-29 13:44:52 +00:00
|
|
|
std::cerr << "DidLogToHome" << std::endl;
|
2013-11-14 16:48:14 +00:00
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
if (readOnlyFGHome) {
|
|
|
|
flightgear::addSentryTag("fghome-readonly", "true");
|
2020-10-29 13:44:52 +00:00
|
|
|
std::cerr << "Read-Only-Home" << std::endl;
|
2020-04-07 08:38:54 +00:00
|
|
|
}
|
|
|
|
|
2015-06-07 19:08:03 +00:00
|
|
|
std::string version(FLIGHTGEAR_VERSION);
|
2018-02-03 13:44:26 +00:00
|
|
|
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version " << version );
|
2017-02-27 23:15:07 +00:00
|
|
|
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Build Type " << FG_BUILD_TYPE );
|
2014-01-20 21:54:06 +00:00
|
|
|
SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR);
|
2018-08-11 09:09:11 +00:00
|
|
|
SG_LOG( SG_GENERAL, SG_INFO, "Jenkins number/ID " << JENKINS_BUILD_NUMBER << ":"
|
|
|
|
<< JENKINS_BUILD_ID);
|
1998-11-20 01:02:35 +00:00
|
|
|
|
2020-04-07 08:38:54 +00:00
|
|
|
flightgear::addSentryTag("osg-version", osgGetVersion());
|
|
|
|
|
2019-08-12 16:52:53 +00:00
|
|
|
#ifdef __OpenBSD__
|
|
|
|
{
|
|
|
|
/* OpenBSD defaults to a small maximum data segment, which can cause
|
|
|
|
flightgear to crash with SIGBUS, so output a warning if this is likely.
|
|
|
|
*/
|
|
|
|
struct rlimit rlimit;
|
|
|
|
int e = getrlimit(RLIMIT_DATA, &rlimit);
|
|
|
|
if (e) {
|
|
|
|
SG_LOG( SG_GENERAL, SG_INFO, "This is OpenBSD; getrlimit() failed: " << strerror(errno));
|
|
|
|
}
|
|
|
|
else {
|
2020-09-11 08:49:07 +00:00
|
|
|
unsigned long long required = 4ULL * (1ULL<<30);
|
2019-08-12 16:52:53 +00:00
|
|
|
if (rlimit.rlim_cur < required) {
|
|
|
|
SG_LOG( SG_GENERAL, SG_POPUP, ""
|
|
|
|
<< "Max data segment (" << rlimit.rlim_cur << "bytes) too small.\n"
|
|
|
|
<< "This can cause Flightgear to crash due to SIGBUS.\n"
|
|
|
|
<< "E.g. increase with 'ulimit -d " << required/1024 << "'."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2006-08-07 15:16:46 +00:00
|
|
|
// seed the random number generator
|
2001-01-13 22:06:39 +00:00
|
|
|
sg_srandom_time();
|
|
|
|
|
2001-01-26 00:21:36 +00:00
|
|
|
string_list *col = new string_list;
|
|
|
|
globals->set_channel_options_list( col );
|
|
|
|
|
2015-07-13 22:15:36 +00:00
|
|
|
fgValidatePath(globals->get_fg_home(), false); // initialize static variables
|
2006-03-10 11:46:03 +00:00
|
|
|
upper_case_property("/sim/presets/airport-id");
|
|
|
|
upper_case_property("/sim/presets/runway");
|
|
|
|
upper_case_property("/sim/tower/airport-id");
|
2008-06-06 07:49:41 +00:00
|
|
|
upper_case_property("/autopilot/route-manager/input");
|
2006-03-10 11:46:03 +00:00
|
|
|
|
2016-09-08 10:41:40 +00:00
|
|
|
if (showLauncher) {
|
|
|
|
// to minimise strange interactions when launcher and config files
|
|
|
|
// set overlaping options, we disable the default files. Users can
|
|
|
|
// still explicitly request config files via --config options if they choose.
|
|
|
|
flightgear::Options::sharedInstance()->setShouldLoadDefaultConfig(false);
|
|
|
|
}
|
|
|
|
|
2002-11-16 20:17:11 +00:00
|
|
|
// Load the configuration parameters. (Command line options
|
2006-08-07 15:16:46 +00:00
|
|
|
// override config file options. Config file options override
|
2002-11-16 20:17:11 +00:00
|
|
|
// defaults.)
|
2013-11-16 12:10:32 +00:00
|
|
|
int configResult = fgInitConfig(argc, argv, false);
|
2013-10-15 21:16:50 +00:00
|
|
|
if (configResult == flightgear::FG_OPTIONS_ERROR) {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
} else if (configResult == flightgear::FG_OPTIONS_EXIT) {
|
|
|
|
return EXIT_SUCCESS;
|
1998-04-24 00:49:17 +00:00
|
|
|
}
|
2014-12-26 12:20:51 +00:00
|
|
|
|
|
|
|
#if defined(HAVE_QT)
|
2015-03-06 18:52:06 +00:00
|
|
|
if (showLauncher) {
|
2020-04-07 08:38:54 +00:00
|
|
|
flightgear::addSentryBreadcrumb("starting launcher", "info");
|
2015-10-16 01:05:17 +00:00
|
|
|
if (!flightgear::runLauncherDialog()) {
|
2014-12-26 12:20:51 +00:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
2020-04-07 08:38:54 +00:00
|
|
|
|
|
|
|
flightgear::addSentryBreadcrumb("completed launcher", "info");
|
2014-12-26 12:20:51 +00:00
|
|
|
}
|
2017-03-01 15:27:33 +00:00
|
|
|
#else
|
|
|
|
if (showLauncher) {
|
2018-02-03 13:44:26 +00:00
|
|
|
SG_LOG(SG_GENERAL, SG_ALERT, "\n!Launcher requested, but FlightGear was compiled without Qt support!\n");
|
2017-03-01 15:27:33 +00:00
|
|
|
}
|
2014-12-26 12:20:51 +00:00
|
|
|
#endif
|
2018-02-03 13:44:26 +00:00
|
|
|
|
|
|
|
fgInitSecureMode();
|
2016-03-24 15:10:06 +00:00
|
|
|
fgInitAircraftPaths(false);
|
2014-12-26 12:20:51 +00:00
|
|
|
|
2021-04-02 09:16:20 +00:00
|
|
|
auto errorManager = globals->add_new_subsystem<flightgear::ErrorReporter>(SGSubsystemMgr::GENERAL);
|
2021-02-09 16:56:12 +00:00
|
|
|
errorManager->preinit();
|
|
|
|
|
2013-11-23 19:58:45 +00:00
|
|
|
configResult = fgInitAircraft(false);
|
|
|
|
if (configResult == flightgear::FG_OPTIONS_ERROR) {
|
|
|
|
return EXIT_FAILURE;
|
2020-04-29 13:55:37 +00:00
|
|
|
} else if ((configResult == flightgear::FG_OPTIONS_EXIT) ||
|
|
|
|
(configResult == flightgear::FG_OPTIONS_SHOW_AIRCRAFT))
|
|
|
|
{
|
2013-11-23 19:58:45 +00:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2017-12-15 22:53:46 +00:00
|
|
|
addons::AddonManager::createInstance();
|
Improved infrastructure for add-ons: C++ classes, metadata file, Nasal interface
This commit adds C++ classes for add-on management, most notably
AddonManager, Addon and AddonVersion. The AddonManager is used to
register add-ons. It relies on an std::map<std::string, AddonRef> to
hold the metadata of each registered add-on (keys of the std::map are
add-on identifiers, and AddonRef is currently SGSharedPtr<Addon>).
Accessor methods are available for:
- retrieving the list of registered or loaded add-ons (terminology
explained in $FG_ROOT/Docs/README.add-ons);
- checking if a particular add-on has already been registered or
loaded;
- for each add-on, obtaining an Addon instance which can be queried
for its name, id, version, base path, the minimum and maximum
FlightGear versions it requires, its base node in the Property Tree,
its order in the load sequence, short and long description strings,
home page, etc.
The most important metadata is made accessible in the Property Tree
under /addons/by-id/<addon-id> and the property
/addons/by-id/<addon-id>/loaded can be checked or listened to, in
order to determine when a particular add-on is loaded. There is also a
Nasal interface to access add-on metadata in a convenient way.
In order to provide this metadata, each add-on must from now on have in
its base directory a file called 'addon-metadata.xml'.
All this is documented in much more detail in
$FG_ROOT/Docs/README.add-ons.
Mailing-list discussion:
https://sourceforge.net/p/flightgear/mailman/message/36146017/
2017-11-06 12:58:14 +00:00
|
|
|
|
2013-11-23 19:58:45 +00:00
|
|
|
configResult = flightgear::Options::sharedInstance()->processOptions();
|
|
|
|
if (configResult == flightgear::FG_OPTIONS_ERROR) {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
} else if (configResult == flightgear::FG_OPTIONS_EXIT) {
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2017-08-26 15:49:30 +00:00
|
|
|
// Set the lists of allowed paths for cases where a path comes from an
|
|
|
|
// untrusted source, such as the global property tree (this uses $FG_HOME
|
|
|
|
// and other paths set by Options::processOptions()).
|
|
|
|
fgInitAllowedPaths();
|
|
|
|
|
Integrate the EmbeddedResourceManager into FlightGear
${CMAKE_SOURCE_DIR}/src/EmbeddedResources/FlightGear-resources.xml
(currently empty) is automatically "compiled" into
${CMAKE_BINARY_DIR}/src/EmbeddedResources/FlightGear-resources.[ch]xx by
fgrcc inside the build directory. These files are incorporated into the
FlightGear build (FlightGear-resources.cxx is linked into FlightGear).
When the XML embedded resource declaration file added here,
FlightGear-resources.xml, is compiled, fgrcc is passed the
--root=${CMAKE_SOURCE_DIR} option, so that files referred to in
FlightGear-resources.xml are looked up relatively to the root directory
of the FlightGear repository. One could use a second XML embedded
resource declaration file compiled with a different --root option to
grab files from FGData, for instance. I would name such a file
FGData-resources.xml to be consistent with the current naming scheme.
Note: this --root option applies to the paths of real files. Don't
confuse it with the 'prefix' attribute of <qresource> elements
inside XML resource declaration files (such as
FlightGear-resources.xml), which applies to the virtual path of
each resource defined beneath.
The commands in src/Main/CMakeLists.txt ensure that
FlightGear-resources.xml is recompiled with fgrcc whenever it is
changed, and obviously also when FlightGear-resources.cxx or
FlightGear-resources.hxx is missing. However, CMake doesn't know how to
parse fgrcc XML resource declaration files, therefore when a resource is
modified but the XML file it is declared in is not (here,
FlightGear-resources.xml), you have to trigger yourself a recompilation
of the XML resource declaration file to see the new resource contents
inside FlightGear. The easiest ways to do so are:
- either update the timestamp of the XML resource declaration file;
- or remove one or both of the generated files
(FlightGear-resources.cxx and FlightGear-resources.hxx here).
The EmbeddedResourceManager is created in fgMainInit() just after
Options::processOptions() set the language that was either requested by
the user or obtained from the system (locales). Resources from
FlightGear-resources.cxx are added to it, after which
EmbeddedResourceManager::selectLocale() is called with the user's
preferred locale (obtained with FGLocale::getPreferredLanguage()).
Upon reset (fgStartNewReset()), EmbeddedResourceManager::selectLocale()
is called in a similar way after Options::processOptions(), however in
this case the EmbeddedResourceManager instance doesn't have to be
recreated.
2017-04-27 22:09:14 +00:00
|
|
|
const auto& resMgr = simgear::EmbeddedResourceManager::createInstance();
|
|
|
|
initFlightGearEmbeddedResources();
|
|
|
|
// The language was set in processOptions()
|
|
|
|
const std::string locale = globals->get_locale()->getPreferredLanguage();
|
|
|
|
// Must always be done after all resources have been added to 'resMgr'
|
|
|
|
resMgr->selectLocale(locale);
|
|
|
|
SG_LOG(SG_GENERAL, SG_INFO,
|
|
|
|
"EmbeddedResourceManager: selected locale '" << locale << "'");
|
|
|
|
|
2020-06-17 09:57:34 +00:00
|
|
|
if (fgGetBool("/sim/autosave-migration/did-migrate", false)) {
|
|
|
|
// inform the user we did migration. This is the earliest point
|
|
|
|
// we can do it, since now the locale is set
|
|
|
|
auto locale = globals->get_locale();
|
|
|
|
const auto title = locale->getLocalizedString("settings-migration-title", "sys", "Settings migrated");
|
|
|
|
const auto msg = locale->getLocalizedString("settings-migration-text", "sys",
|
|
|
|
"Saved settings were migrated from a previous version of FlightGear. "
|
|
|
|
"If you encounter any problems when using the system, try restoring "
|
|
|
|
"the default settings, before reporting a problem. "
|
|
|
|
"Saved settings can affect the appearance, performance and features of the simulator.");
|
|
|
|
flightgear::modalMessageBox(title, msg);
|
|
|
|
}
|
|
|
|
|
2018-02-27 00:17:30 +00:00
|
|
|
// Copy the property nodes for the menus added by registered add-ons
|
|
|
|
addons::AddonManager::instance()->addAddonMenusToFGMenubar();
|
|
|
|
|
2020-11-14 14:31:18 +00:00
|
|
|
auto presets = globals->add_new_subsystem<flightgear::GraphicsPresets>(SGSubsystemMgr::DISPLAY);
|
|
|
|
presets->applyInitialPreset();
|
|
|
|
|
1998-08-20 15:10:33 +00:00
|
|
|
// Initialize the Window/Graphics environment.
|
2020-11-14 14:31:18 +00:00
|
|
|
|
2004-03-31 21:10:32 +00:00
|
|
|
fgOSInit(&argc, argv);
|
2004-08-24 08:40:41 +00:00
|
|
|
_bootstrap_OSInit++;
|
1998-08-20 15:10:33 +00:00
|
|
|
|
2004-12-20 08:36:56 +00:00
|
|
|
fgRegisterIdleHandler( &fgIdleFunction );
|
2004-03-31 21:10:32 +00:00
|
|
|
|
2010-10-23 19:37:26 +00:00
|
|
|
// Initialize sockets (WinSock needs this)
|
|
|
|
simgear::Socket::initSockets();
|
2005-05-04 21:28:42 +00:00
|
|
|
|
2004-03-31 21:10:32 +00:00
|
|
|
// Clouds3D requires an alpha channel
|
2010-07-01 18:51:05 +00:00
|
|
|
fgOSOpenWindow(true /* request stencil buffer */);
|
2013-11-16 12:10:21 +00:00
|
|
|
fgOSResetProperties();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2005-05-08 14:32:56 +00:00
|
|
|
fntInit();
|
2016-11-18 16:53:23 +00:00
|
|
|
globals->get_renderer()->preinit();
|
2004-05-20 13:27:40 +00:00
|
|
|
|
2017-04-12 10:13:14 +00:00
|
|
|
if (fgGetBool("/sim/ati-viewport-hack", true)) {
|
|
|
|
SG_LOG(SG_GENERAL, SG_WARN, "Enabling ATI/AMD viewport hack");
|
2020-04-07 08:38:54 +00:00
|
|
|
flightgear::addSentryTag("ati-viewport-hack", "enabled");
|
2017-04-12 10:13:14 +00:00
|
|
|
ATIScreenSizeHack();
|
|
|
|
}
|
|
|
|
|
2013-02-08 13:15:20 +00:00
|
|
|
fgOutputSettings();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2013-12-09 00:13:44 +00:00
|
|
|
//try to disable the screensaver
|
|
|
|
fgOSDisableScreensaver();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2004-10-17 17:29:34 +00:00
|
|
|
// pass control off to the master event handler
|
2010-09-29 20:04:11 +00:00
|
|
|
int result = fgOSMainLoop();
|
2020-08-11 17:32:36 +00:00
|
|
|
flightgear::unregisterMainLoopProperties();
|
|
|
|
|
2013-11-12 22:26:37 +00:00
|
|
|
fgOSCloseWindow();
|
2020-03-17 16:05:24 +00:00
|
|
|
fgShutdownHome();
|
2021-06-14 09:49:29 +00:00
|
|
|
|
|
|
|
const bool requestLauncherRestart = fgGetBool("/sim/restart-launcher-on-exit");
|
|
|
|
|
|
|
|
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_stopped);
|
2019-06-11 11:44:59 +00:00
|
|
|
|
2013-12-09 23:25:04 +00:00
|
|
|
simgear::clearEffectCache();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2010-09-29 20:04:11 +00:00
|
|
|
// clean up here; ensure we null globals to avoid
|
|
|
|
// confusing the atexit() handler
|
|
|
|
delete globals;
|
2018-08-11 09:09:11 +00:00
|
|
|
globals = nullptr;
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2012-08-27 23:26:36 +00:00
|
|
|
// delete the NavCache here. This will cause the destruction of many cached
|
|
|
|
// objects (eg, airports, navaids, runways).
|
|
|
|
delete flightgear::NavDataCache::instance();
|
2017-03-21 20:59:35 +00:00
|
|
|
|
2021-06-14 09:49:29 +00:00
|
|
|
#if defined(HAVE_QT)
|
|
|
|
if (requestLauncherRestart) {
|
|
|
|
string_list originalArgs;
|
|
|
|
for (int arg = 1; arg < argc; ++arg) {
|
|
|
|
originalArgs.push_back(argv[arg]);
|
|
|
|
}
|
|
|
|
flightgear::addSentryBreadcrumb("Requested to restart launcher", "info");
|
|
|
|
flightgear::startLaunchOnExit(originalArgs);
|
|
|
|
}
|
|
|
|
#endif
|
2010-09-29 20:04:11 +00:00
|
|
|
return result;
|
1998-04-21 17:02:27 +00:00
|
|
|
}
|