From f85cb2c888f4516bc1ef745cd1048cd7b8665a51 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 11 Sep 2015 19:12:09 -0700 Subject: [PATCH 01/35] first cut at cleaning up ScriptEngine class --- assignment-client/src/Agent.cpp | 7 +- assignment-client/src/Agent.h | 2 +- interface/src/Application.cpp | 68 +++--- .../src/EntityTreeRenderer.cpp | 3 + libraries/script-engine/src/ScriptEngine.cpp | 208 ++++++++++++------ libraries/script-engine/src/ScriptEngine.h | 160 ++++++++------ 6 files changed, 282 insertions(+), 166 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 300976f81c..4da7ff5c78 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -169,9 +169,8 @@ void Agent::run() { _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); auto avatarHashMap = DependencyManager::set(); - - _scriptEngine.setAvatarHashMap(avatarHashMap.data(), "AvatarList"); - + _scriptEngine.registerGlobalObject("AvatarList", avatarHashMap.data()); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar"); @@ -185,6 +184,8 @@ void Agent::run() { _scriptEngine.setParentURL(_payload); } + // FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why + // viewers would need this called. _scriptEngine.init(); // must be done before we set up the viewers _scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get().data()); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 4c207e59aa..abd99e8c7e 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -37,7 +37,7 @@ class Agent : public ThreadedAssignment { public: Agent(NLPacket& packet); - void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); } + void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); } bool isAvatar() const { return _scriptEngine.isAvatar(); } bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e4f902ef1d..4abfed1a03 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4011,8 +4011,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri AvatarManager::registerMetaTypes(scriptEngine); // hook our avatar and avatar hash map object into this script engine - scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features - scriptEngine->setAvatarHashMap(DependencyManager::get().data(), "AvatarList"); + scriptEngine->registerGlobalObject("MyAvatar", _myAvatar); + scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Camera", &_myCamera); @@ -4036,9 +4036,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); - QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter, windowValue); + LocationScriptingInterface::locationSetter, "Window"); // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); @@ -4068,9 +4068,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Paths", DependencyManager::get().data()); - QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance()); - scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0); - scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0); + scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance()); + scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0); + scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0); scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); @@ -4079,31 +4079,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri #ifdef HAVE_RTMIDI scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); #endif - - // TODO: Consider moving some of this functionality into the ScriptEngine class instead. It seems wrong that this - // work is being done in the Application class when really these dependencies are more related to the ScriptEngine's - // implementation - QThread* workerThread = new QThread(this); - QString scriptEngineName = QString("Script Thread:") + scriptEngine->getFilename(); - workerThread->setObjectName(scriptEngineName); - - // when the worker thread is started, call our engine's run.. - connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run); - - // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue - connect(scriptEngine, &ScriptEngine::doneRunning, scriptEngine, &ScriptEngine::deleteLater); - connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); - - // tell the thread to stop when the script engine is done - connect(scriptEngine, &ScriptEngine::destroyed, workerThread, &QThread::quit); - - auto nodeList = DependencyManager::get(); - connect(nodeList.data(), &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled); - - scriptEngine->moveToThread(workerThread); - - // Starts an event loop, and emits workerThread->started() - workerThread->start(); } void Application::initializeAcceptedFiles() { @@ -4249,17 +4224,34 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser scriptEngine->setUserLoaded(isUserLoaded); if (scriptFilename.isNull()) { + // This appears to be the script engine used by the script widget's evaluation window before the file has been saved... + + qDebug() << "############################# HELLO WE ARE HERE!!!! ##################################"; // this had better be the script editor (we should de-couple so somebody who thinks they are loading a script // doesn't just get an empty script engine) // we can complete setup now since there isn't a script we have to load registerScriptEngineWithApplicationServices(scriptEngine); + scriptEngine->runInThread(); + + //FIXME - intentionally attempting to test thread safe call to registerGlobalObject() + qDebug() << "about to attempt to call registerGlobalObject on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerGlobalObject("LODManager2", DependencyManager::get().data()); + + qDebug() << "about to attempt to call registerFunction on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerFunction("WebWindow2", WebWindowClass::constructor, 1); + + qDebug() << "about to attempt to call registerGetterSetter on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerGetterSetter("foo_location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); + + } else { // connect to the appropriate signals of this script engine connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded); connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &Application::handleScriptLoadError); // get the script engine object to load the script at the designated script URL + qDebug() << "calling scriptEngine->loadURL(" << scriptUrl << ", " << reload << ");"; scriptEngine->loadURL(scriptUrl, reload); } @@ -4276,6 +4268,7 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) { } void Application::handleScriptEngineLoaded(const QString& scriptFilename) { + qDebug() << "handleScriptEngineLoaded().... scriptFilename:" << scriptFilename; ScriptEngine* scriptEngine = qobject_cast(sender()); _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); @@ -4284,6 +4277,17 @@ void Application::handleScriptEngineLoaded(const QString& scriptFilename) { // register our application services and set it off on its own thread registerScriptEngineWithApplicationServices(scriptEngine); + scriptEngine->runInThread(); + + //FIXME - intentionally attempting to test thread safe call to registerGlobalObject() + qDebug() << "about to attempt to call registerGlobalObject on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerGlobalObject("LODManager2", DependencyManager::get().data()); + + qDebug() << "about to attempt to call registerFunction on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerFunction("WebWindow2", WebWindowClass::constructor, 1); + + qDebug() << "about to attempt to call registerGetterSetter on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; + scriptEngine->registerGetterSetter("foo_location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); } void Application::handleScriptLoadError(const QString& scriptFilename) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 93a9996740..61ff8bbea8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -116,6 +116,9 @@ void EntityTreeRenderer::init() { _scriptingServices->getControllerScriptingInterface(), false); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); + // FIXME - this is dubious need to rework + _entitiesScriptEngine->runInThread(); + _sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL, false); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e401398d24..d9adb1f6d2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -44,6 +44,8 @@ #include "MIDIEvent.h" +Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) +static int functionSignatureMetaID = qRegisterMetaType(); static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ QString message = ""; @@ -97,7 +99,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _controllerScriptingInterface(controllerScriptingInterface), _wantSignals(wantSignals), _avatarData(NULL), - _scriptName(), _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), @@ -122,6 +123,27 @@ ScriptEngine::~ScriptEngine() { } } +void ScriptEngine::runInThread() { + QThread* workerThread = new QThread(this); + QString scriptEngineName = QString("Script Thread:") + getFilename(); + workerThread->setObjectName(scriptEngineName); + + // when the worker thread is started, call our engine's run.. + connect(workerThread, &QThread::started, this, &ScriptEngine::run); + + // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue + connect(this, &ScriptEngine::doneRunning, this, &ScriptEngine::deleteLater); + connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); + + // tell the thread to stop when the script engine is done + connect(this, &ScriptEngine::destroyed, workerThread, &QThread::quit); + + moveToThread(workerThread); + + // Starts an event loop, and emits workerThread->started() + workerThread->start(); +} + QSet ScriptEngine::_allKnownScriptEngines; QMutex ScriptEngine::_allScriptsMutex; bool ScriptEngine::_stoppingAllScripts = false; @@ -180,8 +202,6 @@ void ScriptEngine::stopAllScripts(QObject* application) { void ScriptEngine::waitTillDoneRunning() { - QString scriptName = getFilename(); - // If the script never started running or finished running before we got here, we don't need to wait for it if (_isRunning) { @@ -244,14 +264,6 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa registerGlobalObject(objectName, _avatarData); } -void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { - // remove the old Avatar property, if it exists - globalObject().setProperty(objectName, QScriptValue()); - - // give the script engine the new avatar hash map - registerGlobalObject(objectName, avatarHashMap); -} - bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; @@ -261,7 +273,10 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents, const QStrin return true; } +// FIXME - remove the file/url scheme code here and let the script cache handle things void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { + qDebug() << "ScriptEngine::loadURL(" << scriptURL << ", " << reload << ");"; + if (_isRunning) { return; } @@ -298,7 +313,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { } else { bool isPending; auto scriptCache = DependencyManager::get(); + qDebug() << "calling scriptCache->getScript(" << url << ", " << reload << ");"; scriptCache->getScript(url, this, isPending, reload); + qDebug() << " scriptCache->getScript(" << url << ", " << reload << ") ... isPending:" << isPending; + } } } @@ -383,57 +401,97 @@ void ScriptEngine::init() { globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); } -QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { +void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; + QMetaObject::invokeMethod(this, "registerGlobalObject", + Q_ARG(const QString&, name), + Q_ARG(QObject*, object)); + return; + } + //qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; + if (object) { QScriptValue value = newQObject(object); globalObject().setProperty(name, value); - return value; } - return QScriptValue::NullValue; } -void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { - registerFunction(globalObject(), name, fun, numArguments); +void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; + QMetaObject::invokeMethod(this, "registerFunction", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } + //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; + + QScriptValue scriptFun = newFunction(functionSignature, numArguments); + globalObject().setProperty(name, scriptFun); } -void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { - QScriptValue scriptFun = newFunction(fun, numArguments); - parent.setProperty(name, scriptFun); +void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; + QMetaObject::invokeMethod(this, "registerFunction", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } + //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; + + QScriptValue object = globalObject().property(parent); + if (object.isValid()) { + QScriptValue scriptFun = newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } } void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, QScriptValue object) { + QScriptEngine::FunctionSignature setter, const QString& parent) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + " name:" << name << "parent:" << parent; + QMetaObject::invokeMethod(this, "registerGetterSetter", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, getter), + Q_ARG(QScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); + return; + } + //qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; + QScriptValue setterFunction = newFunction(setter, 1); QScriptValue getterFunction = newFunction(getter); - if (!object.isNull()) { - object.setProperty(name, setterFunction, QScriptValue::PropertySetter); - object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + if (!parent.isNull()) { + QScriptValue object = globalObject().property(parent); + if (object.isValid()) { + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } } else { globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); } } -// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args -void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator) { - if (!_registeredHandlers.contains(entityID)) { - return; - } - const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - if (!handlersOnEntity.contains(eventName)) { - return; - } - QScriptValueList handlersForEvent = handlersOnEntity[eventName]; - if (!handlersForEvent.isEmpty()) { - QScriptValueList args = argGenerator(); - for (int i = 0; i < handlersForEvent.count(); ++i) { - handlersForEvent[i].call(QScriptValue(), args); - } - } -} // Unregister the handlers for this eventName and entityID. void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << " eventName:" << eventName; + QMetaObject::invokeMethod(this, "removeEventHandler", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, eventName), + Q_ARG(QScriptValue, handler)); + return; + } + //qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + if (!_registeredHandlers.contains(entityID)) { return; } @@ -449,6 +507,17 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin } // Register the handler. void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << " eventName:" << eventName; + QMetaObject::invokeMethod(this, "addEventHandler", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, eventName), + Q_ARG(QScriptValue, handler)); + return; + } + //qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... // Connect up ALL the handlers to the global entities object's signals. // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) @@ -503,34 +572,23 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& } -void ScriptEngine::evaluate() { - if (_stoppingAllScripts) { - return; // bail early - } - - if (!_isInitialized) { - init(); - } - - QScriptValue result = evaluate(_scriptContents); - - // TODO: why do we check this twice? It seems like the call to clearExceptions() in the lower level evaluate call - // will cause this code to never actually run... - if (hasUncaughtException()) { - int line = uncaughtExceptionLineNumber(); - qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString(); - if (_wantSignals) { - emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); - } - clearExceptions(); - } -} - QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { if (_stoppingAllScripts) { return QScriptValue(); // bail early } + if (QThread::currentThread() != thread()) { + QScriptValue result; + qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; + QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QScriptValue, result), + Q_ARG(const QString&, program), + Q_ARG(const QString&, fileName), + Q_ARG(int, lineNumber)); + return result; + } + _evaluatesPending++; QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); if (hasUncaughtException()) { @@ -967,6 +1025,26 @@ void ScriptEngine::load(const QString& loadFile) { } } -void ScriptEngine::nodeKilled(SharedNodePointer node) { - _outgoingScriptAudioSequenceNumbers.remove(node->getUUID()); +// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args +void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; + assert(false); + return; + } + + if (!_registeredHandlers.contains(entityID)) { + return; + } + const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + if (!handlersOnEntity.contains(eventName)) { + return; + } + QScriptValueList handlersForEvent = handlersOnEntity[eventName]; + if (!handlersForEvent.isEmpty()) { + QScriptValueList args = argGenerator(); + for (int i = 0; i < handlersForEvent.count(); ++i) { + handlersForEvent[i].call(QScriptValue(), args); + } + } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index b9156c718e..ed8d6ad727 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -50,79 +50,75 @@ public: ~ScriptEngine(); - ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + /// Launch the script running in a dedicated thread. This will have the side effect of evalulating + /// the current script contents and calling run(). Callers will likely want to register the script with external + /// services before calling this. + void runInThread(); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can + // properly ensure they are only called on the correct thread + + /// registers a global object by name + Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); + + /// registers a global getter/setter + Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); - /// sets the script contents, will return false if failed, will fail if script is already running - bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); + /// register a global function + Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); - const QString& getScriptName() const { return _scriptName; } - - QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name - void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue); - void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); - void registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, + /// register a function as a method on a previously registered global object + Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); - Q_INVOKABLE void setIsAvatar(bool isAvatar); - bool isAvatar() const { return _isAvatar; } + /// evaluate some code in the context of the ScriptEngine and return the result + Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); // this is also used by the script tool widget - void setAvatarData(AvatarData* avatarData, const QString& objectName); - void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName); - - bool isListeningToAudioStream() const { return _isListeningToAudioStream; } - void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } - - void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } - bool isPlayingAvatarSound() const { return _avatarSound != NULL; } - - void init(); - void run(); /// runs continuously until Agent.stop() is called - void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller - - void timerFired(); - - bool hasScript() const { return !_scriptContents.isEmpty(); } - - bool isFinished() const { return _isFinished; } - bool isRunning() const { return _isRunning; } - bool evaluatePending() const { return _evaluatesPending > 0; } - - void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } - bool isUserLoaded() const { return _isUserLoaded; } - - void setIsAgent(bool isAgent) { _isAgent = isAgent; } - - void setParentURL(const QString& parentURL) { _parentURL = parentURL; } - - QString getFilename() const; - - static void stopAllScripts(QObject* application); - - void waitTillDoneRunning(); - - virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); - virtual void errorInLoadingScript(const QUrl& url); + /// if the script engine is not already running, this will download the URL and start the process of seting it up + /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed + /// to scripts. we may not need this to be invokable + void loadURL(const QUrl& scriptURL, bool reload); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - these are intended to be public interfaces available to scripts Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); -public slots: - void loadURL(const QUrl& scriptURL, bool reload); - void stop(); + Q_INVOKABLE void load(const QString& loadfile); + Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue()); - QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); - 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)); } - void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); - void include(const QString& includeFile, QScriptValue callback = QScriptValue()); - void load(const QString& loadfile); - void print(const QString& message); - QUrl resolvePath(const QString& path) const; + Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); + Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); + Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + Q_INVOKABLE void print(const QString& message); + Q_INVOKABLE QUrl resolvePath(const QString& path) const; - void nodeKilled(SharedNodePointer node); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts + Q_INVOKABLE void stop(); + + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget + bool isRunning() const { return _isRunning; } // used by ScriptWidget + + static void stopAllScripts(QObject* application); // used by Application on shutdown + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - These are the callback implementations for ScriptUser the get called by ScriptCache when the contents + // of a script are available. + virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); + virtual void errorInLoadingScript(const QUrl& url); + + // These are currently used by Application to track if a script is user loaded or not. Consider finding a solution + // inside of Application so that the ScriptEngine class is not polluted by this notion + void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } + bool isUserLoaded() const { return _isUserLoaded; } + + // NOTE - this is used by the TypedArray implemetation. we need to review this for thread safety + ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } signals: void scriptLoaded(const QString& scriptFilename); @@ -158,16 +154,17 @@ protected: bool _wantSignals = true; private: + QString getFilename() const; + void waitTillDoneRunning(); + bool evaluatePending() const { return _evaluatesPending > 0; } + void timerFired(); void stopAllTimers(); - void sendAvatarIdentityPacket(); - void sendAvatarBillboardPacket(); QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); AbstractControllerScriptingInterface* _controllerScriptingInterface; AvatarData* _avatarData; - QString _scriptName; QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; @@ -186,6 +183,39 @@ private: static QMutex _allScriptsMutex; static bool _stoppingAllScripts; static bool _doneRunningThisScript; + +private: + //FIXME- EntityTreeRender shouldn't be using these methods directly -- these methods need to be depricated from the public interfaces + friend class EntityTreeRenderer; + + //FIXME - used in EntityTreeRenderer when evaluating entity scripts, which needs to be moved into ScriptEngine + // also used in Agent, but that would be fixed if Agent was using proper apis for loading content + void setParentURL(const QString& parentURL) { _parentURL = parentURL; } + +private: + //FIXME- Agent shouldn't be using these methods directly -- these methods need to be depricated from the public interfaces + friend class Agent; + + /// FIXME - DEPRICATED - remove callers and fix to use standard API + /// sets the script contents, will return false if failed, will fail if script is already running + bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); + + /// FIXME - these should only be used internally + void init(); + void run(); + + // FIXME - all of these needto be removed and the code that depends on it in Agent.cpp should be moved into Agent.cpp + void setIsAgent(bool isAgent) { _isAgent = isAgent; } + void setIsAvatar(bool isAvatar); + bool isAvatar() const { return _isAvatar; } + void setAvatarData(AvatarData* avatarData, const QString& objectName); + bool isListeningToAudioStream() const { return _isListeningToAudioStream; } + void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } + void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } + bool isPlayingAvatarSound() const { return _avatarSound != NULL; } + + void sendAvatarIdentityPacket(); + void sendAvatarBillboardPacket(); }; #endif // hifi_ScriptEngine_h From 8395fb6eff2e21e0c53fed9069c52fb315b65a2a Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 11 Sep 2015 21:24:39 -0700 Subject: [PATCH 02/35] normalize how ScriptCache::getScript() works relative to ResourceManager --- libraries/networking/src/ResourceManager.cpp | 26 +++++++++----- libraries/networking/src/ResourceManager.h | 1 + libraries/script-engine/src/ScriptCache.cpp | 6 ++-- libraries/script-engine/src/ScriptCache.h | 4 +-- libraries/script-engine/src/ScriptEngine.cpp | 38 ++------------------ 5 files changed, 28 insertions(+), 47 deletions(-) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 8af33c5463..774664f2c8 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -17,21 +17,31 @@ #include -ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { +QUrl ResourceManager::normalizeURL(const QUrl& url) { auto scheme = url.scheme(); + if (!(scheme == URL_SCHEME_FILE || + scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP || + scheme == URL_SCHEME_ATP)) { + + // check the degenerative file case: on windows we can often have urls of the form c:/filename + // this checks for and works around that case. + QUrl urlWithFileScheme{ URL_SCHEME_FILE + ":///" + url.toString() }; + if (!urlWithFileScheme.toLocalFile().isEmpty()) { + return urlWithFileScheme; + } + } + return url; +} + +ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { + auto normalizedURL = normalizeURL(url); + auto scheme = normalizedURL.scheme(); if (scheme == URL_SCHEME_FILE) { return new FileResourceRequest(parent, url); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { return new HTTPResourceRequest(parent, url); } else if (scheme == URL_SCHEME_ATP) { return new AssetResourceRequest(parent, url); - } else { - // check the degenerative file case: on windows we can often have urls of the form c:/filename - // this checks for and works around that case. - QUrl urlWithFileScheme { URL_SCHEME_FILE + ":///" + url.toString() }; - if (!urlWithFileScheme.toLocalFile().isEmpty()) { - return new FileResourceRequest(parent, urlWithFileScheme); - } } qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 3748036c8e..40b67b1cd1 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -24,6 +24,7 @@ const QString URL_SCHEME_ATP = "atp"; class ResourceManager { public: + static QUrl normalizeURL(const QUrl& url); static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); }; diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 9e04cd4ec3..38837ec4b9 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -26,7 +26,8 @@ ScriptCache::ScriptCache(QObject* parent) { // nothing to do here... } -QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) { +QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool reload) { + QUrl url = ResourceManager::normalizeURL(unnormalizedURL); QString scriptContents; if (_scriptCache.contains(url) && !reload) { qCDebug(scriptengine) << "Found script in cache:" << url.toString(); @@ -50,7 +51,8 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is return scriptContents; } -void ScriptCache::deleteScript(const QUrl& url) { +void ScriptCache::deleteScript(const QUrl& unnormalizedURL) { + QUrl url = ResourceManager::normalizeURL(unnormalizedURL); if (_scriptCache.contains(url)) { qCDebug(scriptengine) << "Delete script from cache:" << url.toString(); _scriptCache.remove(url); diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h index 25a36c04d8..89db5596ba 100644 --- a/libraries/script-engine/src/ScriptCache.h +++ b/libraries/script-engine/src/ScriptCache.h @@ -26,8 +26,8 @@ class ScriptCache : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool redownload = false); - void deleteScript(const QUrl& url); + QString getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool redownload = false); + void deleteScript(const QUrl& unnormalizedURL); void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); } bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d9adb1f6d2..5c4623f7cf 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -275,8 +275,6 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents, const QStrin // FIXME - remove the file/url scheme code here and let the script cache handle things void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { - qDebug() << "ScriptEngine::loadURL(" << scriptURL << ", " << reload << ");"; - if (_isRunning) { return; } @@ -286,39 +284,9 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { QUrl url(scriptURL); - // if the scheme length is one or lower, maybe they typed in a file, let's try - const int WINDOWS_DRIVE_LETTER_SIZE = 1; - if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { - url = QUrl::fromLocalFile(_fileNameString); - } - - // ok, let's see if it's valid... and if so, load it - if (url.isValid()) { - if (url.scheme() == "file") { - _fileNameString = url.toLocalFile(); - QFile scriptFile(_fileNameString); - if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { - qCDebug(scriptengine) << "ScriptEngine loading file:" << _fileNameString; - QTextStream in(&scriptFile); - _scriptContents = in.readAll(); - if (_wantSignals) { - emit scriptLoaded(_fileNameString); - } - } else { - qCDebug(scriptengine) << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__; - if (_wantSignals) { - emit errorLoadingScript(_fileNameString); - } - } - } else { - bool isPending; - auto scriptCache = DependencyManager::get(); - qDebug() << "calling scriptCache->getScript(" << url << ", " << reload << ");"; - scriptCache->getScript(url, this, isPending, reload); - qDebug() << " scriptCache->getScript(" << url << ", " << reload << ") ... isPending:" << isPending; - - } - } + bool isPending; + auto scriptCache = DependencyManager::get(); + scriptCache->getScript(url, this, isPending, reload); } void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { From 54c56a92f1f5f1d63b78f3d3856aac5c2511bc64 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 11 Sep 2015 21:56:00 -0700 Subject: [PATCH 03/35] move agent specific code out of ScriptEngine --- assignment-client/src/Agent.cpp | 159 ++++++++++++++++++- assignment-client/src/Agent.h | 37 +++-- libraries/script-engine/src/ScriptEngine.cpp | 158 ------------------ libraries/script-engine/src/ScriptEngine.h | 23 +-- 4 files changed, 185 insertions(+), 192 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4da7ff5c78..3cbc9cbdd7 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -43,7 +43,6 @@ Agent::Agent(NLPacket& packet) : { // be the parent of the script engine so it gets moved when we do _scriptEngine.setParent(this); - _scriptEngine.setIsAgent(true); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -166,7 +165,7 @@ void Agent::run() { scriptedAvatar.setSkeletonModelURL(QUrl()); // give this AvatarData object to the script engine - _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); + setAvatarData(&scriptedAvatar, "Avatar"); auto avatarHashMap = DependencyManager::set(); _scriptEngine.registerGlobalObject("AvatarList", avatarHashMap.data()); @@ -200,9 +199,165 @@ void Agent::run() { _entityViewer.init(); entityScriptingInterface->setEntityTree(_entityViewer.getTree()); + // wire up our additional agent related processing to the update signal + QObject::connect(&_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio); + _scriptEngine.setScriptContents(scriptContents); _scriptEngine.run(); setFinished(true); + + // kill the avatar identity timer + delete _avatarIdentityTimer; + +} + +void Agent::setIsAvatar(bool isAvatar) { + _isAvatar = isAvatar; + + if (_isAvatar && !_avatarIdentityTimer) { + // set up the avatar timers + _avatarIdentityTimer = new QTimer(this); + _avatarBillboardTimer = new QTimer(this); + + // connect our slot + connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); + connect(_avatarBillboardTimer, &QTimer::timeout, this, &Agent::sendAvatarBillboardPacket); + + // start the timers + _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); + _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); + } + + if (!_isAvatar) { + delete _avatarIdentityTimer; + _avatarIdentityTimer = NULL; + delete _avatarBillboardTimer; + _avatarBillboardTimer = NULL; + } +} + +void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) { + _avatarData = avatarData; + _scriptEngine.registerGlobalObject(objectName, avatarData); +} + +void Agent::sendAvatarIdentityPacket() { + if (_isAvatar && _avatarData) { + _avatarData->sendIdentityPacket(); + } +} + +void Agent::sendAvatarBillboardPacket() { + if (_isAvatar && _avatarData) { + _avatarData->sendBillboardPacket(); + } +} + + +void Agent::processAgentAvatarAndAudio(float deltaTime) { + qDebug() << "processAgentAvatarAndAudio()"; + if (!_scriptEngine.isFinished() && _isAvatar && _avatarData) { + + const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) + / (1000 * 1000)) + 0.5); + const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); + + QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); + _avatarData->doneEncoding(true); + auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size()); + + avatarPacket->write(avatarByteArray); + + auto nodeList = DependencyManager::get(); + + nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); + + if (_isListeningToAudioStream || _avatarSound) { + // if we have an avatar audio stream then send it out to our audio-mixer + bool silentFrame = true; + + int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES; + const int16_t* nextSoundOutput = NULL; + + if (_avatarSound) { + + const QByteArray& soundByteArray = _avatarSound->getByteArray(); + nextSoundOutput = reinterpret_cast(soundByteArray.data() + + _numAvatarSoundSentBytes); + + int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES + ? SCRIPT_AUDIO_BUFFER_BYTES + : soundByteArray.size() - _numAvatarSoundSentBytes; + numAvailableSamples = numAvailableBytes / sizeof(int16_t); + + + // check if the all of the _numAvatarAudioBufferSamples to be sent are silence + for (int i = 0; i < numAvailableSamples; ++i) { + if (nextSoundOutput[i] != 0) { + silentFrame = false; + break; + } + } + + _numAvatarSoundSentBytes += numAvailableBytes; + if (_numAvatarSoundSentBytes == soundByteArray.size()) { + // we're done with this sound object - so set our pointer back to NULL + // and our sent bytes back to zero + _avatarSound = NULL; + _numAvatarSoundSentBytes = 0; + } + } + + auto audioPacket = NLPacket::create(silentFrame + ? PacketType::SilentAudioFrame + : PacketType::MicrophoneAudioNoEcho); + + // seek past the sequence number, will be packed when destination node is known + audioPacket->seek(sizeof(quint16)); + + if (silentFrame) { + if (!_isListeningToAudioStream) { + // if we have a silent frame and we're not listening then just send nothing and break out of here + return; + } + + // write the number of silent samples so the audio-mixer can uphold timing + audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES); + + // use the orientation and position of this avatar for the source of this audio + audioPacket->writePrimitive(_avatarData->getPosition()); + glm::quat headOrientation = _avatarData->getHeadOrientation(); + audioPacket->writePrimitive(headOrientation); + + }else if (nextSoundOutput) { + // assume scripted avatar audio is mono and set channel flag to zero + audioPacket->writePrimitive((quint8)0); + + // use the orientation and position of this avatar for the source of this audio + audioPacket->writePrimitive(_avatarData->getPosition()); + glm::quat headOrientation = _avatarData->getHeadOrientation(); + audioPacket->writePrimitive(headOrientation); + + // write the raw audio data + audioPacket->write(reinterpret_cast(nextSoundOutput), numAvailableSamples * sizeof(int16_t)); + } + + // write audio packet to AudioMixer nodes + auto nodeList = DependencyManager::get(); + nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){ + // only send to nodes of type AudioMixer + if (node->getType() == NodeType::AudioMixer) { + // pack sequence number + quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++; + audioPacket->seek(0); + audioPacket->writePrimitive(sequence); + + // send audio packet + nodeList->sendUnreliablePacket(*audioPacket, *node); + } + }); + } + } } void Agent::aboutToFinish() { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index abd99e8c7e..8d16079325 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -37,28 +37,28 @@ class Agent : public ThreadedAssignment { public: Agent(NLPacket& packet); - void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); } - bool isAvatar() const { return _scriptEngine.isAvatar(); } - - bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); } - - bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); } - void setIsListeningToAudioStream(bool isListeningToAudioStream) - { _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); } - + void setIsAvatar(bool isAvatar); + bool isAvatar() const { return _isAvatar; } + + bool isPlayingAvatarSound() const { return _avatarSound != NULL; } + + bool isListeningToAudioStream() const { return _isListeningToAudioStream; } + void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } + float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; } virtual void aboutToFinish(); public slots: void run(); - void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); } + void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); } private slots: void handleAudioPacket(QSharedPointer packet); void handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode); void handleJurisdictionPacket(QSharedPointer packet, SharedNodePointer senderNode); void sendPingRequests(); + void processAgentAvatarAndAudio(float deltaTime); private: ScriptEngine _scriptEngine; @@ -68,6 +68,23 @@ private: MixedAudioStream _receivedAudioStream; float _lastReceivedAudioLoudness; + + void setAvatarData(AvatarData* avatarData, const QString& objectName); + void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } + + void sendAvatarIdentityPacket(); + void sendAvatarBillboardPacket(); + + // FIXME - Agent only data + AvatarData* _avatarData = nullptr; + bool _isListeningToAudioStream = false; + Sound* _avatarSound = nullptr; + int _numAvatarSoundSentBytes = 0; + bool _isAvatar = false; + QTimer* _avatarIdentityTimer = nullptr; + QTimer* _avatarBillboardTimer = nullptr; + QHash _outgoingScriptAudioSequenceNumbers; + }; #endif // hifi_Agent_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 5c4623f7cf..3b01915e6d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -89,16 +89,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _isFinished(false), _isRunning(false), _isInitialized(false), - _isAvatar(false), - _avatarIdentityTimer(NULL), - _avatarBillboardTimer(NULL), - _timerFunctionMap(), - _isListeningToAudioStream(false), - _avatarSound(NULL), - _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _wantSignals(wantSignals), - _avatarData(NULL), _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), @@ -229,40 +221,6 @@ QString ScriptEngine::getFilename() const { } -void ScriptEngine::setIsAvatar(bool isAvatar) { - _isAvatar = isAvatar; - - if (_isAvatar && !_avatarIdentityTimer) { - // set up the avatar timers - _avatarIdentityTimer = new QTimer(this); - _avatarBillboardTimer = new QTimer(this); - - // connect our slot - connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); - connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); - - // start the timers - _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); - _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); - } - - if (!_isAvatar) { - delete _avatarIdentityTimer; - _avatarIdentityTimer = NULL; - delete _avatarBillboardTimer; - _avatarBillboardTimer = NULL; - } -} - -void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { - _avatarData = avatarData; - - // remove the old Avatar property, if it exists - globalObject().setProperty(objectName, QScriptValue()); - - // give the script engine the new Avatar script property - registerGlobalObject(objectName, _avatarData); -} bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { @@ -571,18 +529,6 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN return result; } -void ScriptEngine::sendAvatarIdentityPacket() { - if (_isAvatar && _avatarData) { - _avatarData->sendIdentityPacket(); - } -} - -void ScriptEngine::sendAvatarBillboardPacket() { - if (_isAvatar && _avatarData) { - _avatarData->sendBillboardPacket(); - } -} - void ScriptEngine::run() { // TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if // we're in the process of stopping? @@ -634,107 +580,6 @@ void ScriptEngine::run() { } } - if (!_isFinished && _isAvatar && _avatarData) { - - const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) - / (1000 * 1000)) + 0.5); - const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); - - QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); - _avatarData->doneEncoding(true); - auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size()); - - avatarPacket->write(avatarByteArray); - - nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); - - if (_isListeningToAudioStream || _avatarSound) { - // if we have an avatar audio stream then send it out to our audio-mixer - bool silentFrame = true; - - int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES; - const int16_t* nextSoundOutput = NULL; - - if (_avatarSound) { - - const QByteArray& soundByteArray = _avatarSound->getByteArray(); - nextSoundOutput = reinterpret_cast(soundByteArray.data() - + _numAvatarSoundSentBytes); - - int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES - ? SCRIPT_AUDIO_BUFFER_BYTES - : soundByteArray.size() - _numAvatarSoundSentBytes; - numAvailableSamples = numAvailableBytes / sizeof(int16_t); - - - // check if the all of the _numAvatarAudioBufferSamples to be sent are silence - for (int i = 0; i < numAvailableSamples; ++i) { - if (nextSoundOutput[i] != 0) { - silentFrame = false; - break; - } - } - - _numAvatarSoundSentBytes += numAvailableBytes; - if (_numAvatarSoundSentBytes == soundByteArray.size()) { - // we're done with this sound object - so set our pointer back to NULL - // and our sent bytes back to zero - _avatarSound = NULL; - _numAvatarSoundSentBytes = 0; - } - } - - auto audioPacket = NLPacket::create(silentFrame - ? PacketType::SilentAudioFrame - : PacketType::MicrophoneAudioNoEcho); - - // seek past the sequence number, will be packed when destination node is known - audioPacket->seek(sizeof(quint16)); - - if (silentFrame) { - if (!_isListeningToAudioStream) { - // if we have a silent frame and we're not listening then just send nothing and break out of here - break; - } - - // write the number of silent samples so the audio-mixer can uphold timing - audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES); - - // use the orientation and position of this avatar for the source of this audio - audioPacket->writePrimitive(_avatarData->getPosition()); - glm::quat headOrientation = _avatarData->getHeadOrientation(); - audioPacket->writePrimitive(headOrientation); - - } else if (nextSoundOutput) { - // assume scripted avatar audio is mono and set channel flag to zero - audioPacket->writePrimitive((quint8) 0); - - // use the orientation and position of this avatar for the source of this audio - audioPacket->writePrimitive(_avatarData->getPosition()); - glm::quat headOrientation = _avatarData->getHeadOrientation(); - audioPacket->writePrimitive(headOrientation); - - // write the raw audio data - audioPacket->write(reinterpret_cast(nextSoundOutput), numAvailableSamples * sizeof(int16_t)); - } - - // write audio packet to AudioMixer nodes - auto nodeList = DependencyManager::get(); - nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){ - // only send to nodes of type AudioMixer - if (node->getType() == NodeType::AudioMixer) { - // pack sequence number - quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++; - audioPacket->seek(0); - audioPacket->writePrimitive(sequence); - - // send audio packet - nodeList->sendUnreliablePacket(*audioPacket, *node); - } - }); - } - } - qint64 now = usecTimestampNow(); float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; @@ -761,9 +606,6 @@ void ScriptEngine::run() { emit scriptEnding(); } - // kill the avatar identity timer - delete _avatarIdentityTimer; - if (entityScriptingInterface->getEntityPacketSender()->serversExist()) { // release the queue of edit entity messages. entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index ed8d6ad727..f783c1259b 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -142,17 +142,10 @@ protected: bool _isRunning; int _evaluatesPending = 0; bool _isInitialized; - bool _isAvatar; - QTimer* _avatarIdentityTimer; - QTimer* _avatarBillboardTimer; QHash _timerFunctionMap; - bool _isListeningToAudioStream; - Sound* _avatarSound; - int _numAvatarSoundSentBytes; - bool _isAgent = false; QSet _includedURLs; bool _wantSignals = true; - + private: QString getFilename() const; void waitTillDoneRunning(); @@ -164,7 +157,6 @@ private: void stopTimer(QTimer* timer); AbstractControllerScriptingInterface* _controllerScriptingInterface; - AvatarData* _avatarData; QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; @@ -174,7 +166,6 @@ private: ArrayBufferClass* _arrayBufferClass; - QHash _outgoingScriptAudioSequenceNumbers; QHash _registeredHandlers; void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator); @@ -204,18 +195,6 @@ private: void init(); void run(); - // FIXME - all of these needto be removed and the code that depends on it in Agent.cpp should be moved into Agent.cpp - void setIsAgent(bool isAgent) { _isAgent = isAgent; } - void setIsAvatar(bool isAvatar); - bool isAvatar() const { return _isAvatar; } - void setAvatarData(AvatarData* avatarData, const QString& objectName); - bool isListeningToAudioStream() const { return _isListeningToAudioStream; } - void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } - void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } - bool isPlayingAvatarSound() const { return _avatarSound != NULL; } - - void sendAvatarIdentityPacket(); - void sendAvatarBillboardPacket(); }; #endif // hifi_ScriptEngine_h From 56118e420491e46aaff05d27b66c347090324a72 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 11 Sep 2015 22:12:42 -0700 Subject: [PATCH 04/35] migrate Agent to use standard APIs of ScriptEngine --- assignment-client/src/Agent.cpp | 38 ++++++++++---------- assignment-client/src/Agent.h | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 10 ------ libraries/script-engine/src/ScriptEngine.h | 18 +++------- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 3cbc9cbdd7..ddbe164884 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -42,7 +42,7 @@ Agent::Agent(NLPacket& packet) : DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false)) { // be the parent of the script engine so it gets moved when we do - _scriptEngine.setParent(this); + _scriptEngine->setParent(this); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -156,8 +156,10 @@ void Agent::run() { qDebug() << "Downloaded script:" << scriptContents; + _scriptEngine = new ScriptEngine(scriptContents, _payload); + // setup an Avatar for the script to use - ScriptableAvatar scriptedAvatar(&_scriptEngine); + ScriptableAvatar scriptedAvatar(_scriptEngine); scriptedAvatar.setForceFaceTrackerConnected(true); // call model URL setters with empty URLs so our avatar, if user, will have the default models @@ -168,7 +170,7 @@ void Agent::run() { setAvatarData(&scriptedAvatar, "Avatar"); auto avatarHashMap = DependencyManager::set(); - _scriptEngine.registerGlobalObject("AvatarList", avatarHashMap.data()); + _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); @@ -177,38 +179,36 @@ void Agent::run() { packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket"); // register ourselves to the script engine - _scriptEngine.registerGlobalObject("Agent", this); - - if (!_payload.isEmpty()) { - _scriptEngine.setParentURL(_payload); - } + _scriptEngine->registerGlobalObject("Agent", this); // FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why // viewers would need this called. - _scriptEngine.init(); // must be done before we set up the viewers + //_scriptEngine->init(); // must be done before we set up the viewers - _scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get().data()); + _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor); - _scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); + _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); auto entityScriptingInterface = DependencyManager::get(); - _scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer); + _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); _entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener()); _entityViewer.init(); entityScriptingInterface->setEntityTree(_entityViewer.getTree()); // wire up our additional agent related processing to the update signal - QObject::connect(&_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio); + QObject::connect(_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio); - _scriptEngine.setScriptContents(scriptContents); - _scriptEngine.run(); + _scriptEngine->run(); setFinished(true); // kill the avatar identity timer delete _avatarIdentityTimer; + // delete the script engine + delete _scriptEngine; + } void Agent::setIsAvatar(bool isAvatar) { @@ -238,7 +238,7 @@ void Agent::setIsAvatar(bool isAvatar) { void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; - _scriptEngine.registerGlobalObject(objectName, avatarData); + _scriptEngine->registerGlobalObject(objectName, avatarData); } void Agent::sendAvatarIdentityPacket() { @@ -256,7 +256,7 @@ void Agent::sendAvatarBillboardPacket() { void Agent::processAgentAvatarAndAudio(float deltaTime) { qDebug() << "processAgentAvatarAndAudio()"; - if (!_scriptEngine.isFinished() && _isAvatar && _avatarData) { + if (!_scriptEngine->isFinished() && _isAvatar && _avatarData) { const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) / (1000 * 1000)) + 0.5); @@ -361,7 +361,7 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) { } void Agent::aboutToFinish() { - _scriptEngine.stop(); + _scriptEngine->stop(); _pingTimer->stop(); delete _pingTimer; diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 8d16079325..52e1dfec6c 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -61,7 +61,7 @@ private slots: void processAgentAvatarAndAudio(float deltaTime); private: - ScriptEngine _scriptEngine; + ScriptEngine* _scriptEngine; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; QTimer* _pingTimer; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 3b01915e6d..c69f017f90 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -221,16 +221,6 @@ QString ScriptEngine::getFilename() const { } - -bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { - if (_isRunning) { - return false; - } - _scriptContents = scriptContents; - _fileNameString = fileNameString; - return true; -} - // FIXME - remove the file/url scheme code here and let the script cache handle things void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { if (_isRunning) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index f783c1259b..19c96822c2 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -50,11 +50,14 @@ public: ~ScriptEngine(); - /// Launch the script running in a dedicated thread. This will have the side effect of evalulating + /// run the script in a dedicated thread. This will have the side effect of evalulating /// the current script contents and calling run(). Callers will likely want to register the script with external /// services before calling this. void runInThread(); + /// run the script in the callers thread, exit when stop() is called. + void run(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread @@ -147,6 +150,7 @@ protected: bool _wantSignals = true; private: + void init(); QString getFilename() const; void waitTillDoneRunning(); bool evaluatePending() const { return _evaluatesPending > 0; } @@ -183,18 +187,6 @@ private: // also used in Agent, but that would be fixed if Agent was using proper apis for loading content void setParentURL(const QString& parentURL) { _parentURL = parentURL; } -private: - //FIXME- Agent shouldn't be using these methods directly -- these methods need to be depricated from the public interfaces - friend class Agent; - - /// FIXME - DEPRICATED - remove callers and fix to use standard API - /// sets the script contents, will return false if failed, will fail if script is already running - bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); - - /// FIXME - these should only be used internally - void init(); - void run(); - }; #endif // hifi_ScriptEngine_h From f4aad0562104b3cc3df836f5506727bdb56da597 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 14 Sep 2015 14:07:16 -0700 Subject: [PATCH 05/35] reduce radius of hand-intersects-object test. fix lifetime of laser line --- examples/controllers/handControllerGrab.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 7d4380d764..f50e4f63db 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -45,7 +45,7 @@ var INTERSECT_COLOR = { blue: 10 }; -var GRAB_RADIUS = 1.5; +var GRAB_RADIUS = 0.5; var GRAB_COLOR = { red: 250, @@ -64,6 +64,7 @@ var RIGHT = 1; var LEFT = 0; var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right"); var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left"); +var startTime = Date.now(); //Need to wait before calling these methods for some reason... @@ -78,7 +79,6 @@ function controller(side, triggerAction, pullAction, hand) { this.getHandPosition = MyAvatar.getRightPalmPosition; this.getHandRotation = MyAvatar.getRightPalmRotation; } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; this.getHandRotation = MyAvatar.getLeftPalmRotation; } @@ -92,6 +92,7 @@ function controller(side, triggerAction, pullAction, hand) { this.prevTriggerValue = 0; this.palm = 2 * side; this.tip = 2 * side + 1; + this.lineCreationTime = Date.now(); this.pointer = Entities.addEntity({ type: "Line", name: "pointer", @@ -114,10 +115,8 @@ controller.prototype.updateLine = function() { Entities.editEntity(this.pointer, { position: handPosition, - linePoints: [ - ZERO_VEC, - Vec3.multiply(direction, LINE_LENGTH) - ] + linePoints: [ ZERO_VEC, Vec3.multiply(direction, LINE_LENGTH) ], + lifetime: (Date.now() - startTime) / 1000.0 + 60.0 // set lifetime such that line has another minute. }); //only check if we havent already grabbed an object @@ -378,4 +377,4 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update) -Controller.actionEvent.connect(onActionEvent); \ No newline at end of file +Controller.actionEvent.connect(onActionEvent); From d874c9b3823de6e0b60a4442ea424f8f82e10695 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 14 Sep 2015 14:20:49 -0700 Subject: [PATCH 06/35] snip out tractor beam --- examples/controllers/handControllerGrab.js | 52 ++++++++-------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index f50e4f63db..053227e889 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -58,8 +58,6 @@ var DISTANCE_HOLD_THRESHOLD = 0.8; var right4Action = 18; var left4Action = 17; -var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5; - var RIGHT = 1; var LEFT = 0; var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right"); @@ -85,7 +83,6 @@ function controller(side, triggerAction, pullAction, hand) { this.triggerAction = triggerAction; this.pullAction = pullAction; this.actionID = null; - this.tractorBeamActive = false; this.distanceHolding = false; this.closeGrabbing = false; this.triggerValue = 0; @@ -116,7 +113,7 @@ controller.prototype.updateLine = function() { Entities.editEntity(this.pointer, { position: handPosition, linePoints: [ ZERO_VEC, Vec3.multiply(direction, LINE_LENGTH) ], - lifetime: (Date.now() - startTime) / 1000.0 + 60.0 // set lifetime such that line has another minute. + lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME }); //only check if we havent already grabbed an object @@ -143,7 +140,7 @@ controller.prototype.checkPointer = function() { Script.setTimeout(function() { var props = Entities.getEntityProperties(self.pointer); Entities.editEntity(self.pointer, { - lifetime: props.age + EXTRA_TIME + LIFETIME + lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME }); self.checkPointer(); }, POINTER_CHECK_TIME); @@ -172,9 +169,6 @@ controller.prototype.checkForIntersections = function(origin, direction) { } controller.prototype.attemptMove = function() { - if (this.tractorBeamActive) { - return; - } if (this.grabbedEntity || this.distanceHolding) { var handPosition = Controller.getSpatialControlPosition(this.palm); var direction = Controller.getSpatialControlNormal(this.tip); @@ -217,19 +211,11 @@ controller.prototype.letGo = function() { this.grabbedEntity = null; this.actionID = null; this.distanceHolding = false; - this.tractorBeamActive = false; this.checkForEntityArrival = false; this.closeGrabbing = false; } controller.prototype.update = function() { - if (this.tractorBeamActive && this.checkForEntityArrival) { - var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity - if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) { - this.letGo(); - } - return; - } this.triggerValue = Controller.getActionValue(this.triggerAction); if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) { //First check if an object is within close range and then run the close grabbing logic @@ -331,23 +317,23 @@ controller.prototype.deactivateEntity = function(entity) { } controller.prototype.onActionEvent = function(action, state) { - if (this.pullAction === action && state === 1) { - if (this.actionID !== null) { - var self = this; - this.tractorBeamActive = true; - //We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some - //low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving! - Script.setTimeout(function() { - self.checkForEntityArrival = true; - }, 500); - var handPosition = Controller.getSpatialControlPosition(this.palm); - var direction = Vec3.normalize(Controller.getSpatialControlNormal(this.tip)); - //move final destination along line a bit, so it doesnt hit avatar hand - Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: Vec3.sum(handPosition, Vec3.multiply(3, direction)) - }); - } - } + // if (this.pullAction === action && state === 1) { + // if (this.actionID !== null) { + // var self = this; + // this.tractorBeamActive = true; + // //We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some + // //low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving! + // Script.setTimeout(function() { + // self.checkForEntityArrival = true; + // }, 500); + // var handPosition = Controller.getSpatialControlPosition(this.palm); + // var direction = Vec3.normalize(Controller.getSpatialControlNormal(this.tip)); + // //move final destination along line a bit, so it doesnt hit avatar hand + // Entities.updateAction(this.grabbedEntity, this.actionID, { + // targetPosition: Vec3.sum(handPosition, Vec3.multiply(3, direction)) + // }); + // } + // } } From 18fbf896f11af63eee0174ec4d5a3157ef320077 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 14 Sep 2015 15:13:43 -0700 Subject: [PATCH 07/35] first cut at moving entity scripts into ScriptEngine --- .../src/EntityTreeRenderer.cpp | 329 ++---------------- .../src/EntityTreeRenderer.h | 14 +- libraries/script-engine/src/ScriptCache.cpp | 47 +++ libraries/script-engine/src/ScriptCache.h | 14 +- libraries/script-engine/src/ScriptEngine.cpp | 179 ++++++++++ libraries/script-engine/src/ScriptEngine.h | 16 + 6 files changed, 283 insertions(+), 316 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 61ff8bbea8..22d857c763 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -90,10 +90,12 @@ EntityTreeRenderer::~EntityTreeRenderer() { void EntityTreeRenderer::clear() { leaveAllEntities(); + /* foreach (const EntityItemID& entityID, _entityScripts.keys()) { checkAndCallUnload(entityID); } _entityScripts.clear(); + */ auto scene = _viewState->getMain3DScene(); render::PendingChanges pendingChanges; @@ -136,175 +138,8 @@ void EntityTreeRenderer::shutdown() { _shuttingDown = true; } -void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { - if (_waitingOnPreload.contains(url)) { - QList entityIDs = _waitingOnPreload.values(url); - _waitingOnPreload.remove(url); - foreach(EntityItemID entityID, entityIDs) { - checkAndCallPreload(entityID); - } - } -} - -void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) { - if (_waitingOnPreload.contains(url)) { - _waitingOnPreload.remove(url); - } -} - -QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload, bool reload) { - EntityItemPointer entity = static_cast(_tree)->findEntityByEntityItemID(entityItemID); - return loadEntityScript(entity, isPreload, reload); -} -QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut, - bool& reload) { - isPending = false; - QUrl url(scriptMaybeURLorText); - - // If the url is not valid, this must be script text... - // We document "direct injection" scripts as starting with "(function...", and that would never be a valid url. - // But QUrl thinks it is. - if (!url.isValid() || scriptMaybeURLorText.startsWith("(")) { - isURL = false; - return scriptMaybeURLorText; - } - isURL = true; - urlOut = url; - - QString scriptContents; // assume empty - - // if the scheme length is one or lower, maybe they typed in a file, let's try - const int WINDOWS_DRIVE_LETTER_SIZE = 1; - if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { - url = QUrl::fromLocalFile(scriptMaybeURLorText); - } - - // ok, let's see if it's valid... and if so, load it - if (url.isValid()) { - if (url.scheme() == "file") { - QString fileName = url.toLocalFile(); - QFile scriptFile(fileName); - if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { - qCDebug(entitiesrenderer) << "Loading file:" << fileName; - QTextStream in(&scriptFile); - scriptContents = in.readAll(); - } else { - qCDebug(entitiesrenderer) << "ERROR Loading file:" << fileName; - } - } else { - auto scriptCache = DependencyManager::get(); - - if (!scriptCache->isInBadScriptList(url)) { - scriptContents = scriptCache->getScript(url, this, isPending, reload); - } - } - } - - return scriptContents; -} - - -QScriptValue EntityTreeRenderer::loadEntityScript(EntityItemPointer entity, bool isPreload, bool reload) { - if (_shuttingDown) { - return QScriptValue(); // since we're shutting down, we don't load any more scripts - } - - if (!entity) { - return QScriptValue(); // no entity... - } - - // NOTE: we keep local variables for the entityID and the script because - // below in loadScriptContents() it's possible for us to execute the - // application event loop, which may cause our entity to be deleted on - // us. We don't really need access the entity after this point, can - // can accomplish all we need to here with just the script "text" and the ID. - EntityItemID entityID = entity->getEntityItemID(); - QString entityScript = entity->getScript(); - - if (_entityScripts.contains(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; - - // check to make sure our script text hasn't changed on us since we last loaded it and we're not redownloading it - if (details.scriptText == entityScript && !reload) { - return details.scriptObject; // previously loaded - } - - // if we got here, then we previously loaded a script, but the entity's script value - // has changed and so we need to reload it. - _entityScripts.remove(entityID); - } - if (entityScript.isEmpty()) { - return QScriptValue(); // no script - } - - bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text. - bool isPending = false; - QUrl url; - QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url, reload); - - if (isPending && isPreload && isURL) { - _waitingOnPreload.insert(url, entityID); - } - - auto scriptCache = DependencyManager::get(); - - if (isURL && scriptCache->isInBadScriptList(url)) { - return QScriptValue(); // no script contents... - } - - if (scriptContents.isEmpty()) { - return QScriptValue(); // no script contents... - } - - QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; - qCDebug(entitiesrenderer) << " " << syntaxCheck.errorMessage() << ":" - << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); - qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript; - - scriptCache->addScriptToBadScriptList(url); - - return QScriptValue(); // invalid script - } - - if (isURL) { - _entitiesScriptEngine->setParentURL(entity->getScript()); - } - QScriptValue entityScriptConstructor = _sandboxScriptEngine->evaluate(scriptContents); - - if (!entityScriptConstructor.isFunction()) { - qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; - qCDebug(entitiesrenderer) << " NOT CONSTRUCTOR"; - qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript; - - scriptCache->addScriptToBadScriptList(url); - - return QScriptValue(); // invalid script - } else { - entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); - } - - QScriptValue entityScriptObject = entityScriptConstructor.construct(); - EntityScriptDetails newDetails = { entityScript, entityScriptObject }; - _entityScripts[entityID] = newDetails; - - if (isURL) { - _entitiesScriptEngine->setParentURL(""); - } - - return entityScriptObject; // newly constructed -} - -QScriptValue EntityTreeRenderer::getPreviouslyLoadedEntityScript(const EntityItemID& entityID) { - if (_entityScripts.contains(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; - return details.scriptObject; // previously loaded - } - return QScriptValue(); // no script -} void EntityTreeRenderer::setTree(Octree* newTree) { OctreeRenderer::setTree(newTree); @@ -324,11 +159,7 @@ void EntityTreeRenderer::update() { // and we want to simulate this message here as well as in mouse move if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent); - QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent); - QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); - if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { - currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent); } } @@ -356,19 +187,14 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { _tree->unlock(); // Note: at this point we don't need to worry about the tree being locked, because we only deal with - // EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts + // EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts // for entity IDs that no longer exist. // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { if (!entitiesContainingAvatar.contains(entityID)) { emit leaveEntity(entityID); - QScriptValueList entityArgs = createEntityArgs(entityID); - QScriptValue entityScript = loadEntityScript(entityID); - if (entityScript.property("leaveEntity").isValid()) { - entityScript.property("leaveEntity").call(entityScript, entityArgs); - } - + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } @@ -376,11 +202,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, entitiesContainingAvatar) { if (!_currentEntitiesInside.contains(entityID)) { emit enterEntity(entityID); - QScriptValueList entityArgs = createEntityArgs(entityID); - QScriptValue entityScript = loadEntityScript(entityID); - if (entityScript.property("enterEntity").isValid()) { - entityScript.property("enterEntity").call(entityScript, entityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); } } _currentEntitiesInside = entitiesContainingAvatar; @@ -395,11 +217,7 @@ void EntityTreeRenderer::leaveAllEntities() { // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { emit leaveEntity(entityID); - QScriptValueList entityArgs = createEntityArgs(entityID); - QScriptValue entityScript = loadEntityScript(entityID); - if (entityScript.property("leaveEntity").isValid()) { - entityScript.property("leaveEntity").call(entityScript, entityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } _currentEntitiesInside.clear(); @@ -821,27 +639,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection); } -QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { - QScriptValueList args; - args << entityID.toScriptValue(_entitiesScriptEngine); - args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine); - return args; -} - -QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent) { - QScriptValueList args; - args << entityID.toScriptValue(_entitiesScriptEngine); - args << mouseEvent.toScriptValue(_entitiesScriptEngine); - return args; -} - - -QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) { - QScriptValueList args; - args << entityID.toScriptValue(_entitiesScriptEngine); - return args; -} - void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. @@ -864,18 +661,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device } emit mousePressOnEntity(rayPickResult, event, deviceID); - - QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); - QScriptValue entityScript = loadEntityScript(rayPickResult.entity); - if (entityScript.property("mousePressOnEntity").isValid()) { - entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event, deviceID)); _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); - if (entityScript.property("clickDownOnEntity").isValid()) { - entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event, deviceID)); } else { emit mousePressOffEntity(rayPickResult, event, deviceID); } @@ -896,24 +686,14 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult, event, deviceID); - - QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); - QScriptValue entityScript = loadEntityScript(rayPickResult.entity); - if (entityScript.property("mouseReleaseOnEntity").isValid()) { - entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event, deviceID)); } // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickOn event if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); - - QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); - QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); - if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) { - currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event, deviceID)); } // makes it the unknown ID, we just released so we can't be clicking on anything @@ -935,17 +715,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI bool precisionPicking = false; // for mouse moves we do not do precision picking RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { - //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; - QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); - // load the entity script if needed... - QScriptValue entityScript = loadEntityScript(rayPickResult.entity); - if (entityScript.property("mouseMoveEvent").isValid()) { - entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs); - } - emit mouseMoveOnEntity(rayPickResult, event, deviceID); - if (entityScript.property("mouseMoveOnEntity").isValid()) { - entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs); - } + + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event, deviceID)); + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event, deviceID)); // handle the hover logic... @@ -953,30 +725,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); - - QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); - - QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); - if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { - currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID)); } // If the new hover entity does not match the previous hover entity then we are entering the new one // this is true if the _currentHoverOverEntityID is known or unknown if (rayPickResult.entityID != _currentHoverOverEntityID) { - emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); - if (entityScript.property("hoverEnterEntity").isValid()) { - entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event, deviceID)); } // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); - if (entityScript.property("hoverOverEntity").isValid()) { - entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event, deviceID)); // remember what we're hovering over _currentHoverOverEntityID = rayPickResult.entityID; @@ -987,14 +748,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); - - QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); - - QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); - if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { - currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); - } - + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID)); _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID } } @@ -1003,13 +757,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI // not yet released the hold then this is still considered a holdingClickOnEntity event if (!_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); - - QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); - - QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); - if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { - currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); - } + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event, deviceID)); } _lastMouseEvent = MouseEvent(*event, deviceID); _lastMouseEventValid = true; @@ -1017,9 +765,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { if (_tree && !_shuttingDown) { - checkAndCallUnload(entityID); + //checkAndCallUnload(entityID); } - _entityScripts.remove(entityID); + //_entityScripts.remove(entityID); // here's where we remove the entity payload from the scene if (_entitiesInScene.contains(entityID)) { @@ -1032,7 +780,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { } void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { - checkAndCallPreload(entityID); + //checkAndCallPreload(entityID); auto entity = static_cast(_tree)->findEntityByID(entityID); if (entity) { addEntityToScene(entity); @@ -1059,22 +807,14 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { - // load the entity script if needed... - QScriptValue entityScript = loadEntityScript(entityID, true, reload); // is preload! - if (entityScript.property("preload").isValid()) { - QScriptValueList entityArgs = createEntityArgs(entityID); - entityScript.property("preload").call(entityScript, entityArgs); - } + EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); + _entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload); } } void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) { if (_tree && !_shuttingDown) { - QScriptValue entityScript = getPreviouslyLoadedEntityScript(entityID); - if (entityScript.property("unload").isValid()) { - QScriptValueList entityArgs = createEntityArgs(entityID); - entityScript.property("unload").call(entityScript, entityArgs); - } + _entitiesScriptEngine->unloadEntityScript(entityID); } } @@ -1152,24 +892,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons // And now the entity scripts emit collisionWithEntity(idA, idB, collision); - QScriptValue entityScriptA = loadEntityScript(idA); - if (entityScriptA.property("collisionWithEntity").isValid()) { - QScriptValueList args; - args << idA.toScriptValue(_entitiesScriptEngine); - args << idB.toScriptValue(_entitiesScriptEngine); - args << collisionToScriptValue(_entitiesScriptEngine, collision); - entityScriptA.property("collisionWithEntity").call(entityScriptA, args); - } - + _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); emit collisionWithEntity(idB, idA, collision); - QScriptValue entityScriptB = loadEntityScript(idB); - if (entityScriptB.property("collisionWithEntity").isValid()) { - QScriptValueList args; - args << idB.toScriptValue(_entitiesScriptEngine); - args << idA.toScriptValue(_entitiesScriptEngine); - args << collisionToScriptValue(_entitiesScriptEngine, collision); - entityScriptB.property("collisionWithEntity").call(entityScriptA, args); - } + _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); } void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 1bfbdaf810..b9a3eb5cdd 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -28,14 +28,9 @@ class Model; class ScriptEngine; class ZoneEntityItem; -class EntityScriptDetails { -public: - QString scriptText; - QScriptValue scriptObject; -}; // Generic client side Octree renderer class. -class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser { +class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { Q_OBJECT public: EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -87,9 +82,6 @@ public: /// hovering over, and entering entities void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface); - virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); - virtual void errorInLoadingScript(const QUrl& url); - // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } @@ -157,10 +149,8 @@ private: QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false, bool reload = false); QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID); QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url, bool& reload); - QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); - QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent); - QHash _entityScripts; + //QHash _entityScripts; void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 38837ec4b9..1b4997c5cd 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -81,4 +81,51 @@ void ScriptCache::scriptDownloaded() { req->deleteLater(); } +void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) { + QUrl unnormalizedURL(scriptOrURL); + QUrl url = ResourceManager::normalizeURL(unnormalizedURL); + + // attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the entityScript use case) + if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) { + contentAvailable(scriptOrURL, scriptOrURL, false, true); + return; + } + + if (_scriptCache.contains(url) && !forceDownload) { + qCDebug(scriptengine) << "Found script in cache:" << url.toString(); + contentAvailable(url.toString(), _scriptCache[url], true, true); + } else { + bool alreadyWaiting = _contentCallbacks.contains(url); + _contentCallbacks.insert(url, contentAvailable); + + if (alreadyWaiting) { + qCDebug(scriptengine) << "Already downloading script at:" << url.toString(); + } else { + auto request = ResourceManager::createResourceRequest(this, url); + request->setCacheEnabled(!forceDownload); + connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable); + request->send(); + } + } +} + +void ScriptCache::scriptContentAvailable() { + ResourceRequest* req = qobject_cast(sender()); + QUrl url = req->getUrl(); + QList allCallbacks = _contentCallbacks.values(url); + _contentCallbacks.remove(url); + + bool success = req->getResult() == ResourceRequest::Success; + + if (success) { + _scriptCache[url] = req->getData(); + qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); + } else { + qCWarning(scriptengine) << "Error loading script from URL " << url; + } + foreach(contentAvailableCallback thisCallback, allCallbacks) { + thisCallback(url.toString(), _scriptCache[url], true, success); + } + req->deleteLater(); +} diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h index 89db5596ba..7de14a09f7 100644 --- a/libraries/script-engine/src/ScriptCache.h +++ b/libraries/script-engine/src/ScriptCache.h @@ -20,23 +20,33 @@ public: virtual void errorInLoadingScript(const QUrl& url) = 0; }; +using contentAvailableCallback = std::function; + /// Interface for loading scripts class ScriptCache : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: + void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false); + + QString getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool redownload = false); void deleteScript(const QUrl& unnormalizedURL); + + // FIXME - how do we remove a script from the bad script list in the case of a redownload? void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); } bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); } private slots: - void scriptDownloaded(); - + void scriptDownloaded(); // old version + void scriptContentAvailable(); // new version + private: ScriptCache(QObject* parent = NULL); + QMultiMap _contentCallbacks; + QHash _scriptCache; QMultiMap _scriptUsers; QSet _badScripts; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c69f017f90..f277a40470 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -848,3 +849,181 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e } } } + +// since all of these operations can be asynch we will always do the actual work in the response handler +// for the download +void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << + "entityScript:" << entityScript << + "forceRedownload:" << forceRedownload; + + QMetaObject::invokeMethod(this, "loadEntityScript", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, entityScript), + Q_ARG(bool, forceRedownload)); + return; + } + qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] " + "entityID:" << entityID << + "entityScript:" << entityScript << + "forceRedownload:" << forceRedownload; + + // If we've been called our known entityScripts should not know about us.. + assert(!_entityScripts.contains(entityID)); + + auto scriptCache = DependencyManager::get(); + + scriptCache->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { + + // first check the syntax of the script contents + QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; + qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" + << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); + qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; + scriptCache->addScriptToBadScriptList(scriptOrURL); + return; // done processing script + } + + if (isURL) { + setParentURL(scriptOrURL); + } + + QScriptEngine sandbox; + QScriptValue testConstructor = sandbox.evaluate(contents); + + if (!testConstructor.isFunction()) { + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; + qCDebug(scriptengine) << " NOT CONSTRUCTOR"; + qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; + scriptCache->addScriptToBadScriptList(scriptOrURL); + return; // done processing script + } + + QScriptValue entityScriptConstructor = evaluate(contents); + + QScriptValue entityScriptObject = entityScriptConstructor.construct(); + EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject }; + _entityScripts[entityID] = newDetails; + + if (isURL) { + setParentURL(""); + } + + // if we got this far, then call the preload method + callEntityScriptMethod(entityID, "preload"); + + }, forceRedownload); +} + +void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID; + + QMetaObject::invokeMethod(this, "unloadEntityScript", + Q_ARG(const EntityItemID&, entityID)); + return; + } + qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] " + "entityID:" << entityID; + + if (_entityScripts.contains(entityID)) { + callEntityScriptMethod(entityID, "unload"); + _entityScripts.remove(entityID); + } +} + +void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName; + + QMetaObject::invokeMethod(this, "callEntityScriptMethod", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName)); + return; + } + qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName; + + if (_entityScripts.contains(entityID)) { + EntityScriptDetails details = _entityScripts[entityID]; + QScriptValue entityScript = details.scriptObject; // previously loaded + if (entityScript.property(methodName).isFunction()) { + QScriptValueList args; + args << entityID.toScriptValue(this); + entityScript.property(methodName).call(entityScript, args); + } + } +} + +void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName << + "event: mouseEvent"; + + QMetaObject::invokeMethod(this, "callEntityScriptMethod", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const MouseEvent&, event)); + return; + } + qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName << + "event: mouseEvent"; + + if (_entityScripts.contains(entityID)) { + EntityScriptDetails details = _entityScripts[entityID]; + QScriptValue entityScript = details.scriptObject; // previously loaded + if (entityScript.property(methodName).isFunction()) { + QScriptValueList args; + args << entityID.toScriptValue(this); + args << event.toScriptValue(this); + entityScript.property(methodName).call(entityScript, args); + } + } +} + + +void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { + if (QThread::currentThread() != thread()) { + qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName << + "otherID:" << otherID << + "collision: collision"; + + QMetaObject::invokeMethod(this, "callEntityScriptMethod", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const EntityItemID&, otherID), + Q_ARG(const Collision&, collision)); + return; + } + qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << + "methodName:" << methodName << + "otherID:" << otherID << + "collision: collision"; + + if (_entityScripts.contains(entityID)) { + EntityScriptDetails details = _entityScripts[entityID]; + QScriptValue entityScript = details.scriptObject; // previously loaded + if (entityScript.property(methodName).isFunction()) { + QScriptValueList args; + args << entityID.toScriptValue(this); + args << otherID.toScriptValue(this); + args << collisionToScriptValue(this, collision); + entityScript.property(methodName).call(entityScript, args); + } + } +} diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 19c96822c2..ab1833636c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -40,6 +40,12 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1 typedef QHash RegisteredEventHandlers; +class EntityScriptDetails { +public: + QString scriptText; + QScriptValue scriptObject; +}; + class ScriptEngine : public QScriptEngine, public ScriptUser { Q_OBJECT public: @@ -100,6 +106,13 @@ public: Q_INVOKABLE void print(const QString& message); Q_INVOKABLE QUrl resolvePath(const QString& path) const; + // Entity Script Related methods + Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded + Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName); + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event); + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(); @@ -149,6 +162,9 @@ protected: QSet _includedURLs; bool _wantSignals = true; + QHash _entityScripts; + + private: void init(); QString getFilename() const; From 75ec1428275dee7fa39ac2c347d559b7cc79ef64 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 14 Sep 2015 16:42:57 -0700 Subject: [PATCH 08/35] fix animation-tests --- .../animation/src/SwingTwistConstraint.cpp | 5 +++- .../animation/src/SwingTwistConstraint.h | 3 +++ .../src/AnimInverseKinematicsTests.cpp | 25 +++++++++++-------- .../animation/src/RotationConstraintTests.cpp | 4 +++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index b788be5ce7..f9cea2abad 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -72,7 +72,7 @@ SwingTwistConstraint::SwingTwistConstraint() : _swingLimitFunction(), _minTwist(-PI), _maxTwist(PI), - _lastTwistBoundary(0) + _lastTwistBoundary(LAST_CLAMP_NO_BOUNDARY) { } @@ -249,3 +249,6 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { return false; } +void SwingTwistConstraint::clearHistory() { + _lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY; +} diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index 180127e1e1..155f72a518 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -73,6 +73,9 @@ public: /// \return reference to SwingLimitFunction instance for unit-testing const SwingLimitFunction& getSwingLimitFunction() const { return _swingLimitFunction; } + /// \brief exposed for unit testing + void clearHistory(); + protected: SwingLimitFunction _swingLimitFunction; float _minTwist; diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 3958db1242..bb15a1d257 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -35,7 +35,6 @@ void makeTestFBXJoints(std::vector& fbxJoints) { joint.freeLineage.clear(); joint.parentIndex = -1; joint.distanceToParent = 1.0f; - joint.boneRadius = 1.0f; joint.translation = origin; // T joint.preTransform = glm::mat4(); // Roff * Rp @@ -96,11 +95,11 @@ void makeTestFBXJoints(std::vector& fbxJoints) { void AnimInverseKinematicsTests::testSingleChain() { std::vector fbxJoints; - AnimPose offset; - makeTestFBXJoints(fbxJoints, offset); + makeTestFBXJoints(fbxJoints); // create a skeleton and doll - AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints); + AnimPose offset; + AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints, offset); AnimSkeleton::Pointer skeletonPtr(skeleton); AnimInverseKinematics ikDoll("doll"); ikDoll.setSkeleton(skeletonPtr); @@ -130,10 +129,13 @@ void AnimInverseKinematicsTests::testSingleChain() { // // A------>B------>C------>D // - int indexD = 3; glm::vec3 targetPosition(2.0f, 1.0f, 0.0f); glm::quat targetRotation = glm::angleAxis(PI / 2.0f, zAxis); - ikDoll.updateTarget(indexD, targetPosition, targetRotation); + AnimVariantMap varMap; + varMap.set("positionD", targetPosition); + varMap.set("rotationD", targetRotation); + ikDoll.setTargetVars("D", "positionD", "rotationD"); + AnimNode::Triggers triggers; // the IK solution should be: // @@ -143,7 +145,7 @@ void AnimInverseKinematicsTests::testSingleChain() { // A------>B------>C // float dt = 1.0f; - ikDoll.evaluate(dt); + ikDoll.evaluate(varMap, dt, triggers); // verify absolute results // NOTE: since we expect this solution to converge very quickly (one loop) @@ -204,17 +206,20 @@ void AnimInverseKinematicsTests::testSingleChain() { // | // A------>B t // - int indexD = 3; glm::vec3 targetPosition(3.0f, 0.0f, 0.0f); glm::quat targetRotation = identity; - ikDoll.updateTarget(indexD, targetPosition, targetRotation); + AnimVariantMap varMap; + varMap.set("positionD", targetPosition); + varMap.set("rotationD", targetRotation); + ikDoll.setTargetVars("D", "positionD", "rotationD"); + AnimNode::Triggers triggers; // the IK solution should be: // // A------>B------>C------>D // float dt = 1.0f; - ikDoll.evaluate(dt); + ikDoll.evaluate(varMap, dt, triggers); // verify absolute results // NOTE: the IK algorithm doesn't converge very fast for full-reach targets, diff --git a/tests/animation/src/RotationConstraintTests.cpp b/tests/animation/src/RotationConstraintTests.cpp index 4aac875f2a..7aacf26826 100644 --- a/tests/animation/src/RotationConstraintTests.cpp +++ b/tests/animation/src/RotationConstraintTests.cpp @@ -193,6 +193,7 @@ void RotationConstraintTests::testSwingTwistConstraint() { glm::quat inputRotation = swingRotation * twistRotation * referenceRotation; glm::quat outputRotation = inputRotation; + shoulder.clearHistory(); bool updated = shoulder.apply(outputRotation); QVERIFY(updated == false); QCOMPARE_WITH_ABS_ERROR(inputRotation, outputRotation, EPSILON); @@ -223,6 +224,7 @@ void RotationConstraintTests::testSwingTwistConstraint() { glm::quat inputRotation = swingRotation * twistRotation * referenceRotation; glm::quat outputRotation = inputRotation; + shoulder.clearHistory(); bool updated = shoulder.apply(outputRotation); QVERIFY(updated == true); @@ -257,6 +259,7 @@ void RotationConstraintTests::testSwingTwistConstraint() { glm::quat inputRotation = swingRotation * twistRotation * referenceRotation; glm::quat outputRotation = inputRotation; + shoulder.clearHistory(); bool updated = shoulder.apply(outputRotation); QVERIFY(updated == true); @@ -291,6 +294,7 @@ void RotationConstraintTests::testSwingTwistConstraint() { glm::quat inputRotation = swingRotation * twistRotation * referenceRotation; glm::quat outputRotation = inputRotation; + shoulder.clearHistory(); bool updated = shoulder.apply(outputRotation); QVERIFY(updated == true); From 84ab4fd5856474e630bca9758f13f0fd8bade0b6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 14 Sep 2015 17:13:43 -0700 Subject: [PATCH 09/35] force minimum for p_hrc time_point values --- libraries/networking/src/udt/CongestionControl.h | 3 ++- libraries/networking/src/udt/Connection.cpp | 6 +++--- libraries/networking/src/udt/Connection.h | 6 +++--- libraries/networking/src/udt/PacketTimeWindow.h | 4 ++-- libraries/networking/src/udt/SendQueue.cpp | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index adbf1f0e85..5f9b64c1ad 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -108,7 +108,8 @@ public: private: void stopSlowStart(); // stops the slow start on loss or timeout - p_high_resolution_clock::time_point _lastRCTime; // last rate increase time + p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::time_point().min(); // last rate increase time + bool _slowStart { true }; // if in slow start phase SequenceNumber _slowStartLastAck; // last ACKed seq num bool _loss { false }; // if loss happened since last rate increase diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 21dce2831c..4aa785fa8c 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -222,7 +222,7 @@ void Connection::recordRetransmission() { } void Connection::sendACK(bool wasCausedBySyncTimeout) { - static p_high_resolution_clock::time_point lastACKSendTime; + static p_high_resolution_clock::time_point lastACKSendTime = p_high_resolution_clock::time_point().min(); auto currentTime = p_high_resolution_clock::now(); SequenceNumber nextACKNumber = nextACK(); @@ -534,7 +534,7 @@ void Connection::processACK(std::unique_ptr controlPacket) { // This will be the case if it has been longer than the sync interval OR // it looks like they haven't received our ACK2 for this ACK auto currentTime = p_high_resolution_clock::now(); - static p_high_resolution_clock::time_point lastACK2SendTime; + static p_high_resolution_clock::time_point lastACK2SendTime = p_high_resolution_clock::time_point().min(); microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); @@ -779,7 +779,7 @@ void Connection::resetReceiveState() { // clear the loss list and _lastNAKTime _lossList.clear(); - _lastNAKTime = p_high_resolution_clock::time_point(); + _lastNAKTime = p_high_resolution_clock::time_point().min(); // the _nakInterval need not be reset, that will happen on loss diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 31ef664ce5..346f559165 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -114,13 +114,13 @@ private: int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms - p_high_resolution_clock::time_point _lastNAKTime; + p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::time_point().min(); bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client - p_high_resolution_clock::time_point _connectionStart; // holds the time_point for creation of this connection - p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender + p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::time_point().min(); // holds the time_point for creation of this connection + p_high_resolution_clock::time_point _lastReceiveTime = p_high_resolution_clock::time_point().min(); // holds the last time we received anything from sender bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection LossList _lossList; // List of all missing packets diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h index c2a90d0f6c..6030e6809f 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.h +++ b/libraries/networking/src/udt/PacketTimeWindow.h @@ -42,8 +42,8 @@ private: std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals std::vector _probeIntervals; // vector of microsecond intervals between probe pair arrivals - p_high_resolution_clock::time_point _lastPacketTime; // the time_point when last packet arrived - p_high_resolution_clock::time_point _firstProbeTime; // the time_point when first probe in pair arrived + p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::time_point().min(); // the time_point when last packet arrived + p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::time_point().min(); // the time_point when first probe in pair arrived }; } diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 2890d52c2b..7f1590f995 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -281,7 +281,7 @@ void SendQueue::run() { // if it has been at least 100ms since we last sent a handshake, send another now // hold the time of last send in a static - static auto lastSendHandshake = p_high_resolution_clock::time_point(); + static auto lastSendHandshake = p_high_resolution_clock::time_point().min(); static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100); From a27e0e7cc7ca2dae9b9e08cc5f176ac4304aa767 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 14 Sep 2015 17:18:17 -0700 Subject: [PATCH 10/35] remove an unused alias --- libraries/networking/src/udt/SendQueue.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 401ebd384d..2e7ec90c45 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -45,8 +45,6 @@ class SendQueue : public QObject { Q_OBJECT public: - using time_point = p_high_resolution_clock::time_point; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination); void queuePacket(std::unique_ptr packet); From 90f46ba2c8bb64538270d71cf4fd6a53559b1cf6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 14 Sep 2015 18:54:12 -0700 Subject: [PATCH 11/35] Added hand state machines to AnimGraph. * Application: Forward trigger values to the MyAvatar's PalmData * SkeletonModel: Pass PalmData to Rig via updateRigFromHandData() this is more explicit then the Rig::inverseKinematics methods. * AnimNodeLoader & AnimOverlay: add support for LeftHand and RightHand bone sets * Rig::updateRigFromHandData() read the triggers and set stateMachine trigger vars * avatar.json - udpated with new hand state machine with temporary animations --- interface/src/Application.cpp | 7 +- interface/src/Application.h | 2 +- interface/src/avatar/MyAvatar.cpp | 4 +- interface/src/avatar/SkeletonModel.cpp | 79 ++- .../animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/AnimNodeLoader.cpp | 13 +- libraries/animation/src/AnimOverlay.cpp | 42 +- libraries/animation/src/AnimOverlay.h | 10 +- libraries/animation/src/Rig.cpp | 56 ++ libraries/animation/src/Rig.h | 13 + tests/animation/src/data/avatar.json | 516 ++++++++++++------ 11 files changed, 525 insertions(+), 219 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 164f04f2eb..3a44562e51 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2868,8 +2868,8 @@ void Application::update(float deltaTime) { UserInputMapper::PoseValue leftHand = userInputMapper->getPoseState(UserInputMapper::LEFT_HAND); UserInputMapper::PoseValue rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND); Hand* hand = DependencyManager::get()->getMyAvatar()->getHand(); - setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX); - setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX); + setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK)); + setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::RIGHT_HAND_CLICK)); if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) { emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK), userInputMapper->getActionState(UserInputMapper::SHIFT), LEFT_HAND_INDEX); @@ -4970,7 +4970,7 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } -void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index) { +void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue) { PalmData* palm; bool foundHand = false; for (size_t j = 0; j < hand->getNumPalms(); j++) { @@ -5040,6 +5040,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float palm->setTipVelocity(glm::vec3(0.0f)); } palm->setTipPosition(newTipPosition); + palm->setTrigger(triggerValue); } void Application::emulateMouse(Hand* hand, float click, float shift, int index) { diff --git a/interface/src/Application.h b/interface/src/Application.h index b997fae823..6213dae4fa 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -485,7 +485,7 @@ private: void update(float deltaTime); - void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index); + void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue); void emulateMouse(Hand* hand, float click, float shift, int index); // Various helper functions called during update() diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9496c60d26..91e7ba191f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1298,8 +1298,8 @@ void MyAvatar::initAnimGraph() { // // or run a local web-server // python -m SimpleHTTPServer& - // auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/8f824da2908fd89ad1befadd1d8f5d7b3b6efa66/ik-avatar.json"); + auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + //auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/8f824da2908fd89ad1befadd1d8f5d7b3b6efa66/ik-avatar.json"); _rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 41ca193a7a..91222ad30e 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -96,6 +96,17 @@ void SkeletonModel::initJointStates(QVector states) { emit skeletonLoaded(); } +static const PalmData* getPalmWithIndex(Hand* hand, int index) { + const PalmData* palm = nullptr; + for (size_t j = 0; j < hand->getNumPalms(); j++) { + if (hand->getPalms()[j].getSixenseID() == index) { + palm = &(hand->getPalms()[j]); + break; + } + } + return palm; +} + const float PALM_PRIORITY = DEFAULT_PRIORITY; // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { @@ -108,34 +119,60 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { MyAvatar* myAvatar = static_cast(_owningAvatar); const FBXGeometry& geometry = _geometry->getFBXGeometry(); - Rig::HeadParameters params; - params.modelRotation = getRotation(); - params.modelTranslation = getTranslation(); - params.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); - params.leanSideways = head->getFinalLeanSideways(); - params.leanForward = head->getFinalLeanForward(); - params.torsoTwist = head->getTorsoTwist(); - params.localHeadOrientation = head->getFinalOrientationInLocalFrame(); - params.localHeadPitch = head->getFinalPitch(); - params.localHeadYaw = head->getFinalYaw(); - params.localHeadRoll = head->getFinalRoll(); - params.isInHMD = qApp->getAvatarUpdater()->isHMDMode(); + Rig::HeadParameters headParams; + headParams.modelRotation = getRotation(); + headParams.modelTranslation = getTranslation(); + headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); + headParams.leanSideways = head->getFinalLeanSideways(); + headParams.leanForward = head->getFinalLeanForward(); + headParams.torsoTwist = head->getTorsoTwist(); + headParams.localHeadOrientation = head->getFinalOrientationInLocalFrame(); + headParams.localHeadPitch = head->getFinalPitch(); + headParams.localHeadYaw = head->getFinalYaw(); + headParams.localHeadRoll = head->getFinalRoll(); + headParams.isInHMD = qApp->getAvatarUpdater()->isHMDMode(); // get HMD position from sensor space into world space, and back into model space glm::mat4 worldToModel = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition())); glm::vec3 yAxis(0.0f, 1.0f, 0.0f); glm::vec3 hmdPosition = glm::angleAxis((float)M_PI, yAxis) * transformPoint(worldToModel * myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition()); - params.localHeadPosition = hmdPosition; + headParams.localHeadPosition = hmdPosition; - params.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); - params.eyeLookAt = head->getLookAtPosition(); - params.eyeSaccade = head->getSaccade(); - params.leanJointIndex = geometry.leanJointIndex; - params.neckJointIndex = geometry.neckJointIndex; - params.leftEyeJointIndex = geometry.leftEyeJointIndex; - params.rightEyeJointIndex = geometry.rightEyeJointIndex; + headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame(); + headParams.eyeLookAt = head->getLookAtPosition(); + headParams.eyeSaccade = head->getSaccade(); + headParams.leanJointIndex = geometry.leanJointIndex; + headParams.neckJointIndex = geometry.neckJointIndex; + headParams.leftEyeJointIndex = geometry.leftEyeJointIndex; + headParams.rightEyeJointIndex = geometry.rightEyeJointIndex; + + _rig->updateFromHeadParameters(headParams); + + Rig::HandParameters handParams; + + // this is sooo getto + const PalmData* leftPalm = getPalmWithIndex(myAvatar->getHand(), LEFT_HAND_INDEX); + if (leftPalm && leftPalm->isActive()) { + handParams.isLeftEnabled = true; + handParams.leftPosition = leftPalm->getRawPosition(); + handParams.leftOrientation = leftPalm->getRawRotation(); + handParams.leftTrigger = leftPalm->getTrigger(); + } else { + handParams.isLeftEnabled = false; + } + + const PalmData* rightPalm = getPalmWithIndex(myAvatar->getHand(), RIGHT_HAND_INDEX); + if (rightPalm && rightPalm->isActive()) { + handParams.isRightEnabled = true; + handParams.rightPosition = rightPalm->getRawPosition(); + handParams.rightOrientation = rightPalm->getRawRotation(); + handParams.rightTrigger = rightPalm->getTrigger(); + } else { + handParams.isRightEnabled = false; + } + + _rig->updateFromHandParameters(handParams); - _rig->updateFromHeadParameters(params); } else { // This is a little more work than we really want. // diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 87eae99f5d..b2c4b6401c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -342,7 +342,7 @@ void AnimInverseKinematics::initConstraints() { // compute corresponding absolute poses int numJoints = (int)_defaultRelativePoses.size(); AnimPoseVec absolutePoses; - absolutePoses.reserve(numJoints); + absolutePoses.resize(numJoints); for (int i = 0; i < numJoints; ++i) { int parentIndex = _skeleton->getParentIndex(i); if (parentIndex < 0) { diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index aa58845f01..9d6eb12e44 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -172,7 +172,12 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString(); return nullptr; } - node->addChild(loadNode(childValue.toObject(), jsonUrl)); + AnimNode::Pointer child = loadNode(childValue.toObject(), jsonUrl); + if (child) { + node->addChild(child); + } else { + return nullptr; + } } if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { @@ -232,13 +237,15 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "fullBody", "upperBody", "lowerBody", - "rightArm", "leftArm", + "rightArm", "aboveTheHead", "belowTheHead", "headOnly", "spineOnly", - "empty" + "empty", + "leftHand", + "rightHand" }; static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 52026f7711..1a0a16ca8a 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -26,12 +26,14 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { case FullBodyBoneSet: buildFullBodyBoneSet(); break; case UpperBodyBoneSet: buildUpperBodyBoneSet(); break; case LowerBodyBoneSet: buildLowerBodyBoneSet(); break; - case RightArmBoneSet: buildRightArmBoneSet(); break; case LeftArmBoneSet: buildLeftArmBoneSet(); break; + case RightArmBoneSet: buildRightArmBoneSet(); break; case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break; case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break; case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break; case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break; + case LeftHandBoneSet: buildLeftHandBoneSet(); break; + case RightHandBoneSet: buildRightHandBoneSet(); break; default: case EmptyBoneSet: buildEmptyBoneSet(); break; } @@ -110,15 +112,6 @@ void AnimOverlay::buildLowerBodyBoneSet() { _boneSetVec[hipsJoint] = 0.0f; } -void AnimOverlay::buildRightArmBoneSet() { - assert(_skeleton); - buildEmptyBoneSet(); - int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder"); - for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) { - _boneSetVec[i] = 1.0f; - }); -} - void AnimOverlay::buildLeftArmBoneSet() { assert(_skeleton); buildEmptyBoneSet(); @@ -128,6 +121,15 @@ void AnimOverlay::buildLeftArmBoneSet() { }); } +void AnimOverlay::buildRightArmBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder"); + for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + void AnimOverlay::buildAboveTheHeadBoneSet() { assert(_skeleton); buildEmptyBoneSet(); @@ -168,13 +170,31 @@ void AnimOverlay::buildEmptyBoneSet() { } } +void AnimOverlay::buildLeftHandBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int headJoint = _skeleton->nameToJointIndex("LeftHand"); + for_each_child_joint(_skeleton, headJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + +void AnimOverlay::buildRightHandBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int headJoint = _skeleton->nameToJointIndex("RightHand"); + for_each_child_joint(_skeleton, headJoint, [&](int i) { + _boneSetVec[i] = 1.0f; + }); +} + // for AnimDebugDraw rendering const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; } void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { - _skeleton = skeleton; + AnimNode::setSkeletonInternal(skeleton); // we have to re-build the bone set when the skeleton changes. buildBoneSet(_boneSet); diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index de563cc403..2a87c54997 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -28,14 +28,16 @@ public: FullBodyBoneSet = 0, UpperBodyBoneSet, LowerBodyBoneSet, - RightArmBoneSet, LeftArmBoneSet, + RightArmBoneSet, AboveTheHeadBoneSet, BelowTheHeadBoneSet, HeadOnlyBoneSet, SpineOnlyBoneSet, EmptyBoneSet, - NumBoneSets, + LeftHandBoneSet, + RightHandBoneSet, + NumBoneSets }; AnimOverlay(const std::string& id, BoneSet boneSet, float alpha); @@ -64,13 +66,15 @@ public: void buildFullBodyBoneSet(); void buildUpperBodyBoneSet(); void buildLowerBodyBoneSet(); - void buildRightArmBoneSet(); void buildLeftArmBoneSet(); + void buildRightArmBoneSet(); void buildAboveTheHeadBoneSet(); void buildBelowTheHeadBoneSet(); void buildHeadOnlyBoneSet(); void buildSpineOnlyBoneSet(); void buildEmptyBoneSet(); + void buildLeftHandBoneSet(); + void buildRightHandBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3729dc8ca7..6baae8fa3e 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1048,6 +1048,62 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } +void Rig::updateFromHandParameters(const HandParameters& params) { + + if (_enableAnimGraph && _animSkeleton) { + + // AJT: TODO rotate these into the correct coordinate space + /* + if (params.isLeftEnabled) { + auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + _animVars.set("leftHandPosition", params.leftPosition + rootTrans); + _animVars.set("leftHandRotation", params.leftOrientation); + } else { + _animVars.unset("leftHandPosition"); + _animVars.unset("leftHandRotation"); + } + + if (params.isRightEnabled) { + auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; + _animVars.set("rightHandPosition", params.rightPosition + rootTrans); + _animVars.set("rightHandRotation", params.rightOrientation); + } else { + _animVars.unset("rightHandPosition"); + _animVars.unset("rightHandRotation"); + } + */ + + // AJT: REMOVE for grab/point debugging. + _animVars.set("isLeftHandIdle", false); + _animVars.set("isLeftHandPoint", false); + _animVars.set("isLeftHandClose", false); + if (params.leftTrigger > 0.3333f) { + if (params.leftTrigger > 0.6666f) { + _animVars.set("isLeftHandClose", true); + } else { + _animVars.set("isLeftHandPoint", true); + } + } else { + _animVars.set("isLeftHandIdle", true); + } + + // AJT: REMOVE for grab/point debugging. + _animVars.set("isRightHandIdle", false); + _animVars.set("isRightHandPoint", false); + _animVars.set("isRightHandClose", false); + if (params.rightTrigger > 0.3333f) { + if (params.rightTrigger > 0.6666f) { + _animVars.set("isRightHandClose", true); + } else { + _animVars.set("isRightHandPoint", true); + } + } else { + _animVars.set("isRightHandIdle", true); + } + + } +} + void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { if (!_enableAnimGraph) { return; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 18e888d2b0..f57b446417 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -76,6 +76,17 @@ public: void dump() const; }; + struct HandParameters { + bool isLeftEnabled; + bool isRightEnabled; + glm::vec3 leftPosition = glm::vec3(); + glm::quat leftOrientation = glm::quat(); + glm::vec3 rightPosition = glm::vec3(); + glm::quat rightOrientation = glm::quat(); + float leftTrigger = 0.0f; + float rightTrigger = 0.0f; + }; + virtual ~Rig() {} RigPointer getRigPointer() { return shared_from_this(); } @@ -172,6 +183,8 @@ public: void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade = glm::vec3(0.0f)); + void updateFromHandParameters(const HandParameters& params); + virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority) = 0; diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 08425201b0..1348824ba0 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -52,190 +52,358 @@ "children": [] }, { - "id": "mainStateMachine", - "type": "stateMachine", + "id": "rightHandOverlay", + "type": "overlay", "data": { - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkFwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "walkBwd", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "strafeLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningLeft", "state": "turnLeft" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTurning", "state": "idle" }, - { "var": "isMovingForward", "state": "walkFwd" }, - { "var": "isMovingBackward", "state": "walkBwd" }, - { "var": "isMovingRight", "state": "strafeRight" }, - { "var": "isMovingLeft", "state": "strafeLeft" }, - { "var": "isTurningRight", "state": "turnRight" } - ] - } - ] + "alpha": 1.0, + "boneSet": "rightHand" }, "children": [ { - "id": "idle", - "type": "clip", + "id": "rightHandStateMachine", + "type": "stateMachine", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, - "timeScale": 1.0, - "loopFlag": true + "currentState": "rightHandIdle", + "states": [ + { + "id": "rightHandIdle", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isRightHandPoint", "state": "rightHandPoint" }, + { "var": "isRightHandClose", "state": "rightHandClose" } + ] + }, + { + "id": "rightHandPoint", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isRightHandIdle", "state": "rightHandIdle" }, + { "var": "isRightHandClose", "state": "rightHandClose" } + ] + }, + { + "id": "rightHandClose", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isRightHandIdle", "state": "rightHandIdle" }, + { "var": "isRightHandPoint", "state": "rightHandPoint" } + ] + } + ] }, - "children": [] + "children": [ + { + "id": "rightHandIdle", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", + "startFrame": 30.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandPoint", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandClose", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { - "id": "walkFwd", - "type": "clip", + "id": "leftHandOverlay", + "type": "overlay", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" + "alpha": 1.0, + "boneSet": "leftHand" }, - "children": [] - }, - { - "id": "walkBwd", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", - "startFrame": 0.0, - "endFrame": 37.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" - }, - "children": [] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeft", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeRight", - "type": "clip", - "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandIdle", + "states": [ + { + "id": "leftHandIdle", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isLeftHandPoint", "state": "leftHandPoint" }, + { "var": "isLeftHandClose", "state": "leftHandClose" } + ] + }, + { + "id": "leftHandPoint", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isLeftHandIdle", "state": "leftHandIdle" }, + { "var": "isLeftHandClose", "state": "leftHandClose" } + ] + }, + { + "id": "leftHandClose", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isLeftHandIdle", "state": "leftHandIdle" }, + { "var": "isLeftHandPoint", "state": "leftHandPoint" } + ] + } + ] + }, + "children": [ + { + "id": "leftHandIdle", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx", + "startFrame": 30.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandPoint", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandClose", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkFwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "walkBwd", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "strafeLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isTurningRight", "state": "turnRight" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningLeft", "state": "turnLeft" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTurning", "state": "idle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRight", "state": "strafeRight" }, + { "var": "isMovingLeft", "state": "strafeLeft" }, + { "var": "isTurningRight", "state": "turnRight" } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "walkBwd", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", + "startFrame": 0.0, + "endFrame": 37.0, + "timeScale": 1.0, + "loopFlag": true, + "timeScaleVar": "walkTimeScale" + }, + "children": [] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeRight", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", + "startFrame": 0.0, + "endFrame": 31.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] } ] } From a3c0288eae5b28f0080ff38a0fc2707f01c2f230 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 14 Sep 2015 20:26:04 -0700 Subject: [PATCH 12/35] more work on proper threading of ScriptEngine --- interface/src/Application.cpp | 26 +-- .../src/EntityTreeRenderer.cpp | 22 +- .../src/EntityTreeRenderer.h | 8 - libraries/script-engine/src/ScriptCache.cpp | 17 +- libraries/script-engine/src/ScriptEngine.cpp | 195 ++++++++++++------ libraries/script-engine/src/ScriptEngine.h | 1 + 6 files changed, 152 insertions(+), 117 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4abfed1a03..a4b52cc67e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4226,25 +4226,12 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser if (scriptFilename.isNull()) { // This appears to be the script engine used by the script widget's evaluation window before the file has been saved... - qDebug() << "############################# HELLO WE ARE HERE!!!! ##################################"; // this had better be the script editor (we should de-couple so somebody who thinks they are loading a script // doesn't just get an empty script engine) // we can complete setup now since there isn't a script we have to load registerScriptEngineWithApplicationServices(scriptEngine); scriptEngine->runInThread(); - - //FIXME - intentionally attempting to test thread safe call to registerGlobalObject() - qDebug() << "about to attempt to call registerGlobalObject on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerGlobalObject("LODManager2", DependencyManager::get().data()); - - qDebug() << "about to attempt to call registerFunction on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerFunction("WebWindow2", WebWindowClass::constructor, 1); - - qDebug() << "about to attempt to call registerGetterSetter on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerGetterSetter("foo_location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); - - } else { // connect to the appropriate signals of this script engine connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded); @@ -4267,8 +4254,8 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) { loadScript(scriptName, isUserLoaded, false, false, true); } +// FIXME - change to new version of ScriptCache loading notification void Application::handleScriptEngineLoaded(const QString& scriptFilename) { - qDebug() << "handleScriptEngineLoaded().... scriptFilename:" << scriptFilename; ScriptEngine* scriptEngine = qobject_cast(sender()); _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); @@ -4278,18 +4265,9 @@ void Application::handleScriptEngineLoaded(const QString& scriptFilename) { // register our application services and set it off on its own thread registerScriptEngineWithApplicationServices(scriptEngine); scriptEngine->runInThread(); - - //FIXME - intentionally attempting to test thread safe call to registerGlobalObject() - qDebug() << "about to attempt to call registerGlobalObject on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerGlobalObject("LODManager2", DependencyManager::get().data()); - - qDebug() << "about to attempt to call registerFunction on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerFunction("WebWindow2", WebWindowClass::constructor, 1); - - qDebug() << "about to attempt to call registerGetterSetter on wrong thread!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"; - scriptEngine->registerGetterSetter("foo_location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); } +// FIXME - change to new version of ScriptCache loading notification void Application::handleScriptLoadError(const QString& scriptFilename) { qCDebug(interfaceapp) << "Application::loadScript(), script failed to load..."; QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load."); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 22d857c763..d34aabf6ca 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -49,7 +49,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf OctreeRenderer(), _wantScripts(wantScripts), _entitiesScriptEngine(NULL), - _sandboxScriptEngine(NULL), _lastMouseEventValid(false), _viewState(viewState), _scriptingServices(scriptingServices), @@ -77,15 +76,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf EntityTreeRenderer::~EntityTreeRenderer() { // NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a // signal tied to call it's deleteLater on doneRunning - if (_sandboxScriptEngine) { - // TODO: consider reworking how _sandboxScriptEngine is managed. It's treated differently than _entitiesScriptEngine - // because we don't call registerScriptEngineWithApplicationServices() for it. This implementation is confusing and - // potentially error prone because it's not a full fledged ScriptEngine that has been fully connected to the - // application. We did this so that scripts that were ill-formed could be evaluated but not execute against the - // application services. But this means it's shutdown behavior is different from other ScriptEngines - delete _sandboxScriptEngine; - _sandboxScriptEngine = NULL; - } } void EntityTreeRenderer::clear() { @@ -115,13 +105,9 @@ void EntityTreeRenderer::init() { if (_wantScripts) { _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities", - _scriptingServices->getControllerScriptingInterface(), false); + _scriptingServices->getControllerScriptingInterface()); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); - - // FIXME - this is dubious need to rework _entitiesScriptEngine->runInThread(); - - _sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL, false); } // make sure our "last avatar position" is something other than our current position, so that on our @@ -780,7 +766,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { } void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { - //checkAndCallPreload(entityID); + checkAndCallPreload(entityID); auto entity = static_cast(_tree)->findEntityByID(entityID); if (entity) { addEntityToScene(entity); @@ -808,7 +794,9 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); - _entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload); + if (!entity->getScript().isEmpty()) { + _entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload); + } } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index b9a3eb5cdd..d740bb2729 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -143,14 +143,6 @@ private: bool _wantScripts; ScriptEngine* _entitiesScriptEngine; - ScriptEngine* _sandboxScriptEngine; - - QScriptValue loadEntityScript(EntityItemPointer entity, bool isPreload = false, bool reload = false); - QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false, bool reload = false); - QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID); - QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url, bool& reload); - - //QHash _entityScripts; void playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 1b4997c5cd..e2c07c05d0 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,7 @@ QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUs if (alreadyWaiting) { qCDebug(scriptengine) << "Already downloading script at:" << url.toString(); } else { - auto request = ResourceManager::createResourceRequest(this, url); + auto request = ResourceManager::createResourceRequest(nullptr, url); request->setCacheEnabled(!reload); connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded); request->send(); @@ -82,6 +83,9 @@ void ScriptCache::scriptDownloaded() { } void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) { + #ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif QUrl unnormalizedURL(scriptOrURL); QUrl url = ResourceManager::normalizeURL(unnormalizedURL); @@ -93,6 +97,9 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable if (_scriptCache.contains(url) && !forceDownload) { qCDebug(scriptengine) << "Found script in cache:" << url.toString(); + #if 1 // def THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptCache::getScriptContents() about to call contentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif contentAvailable(url.toString(), _scriptCache[url], true, true); } else { bool alreadyWaiting = _contentCallbacks.contains(url); @@ -101,7 +108,10 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable if (alreadyWaiting) { qCDebug(scriptengine) << "Already downloading script at:" << url.toString(); } else { - auto request = ResourceManager::createResourceRequest(this, url); + #ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif + auto request = ResourceManager::createResourceRequest(nullptr, url); request->setCacheEnabled(!forceDownload); connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable); request->send(); @@ -110,6 +120,9 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable } void ScriptCache::scriptContentAvailable() { + #ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif ResourceRequest* req = qobject_cast(sender()); QUrl url = req->getUrl(); QList allCallbacks = _contentCallbacks.values(url); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f277a40470..e5ce867df3 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -222,7 +222,7 @@ QString ScriptEngine::getFilename() const { } -// FIXME - remove the file/url scheme code here and let the script cache handle things +// FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { if (_isRunning) { return; @@ -238,6 +238,7 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { scriptCache->getScript(url, this, isPending, reload); } +// FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { _scriptContents = scriptContents; if (_wantSignals) { @@ -245,6 +246,7 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip } } +// FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::errorInLoadingScript(const QUrl& url) { qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__; if (_wantSignals) { @@ -320,13 +322,17 @@ void ScriptEngine::init() { void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; + #endif QMetaObject::invokeMethod(this, "registerGlobalObject", Q_ARG(const QString&, name), Q_ARG(QObject*, object)); return; } - //qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; + #endif if (object) { QScriptValue value = newQObject(object); @@ -336,14 +342,18 @@ void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; + #endif QMetaObject::invokeMethod(this, "registerFunction", Q_ARG(const QString&, name), Q_ARG(QScriptEngine::FunctionSignature, functionSignature), Q_ARG(int, numArguments)); return; } - //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; + #endif QScriptValue scriptFun = newFunction(functionSignature, numArguments); globalObject().setProperty(name, scriptFun); @@ -351,14 +361,18 @@ void ScriptEngine::registerFunction(const QString& name, QScriptEngine::Function void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; + #endif QMetaObject::invokeMethod(this, "registerFunction", Q_ARG(const QString&, name), Q_ARG(QScriptEngine::FunctionSignature, functionSignature), Q_ARG(int, numArguments)); return; } - //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; + #endif QScriptValue object = globalObject().property(parent); if (object.isValid()) { @@ -370,8 +384,10 @@ void ScriptEngine::registerFunction(const QString& parent, const QString& name, void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, const QString& parent) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " " name:" << name << "parent:" << parent; + #endif QMetaObject::invokeMethod(this, "registerGetterSetter", Q_ARG(const QString&, name), Q_ARG(QScriptEngine::FunctionSignature, getter), @@ -379,7 +395,9 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func Q_ARG(const QString&, parent)); return; } - //qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; + #endif QScriptValue setterFunction = newFunction(setter, 1); QScriptValue getterFunction = newFunction(getter); @@ -399,15 +417,19 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func // Unregister the handlers for this eventName and entityID. void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << " eventName:" << eventName; + #endif QMetaObject::invokeMethod(this, "removeEventHandler", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, eventName), Q_ARG(QScriptValue, handler)); return; } - //qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + #endif if (!_registeredHandlers.contains(entityID)) { return; @@ -425,15 +447,20 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin // Register the handler. void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << " eventName:" << eventName; + #endif + QMetaObject::invokeMethod(this, "addEventHandler", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, eventName), Q_ARG(QScriptValue, handler)); return; } - //qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; + #endif if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... // Connect up ALL the handlers to the global entities object's signals. @@ -496,8 +523,10 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN if (QThread::currentThread() != thread()) { QScriptValue result; + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; + #endif QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, result), Q_ARG(const QString&, program), @@ -854,10 +883,10 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e // for the download void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << - "entityScript:" << entityScript << - "forceRedownload:" << forceRedownload; + "entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload; + #endif QMetaObject::invokeMethod(this, "loadEntityScript", Q_ARG(const EntityItemID&, entityID), @@ -865,71 +894,104 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& Q_ARG(bool, forceRedownload)); return; } + #ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] " - "entityID:" << entityID << - "entityScript:" << entityScript << - "forceRedownload:" << forceRedownload; + "entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload; + #endif // If we've been called our known entityScripts should not know about us.. assert(!_entityScripts.contains(entityID)); + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif + DependencyManager::get()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif + + this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success); + }, forceRedownload); +} + +// since all of these operations can be asynch we will always do the actual work in the response handler +// for the download +void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { + if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING + qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" << contents << "isURL:" << isURL << "success:" << success; + #endif + + QMetaObject::invokeMethod(this, "entityScriptContentAvailable", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, scriptOrURL), + Q_ARG(const QString&, contents), + Q_ARG(bool, isURL), + Q_ARG(bool, success)); + return; + } + + #ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; + #endif + auto scriptCache = DependencyManager::get(); - scriptCache->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { - - // first check the syntax of the script contents - QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; - qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" + // first check the syntax of the script contents + QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; + qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); - qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; - scriptCache->addScriptToBadScriptList(scriptOrURL); - return; // done processing script - } + qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; + scriptCache->addScriptToBadScriptList(scriptOrURL); + return; // done processing script + } - if (isURL) { - setParentURL(scriptOrURL); - } + if (isURL) { + setParentURL(scriptOrURL); + } - QScriptEngine sandbox; - QScriptValue testConstructor = sandbox.evaluate(contents); + QScriptEngine sandbox; + QScriptValue testConstructor = sandbox.evaluate(contents); - if (!testConstructor.isFunction()) { - qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; - qCDebug(scriptengine) << " NOT CONSTRUCTOR"; - qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; - scriptCache->addScriptToBadScriptList(scriptOrURL); - return; // done processing script - } + if (!testConstructor.isFunction()) { + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; + qCDebug(scriptengine) << " NOT CONSTRUCTOR"; + qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; + scriptCache->addScriptToBadScriptList(scriptOrURL); + return; // done processing script + } - QScriptValue entityScriptConstructor = evaluate(contents); + QScriptValue entityScriptConstructor = evaluate(contents); - QScriptValue entityScriptObject = entityScriptConstructor.construct(); - EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject }; - _entityScripts[entityID] = newDetails; + QScriptValue entityScriptObject = entityScriptConstructor.construct(); + EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject }; + _entityScripts[entityID] = newDetails; + if (isURL) { + setParentURL(""); + } - if (isURL) { - setParentURL(""); - } - - // if we got this far, then call the preload method - callEntityScriptMethod(entityID, "preload"); - - }, forceRedownload); + // if we got this far, then call the preload method + callEntityScriptMethod(entityID, "preload"); } void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID; + #endif QMetaObject::invokeMethod(this, "unloadEntityScript", Q_ARG(const EntityItemID&, entityID)); return; } + #ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] " "entityID:" << entityID; + #endif if (_entityScripts.contains(entityID)) { callEntityScriptMethod(entityID, "unload"); @@ -939,18 +1001,20 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName; + "entityID:" << entityID << "methodName:" << methodName; + #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName)); return; } + #ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName; + "entityID:" << entityID << "methodName:" << methodName; + #endif if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; @@ -960,15 +1024,16 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS args << entityID.toScriptValue(this); entityScript.property(methodName).call(entityScript, args); } + } } void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName << - "event: mouseEvent"; + "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; + #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), @@ -976,10 +1041,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS Q_ARG(const MouseEvent&, event)); return; } + #ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName << - "event: mouseEvent"; + "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; + #endif if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; @@ -996,11 +1061,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) { + #ifdef THREAD_DEBUGGING qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName << - "otherID:" << otherID << - "collision: collision"; + "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; + #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), @@ -1009,11 +1073,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS Q_ARG(const Collision&, collision)); return; } + #ifdef THREAD_DEBUGGING qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << - "methodName:" << methodName << - "otherID:" << otherID << - "collision: collision"; + "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; + #endif if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index ab1833636c..3b42fcc373 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -188,6 +188,7 @@ private: QHash _registeredHandlers; void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator); + Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); private: static QSet _allKnownScriptEngines; From 94b273a029033da34526f9a9e3d829fbb0f8e258 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 14 Sep 2015 20:59:10 -0700 Subject: [PATCH 13/35] more work on script engine --- assignment-client/src/Agent.h | 1 - interface/src/Application.cpp | 1 - .../src/EntityTreeRenderer.cpp | 18 +++--------------- .../entities-renderer/src/EntityTreeRenderer.h | 1 - libraries/script-engine/src/ScriptEngine.cpp | 18 ++++++++++++++++++ libraries/script-engine/src/ScriptEngine.h | 12 +++--------- 6 files changed, 24 insertions(+), 27 deletions(-) diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 52e1dfec6c..33a8eb58c2 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -75,7 +75,6 @@ private: void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); - // FIXME - Agent only data AvatarData* _avatarData = nullptr; bool _isListeningToAudioStream = false; Sound* _avatarSound = nullptr; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a4b52cc67e..0d9a0278ec 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4238,7 +4238,6 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &Application::handleScriptLoadError); // get the script engine object to load the script at the designated script URL - qDebug() << "calling scriptEngine->loadURL(" << scriptUrl << ", " << reload << ");"; scriptEngine->loadURL(scriptUrl, reload); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index d34aabf6ca..3736f164c2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -80,12 +80,7 @@ EntityTreeRenderer::~EntityTreeRenderer() { void EntityTreeRenderer::clear() { leaveAllEntities(); - /* - foreach (const EntityItemID& entityID, _entityScripts.keys()) { - checkAndCallUnload(entityID); - } - _entityScripts.clear(); - */ + _entitiesScriptEngine->unloadAllEntityScripts(); auto scene = _viewState->getMain3DScene(); render::PendingChanges pendingChanges; @@ -751,9 +746,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { if (_tree && !_shuttingDown) { - //checkAndCallUnload(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID); } - //_entityScripts.remove(entityID); // here's where we remove the entity payload from the scene if (_entitiesInScene.contains(entityID)) { @@ -786,7 +780,7 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) { void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { - checkAndCallUnload(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID); checkAndCallPreload(entityID, reload); } } @@ -800,12 +794,6 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const } } -void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) { - if (_tree && !_shuttingDown) { - _entitiesScriptEngine->unloadEntityScript(entityID); - } -} - void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTree* entityTree, const EntityItemID& id, const Collision& collision) { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (!entity) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index d740bb2729..2b1784c8b5 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -125,7 +125,6 @@ private: void applyZonePropertiesToScene(std::shared_ptr zone); void renderElementProxy(EntityTreeElement* entityTreeElement, RenderArgs* args); void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false); - void checkAndCallUnload(const EntityItemID& entityID); QList _releasedModels; void renderProxies(EntityItemPointer entity, RenderArgs* args); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e5ce867df3..62b9410ede 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -999,6 +999,24 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { } } +void ScriptEngine::unloadAllEntityScripts() { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qDebug() << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + + QMetaObject::invokeMethod(this, "unloadAllEntityScripts"); + return; + } +#ifdef THREAD_DEBUGGING + qDebug() << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; +#endif + foreach(const EntityItemID& entityID, _entityScripts.keys()) { + callEntityScriptMethod(entityID, "unload"); + } + _entityScripts.clear(); +} + void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 3b42fcc373..3f509cf994 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -109,6 +109,7 @@ public: // Entity Script Related methods Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method + Q_INVOKABLE void unloadAllEntityScripts(); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); @@ -173,6 +174,8 @@ private: void timerFired(); void stopAllTimers(); + void setParentURL(const QString& parentURL) { _parentURL = parentURL; } + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); @@ -190,20 +193,11 @@ private: void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function argGenerator); Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); -private: static QSet _allKnownScriptEngines; static QMutex _allScriptsMutex; static bool _stoppingAllScripts; static bool _doneRunningThisScript; -private: - //FIXME- EntityTreeRender shouldn't be using these methods directly -- these methods need to be depricated from the public interfaces - friend class EntityTreeRenderer; - - //FIXME - used in EntityTreeRenderer when evaluating entity scripts, which needs to be moved into ScriptEngine - // also used in Agent, but that would be fixed if Agent was using proper apis for loading content - void setParentURL(const QString& parentURL) { _parentURL = parentURL; } - }; #endif // hifi_ScriptEngine_h From e92d195030b2188ecc194e78e18ad80dc47637b7 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 14 Sep 2015 21:18:36 -0700 Subject: [PATCH 14/35] tweak --- libraries/script-engine/src/ScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e635f9f470..1ef3769970 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -90,9 +90,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _isFinished(false), _isRunning(false), _isInitialized(false), - _controllerScriptingInterface(controllerScriptingInterface), _timerFunctionMap(), _wantSignals(wantSignals), + _controllerScriptingInterface(controllerScriptingInterface), _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), From 3f688f92d343f74ca41f4ab026a340c71932a497 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 14 Sep 2015 16:09:13 -0700 Subject: [PATCH 15/35] Don't do an O(N^2) operation on cluster matrices --- libraries/render-utils/src/Model.cpp | 31 +++++++++++++++++++++++----- libraries/render-utils/src/Model.h | 1 + 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4c9279c8fd..cfbcd0599a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -798,7 +798,7 @@ namespace render { return payload->model->renderPart(args, payload->meshIndex, payload->partIndex, payload->transparent); } } - + /* template <> const model::MaterialKey& shapeGetMaterialKey(const MeshPartPayload::Pointer& payload) { return payload->model->getPartMaterial(payload->meshIndex, payload->partIndex); }*/ @@ -829,6 +829,9 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan auto renderData = MeshPartPayload::Pointer(renderItem); auto renderPayload = std::make_shared(renderData); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); _renderItems.insert(item, renderPayload); somethingAdded = true; } @@ -838,6 +841,9 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan auto renderData = MeshPartPayload::Pointer(renderItem); auto renderPayload = std::make_shared(renderData); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); _renderItems.insert(item, renderPayload); somethingAdded = true; } @@ -860,6 +866,9 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan auto renderPayload = std::make_shared(renderData); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); _renderItems.insert(item, renderPayload); somethingAdded = true; } @@ -870,6 +879,9 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan auto renderPayload = std::make_shared(renderData); renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); _renderItems.insert(item, renderPayload); somethingAdded = true; } @@ -1293,11 +1305,15 @@ void Model::simulateInternal(float deltaTime) { updateRig(deltaTime, parentTransform); } void Model::updateClusterMatrices() { + if (!_needsUpdateClusterMatrices) { + return; + } + _needsUpdateClusterMatrices = false; const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), - glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), - glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), - glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), + glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale; glm::mat4 modelToWorld = glm::mat4_cast(_rotation); @@ -1803,13 +1819,15 @@ bool Model::initWhenReady(render::ScenePointer scene) { segregateMeshGroups(); render::PendingChanges pendingChanges; - foreach (auto renderItem, _transparentRenderItems) { auto item = scene->allocateID(); auto renderData = MeshPartPayload::Pointer(renderItem); auto renderPayload = std::make_shared(renderData); _renderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); } foreach (auto renderItem, _opaqueRenderItems) { @@ -1818,6 +1836,9 @@ bool Model::initWhenReady(render::ScenePointer scene) { auto renderPayload = std::make_shared(renderData); _renderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [&](MeshPartPayload& data) { + data.model->_needsUpdateClusterMatrices = true; + }); } scene->enqueuePendingChanges(pendingChanges); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 348e5cf549..93b98da8b5 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -497,6 +497,7 @@ private: QMap _renderItems; bool _readyWhenAdded = false; bool _needsReload = true; + bool _needsUpdateClusterMatrices = true; protected: RigPointer _rig; From f4818c17cf22c1d0f286604cea201e06536a5ea7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 15 Sep 2015 07:44:57 -0700 Subject: [PATCH 16/35] Fix avatar and other model animations --- libraries/render-utils/src/Model.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index cfbcd0599a..a4dc1b1de3 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1295,6 +1295,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) { //virtual void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { + _needsUpdateClusterMatrices = true; _rig->updateAnimations(deltaTime, parentTransform); } void Model::simulateInternal(float deltaTime) { From 70d2288407816cea1cf1862af7e211b6f00dd0cf Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 08:04:26 -0700 Subject: [PATCH 17/35] CR feedback --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 4c74bf3793..2fd760bbd3 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -778,7 +778,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); - if (!entity->getScript().isEmpty()) { + if (entity && !entity->getScript().isEmpty()) { _entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload); } } From 6a186ad1fe420ad753fb24cfd38bec8dfba12055 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 15 Sep 2015 09:48:17 -0700 Subject: [PATCH 18/35] ensure a handshake goes out the first time --- libraries/networking/src/udt/SendQueue.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 7f1590f995..a09ea6ca9a 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -279,17 +279,13 @@ void SendQueue::run() { if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client // if it has been at least 100ms since we last sent a handshake, send another now - - // hold the time of last send in a static - static auto lastSendHandshake = p_high_resolution_clock::time_point().min(); - + static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100); - // calculation the duration since the last handshake send - auto sinceLastHandshake = std::chrono::duration_cast(p_high_resolution_clock::now() - - lastSendHandshake); + // hold the time of last send in a static + static auto lastSendHandshake = p_high_resolution_clock::now() - HANDSHAKE_RESEND_INTERVAL_MS; - if (sinceLastHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) { + if (p_high_resolution_clock::now() - lastSendHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) { // it has been long enough since last handshake, send another static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0); @@ -299,9 +295,7 @@ void SendQueue::run() { } // we wait for the ACK or the re-send interval to expire - _handshakeACKCondition.wait_until(handshakeLock, - p_high_resolution_clock::now() - + HANDSHAKE_RESEND_INTERVAL_MS); + _handshakeACKCondition.wait_until(handshakeLock, p_high_resolution_clock::now() + HANDSHAKE_RESEND_INTERVAL_MS); // Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake. // Either way let's continue processing - no packets will be sent if no handshake ACK has been received. From 5045f8558eaa5db1ebc407556ba274775f9c00c7 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 15 Sep 2015 10:08:24 -0700 Subject: [PATCH 19/35] Fix incorrect scale factor for converting int16_t to/from float --- libraries/audio/src/AudioFilterBank.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h index 4ea5a3568a..b99e1ca774 100644 --- a/libraries/audio/src/AudioFilterBank.h +++ b/libraries/audio/src/AudioFilterBank.h @@ -125,7 +125,7 @@ public: return; } - const int scale = (2 << ((8 * sizeof(int16_t)) - 1)); + const int scale = (1 << ((8 * sizeof(int16_t)) - 1)); // de-interleave and convert int16_t to float32 (normalized to -1. ... 1.) for (uint32_t i = 0; i < frameCount; ++i) { From 6756d5364b9739a3995524f589413ec6fed250db Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 15 Sep 2015 10:11:05 -0700 Subject: [PATCH 20/35] sensible defaults for other time_point uses --- .../networking/src/udt/CongestionControl.cpp | 1 - libraries/networking/src/udt/CongestionControl.h | 2 +- libraries/networking/src/udt/Connection.cpp | 14 +++++++------- libraries/networking/src/udt/Connection.h | 7 ++++--- libraries/networking/src/udt/PacketTimeWindow.cpp | 15 +++++++++------ libraries/networking/src/udt/PacketTimeWindow.h | 4 ++-- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index 2240f2c193..ea46d60acb 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -34,7 +34,6 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) { } DefaultCC::DefaultCC() : - _lastRCTime(p_high_resolution_clock::now()), _slowStartLastAck(_sendCurrSeqNum), _lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX }) { diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index 5f9b64c1ad..fa1bf73ecf 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -108,7 +108,7 @@ public: private: void stopSlowStart(); // stops the slow start on loss or timeout - p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::time_point().min(); // last rate increase time + p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::now(); // last rate increase time bool _slowStart { true }; // if in slow start phase SequenceNumber _slowStartLastAck; // last ACKed seq num diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 4aa785fa8c..2fb28f81ee 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -28,7 +28,6 @@ using namespace udt; using namespace std::chrono; Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl) : - _connectionStart(p_high_resolution_clock::now()), _parentSocket(parentSocket), _destination(destination), _congestionControl(move(congestionControl)) @@ -222,7 +221,7 @@ void Connection::recordRetransmission() { } void Connection::sendACK(bool wasCausedBySyncTimeout) { - static p_high_resolution_clock::time_point lastACKSendTime = p_high_resolution_clock::time_point().min(); + static p_high_resolution_clock::time_point lastACKSendTime; auto currentTime = p_high_resolution_clock::now(); SequenceNumber nextACKNumber = nextACK(); @@ -278,11 +277,11 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { // pack in the receive speed and estimatedBandwidth ackPacket->writePrimitive(packetReceiveSpeed); ackPacket->writePrimitive(estimatedBandwidth); - - // record this as the last ACK send time - lastACKSendTime = p_high_resolution_clock::now(); } + // record this as the last ACK send time + lastACKSendTime = p_high_resolution_clock::now(); + // have the socket send off our packet _parentSocket->writeBasePacket(*ackPacket, _destination); @@ -534,7 +533,8 @@ void Connection::processACK(std::unique_ptr controlPacket) { // This will be the case if it has been longer than the sync interval OR // it looks like they haven't received our ACK2 for this ACK auto currentTime = p_high_resolution_clock::now(); - static p_high_resolution_clock::time_point lastACK2SendTime = p_high_resolution_clock::time_point().min(); + static p_high_resolution_clock::time_point lastACK2SendTime = + p_high_resolution_clock::now() - std::chrono::microseconds(_synInterval); microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); @@ -779,7 +779,7 @@ void Connection::resetReceiveState() { // clear the loss list and _lastNAKTime _lossList.clear(); - _lastNAKTime = p_high_resolution_clock::time_point().min(); + _lastNAKTime = p_high_resolution_clock::now(); // the _nakInterval need not be reset, that will happen on loss diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 346f559165..2b1dec1ae9 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -114,13 +114,14 @@ private: int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms - p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::time_point().min(); + p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::now(); bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client - p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::time_point().min(); // holds the time_point for creation of this connection - p_high_resolution_clock::time_point _lastReceiveTime = p_high_resolution_clock::time_point().min(); // holds the last time we received anything from sender + p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection + p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender + bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection LossList _lossList; // List of all missing packets diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp index 51bba2c52c..915810b93e 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.cpp +++ b/libraries/networking/src/udt/PacketTimeWindow.cpp @@ -93,15 +93,18 @@ int32_t PacketTimeWindow::getEstimatedBandwidth() const { } void PacketTimeWindow::onPacketArrival() { + // take the current time auto now = p_high_resolution_clock::now(); - // record the interval between this packet and the last one - _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); - - // reset the currentPacketInterval index when it wraps - if (_currentPacketInterval == _numPacketIntervals) { - _currentPacketInterval = 0; + if (_packetIntervals.size() > 0) { + // record the interval between this packet and the last one + _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); + + // reset the currentPacketInterval index when it wraps + if (_currentPacketInterval == _numPacketIntervals) { + _currentPacketInterval = 0; + } } // remember this as the last packet arrival time diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h index 6030e6809f..6f7ed9f70f 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.h +++ b/libraries/networking/src/udt/PacketTimeWindow.h @@ -42,8 +42,8 @@ private: std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals std::vector _probeIntervals; // vector of microsecond intervals between probe pair arrivals - p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::time_point().min(); // the time_point when last packet arrived - p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::time_point().min(); // the time_point when first probe in pair arrived + p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::now(); // the time_point when last packet arrived + p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::now(); // the time_point when first probe in pair arrived }; } From ffe3fcf4cea8fa88bc172a64ce5b219ae09de774 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 15 Sep 2015 10:18:53 -0700 Subject: [PATCH 21/35] Replace repeated division with multiply by inverse --- libraries/audio/src/AudioFilterBank.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h index b99e1ca774..64ea5396e1 100644 --- a/libraries/audio/src/AudioFilterBank.h +++ b/libraries/audio/src/AudioFilterBank.h @@ -130,7 +130,7 @@ public: // de-interleave and convert int16_t to float32 (normalized to -1. ... 1.) for (uint32_t i = 0; i < frameCount; ++i) { for (uint32_t j = 0; j < _channelCount; ++j) { - _buffer[j][i] = ((float)(*in++)) / scale; + _buffer[j][i] = ((float)(*in++)) * (1.0f / scale); } } From 99b0046fc7cde8b0117befa25cb8f6afcee99bb1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Sep 2015 10:43:17 -0700 Subject: [PATCH 22/35] various adjustments --- examples/controllers/handControllerGrab.js | 101 ++++++++++++--------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 053227e889..d938b9ff55 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -4,7 +4,7 @@ // Created by Eric Levin on 9/2/15 // Copyright 2015 High Fidelity, Inc. // -// Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller. +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,6 +13,8 @@ Script.include("../libraries/utils.js"); +var RADIUS_FACTOR = 4; + var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -45,7 +47,7 @@ var INTERSECT_COLOR = { blue: 10 }; -var GRAB_RADIUS = 0.5; +var GRAB_RADIUS = 0.3; var GRAB_COLOR = { red: 250, @@ -107,8 +109,20 @@ function controller(side, triggerAction, pullAction, hand) { controller.prototype.updateLine = function() { - var handPosition = Controller.getSpatialControlPosition(this.palm); - var direction = Controller.getSpatialControlNormal(this.tip); + var handPosition = this.getHandPosition(); + var direction = Quat.getUp(this.getHandRotation()); + Quat.print("blah", this.getHandRotation()); + + //only check if we havent already grabbed an object + if (this.distanceHolding) { + Entities.editEntity(this.pointer, { + position: handPosition, + linePoints: [ ZERO_VEC, Vec3.subtract(Entities.getEntityProperties(this.grabbedEntity).position, handPosition) ], + lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME + }); + + return; + } Entities.editEntity(this.pointer, { position: handPosition, @@ -116,10 +130,6 @@ controller.prototype.updateLine = function() { lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME }); - //only check if we havent already grabbed an object - if (this.distanceHolding) { - return; - } //move origin a bit away from hand so nothing gets in way var origin = Vec3.sum(handPosition, direction); @@ -168,25 +178,57 @@ controller.prototype.checkForIntersections = function(origin, direction) { return false; } + +controller.prototype.getSuperSpinHandRotation = function(handRotation) { + + // origHand * handChange = nowHand + + // -1 + // handChange = origHand * nowHand + + var handChange = Quat.multiply(Quat.inverse(this.handOriginalRotation), handRotation); + return Quat.multiply(this.grabbedOriginalRotation, handChange); + + // var superHandChange = Quat.multiply(Quat.inverse(this.handOriginalRotation), + // // Quat.slerp(this.handOriginalRotation, handRotation, 1.5) + // handRotation + // ); + // return Quat.multiply(this.grabbedOriginalRotation, superHandChange); +} + + controller.prototype.attemptMove = function() { if (this.grabbedEntity || this.distanceHolding) { var handPosition = Controller.getSpatialControlPosition(this.palm); - var direction = Controller.getSpatialControlNormal(this.tip); + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity)) this.distanceHolding = true; if (this.actionID === null) { + this.currentObjectPosition = Entities.getEntityProperties(this.grabbedEntity).position; + this.handPreviousPosition = handPosition; + this.grabbedOriginalRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; + this.handOriginalRotation = handRotation; this.actionID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: newPosition, - linearTimeScale: .1 + targetPosition: this.currentObjectPosition, + linearTimeScale: .1, + targetRotation: this.getSuperSpinHandRotation(handRotation), + angularTimeScale: .1 }); } else { + var handMoved = Vec3.subtract(handPosition, this.handPreviousPosition); + this.handPreviousPosition = handPosition; + var radius = Math.max(Vec3.distance(this.currentObjectPosition, handPosition) * RADIUS_FACTOR, 1.0); + var superHandMoved = Vec3.multiply(handMoved, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: newPosition + targetPosition: this.currentObjectPosition, + linearTimeScale: .1, + // targetRotation: this.getSuperSpinHandRotation(handRotation), + targetRotation: handRotation, + angularTimeScale: .1 }); } } - } controller.prototype.showPointer = function() { @@ -211,7 +253,6 @@ controller.prototype.letGo = function() { this.grabbedEntity = null; this.actionID = null; this.distanceHolding = false; - this.checkForEntityArrival = false; this.closeGrabbing = false; } @@ -238,7 +279,6 @@ controller.prototype.update = function() { this.attemptMove(); } - this.prevTriggerValue = this.triggerValue; } @@ -316,27 +356,6 @@ controller.prototype.deactivateEntity = function(entity) { setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data); } -controller.prototype.onActionEvent = function(action, state) { - // if (this.pullAction === action && state === 1) { - // if (this.actionID !== null) { - // var self = this; - // this.tractorBeamActive = true; - // //We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some - // //low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving! - // Script.setTimeout(function() { - // self.checkForEntityArrival = true; - // }, 500); - // var handPosition = Controller.getSpatialControlPosition(this.palm); - // var direction = Vec3.normalize(Controller.getSpatialControlNormal(this.tip)); - // //move final destination along line a bit, so it doesnt hit avatar hand - // Entities.updateAction(this.grabbedEntity, this.actionID, { - // targetPosition: Vec3.sum(handPosition, Vec3.multiply(3, direction)) - // }); - // } - // } - -} - controller.prototype.cleanup = function() { Entities.deleteEntity(this.pointer); if (this.grabbedEntity) { @@ -349,13 +368,6 @@ function update() { leftController.update(); } -function onActionEvent(action, state) { - rightController.onActionEvent(action, state); - leftController.onActionEvent(action, state); - -} - - function cleanup() { rightController.cleanup(); leftController.cleanup(); @@ -363,4 +375,3 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update) -Controller.actionEvent.connect(onActionEvent); From 6521de81634ac390fd8dcc5a83806bb193d2a6b1 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 11:00:05 -0700 Subject: [PATCH 23/35] rework ScriptEngine and worker thread shutdown --- libraries/script-engine/src/ScriptEngine.cpp | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1ef3769970..19e4a8f443 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -107,6 +107,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam } ScriptEngine::~ScriptEngine() { + qDebug() << "ScriptEngine::~ScriptEngine().... this:" << this << "my thread:" << thread(); // If we're not already in the middle of stopping all scripts, then we should remove ourselves // from the list of running scripts. We don't do this if we're in the process of stopping all scripts // because that method removes scripts from its list as it iterates them @@ -117,20 +118,28 @@ ScriptEngine::~ScriptEngine() { } } +class MyWorkerThread : public QThread { +public: + MyWorkerThread(QObject* parent = nullptr) : QThread(parent) { qDebug() << "MyWorkerThread::MyWorkerThread() this:" << this; } + ~MyWorkerThread() { qDebug() << "MyWorkerThread::~MyWorkerThread() this:" << this; } +}; + void ScriptEngine::runInThread() { - QThread* workerThread = new QThread(this); + QThread* workerThread = new MyWorkerThread(); // thread is owned but ScriptEngine, so if the ScriptEngine is destroyed, the thread will be too. QString scriptEngineName = QString("Script Thread:") + getFilename(); workerThread->setObjectName(scriptEngineName); // when the worker thread is started, call our engine's run.. connect(workerThread, &QThread::started, this, &ScriptEngine::run); - // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue - connect(this, &ScriptEngine::doneRunning, this, &ScriptEngine::deleteLater); + // tell the thread to stop when the script engine is done + connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); + + // when the thread is finished, add thread to the deleteLater queue connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); - // tell the thread to stop when the script engine is done - connect(this, &ScriptEngine::destroyed, workerThread, &QThread::quit); + // when the thread is destroyed, add scriptEngine to the deleteLater queue + connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater); moveToThread(workerThread); @@ -650,10 +659,12 @@ void ScriptEngine::run() { _isRunning = false; if (_wantSignals) { emit runningStateChanged(); + qDebug() << "ScriptEngine::run().... about to emit doneRunning().... this:" << this << "my thread:" << thread() << "current thread:" << QThread::currentThread(); emit doneRunning(); } _doneRunningThisScript = true; + qDebug() << "ScriptEngine::run().... END OF RUN.... this:" << this << "my thread:" << thread() << "current thread:" << QThread::currentThread(); } // NOTE: This is private because it must be called on the same thread that created the timers, which is why From 7b5d6c20e92a6f185dfab67cffdb00b13ad5b2af Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Sep 2015 11:21:13 -0700 Subject: [PATCH 24/35] double change in hand rotation before applying it to the object --- examples/controllers/handControllerGrab.js | 52 ++++++++++------------ 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index d938b9ff55..8fcc7d7bb9 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -111,7 +111,6 @@ function controller(side, triggerAction, pullAction, hand) { controller.prototype.updateLine = function() { var handPosition = this.getHandPosition(); var direction = Quat.getUp(this.getHandRotation()); - Quat.print("blah", this.getHandRotation()); //only check if we havent already grabbed an object if (this.distanceHolding) { @@ -179,24 +178,6 @@ controller.prototype.checkForIntersections = function(origin, direction) { } -controller.prototype.getSuperSpinHandRotation = function(handRotation) { - - // origHand * handChange = nowHand - - // -1 - // handChange = origHand * nowHand - - var handChange = Quat.multiply(Quat.inverse(this.handOriginalRotation), handRotation); - return Quat.multiply(this.grabbedOriginalRotation, handChange); - - // var superHandChange = Quat.multiply(Quat.inverse(this.handOriginalRotation), - // // Quat.slerp(this.handOriginalRotation, handRotation, 1.5) - // handRotation - // ); - // return Quat.multiply(this.grabbedOriginalRotation, superHandChange); -} - - controller.prototype.attemptMove = function() { if (this.grabbedEntity || this.distanceHolding) { var handPosition = Controller.getSpatialControlPosition(this.palm); @@ -205,27 +186,42 @@ controller.prototype.attemptMove = function() { this.distanceHolding = true; if (this.actionID === null) { this.currentObjectPosition = Entities.getEntityProperties(this.grabbedEntity).position; + this.currentObjectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; + this.handPreviousPosition = handPosition; - this.grabbedOriginalRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; - this.handOriginalRotation = handRotation; + this.handPreviousRotation = handRotation; + this.actionID = Entities.addAction("spring", this.grabbedEntity, { targetPosition: this.currentObjectPosition, linearTimeScale: .1, - targetRotation: this.getSuperSpinHandRotation(handRotation), + targetRotation: this.currentObjectRotation, angularTimeScale: .1 }); } else { + var radius = Math.max(Vec3.distance(this.currentObjectPosition, handPosition) * RADIUS_FACTOR, 1.0); + var handMoved = Vec3.subtract(handPosition, this.handPreviousPosition); this.handPreviousPosition = handPosition; - var radius = Math.max(Vec3.distance(this.currentObjectPosition, handPosition) * RADIUS_FACTOR, 1.0); var superHandMoved = Vec3.multiply(handMoved, radius); this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + + // ---------------- this tracks hand rotation + // var handChange = Quat.multiply(handRotation, Quat.inverse(this.handPreviousRotation)); + // this.handPreviousRotation = handRotation; + // this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + // ---------------- + + // ---------------- this doubles hand rotation + var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation, 2.0), + Quat.inverse(this.handPreviousRotation)); + this.handPreviousRotation = handRotation; + this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); + // ---------------- + + Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: .1, - // targetRotation: this.getSuperSpinHandRotation(handRotation), - targetRotation: handRotation, - angularTimeScale: .1 + targetPosition: this.currentObjectPosition, linearTimeScale: .1, + targetRotation: this.currentObjectRotation, angularTimeScale: .1 }); } } From bdae3e420b21e80dc71c9ebe121b2dbe6717d9c0 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 11:24:04 -0700 Subject: [PATCH 25/35] fix crash in AC --- assignment-client/src/Agent.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ddbe164884..fda226b934 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -41,9 +41,6 @@ Agent::Agent(NLPacket& packet) : DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false)) { - // be the parent of the script engine so it gets moved when we do - _scriptEngine->setParent(this); - DependencyManager::get()->setPacketSender(&_entityEditSender); DependencyManager::set(); @@ -157,6 +154,7 @@ void Agent::run() { qDebug() << "Downloaded script:" << scriptContents; _scriptEngine = new ScriptEngine(scriptContents, _payload); + _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do // setup an Avatar for the script to use ScriptableAvatar scriptedAvatar(_scriptEngine); @@ -255,7 +253,6 @@ void Agent::sendAvatarBillboardPacket() { void Agent::processAgentAvatarAndAudio(float deltaTime) { - qDebug() << "processAgentAvatarAndAudio()"; if (!_scriptEngine->isFinished() && _isAvatar && _avatarData) { const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) From e650e4038836d6aaec70be3e4ba742e77beab75d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Sep 2015 11:32:56 -0700 Subject: [PATCH 26/35] try harder to have line rezzed when it's needed. don't ignore entities that are too close when doing line intersection --- examples/controllers/handControllerGrab.js | 43 +++++++++++++--------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 8fcc7d7bb9..82fd847f25 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -91,24 +91,34 @@ function controller(side, triggerAction, pullAction, hand) { this.prevTriggerValue = 0; this.palm = 2 * side; this.tip = 2 * side + 1; - this.lineCreationTime = Date.now(); - this.pointer = Entities.addEntity({ - type: "Line", - name: "pointer", - color: NO_INTERSECT_COLOR, - dimensions: { - x: 1000, - y: 1000, - z: 1000 - }, - visible: false, - lifetime: LIFETIME - }); - + this.pointer = null; } controller.prototype.updateLine = function() { + if (this.pointer != null) { + if (Entities.getEntityProperties(this.pointer).id != this.poitner) { + this.pointer == null; + } + } + + if (this.pointer == null) { + this.lineCreationTime = Date.now(); + this.pointer = Entities.addEntity({ + type: "Line", + name: "pointer", + color: NO_INTERSECT_COLOR, + dimensions: { + x: 1000, + y: 1000, + z: 1000 + }, + visible: true, + lifetime: LIFETIME + }); + } + + var handPosition = this.getHandPosition(); var direction = Quat.getUp(this.getHandRotation()); @@ -129,10 +139,7 @@ controller.prototype.updateLine = function() { lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME }); - - //move origin a bit away from hand so nothing gets in way - var origin = Vec3.sum(handPosition, direction); - if (this.checkForIntersections(origin, direction)) { + if (this.checkForIntersections(handPosition, direction)) { Entities.editEntity(this.pointer, { color: INTERSECT_COLOR, }); From 41caa36038084d53d5be0b28b572c9b2c73307c6 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 11:50:10 -0700 Subject: [PATCH 27/35] removed some debug code --- libraries/script-engine/src/ScriptEngine.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 19e4a8f443..288e60edf0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -118,14 +118,8 @@ ScriptEngine::~ScriptEngine() { } } -class MyWorkerThread : public QThread { -public: - MyWorkerThread(QObject* parent = nullptr) : QThread(parent) { qDebug() << "MyWorkerThread::MyWorkerThread() this:" << this; } - ~MyWorkerThread() { qDebug() << "MyWorkerThread::~MyWorkerThread() this:" << this; } -}; - void ScriptEngine::runInThread() { - QThread* workerThread = new MyWorkerThread(); // thread is owned but ScriptEngine, so if the ScriptEngine is destroyed, the thread will be too. + QThread* workerThread = new QThread(); // thread is owned but ScriptEngine, so if the ScriptEngine is destroyed, the thread will be too. QString scriptEngineName = QString("Script Thread:") + getFilename(); workerThread->setObjectName(scriptEngineName); @@ -659,12 +653,10 @@ void ScriptEngine::run() { _isRunning = false; if (_wantSignals) { emit runningStateChanged(); - qDebug() << "ScriptEngine::run().... about to emit doneRunning().... this:" << this << "my thread:" << thread() << "current thread:" << QThread::currentThread(); emit doneRunning(); } _doneRunningThisScript = true; - qDebug() << "ScriptEngine::run().... END OF RUN.... this:" << this << "my thread:" << thread() << "current thread:" << QThread::currentThread(); } // NOTE: This is private because it must be called on the same thread that created the timers, which is why From 016a5e5f0a0607ead6a7c4ada9be4c2f600a2240 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 11:52:07 -0700 Subject: [PATCH 28/35] cleanup comments --- libraries/script-engine/src/ScriptEngine.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 288e60edf0..a7136edd7b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -107,7 +107,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam } ScriptEngine::~ScriptEngine() { - qDebug() << "ScriptEngine::~ScriptEngine().... this:" << this << "my thread:" << thread(); // If we're not already in the middle of stopping all scripts, then we should remove ourselves // from the list of running scripts. We don't do this if we're in the process of stopping all scripts // because that method removes scripts from its list as it iterates them @@ -119,7 +118,7 @@ ScriptEngine::~ScriptEngine() { } void ScriptEngine::runInThread() { - QThread* workerThread = new QThread(); // thread is owned but ScriptEngine, so if the ScriptEngine is destroyed, the thread will be too. + QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete QString scriptEngineName = QString("Script Thread:") + getFilename(); workerThread->setObjectName(scriptEngineName); @@ -132,7 +131,7 @@ void ScriptEngine::runInThread() { // when the thread is finished, add thread to the deleteLater queue connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); - // when the thread is destroyed, add scriptEngine to the deleteLater queue + // when the thread is finished, add scriptEngine to the deleteLater queue connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater); moveToThread(workerThread); From 1948829ca8eb66f9851644064de0b3c5208b070c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 15 Sep 2015 15:09:08 -0700 Subject: [PATCH 29/35] Improved state machine for right hand. The hand state machine has the following features * There's a idle to point animation, followed by a looping point hold state. * There's a point to idle animation. * The grab state is composed of a linear blend between an open and closed pose. Additionally the C++ code will ramp on the left and right hand overlays, This allows the fingers to be animated normally when the user is not actively pointing or grabbing. --- interface/src/avatar/MyAvatar.cpp | 7 +- interface/src/avatar/SkeletonModel.cpp | 4 +- libraries/animation/src/Rig.cpp | 70 ++++++------- libraries/animation/src/Rig.h | 6 +- tests/animation/src/data/avatar.json | 138 ++++++++++++++++++------- 5 files changed, 144 insertions(+), 81 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 91e7ba191f..da2e78c5a3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1296,10 +1296,13 @@ void MyAvatar::initAnimGraph() { // ik-avatar.json // https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060 // + // ik-avatar-hands.json + // https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb + // // or run a local web-server // python -m SimpleHTTPServer& - auto graphUrl = QUrl("http://localhost:8000/avatar.json"); - //auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/8f824da2908fd89ad1befadd1d8f5d7b3b6efa66/ik-avatar.json"); + //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); + auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb/raw/883c7ce8e75ad3f72a0d513c317fe4b74a41c3b8/ik-avatar-hands.json"); _rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 91222ad30e..856bacbf5d 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -146,7 +146,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.leftEyeJointIndex = geometry.leftEyeJointIndex; headParams.rightEyeJointIndex = geometry.rightEyeJointIndex; - _rig->updateFromHeadParameters(headParams); + _rig->updateFromHeadParameters(headParams, deltaTime); Rig::HandParameters handParams; @@ -171,7 +171,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { handParams.isRightEnabled = false; } - _rig->updateFromHandParameters(handParams); + _rig->updateFromHandParameters(handParams, deltaTime); } else { // This is a little more work than we really want. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6baae8fa3e..076baf92c5 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -952,7 +952,7 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { return _jointStates[jointIndex].getDefaultRotationInParentFrame(); } -void Rig::updateFromHeadParameters(const HeadParameters& params) { +void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { if (params.enableLean) { updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); } @@ -1048,59 +1048,49 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } } -void Rig::updateFromHandParameters(const HandParameters& params) { +void Rig::updateFromHandParameters(const HandParameters& params, float dt) { if (_enableAnimGraph && _animSkeleton) { - // AJT: TODO rotate these into the correct coordinate space - /* - if (params.isLeftEnabled) { - auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; - _animVars.set("leftHandPosition", params.leftPosition + rootTrans); - _animVars.set("leftHandRotation", params.leftOrientation); - } else { - _animVars.unset("leftHandPosition"); - _animVars.unset("leftHandRotation"); - } - - if (params.isRightEnabled) { - auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans; - _animVars.set("rightHandPosition", params.rightPosition + rootTrans); - _animVars.set("rightHandRotation", params.rightOrientation); - } else { - _animVars.unset("rightHandPosition"); - _animVars.unset("rightHandRotation"); - } - */ - - // AJT: REMOVE for grab/point debugging. + // set leftHand grab vars _animVars.set("isLeftHandIdle", false); _animVars.set("isLeftHandPoint", false); - _animVars.set("isLeftHandClose", false); - if (params.leftTrigger > 0.3333f) { - if (params.leftTrigger > 0.6666f) { - _animVars.set("isLeftHandClose", true); - } else { - _animVars.set("isLeftHandPoint", true); - } + _animVars.set("isLeftHandGrab", false); + + // Split the trigger range into three zones. + bool rampOut = false; + if (params.leftTrigger > 0.6666f) { + _animVars.set("isLeftHandGrab", true); + } else if (params.leftTrigger > 0.3333f) { + _animVars.set("isLeftHandPoint", true); } else { _animVars.set("isLeftHandIdle", true); + rampOut = true; } + const float OVERLAY_RAMP_OUT_SPEED = 6.0f; // ramp in and out over 1/6th of a sec + _leftHandOverlayAlpha = glm::clamp(_leftHandOverlayAlpha + (rampOut ? -1.0f : 1.0f) * OVERLAY_RAMP_OUT_SPEED * dt, 0.0f, 1.0f); + _animVars.set("leftHandOverlayAlpha", _leftHandOverlayAlpha); + _animVars.set("leftHandGrabBlend", params.leftTrigger); - // AJT: REMOVE for grab/point debugging. + + // set leftHand grab vars _animVars.set("isRightHandIdle", false); _animVars.set("isRightHandPoint", false); - _animVars.set("isRightHandClose", false); - if (params.rightTrigger > 0.3333f) { - if (params.rightTrigger > 0.6666f) { - _animVars.set("isRightHandClose", true); - } else { - _animVars.set("isRightHandPoint", true); - } + _animVars.set("isRightHandGrab", false); + + // Split the trigger range into three zones + rampOut = false; + if (params.rightTrigger > 0.6666f) { + _animVars.set("isRightHandGrab", true); + } else if (params.rightTrigger > 0.3333f) { + _animVars.set("isRightHandPoint", true); } else { _animVars.set("isRightHandIdle", true); + rampOut = true; } - + _rightHandOverlayAlpha = glm::clamp(_rightHandOverlayAlpha + (rampOut ? -1.0f : 1.0f) * OVERLAY_RAMP_OUT_SPEED * dt, 0.0f, 1.0f); + _animVars.set("rightHandOverlayAlpha", _rightHandOverlayAlpha); + _animVars.set("rightHandGrabBlend", params.rightTrigger); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index f57b446417..9939f383b7 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -179,11 +179,11 @@ public: void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; } bool getEnableAnimGraph() const { return _enableAnimGraph; } - void updateFromHeadParameters(const HeadParameters& params); + void updateFromHeadParameters(const HeadParameters& params, float dt); void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade = glm::vec3(0.0f)); - void updateFromHandParameters(const HandParameters& params); + void updateFromHandParameters(const HandParameters& params, float dt); virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, float scale, float priority) = 0; @@ -228,6 +228,8 @@ public: Move }; RigRole _state = RigRole::Idle; + float _leftHandOverlayAlpha = 0.0f; + float _rightHandOverlayAlpha = 0.0f; }; #endif /* defined(__hifi__Rig__) */ diff --git a/tests/animation/src/data/avatar.json b/tests/animation/src/data/avatar.json index 1348824ba0..72650893d7 100644 --- a/tests/animation/src/data/avatar.json +++ b/tests/animation/src/data/avatar.json @@ -56,7 +56,8 @@ "type": "overlay", "data": { "alpha": 1.0, - "boneSet": "rightHand" + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, "children": [ { @@ -67,29 +68,49 @@ "states": [ { "id": "rightHandIdle", - "interpTarget": 6, - "interpDuration": 6, + "interpTarget": 3, + "interpDuration": 3, "transitions": [ - { "var": "isRightHandPoint", "state": "rightHandPoint" }, - { "var": "isRightHandClose", "state": "rightHandClose" } + { "var": "isRightHandPoint", "state": "rightHandPointIntro" }, + { "var": "isRightHandGrab", "state": "rightHandGrab" } ] }, { - "id": "rightHandPoint", - "interpTarget": 6, - "interpDuration": 6, + "id": "rightHandPointIntro", + "interpTarget": 3, + "interpDuration": 3, "transitions": [ { "var": "isRightHandIdle", "state": "rightHandIdle" }, - { "var": "isRightHandClose", "state": "rightHandClose" } + { "var": "isRightHandPointIntroOnDone", "state": "rightHandPointHold" }, + { "var": "isRightHandGrab", "state": "rightHandGrab" } ] }, { - "id": "rightHandClose", - "interpTarget": 6, - "interpDuration": 6, + "id": "rightHandPointHold", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandIdle", "state": "rightHandPointOutro" }, + { "var": "isRightHandGrab", "state": "rightHandGrab" } + ] + }, + { + "id": "rightHandPointOutro", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandPointOutroOnDone", "state": "rightHandIdle" }, + { "var": "isRightHandGrab", "state": "rightHandGrab" }, + { "var": "isRightHandPoint", "state": "rightHandPointHold" } + ] + }, + { + "id": "rightHandGrab", + "interpTarget": 3, + "interpDuration": 3, "transitions": [ { "var": "isRightHandIdle", "state": "rightHandIdle" }, - { "var": "isRightHandPoint", "state": "rightHandPoint" } + { "var": "isRightHandPoint_DISABLED", "state": "rightHandPointHold" } ] } ] @@ -99,19 +120,7 @@ "id": "rightHandIdle", "type": "clip", "data": { - "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", - "startFrame": 30.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandPoint", - "type": "clip", - "data": { - "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx", "startFrame": 0.0, "endFrame": 0.0, "timeScale": 1.0, @@ -120,16 +129,74 @@ "children": [] }, { - "id": "rightHandClose", + "id": "rightHandPointHold", "type": "clip", "data": { - "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", - "startFrame": 15.0, - "endFrame": 15.0, + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx", + "startFrame": 12.0, + "endFrame": 12.0, "timeScale": 1.0, "loopFlag": true }, "children": [] + }, + { + "id": "rightHandPointIntro", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx", + "startFrame": 0.0, + "endFrame": 12.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "rightHandPointOutro", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx", + "startFrame": 0.0, + "endFrame": 65.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "rightHandGrab", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGrabBlend" + }, + "children": [ + { + "id": "rightHandOpen", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandClose", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] }, @@ -138,7 +205,8 @@ "type": "overlay", "data": { "alpha": 1.0, - "boneSet": "leftHand" + "boneSet": "leftHand", + "alphaVar" : "leftHandOverlay" }, "children": [ { @@ -153,7 +221,7 @@ "interpDuration": 6, "transitions": [ { "var": "isLeftHandPoint", "state": "leftHandPoint" }, - { "var": "isLeftHandClose", "state": "leftHandClose" } + { "var": "isLeftHandGrab", "state": "leftHandGrab" } ] }, { @@ -162,11 +230,11 @@ "interpDuration": 6, "transitions": [ { "var": "isLeftHandIdle", "state": "leftHandIdle" }, - { "var": "isLeftHandClose", "state": "leftHandClose" } + { "var": "isLeftHandGrab", "state": "leftHandGrab" } ] }, { - "id": "leftHandClose", + "id": "leftHandGrab", "interpTarget": 6, "interpDuration": 6, "transitions": [ @@ -202,7 +270,7 @@ "children": [] }, { - "id": "leftHandClose", + "id": "leftHandGrab", "type": "clip", "data": { "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx", From 8454cb916c4ec8a25f39fff85930d13a159da84b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 15 Sep 2015 15:15:26 -0700 Subject: [PATCH 30/35] avoid branching by using %= operator --- libraries/networking/src/udt/PacketTimeWindow.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp index 915810b93e..0c95d21bc6 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.cpp +++ b/libraries/networking/src/udt/PacketTimeWindow.cpp @@ -102,9 +102,7 @@ void PacketTimeWindow::onPacketArrival() { _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); // reset the currentPacketInterval index when it wraps - if (_currentPacketInterval == _numPacketIntervals) { - _currentPacketInterval = 0; - } + _currentPacketInterval %= _numPacketIntervals; } // remember this as the last packet arrival time @@ -123,7 +121,5 @@ void PacketTimeWindow::onProbePair2Arrival() { _probeIntervals[_currentProbeInterval++] = duration_cast(now - _firstProbeTime).count(); // reset the currentProbeInterval index when it wraps - if (_currentProbeInterval == _numProbeIntervals) { - _currentProbeInterval = 0; - } + _currentProbeInterval %= _numProbeIntervals; } From f13c839f4a5b5eb7a47058d3d9de4f5a2435af58 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 15 Sep 2015 15:19:11 -0700 Subject: [PATCH 31/35] Removed comment --- interface/src/avatar/SkeletonModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 856bacbf5d..3894a0ade9 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -150,7 +150,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::HandParameters handParams; - // this is sooo getto const PalmData* leftPalm = getPalmWithIndex(myAvatar->getHand(), LEFT_HAND_INDEX); if (leftPalm && leftPalm->isActive()) { handParams.isLeftEnabled = true; From 22b979a663e735ecb15700c3afbb510c49e94e6b Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 16:02:22 -0700 Subject: [PATCH 32/35] couple of entity server corner cases --- assignment-client/src/octree/OctreeServer.cpp | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 0368e8a408..2c9e835fa7 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -906,10 +906,14 @@ void OctreeServer::readConfiguration() { domainHandler.requestDomainSettings(); loop.exec(); + qDebug() << "Got domain settings from domain-server."; + if (domainHandler.getSettingsObject().isEmpty()) { qDebug() << "No settings object from domain-server."; } - const QJsonObject& settingsObject = domainHandler.getSettingsObject(); + QJsonObject settingsObject { domainHandler.getSettingsObject() }; + + QString settingsKey = getMyDomainSettingsKey(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); _settings = settingsSectionObject; // keep this for later @@ -1333,19 +1337,22 @@ void OctreeServer::sendStatsPacket() { QJsonObject statsObject2; statsObject2["data"] = dataObject1; statsObject2["timing"] = timingArray1; - - // Stats Object 3 + QJsonObject dataArray2; - dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount(); - dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed(); - dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed(); - QJsonObject timingArray2; - timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket(); - timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket(); - timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket(); - timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement(); - timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); + + // Stats Object 3 + if (_octreeInboundPacketProcessor) { + dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount(); + dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed(); + dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed(); + + timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket(); + timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket(); + timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket(); + timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement(); + timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); + } QJsonObject statsObject3; statsObject3["data"] = dataArray2; From 4a24a3767b4dba69c2376f498e32832438009f1f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 16:17:05 -0700 Subject: [PATCH 33/35] couple of entity server corner cases --- assignment-client/src/octree/OctreeServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 2c9e835fa7..979f9f6661 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -908,11 +908,11 @@ void OctreeServer::readConfiguration() { qDebug() << "Got domain settings from domain-server."; - if (domainHandler.getSettingsObject().isEmpty()) { - qDebug() << "No settings object from domain-server."; - } QJsonObject settingsObject { domainHandler.getSettingsObject() }; + if (settingsObject.isEmpty()) { + qDebug() << "No settings object from domain-server."; + } QString settingsKey = getMyDomainSettingsKey(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); From 0aed08a0069e9c568e54e538381f4e57a96aa606 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 16:52:15 -0700 Subject: [PATCH 34/35] more robust handling of readConfiguration --- .../src/entities/EntityServer.cpp | 4 +++- assignment-client/src/entities/EntityServer.h | 2 +- .../src/octree/OctreeQueryNode.cpp | 3 ++- assignment-client/src/octree/OctreeServer.cpp | 19 ++++++++++--------- assignment-client/src/octree/OctreeServer.h | 4 ++-- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index bde53e4a87..bdd5728246 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -142,7 +142,7 @@ void EntityServer::pruneDeletedEntities() { } } -void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { +bool EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { bool wantEditLogging = false; readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging); qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging)); @@ -150,4 +150,6 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio EntityTreePointer tree = std::static_pointer_cast(_tree); tree->setWantEditLogging(wantEditLogging); + + return true; } diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index d2be75d05e..114e0e1726 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -41,7 +41,7 @@ public: virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent); virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode); - virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject); + virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override; public slots: void pruneDeletedEntities(); diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index c68fb4e1b5..f70ff62f91 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -242,7 +242,8 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { if (0.0f != getCameraAspectRatio() && 0.0f != getCameraNearClip() && - 0.0f != getCameraFarClip()) { + 0.0f != getCameraFarClip() && + getCameraNearClip() != getCameraFarClip()) { newestViewFrustum.setProjection(glm::perspective( glm::radians(wideFOV), // hack getCameraAspectRatio(), diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 979f9f6661..ee0403d57b 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -887,7 +887,7 @@ bool OctreeServer::readOptionString(const QString& optionName, const QJsonObject return optionAvailable; } -void OctreeServer::readConfiguration() { +bool OctreeServer::readConfiguration() { // if the assignment had a payload, read and parse that if (getPayload().size() > 0) { parsePayload(); @@ -906,14 +906,13 @@ void OctreeServer::readConfiguration() { domainHandler.requestDomainSettings(); loop.exec(); - qDebug() << "Got domain settings from domain-server."; - - QJsonObject settingsObject { domainHandler.getSettingsObject() }; - - if (settingsObject.isEmpty()) { - qDebug() << "No settings object from domain-server."; + if (domainHandler.getSettingsObject().isEmpty()) { + qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; + setFinished(true); + return false; } + const QJsonObject& settingsObject = domainHandler.getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); _settings = settingsSectionObject; // keep this for later @@ -1021,7 +1020,7 @@ void OctreeServer::readConfiguration() { packetsPerSecondTotalMax, _packetsTotalPerInterval); - readAdditionalConfiguration(settingsSectionObject); + return readAdditionalConfiguration(settingsSectionObject); } void OctreeServer::run() { @@ -1047,7 +1046,9 @@ void OctreeServer::run() { commonInit(getMyLoggingServerTargetName(), getMyNodeType()); // read the configuration from either the payload or the domain server configuration - readConfiguration(); + if (!readConfiguration()) { + return; // bailing on run, because readConfiguration failed + } beforeRun(); // after payload has been processed diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 64903ee558..b8e4a5c261 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -135,8 +135,8 @@ protected: bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result); bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result); bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result); - void readConfiguration(); - virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { }; + bool readConfiguration(); + virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { return true; }; void parsePayload(); void initHTTPManager(int port); void resetSendingStats(); From 54972515dc65542f57573da9881fca6088021f8d Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 15 Sep 2015 17:04:40 -0700 Subject: [PATCH 35/35] make sure timer is owned --- libraries/networking/src/ThreadedAssignment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index a5df256e29..aee2805f32 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -65,7 +65,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy auto nodeList = DependencyManager::get(); nodeList->setOwnerType(nodeType); - _domainServerTimer = new QTimer(); + _domainServerTimer = new QTimer(this); connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);