diff --git a/src/AIModel/AIBase.cxx b/src/AIModel/AIBase.cxx index 07fc6c31a..a9d64e341 100644 --- a/src/AIModel/AIBase.cxx +++ b/src/AIModel/AIBase.cxx @@ -75,8 +75,7 @@ FGAIBase::FGAIBase(object_type ot, bool enableHot) : _refID( _newAIModelID() ), _otype(ot), _initialized(false), - _aimodel(0), - _fxpath(""), + _modeldata(0), _fx(0) { @@ -232,21 +231,28 @@ void FGAIBase::update(double dt) { pitch*speed ); _fx->set_velocity( velocity ); } - else if ((_aimodel)&&(fgGetBool("/sim/sound/aimodels/enabled",false))) + else if ((_modeldata)&&(_modeldata->needInitilization())) { - string fxpath = _aimodel->get_sound_path(); - if (fxpath != "") - { - _fxpath = fxpath; - props->setStringValue("sim/sound/path", _fxpath.c_str()); + // process deferred nasal initialization, + // which must be done in main thread + _modeldata->init(); - // initialize the sound configuration - SGSoundMgr *smgr = globals->get_soundmgr(); - stringstream name; - name << "aifx:"; - name << _refID; - _fx = new FGFX(smgr, name.str(), props); - _fx->init(); + // sound initialization + if (fgGetBool("/sim/sound/aimodels/enabled",false)) + { + string fxpath = _modeldata->get_sound_path(); + if (fxpath != "") + { + props->setStringValue("sim/sound/path", fxpath.c_str()); + + // initialize the sound configuration + SGSoundMgr *smgr = globals->get_soundmgr(); + stringstream name; + name << "aifx:"; + name << _refID; + _fx = new FGFX(smgr, name.str(), props); + _fx->init(); + } } } } @@ -324,8 +330,8 @@ bool FGAIBase::init(bool search_in_AI_path) else _installed = true; - _aimodel = new FGAIModelData(props); - osg::Node * mdl = SGModelLib::loadDeferredModel(f, props, _aimodel); + _modeldata = new FGAIModelData(props); + osg::Node * mdl = SGModelLib::loadDeferredModel(f, props, _modeldata); _model = new osg::LOD; _model->setName("AI-model range animation node"); @@ -912,7 +918,8 @@ int FGAIBase::_newAIModelID() { FGAIModelData::FGAIModelData(SGPropertyNode *root) : _nasal( new FGNasalModelData(root) ), - _path("") + _ready(false), + _initialized(false) { } @@ -923,9 +930,21 @@ FGAIModelData::~FGAIModelData() void FGAIModelData::modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *n) { - const char* fxpath = prop->getStringValue("sound/path"); - if (fxpath) { - _path = string(fxpath); - } - _nasal->modelLoaded(path, prop, n); + // WARNING: All this is called in a separate OSG thread! Only use thread-safe stuff + // here that is fine to be run concurrently with stuff in the main loop! + if (_ready) + return; + _fxpath = _prop->getStringValue("sound/path"); + _prop = prop; + _path = path; + _ready = true; +} + +// do Nasal initialization (must be called in the main loop) +void FGAIModelData::init() +{ + // call FGNasalSys to create context and run hooks (not-thread safe!) + _nasal->modelLoaded(_path, _prop, 0); + _prop = 0; + _initialized = true; } diff --git a/src/AIModel/AIBase.hxx b/src/AIModel/AIBase.hxx index b4815c41a..b4782eb43 100644 --- a/src/AIModel/AIBase.hxx +++ b/src/AIModel/AIBase.hxx @@ -21,7 +21,6 @@ #define _FG_AIBASE_HXX #include -#include #include #include @@ -38,7 +37,6 @@ using std::string; -using std::list; class SGMaterial; class FGAIManager; @@ -230,9 +228,8 @@ private: bool _initialized; osg::ref_ptr _model; //The 3D model LOD object - osg::ref_ptr _aimodel; + osg::ref_ptr _modeldata; - string _fxpath; SGSharedPtr _fx; public: @@ -453,12 +450,24 @@ class FGAIModelData : public simgear::SGModelData { public: FGAIModelData(SGPropertyNode *root = 0); ~FGAIModelData(); + + /** osg callback, thread-safe */ void modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *n); - inline string& get_sound_path() { return _path; }; + + /** init hook to be called after model is loaded. + * Not thread-safe. Call from main thread only. */ + void init(void); + + bool needInitilization(void) { return _ready && !_initialized;} + bool isInitialized(void) { return _initialized;} + inline std::string& get_sound_path() { return _fxpath;} private: FGNasalModelData *_nasal; - string _path; + SGPropertyNode_ptr _prop; + std::string _path, _fxpath; + bool _ready; + bool _initialized; }; #endif // _FG_AIBASE_HXX diff --git a/src/Airports/groundnetwork.cxx b/src/Airports/groundnetwork.cxx index bf4d24f7d..8e9b16fe6 100644 --- a/src/Airports/groundnetwork.cxx +++ b/src/Airports/groundnetwork.cxx @@ -580,9 +580,23 @@ FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end, } FGTaxiNode *firstNode = findNode(start); + if (!firstNode) + { + SG_LOG(SG_GENERAL, SG_ALERT, + "Error in ground network. Failed to find first waypoint: " << start + << " at " << ((parent) ? parent->getId() : "")); + return FGTaxiRoute(); + } firstNode->setPathScore(0); FGTaxiNode *lastNode = findNode(end); + if (!lastNode) + { + SG_LOG(SG_GENERAL, SG_ALERT, + "Error in ground network. Failed to find last waypoint: " << end + << " at " << ((parent) ? parent->getId() : "")); + return FGTaxiRoute(); + } FGTaxiNodeVector unvisited(*currNodesSet); // working copy @@ -606,6 +620,13 @@ FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end, seg != best->getEndRoute(); seg++) { if (fullSearch || (*seg)->isPushBack()) { FGTaxiNode *tgt = (*seg)->getEnd(); + if (!tgt) + { + SG_LOG(SG_GENERAL, SG_ALERT, + "Error in ground network. Found empty segment " + << " at " << ((parent) ? parent->getId() : "")); + return FGTaxiRoute(); + } double alt = best->getPathScore() + (*seg)->getLength() + (*seg)->getPenalty(nParkings); diff --git a/src/GUI/FGCocoaMenuBar.mm b/src/GUI/FGCocoaMenuBar.mm index 7b44c7820..18643cdea 100644 --- a/src/GUI/FGCocoaMenuBar.mm +++ b/src/GUI/FGCocoaMenuBar.mm @@ -8,6 +8,7 @@ #include #include #include +#include #include
@@ -16,6 +17,7 @@ using std::string; using std::map; using std::cout; +using namespace simgear; typedef std::map MenuItemBindings; @@ -27,7 +29,6 @@ public: CocoaMenuBarPrivate(); ~CocoaMenuBarPrivate(); - bool labelIsSeparator(const std::string& s) const; void menuFromProps(NSMenu* menu, SGPropertyNode* menuNode); void fireBindingsForItem(NSMenuItem* item); @@ -63,6 +64,51 @@ static NSString* stdStringToCocoa(const string& s) return [NSString stringWithUTF8String:s.c_str()]; } +static void setFunctionKeyShortcut(NSMenuItem* item, unichar shortcut) +{ + unichar ch[1]; + ch[0] = shortcut; + [item setKeyEquivalentModifierMask:NSFunctionKeyMask]; + [item setKeyEquivalent:[NSString stringWithCharacters:ch length:1]]; + +} + +static void setItemShortcutFromString(NSMenuItem* item, const string& s) +{ + const char* shortcut = ""; + + bool hasCtrl = strutils::starts_with(s, "Ctrl-"); + bool hasShift = strutils::starts_with(s, "Shift-"); + bool hasAlt = strutils::starts_with(s, "Alt-"); + + int offset = 0; // character offset from start of string + if (hasShift) offset += 6; + if (hasCtrl) offset += 5; + if (hasAlt) offset += 4; + + shortcut = s.c_str() + offset; + if (!strcmp(shortcut, "Esc")) + shortcut = "\e"; + + if (!strcmp(shortcut, "F11")) { + setFunctionKeyShortcut(item, NSF11FunctionKey); + return; + } + + if (!strcmp(shortcut, "F12")) { + setFunctionKeyShortcut(item, NSF12FunctionKey); + return; + } + + [item setKeyEquivalent:[NSString stringWithCString:shortcut encoding:NSUTF8StringEncoding]]; + NSUInteger modifiers = 0; + if (hasCtrl) modifiers |= NSControlKeyMask; + if (hasShift) modifiers |= NSShiftKeyMask; + if (hasAlt) modifiers |= NSAlternateKeyMask; + + [item setKeyEquivalentModifierMask:modifiers]; +} + class EnabledListener : public SGPropertyChangeListener { public: @@ -94,15 +140,9 @@ FGCocoaMenuBar::CocoaMenuBarPrivate::~CocoaMenuBarPrivate() [delegate release]; } -bool FGCocoaMenuBar::CocoaMenuBarPrivate::labelIsSeparator(const std::string& s) const +static bool labelIsSeparator(NSString* s) { - for (unsigned int i=0; isetBoolValue("enabled", true); } + string shortcut; string l = n->getStringValue("label"); string::size_type pos = l.find("("); if (pos != string::npos) { - l = l.substr(0, pos); + string full(l); + l = full.substr(0, pos); + shortcut = full.substr(pos + 1, full.size() - (pos + 2)); } - NSString* label = stdStringToCocoa(l); - NSString* shortcut = @""; + NSString* label = stdStringToCocoa(strutils::simplify(l)); + NSMenuItem* item; if (index >= [menu numberOfItems]) { - if (labelIsSeparator(l)) { + if (labelIsSeparator(label)) { item = [NSMenuItem separatorItem]; [menu addItem:item]; } else { - item = [menu addItemWithTitle:label action:nil keyEquivalent:shortcut]; + item = [menu addItemWithTitle:label action:nil keyEquivalent:@""]; + if (!shortcut.empty()) { + setItemShortcutFromString(item, shortcut); + } + n->getNode("enabled")->addChangeListener(new EnabledListener(item)); [item setTarget:delegate]; [item setAction:@selector(itemAction:)]; diff --git a/src/Instrumentation/NavDisplay.cxx b/src/Instrumentation/NavDisplay.cxx index 7b7e3488a..8d14cb752 100644 --- a/src/Instrumentation/NavDisplay.cxx +++ b/src/Instrumentation/NavDisplay.cxx @@ -1123,6 +1123,16 @@ void NavDisplay::computePositionedState(FGPositioned* pos, string_set& states) } // FGPositioned::Type switch } +static string mapAINodeToType(SGPropertyNode* model) +{ + // assume all multiplayer items are aircraft for the moment. Not ideal. + if (!strcmp(model->getName(), "multiplayer")) { + return "ai-aircraft"; + } + + return string("ai-") + model->getName(); +} + void NavDisplay::processAI() { SGPropertyNode *ai = fgGetNode("/ai/models", true); @@ -1137,7 +1147,7 @@ void NavDisplay::processAI() string_set ss; computeAIStates(model, ss); SymbolDefVector rules; - findRules(string("ai-") + model->getName(), ss, rules); + findRules(mapAINodeToType(model), ss, rules); if (rules.empty()) { return; // no rules matched, we can skip this item } @@ -1160,14 +1170,15 @@ void NavDisplay::processAI() void NavDisplay::computeAIStates(const SGPropertyNode* ai, string_set& states) { int threatLevel = ai->getIntValue("tcas/threat-level",-1); - if (threatLevel >= 0) { - states.insert("tcas"); - - std::ostringstream os; - os << "tcas-threat-level-" << threatLevel; - states.insert(os.str()); - } - + if (threatLevel < 1) + threatLevel = 0; + + states.insert("tcas"); + + std::ostringstream os; + os << "tcas-threat-level-" << threatLevel; + states.insert(os.str()); + double vspeed = ai->getDoubleValue("velocities/vertical-speed-fps"); if (vspeed < -3.0) { states.insert("descending"); diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index cf08e334c..47ecbd449 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -502,7 +502,7 @@ static SGPath platformDefaultDataPath() SGPath appData; appData.set((const char*) path); - appData.append("flightgear.org"); + appData.append("FlightGear"); return appData; } #else diff --git a/src/Main/fg_props.cxx b/src/Main/fg_props.cxx index 1e6240570..4ad261adf 100644 --- a/src/Main/fg_props.cxx +++ b/src/Main/fg_props.cxx @@ -365,11 +365,8 @@ getMagDip () static double getHeadingMag () { - double magheading; - magheading = fgGetDouble("/orientation/heading-deg") - getMagVar(); - if (magheading <= 0) magheading += 360; - else if (magheading > 360) magheading -= 360; - return magheading; + double magheading = fgGetDouble("/orientation/heading-deg") - getMagVar(); + return SGMiscd::normalizePeriodic(0.5, 360.5, magheading ); } /** @@ -378,11 +375,8 @@ getHeadingMag () static double getTrackMag () { - double magtrack; - magtrack = fgGetDouble("/orientation/track-deg") - getMagVar(); - if (magtrack <= 0) magtrack += 360; - else if (magtrack > 360) magtrack -= 360; - return magtrack; + double magtrack = fgGetDouble("/orientation/track-deg") - getMagVar(); + return SGMiscd::normalizePeriodic(0.5, 360.5, magtrack ); } static bool diff --git a/src/Scenery/tilemgr.cxx b/src/Scenery/tilemgr.cxx index 666d64b9c..3bc9258ab 100644 --- a/src/Scenery/tilemgr.cxx +++ b/src/Scenery/tilemgr.cxx @@ -247,9 +247,20 @@ FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel) SGModelLib::loadModel(fullPath.str(), globals->get_props(), new FGNasalModelData); else + { + /* TODO FGNasalModelData's callback "modelLoaded" isn't thread-safe. + * But deferred (or paged) OSG loading runs in a separate thread, which would + * trigger the FGNasalModelData::modelLoaded callback. We're easily doomed + * when this happens and the model actually contains a Nasal "load" hook - which + * would run the Nasal parser and Nasal script execution in a separate thread... + * => Disabling the callback for now, to test if all Nasal related segfaults are + * gone. Proper resolution is TBD. We'll need to somehow decouple the OSG callback, + * so we can run the Nasal stuff in the main thread. + */ result= - SGModelLib::loadDeferredModel(fullPath.str(), globals->get_props(), - new FGNasalModelData); + SGModelLib::loadDeferredModel(fullPath.str(), globals->get_props()/*, + new FGNasalModelData*/); + } } catch (const sg_io_exception& exc) { string m(exc.getMessage()); m += " ";