diff --git a/src/AIModel/AIBase.cxx b/src/AIModel/AIBase.cxx index a9d64e341..1431cc411 100644 --- a/src/AIModel/AIBase.cxx +++ b/src/AIModel/AIBase.cxx @@ -917,7 +917,7 @@ int FGAIBase::_newAIModelID() { FGAIModelData::FGAIModelData(SGPropertyNode *root) - : _nasal( new FGNasalModelData(root) ), + : _nasal( new FGNasalModelDataProxy(root) ), _ready(false), _initialized(false) { @@ -926,25 +926,17 @@ FGAIModelData::FGAIModelData(SGPropertyNode *root) FGAIModelData::~FGAIModelData() { delete _nasal; + _nasal = NULL; } void FGAIModelData::modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *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! + // WARNING: Called in a separate OSG thread! Only use thread-safe stuff here... if (_ready) return; - _fxpath = _prop->getStringValue("sound/path"); - _prop = prop; - _path = path; + + _fxpath = prop->getStringValue("sound/path"); + _nasal->modelLoaded(path, prop, n); + _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 b4782eb43..eaf2c3ea1 100644 --- a/src/AIModel/AIBase.hxx +++ b/src/AIModel/AIBase.hxx @@ -42,7 +42,7 @@ class SGMaterial; class FGAIManager; class FGAIFlightPlan; class FGFX; -class FGNasalModelData; +class FGNasalModelDataProxy; class FGAIModelData; // defined below @@ -456,16 +456,15 @@ public: /** init hook to be called after model is loaded. * Not thread-safe. Call from main thread only. */ - void init(void); + void init(void) { _initialized = true; } bool needInitilization(void) { return _ready && !_initialized;} bool isInitialized(void) { return _initialized;} inline std::string& get_sound_path() { return _fxpath;} private: - FGNasalModelData *_nasal; - SGPropertyNode_ptr _prop; - std::string _path, _fxpath; + FGNasalModelDataProxy *_nasal; + std::string _fxpath; bool _ready; bool _initialized; }; diff --git a/src/Scenery/tilemgr.cxx b/src/Scenery/tilemgr.cxx index e235a8eda..4ebce444c 100644 --- a/src/Scenery/tilemgr.cxx +++ b/src/Scenery/tilemgr.cxx @@ -244,21 +244,12 @@ FGTileMgr::loadTileModel(const string& modelPath, bool cacheModel) if(cacheModel) result = SGModelLib::loadModel(fullPath.str(), globals->get_props(), - new FGNasalModelData); + _disableNasalHooks->getBoolValue() ? NULL : new FGNasalModelDataProxy); 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(), - _disableNasalHooks->getBoolValue() ? NULL : new FGNasalModelData); + _disableNasalHooks->getBoolValue() ? NULL : new FGNasalModelDataProxy); } } catch (const sg_io_exception& exc) { string m(exc.getMessage()); diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 777d1eede..eca5d59c4 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -878,6 +878,19 @@ void FGNasalSys::update(double) _dead_listener.clear(); } + if (!_loadList.empty()) + { + // process Nasal load hook (only one per update loop to avoid excessive lags) + _loadList.pop()->load(); + } + else + if (!_unloadList.empty()) + { + // process pending Nasal unload hooks after _all_ load hooks were processed + // (only unload one per update loop to avoid excessive lags) + _unloadList.pop()->unload(); + } + // The global context is a legacy thing. We use dynamically // created contexts for naCall() now, so that we can call them // recursively. But there are still spots that want to use it for @@ -1358,36 +1371,26 @@ bool FGNasalListener::changed(SGPropertyNode* node) unsigned int FGNasalModelData::_module_id = 0; -void FGNasalModelData::modelLoaded(const string& path, SGPropertyNode *prop, - osg::Node *) +void FGNasalModelData::load() { - if(!prop) - return; - SGPropertyNode *nasal = prop->getNode("nasal"); - if(!nasal) - return; - - SGPropertyNode *load = nasal->getNode("load"); - _unload = nasal->getNode("unload"); - if(!load && !_unload) - return; - std::stringstream m; m << "__model" << _module_id++; _module = m.str(); - const char *s = load ? load->getStringValue() : ""; + SG_LOG(SG_NASAL, SG_DEBUG, "Loading nasal module " << _module.c_str()); + + const char *s = _load ? _load->getStringValue() : ""; naRef arg[2]; arg[0] = nasalSys->propNodeGhost(_root); - arg[1] = nasalSys->propNodeGhost(prop); - nasalSys->createModule(_module.c_str(), path.c_str(), s, strlen(s), + arg[1] = nasalSys->propNodeGhost(_prop); + nasalSys->createModule(_module.c_str(), _path.c_str(), s, strlen(s), _root, 2, arg); } -FGNasalModelData::~FGNasalModelData() +void FGNasalModelData::unload() { - if(_module.empty()) + if (_module.empty()) return; if(!nasalSys) { @@ -1396,14 +1399,52 @@ FGNasalModelData::~FGNasalModelData() return; } - if(_unload) { + SG_LOG(SG_NASAL, SG_DEBUG, "Unloading nasal module " << _module.c_str()); + + if (_unload) + { const char *s = _unload->getStringValue(); nasalSys->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _root); } + nasalSys->deleteModule(_module.c_str()); } +void FGNasalModelDataProxy::modelLoaded(const string& path, SGPropertyNode *prop, + osg::Node *) +{ + if(!nasalSys) { + SG_LOG(SG_NASAL, SG_WARN, "Trying to run a script " + "without Nasal subsystem present."); + return; + } + if(!prop) + return; + + SGPropertyNode *nasal = prop->getNode("nasal"); + if(!nasal) + return; + + SGPropertyNode* load = nasal->getNode("load"); + SGPropertyNode* unload = nasal->getNode("unload"); + + if ((!load) && (!unload)) + return; + + _data = new FGNasalModelData(_root, path, prop, load, unload); + + // register Nasal module to be created and loaded in the main thread. + nasalSys->registerToLoad(_data); +} + +FGNasalModelDataProxy::~FGNasalModelDataProxy() +{ + // when necessary, register Nasal module to be destroyed/unloaded + // in the main thread. + if ((_data.valid())&&(nasalSys)) + nasalSys->registerToUnload(_data); +} // NasalXMLVisitor class: handles EasyXML visitor callback for parsexml() // diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index ceff996da..c8367f41b 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -7,6 +7,7 @@ #include #include #include +#include #include using std::map; @@ -15,6 +16,56 @@ using std::map; class FGNasalScript; class FGNasalListener; + +/** Nasal model data container. + * load and unload methods must be run in main thread (not thread-safe). */ +class FGNasalModelData : public SGReferenced +{ +public: + /** Constructor to be run in an arbitrary thread. */ + FGNasalModelData(SGPropertyNode *root, const string& path, SGPropertyNode *prop, + SGPropertyNode* load, SGPropertyNode* unload) : + _path(path), + _root(root), _prop(prop), + _load(load), _unload(unload) + { + } + + /** Load hook. Always call from inside the main loop. */ + void load(); + + /** Unload hook. Always call from inside the main loop. */ + void unload(); + +private: + static unsigned int _module_id; + + string _module, _path; + SGPropertyNode_ptr _root, _prop; + SGConstPropertyNode_ptr _load, _unload; +}; + +/** Thread-safe proxy for FGNasalModelData. + * modelLoaded/destroy methods only register the requested + * operation. Actual (un)loading of Nasal module is deferred + * and done in the main loop. */ +class FGNasalModelDataProxy : public simgear::SGModelData +{ +public: + FGNasalModelDataProxy(SGPropertyNode *root = 0) : + _root(root), _data(0) + { + } + + ~FGNasalModelDataProxy(); + + void modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *); + +protected: + SGPropertyNode_ptr _root; + SGSharedPtr _data; +}; + class FGNasalSys : public SGSubsystem { public: @@ -63,11 +114,17 @@ public: naRef call(naRef code, int argc, naRef* args, naRef locals); naRef propNodeGhost(SGPropertyNode* handle); + void registerToLoad(FGNasalModelData* data) { _loadList.push(data);} + void registerToUnload(FGNasalModelData* data) { _unloadList.push(data);} + private: friend class FGNasalScript; friend class FGNasalListener; friend class FGNasalModuleListener; + SGLockedQueue > _loadList; + SGLockedQueue > _unloadList; + // // FGTimer subclass for handling Nasal timer callbacks. // See the implementation of the settimer() extension function for @@ -165,20 +222,6 @@ private: }; -class FGNasalModelData : public simgear::SGModelData { -public: - FGNasalModelData(SGPropertyNode *root = 0) : _root(root), _unload(0) {} - ~FGNasalModelData(); - void modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *); - -private: - static unsigned int _module_id; - string _module; - SGPropertyNode_ptr _root; - SGConstPropertyNode_ptr _unload; -}; - - class NasalXMLVisitor : public XMLVisitor { public: NasalXMLVisitor(naContext c, int argc, naRef* args);