From ff2afca50e386d27fbb622cabf07c593365ba457 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 31 Jan 2014 15:24:54 -0800 Subject: [PATCH 1/9] Special handling for blendshapes in Blender exports. They're under the model, like the geometry. --- interface/src/renderer/FBXReader.cpp | 103 ++++++++++++++++----------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index d128206c80..c07eb8469d 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -738,6 +738,22 @@ ExtractedMesh extractMesh(const FBXNode& object) { return data.extracted; } +FBXBlendshape extractBlendshape(const FBXNode& object) { + FBXBlendshape blendshape; + foreach (const FBXNode& data, object.children) { + if (data.name == "Indexes") { + blendshape.indices = getIntVector(data.properties, 0); + + } else if (data.name == "Vertices") { + blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0)); + + } else if (data.name == "Normals") { + blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0)); + } + } + return blendshape; +} + void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { glm::vec3 normal = glm::normalize(mesh.normals.at(firstIndex)); glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); @@ -760,6 +776,33 @@ QVector getIndices(const QVector ids, QVector modelIDs) { return indices; } +typedef QPair WeightedIndex; + +void addBlendshapes(const ExtractedBlendshape& extracted, const QList& indices, ExtractedMesh& extractedMesh) { + foreach (const WeightedIndex& index, indices) { + extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); + extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); + FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; + QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; + for (int i = 0; i < extracted.blendshape.indices.size(); i++) { + int oldIndex = extracted.blendshape.indices.at(i); + for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex); + it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) { + QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value()); + if (blendshapeIndex == blendshapeIndexMap.end()) { + blendshapeIndexMap.insert(it.value(), blendshape.indices.size()); + blendshape.indices.append(it.value()); + blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second); + blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second); + } else { + blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second; + blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second; + } + } + } + } +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -799,7 +842,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector jointRightFingertipIDs(jointRightFingertipNames.size()); QVariantHash blendshapeMappings = mapping.value("bs").toHash(); - typedef QPair WeightedIndex; QMultiHash blendshapeIndices; for (int i = 0;; i++) { QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; @@ -827,22 +869,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) meshes.insert(getID(object.properties), extractMesh(object)); } else { // object.properties.at(2) == "Shape" - ExtractedBlendshape extracted = { getID(object.properties) }; - - foreach (const FBXNode& data, object.children) { - if (data.name == "Indexes") { - extracted.blendshape.indices = getIntVector(data.properties, 0); - - } else if (data.name == "Vertices") { - extracted.blendshape.vertices = createVec3Vector( - getDoubleVector(data.properties, 0)); - - } else if (data.name == "Normals") { - extracted.blendshape.normals = createVec3Vector( - getDoubleVector(data.properties, 0)); - } - } - + ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) }; blendshapes.append(extracted); } } else if (object.name == "Model") { @@ -900,6 +927,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false; glm::vec3 rotationMin, rotationMax; FBXModel model = { name, -1 }; + ExtractedMesh* mesh = NULL; + QVector blendshapes; foreach (const FBXNode& subobject, object.children) { bool properties = false; QByteArray propertyName; @@ -969,9 +998,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } else if (subobject.name == "Vertices") { // it's a mesh as well as a model - meshes.insert(getID(object.properties), extractMesh(object)); + mesh = &meshes[getID(object.properties)]; + *mesh = extractMesh(object); + + } else if (subobject.name == "Shape") { + ExtractedBlendshape blendshape = { subobject.properties.at(0).toString(), + extractBlendshape(subobject) }; + blendshapes.append(blendshape); } } + + // add the blendshapes included in the model, if any + if (mesh) { + foreach (const ExtractedBlendshape& extracted, blendshapes) { + addBlendshapes(extracted, blendshapeIndices.values(extracted.id.toLatin1()), *mesh); + } + } + // see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html model.translation = translation; model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot); @@ -1084,29 +1127,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString blendshapeChannelID = parentMap.value(extracted.id); QString blendshapeID = parentMap.value(blendshapeChannelID); QString meshID = parentMap.value(blendshapeID); - ExtractedMesh& extractedMesh = meshes[meshID]; - foreach (const WeightedIndex& index, blendshapeChannelIndices.values(blendshapeChannelID)) { - extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); - extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); - FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; - QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; - for (int i = 0; i < extracted.blendshape.indices.size(); i++) { - int oldIndex = extracted.blendshape.indices.at(i); - for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex); - it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) { - QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value()); - if (blendshapeIndex == blendshapeIndexMap.end()) { - blendshapeIndexMap.insert(it.value(), blendshape.indices.size()); - blendshape.indices.append(it.value()); - blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second); - blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second); - } else { - blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second; - blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second; - } - } - } - } + addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); } // get offset transform from mapping From d2fd4bf445b22d1d5383bf591f9cefc5fe85c58a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Jan 2014 16:36:44 -0800 Subject: [PATCH 2/9] expose QTimer to the script engine --- libraries/script-engine/src/ScriptEngine.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6ea3742592..25467389de 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -105,6 +105,7 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents) { } Q_SCRIPT_DECLARE_QMETAOBJECT(AudioInjectorOptions, QObject*) +Q_SCRIPT_DECLARE_QMETAOBJECT(QTimer, QObject*) void ScriptEngine::init() { if (_isInitialized) { @@ -132,6 +133,9 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); + + QScriptValue timerValue = _engine.scriptValueFromQMetaObject(); + _engine.globalObject().setProperty("Timer", timerValue); registerGlobalObject("Agent", this); registerGlobalObject("Audio", &_audioScriptingInterface); From a7f240650584b3597a8af8f0a2303b5c919c0882 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Jan 2014 16:40:24 -0800 Subject: [PATCH 3/9] add an example timer script --- examples/timer.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/timer.js diff --git a/examples/timer.js b/examples/timer.js new file mode 100644 index 0000000000..7ce6418c24 --- /dev/null +++ b/examples/timer.js @@ -0,0 +1,5 @@ +var timer = new Timer(); +timer.interval = 1000; +timer.singleShot = true; // set this is you only want the timer to fire once +timer.timeout.connect(function() { print("TIMER FIRED!"); }); +timer.start(); From d65a3ee1747b160805e45bda12131f7b1e23c38b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Jan 2014 17:38:53 -0800 Subject: [PATCH 4/9] spacing cleanup in Application and ScriptEngine --- interface/src/Application.cpp | 51 ++++++++++---------- libraries/script-engine/src/ScriptEngine.cpp | 3 +- libraries/script-engine/src/ScriptEngine.h | 4 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf17736368..289e7e2430 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -321,7 +321,7 @@ Application::~Application() { _persistThread->deleteLater(); _persistThread = NULL; } - + storeSizeAndPosition(); saveScripts(); _sharedVoxelSystem.changeTree(new VoxelTree); @@ -4186,33 +4186,33 @@ void Application::packetSent(quint64 length) { _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length); } -void Application::loadScripts(){ - // loads all saved scripts - QSettings* settings = new QSettings(this); - int size = settings->beginReadArray("Settings"); - for(int i=0; isetArrayIndex(i); - QString string = settings->value("script").toString(); - loadScript(string); - } - settings->endArray(); - +void Application::loadScripts() { + // loads all saved scripts + QSettings* settings = new QSettings(this); + int size = settings->beginReadArray("Settings"); + + for (int i = 0; i < size; ++i){ + settings->setArrayIndex(i); + QString string = settings->value("script").toString(); + loadScript(string); + } + + settings->endArray(); } -void Application::saveScripts(){ - // saves all current running scripts - QSettings* settings = new QSettings(this); - settings->beginWriteArray("Settings"); - for(int i=0; i<_activeScripts.size(); ++i){ - settings->setArrayIndex(i); - settings->setValue("script", _activeScripts.at(i)); - } - settings->endArray(); - +void Application::saveScripts() { + // saves all current running scripts + QSettings* settings = new QSettings(this); + settings->beginWriteArray("Settings"); + for (int i = 0; i < _activeScripts.size(); ++i){ + settings->setArrayIndex(i); + settings->setValue("script", _activeScripts.at(i)); + } + + settings->endArray(); } -void Application::removeScriptName(const QString& fileNameString) -{ +void Application::removeScriptName(const QString& fileNameString) { _activeScripts.removeOne(fileNameString); } @@ -4244,7 +4244,8 @@ void Application::loadScript(const QString& fileNameString) { // start the script on a new thread... bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), + &_controllerScriptingInterface); scriptEngine->setupMenuItems(); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9a7a9197b0..a18fb58531 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -40,7 +40,8 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, AbstractMenuInterface* menu, +ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, + AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) : _isAvatar(false), _dataServerScriptingInterface(), diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8f29379266..a07f18f5f7 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,8 +33,8 @@ class ScriptEngine : public QObject { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, - AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); + const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, + AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); ~ScriptEngine(); From 40a963cf927500ab371918ba672cc1038b475ad1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 31 Jan 2014 17:50:51 -0800 Subject: [PATCH 5/9] some application destructor cleanup --- interface/src/AbstractLoggerInterface.h | 1 + interface/src/Application.cpp | 9 ++------- interface/src/FileLogger.cpp | 5 ++++- interface/src/FileLogger.h | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h index d8a48fc2fd..cedab1fad2 100644 --- a/interface/src/AbstractLoggerInterface.h +++ b/interface/src/AbstractLoggerInterface.h @@ -17,6 +17,7 @@ class AbstractLoggerInterface : public QObject { Q_OBJECT public: + AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {}; inline bool extraDebugging() { return _extraDebugging; }; inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; }; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 289e7e2430..d0d0fc4c40 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -154,7 +154,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _resetRecentMaxPacketsSoon(true), _swatch(NULL), _pasteMode(false), - _logger(new FileLogger()), + _logger(new FileLogger(this)), _persistThread(NULL) { _myAvatar = _avatarManager.getMyAvatar(); @@ -328,12 +328,7 @@ Application::~Application() { VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown Menu::getInstance()->deleteLater(); - - _avatarManager.clear(); - _myAvatar = NULL; - - delete _logger; - delete _settings; + delete _glWidget; } diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 3c98b285a3..81c626a46e 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -18,7 +18,10 @@ const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; const QString LOGS_DIRECTORY = "Logs"; -FileLogger::FileLogger() : _logData(NULL) { +FileLogger::FileLogger(QObject* parent) : + AbstractLoggerInterface(parent), + _logData(NULL) +{ setExtraDebugging(false); _fileName = FileUtils::standardPath(LOGS_DIRECTORY); diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index 6a17032ae2..35cafa4db7 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -16,7 +16,7 @@ class FileLogger : public AbstractLoggerInterface { Q_OBJECT public: - FileLogger(); + FileLogger(QObject* parent = NULL); virtual void addMessage(QString); virtual QStringList getLogData() { return _logData; }; From e1350f8e242736fa40cc404e7a6a4b524d8ea8b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 31 Jan 2014 19:00:24 -0800 Subject: [PATCH 6/9] Trying out a transform tweak. --- interface/src/renderer/FBXReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index c07eb8469d..fd844e61ec 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -1321,7 +1321,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) qDebug() << "Joint not in model list: " << jointID; fbxCluster.jointIndex = 0; } - fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink); extracted.mesh.clusters.append(fbxCluster); // override the bind rotation with the transform link From 2b4876181a93b1ba23b8ee76f7123a1336c9a591 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 31 Jan 2014 19:38:45 -0800 Subject: [PATCH 7/9] Different take on the transform fix. --- interface/src/renderer/FBXReader.cpp | 48 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index fd844e61ec..e2c3bfafdd 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -803,6 +803,22 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList& parentMap, + const QHash& models, const QString& modelID) { + QString topID = modelID; + forever { + foreach (const QString& parentID, parentMap.values(topID)) { + if (models.contains(parentID)) { + topID = parentID; + goto outerContinue; + } + } + return topID; + + outerContinue: ; + } +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -1142,6 +1158,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector modelIDs; QSet remainingModels; for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { + // models with clusters must be parented to the cluster top + foreach (const QString& deformerID, childMap.values(model.key())) { + foreach (const QString& clusterID, childMap.values(deformerID)) { + if (!clusters.contains(clusterID)) { + continue; + } + QString topID = getTopModelID(parentMap, models, childMap.value(clusterID)); + childMap.remove(parentMap.take(model.key()), model.key()); + parentMap.insert(model.key(), topID); + goto outerBreak; + } + } + outerBreak: + // make sure the parent is in the child map QString parent = parentMap.value(model.key()); if (!childMap.contains(parent, model.key())) { @@ -1150,20 +1180,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) remainingModels.insert(model.key()); } while (!remainingModels.isEmpty()) { - QString top = *remainingModels.constBegin(); - forever { - foreach (const QString& name, parentMap.values(top)) { - if (models.contains(name)) { - top = name; - goto outerContinue; - } - } - top = parentMap.value(top); - break; - - outerContinue: ; - } - appendModelIDs(top, childMap, models, remainingModels, modelIDs); + QString topID = getTopModelID(parentMap, models, *remainingModels.constBegin()); + appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); } // convert the models to joints @@ -1321,7 +1339,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) qDebug() << "Joint not in model list: " << jointID; fbxCluster.jointIndex = 0; } - fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink); + fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; extracted.mesh.clusters.append(fbxCluster); // override the bind rotation with the transform link From 9dd8c11e1e1efdca60d800734fc996b23b123929 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Feb 2014 09:59:16 -0800 Subject: [PATCH 8/9] change script timer setup to match HTML dom standard --- examples/timer.js | 13 ++++--- libraries/script-engine/src/ScriptEngine.cpp | 40 +++++++++++++++++--- libraries/script-engine/src/ScriptEngine.h | 9 ++++- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/examples/timer.js b/examples/timer.js index 7ce6418c24..db9a51ef6f 100644 --- a/examples/timer.js +++ b/examples/timer.js @@ -1,5 +1,8 @@ -var timer = new Timer(); -timer.interval = 1000; -timer.singleShot = true; // set this is you only want the timer to fire once -timer.timeout.connect(function() { print("TIMER FIRED!"); }); -timer.start(); +var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 1000); +var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000); + +// this would stop a scheduled single shot timer +Script.clearTimeout(one_timer); + +// this stops the repeating timer +Script.clearInterval(multiple_timer); \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d040e5b68e..8e45c319c2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -105,9 +105,6 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents) { return true; } -Q_SCRIPT_DECLARE_QMETAOBJECT(AudioInjectorOptions, QObject*) -Q_SCRIPT_DECLARE_QMETAOBJECT(QTimer, QObject*) - void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -134,9 +131,6 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - - QScriptValue timerValue = _engine.scriptValueFromQMetaObject(); - _engine.globalObject().setProperty("Timer", timerValue); registerGlobalObject("Script", this); registerGlobalObject("Audio", &_audioScriptingInterface); @@ -302,4 +296,38 @@ void ScriptEngine::stop() { _isFinished = true; } +void ScriptEngine::timerFired() { + QTimer* callingTimer = reinterpret_cast(sender()); + + // call the associated JS function, if it exists + QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); + if (timerFunction.isValid()) { + timerFunction.call(); + } + + if (!callingTimer->isActive()) { + // this timer is done, we can kill it + qDebug() << "Deleting a single shot timer"; + delete callingTimer; + } +} + +void ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { + // create the timer, add it to the map, and start it + QTimer* newTimer = new QTimer(this); + connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); + _timerFunctionMap.insert(newTimer, function); + + newTimer->setSingleShot(isSingleShot); + + newTimer->start(intervalMS); +} + +void ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { + setupTimerWithInterval(function, intervalMS, false); +} + +void ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { + setupTimerWithInterval(function, timeoutMS, true); +} diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index a07f18f5f7..d706ed7bb0 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -59,12 +59,17 @@ public: bool isAvatar() const { return _isAvatar; } void setAvatarData(AvatarData* avatarData, const QString& objectName); + + void timerFired(); public slots: void init(); void run(); /// runs continuously until Agent.stop() is called void stop(); void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller + + void setInterval(const QScriptValue& function, int intervalMS); + void setTimeout(const QScriptValue& function, int timeoutMS); signals: void willSendAudioDataCallback(); @@ -73,15 +78,17 @@ signals: void finished(const QString& fileNameString); protected: - QString _scriptContents; bool _isFinished; bool _isRunning; bool _isInitialized; QScriptEngine _engine; bool _isAvatar; + QHash _timerFunctionMap; private: + void setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); + static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface; From e2842ab7a472733cef92eef101674994028e345f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 3 Feb 2014 10:10:58 -0800 Subject: [PATCH 9/9] stop timers when the script does, add cleanup methods --- examples/timer.js | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 29 ++++++++++++++------ libraries/script-engine/src/ScriptEngine.h | 11 +++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/examples/timer.js b/examples/timer.js index db9a51ef6f..c7ad0290ab 100644 --- a/examples/timer.js +++ b/examples/timer.js @@ -1,4 +1,4 @@ -var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 1000); +var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 10000); var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000); // this would stop a scheduled single shot timer diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 8e45c319c2..3b5cedb3ad 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -312,22 +312,35 @@ void ScriptEngine::timerFired() { } } -void ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { +QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { // create the timer, add it to the map, and start it QTimer* newTimer = new QTimer(this); - connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); - _timerFunctionMap.insert(newTimer, function); - newTimer->setSingleShot(isSingleShot); + connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); + + // make sure the timer stops when the script does + connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); + + _timerFunctionMap.insert(newTimer, function); + newTimer->start(intervalMS); + return newTimer; } -void ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { - setupTimerWithInterval(function, intervalMS, false); +QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { + return setupTimerWithInterval(function, intervalMS, false); } -void ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { - setupTimerWithInterval(function, timeoutMS, true); +QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { + return setupTimerWithInterval(function, timeoutMS, true); +} + +void ScriptEngine::stopTimer(QTimer *timer) { + if (_timerFunctionMap.contains(timer)) { + timer->stop(); + _timerFunctionMap.remove(timer); + delete timer; + } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index d706ed7bb0..12909a16eb 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -68,9 +68,11 @@ public slots: void stop(); void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller - void setInterval(const QScriptValue& function, int intervalMS); - void setTimeout(const QScriptValue& function, int timeoutMS); - + QObject* setInterval(const QScriptValue& function, int intervalMS); + QObject* setTimeout(const QScriptValue& function, int timeoutMS); + void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + signals: void willSendAudioDataCallback(); void willSendVisualDataCallback(); @@ -87,7 +89,8 @@ protected: QHash _timerFunctionMap; private: - void setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); + void stopTimer(QTimer* timer); static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface;