From 3135d872183448bb3e5146c8c6bb713269b4f24c Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 20 Mar 2017 22:27:10 +0000 Subject: [PATCH 1/2] Stop TTS from playing over itself. Simple blocking of the SGSubsystem until the current TTS sample has finished playing, allowing a 0.5s gap between transmissions. Good radio comms! --- src/Sound/flitevoice.cxx | 25 ++++++++++++++++--------- src/Sound/flitevoice.hxx | 3 ++- src/Sound/voice.cxx | 10 ++++------ src/Sound/voice.hxx | 2 +- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Sound/flitevoice.cxx b/src/Sound/flitevoice.cxx index c38e96154..9cf0e4687 100644 --- a/src/Sound/flitevoice.cxx +++ b/src/Sound/flitevoice.cxx @@ -33,7 +33,7 @@ using std::string; #include "VoiceSynthesizer.hxx" FGFLITEVoice::FGFLITEVoice(FGVoiceMgr * mgr, const SGPropertyNode_ptr node, const char * sampleGroupRefName) - : FGVoice(mgr), _synthesizer( NULL) + : FGVoice(mgr), _synthesizer( NULL), _seconds_to_run(0.0) { _sampleName = node->getStringValue("desc", node->getPath().c_str()); @@ -68,14 +68,21 @@ void FGFLITEVoice::speak(const string & msg) } } -void FGFLITEVoice::update() +void FGFLITEVoice::update(double dt) { - SGSharedPtr sample = _sampleQueue.pop(); - if (sample.valid()) { - _sgr->remove(_sampleName); - _sgr->add(sample, _sampleName); - _sgr->resume(); - _sgr->play(_sampleName, false); + _seconds_to_run -= dt; + + if (_seconds_to_run < 0.0) { + SGSharedPtr sample = _sampleQueue.pop(); + if (sample.valid()) { + _sgr->remove(_sampleName); + _sgr->add(sample, _sampleName); + _sgr->resume(); + _sgr->play(_sampleName, false); + + // Don't play any further TTS until we've finished playing this sample, + // allowing 500ms for a gap between transmissions. Good radio comms! + _seconds_to_run = 0.5 + ((float) sample->get_no_samples()) / ((float) sample->get_frequency()); + } } } - diff --git a/src/Sound/flitevoice.hxx b/src/Sound/flitevoice.hxx index 18ed0de27..7891458fa 100644 --- a/src/Sound/flitevoice.hxx +++ b/src/Sound/flitevoice.hxx @@ -35,7 +35,7 @@ public: FGFLITEVoice(FGVoiceMgr *, const SGPropertyNode_ptr, const char * sampleGroupRefName = "flite-voice"); virtual ~FGFLITEVoice(); virtual void speak(const std::string & msg); - virtual void update(); + virtual void update(double dt); private: FGFLITEVoice(const FGFLITEVoice & other); @@ -45,6 +45,7 @@ private: VoiceSynthesizer * _synthesizer; SGLockedQueue > _sampleQueue; std::string _sampleName; + double _seconds_to_run; }; #endif // _FLITEVOICE_HXX diff --git a/src/Sound/voice.cxx b/src/Sound/voice.cxx index 8817efe81..a3362c9ba 100644 --- a/src/Sound/voice.cxx +++ b/src/Sound/voice.cxx @@ -43,7 +43,7 @@ public: FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr); virtual ~FGFestivalVoice(); virtual void speak( const string & msg ); - virtual void update(); + virtual void update(double); void setVolume(double); void setPitch(double); void setSpeed(double); @@ -129,14 +129,14 @@ void FGVoiceMgr::shutdown() } -void FGVoiceMgr::update(double) +void FGVoiceMgr::update(double dt) { if (!_enabled) return; _paused = !_pausedNode->getBoolValue(); for (unsigned int i = 0; i < _voices.size(); i++) { - _voices[i]->update(); + _voices[i]->update(dt); #if !defined(ENABLE_THREADS) _voices[i]->speak(); #endif @@ -235,7 +235,7 @@ void FGFestivalVoice::speak( const string & msg ) } -void FGFestivalVoice::update(void) +void FGFestivalVoice::update(double dt) { double d; d = _volumeNode->getDoubleValue(); @@ -333,5 +333,3 @@ void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node) pushMessage(m); } - - diff --git a/src/Sound/voice.hxx b/src/Sound/voice.hxx index eabd00a40..5625a9784 100644 --- a/src/Sound/voice.hxx +++ b/src/Sound/voice.hxx @@ -99,7 +99,7 @@ public: FGVoice(FGVoiceMgr * mgr ) : _mgr(mgr) {} virtual ~FGVoice() {} virtual void speak( const std::string & msg ) = 0; - virtual void update() = 0; + virtual void update(double dt) = 0; void pushMessage( const std::string & m); bool speak(); From 74e28492937ac66d4fda0375c07388b384917ef7 Mon Sep 17 00:00:00 2001 From: James Turner Date: Tue, 21 Mar 2017 21:59:35 +0100 Subject: [PATCH 2/2] Move some commands to a separate file. This simplifies building a subset of the code for testing. --- src/Main/CMakeLists.txt | 1 + src/Main/fg_commands.cxx | 525 ++----------------------------- src/Main/fg_commands.hxx | 2 + src/Main/fg_scene_commands.cxx | 555 +++++++++++++++++++++++++++++++++ src/Main/main.cxx | 59 ++-- 5 files changed, 611 insertions(+), 531 deletions(-) create mode 100644 src/Main/fg_scene_commands.cxx diff --git a/src/Main/CMakeLists.txt b/src/Main/CMakeLists.txt index e568aa644..640c1ce7a 100644 --- a/src/Main/CMakeLists.txt +++ b/src/Main/CMakeLists.txt @@ -6,6 +6,7 @@ endif (MSVC) set(SOURCES bootstrap.cxx fg_commands.cxx + fg_scene_commands.cxx fg_init.cxx fg_io.cxx fg_os_common.cxx diff --git a/src/Main/fg_commands.cxx b/src/Main/fg_commands.cxx index 3e4761336..4ed34d63e 100644 --- a/src/Main/fg_commands.cxx +++ b/src/Main/fg_commands.cxx @@ -15,31 +15,24 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include -#include -#include -#include #include -#include #include -#include #include #include #include #include #include #include +#include #include "fg_init.hxx" #include "fg_io.hxx" @@ -58,10 +51,6 @@ # include #endif -#if defined(HAVE_QT) -#include -#endif - using std::string; using std::ifstream; using std::ofstream; @@ -121,7 +110,7 @@ limit_value (double * value, const SGPropertyNode * arg) if (min_node == 0 || max_node == 0) wrap = false; - + if (wrap) { // wrap such that min <= x < max double min_val = min_node->getDoubleValue(); double max_val = max_node->getDoubleValue(); @@ -188,54 +177,6 @@ do_nasal (const SGPropertyNode * arg) return ((FGNasalSys*)globals->get_subsystem("nasal"))->handleCommand(arg); } -/** - * Built-in command: exit FlightGear. - * - * status: the exit status to return to the operating system (defaults to 0) - */ -static bool -do_exit (const SGPropertyNode * arg) -{ - SG_LOG(SG_INPUT, SG_INFO, "Program exit requested."); - fgSetBool("/sim/signals/exit", true); - globals->saveUserSettings(); - fgOSExit(arg->getIntValue("status", 0)); - return true; -} - - -/** - * Reset FlightGear (Shift-Escape or Menu->File->Reset) - */ -static bool -do_reset (const SGPropertyNode * arg) -{ - fgResetIdleState(); - return true; -} - - -/** - * Change aircraft - */ -static bool -do_switch_aircraft (const SGPropertyNode * arg) -{ - fgSetString("/sim/aircraft", arg->getStringValue("aircraft")); - // start a reset - fgResetIdleState(); - return true; -} - -/** - */ -static bool -do_reposition (const SGPropertyNode * arg) -{ - fgStartReposition(); - return true; -} - /** * Built-in command: replay the FDR buffer */ @@ -269,7 +210,7 @@ do_pause (const SGPropertyNode * arg) fgSetBool("/sim/freeze/master",!paused); fgSetBool("/sim/freeze/clock",!paused); } - + syncPausePopupState(); return true; } @@ -366,42 +307,6 @@ do_load_tape (const SGPropertyNode * arg) return true; } -/** - * Built-in command: (re)load the panel. - * - * path (optional): the file name to load the panel from - * (relative to FG_ROOT). Defaults to the value of /sim/panel/path, - * and if that's unspecified, to "Panels/Default/default.xml". - */ -static bool -do_panel_load (const SGPropertyNode * arg) -{ - string panel_path = arg->getStringValue("path"); - if (!panel_path.empty()) { - // write to the standard property, which will force a load - fgSetString("/sim/panel/path", panel_path.c_str()); - } - - return true; -} - -/** - * Built-in command: (re)load preferences. - * - * path (optional): the file name to load the panel from (relative - * to FG_ROOT). Defaults to "preferences.xml". - */ -static bool -do_preferences_load (const SGPropertyNode * arg) -{ - // disabling this command which was formerly used to reload 'preferences.xml' - // reloading the defaults doesn't make sense (better to reset the simulator), - // and loading arbitrary XML docs into the property-tree can be done via - // the standard load-xml command. - SG_LOG(SG_GENERAL, SG_ALERT, "preferences-load command is deprecated and non-functional"); - return false; -} - static void do_view_next(bool do_it) { @@ -424,17 +329,6 @@ do_view_prev(bool do_it) } } -/** - * An fgcommand to toggle fullscreen mode. - * No parameters. - */ -static bool -do_toggle_fullscreen(const SGPropertyNode *arg) -{ - fgOSFullScreen(); - return true; -} - /** * Built-in command: cycle view. */ @@ -446,112 +340,6 @@ do_view_cycle (const SGPropertyNode * arg) return true; } -/** - * Built-in command: capture screen. - */ -static bool -do_screen_capture (const SGPropertyNode * arg) -{ - return fgDumpSnapShot(); -} - -static bool -do_reload_shaders (const SGPropertyNode*) -{ - simgear::reload_shaders(); - return true; -} - -static bool -do_dump_scene_graph (const SGPropertyNode*) -{ - fgDumpSceneGraph(); - return true; -} - -static bool -do_dump_terrain_branch (const SGPropertyNode*) -{ - fgDumpTerrainBranch(); - - double lon_deg = fgGetDouble("/position/longitude-deg"); - double lat_deg = fgGetDouble("/position/latitude-deg"); - SGGeod geodPos = SGGeod::fromDegFt(lon_deg, lat_deg, 0.0); - SGVec3d zero = SGVec3d::fromGeod(geodPos); - - SG_LOG(SG_INPUT, SG_INFO, "Model parameters:"); - SG_LOG(SG_INPUT, SG_INFO, "Center: " << zero.x() << ", " << zero.y() << ", " << zero.z() ); - SG_LOG(SG_INPUT, SG_INFO, "Rotation: " << lat_deg << ", " << lon_deg ); - - return true; -} - -static bool -do_print_visible_scene_info(const SGPropertyNode*) -{ - fgPrintVisibleSceneInfoCommand(); - return true; -} - -/** - * Built-in command: hires capture screen. - */ -static bool -do_hires_screen_capture (const SGPropertyNode * arg) -{ - fgHiResDump(); - return true; -} - - -/** - * Reload the tile cache. - */ -static bool -do_tile_cache_reload (const SGPropertyNode * arg) -{ - SGPropertyNode *master_freeze = fgGetNode("/sim/freeze/master"); - bool freeze = master_freeze->getBoolValue(); - SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache"); - if ( !freeze ) { - master_freeze->setBoolValue(true); - } - - globals->get_subsystem("tile-manager")->reinit(); - - if ( !freeze ) { - master_freeze->setBoolValue(false); - } - return true; -} - -/** - * Reload the materials definition - */ -static bool -do_materials_reload (const SGPropertyNode * arg) -{ - SG_LOG(SG_INPUT, SG_INFO, "Reloading Materials"); - SGMaterialLib* new_matlib = new SGMaterialLib; - SGPath mpath( globals->get_fg_root() ); - mpath.append( fgGetString("/sim/rendering/materials-file") ); - bool loaded = new_matlib->load(globals->get_fg_root().local8BitStr(), - mpath.local8BitStr(), - globals->get_props()); - - if ( ! loaded ) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Error loading materials file " << mpath ); - return false; - } - - globals->set_matlib(new_matlib); - FGScenery* scenery = static_cast(globals->get_scenery()); - scenery->materialLibChanged(); - - return true; -} - /** * Built-in command: toggle a bool property value. * @@ -599,12 +387,12 @@ do_property_assign (const SGPropertyNode * arg) * * property: the name of the property to increment or decrement. * step: the amount of the increment or decrement (default: 0). - * offset: offset from the current setting (used for the mouse; multiplied + * offset: offset from the current setting (used for the mouse; multiplied * by factor) * factor: scaling amount for the offset (defaults to 1). * min: the minimum allowed value (default: no minimum). * max: the maximum allowed value (default: no maximum). - * mask: 'integer' to apply only to the left of the decimal point, + * mask: 'integer' to apply only to the left of the decimal point, * 'decimal' to apply only to the right of the decimal point, * or 'all' to apply to the whole number (the default). * wrap: true if the value should be wrapped when it passes min or max; @@ -622,7 +410,7 @@ do_property_adjust (const SGPropertyNode * arg) else amount = (arg->getDoubleValue("factor", 1) * arg->getDoubleValue("offset")); - + double unmodifiable, modifiable; split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"), &unmodifiable, &modifiable); @@ -642,7 +430,7 @@ do_property_adjust (const SGPropertyNode * arg) * factor: the amount by which to multiply. * min: the minimum allowed value (default: no minimum). * max: the maximum allowed value (default: no maximum). - * mask: 'integer' to apply only to the left of the decimal point, + * mask: 'integer' to apply only to the left of the decimal point, * 'decimal' to apply only to the right of the decimal point, * or 'all' to apply to the whole number (the default). * wrap: true if the value should be wrapped when it passes min or max; @@ -743,11 +531,11 @@ do_property_cycle (const SGPropertyNode * arg) { SGPropertyNode * prop = get_prop(arg); std::vector values = arg->getChildren("value"); - + bool wrap = arg->getBoolValue("wrap", true); // compatible with knob/pick animations int offset = arg->getIntValue("offset", 1); - + int selection = -1; int nSelections = values.size(); @@ -774,7 +562,7 @@ do_property_cycle (const SGPropertyNode * arg) SG_CLAMP_RANGE(selection, 0, nSelections - 1); } } - + prop->setUnspecifiedValue(values[selection]->getStringValue()); return true; } @@ -894,188 +682,6 @@ do_data_logging_commit (const SGPropertyNode * arg) return true; } -/** - * Built-in command: Add a dialog to the GUI system. Does *not* - * display the dialog. The property node should have the same format - * as a dialog XML configuration. It must include: - * - * name: the name of the GUI dialog for future reference. - */ -static bool -do_dialog_new (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - if (!gui) { - return false; - } - - // Note the casting away of const: this is *real*. Doing a - // "dialog-apply" command later on will mutate this property node. - // I'm not convinced that this isn't the Right Thing though; it - // allows client to create a node, pass it to dialog-new, and get - // the values back from the dialog by reading the same node. - // Perhaps command arguments are not as "const" as they would - // seem? - gui->newDialog((SGPropertyNode*)arg); - return true; -} - -/** - * Built-in command: Show an XML-configured dialog. - * - * dialog-name: the name of the GUI dialog to display. - */ -static bool -do_dialog_show (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - gui->showDialog(arg->getStringValue("dialog-name")); - return true; -} - - -/** - * Built-in Command: Hide the active XML-configured dialog. - */ -static bool -do_dialog_close (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - if(arg->hasValue("dialog-name")) - return gui->closeDialog(arg->getStringValue("dialog-name")); - return gui->closeActiveDialog(); -} - - -/** - * Update a value in the active XML-configured dialog. - * - * object-name: The name of the GUI object(s) (all GUI objects if omitted). - */ -static bool -do_dialog_update (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - FGDialog * dialog; - if (arg->hasValue("dialog-name")) - dialog = gui->getDialog(arg->getStringValue("dialog-name")); - else - dialog = gui->getActiveDialog(); - - if (dialog != 0) { - dialog->updateValues(arg->getStringValue("object-name")); - return true; - } else { - return false; - } -} - -static bool -do_open_browser (const SGPropertyNode * arg) -{ - string path; - if (arg->hasValue("path")) - path = arg->getStringValue("path"); - else - if (arg->hasValue("url")) - path = arg->getStringValue("url"); - else - return false; - - return openBrowser(path); -} - -static bool -do_open_launcher(const SGPropertyNode *) -{ -#if defined(HAVE_QT) - bool ok = flightgear::runInAppLauncherDialog(); - if (ok) { - // start a full reset - fgResetIdleState(); - } - return true; // don't report failure -#else - return false; -#endif -} - -/** - * Apply a value in the active XML-configured dialog. - * - * object-name: The name of the GUI object(s) (all GUI objects if omitted). - */ -static bool -do_dialog_apply (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - FGDialog * dialog; - if (arg->hasValue("dialog-name")) - dialog = gui->getDialog(arg->getStringValue("dialog-name")); - else - dialog = gui->getActiveDialog(); - - if (dialog != 0) { - dialog->applyValues(arg->getStringValue("object-name")); - return true; - } else { - return false; - } -} - - -/** - * Redraw GUI (applying new widget colors). Doesn't reload the dialogs, - * unlike reinit(). - */ -static bool -do_gui_redraw (const SGPropertyNode * arg) -{ - NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); - gui->redraw(); - return true; -} - - -/** - * Adds model to the scenery. The path to the added branch (/models/model[*]) - * is returned in property "property". - */ -static bool -do_add_model (const SGPropertyNode * arg) -{ - SGPropertyNode * model = fgGetNode("models", true); - int i; - for (i = 0; model->hasChild("model",i); i++); - model = model->getChild("model", i, true); - copyProperties(arg, model); - if (model->hasValue("elevation-m")) - model->setDoubleValue("elevation-ft", model->getDoubleValue("elevation-m") - * SG_METER_TO_FEET); - model->getNode("load", true); - model->removeChildren("load"); - const_cast(arg)->setStringValue("property", model->getPath()); - return true; -} - -/** - * Built-in command: commit presets (read from in /sim/presets/) - */ -static bool -do_presets_commit (const SGPropertyNode * arg) -{ - if (fgGetBool("/sim/initialized", false)) { - fgResetIdleState(); - } else { - // Nasal can trigger this during initial init, which confuses - // the logic in ReInitSubsystems, since initial state has not been - // saved at that time. Short-circuit everything here. - flightgear::initPosition(); - } - - return true; -} - /** * Built-in command: set log level (0 ... 7) */ @@ -1087,21 +693,6 @@ do_log_level (const SGPropertyNode * arg) return true; } -/* -static bool -do_decrease_visibility (const SGPropertyNode * arg) -{ - Environment::Presets::VisibilitySingleton::instance()->adjust( 0.9 ); - return true; -} - -static bool -do_increase_visibility (const SGPropertyNode * arg) -{ - Environment::Presets::VisibilitySingleton::instance()->adjust( 1.1 ); - return true; -} -*/ /** * An fgcommand to allow loading of xml files via nasal, * the xml file's structure will be made available within @@ -1123,7 +714,7 @@ do_load_xml_to_proptree(const SGPropertyNode * arg) if (file.extension() != "xml") file.concat(".xml"); - + std::string icao = arg->getStringValue("icao"); if (icao.empty()) { if (file.isRelative()) { @@ -1132,7 +723,7 @@ do_load_xml_to_proptree(const SGPropertyNode * arg) file = absPath; else { - SG_LOG(SG_IO, SG_ALERT, "loadxml: Cannot find XML property file '" + SG_LOG(SG_IO, SG_ALERT, "loadxml: Cannot find XML property file '" << file << "'."); return false; } @@ -1144,7 +735,7 @@ do_load_xml_to_proptree(const SGPropertyNode * arg) return false; } } - + SGPath validated_path = fgValidatePath(file, false); if (validated_path.isNull()) { SG_LOG(SG_IO, SG_ALERT, "loadxml: reading '" << file << "' denied " @@ -1176,30 +767,30 @@ do_load_xml_from_url(const SGPropertyNode * arg) SG_LOG(SG_IO, SG_ALERT, "xmlhttprequest: HTTP client not running"); return false; } - + std::string url(arg->getStringValue("url")); if (url.empty()) return false; - + SGPropertyNode *targetnode; if (arg->hasValue("targetnode")) targetnode = fgGetNode(arg->getStringValue("targetnode"), true); else targetnode = const_cast(arg)->getNode("data", true); - + RemoteXMLRequest* req = new RemoteXMLRequest(url, targetnode); - + if (arg->hasChild("body")) req->setBodyData(arg->getChild("body")); - + // connect up optional reporting properties - if (arg->hasValue("complete")) + if (arg->hasValue("complete")) req->setCompletionProp(fgGetNode(arg->getStringValue("complete"), true)); - if (arg->hasValue("failure")) + if (arg->hasValue("failure")) req->setFailedProp(fgGetNode(arg->getStringValue("failure"), true)); - if (arg->hasValue("status")) + if (arg->hasValue("status")) req->setStatusProp(fgGetNode(arg->getStringValue("status"), true)); - + http->makeRequest(req); return true; } @@ -1253,49 +844,6 @@ do_save_xml_from_proptree(const SGPropertyNode * arg) return true; } -static bool -do_press_cockpit_button (const SGPropertyNode *arg) -{ - const char *prefix = arg->getStringValue("prefix"); - - if (arg->getBoolValue("guarded") && fgGetDouble((string(prefix) + "-guard").c_str()) < 1) - return true; - - string prop = string(prefix) + "-button"; - double value; - - if (arg->getBoolValue("latching")) - value = fgGetDouble(prop.c_str()) > 0 ? 0 : 1; - else - value = 1; - - fgSetDouble(prop.c_str(), value); - fgSetBool(arg->getStringValue("discrete"), value > 0); - - return true; -} - -static bool -do_release_cockpit_button (const SGPropertyNode *arg) -{ - const char *prefix = arg->getStringValue("prefix"); - - if (arg->getBoolValue("guarded")) { - string prop = string(prefix) + "-guard"; - if (fgGetDouble(prop.c_str()) < 1) { - fgSetDouble(prop.c_str(), 1); - return true; - } - } - - if (! arg->getBoolValue("latching")) { - fgSetDouble((string(prefix) + "-button").c_str(), 0); - fgSetBool(arg->getStringValue("discrete"), false); - } - - return true; -} - // Optional profiling commands using gperftools: // http://code.google.com/p/gperftools/ @@ -1355,22 +903,12 @@ static struct { } built_ins [] = { { "null", do_null }, { "nasal", do_nasal }, - { "exit", do_exit }, - { "reset", do_reset }, - { "reposition", do_reposition }, - { "switch-aircraft", do_switch_aircraft }, { "pause", do_pause }, { "load", do_load }, { "save", do_save }, { "save-tape", do_save_tape }, { "load-tape", do_load_tape }, - { "panel-load", do_panel_load }, - { "preferences-load", do_preferences_load }, - { "toggle-fullscreen", do_toggle_fullscreen }, { "view-cycle", do_view_cycle }, - { "screen-capture", do_screen_capture }, - { "hires-screen-capture", do_hires_screen_capture }, - { "tile-cache-reload", do_tile_cache_reload }, /* { "set-sea-level-air-temp-degc", do_set_sea_level_degc }, { "set-outside-air-temp-degc", do_set_oat_degc }, @@ -1387,18 +925,8 @@ static struct { { "property-randomize", do_property_randomize }, { "property-interpolate", do_property_interpolate }, { "data-logging-commit", do_data_logging_commit }, - { "dialog-new", do_dialog_new }, - { "dialog-show", do_dialog_show }, - { "dialog-close", do_dialog_close }, - { "dialog-update", do_dialog_update }, - { "dialog-apply", do_dialog_apply }, - { "open-browser", do_open_browser }, - { "gui-redraw", do_gui_redraw }, - { "add-model", do_add_model }, - { "presets-commit", do_presets_commit }, { "log-level", do_log_level }, { "replay", do_replay }, - { "open-launcher", do_open_launcher }, /* { "decrease-visibility", do_decrease_visibility }, { "increase-visibility", do_increase_visibility }, @@ -1406,14 +934,7 @@ static struct { { "loadxml", do_load_xml_to_proptree}, { "savexml", do_save_xml_from_proptree }, { "xmlhttprequest", do_load_xml_from_url }, - { "press-cockpit-button", do_press_cockpit_button }, - { "release-cockpit-button", do_release_cockpit_button }, - { "dump-scenegraph", do_dump_scene_graph }, - { "dump-terrainbranch", do_dump_terrain_branch }, - { "print-visible-scene", do_print_visible_scene_info }, - { "reload-shaders", do_reload_shaders }, - { "reload-materials", do_materials_reload }, - + { "profiler-start", do_profiler_start }, { "profiler-stop", do_profiler_stop }, diff --git a/src/Main/fg_commands.hxx b/src/Main/fg_commands.hxx index 309dca90b..acd30bee3 100644 --- a/src/Main/fg_commands.hxx +++ b/src/Main/fg_commands.hxx @@ -8,6 +8,8 @@ */ void fgInitCommands (); +void fgInitSceneCommands(); + // end of fg_commands.hxx #endif diff --git a/src/Main/fg_scene_commands.cxx b/src/Main/fg_scene_commands.cxx new file mode 100644 index 000000000..1342ccf1b --- /dev/null +++ b/src/Main/fg_scene_commands.cxx @@ -0,0 +1,555 @@ +// fg_scene_commands.cxx - internal FGFS commands. + +#include "config.h" + +#include // strcmp() + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fg_init.hxx" +#include "fg_io.hxx" +#include "fg_os.hxx" +#include "fg_commands.hxx" +#include "fg_props.hxx" +#include "globals.hxx" +#include "logger.hxx" +#include "util.hxx" +#include "main.hxx" +#include "positioninit.hxx" + +#include + +#if FG_HAVE_GPERFTOOLS +# include +#endif + +#if defined(HAVE_QT) +#include +#endif + + +//////////////////////////////////////////////////////////////////////// +// Command implementations. +//////////////////////////////////////////////////////////////////////// + +/** + * Built-in command: exit FlightGear. + * + * status: the exit status to return to the operating system (defaults to 0) + */ +static bool +do_exit (const SGPropertyNode * arg) +{ + SG_LOG(SG_INPUT, SG_INFO, "Program exit requested."); + fgSetBool("/sim/signals/exit", true); + globals->saveUserSettings(); + fgOSExit(arg->getIntValue("status", 0)); + return true; +} + + +/** + * Reset FlightGear (Shift-Escape or Menu->File->Reset) + */ +static bool +do_reset (const SGPropertyNode * arg) +{ + fgResetIdleState(); + return true; +} + + +/** + * Change aircraft + */ +static bool +do_switch_aircraft (const SGPropertyNode * arg) +{ + fgSetString("/sim/aircraft", arg->getStringValue("aircraft")); + // start a reset + fgResetIdleState(); + return true; +} + +/** + */ +static bool +do_reposition (const SGPropertyNode * arg) +{ + fgStartReposition(); + return true; +} + +/** + * Built-in command: (re)load the panel. + * + * path (optional): the file name to load the panel from + * (relative to FG_ROOT). Defaults to the value of /sim/panel/path, + * and if that's unspecified, to "Panels/Default/default.xml". + */ +static bool +do_panel_load (const SGPropertyNode * arg) +{ + string panel_path = arg->getStringValue("path"); + if (!panel_path.empty()) { + // write to the standard property, which will force a load + fgSetString("/sim/panel/path", panel_path.c_str()); + } + + return true; +} + +/** + * Built-in command: (re)load preferences. + * + * path (optional): the file name to load the panel from (relative + * to FG_ROOT). Defaults to "preferences.xml". + */ +static bool +do_preferences_load (const SGPropertyNode * arg) +{ + // disabling this command which was formerly used to reload 'preferences.xml' + // reloading the defaults doesn't make sense (better to reset the simulator), + // and loading arbitrary XML docs into the property-tree can be done via + // the standard load-xml command. + SG_LOG(SG_GENERAL, SG_ALERT, "preferences-load command is deprecated and non-functional"); + return false; +} + +/** + * An fgcommand to toggle fullscreen mode. + * No parameters. + */ +static bool +do_toggle_fullscreen(const SGPropertyNode *arg) +{ + fgOSFullScreen(); + return true; +} + +/** + * Built-in command: capture screen. + */ +static bool +do_screen_capture (const SGPropertyNode * arg) +{ + return fgDumpSnapShot(); +} + +static bool +do_reload_shaders (const SGPropertyNode*) +{ + simgear::reload_shaders(); + return true; +} + +static bool +do_dump_scene_graph (const SGPropertyNode*) +{ + fgDumpSceneGraph(); + return true; +} + +static bool +do_dump_terrain_branch (const SGPropertyNode*) +{ + fgDumpTerrainBranch(); + + double lon_deg = fgGetDouble("/position/longitude-deg"); + double lat_deg = fgGetDouble("/position/latitude-deg"); + SGGeod geodPos = SGGeod::fromDegFt(lon_deg, lat_deg, 0.0); + SGVec3d zero = SGVec3d::fromGeod(geodPos); + + SG_LOG(SG_INPUT, SG_INFO, "Model parameters:"); + SG_LOG(SG_INPUT, SG_INFO, "Center: " << zero.x() << ", " << zero.y() << ", " << zero.z() ); + SG_LOG(SG_INPUT, SG_INFO, "Rotation: " << lat_deg << ", " << lon_deg ); + + return true; +} + +static bool +do_print_visible_scene_info(const SGPropertyNode*) +{ + fgPrintVisibleSceneInfoCommand(); + return true; +} + +/** + * Built-in command: hires capture screen. + */ +static bool +do_hires_screen_capture (const SGPropertyNode * arg) +{ + fgHiResDump(); + return true; +} + + +/** + * Reload the tile cache. + */ +static bool +do_tile_cache_reload (const SGPropertyNode * arg) +{ + SGPropertyNode *master_freeze = fgGetNode("/sim/freeze/master"); + bool freeze = master_freeze->getBoolValue(); + SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache"); + if ( !freeze ) { + master_freeze->setBoolValue(true); + } + + globals->get_subsystem("tile-manager")->reinit(); + + if ( !freeze ) { + master_freeze->setBoolValue(false); + } + return true; +} + +/** + * Reload the materials definition + */ +static bool +do_materials_reload (const SGPropertyNode * arg) +{ + SG_LOG(SG_INPUT, SG_INFO, "Reloading Materials"); + SGMaterialLib* new_matlib = new SGMaterialLib; + SGPath mpath( globals->get_fg_root() ); + mpath.append( fgGetString("/sim/rendering/materials-file") ); + bool loaded = new_matlib->load(globals->get_fg_root().local8BitStr(), + mpath.local8BitStr(), + globals->get_props()); + + if ( ! loaded ) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Error loading materials file " << mpath ); + return false; + } + + globals->set_matlib(new_matlib); + FGScenery* scenery = static_cast(globals->get_scenery()); + scenery->materialLibChanged(); + + return true; +} + +/** + * Built-in command: Add a dialog to the GUI system. Does *not* + * display the dialog. The property node should have the same format + * as a dialog XML configuration. It must include: + * + * name: the name of the GUI dialog for future reference. + */ +static bool +do_dialog_new (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + if (!gui) { + return false; + } + + // Note the casting away of const: this is *real*. Doing a + // "dialog-apply" command later on will mutate this property node. + // I'm not convinced that this isn't the Right Thing though; it + // allows client to create a node, pass it to dialog-new, and get + // the values back from the dialog by reading the same node. + // Perhaps command arguments are not as "const" as they would + // seem? + gui->newDialog((SGPropertyNode*)arg); + return true; +} + +/** + * Built-in command: Show an XML-configured dialog. + * + * dialog-name: the name of the GUI dialog to display. + */ +static bool +do_dialog_show (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + gui->showDialog(arg->getStringValue("dialog-name")); + return true; +} + + +/** + * Built-in Command: Hide the active XML-configured dialog. + */ +static bool +do_dialog_close (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + if(arg->hasValue("dialog-name")) + return gui->closeDialog(arg->getStringValue("dialog-name")); + return gui->closeActiveDialog(); +} + + +/** + * Update a value in the active XML-configured dialog. + * + * object-name: The name of the GUI object(s) (all GUI objects if omitted). + */ +static bool +do_dialog_update (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + FGDialog * dialog; + if (arg->hasValue("dialog-name")) + dialog = gui->getDialog(arg->getStringValue("dialog-name")); + else + dialog = gui->getActiveDialog(); + + if (dialog != 0) { + dialog->updateValues(arg->getStringValue("object-name")); + return true; + } else { + return false; + } +} + +static bool +do_open_browser (const SGPropertyNode * arg) +{ + string path; + if (arg->hasValue("path")) + path = arg->getStringValue("path"); + else + if (arg->hasValue("url")) + path = arg->getStringValue("url"); + else + return false; + + return openBrowser(path); +} + +static bool +do_open_launcher(const SGPropertyNode *) +{ +#if defined(HAVE_QT) + bool ok = flightgear::runInAppLauncherDialog(); + if (ok) { + // start a full reset + fgResetIdleState(); + } + return true; // don't report failure +#else + return false; +#endif +} + +/** + * Apply a value in the active XML-configured dialog. + * + * object-name: The name of the GUI object(s) (all GUI objects if omitted). + */ +static bool +do_dialog_apply (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + FGDialog * dialog; + if (arg->hasValue("dialog-name")) + dialog = gui->getDialog(arg->getStringValue("dialog-name")); + else + dialog = gui->getActiveDialog(); + + if (dialog != 0) { + dialog->applyValues(arg->getStringValue("object-name")); + return true; + } else { + return false; + } +} + + +/** + * Redraw GUI (applying new widget colors). Doesn't reload the dialogs, + * unlike reinit(). + */ +static bool +do_gui_redraw (const SGPropertyNode * arg) +{ + NewGUI * gui = (NewGUI *)globals->get_subsystem("gui"); + gui->redraw(); + return true; +} + + +/** + * Adds model to the scenery. The path to the added branch (/models/model[*]) + * is returned in property "property". + */ +static bool +do_add_model (const SGPropertyNode * arg) +{ + SGPropertyNode * model = fgGetNode("models", true); + int i; + for (i = 0; model->hasChild("model",i); i++); + model = model->getChild("model", i, true); + copyProperties(arg, model); + if (model->hasValue("elevation-m")) + model->setDoubleValue("elevation-ft", model->getDoubleValue("elevation-m") + * SG_METER_TO_FEET); + model->getNode("load", true); + model->removeChildren("load"); + const_cast(arg)->setStringValue("property", model->getPath()); + return true; +} + +/** + * Built-in command: commit presets (read from in /sim/presets/) + */ +static bool +do_presets_commit (const SGPropertyNode * arg) +{ + if (fgGetBool("/sim/initialized", false)) { + fgResetIdleState(); + } else { + // Nasal can trigger this during initial init, which confuses + // the logic in ReInitSubsystems, since initial state has not been + // saved at that time. Short-circuit everything here. + flightgear::initPosition(); + } + + return true; +} + +static bool +do_press_cockpit_button (const SGPropertyNode *arg) +{ + const char *prefix = arg->getStringValue("prefix"); + + if (arg->getBoolValue("guarded") && fgGetDouble((string(prefix) + "-guard").c_str()) < 1) + return true; + + string prop = string(prefix) + "-button"; + double value; + + if (arg->getBoolValue("latching")) + value = fgGetDouble(prop.c_str()) > 0 ? 0 : 1; + else + value = 1; + + fgSetDouble(prop.c_str(), value); + fgSetBool(arg->getStringValue("discrete"), value > 0); + + return true; +} + +static bool +do_release_cockpit_button (const SGPropertyNode *arg) +{ + const char *prefix = arg->getStringValue("prefix"); + + if (arg->getBoolValue("guarded")) { + string prop = string(prefix) + "-guard"; + if (fgGetDouble(prop.c_str()) < 1) { + fgSetDouble(prop.c_str(), 1); + return true; + } + } + + if (! arg->getBoolValue("latching")) { + fgSetDouble((string(prefix) + "-button").c_str(), 0); + fgSetBool(arg->getStringValue("discrete"), false); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////// +// Command setup. +//////////////////////////////////////////////////////////////////////// + + +/** + * Table of built-in commands. + * + * New commands do not have to be added here; any module in the application + * can add a new command using globals->get_commands()->addCommand(...). + */ +static struct { + const char * name; + SGCommandMgr::command_t command; +} built_ins [] = { + { "exit", do_exit }, + { "reset", do_reset }, + { "reposition", do_reposition }, + { "switch-aircraft", do_switch_aircraft }, + { "panel-load", do_panel_load }, + { "preferences-load", do_preferences_load }, + { "toggle-fullscreen", do_toggle_fullscreen }, + { "screen-capture", do_screen_capture }, + { "hires-screen-capture", do_hires_screen_capture }, + { "tile-cache-reload", do_tile_cache_reload }, + { "dialog-new", do_dialog_new }, + { "dialog-show", do_dialog_show }, + { "dialog-close", do_dialog_close }, + { "dialog-update", do_dialog_update }, + { "dialog-apply", do_dialog_apply }, + { "open-browser", do_open_browser }, + { "gui-redraw", do_gui_redraw }, + { "add-model", do_add_model }, + { "presets-commit", do_presets_commit }, + { "press-cockpit-button", do_press_cockpit_button }, + { "release-cockpit-button", do_release_cockpit_button }, + { "dump-scenegraph", do_dump_scene_graph }, + { "dump-terrainbranch", do_dump_terrain_branch }, + { "print-visible-scene", do_print_visible_scene_info }, + { "reload-shaders", do_reload_shaders }, + { "reload-materials", do_materials_reload }, + { "open-launcher", do_open_launcher }, + { 0, 0 } // zero-terminated +}; + + +/** + * Initialize the default built-in commands. + * + * Other commands may be added by other parts of the application. + */ +void +fgInitSceneCommands () +{ + SG_LOG(SG_GENERAL, SG_BULK, "Initializing scene built-in commands:"); + for (int i = 0; built_ins[i].name != 0; i++) { + SG_LOG(SG_GENERAL, SG_BULK, " " << built_ins[i].name); + globals->get_commands()->addCommand(built_ins[i].name, + built_ins[i].command); + } + +} diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 5cb7ddc48..0dfac209e 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -125,16 +125,16 @@ static void initTerrasync() // (even if we are in read-only mode) SGPath terraSyncDir(globals->get_terrasync_dir()); globals->append_data_path(terraSyncDir); - + if (fgGetBool("/sim/fghome-readonly", false)) { return; } - + // start TerraSync up now, so it can be synchronizing shared models // and airports data in parallel with a nav-cache rebuild. SGPath tsyncCache(terraSyncDir); tsyncCache.append("terrasync-cache.xml"); - + // wipe the cache file if requested if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { SG_LOG(SG_GENERAL, SG_INFO, "restore-defaults requested, wiping terrasync update cache at " << @@ -143,7 +143,7 @@ static void initTerrasync() tsyncCache.remove(); } } - + fgSetString("/sim/terrasync/cache-path", tsyncCache.utf8Str()); // make fg-root dir available so existing Scenery data can be copied, and @@ -153,7 +153,7 @@ static void initTerrasync() simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync(); terra_sync->setRoot(globals->get_props()); globals->add_subsystem("terrasync", terra_sync); - + terra_sync->bind(); terra_sync->init(); } @@ -203,7 +203,7 @@ static void checkOpenGLVersion() // versions doesn't help them much return; #endif - + // format of these strings is not standardised, so be careful about // parsing them. std::string versionString(fgGetString("/sim/rendering/gl-version")); @@ -248,7 +248,7 @@ static void fgIdleFunction ( void ) { // 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. - + if ( idle_state == 0 ) { if (guiInit()) { @@ -265,7 +265,7 @@ static void fgIdleFunction ( void ) { fgSplashProgress("loading-nav-dat"); } else if ( idle_state == 3 ) { - + bool done = fgInitNav(); if (done) { ++idle_state; @@ -276,7 +276,7 @@ static void fgIdleFunction ( void ) { TimeManager* t = new TimeManager; globals->add_subsystem("time", t, SGSubsystemMgr::INIT); - + // Do some quick general initializations if( !fgInitGeneral()) { throw sg_exception("General initialization failed"); @@ -286,6 +286,7 @@ static void fgIdleFunction ( void ) { // Initialize the property-based built-in commands //////////////////////////////////////////////////////////////////// fgInitCommands(); + fgInitSceneCommands(); flightgear::registerSubsystemCommands(globals->get_commands()); @@ -294,21 +295,21 @@ static void fgIdleFunction ( void ) { //////////////////////////////////////////////////////////////////// globals->set_matlib( new SGMaterialLib ); simgear::SGModelLib::setPanelFunc(FGPanelNode::load); - + } else if (( idle_state == 5 ) || (idle_state == 2005)) { idle_state+=2; flightgear::initPosition(); flightgear::initTowerLocationListener(); - + simgear::SGModelLib::init(globals->get_fg_root().local8BitStr(), globals->get_props()); - + TimeManager* timeManager = (TimeManager*) globals->get_subsystem("time"); timeManager->init(); - + //////////////////////////////////////////////////////////////////// // Initialize the TG scenery subsystem. //////////////////////////////////////////////////////////////////// - + globals->add_new_subsystem(SGSubsystemMgr::DISPLAY); globals->get_scenery()->init(); globals->get_scenery()->bind(); @@ -322,7 +323,7 @@ static void fgIdleFunction ( void ) { fgCreateSubsystems(isReset); SG_LOG(SG_GENERAL, SG_INFO, "Creating subsystems took:" << st.elapsedMSec()); fgSplashProgress("binding-subsystems"); - + } else if ( idle_state == 8 ) { idle_state++; SGTimeStamp st; @@ -339,14 +340,14 @@ static void fgIdleFunction ( void ) { } else { fgSplashProgress("init-subsystems"); } - + } else if ( idle_state == 10 ) { idle_state = 900; fgPostInitSubsystems(); fgSplashProgress("finalize-position"); } else if ( idle_state == 900 ) { idle_state = 1000; - + // setup OpenGL view parameters globals->get_renderer()->setupView(); @@ -369,7 +370,7 @@ static void fgIdleFunction ( void ) { fgSetBool("sim/sceneryloaded", false); registerMainLoop(); } - + if ( idle_state == 2000 ) { fgStartNewReset(); idle_state = 2005; @@ -446,12 +447,12 @@ int fgMainInit( int argc, char **argv ) if (!fgInitHome()) { return EXIT_FAILURE; } - + if (!fgGetBool("/sim/fghome-readonly")) { // now home is initialised, we can log to a file inside it logToHome(); } - + std::string version(FLIGHTGEAR_VERSION); SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version " << version ); @@ -516,14 +517,14 @@ int fgMainInit( int argc, char **argv ) } else if (configResult == flightgear::FG_OPTIONS_EXIT) { return EXIT_SUCCESS; } - + configResult = flightgear::Options::sharedInstance()->processOptions(); if (configResult == flightgear::FG_OPTIONS_ERROR) { return EXIT_FAILURE; } else if (configResult == flightgear::FG_OPTIONS_EXIT) { return EXIT_SUCCESS; } - + // Initialize the Window/Graphics environment. fgOSInit(&argc, argv); _bootstrap_OSInit++; @@ -536,30 +537,30 @@ int fgMainInit( int argc, char **argv ) // Clouds3D requires an alpha channel fgOSOpenWindow(true /* request stencil buffer */); fgOSResetProperties(); - + fntInit(); globals->get_renderer()->preinit(); fgOutputSettings(); - + //try to disable the screensaver fgOSDisableScreensaver(); - + // pass control off to the master event handler int result = fgOSMainLoop(); frame_signal.clear(); fgOSCloseWindow(); - + simgear::clearEffectCache(); - + // clean up here; ensure we null globals to avoid // confusing the atexit() handler delete globals; globals = NULL; - + // delete the NavCache here. This will cause the destruction of many cached // objects (eg, airports, navaids, runways). delete flightgear::NavDataCache::instance(); - + return result; }