diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index abb94f95e3c..345118a89b0 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME assignment-client) -setup_hifi_project(Core Gui Network Script Quick WebSockets) +setup_hifi_project(Core Gui Network Quick WebSockets) # Fix up the rpath so macdeployqt works if (APPLE) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4b9b2d5095f..121276efcc1 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -56,7 +59,7 @@ #include "entities/AssignmentParentFinder.h" #include "AssignmentDynamicFactory.h" -#include "RecordingScriptingInterface.h" +#include #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" @@ -180,7 +183,7 @@ static const QString AGENT_LOGGING_NAME = "agent"; void Agent::run() { // Create ScriptEngines on threaded-assignment thread then move to main thread. - DependencyManager::set(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread()); + DependencyManager::set(ScriptManager::AGENT_SCRIPT)->moveToThread(qApp->thread()); DependencyManager::set(); @@ -372,7 +375,7 @@ void Agent::executeScript() { // the following block is scoped so that any shared pointers we take here // are cleared before we call setFinished at the end of the function { - _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); + _scriptManager = scriptManagerFactory(ScriptManager::AGENT_SCRIPT, _scriptContents, _payload); // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); @@ -386,10 +389,11 @@ void Agent::executeScript() { scriptedAvatar->getHeadOrientation(); // give this AvatarData object to the script engine - _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); + auto scriptEngine = _scriptManager->engine(); + scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); // give scripts access to the Users object - _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); auto player = DependencyManager::get(); connect(player.data(), &recording::Deck::playbackStateChanged, [&player, &scriptedAvatar] { @@ -493,26 +497,26 @@ void Agent::executeScript() { }); auto avatarHashMap = DependencyManager::set(); - _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); + scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); + scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); - _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); - _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); - _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + ScriptValue webSocketServerConstructorValue = scriptEngine->newFunction(WebSocketServerClass::constructor); + scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); auto entityScriptingInterface = DependencyManager::get(); - _scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); + scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer); - _scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); auto recordingInterface = DependencyManager::get(); - _scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); + scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); entityScriptingInterface->init(); @@ -522,8 +526,8 @@ void Agent::executeScript() { DependencyManager::set(_entityViewer.getTree()); - DependencyManager::get()->runScriptInitializers(_scriptEngine); - _scriptEngine->run(); + DependencyManager::get()->runScriptInitializers(_scriptManager); + _scriptManager->run(); Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE); @@ -602,7 +606,7 @@ void Agent::setIsAvatar(bool isAvatar) { // start the timer _avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS); - connect(_scriptEngine.data(), &ScriptEngine::update, + connect(_scriptManager.get(), &ScriptManager::update, scriptableAvatar.data(), &ScriptableAvatar::update, Qt::QueuedConnection); // tell the avatarAudioTimer to start ticking @@ -638,7 +642,7 @@ void Agent::setIsAvatar(bool isAvatar) { nodeList->sendPacket(std::move(packet), *node); }); - disconnect(_scriptEngine.data(), &ScriptEngine::update, + disconnect(_scriptManager.get(), &ScriptManager::update, scriptableAvatar.data(), &ScriptableAvatar::update); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); @@ -875,7 +879,7 @@ void Agent::aboutToFinish() { // drop our shared pointer to the script engine, then ask ScriptEngines to shutdown scripting // this ensures that the ScriptEngine goes down before ScriptEngines - _scriptEngine.clear(); + _scriptManager.reset(); { DependencyManager::get()->shutdownScripting(); @@ -895,8 +899,8 @@ void Agent::aboutToFinish() { } void Agent::stop() { - if (_scriptEngine) { - _scriptEngine->stop(); + if (_scriptManager) { + _scriptManager->stop(); } else { setFinished(true); } diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index eb58e32897c..efed440a8ae 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -15,12 +15,12 @@ #include #include -#include #include #include #include #include #include +#include #include #include @@ -29,11 +29,17 @@ #include +#include #include "AudioGate.h" #include "MixedAudioStream.h" #include "entities/EntityTreeHeadlessViewer.h" #include "avatars/ScriptableAvatar.h" +class ScriptEngine; +class ScriptManager; +using ScriptEnginePointer = std::shared_ptr; +using ScriptManagerPointer = std::shared_ptr; + class Agent : public ThreadedAssignment { Q_OBJECT @@ -90,7 +96,7 @@ private slots: void encodeFrameOfZeros(QByteArray& encodedZeros); void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer); - ScriptEnginePointer _scriptEngine; + ScriptManagerPointer _scriptManager; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 752eaf81d28..15c9fce788d 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -17,16 +17,18 @@ #include #include +#include #include #include #include #include #include #include +#include #include -ScriptableAvatar::ScriptableAvatar() { +ScriptableAvatar::ScriptableAvatar(): _scriptEngine(newScriptEngine()) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); } @@ -311,7 +313,7 @@ AvatarEntityMap ScriptableAvatar::getAvatarEntityDataInternal(bool allProperties EntityItemProperties properties = entity->getProperties(desiredProperties); QByteArray blob; - EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob, allProperties); + EntityItemProperties::propertiesToBlob(*_scriptEngine, sessionID, properties, blob, allProperties); data[id] = blob; } }); @@ -335,7 +337,7 @@ void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityDa while (dataItr != avatarEntityData.end()) { EntityItemProperties properties; const QByteArray& blob = dataItr.value(); - if (!blob.isNull() && EntityItemProperties::blobToProperties(_scriptEngine, blob, properties)) { + if (!blob.isNull() && EntityItemProperties::blobToProperties(*_scriptEngine, blob, properties)) { newProperties[dataItr.key()] = properties; } ++dataItr; @@ -415,7 +417,7 @@ void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArra EntityItemPointer entity; EntityItemProperties properties; - if (!EntityItemProperties::blobToProperties(_scriptEngine, entityData, properties)) { + if (!EntityItemProperties::blobToProperties(*_scriptEngine, entityData, properties)) { // entityData is corrupt return; } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 8e58108e8cb..f6c721a3246 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -220,7 +220,7 @@ public slots: QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal QUrl _skeletonFBXURL; - mutable QScriptEngine _scriptEngine; + mutable ScriptEnginePointer _scriptEngine; std::map _entities; /// Loads the joint indices, names from the FST file (if any) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 4ff920516af..3b8144164f9 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -14,7 +14,9 @@ #include #include +#include #include +#include #include #include #include @@ -130,7 +132,7 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointerwritePrimitive(messageID); EntityScriptDetails details; - if (_entitiesScriptEngine->getEntityScriptDetails(entityID, details)) { + if (_entitiesScriptManager->getEntityScriptDetails(entityID, details)) { replyPacketList->writePrimitive(true); replyPacketList->writePrimitive(details.status); replyPacketList->writeString(details.errorInfo); @@ -175,7 +177,7 @@ void EntityScriptServer::handleSettings() { } void EntityScriptServer::updateEntityPPS() { - int numRunningScripts = _entitiesScriptEngine->getNumRunningEntityScripts(); + int numRunningScripts = _entitiesScriptManager->getNumRunningEntityScripts(); int pps; if (std::numeric_limits::max() / _entityPPSPerScript < numRunningScripts) { qWarning() << QString("Integer multiplication would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); @@ -236,7 +238,7 @@ void EntityScriptServer::pushLogs() { void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode) { - if (_entitiesScriptEngine && _entityViewer.getTree() && !_shuttingDown) { + if (_entitiesScriptManager && _entityViewer.getTree() && !_shuttingDown) { auto entityID = QUuid::fromRfc4122(receivedMessage->read(NUM_BYTES_RFC4122_UUID)); auto method = receivedMessage->readString(); @@ -250,13 +252,13 @@ void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointercallEntityScriptMethod(entityID, method, params, senderNode->getUUID()); + _entitiesScriptManager->callEntityScriptMethod(entityID, method, params, senderNode->getUUID()); } } void EntityScriptServer::run() { - DependencyManager::set(ScriptEngine::ENTITY_SERVER_SCRIPT); + DependencyManager::set(ScriptManager::ENTITY_SERVER_SCRIPT); DependencyManager::set(); DependencyManager::set(); @@ -446,7 +448,8 @@ void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) { void EntityScriptServer::resetEntitiesScriptEngine() { auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount); - auto newEngine = scriptEngineFactory(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName); + auto newManager = scriptManagerFactory(ScriptManager::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName); + auto newEngine = newManager->engine(); auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor); newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); @@ -456,42 +459,42 @@ void EntityScriptServer::resetEntitiesScriptEngine() { // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get().data(); - connect(newEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); - connect(newEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); - connect(newEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); - connect(newEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); + connect(newManager.get(), &ScriptManager::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); + connect(newManager.get(), &ScriptManager::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); + connect(newManager.get(), &ScriptManager::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); + connect(newManager.get(), &ScriptManager::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); - connect(newEngine.data(), &ScriptEngine::update, this, [this] { + connect(newManager.get(), &ScriptManager::update, this, [this] { _entityViewer.queryOctree(); _entityViewer.getTree()->preUpdate(); _entityViewer.getTree()->update(); }); - scriptEngines->runScriptInitializers(newEngine); - newEngine->runInThread(); - auto newEngineSP = qSharedPointerCast(newEngine); + scriptEngines->runScriptInitializers(newManager); + newManager->runInThread(); + std::shared_ptr newEngineSP = newManager; // On the entity script server, these are the same DependencyManager::get()->setPersistentEntitiesScriptEngine(newEngineSP); DependencyManager::get()->setNonPersistentEntitiesScriptEngine(newEngineSP); - if (_entitiesScriptEngine) { - disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + if (_entitiesScriptManager) { + disconnect(_entitiesScriptManager.get(), &ScriptManager::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); } - _entitiesScriptEngine.swap(newEngine); - connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + _entitiesScriptManager.swap(newManager); + connect(_entitiesScriptManager.get(), &ScriptManager::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); } void EntityScriptServer::clear() { // unload and stop the engine - if (_entitiesScriptEngine) { + if (_entitiesScriptManager) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread - _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(); - _entitiesScriptEngine->waitTillDoneRunning(); + _entitiesScriptManager->unloadAllEntityScripts(); + _entitiesScriptManager->stop(); + _entitiesScriptManager->waitTillDoneRunning(); } _entityViewer.clear(); @@ -503,8 +506,8 @@ void EntityScriptServer::clear() { } void EntityScriptServer::shutdownScriptEngine() { - if (_entitiesScriptEngine) { - _entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential + if (_entitiesScriptManager) { + _entitiesScriptManager->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential } _shuttingDown = true; @@ -513,7 +516,7 @@ void EntityScriptServer::shutdownScriptEngine() { auto scriptEngines = DependencyManager::get(); scriptEngines->shutdownScripting(); - _entitiesScriptEngine.clear(); + _entitiesScriptManager.reset(); auto entityScriptingInterface = DependencyManager::get(); // our entity tree is going to go away so tell that to the EntityScriptingInterface @@ -531,8 +534,8 @@ void EntityScriptServer::addingEntity(const EntityItemID& entityID) { } void EntityScriptServer::deletingEntity(const EntityItemID& entityID) { - if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID, true); + if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptManager) { + _entitiesScriptManager->unloadEntityScript(entityID, true); } } @@ -543,20 +546,20 @@ void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID } void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload) { - if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { + if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptManager) { EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID); EntityScriptDetails details; - bool isRunning = _entitiesScriptEngine->getEntityScriptDetails(entityID, details); + bool isRunning = _entitiesScriptManager->getEntityScriptDetails(entityID, details); if (entity && (forceRedownload || !isRunning || details.scriptText != entity->getServerScripts())) { if (isRunning) { - _entitiesScriptEngine->unloadEntityScript(entityID, true); + _entitiesScriptManager->unloadEntityScript(entityID, true); } QString scriptUrl = entity->getServerScripts(); if (!scriptUrl.isEmpty()) { scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); - _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, forceRedownload); + _entitiesScriptManager->loadEntityScript(entityID, scriptUrl, forceRedownload); } } } @@ -573,9 +576,9 @@ void EntityScriptServer::sendStatsPacket() { QJsonObject scriptEngineStats; int numberRunningScripts = 0; - const auto scriptEngine = _entitiesScriptEngine; - if (scriptEngine) { - numberRunningScripts = scriptEngine->getNumRunningEntityScripts(); + const auto scriptManager = _entitiesScriptManager; + if (scriptManager) { + numberRunningScripts = scriptManager->getNumRunningEntityScripts(); } scriptEngineStats["number_running_scripts"] = numberRunningScripts; statsObject["script_engine_stats"] = scriptEngineStats; diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index b7929eb5af3..71d6340fae0 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -18,12 +18,14 @@ #include #include #include +#include #include #include -#include #include #include +#include + #include "../entities/EntityTreeHeadlessViewer.h" class EntityScriptServer : public ThreadedAssignment { @@ -76,7 +78,7 @@ private slots: bool _shuttingDown { false }; static int _entitiesScriptEngineCount; - ScriptEnginePointer _entitiesScriptEngine; + ScriptManagerPointer _entitiesScriptManager; SimpleEntitySimulationPointer _entitySimulation; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index a3a85684b4d..6f123f23612 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -25,6 +25,7 @@ symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CU # link the shared hifi libraries include_hifi_library_headers(gpu) include_hifi_library_headers(graphics) +include_hifi_library_headers(script-engine) link_hifi_libraries(embedded-webserver networking shared avatars octree) target_zlib() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 0462ba22143..8a2b57484e1 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -21,7 +21,7 @@ set(CUSTOM_INTERFACE_QRC_PATHS "") find_package( Qt5 COMPONENTS - Gui Widgets Multimedia Network Qml Quick Script Svg + Gui Widgets Multimedia Network Qml Quick Svg ${PLATFORM_QT_COMPONENTS} WebChannel WebSockets ) @@ -217,7 +217,7 @@ link_hifi_libraries( shared workload task octree ktx gpu gl procedural graphics graphics-scripting render pointers recording hfm model-serializers networking material-networking model-networking model-baker entities avatars - audio audio-client animation script-engine physics + audio audio-client animation physics render-utils entities-renderer avatars-renderer ui qml auto-updater midi controllers plugins image platform ui-plugins display-plugins input-plugins @@ -227,6 +227,7 @@ link_hifi_libraries( ${PLATFORM_PLUGIN_LIBRARIES} shaders ) +include_hifi_library_headers(script-engine) # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") @@ -291,7 +292,7 @@ endif () target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::Widgets - Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::Qml Qt5::Quick Qt5::Svg Qt5::WebChannel ${PLATFORM_QT_LIBRARIES} ) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5fee0112e38..92f2d770f21 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -130,15 +130,19 @@ #include #include #include -#include +#include +#include #include #include #include #include #include #include -#include #include +#include +#include +#include +#include #include #include #include @@ -166,6 +170,7 @@ #include #include #include "recording/ClipCache.h" +#include #include "AudioClient.h" #include "audio/AudioScope.h" @@ -861,7 +866,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { #endif DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption); + DependencyManager::set(ScriptManager::CLIENT_SCRIPT, defaultScriptsOverrideOption); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1446,8 +1451,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo { auto scriptEngines = DependencyManager::get().data(); - scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine) { - registerScriptEngineWithApplicationServices(engine); + scriptEngines->registerScriptInitializer([this](ScriptManagerPointer manager) { + registerScriptEngineWithApplicationServices(manager); }); connect(scriptEngines, &ScriptEngines::scriptCountChanged, this, [this] { @@ -5900,7 +5905,7 @@ void Application::loadAvatarScripts(const QVector& urls) { if (index < 0) { auto scriptEnginePointer = scriptEngines->loadScript(url, false); if (scriptEnginePointer) { - scriptEnginePointer->setType(ScriptEngine::Type::AVATAR); + scriptEnginePointer->setType(ScriptManager::Type::AVATAR); } } } @@ -5911,7 +5916,7 @@ void Application::unloadAvatarScripts() { auto urls = scriptEngines->getRunningScripts(); for (auto url : urls) { auto scriptEngine = scriptEngines->getScriptEngine(url); - if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) { + if (scriptEngine->getType() == ScriptManager::Type::AVATAR) { scriptEngines->stopScript(url, false); } } @@ -7501,9 +7506,10 @@ void Application::addingEntityWithCertificate(const QString& certificateID, cons ledger->updateLocation(certificateID, placeName); } -void Application::registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) { +void Application::registerScriptEngineWithApplicationServices(const ScriptManagerPointer& scriptManager) { - scriptEngine->setEmitScriptUpdatesFunction([this]() { + auto scriptEngine = scriptManager->engine(); + scriptManager->setEmitScriptUpdatesFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); return !entityServerNode || isPhysicsEnabled(); }); @@ -7535,13 +7541,13 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); - connect(scriptEngine.data(), &ScriptEngine::finished, clipboardScriptable, &ClipboardScriptingInterface::deleteLater); + connect(scriptManager.get(), &ScriptManager::finished, clipboardScriptable, &ClipboardScriptingInterface::deleteLater); scriptEngine->registerGlobalObject("Overlays", &_overlays); - qScriptRegisterMetaType(scriptEngine.data(), RayToOverlayIntersectionResultToScriptValue, + scriptRegisterMetaType(scriptEngine.get(), RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); - bool clientScript = scriptEngine->isClientScript(); + bool clientScript = scriptManager->isClientScript(); #if !defined(DISABLE_QML) scriptEngine->registerGlobalObject("OffscreenFlags", getOffscreenUI()->getFlags()); @@ -7556,13 +7562,13 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine } #endif - qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - qScriptRegisterMetaType(scriptEngine.data(), + scriptRegisterMetaType(scriptEngine.get(), wrapperToScriptValue, wrapperFromScriptValue); + scriptRegisterMetaType(scriptEngine.get(), wrapperToScriptValue, wrapperFromScriptValue); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); - qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - qScriptRegisterMetaType(scriptEngine.data(), + scriptRegisterMetaType(scriptEngine.get(), wrapperToScriptValue, wrapperFromScriptValue); + scriptRegisterMetaType(scriptEngine.get(), wrapperToScriptValue, wrapperFromScriptValue); scriptEngine->registerGlobalObject("Tablet", DependencyManager::get().data()); // FIXME remove these deprecated names for the tablet scripting interface @@ -7613,12 +7619,12 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED scriptEngine->registerGlobalObject("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance()); - qScriptRegisterMetaType(scriptEngine.data(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); + scriptRegisterMetaType(scriptEngine.get(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data()); - qScriptRegisterMetaType(scriptEngine.data(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue); + scriptRegisterMetaType(scriptEngine.get(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue); scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface()); @@ -7633,7 +7639,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("Render", RenderScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Workload", _gameWorkload._engine->getConfiguration().get()); - GraphicsScriptingInterface::registerMetaTypes(scriptEngine.data()); + GraphicsScriptingInterface::registerMetaTypes(scriptEngine.get()); scriptEngine->registerGlobalObject("Graphics", DependencyManager::get().data()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); @@ -7645,11 +7651,11 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { - scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine.data(), steamClient.get())); + scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptManager.get(), steamClient.get())); } auto scriptingInterface = DependencyManager::get(); scriptEngine->registerGlobalObject("Controller", scriptingInterface.data()); - UserInputMapper::registerControllerTypes(scriptEngine.data()); + UserInputMapper::registerControllerTypes(scriptEngine.get()); auto recordingInterface = DependencyManager::get(); scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); @@ -7665,18 +7671,18 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); // Deprecated. scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get().data()); - registerInteractiveWindowMetaType(scriptEngine.data()); + registerInteractiveWindowMetaType(scriptEngine.get()); auto pickScriptingInterface = DependencyManager::get(); - pickScriptingInterface->registerMetaTypes(scriptEngine.data()); + pickScriptingInterface->registerMetaTypes(scriptEngine.get()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get().data(); - connect(scriptEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); - connect(scriptEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); - connect(scriptEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); - connect(scriptEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); - connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, scriptEngines, &ScriptEngines::onClearDebugWindow); + connect(scriptManager.get(), &ScriptManager::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); + connect(scriptManager.get(), &ScriptManager::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); + connect(scriptManager.get(), &ScriptManager::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); + connect(scriptManager.get(), &ScriptManager::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); + connect(scriptManager.get(), &ScriptManager::clearDebugWindow, scriptEngines, &ScriptEngines::onClearDebugWindow); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 215473ddfbc..50a31ebc7c6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -14,6 +14,7 @@ #define hifi_Application_h #include +#include #include #include @@ -21,11 +22,13 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -40,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -89,6 +91,8 @@ class MainWindow; class AssetUpload; class CompositorHelper; class AudioInjector; +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; namespace controller { class StateController; @@ -244,7 +248,7 @@ class Application : public QApplication, NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } - virtual void registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) override; + virtual void registerScriptEngineWithApplicationServices(const ScriptManagerPointer& scriptManager) override; virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { copyDisplayViewFrustum(viewOut); } virtual QThread* getMainThread() override { return thread(); } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index f5d7eadc4d0..35ac531c810 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -49,12 +49,12 @@ void addAvatarEntities(const QVariantList& avatarEntities) { EntitySimulationPointer entitySimulation = entityTree->getSimulation(); PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); - QScriptEngine scriptEngine; + ScriptEnginePointer scriptEngine = newScriptEngine(); for (int index = 0; index < avatarEntities.count(); index++) { const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap(); QVariant variantProperties = avatarEntityProperties["properties"]; QVariantMap asMap = variantProperties.toMap(); - QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); + ScriptValue scriptProperties = variantMapToScriptValue(asMap, *scriptEngine); EntityItemProperties entityProperties; EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, entityProperties); @@ -298,7 +298,7 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - QScriptEngine scriptEngine; + ScriptEnginePointer scriptEngine = newScriptEngine(); auto avatarEntities = myAvatar->getAvatarEntityDataNonDefault(); for (auto entityID : avatarEntities.keys()) { auto entity = entityTree->findEntityByID(entityID); @@ -318,7 +318,7 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { desiredProperties -= PROP_JOINT_TRANSLATIONS; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); - QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + ScriptValue scriptProperties = EntityItemPropertiesToScriptValue(scriptEngine.get(), entityProperties); avatarEntityData["properties"] = scriptProperties.toVariant(); wearableEntities.append(QVariant(avatarEntityData)); } diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 1c6ef387f3a..3acd4f47819 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -426,13 +426,14 @@ WorldDetailQuality LODManager::getWorldDetailQuality() const { return qApp->isHMDMode() ? _hmdWorldDetailQuality : _desktopWorldDetailQuality; } -QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality) { - return worldDetailQuality; +ScriptValue worldDetailQualityToScriptValue(ScriptEngine* engine, const WorldDetailQuality& worldDetailQuality) { + return engine->newValue(worldDetailQuality); } -void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality) { +bool worldDetailQualityFromScriptValue(const ScriptValue& object, WorldDetailQuality& worldDetailQuality) { worldDetailQuality = static_cast(std::min(std::max(object.toInt32(), (int)WORLD_DETAIL_LOW), (int)WORLD_DETAIL_HIGH)); + return true; } void LODManager::setLODQualityLevel(float quality) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 419ca9cddcc..afebe9d7747 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -22,7 +22,9 @@ #include #include #include +#include +class ScriptEngine; /*@jsdoc *

The world detail quality rendered.

@@ -380,7 +382,7 @@ class LODManager : public QObject, public Dependency { glm::vec4 _pidOutputs{ 0.0f }; }; -QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality); -void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality); +ScriptValue worldDetailQualityToScriptValue(ScriptEngine* engine, const WorldDetailQuality& worldDetailQuality); +bool worldDetailQualityFromScriptValue(const ScriptValue& object, WorldDetailQuality& worldDetailQuality); #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index aa116a9ce52..5d21896753c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include diff --git a/interface/src/PerformanceManager.cpp b/interface/src/PerformanceManager.cpp index 190071724a6..090c72d07e8 100644 --- a/interface/src/PerformanceManager.cpp +++ b/interface/src/PerformanceManager.cpp @@ -19,7 +19,11 @@ PerformanceManager::PerformanceManager() { - setPerformancePreset((PerformancePreset) _performancePresetSetting.get()); + static std::once_flag registry_flag; + std::call_once(registry_flag, [] { + qRegisterMetaType("PerformanceManager::PerformancePreset"); + }); + setPerformancePreset((PerformancePreset)_performancePresetSetting.get()); } void PerformanceManager::setupPerformancePresetSettings(bool evaluatePlatformTier) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 943845bda75..191770642bf 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -13,7 +13,8 @@ #include -#include +#include +#include #include "AvatarLogging.h" @@ -735,8 +736,8 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) } RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, - const QScriptValue& avatarIdsToInclude, - const QScriptValue& avatarIdsToDiscard, + const ScriptValue& avatarIdsToInclude, + const ScriptValue& avatarIdsToDiscard, bool pickAgainstMesh) { QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); @@ -980,7 +981,7 @@ float AvatarManager::getAvatarSortCoefficient(const QString& name) { } // HACK -void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptValue& value) { +void AvatarManager::setAvatarSortCoefficient(const QString& name, const ScriptValue& value) { bool somethingChanged = false; if (value.isNumber()) { float numericalValue = (float)value.toNumber(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index f7e951d21d2..e2784008211 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -26,13 +26,14 @@ #include #include #include // for SetOfEntities +#include #include "AvatarMotionState.h" #include "DetailedMotionState.h" #include "MyAvatar.h" #include "OtherAvatar.h" - +class ScriptEngine; using SortedAvatar = std::pair>; /*@jsdoc @@ -94,7 +95,7 @@ class AvatarManager : public AvatarHashMap { */ /// Registers the script types associated with the avatar manager. - static void registerMetaTypes(QScriptEngine* engine); + static void registerMetaTypes(ScriptEngine* engine); virtual ~AvatarManager(); @@ -185,8 +186,8 @@ class AvatarManager : public AvatarHashMap { * } */ Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, - const QScriptValue& avatarIdsToInclude = QScriptValue(), - const QScriptValue& avatarIdsToDiscard = QScriptValue(), + const ScriptValue& avatarIdsToInclude = ScriptValue(), + const ScriptValue& avatarIdsToDiscard = ScriptValue(), bool pickAgainstMesh = true); /*@jsdoc * @function AvatarManager.findRayIntersectionVector @@ -229,7 +230,7 @@ class AvatarManager : public AvatarHashMap { * @param {number} value - Value. * @deprecated This function is deprecated and will be removed. */ - Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value); + Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const ScriptValue& value); /*@jsdoc * Gets PAL (People Access List) data for one or more avatars. Using this method is faster than iterating over each avatar diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0d650bf2e73..3fdcc6f1d34 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -43,13 +43,15 @@ #include #include #include +#include +#include #include #include #include #include #include #include -#include +#include #include #include #include @@ -377,8 +379,6 @@ MyAvatar::MyAvatar(QThread* thread) : MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); - delete _scriptEngine; - _scriptEngine = nullptr; if (_addAvatarEntitiesToTreeTimer.isActive()) { _addAvatarEntitiesToTreeTimer.stop(); } @@ -438,18 +438,18 @@ void MyAvatar::enableHandTouchForID(const QUuid& entityID) { } void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) { - QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + ScriptValue value = engine->newQObject(this, ScriptEngine::QtOwnership); engine->globalObject().setProperty("MyAvatar", value); - QScriptValue driveKeys = engine->newObject(); + ScriptValue driveKeys = engine->newObject(); auto metaEnum = QMetaEnum::fromType(); for (int i = 0; i < MAX_DRIVE_KEYS; ++i) { driveKeys.setProperty(metaEnum.key(i), metaEnum.value(i)); } engine->globalObject().setProperty("DriveKeys", driveKeys); - qScriptRegisterMetaType(engine.data(), audioListenModeToScriptValue, audioListenModeFromScriptValue); - qScriptRegisterMetaType(engine.data(), driveKeysToScriptValue, driveKeysFromScriptValue); + scriptRegisterMetaType(engine.get(), audioListenModeToScriptValue, audioListenModeFromScriptValue); + scriptRegisterMetaType(engine.get(), driveKeysToScriptValue, driveKeysFromScriptValue); } void MyAvatar::setOrientationVar(const QVariant& newOrientationVar) { @@ -2061,7 +2061,7 @@ void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const { void MyAvatar::loadData() { if (!_scriptEngine) { - _scriptEngine = new QScriptEngine(); + _scriptEngine = newScriptEngine(); } getHead()->setBasePitch(_headPitchSetting.get()); @@ -2669,8 +2669,7 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { EntityItemProperties entityProperties = entity->getProperties(desiredProperties); { std::lock_guard guard(_scriptEngineLock); - QScriptValue scriptProperties; - scriptProperties = EntityItemPropertiesToScriptValue(_scriptEngine, entityProperties); + ScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_scriptEngine.get(), entityProperties); avatarEntityData["properties"] = scriptProperties.toVariant(); } avatarEntitiesData.append(QVariant(avatarEntityData)); @@ -5702,20 +5701,22 @@ void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) { } } -QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode) { - return audioListenerMode; +ScriptValue audioListenModeToScriptValue(ScriptEngine* engine, const AudioListenerMode& audioListenerMode) { + return engine->newValue(audioListenerMode); } -void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode) { +bool audioListenModeFromScriptValue(const ScriptValue& object, AudioListenerMode& audioListenerMode) { audioListenerMode = static_cast(object.toUInt16()); + return true; } -QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys) { - return driveKeys; +ScriptValue driveKeysToScriptValue(ScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys) { + return engine->newValue(driveKeys); } -void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys) { +bool driveKeysFromScriptValue(const ScriptValue& object, MyAvatar::DriveKeys& driveKeys) { driveKeys = static_cast(object.toUInt16()); + return true; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 8b5ffa233a4..80b6de7bcb3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -14,6 +14,7 @@ #define hifi_MyAvatar_h #include +#include #include @@ -27,10 +28,10 @@ #include #include #include -#include #include #include #include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -40,6 +41,8 @@ class AvatarActionHold; class ModelItemID; class MyHead; class DetailedMotionState; +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; /*@jsdoc *

Locomotion control types.

@@ -869,7 +872,7 @@ class MyAvatar : public Avatar { * MyAvatar.removeAnimationStateHandler(handler); * }, 100); */ - Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } + Q_INVOKABLE ScriptValue addAnimationStateHandler(const ScriptValue& handler, const ScriptValue& propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } /*@jsdoc * Removes an animation state handler function. @@ -877,7 +880,7 @@ class MyAvatar : public Avatar { * @param {number} handler - The ID of the animation state handler function to remove. */ // Removes a handler previously added by addAnimationStateHandler. - Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } + Q_INVOKABLE void removeAnimationStateHandler(const ScriptValue& handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } /*@jsdoc @@ -3098,7 +3101,7 @@ private slots: // // keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete) mutable std::mutex _scriptEngineLock; - QScriptEngine* _scriptEngine { nullptr }; + ScriptEnginePointer _scriptEngine { nullptr }; bool _needToSaveAvatarEntitySettings { false }; bool _reactionTriggers[NUM_AVATAR_TRIGGER_REACTIONS] { false, false }; @@ -3115,11 +3118,11 @@ private slots: QTimer _addAvatarEntitiesToTreeTimer; }; -QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); -void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode); +ScriptValue audioListenModeToScriptValue(ScriptEngine* engine, const AudioListenerMode& audioListenerMode); +bool audioListenModeFromScriptValue(const ScriptValue& object, AudioListenerMode& audioListenerMode); -QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys); -void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys); +ScriptValue driveKeysToScriptValue(ScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys); +bool driveKeysFromScriptValue(const ScriptValue& object, MyAvatar::DriveKeys& driveKeys); bool isWearableEntity(const EntityItemPointer& entity); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 47105e0f3a2..78363b077d1 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -374,7 +374,7 @@ bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmedi // Don't try to re-load (install) a script if it's already running QStringList runningScripts = DependencyManager::get()->getRunningScripts(); if (!runningScripts.contains(scriptUrl)) { - if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { + if (!(DependencyManager::get()->loadScript(scriptUrl.trimmed()))) { qCDebug(commerce) << "Couldn't load script."; return false; } diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp index 16fe65a989f..ed5fd2ecb69 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.cpp +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -11,14 +11,14 @@ #include "LaserPointerScriptingInterface.h" -#include "RegisteredMetaTypes.h" #include "PointerScriptingInterface.h" +#include -void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { +void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); } -void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { +void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue& includeItems) const { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index ab3bca57d62..fe70509a0c8 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -16,6 +16,8 @@ #include "DependencyManager.h" #include +class ScriptValue; + class LaserPointerScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -113,7 +115,7 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pointer. * @param {Uuid[]} ignoreItems - A list of IDs to ignore. */ - Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; + Q_INVOKABLE void setIgnoreItems(unsigned int uid, const ScriptValue& ignoreEntities) const; /*@jsdoc * Sets a list of entity and avatar IDs that a pointer should include during intersection, instead of intersecting with @@ -122,7 +124,7 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pointer. * @param {Uuid[]} includeItems - A list of IDs to include. */ - Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; + Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeEntities) const; /*@jsdoc diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 8a1675cfe18..b0223888aa4 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "PickScriptingInterface.h" #include "RayPick.h" diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index e7777efe458..b5ef887f628 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -29,6 +29,8 @@ #include "EntityTransformNode.h" #include +#include +#include static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, StylusPick::WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f); @@ -425,11 +427,11 @@ void PickScriptingInterface::setPrecisionPicking(unsigned int uid, bool precisio DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } -void PickScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) { +void PickScriptingInterface::setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems) { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); } -void PickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) { +void PickScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue& includeItems) { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } @@ -445,23 +447,24 @@ bool PickScriptingInterface::isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } -QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) { - return pickType; +ScriptValue pickTypesToScriptValue(ScriptEngine* engine, const PickQuery::PickType& pickType) { + return engine->newValue(pickType); } -void pickTypesFromScriptValue(const QScriptValue& object, PickQuery::PickType& pickType) { +bool pickTypesFromScriptValue(const ScriptValue& object, PickQuery::PickType& pickType) { pickType = static_cast(object.toUInt16()); + return true; } -void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - QScriptValue pickTypes = engine->newObject(); +void PickScriptingInterface::registerMetaTypes(ScriptEngine* engine) { + ScriptValue pickTypes = engine->newObject(); auto metaEnum = QMetaEnum::fromType(); for (int i = 0; i < PickQuery::PickType::NUM_PICK_TYPES; ++i) { pickTypes.setProperty(metaEnum.key(i), metaEnum.value(i)); } engine->globalObject().setProperty("PickType", pickTypes); - qScriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue); + scriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue); } unsigned int PickScriptingInterface::getPerFrameTimeBudget() const { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 72470b42ee1..8afe5c45d35 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -15,6 +15,9 @@ #include #include +class ScriptEngine; +class ScriptValue; + /*@jsdoc * The Picks API lets you create and manage objects for repeatedly calculating intersections. * @@ -103,7 +106,7 @@ class PickScriptingInterface : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - void registerMetaTypes(QScriptEngine* engine); + void registerMetaTypes(ScriptEngine* engine); /*@jsdoc * Creates a new pick. Different {@link PickType}s use different properties, and within one PickType the properties you @@ -245,7 +248,7 @@ class PickScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pick. * @param {Uuid[]} ignoreItems - The list of IDs to ignore. */ - Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems); + Q_INVOKABLE void setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems); /*@jsdoc * Sets a list of entity and avatar IDs that a pick should include during intersection, instead of intersecting with @@ -255,7 +258,7 @@ class PickScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pick. * @param {Uuid[]} includeItems - The list of IDs to include. */ - Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeItems); + Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeItems); /*@jsdoc * Checks if a pick is associated with the left hand: a ray or parabola pick with joint property set to diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 0b4c399d3bd..259fbf428d8 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include "Application.h" #include "PickManager.h" #include "LaserPointer.h" @@ -23,11 +25,11 @@ static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; static const glm::vec3 DEFAULT_POSITION_OFFSET{0.0f, 0.0f, -StylusPick::WEB_STYLUS_LENGTH / 2.0f}; static const glm::vec3 DEFAULT_MODEL_DIMENSIONS{0.01f, 0.01f, StylusPick::WEB_STYLUS_LENGTH}; -void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { +void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); } -void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { +void PointerScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue& includeItems) const { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 58439f221d4..836a86b7bdd 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -15,6 +15,8 @@ #include #include +class ScriptValue; + /*@jsdoc * The Pointers API lets you create, manage, and visually represent objects for repeatedly calculating * intersections with avatars, entities, and overlays. Pointers can also be configured to generate events on entities and @@ -365,7 +367,7 @@ class PointerScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pointer. * @param {Uuid[]} ignoreItems - A list of IDs to ignore. */ - Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; + Q_INVOKABLE void setIgnoreItems(unsigned int uid, const ScriptValue& ignoreEntities) const; /*@jsdoc * Sets a list of entity and avatar IDs that a pointer should include during intersection, instead of intersecting with @@ -375,7 +377,7 @@ class PointerScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the pointer. * @param {Uuid[]} includeItems - A list of IDs to include. */ - Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; + Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeEntities) const; /*@jsdoc diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index a837121e6ad..6371632368a 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -15,6 +15,7 @@ #include "GLMHelpers.h" #include +#include unsigned int RayPickScriptingInterface::createRayPick(const QVariant& properties) { return DependencyManager::get()->createPick(PickQuery::PickType::Ray, properties); @@ -45,11 +46,11 @@ void RayPickScriptingInterface::setPrecisionPicking(unsigned int uid, bool preci DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } -void RayPickScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) { +void RayPickScriptingInterface::setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems) { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); } -void RayPickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) { +void RayPickScriptingInterface::setIncludeItems(unsigned int uid, const ScriptValue& includeItems) { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index 0aed9e4bdfa..acc11111c76 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -18,6 +18,8 @@ #include "PickScriptingInterface.h" +class ScriptValue; + /*@jsdoc * The RayPick API is a subset of the {@link Picks} API, as used for ray picks. * @@ -121,7 +123,7 @@ class RayPickScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the ray pick. * @param {Uuid[]} ignoreItems - The list of IDs to ignore. */ - Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); + Q_INVOKABLE void setIgnoreItems(unsigned int uid, const ScriptValue& ignoreEntities); /*@jsdoc * Sets a list of entity and avatar IDs that a ray pick should include during intersection, instead of intersecting with @@ -130,7 +132,7 @@ class RayPickScriptingInterface : public QObject, public Dependency { * @param {number} id - The ID of the ray pick. * @param {Uuid[]} includeItems - The list of IDs to include. */ - Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities); + Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeEntities); /*@jsdoc diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index 87aacad631b..042cb25978c 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -123,10 +123,10 @@ DownloadInfoResult::DownloadInfoResult() : * @property {number[]} downloading - The download percentage remaining of each asset currently downloading. * @property {number} pending - The number of assets pending download. */ -QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) { - QScriptValue object = engine->newObject(); +ScriptValue DownloadInfoResultToScriptValue(ScriptEngine* engine, const DownloadInfoResult& result) { + ScriptValue object = engine->newObject(); - QScriptValue array = engine->newArray(result.downloading.count()); + ScriptValue array = engine->newArray(result.downloading.count()); for (int i = 0; i < result.downloading.count(); i += 1) { array.setProperty(i, result.downloading[i]); } @@ -136,7 +136,7 @@ QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const Downlo return object; } -void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result) { +bool DownloadInfoResultFromScriptValue(const ScriptValue& object, DownloadInfoResult& result) { QList downloading = object.property("downloading").toVariant().toList(); result.downloading.clear(); for (int i = 0; i < downloading.count(); i += 1) { @@ -144,6 +144,7 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR } result.pending = object.property("pending").toVariant().toFloat(); + return true; } DownloadInfoResult AccountServicesScriptingInterface::getDownloadInfo() { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index 723622523ce..2d13891fa57 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -13,14 +13,14 @@ #define hifi_AccountServicesScriptingInterface_h #include -#include -#include -#include #include #include #include #include +#include + +class ScriptEngine; class DownloadInfoResult { public: @@ -31,8 +31,8 @@ class DownloadInfoResult { Q_DECLARE_METATYPE(DownloadInfoResult) -QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result); -void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result); +ScriptValue DownloadInfoResultToScriptValue(ScriptEngine* engine, const DownloadInfoResult& result); +bool DownloadInfoResultFromScriptValue(const ScriptValue& object, DownloadInfoResult& result); class AccountServicesScriptingInterface : public QObject { Q_OBJECT diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 5b90474d233..0c80d0384df 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -11,7 +11,6 @@ #include "AssetMappingsScriptingInterface.h" -#include #include #include diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index b27a72fbd0f..bc0676b1bca 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -15,14 +15,12 @@ #define hifi_AssetMappingsScriptingInterface_h #include -#include #include #include #include "DependencyManager.h" - class AssetMappingModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(bool autoRefreshEnabled READ isAutoRefreshEnabled WRITE setAutoRefreshEnabled) diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 28d5f8d4446..68eb27c7d06 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -13,7 +13,6 @@ #define hifi_DesktopScriptingInterface_h #include -#include #include diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 79c0452a452..609037b7149 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -11,14 +11,15 @@ #include "HMDScriptingInterface.h" -#include - #include #include #include #include #include #include +#include +#include +#include #include #include "Application.h" @@ -151,23 +152,23 @@ bool HMDScriptingInterface::getAwayStateWhenFocusLostInVREnabled() { } -QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) { +ScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(ScriptContext* context, ScriptEngine* engine) { glm::vec3 hudIntersection; auto instance = DependencyManager::get(); if (instance->getHUDLookAtPosition3D(hudIntersection)) { glm::vec2 overlayPos = qApp->getApplicationCompositor().overlayFromSphereSurface(hudIntersection); - return qScriptValueFromValue(engine, overlayPos); + return scriptValueFromValue(engine, overlayPos); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine) { +ScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(ScriptContext* context, ScriptEngine* engine) { glm::vec3 result; auto instance = DependencyManager::get(); if (instance->getHUDLookAtPosition3D(result)) { - return qScriptValueFromValue(engine, result); + return scriptValueFromValue(engine, result); } - return QScriptValue::NullValue; + return engine->nullValue(); } bool HMDScriptingInterface::getHUDLookAtPosition3D(glm::vec3& result) const { diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 7790c482fab..ad0bcad1002 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -14,15 +14,15 @@ #include -#include -class QScriptContext; -class QScriptEngine; - #include #include #include #include +#include + +class ScriptContext; +class ScriptEngine; /*@jsdoc * The HMD API provides access to the HMD used in VR display mode. @@ -442,14 +442,14 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * @function HMD.getHUDLookAtPosition2D * @returns {Vec2} The position on the HUD overlay that your HMD is looking at, in pixels. */ - static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine); + static ScriptValue getHUDLookAtPosition2D(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Gets the position on the HUD overlay that your HMD is looking at, in world coordinates. * @function HMD.getHUDLookAtPosition3D * @returns {Vec3} The position on the HUD overlay the your HMD is looking at, in world coordinates. */ - static QScriptValue getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine); + static ScriptValue getHUDLookAtPosition3D(ScriptContext* context, ScriptEngine* engine); bool isMounted() const override; diff --git a/interface/src/scripting/PerformanceScriptingInterface.cpp b/interface/src/scripting/PerformanceScriptingInterface.cpp index ec56e833238..fb4561aea8d 100644 --- a/interface/src/scripting/PerformanceScriptingInterface.cpp +++ b/interface/src/scripting/PerformanceScriptingInterface.cpp @@ -16,6 +16,8 @@ std::once_flag PerformanceScriptingInterface::registry_flag; PerformanceScriptingInterface::PerformanceScriptingInterface() { std::call_once(registry_flag, [] { qmlRegisterType("PerformanceEnums", 1, 0, "PerformanceEnums"); + qRegisterMetaType("PerformanceScriptingInterface::PerformancePreset"); + qRegisterMetaType("PerformanceScriptingInterface::RefreshRateProfile"); }); } diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 71b54f95fd5..8391c404d83 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -12,8 +12,6 @@ #include #include -class QScriptValue; - /*@jsdoc * The PlatformInfo API provides information about the hardware platform being used. * diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 5ecb1a6e43a..aec7ae3b4cd 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -20,6 +20,7 @@ std::once_flag RenderScriptingInterface::registry_flag; RenderScriptingInterface::RenderScriptingInterface() { std::call_once(registry_flag, [] { qmlRegisterType("RenderEnums", 1, 0, "RenderEnums"); + qRegisterMetaType("RenderScriptingInterface::RenderMethod"); }); } diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index 13eddddb1f5..9ad2ef187c9 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -47,7 +47,7 @@ void SettingsScriptingInterface::setValue(const QString& setting, const QVariant } } // Make a deep-copy of the string. - // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine. + // Dangling pointers can occur with QStrings that are implicitly shared from a ScriptEngine. QString deepCopy = QString::fromUtf16(setting.utf16()); Setting::Handle(deepCopy).set(value); emit valueChanged(setting, value); diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 53630b3eede..2985ae6e50e 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -153,7 +154,7 @@ void TestScriptingInterface::savePhysicsSimulationStats(QString originalPath) { qApp->saveNextPhysicsStats(path); } -void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) { +void TestScriptingInterface::profileRange(const QString& name, const ScriptValue& fn) { PROFILE_RANGE(script, name); fn.call(); } diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 329f2a87c14..b5305d1df2a 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -12,8 +12,7 @@ #include #include - -class QScriptValue; +#include class TestScriptingInterface : public QObject { Q_OBJECT @@ -127,7 +126,7 @@ public slots: * @param {string} name - Name used to reference the function * @param {function} function - Function to profile */ - Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); + Q_INVOKABLE void profileRange(const QString& name, const ScriptValue& function); /*@jsdoc * Clear all caches (menu command Reload Content) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index e594ecd5369..cdc3fdb09a5 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -76,8 +76,8 @@ WindowScriptingInterface::~WindowScriptingInterface() { _messageBoxes.clear(); } -QScriptValue WindowScriptingInterface::hasFocus() { - return qApp->hasFocus(); +ScriptValue WindowScriptingInterface::hasFocus() { + return engine()->newValue(qApp->hasFocus()); } void WindowScriptingInterface::setFocus() { @@ -96,28 +96,29 @@ void WindowScriptingInterface::raise() { /// Display an alert box /// \param const QString& message message to display -/// \return QScriptValue::UndefinedValue +/// \return ScriptValue::UndefinedValue void WindowScriptingInterface::alert(const QString& message) { OffscreenUi::asyncWarning("", message, QMessageBox::Ok, QMessageBox::Ok); } /// Display a confirmation box with the options 'Yes' and 'No' /// \param const QString& message message to display -/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise -QScriptValue WindowScriptingInterface::confirm(const QString& message) { - return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes))); +/// \return ScriptValue `true` if 'Yes' was clicked, `false` otherwise +ScriptValue WindowScriptingInterface::confirm(const QString& message) { + return engine()->newValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes))); } /// Display a prompt with a text box /// \param const QString& message message to display /// \param const QString& defaultText default text in the text box -/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise. -QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { +/// \return ScriptValue string text value in text box if the dialog was accepted, `null` otherwise. +ScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText); - if (QScriptValue(result).equals("")) { - return QScriptValue::NullValue; + auto sResult = engine()->newValue(result); + if (sResult.equals(engine()->newValue(""))) { + return engine()->nullValue(); } - return QScriptValue(result); + return sResult; } /// Display a prompt with a text box @@ -217,8 +218,8 @@ void WindowScriptingInterface::ensureReticleVisible() const { /// working directory. /// \param const QString& title title of the window /// \param const QString& directory directory to start the directory browser at -/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` -QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) { +/// \return ScriptValue file path as a string if one was selected, otherwise `ScriptValue::NullValue` +ScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) { ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { @@ -231,7 +232,7 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result); } /// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current @@ -261,8 +262,8 @@ void WindowScriptingInterface::browseDirAsync(const QString& title, const QStrin /// \param const QString& title title of the window /// \param const QString& directory directory to start the file browser at /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` -/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` -QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { +/// \return ScriptValue file path as a string if one was selected, otherwise `ScriptValue::NullValue` +ScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { @@ -275,7 +276,7 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result); } /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current @@ -308,8 +309,8 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString& /// \param const QString& title title of the window /// \param const QString& directory directory to start the file browser at /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` -/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` -QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) { +/// \return ScriptValue file path as a string if one was selected, otherwise `ScriptValue::NullValue` +ScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) { ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { @@ -322,7 +323,7 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString& if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result); } /// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current @@ -355,8 +356,8 @@ void WindowScriptingInterface::saveAsync(const QString& title, const QString& di /// \param const QString& title title of the window /// \param const QString& directory directory to start the asset browser at /// \param const QString& nameFilter filter to filter asset names by - see `QFileDialog` -/// \return QScriptValue asset path as a string if one was selected, otherwise `QScriptValue::NullValue` -QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const QString& directory, const QString& nameFilter) { +/// \return ScriptValue asset path as a string if one was selected, otherwise `ScriptValue::NullValue` +ScriptValue WindowScriptingInterface::browseAssets(const QString& title, const QString& directory, const QString& nameFilter) { ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { @@ -372,7 +373,7 @@ QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const if (!result.isEmpty()) { setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + return result.isEmpty() ? engine()->nullValue() : engine()->newValue(result); } /// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index e7a2e809576..e3f78c0a4fd 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -17,10 +17,12 @@ #include #include #include -#include #include #include +#include +#include + /*@jsdoc * The Window API provides various facilities not covered elsewhere, including: window dimensions, window focus, @@ -46,7 +48,7 @@ * @property {location} location - Provides facilities for working with your current metaverse location. */ -class WindowScriptingInterface : public QObject, public Dependency { +class WindowScriptingInterface : public QObject, protected Scriptable, public Dependency { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) Q_PROPERTY(int innerHeight READ getInnerHeight) @@ -69,7 +71,7 @@ public slots: * @function Window.hasFocus * @returns {boolean} true if the Interface window has focus, false if it doesn't. */ - QScriptValue hasFocus(); + ScriptValue hasFocus(); /*@jsdoc * Makes the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to @@ -104,7 +106,7 @@ public slots: * var answer = Window.confirm("Are you sure?"); * print(answer); // true or false */ - QScriptValue confirm(const QString& message = ""); + ScriptValue confirm(const QString& message = ""); /*@jsdoc * Prompts the user to enter some text. Displays a modal dialog with a message and a text box, plus "OK" and "Cancel" @@ -121,7 +123,7 @@ public slots: * print("User answer: " + answer); * } */ - QScriptValue prompt(const QString& message, const QString& defaultText); + ScriptValue prompt(const QString& message, const QString& defaultText); /*@jsdoc * Prompts the user to enter some text. Displays a non-modal dialog with a message and a text box, plus "OK" and "Cancel" @@ -151,7 +153,7 @@ public slots: * var directory = Window.browseDir("Select Directory", Paths.resources); * print("Directory: " + directory); */ - QScriptValue browseDir(const QString& title = "", const QString& directory = ""); + ScriptValue browseDir(const QString& title = "", const QString& directory = ""); /*@jsdoc * Prompts the user to choose a directory. Displays a non-modal dialog that navigates the directory tree. A @@ -183,7 +185,7 @@ public slots: * var filename = Window.browse("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)"); * print("File: " + filename); */ - QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + ScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); /*@jsdoc * Prompts the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A @@ -219,7 +221,7 @@ public slots: * var filename = Window.save("Save to JSON file", Paths.resources, "*.json"); * print("File: " + filename); */ - QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + ScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); /*@jsdoc * Prompts the user to specify the path and name of a file to save to. Displays a non-modal dialog that navigates the @@ -254,7 +256,7 @@ public slots: * var asset = Window.browseAssets("Select FBX File", "/", "*.fbx"); * print("FBX file: " + asset); */ - QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + ScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); /*@jsdoc * Prompts the user to choose an Asset Server item. Displays a non-modal dialog that navigates the tree of assets on the diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index d1bdbb16523..cda66c5cf27 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "OffscreenUi.h" #include "shared/QtHelpers.h" @@ -91,18 +93,19 @@ static void dockWidgetDeleter(DockWidget* dockWidget) { dockWidget->deleteLater(); } -void registerInteractiveWindowMetaType(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue); +void registerInteractiveWindowMetaType(ScriptEngine* engine) { + scriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue); } -QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in) { - return engine->newQObject(in, QScriptEngine::ScriptOwnership); +ScriptValue interactiveWindowPointerToScriptValue(ScriptEngine* engine, const InteractiveWindowPointer& in) { + return engine->newQObject(in, ScriptEngine::ScriptOwnership); } -void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out) { +bool interactiveWindowPointerFromScriptValue(const ScriptValue& object, InteractiveWindowPointer& out) { if (const auto interactiveWindow = qobject_cast(object.toQObject())) { out = interactiveWindow; } + return true; } void InteractiveWindow::forwardKeyPressEvent(int key, int modifiers) { diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index 744875fef52..9cce6944cbd 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -16,12 +16,14 @@ #include #include -#include #include #include #include #include +#include + +class ScriptEngine; class QmlWindowProxy : public QmlWrapper { Q_OBJECT @@ -408,10 +410,10 @@ protected slots: typedef InteractiveWindow* InteractiveWindowPointer; -QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in); -void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out); +ScriptValue interactiveWindowPointerToScriptValue(ScriptEngine* engine, const InteractiveWindowPointer& in); +bool interactiveWindowPointerFromScriptValue(const ScriptValue& object, InteractiveWindowPointer& out); -void registerInteractiveWindowMetaType(QScriptEngine* engine); +void registerInteractiveWindowMetaType(ScriptEngine* engine); Q_DECLARE_METATYPE(InteractiveWindowPointer) diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index ed4ee97780d..7d4fadab8e5 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include @@ -133,7 +135,7 @@ QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { return model; } -JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : +JSConsole::JSConsole(QWidget* parent, const ScriptManagerPointer& scriptManager) : QWidget(parent), _ui(new Ui::Console), _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), @@ -181,11 +183,11 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : QObject::connect(_completer, static_cast(&QCompleter::highlighted), this, &JSConsole::highlightedCompletion); - setScriptEngine(scriptEngine); + setScriptManager(scriptManager); resizeTextInput(); - connect(&_executeWatcher, &QFutureWatcher::finished, this, &JSConsole::commandFinished); + connect(&_executeWatcher, &QFutureWatcher::finished, this, &JSConsole::commandFinished); } void JSConsole::insertCompletion(const QModelIndex& completion) { @@ -305,33 +307,33 @@ void JSConsole::highlightedCompletion(const QModelIndex& completion) { } JSConsole::~JSConsole() { - if (_scriptEngine) { - disconnect(_scriptEngine.data(), nullptr, this, nullptr); - _scriptEngine.reset(); + if (_scriptManager) { + disconnect(_scriptManager.get(), nullptr, this, nullptr); + _scriptManager.reset(); } delete _ui; } -void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { - if (_scriptEngine == scriptEngine && scriptEngine != nullptr) { +void JSConsole::setScriptManager(const ScriptManagerPointer& scriptManager) { + if (_scriptManager == scriptManager && scriptManager != nullptr) { return; } - if (_scriptEngine != nullptr) { - disconnect(_scriptEngine.data(), nullptr, this, nullptr); - _scriptEngine.reset(); + if (_scriptManager != nullptr) { + disconnect(_scriptManager.get(), nullptr, this, nullptr); + _scriptManager.reset(); } // if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine - if (scriptEngine.isNull()) { - _scriptEngine = DependencyManager::get()->loadScript(_consoleFileName, false); + if (!scriptManager) { + _scriptManager = DependencyManager::get()->loadScript(_consoleFileName, false); } else { - _scriptEngine = scriptEngine; + _scriptManager = scriptManager; } - connect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); - connect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo); - connect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning); - connect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError); + connect(_scriptManager.get(), &ScriptManager::printedMessage, this, &JSConsole::handlePrint); + connect(_scriptManager.get(), &ScriptManager::infoMessage, this, &JSConsole::handleInfo); + connect(_scriptManager.get(), &ScriptManager::warningMessage, this, &JSConsole::handleWarning); + connect(_scriptManager.get(), &ScriptManager::errorMessage, this, &JSConsole::handleError); } void JSConsole::executeCommand(const QString& command) { @@ -347,16 +349,15 @@ void JSConsole::executeCommand(const QString& command) { appendMessage(">", "" + command.toHtmlEscaped() + ""); - QWeakPointer weakScriptEngine = _scriptEngine; + std::weak_ptr weakScriptManager = _scriptManager; auto consoleFileName = _consoleFileName; - QFuture future = QtConcurrent::run([weakScriptEngine, consoleFileName, command]()->QScriptValue{ - QScriptValue result; - auto scriptEngine = weakScriptEngine.lock(); - if (scriptEngine) { - BLOCKING_INVOKE_METHOD(scriptEngine.data(), "evaluate", - Q_RETURN_ARG(QScriptValue, result), - Q_ARG(const QString&, command), - Q_ARG(const QString&, consoleFileName)); + QFuture future = QtConcurrent::run([weakScriptManager, consoleFileName, command]() -> ScriptValue { + ScriptValue result; + auto scriptManager = weakScriptManager.lock(); + if (scriptManager) { + BLOCKING_INVOKE_METHOD(scriptManager.get(), [&scriptManager, &consoleFileName, &command, &result]() -> void { + result = scriptManager->evaluate(command, consoleFileName); + }); } return result; }); @@ -364,7 +365,7 @@ void JSConsole::executeCommand(const QString& command) { } void JSConsole::commandFinished() { - QScriptValue result = _executeWatcher.result(); + ScriptValue result = _executeWatcher.result(); _ui->promptTextEdit->setDisabled(false); @@ -373,7 +374,7 @@ void JSConsole::commandFinished() { _ui->promptTextEdit->setFocus(); } - bool error = (_scriptEngine->hasUncaughtException() || result.isError()); + bool error = (_scriptManager->engine()->hasUncaughtException() || result.isError()); QString gutter = error ? GUTTER_ERROR : GUTTER_PREVIOUS_COMMAND; QString resultColor = error ? RESULT_ERROR_STYLE : RESULT_SUCCESS_STYLE; QString resultStr = "" + result.toString().toHtmlEscaped() + ""; diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index 202eb6ed6a8..4bfa4cb107d 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -12,13 +12,19 @@ #ifndef hifi_JSConsole_h #define hifi_JSConsole_h +#include + #include #include #include #include +#include #include "ui_console.h" -#include "ScriptEngine.h" + +class QStandardItemModel; +class ScriptManager; +using ScriptManagerPointer = std::shared_ptr; const QString CONSOLE_TITLE = "Scripting Console"; const float CONSOLE_WINDOW_OPACITY = 0.95f; @@ -28,10 +34,10 @@ const int CONSOLE_HEIGHT = 200; class JSConsole : public QWidget { Q_OBJECT public: - JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine = ScriptEnginePointer()); + JSConsole(QWidget* parent, const ScriptManagerPointer& scriptManager = ScriptManagerPointer()); ~JSConsole(); - void setScriptEngine(const ScriptEnginePointer& scriptEngine = ScriptEnginePointer()); + void setScriptManager(const ScriptManagerPointer& scriptManager = ScriptManagerPointer()); void clear(); public slots: @@ -66,13 +72,13 @@ private slots: QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr); - QFutureWatcher _executeWatcher; + QFutureWatcher _executeWatcher; Ui::Console* _ui; int _currentCommandInHistory; QString _savedHistoryFilename; QList _commandHistory; QString _rootCommand; - ScriptEnginePointer _scriptEngine; + ScriptManagerPointer _scriptManager; static const QString _consoleFileName; QJsonArray _apiDocs; QCompleter* _completer; diff --git a/interface/src/ui/TestingDialog.cpp b/interface/src/ui/TestingDialog.cpp index 5f0b20ca7e0..efaf49fc1a3 100644 --- a/interface/src/ui/TestingDialog.cpp +++ b/interface/src/ui/TestingDialog.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "ScriptEngines.h" +#include TestingDialog::TestingDialog(QWidget* parent) : QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), @@ -23,12 +24,12 @@ TestingDialog::TestingDialog(QWidget* parent) : _console->setFixedHeight(TESTING_CONSOLE_HEIGHT); - _engine = DependencyManager::get()->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); - _console->setScriptEngine(_engine); - connect(_engine.data(), &ScriptEngine::finished, this, &TestingDialog::onTestingFinished); + _scriptManager = DependencyManager::get()->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); + _console->setScriptManager(_scriptManager); + connect(_scriptManager.get(), &ScriptManager::finished, this, &TestingDialog::onTestingFinished); } void TestingDialog::onTestingFinished(const QString& scriptPath) { - _engine.reset(); - _console->setScriptEngine(); + _scriptManager.reset(); + _console->setScriptManager(); } diff --git a/interface/src/ui/TestingDialog.h b/interface/src/ui/TestingDialog.h index a7e909ca0e1..7707213b037 100644 --- a/interface/src/ui/TestingDialog.h +++ b/interface/src/ui/TestingDialog.h @@ -12,10 +12,14 @@ #ifndef hifi_TestingDialog_h #define hifi_TestingDialog_h +#include + #include -#include "ScriptEngine.h" #include "JSConsole.h" +class ScriptManager; +using ScriptManagerPointer = std::shared_ptr; + const QString windowLabel = "Client Script Tests"; const QString testRunnerRelativePath = "/scripts/developer/tests/unit_tests/testRunner.js"; const unsigned int TESTING_CONSOLE_HEIGHT = 400; @@ -29,7 +33,7 @@ class TestingDialog : public QDialog { private: std::unique_ptr _console; - ScriptEnginePointer _engine; + ScriptManagerPointer _scriptManager; }; #endif diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index d9fd9fdaa16..89a5dae0496 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -12,8 +12,6 @@ #include -#include - #include #include #include @@ -30,6 +28,8 @@ #include #include #include +#include +#include #include #include "VariantMapToScriptValue.h" @@ -42,7 +42,7 @@ Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") std::unordered_map Overlays::_entityToOverlayTypes; std::unordered_map Overlays::_overlayToEntityTypes; -Overlays::Overlays() { +Overlays::Overlays() : _scriptEngine(newScriptEngine()) { ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); _overlayToEntityTypes["rectangle3d"] = "Shape"; @@ -632,16 +632,16 @@ EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& ove } } - QScriptEngine scriptEngine; - QScriptValue props = variantMapToScriptValue(overlayProps, scriptEngine); + ScriptEnginePointer scriptEngine = newScriptEngine(); + ScriptValue props = variantMapToScriptValue(overlayProps, *scriptEngine); EntityItemProperties toReturn; EntityItemPropertiesFromScriptValueHonorReadOnly(props, toReturn); return toReturn; } QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemProperties& properties) { - QScriptEngine scriptEngine; - QVariantMap overlayProps = EntityItemPropertiesToScriptValue(&scriptEngine, properties).toVariant().toMap(); + ScriptEnginePointer scriptEngine = newScriptEngine(); + QVariantMap overlayProps = EntityItemPropertiesToScriptValue(scriptEngine.get(), properties).toVariant().toMap(); QString type = overlayProps["type"].toString(); overlayProps["type"] = entityToOverlayType(type); @@ -740,7 +740,7 @@ QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemPropertie GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksColor, majorTickMarksColor); GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksColor, minorTickMarksColor); } else if (type == "PolyLine") { - QVector points = qVectorVec3FromScriptValue(scriptEngine.newVariant(overlayProps["linePoints"])); + QVector points = qVectorVec3FromScriptValue(scriptEngine->newVariant(overlayProps["linePoints"])); glm::vec3 position = vec3FromVariant(overlayProps["position"]); if (points.length() > 1) { overlayProps["p1"] = vec3toVariant(points[0] + position); @@ -755,7 +755,7 @@ QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemPropertie RENAME_PROP(p2, endPoint); RENAME_PROP(p2, end); - QVector widths = qVectorFloatFromScriptValue(scriptEngine.newVariant(overlayProps["strokeWidths"])); + QVector widths = qVectorFloatFromScriptValue(scriptEngine->newVariant(overlayProps["strokeWidths"])); if (widths.length() > 0) { overlayProps["lineWidth"] = widths[0]; } @@ -1041,8 +1041,8 @@ QVariantMap Overlays::getOverlaysProperties(const QVariant& propertiesById) { } RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, - const QScriptValue& overlayIDsToInclude, - const QScriptValue& overlayIDsToDiscard, + const ScriptValue& overlayIDsToInclude, + const ScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { const QVector include = qVectorEntityItemIDFromScriptValue(overlayIDsToInclude); const QVector discard = qVectorEntityItemIDFromScriptValue(overlayIDsToDiscard); @@ -1110,38 +1110,39 @@ ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(con return overlayResult; } -QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { - QScriptValue obj = engine->newObject(); +ScriptValue RayToOverlayIntersectionResultToScriptValue(ScriptEngine* engine, const RayToOverlayIntersectionResult& value) { + ScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); - QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); + ScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); obj.setProperty("overlayID", overlayIDValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); - QScriptValue intersection = vec3ToScriptValue(engine, value.intersection); + ScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); + ScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { +bool RayToOverlayIntersectionResultFromScriptValue(const ScriptValue& object, RayToOverlayIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); - QScriptValue overlayIDValue = object.property("overlayID"); + ScriptValue overlayIDValue = object.property("overlayID"); quuidFromScriptValue(overlayIDValue, value.overlayID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QScriptValue intersection = object.property("intersection"); + ScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } - QScriptValue surfaceNormal = object.property("surfaceNormal"); + ScriptValue surfaceNormal = object.property("surfaceNormal"); if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } value.extraInfo = object.property("extraInfo").toVariant().toMap(); + return true; } bool Overlays::isLoaded(const QUuid& id) { diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index e59880c6d98..d13e2f821cd 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -20,15 +20,16 @@ #include #include -#include #include +#include #include "Overlay.h" #include class PickRay; +class ScriptEngine; /*@jsdoc * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection}. @@ -52,8 +53,8 @@ class RayToOverlayIntersectionResult { QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); -QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); +ScriptValue RayToOverlayIntersectionResultToScriptValue(ScriptEngine* engine, const RayToOverlayIntersectionResult& value); +bool RayToOverlayIntersectionResultFromScriptValue(const ScriptValue& object, RayToOverlayIntersectionResult& value); class ParabolaToOverlayIntersectionResult { public: @@ -120,7 +121,7 @@ class Overlays : public QObject { void cleanupAllOverlays(); - mutable QScriptEngine _scriptEngine; + mutable ScriptEnginePointer _scriptEngine; public slots: /*@jsdoc @@ -411,8 +412,8 @@ public slots: */ RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false, - const QScriptValue& include = QScriptValue(), - const QScriptValue& discard = QScriptValue(), + const ScriptValue& include = ScriptValue(), + const ScriptValue& discard = ScriptValue(), bool visibleOnly = false, bool collidableOnly = false); diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 2e811969eca..d005480a313 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME animation) -setup_hifi_library(Network Script) -link_hifi_libraries(shared graphics model-serializers) +setup_hifi_library(Network) +link_hifi_libraries(shared graphics model-serializers script-engine) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(hfm) diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index eb9e595c88e..0b44dfd8dbc 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -11,20 +11,20 @@ #include "AnimVariant.h" // which has AnimVariant/AnimVariantMap -#include -#include +#include #include -#include +#include +#include const AnimVariant AnimVariant::False = AnimVariant(); -QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const { +ScriptValue AnimVariantMap::animVariantMapToScriptValue(ScriptEngine* engine, const QStringList& names, bool useNames) const { if (QThread::currentThread() != engine->thread()) { qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread(); Q_ASSERT(false); - return QScriptValue(); + return ScriptValue(); } - QScriptValue target = engine->newObject(); + ScriptValue target = engine->newObject(); auto setOne = [&] (const QString& name, const AnimVariant& value) { switch (value.getType()) { case AnimVariant::Type::Bool: @@ -74,7 +74,7 @@ void AnimVariantMap::copyVariantsFrom(const AnimVariantMap& other) { } } -void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { +void AnimVariantMap::animVariantMapFromScriptValue(const ScriptValue& source) { if (QThread::currentThread() != source.engine()->thread()) { qCWarning(animation) << "Cannot examine Javacript object from non-script thread" << QThread::currentThread(); Q_ASSERT(false); @@ -84,43 +84,43 @@ void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { // Whenever we identify a new outbound type in animVariantMapToScriptValue above, or a new inbound type in the code that follows here, // we would enter it into the dictionary. Then switch on that type here, with the code that follow being executed only if // the type is not known. One problem with that is that there is no checking that two different script use the same name differently. - QScriptValueIterator property(source); - // Note: QScriptValueIterator iterates only over source's own properties. It does not follow the prototype chain. - while (property.hasNext()) { - property.next(); - QScriptValue value = property.value(); + ScriptValueIteratorPointer property(source.newIterator()); + // Note: ScriptValueIterator iterates only over source's own properties. It does not follow the prototype chain. + while (property->hasNext()) { + property->next(); + ScriptValue value = property->value(); if (value.isBool()) { - set(property.name(), value.toBool()); + set(property->name(), value.toBool()); } else if (value.isString()) { - set(property.name(), value.toString()); + set(property->name(), value.toString()); } else if (value.isNumber()) { int asInteger = value.toInt32(); float asFloat = value.toNumber(); if (asInteger == asFloat) { - set(property.name(), asInteger); + set(property->name(), asInteger); } else { - set(property.name(), asFloat); + set(property->name(), asFloat); } } else { // Try to get x,y,z and possibly w if (value.isObject()) { - QScriptValue x = value.property("x"); + ScriptValue x = value.property("x"); if (x.isNumber()) { - QScriptValue y = value.property("y"); + ScriptValue y = value.property("y"); if (y.isNumber()) { - QScriptValue z = value.property("z"); + ScriptValue z = value.property("z"); if (z.isNumber()) { - QScriptValue w = value.property("w"); + ScriptValue w = value.property("w"); if (w.isNumber()) { - set(property.name(), glm::quat(w.toNumber(), x.toNumber(), y.toNumber(), z.toNumber())); + set(property->name(), glm::quat(w.toNumber(), x.toNumber(), y.toNumber(), z.toNumber())); } else { - set(property.name(), glm::vec3(x.toNumber(), y.toNumber(), z.toNumber())); + set(property->name(), glm::vec3(x.toNumber(), y.toNumber(), z.toNumber())); } continue; // we got either a vector or quaternion object, so don't fall through to warning } } } } - qCWarning(animation) << "Ignoring unrecognized data" << value.toString() << "for animation property" << property.name(); + qCWarning(animation) << "Ignoring unrecognized data " << value.toString() << " for animation property " << property->name(); Q_ASSERT(false); } } diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index a8bdb885e54..83cebaa7af1 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -17,10 +17,12 @@ #include #include #include -#include #include #include #include "AnimationLogging.h" +#include + +class ScriptEngine; class AnimVariant { public: @@ -229,9 +231,9 @@ class AnimVariantMap { } // Answer a Plain Old Javascript Object (for the given engine) all of our values set as properties. - QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const; + ScriptValue animVariantMapToScriptValue(ScriptEngine* engine, const QStringList& names, bool useNames) const; // Side-effect us with the value of object's own properties. (No inherited properties.) - void animVariantMapFromScriptValue(const QScriptValue& object); + void animVariantMapFromScriptValue(const ScriptValue& object); void copyVariantsFrom(const AnimVariantMap& other); // For stat debugging. @@ -274,7 +276,7 @@ class AnimVariantMap { glm::quat _rigToGeometryRot; }; -typedef std::function AnimVariantResultHandler; +typedef std::function AnimVariantResultHandler; Q_DECLARE_METATYPE(AnimVariantResultHandler); Q_DECLARE_METATYPE(AnimVariantMap) diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index b1f0cc13a2f..95611cb646e 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -14,8 +14,6 @@ #include #include -#include -#include #include #include diff --git a/libraries/animation/src/AnimationObject.cpp b/libraries/animation/src/AnimationObject.cpp index bcbf4971995..6782768f5b4 100644 --- a/libraries/animation/src/AnimationObject.cpp +++ b/libraries/animation/src/AnimationObject.cpp @@ -11,27 +11,34 @@ #include "AnimationObject.h" -#include +#include +#include +#include +#include #include "AnimationCache.h" +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + registerAnimationTypes(manager->engine().get()); +}); + QStringList AnimationObject::getJointNames() const { - return qscriptvalue_cast(thisObject())->getJointNames(); + return scriptvalue_cast(thisObject())->getJointNames(); } QVector AnimationObject::getFrames() const { - return qscriptvalue_cast(thisObject())->getFrames(); + return scriptvalue_cast(thisObject())->getFrames(); } QVector AnimationFrameObject::getRotations() const { - return qscriptvalue_cast(thisObject()).rotations; + return scriptvalue_cast(thisObject()).rotations; } -void registerAnimationTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); +void registerAnimationTypes(ScriptEngine* engine) { + scriptRegisterSequenceMetaType >(engine); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); + new AnimationFrameObject(), ScriptEngine::ScriptOwnership)); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AnimationObject(), QScriptEngine::ScriptOwnership)); + new AnimationObject(), ScriptEngine::ScriptOwnership)); } diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index 466deb5e963..d170b15b082 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -13,11 +13,11 @@ #define hifi_AnimationObject_h #include -#include #include +#include "Scriptable.h" -class QScriptEngine; +class ScriptEngine; /*@jsdoc * Information about an animation resource, created by {@link AnimationCache.getAnimation}. @@ -35,7 +35,7 @@ class QScriptEngine; * @property {AnimationFrameObject[]} frames - The frames in the animation. Read-only. */ /// Scriptable wrapper for animation pointers. -class AnimationObject : public QObject, protected QScriptable { +class AnimationObject : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(QStringList jointNames READ getJointNames) Q_PROPERTY(QVector frames READ getFrames) @@ -72,7 +72,7 @@ class AnimationObject : public QObject, protected QScriptable { * @property {Quat[]} rotations - Joint rotations. Read-only. */ /// Scriptable wrapper for animation frames. -class AnimationFrameObject : public QObject, protected QScriptable { +class AnimationFrameObject : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(QVector rotations READ getRotations) @@ -86,6 +86,6 @@ class AnimationFrameObject : public QObject, protected QScriptable { Q_INVOKABLE QVector getRotations() const; }; -void registerAnimationTypes(QScriptEngine* engine); +void registerAnimationTypes(ScriptEngine* engine); #endif // hifi_AnimationObject_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ba042d31a88..46dff832c9c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include @@ -21,7 +20,10 @@ #include #include #include +#include +#include #include +#include #include #include "AnimationLogging.h" @@ -1584,7 +1586,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } // Allow script to add/remove handlers and report results, from within their thread. -QScriptValue Rig::addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { // called in script thread +ScriptValue Rig::addAnimationStateHandler(const ScriptValue& handler, const ScriptValue& propertiesList) { // called in script thread // validate argument types if (handler.isFunction() && (isListOfStrings(propertiesList) || propertiesList.isUndefined() || propertiesList.isNull())) { @@ -1599,14 +1601,14 @@ QScriptValue Rig::addAnimationStateHandler(QScriptValue handler, QScriptValue pr if (data.useNames) { data.propertyNames = propertiesList.toVariant().toStringList(); } - return QScriptValue(_nextStateHandlerId); // suitable for giving to removeAnimationStateHandler + return handler.engine()->newValue(_nextStateHandlerId); // suitable for giving to removeAnimationStateHandler } else { qCWarning(animation) << "Rig::addAnimationStateHandler invalid arguments, expected (function, string[])"; - return QScriptValue(QScriptValue::UndefinedValue); + return handler.engine() ? handler.engine()->undefinedValue() : ScriptValue(); } } -void Rig::removeAnimationStateHandler(QScriptValue identifier) { // called in script thread +void Rig::removeAnimationStateHandler(const ScriptValue& identifier) { // called in script thread // validate arguments if (identifier.isNumber()) { QMutexLocker locker(&_stateMutex); @@ -1616,7 +1618,7 @@ void Rig::removeAnimationStateHandler(QScriptValue identifier) { // called in sc } } -void Rig::animationStateHandlerResult(int identifier, QScriptValue result) { // called synchronously from script +void Rig::animationStateHandlerResult(int identifier, const ScriptValue& result) { // called synchronously from script QMutexLocker locker(&_stateMutex); auto found = _stateHandlers.find(identifier); if (found == _stateHandlers.end()) { @@ -1635,9 +1637,9 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh // call out: int identifier = data.key(); StateHandler& value = data.value(); - QScriptValue& function = value.function; + ScriptValue& function = value.function; int rigId = _rigId; - auto handleResult = [rigId, identifier](QScriptValue result) { // called in script thread to get the result back to us. + auto handleResult = [rigId, identifier](const ScriptValue& result) { // called in script thread to get the result back to us. // Hold the rigRegistryMutex to ensure thread-safe access to the rigRegistry, but // also to prevent the rig from being deleted while this lambda is being executed. std::lock_guard guard(rigRegistryMutex); @@ -1650,14 +1652,35 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh rig->animationStateHandlerResult(identifier, result); } }; - // invokeMethod makes a copy of the args, and copies of AnimVariantMap do copy the underlying map, so this will correctly capture - // the state of _animVars and allow continued changes to _animVars in this thread without conflict. - QMetaObject::invokeMethod(function.engine(), "callAnimationStateHandler", Qt::QueuedConnection, - Q_ARG(QScriptValue, function), - Q_ARG(AnimVariantMap, _animVars), - Q_ARG(QStringList, value.propertyNames), - Q_ARG(bool, value.useNames), - Q_ARG(AnimVariantResultHandler, handleResult)); + + { + // make references to the parameters for the lambda here, but let the lambda be the one to take the copies + // Copies of AnimVariantMap do copy the underlying map, so this will correctly capture + // the state of _animVars and allow continued changes to _animVars in this thread without conflict. + const AnimVariantMap& animVars = _animVars; + ScriptEnginePointer engine = function.engine(); + const QStringList& names = value.propertyNames; + bool useNames = value.useNames; + + QMetaObject::invokeMethod( + engine->manager(), + [function, animVars, names, useNames, handleResult, engine] { + ScriptValue javascriptParameters = animVars.animVariantMapToScriptValue(engine.get(), names, useNames); + ScriptValueList callingArguments; + callingArguments << javascriptParameters; + ScriptValue result = function.call(ScriptValue(), callingArguments); + + // validate result from callback function. + if (result.isValid() && result.isObject()) { + handleResult(result); + } else { + qCWarning(animation) << "Rig::updateAnimationStateHandlers invalid return argument from " + "callback, expected an object"; + } + }, + Qt::QueuedConnection); + } + // It turns out that, for thread-safety reasons, ScriptEngine::callAnimationStateHandler will invoke itself if called from other // than the script thread. Thus the above _could_ be replaced with an ordinary call, which will then trigger the same // invokeMethod as is done explicitly above. However, the script-engine library depends on this animation library, not vice versa. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c58be799cfe..9b7704bc159 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -16,10 +16,10 @@ #include #include -#include #include #include #include +#include #include "AnimNode.h" #include "AnimNodeLoader.h" @@ -40,7 +40,7 @@ class Rig : public QObject { struct StateHandler { AnimVariantMap results; QStringList propertyNames; - QScriptValue function; + ScriptValue function; bool useNames; }; @@ -205,9 +205,9 @@ class Rig : public QObject { AnimNode::ConstPointer getAnimNode() const { return _animNode; } AnimNode::ConstPointer findAnimNodeByName(const QString& name) const; AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; } - QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList); - void removeAnimationStateHandler(QScriptValue handler); - void animationStateHandlerResult(int identifier, QScriptValue result); + ScriptValue addAnimationStateHandler(const ScriptValue& handler, const ScriptValue& propertiesList); + void removeAnimationStateHandler(const ScriptValue& handler); + void animationStateHandlerResult(int identifier, const ScriptValue& result); // rig space bool getModelRegistrationPoint(glm::vec3& modelRegistrationPointOut) const; diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 6b88292dd4e..03a9fc087ba 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -6,6 +6,7 @@ setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) +include_hifi_library_headers(script-engine) if (ANDROID) else () diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index a8a398c14ff..7260e21259d 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -5,4 +5,4 @@ if (ANDROID) add_definitions("-D__STDC_CONSTANT_MACROS") endif () -link_hifi_libraries(networking shared plugins) +link_hifi_libraries(networking shared plugins script-engine) diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index 186689d2b46..bed083d7dbd 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -10,6 +10,18 @@ #include "AudioEffectOptions.h" +#include +#include +#include +#include + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine(); + + ScriptValue audioEffectOptionsConstructorValue = scriptEngine->newFunction(AudioEffectOptions::constructor); + scriptEngine->globalObject().setProperty("AudioEffectOptions", audioEffectOptionsConstructorValue); +}); + static const QString BANDWIDTH_HANDLE = "bandwidth"; static const QString PRE_DELAY_HANDLE = "preDelay"; static const QString LATE_DELAY_HANDLE = "lateDelay"; @@ -54,7 +66,7 @@ static const float LATE_MIX_LEFT_DEFAULT = 90.0f; static const float LATE_MIX_RIGHT_DEFAULT = 90.0f; static const float WET_DRY_MIX_DEFAULT = 50.0f; -static void setOption(QScriptValue arguments, const QString name, float defaultValue, float& variable) { +static void setOption(const ScriptValue& arguments, const QString name, float defaultValue, float& variable) { variable = arguments.property(name).isNumber() ? (float)arguments.property(name).toNumber() : defaultValue; } @@ -83,7 +95,7 @@ static void setOption(QScriptValue arguments, const QString name, float defaultV * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail. * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%). */ -AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) { +AudioEffectOptions::AudioEffectOptions(const ScriptValue& arguments) { setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth); setOption(arguments, PRE_DELAY_HANDLE, PRE_DELAY_DEFAULT, _preDelay); setOption(arguments, LATE_DELAY_HANDLE, LATE_DELAY_DEFAULT, _lateDelay); @@ -137,6 +149,6 @@ AudioEffectOptions& AudioEffectOptions::operator=(const AudioEffectOptions &othe return *this; } -QScriptValue AudioEffectOptions::constructor(QScriptContext* context, QScriptEngine* engine) { - return engine->newQObject(new AudioEffectOptions(context->argument(0))); +ScriptValue AudioEffectOptions::constructor(ScriptContext* context, ScriptEngine* engine) { + return engine->newQObject(new AudioEffectOptions(context->argument(0)), ScriptEngine::ScriptOwnership); } diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 14b39e0f33c..7ac2c83a248 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -12,8 +12,10 @@ #define hifi_AudioEffectOptions_h #include -#include -#include +#include + +class ScriptContext; +class ScriptEngine; /*@jsdoc * Audio effect options used by the {@link Audio} API. @@ -78,11 +80,11 @@ class AudioEffectOptions : public QObject { Q_PROPERTY(float wetDryMix READ getWetDryMix WRITE setWetDryMix) public: - AudioEffectOptions(QScriptValue arguments = QScriptValue()); + AudioEffectOptions(const ScriptValue& arguments = ScriptValue()); AudioEffectOptions(const AudioEffectOptions &other); AudioEffectOptions& operator=(const AudioEffectOptions &other); - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine); float getBandwidth() const { return _bandwidth; } void setBandwidth(float bandwidth) { _bandwidth = bandwidth; } diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 39b807fd769..804d935a46d 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -11,9 +11,9 @@ #include "AudioInjectorOptions.h" -#include - -#include +#include +#include +#include #include "AudioLogging.h" @@ -32,8 +32,8 @@ AudioInjectorOptions::AudioInjectorOptions() : { } -QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions) { - QScriptValue obj = engine->newObject(); +ScriptValue injectorOptionsToScriptValue(ScriptEngine* engine, const AudioInjectorOptions& injectorOptions) { + ScriptValue obj = engine->newObject(); if (injectorOptions.positionSet) { obj.setProperty("position", vec3ToScriptValue(engine, injectorOptions.position)); } @@ -66,10 +66,10 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje * @property {boolean} ignorePenumbra=false -

Deprecated: This property is deprecated and will be * removed.

*/ -void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) { +bool injectorOptionsFromScriptValue(const ScriptValue& object, AudioInjectorOptions& injectorOptions) { if (!object.isObject()) { qWarning() << "Audio injector options is not an object."; - return; + return false; } if (injectorOptions.positionSet == false) { @@ -77,53 +77,54 @@ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOpt } injectorOptions.positionSet = false; - QScriptValueIterator it(object); - while (it.hasNext()) { - it.next(); + ScriptValueIteratorPointer it(object.newIterator()); + while (it->hasNext()) { + it->next(); - if (it.name() == "position") { + if (it->name() == "position") { vec3FromScriptValue(object.property("position"), injectorOptions.position); injectorOptions.positionSet = true; - } else if (it.name() == "orientation") { + } else if (it->name() == "orientation") { quatFromScriptValue(object.property("orientation"), injectorOptions.orientation); - } else if (it.name() == "volume") { - if (it.value().isNumber()) { - injectorOptions.volume = it.value().toNumber(); + } else if (it->name() == "volume") { + if (it->value().isNumber()) { + injectorOptions.volume = it->value().toNumber(); } else { qCWarning(audio) << "Audio injector options: volume is not a number"; } - } else if (it.name() == "loop") { - if (it.value().isBool()) { - injectorOptions.loop = it.value().toBool(); + } else if (it->name() == "loop") { + if (it->value().isBool()) { + injectorOptions.loop = it->value().toBool(); } else { qCWarning(audio) << "Audio injector options: loop is not a boolean"; } - } else if (it.name() == "ignorePenumbra") { - if (it.value().isBool()) { - injectorOptions.ignorePenumbra = it.value().toBool(); + } else if (it->name() == "ignorePenumbra") { + if (it->value().isBool()) { + injectorOptions.ignorePenumbra = it->value().toBool(); } else { qCWarning(audio) << "Audio injector options: ignorePenumbra is not a boolean"; } - } else if (it.name() == "localOnly") { - if (it.value().isBool()) { - injectorOptions.localOnly = it.value().toBool(); + } else if (it->name() == "localOnly") { + if (it->value().isBool()) { + injectorOptions.localOnly = it->value().toBool(); } else { qCWarning(audio) << "Audio injector options: localOnly is not a boolean"; } - } else if (it.name() == "secondOffset") { - if (it.value().isNumber()) { - injectorOptions.secondOffset = it.value().toNumber(); + } else if (it->name() == "secondOffset") { + if (it->value().isNumber()) { + injectorOptions.secondOffset = it->value().toNumber(); } else { qCWarning(audio) << "Audio injector options: secondOffset is not a number"; } - } else if (it.name() == "pitch") { - if (it.value().isNumber()) { - injectorOptions.pitch = it.value().toNumber(); + } else if (it->name() == "pitch") { + if (it->value().isNumber()) { + injectorOptions.pitch = it->value().toNumber(); } else { qCWarning(audio) << "Audio injector options: pitch is not a number"; } } else { - qCWarning(audio) << "Unknown audio injector option:" << it.name(); + qCWarning(audio) << "Unknown audio injector option:" << it->name(); } } + return true; } diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index 5dec8a02403..fda05f01177 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -12,10 +12,11 @@ #ifndef hifi_AudioInjectorOptions_h #define hifi_AudioInjectorOptions_h -#include - #include #include +#include + +class ScriptEngine; class AudioInjectorOptions { public: @@ -35,7 +36,7 @@ class AudioInjectorOptions { Q_DECLARE_METATYPE(AudioInjectorOptions); -QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions); -void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions); +ScriptValue injectorOptionsToScriptValue(ScriptEngine* engine, const AudioInjectorOptions& injectorOptions); +bool injectorOptionsFromScriptValue(const ScriptValue& object, AudioInjectorOptions& injectorOptions); #endif // hifi_AudioInjectorOptions_h diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp similarity index 85% rename from libraries/script-engine/src/AudioScriptingInterface.cpp rename to libraries/audio/src/AudioScriptingInterface.cpp index a55cac292f8..ec7a50e5df1 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -16,11 +16,23 @@ #include #include "ScriptAudioInjector.h" -#include "ScriptEngineLogging.h" +#include +#include +#include +#include -void registerAudioMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); - qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + registerAudioMetaTypes(scriptEngine); + scriptEngine->registerGlobalObject("Audio", DependencyManager::get().data()); +}); + + +void registerAudioMetaTypes(ScriptEngine* engine) { + scriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); + scriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); } diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h similarity index 98% rename from libraries/script-engine/src/AudioScriptingInterface.h rename to libraries/audio/src/AudioScriptingInterface.h index 6bfb7352ee8..0c0e3144fa5 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -15,12 +15,13 @@ #ifndef hifi_AudioScriptingInterface_h #define hifi_AudioScriptingInterface_h -#include -#include +#include "AbstractAudioInterface.h" +#include "AudioInjector.h" #include -#include +#include "Sound.h" class ScriptAudioInjector; +class ScriptEngine; /// Provides the Audio scripting API class AudioScriptingInterface : public QObject, public Dependency { @@ -291,7 +292,7 @@ class AudioScriptingInterface : public QObject, public Dependency { AbstractAudioInterface* _localAudioInterface { nullptr }; }; -void registerAudioMetaTypes(QScriptEngine* engine); +void registerAudioMetaTypes(ScriptEngine* engine); #endif // hifi_AudioScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/audio/src/ScriptAudioInjector.cpp similarity index 62% rename from libraries/script-engine/src/ScriptAudioInjector.cpp rename to libraries/audio/src/ScriptAudioInjector.cpp index 0e42ec31e70..28af8b849aa 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/audio/src/ScriptAudioInjector.cpp @@ -1,6 +1,6 @@ // // ScriptAudioInjector.cpp -// libraries/script-engine/src +// libraries/audio/src // // Created by Stephen Birarda on 2015-02-11. // Copyright 2015 High Fidelity, Inc. @@ -11,19 +11,30 @@ #include "ScriptAudioInjector.h" -#include "ScriptEngineLogging.h" +#include +#include +#include +#include +#include -QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in) { +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine, injectorToScriptValue, injectorFromScriptValue); +}); + +ScriptValue injectorToScriptValue(ScriptEngine* engine, ScriptAudioInjector* const& in) { // The AudioScriptingInterface::playSound method can return null, so we need to account for that. if (!in) { - return QScriptValue(QScriptValue::NullValue); + return engine->nullValue(); } - return engine->newQObject(in, QScriptEngine::ScriptOwnership); + return engine->newQObject(in, ScriptEngine::ScriptOwnership); } -void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& out) { +bool injectorFromScriptValue(const ScriptValue& object, ScriptAudioInjector*& out) { out = qobject_cast(object.toQObject()); + return true; } ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/audio/src/ScriptAudioInjector.h similarity index 93% rename from libraries/script-engine/src/ScriptAudioInjector.h rename to libraries/audio/src/ScriptAudioInjector.h index 8818ffab43b..18d0ba72a48 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/audio/src/ScriptAudioInjector.h @@ -1,6 +1,6 @@ // // ScriptAudioInjector.h -// libraries/script-engine/src +// libraries/audio/src // // Created by Stephen Birarda on 2015-02-11. // Copyright 2015 High Fidelity, Inc. @@ -16,8 +16,11 @@ #define hifi_ScriptAudioInjector_h #include +#include -#include +#include "AudioInjectorManager.h" + +class ScriptEngine; /*@jsdoc * Plays or "injects" the content of an audio file. @@ -143,13 +146,13 @@ public slots: private: QWeakPointer _injector; - friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); + friend ScriptValue injectorToScriptValue(ScriptEngine* engine, ScriptAudioInjector* const& in); }; Q_DECLARE_METATYPE(ScriptAudioInjector*) -QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); -void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& out); +ScriptValue injectorToScriptValue(ScriptEngine* engine, ScriptAudioInjector* const& in); +bool injectorFromScriptValue(const ScriptValue& object, ScriptAudioInjector*& out); #endif // hifi_ScriptAudioInjector_h diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 7c5dd3813bc..e0f70cc121a 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "AudioRingBuffer.h" #include "AudioLogging.h" @@ -422,14 +424,15 @@ SoundProcessor::AudioProperties SoundProcessor::interpretAsMP3(const QByteArray& } -QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { - return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership); +ScriptValue soundSharedPointerToScriptValue(ScriptEngine* engine, const SharedSoundPointer& in) { + return engine->newQObject(new SoundScriptingInterface(in), ScriptEngine::ScriptOwnership); } -void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) { +bool soundSharedPointerFromScriptValue(const ScriptValue& object, SharedSoundPointer& out) { if (auto soundInterface = qobject_cast(object.toQObject())) { out = soundInterface->getSound(); } + return true; } SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) { diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index eb6bc67c669..f38d2991ce1 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -16,13 +16,15 @@ #include #include #include -#include +#include #include +#include #include "AudioConstants.h" class AudioData; +class ScriptEngine; using AudioDataPointer = std::shared_ptr; Q_DECLARE_METATYPE(AudioDataPointer); @@ -169,7 +171,7 @@ class SoundScriptingInterface : public QObject { }; Q_DECLARE_METATYPE(SharedSoundPointer) -QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in); -void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out); +ScriptValue soundSharedPointerToScriptValue(ScriptEngine* engine, const SharedSoundPointer& in); +bool soundSharedPointerFromScriptValue(const ScriptValue& object, SharedSoundPointer& out); #endif // hifi_Sound_h diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 0175d1113ae..964af7085e9 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,11 +1,10 @@ set(TARGET_NAME avatars-renderer) -setup_hifi_library(Network Script) -link_hifi_libraries(shared shaders gpu graphics animation material-networking model-networking script-engine render render-utils image entities-renderer physics) +setup_hifi_library(Network) +link_hifi_libraries(shared shaders gpu graphics animation material-networking model-networking render render-utils image entities-renderer physics recording) include_hifi_library_headers(avatars) include_hifi_library_headers(networking) include_hifi_library_headers(hfm) include_hifi_library_headers(model-serializers) -include_hifi_library_headers(recording) include_hifi_library_headers(ktx) include_hifi_library_headers(procedural) include_hifi_library_headers(audio) @@ -14,5 +13,6 @@ include_hifi_library_headers(octree) include_hifi_library_headers(task) include_hifi_library_headers(workload) include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h +include_hifi_library_headers(script-engine) target_bullet() diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index fc6d15cced9..17d0b1dd3d4 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME avatars) -setup_hifi_library(Network Script) -link_hifi_libraries(shared networking) +setup_hifi_library(Network) +link_hifi_libraries(shared networking script-engine) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index cfea4fedd53..373d1e30af3 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -35,6 +35,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -62,6 +67,14 @@ static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water #define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + registerAvatarTypes(scriptEngine); + scriptRegisterMetaType(scriptEngine, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue); + scriptRegisterMetaType(scriptEngine, AvatarEntityMapToScriptValue, AvatarEntityMapFromScriptValue); +}); + size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients) { return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float); } @@ -2534,69 +2547,69 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { } void AttachmentDataObject::setModelURL(const QString& modelURL) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.modelURL = modelURL; thisObject() = engine()->toScriptValue(data); } QString AttachmentDataObject::getModelURL() const { - return qscriptvalue_cast(thisObject()).modelURL.toString(); + return scriptvalue_cast(thisObject()).modelURL.toString(); } void AttachmentDataObject::setJointName(const QString& jointName) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.jointName = jointName; thisObject() = engine()->toScriptValue(data); } QString AttachmentDataObject::getJointName() const { - return qscriptvalue_cast(thisObject()).jointName; + return scriptvalue_cast(thisObject()).jointName; } void AttachmentDataObject::setTranslation(const glm::vec3& translation) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.translation = translation; thisObject() = engine()->toScriptValue(data); } glm::vec3 AttachmentDataObject::getTranslation() const { - return qscriptvalue_cast(thisObject()).translation; + return scriptvalue_cast(thisObject()).translation; } void AttachmentDataObject::setRotation(const glm::quat& rotation) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.rotation = rotation; thisObject() = engine()->toScriptValue(data); } glm::quat AttachmentDataObject::getRotation() const { - return qscriptvalue_cast(thisObject()).rotation; + return scriptvalue_cast(thisObject()).rotation; } void AttachmentDataObject::setScale(float scale) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.scale = scale; thisObject() = engine()->toScriptValue(data); } float AttachmentDataObject::getScale() const { - return qscriptvalue_cast(thisObject()).scale; + return scriptvalue_cast(thisObject()).scale; } void AttachmentDataObject::setIsSoft(bool isSoft) { - AttachmentData data = qscriptvalue_cast(thisObject()); + AttachmentData data = scriptvalue_cast(thisObject()); data.isSoft = isSoft; thisObject() = engine()->toScriptValue(data); } bool AttachmentDataObject::getIsSoft() const { - return qscriptvalue_cast(thisObject()).isSoft; + return scriptvalue_cast(thisObject()).isSoft; } -void registerAvatarTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); +void registerAvatarTypes(ScriptEngine* engine) { + scriptRegisterSequenceMetaType >(engine); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AttachmentDataObject(), QScriptEngine::ScriptOwnership)); + new AttachmentDataObject(), ScriptEngine::ScriptOwnership)); } void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { @@ -3140,40 +3153,41 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const { * @property {SubmeshIntersection} extraInfo - Extra information on the mesh intersected if mesh was picked against, * {} if it wasn't. */ -QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { - QScriptValue obj = engine->newObject(); +ScriptValue RayToAvatarIntersectionResultToScriptValue(ScriptEngine* engine, const RayToAvatarIntersectionResult& value) { + ScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); - QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); + ScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); obj.setProperty("avatarID", avatarIDValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); - QScriptValue intersection = vec3ToScriptValue(engine, value.intersection); + ScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); + ScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("jointIndex", value.jointIndex); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& value) { +bool RayToAvatarIntersectionResultFromScriptValue(const ScriptValue& object, RayToAvatarIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); - QScriptValue avatarIDValue = object.property("avatarID"); + ScriptValue avatarIDValue = object.property("avatarID"); quuidFromScriptValue(avatarIDValue, value.avatarID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QScriptValue intersection = object.property("intersection"); + ScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } - QScriptValue surfaceNormal = object.property("surfaceNormal"); + ScriptValue surfaceNormal = object.property("surfaceNormal"); if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } value.jointIndex = object.property("jointIndex").toInt32(); value.extraInfo = object.property("extraInfo").toVariant().toMap(); + return true; } // these coefficients can be changed via JS for experimental tuning @@ -3186,8 +3200,8 @@ float AvatarData::_avatarSortCoefficientAge { 1.0f }; * An object with the UUIDs of avatar entities as keys and avatar entity properties objects as values. * @typedef {Object.} AvatarEntityMap */ -QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { - QScriptValue obj = engine->newObject(); +ScriptValue AvatarEntityMapToScriptValue(ScriptEngine* engine, const AvatarEntityMap& value) { + ScriptValue obj = engine->newObject(); for (auto entityID : value.keys()) { QByteArray entityProperties = value.value(entityID); QJsonDocument jsonEntityProperties = QJsonDocument::fromBinaryData(entityProperties); @@ -3197,7 +3211,7 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt QVariant variantEntityProperties = jsonEntityProperties.toVariant(); QVariantMap entityPropertiesMap = variantEntityProperties.toMap(); - QScriptValue scriptEntityProperties = variantMapToScriptValue(entityPropertiesMap, *engine); + ScriptValue scriptEntityProperties = variantMapToScriptValue(entityPropertiesMap, *engine); QString key = entityID.toString(); obj.setProperty(key, scriptEntityProperties); @@ -3205,19 +3219,20 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt return obj; } -void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value) { - QScriptValueIterator itr(object); - while (itr.hasNext()) { - itr.next(); - QUuid EntityID = QUuid(itr.name()); +bool AvatarEntityMapFromScriptValue(const ScriptValue& object, AvatarEntityMap& value) { + ScriptValueIteratorPointer itr(object.newIterator()); + while (itr->hasNext()) { + itr->next(); + QUuid EntityID = QUuid(itr->name()); - QScriptValue scriptEntityProperties = itr.value(); + ScriptValue scriptEntityProperties = itr->value(); QVariant variantEntityProperties = scriptEntityProperties.toVariant(); QJsonDocument jsonEntityProperties = QJsonDocument::fromVariant(variantEntityProperties); QByteArray binaryEntityProperties = jsonEntityProperties.toBinaryData(); value[EntityID] = binaryEntityProperties; } + return true; } const float AvatarData::DEFAULT_BUBBLE_SCALE = 2.4f; // magic number determined empirically diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 70f20caa26f..7231e882dea 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -32,8 +32,6 @@ #include #include #include -#include -#include #include #include @@ -50,12 +48,16 @@ #include #include #include +#include +#include #include "AABox.h" #include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" +class ScriptEngine; + using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; @@ -1924,7 +1926,7 @@ Q_DECLARE_METATYPE(AttachmentData) Q_DECLARE_METATYPE(QVector) /// Scriptable wrapper for attachments. -class AttachmentDataObject : public QObject, protected QScriptable { +class AttachmentDataObject : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(QString modelURL READ getModelURL WRITE setModelURL) Q_PROPERTY(QString jointName READ getJointName WRITE setJointName) @@ -1954,7 +1956,7 @@ class AttachmentDataObject : public QObject, protected QScriptable { Q_INVOKABLE bool getIsSoft() const; }; -void registerAvatarTypes(QScriptEngine* engine); +void registerAvatarTypes(ScriptEngine* engine); class RayToAvatarIntersectionResult { public: @@ -1968,8 +1970,8 @@ class RayToAvatarIntersectionResult { QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) -QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); -void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +ScriptValue RayToAvatarIntersectionResultToScriptValue(ScriptEngine* engine, const RayToAvatarIntersectionResult& results); +bool RayToAvatarIntersectionResultFromScriptValue(const ScriptValue& object, RayToAvatarIntersectionResult& results); // No JSDoc because it's not provided as a type to the script engine. class ParabolaToAvatarIntersectionResult { @@ -1986,8 +1988,8 @@ class ParabolaToAvatarIntersectionResult { Q_DECLARE_METATYPE(AvatarEntityMap) -QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value); -void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value); +ScriptValue AvatarEntityMapToScriptValue(ScriptEngine* engine, const AvatarEntityMap& value); +bool AvatarEntityMapFromScriptValue(const ScriptValue& object, AvatarEntityMap& value); // faux joint indexes (-1 means invalid) const int NO_JOINT_INDEX = 65535; // -1 diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index a67af18c40c..5335f8ab5b3 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -11,6 +11,26 @@ #include "ScriptAvatarData.h" +#include +#include + +ScriptValue avatarDataToScriptValue(ScriptEngine* engine, ScriptAvatarData* const& in) { + return engine->newQObject(in, ScriptEngine::ScriptOwnership); +} + +bool avatarDataFromScriptValue(const ScriptValue& object, ScriptAvatarData*& out) { + // This is not implemented because there are no slots/properties that take an AvatarSharedPointer from a script + assert(false); + out = nullptr; + return false; +} + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine, avatarDataToScriptValue, avatarDataFromScriptValue); +}); + ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { diff --git a/libraries/baking/CMakeLists.txt b/libraries/baking/CMakeLists.txt index 12fb1928771..bfb989c05c6 100644 --- a/libraries/baking/CMakeLists.txt +++ b/libraries/baking/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME baking) setup_hifi_library(Concurrent) -link_hifi_libraries(shared shaders graphics networking procedural graphics-scripting ktx image model-serializers model-baker task) +link_hifi_libraries(shared shaders graphics networking procedural graphics-scripting ktx image model-serializers model-baker task script-engine) include_hifi_library_headers(gpu) include_hifi_library_headers(hfm) include_hifi_library_headers(material-networking) diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index 540a2ee358b..68ca7316c40 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include #include @@ -28,6 +30,7 @@ std::function MaterialBaker::_getNextOvenWorkerThreadOperator; static int materialNum = 0; MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, QUrl destinationPath) : + _scriptEngine(newScriptEngine()), _materialData(materialData), _isURL(isURL), _destinationPath(destinationPath), @@ -209,13 +212,15 @@ void MaterialBaker::outputMaterial() { if (_materialResource->parsedMaterials.networkMaterials.size() == 1) { auto networkMaterial = _materialResource->parsedMaterials.networkMaterials.begin(); auto scriptableMaterial = scriptable::ScriptableMaterial(networkMaterial->second); - QVariant materialVariant = scriptable::scriptableMaterialToScriptValue(&_scriptEngine, scriptableMaterial).toVariant(); + QVariant materialVariant = + scriptable::scriptableMaterialToScriptValue(_scriptEngine.get(), scriptableMaterial).toVariant(); json.insert("materials", QJsonDocument::fromVariant(materialVariant).object()); } else { QJsonArray materialArray; for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) { auto scriptableMaterial = scriptable::ScriptableMaterial(networkMaterial.second); - QVariant materialVariant = scriptable::scriptableMaterialToScriptValue(&_scriptEngine, scriptableMaterial).toVariant(); + QVariant materialVariant = + scriptable::scriptableMaterialToScriptValue(_scriptEngine.get(), scriptableMaterial).toVariant(); materialArray.append(QJsonDocument::fromVariant(materialVariant).object()); } json.insert("materials", materialArray); diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h index cb8289cfdb5..e3340083292 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -20,6 +20,7 @@ #include "baking/TextureFileNamer.h" #include +#include static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; @@ -69,7 +70,7 @@ private slots: QString _textureOutputDir; QString _bakedMaterialData; - QScriptEngine _scriptEngine; + ScriptEnginePointer _scriptEngine; static std::function _getNextOvenWorkerThreadOperator; TextureFileNamer _textureFileNamer; diff --git a/libraries/controllers/CMakeLists.txt b/libraries/controllers/CMakeLists.txt index 9c6bbf4aaeb..ee1fc1d1630 100644 --- a/libraries/controllers/CMakeLists.txt +++ b/libraries/controllers/CMakeLists.txt @@ -1,10 +1,10 @@ set(TARGET_NAME controllers) # set a default root dir for each of our optional externals if it was not passed -setup_hifi_library(Script Qml) +setup_hifi_library(Qml) # use setup_hifi_library macro to setup our project and link appropriate Qt modules -link_hifi_libraries(shared) +link_hifi_libraries(shared script-engine) include_hifi_library_headers(networking) GroupSources("src/controllers") diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp index 75f747deaa4..1fad0b9c42d 100644 --- a/libraries/controllers/src/controllers/Pose.cpp +++ b/libraries/controllers/src/controllers/Pose.cpp @@ -8,10 +8,11 @@ #include "Pose.h" -#include -#include +#include +#include #include +#include namespace controller { @@ -39,8 +40,8 @@ namespace controller { * @property {Vec3} angularVelocity - Angular velocity in rad/s. * @property {boolean} valid - true if the pose is valid, otherwise false. */ - QScriptValue Pose::toScriptValue(QScriptEngine* engine, const Pose& pose) { - QScriptValue obj = engine->newObject(); + ScriptValue Pose::toScriptValue(ScriptEngine* engine, const Pose& pose) { + ScriptValue obj = engine->newObject(); obj.setProperty("translation", vec3ToScriptValue(engine, pose.translation)); obj.setProperty("rotation", quatToScriptValue(engine, pose.rotation)); obj.setProperty("velocity", vec3ToScriptValue(engine, pose.velocity)); @@ -49,7 +50,7 @@ namespace controller { return obj; } - void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) { + bool Pose::fromScriptValue(const ScriptValue& object, Pose& pose) { auto translation = object.property("translation"); auto rotation = object.property("rotation"); auto velocity = object.property("velocity"); @@ -66,6 +67,7 @@ namespace controller { } else { pose.valid = false; } + return true; } Pose Pose::transform(const glm::mat4& mat) const { diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h index 186bbdd7339..a1bcbffd2d0 100644 --- a/libraries/controllers/src/controllers/Pose.h +++ b/libraries/controllers/src/controllers/Pose.h @@ -10,9 +10,9 @@ #pragma once #ifndef hifi_controllers_Pose_h #define hifi_controllers_Pose_h +#include -class QScriptEngine; -class QScriptValue; +class ScriptEngine; #include @@ -44,8 +44,8 @@ namespace controller { Pose transform(const glm::mat4& mat) const; Pose postTransform(const glm::mat4& mat) const; - static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event); - static void fromScriptValue(const QScriptValue& object, Pose& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const Pose& event); + static bool fromScriptValue(const ScriptValue& object, Pose& event); }; } diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index e9a831859da..2187f4bc996 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -25,6 +25,26 @@ #include "InputDevice.h" #include "InputRecorder.h" +#include +#include +#include +#include + + +ScriptValue inputControllerToScriptValue(ScriptEngine* engine, controller::InputController* const& in) { + return engine->newQObject(in, ScriptEngine::QtOwnership); +} + +bool inputControllerFromScriptValue(const ScriptValue& object, controller::InputController*& out) { + out = qobject_cast(object.toQObject()); + return true; +} + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine, inputControllerToScriptValue, inputControllerFromScriptValue); +}); static QRegularExpression SANITIZE_NAME_EXPRESSION{ "[\\(\\)\\.\\s]" }; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 969b2d19d21..06b684c7acf 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -29,7 +29,6 @@ #include #include -#include #include #include diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 6a46dea7d94..d1fca933f54 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -26,6 +26,10 @@ #include "StateController.h" #include "InputRecorder.h" #include "Logging.h" +#include "ScriptValueUtils.h" +#include +#include +#include #include "impl/conditionals/AndConditional.h" #include "impl/conditionals/NotConditional.h" @@ -388,17 +392,17 @@ int inputPairMetaTypeId = qRegisterMetaType(); int poseMetaTypeId = qRegisterMetaType("Pose"); int handMetaTypeId = qRegisterMetaType(); -QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input); -void inputFromScriptValue(const QScriptValue& object, Input& input); -QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action); -void actionFromScriptValue(const QScriptValue& object, Action& action); -QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair); -void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair); -QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand); -void handFromScriptValue(const QScriptValue& object, controller::Hand& hand); - -QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) { - QScriptValue obj = engine->newObject(); +ScriptValue inputToScriptValue(ScriptEngine* engine, const Input& input); +bool inputFromScriptValue(const ScriptValue& object, Input& input); +ScriptValue actionToScriptValue(ScriptEngine* engine, const Action& action); +bool actionFromScriptValue(const ScriptValue& object, Action& action); +ScriptValue inputPairToScriptValue(ScriptEngine* engine, const Input::NamedPair& inputPair); +bool inputPairFromScriptValue(const ScriptValue& object, Input::NamedPair& inputPair); +ScriptValue handToScriptValue(ScriptEngine* engine, const controller::Hand& hand); +bool handFromScriptValue(const ScriptValue& object, controller::Hand& hand); + +ScriptValue inputToScriptValue(ScriptEngine* engine, const Input& input) { + ScriptValue obj = engine->newObject(); obj.setProperty("device", input.getDevice()); obj.setProperty("channel", input.getChannel()); obj.setProperty("type", (unsigned short)input.getType()); @@ -406,51 +410,55 @@ QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) { return obj; } -void inputFromScriptValue(const QScriptValue& object, Input& input) { +bool inputFromScriptValue(const ScriptValue& object, Input& input) { input.id = object.property("id").toInt32(); + return true; } -QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action) { - QScriptValue obj = engine->newObject(); +ScriptValue actionToScriptValue(ScriptEngine* engine, const Action& action) { + ScriptValue obj = engine->newObject(); auto userInputMapper = DependencyManager::get(); obj.setProperty("action", (int)action); obj.setProperty("actionName", userInputMapper->getActionName(action)); return obj; } -void actionFromScriptValue(const QScriptValue& object, Action& action) { +bool actionFromScriptValue(const ScriptValue& object, Action& action) { action = Action(object.property("action").toVariant().toInt()); + return true; } -QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair) { - QScriptValue obj = engine->newObject(); +ScriptValue inputPairToScriptValue(ScriptEngine* engine, const Input::NamedPair& inputPair) { + ScriptValue obj = engine->newObject(); obj.setProperty("input", inputToScriptValue(engine, inputPair.first)); obj.setProperty("inputName", inputPair.second); return obj; } -void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair) { +bool inputPairFromScriptValue(const ScriptValue& object, Input::NamedPair& inputPair) { inputFromScriptValue(object.property("input"), inputPair.first); inputPair.second = QString(object.property("inputName").toVariant().toString()); + return true; } -QScriptValue handToScriptValue(QScriptEngine* engine, const controller::Hand& hand) { - return engine->newVariant((int)hand); +ScriptValue handToScriptValue(ScriptEngine* engine, const controller::Hand& hand) { + return engine->newValue((int)hand); } -void handFromScriptValue(const QScriptValue& object, controller::Hand& hand) { +bool handFromScriptValue(const ScriptValue& object, controller::Hand& hand) { hand = Hand(object.toVariant().toInt()); + return true; } -void UserInputMapper::registerControllerTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); - qScriptRegisterSequenceMetaType(engine); - qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); - qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); - qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); - qScriptRegisterMetaType(engine, handToScriptValue, handFromScriptValue); +void UserInputMapper::registerControllerTypes(ScriptEngine* engine) { + scriptRegisterSequenceMetaType >(engine); + scriptRegisterSequenceMetaType(engine); + scriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); + scriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); + scriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); + scriptRegisterMetaType(engine, handToScriptValue, handFromScriptValue); - qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); + scriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); } Input UserInputMapper::makeStandardInput(controller::StandardButtonChannel button) { @@ -658,7 +666,7 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) { return Endpoint::Pointer(); } -Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) { +Endpoint::Pointer UserInputMapper::endpointFor(const ScriptValue& endpoint) { if (endpoint.isNumber()) { return endpointFor(Input(endpoint.toInt32())); } @@ -672,7 +680,7 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) { int length = endpoint.property("length").toInteger(); Endpoint::List children; for (int i = 0; i < length; i++) { - QScriptValue arrayItem = endpoint.property(i); + ScriptValue arrayItem = endpoint.property(i); Endpoint::Pointer destination = endpointFor(arrayItem); if (!destination) { return Endpoint::Pointer(); @@ -883,7 +891,7 @@ Conditional::Pointer UserInputMapper::conditionalFor(const QJSValue& condition) return Conditional::Pointer(); } -Conditional::Pointer UserInputMapper::conditionalFor(const QScriptValue& condition) { +Conditional::Pointer UserInputMapper::conditionalFor(const ScriptValue& condition) { if (condition.isArray()) { int length = condition.property("length").toInteger(); Conditional::List children; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index ee8b34193fb..3fcce461c01 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -32,6 +31,9 @@ #include "Actions.h" #include "StateController.h" +class ScriptEngine; +class ScriptValue; + namespace controller { class RouteBuilderProxy; @@ -64,7 +66,7 @@ namespace controller { virtual ~UserInputMapper(); - static void registerControllerTypes(QScriptEngine* engine); + static void registerControllerTypes(ScriptEngine* engine); void registerDevice(InputDevice::Pointer device); InputDevice::Pointer getDevice(const Input& input); @@ -164,10 +166,10 @@ namespace controller { void enableMapping(const MappingPointer& mapping); void disableMapping(const MappingPointer& mapping); EndpointPointer endpointFor(const QJSValue& endpoint); - EndpointPointer endpointFor(const QScriptValue& endpoint); + EndpointPointer endpointFor(const ScriptValue& endpoint); EndpointPointer compositeEndpointFor(EndpointPointer first, EndpointPointer second); ConditionalPointer conditionalFor(const QJSValue& endpoint); - ConditionalPointer conditionalFor(const QScriptValue& endpoint); + ConditionalPointer conditionalFor(const ScriptValue& endpoint); ConditionalPointer conditionalFor(const Input& endpoint) const; MappingPointer parseMapping(const QJsonValue& json); diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index 692e427e165..00a4c264a9b 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -20,8 +20,6 @@ #include "../Input.h" #include "../Pose.h" -class QScriptValue; - namespace controller { /* * Encapsulates a particular input / output, diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index f230fb83dc9..2a175137036 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -9,7 +9,6 @@ #include "Filter.h" #include -#include #include #include diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp index ff4725fb667..64c837f34b7 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp @@ -17,6 +17,7 @@ #include "RouteBuilderProxy.h" #include "../ScriptingInterface.h" #include "../Logging.h" +#include using namespace controller; @@ -26,7 +27,7 @@ QObject* MappingBuilderProxy::fromQml(const QJSValue& source) { return from(sourceEndpoint); } -QObject* MappingBuilderProxy::from(const QScriptValue& source) { +QObject* MappingBuilderProxy::from(const ScriptValue& source) { qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); auto sourceEndpoint = _parent.endpointFor(source); return from(sourceEndpoint); @@ -49,7 +50,7 @@ QObject* MappingBuilderProxy::makeAxisQml(const QJSValue& source1, const QJSValu return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); } -QObject* MappingBuilderProxy::makeAxis(const QScriptValue& source1, const QScriptValue& source2) { +QObject* MappingBuilderProxy::makeAxis(const ScriptValue& source1, const ScriptValue& source2) { auto source1Endpoint = _parent.endpointFor(source1); auto source2Endpoint = _parent.endpointFor(source2); return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index bf943f373f7..8e60386110f 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -16,8 +16,8 @@ #include "Endpoint.h" class QJSValue; -class QScriptValue; class QJsonValue; +class ScriptValue; namespace controller { @@ -165,7 +165,7 @@ class MappingBuilderProxy : public QObject { * of the route data. If a function, it must return a number or a {@link Pose} value as the route data. * @returns {RouteObject} A route ready for mapping to an action or function using {@link RouteObject} methods. */ - Q_INVOKABLE QObject* from(const QScriptValue& source); + Q_INVOKABLE QObject* from(const ScriptValue& source); /*@jsdoc * Creates a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative @@ -187,7 +187,7 @@ class MappingBuilderProxy : public QObject { * Controller.disableMapping(MAPPING_NAME); * }); */ - Q_INVOKABLE QObject* makeAxis(const QScriptValue& source1, const QScriptValue& source2); + Q_INVOKABLE QObject* makeAxis(const ScriptValue& source1, const ScriptValue& source2); /*@jsdoc * Enables or disables the mapping. When enabled, the routes in the mapping take effect. diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 91027a1a9c8..c747e920a4b 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -17,6 +17,7 @@ #include "MappingBuilderProxy.h" #include "../ScriptingInterface.h" #include "../Logging.h" +#include #include "filters/ClampFilter.h" #include "filters/ConstrainToIntegerFilter.h" @@ -43,7 +44,7 @@ void RouteBuilderProxy::toQml(const QJSValue& destination) { return to(destinationEndpoint); } -void RouteBuilderProxy::to(const QScriptValue& destination) { +void RouteBuilderProxy::to(const ScriptValue& destination) { qCDebug(controllers) << "Completing route " << destination.toString(); auto destinationEndpoint = _parent.endpointFor(destination); return to(destinationEndpoint); @@ -65,7 +66,7 @@ QObject* RouteBuilderProxy::peek(bool enable) { return this; } -QObject* RouteBuilderProxy::when(const QScriptValue& expression) { +QObject* RouteBuilderProxy::when(const ScriptValue& expression) { // FIXME: Support "!" conditional in simple expression and array expression. // Note that "!" is supported when parsing a JSON file, in UserInputMapper::parseConditional(). auto newConditional = _parent.conditionalFor(expression); diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index d9fec2f8084..c242c9597a3 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -18,8 +18,8 @@ #include "../UserInputMapper.h" class QJSValue; -class QScriptValue; class QJsonValue; +class ScriptValue; namespace controller { @@ -115,7 +115,7 @@ class RouteBuilderProxy : public QObject { * Controller.disableMapping(MAPPING_NAME); * }); */ - Q_INVOKABLE void to(const QScriptValue& destination); + Q_INVOKABLE void to(const ScriptValue& destination); /*@jsdoc * Enables or disables writing debug information for a route to the program log. @@ -193,7 +193,7 @@ class RouteBuilderProxy : public QObject { * Controller.disableMapping(MAPPING_NAME); * }); */ - Q_INVOKABLE QObject* when(const QScriptValue& expression); + Q_INVOKABLE QObject* when(const ScriptValue& expression); /*@jsdoc * Filters numeric route values to lie between two values; values outside this range are not passed on through the diff --git a/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h index 800692d02c7..156fdaff44b 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h @@ -11,8 +11,7 @@ #define hifi_Controllers_ScriptConditional_h #include - -#include +#include #include "../Conditional.h" @@ -21,12 +20,12 @@ namespace controller { class ScriptConditional : public QObject, public Conditional { Q_OBJECT; public: - ScriptConditional(const QScriptValue& callable) : _callable(callable) { } + ScriptConditional(const ScriptValue& callable) : _callable(callable) {} virtual bool satisfied() override; protected: Q_INVOKABLE void updateValue(); private: - QScriptValue _callable; + ScriptValue _callable; bool _lastValue { false }; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp index 9f971d2f04c..8ed5222160c 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -11,11 +11,13 @@ #include +#include +#include #include using namespace controller; -QString formatException(const QScriptValue& exception) { +QString formatException(const ScriptValue& exception) { QString note { "UncaughtException" }; QString result; @@ -45,7 +47,7 @@ void ScriptEndpoint::updateValue() { return; } - QScriptValue result = _callable.call(); + ScriptValue result = _callable.call(); if (result.isError()) { // print JavaScript exception qCDebug(controllers).noquote() << formatException(result); @@ -73,8 +75,9 @@ void ScriptEndpoint::internalApply(float value, int sourceID) { Q_ARG(int, sourceID)); return; } - QScriptValue result = _callable.call(QScriptValue(), - QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) })); + ScriptEnginePointer engine = _callable.engine(); + ScriptValue result = _callable.call(ScriptValue(), + ScriptValueList({ engine->newValue(value), engine->newValue(sourceID) })); if (result.isError()) { // print JavaScript exception qCDebug(controllers).noquote() << formatException(result); @@ -91,7 +94,7 @@ void ScriptEndpoint::updatePose() { QMetaObject::invokeMethod(this, "updatePose", Qt::QueuedConnection); return; } - QScriptValue result = _callable.call(); + ScriptValue result = _callable.call(); if (result.isError()) { // print JavaScript exception qCDebug(controllers).noquote() << formatException(result); @@ -114,8 +117,9 @@ void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) { Q_ARG(int, sourceID)); return; } - QScriptValue result = _callable.call(QScriptValue(), - QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) })); + ScriptEnginePointer engine = _callable.engine(); + ScriptValue result = _callable.call(ScriptValue(), + ScriptValueList({ Pose::toScriptValue(engine.get(), newPose), engine->newValue(sourceID) })); if (result.isError()) { // print JavaScript exception qCDebug(controllers).noquote() << formatException(result); diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h index 1aa1746b249..327cb40eaaf 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -10,7 +10,7 @@ #ifndef hifi_Controllers_ScriptEndpoint_h #define hifi_Controllers_ScriptEndpoint_h -#include +#include #include "../Endpoint.h" @@ -20,7 +20,7 @@ class ScriptEndpoint : public Endpoint { Q_OBJECT; public: using Endpoint::apply; - ScriptEndpoint(const QScriptValue& callable) + ScriptEndpoint(const ScriptValue& callable) : Endpoint(Input::INVALID_INPUT), _callable(callable) { } @@ -39,7 +39,7 @@ class ScriptEndpoint : public Endpoint { Q_INVOKABLE void updatePose(); Q_INVOKABLE virtual void internalApply(const Pose& newValue, int sourceID); private: - QScriptValue _callable; + ScriptValue _callable; float _lastValueRead { 0.0f }; AxisValue _lastValueWritten { 0.0f, 0, false }; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 67f34f4831e..54e108247a1 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME entities-renderer) -setup_hifi_library(Network Script) -link_hifi_libraries(shared workload gpu shaders procedural graphics material-networking model-networking script-engine render render-utils image qml ui pointers) +setup_hifi_library(Network) +link_hifi_libraries(shared workload gpu shaders procedural graphics material-networking model-networking script-engine render render-utils image qml ui pointers entities) include_hifi_library_headers(networking) include_hifi_library_headers(gl) include_hifi_library_headers(ktx) @@ -10,7 +10,6 @@ include_hifi_library_headers(physics) include_hifi_library_headers(animation) include_hifi_library_headers(hfm) include_hifi_library_headers(model-serializers) -include_hifi_library_headers(entities) include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) include_hifi_library_headers(task) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index cdca420c32a..e0b0e934dc5 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include @@ -31,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -159,45 +159,55 @@ render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& i int EntityTreeRenderer::_entitiesScriptEngineCount = 0; -void EntityTreeRenderer::setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine) { +void EntityTreeRenderer::setupEntityScriptEngineSignals(const ScriptManagerPointer& scriptManager) { auto entityScriptingInterface = DependencyManager::get(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "mousePressOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "mousePressOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event); // FIXME: this is a duplicate of mouseMoveOnEntity, but it seems like some scripts might use this naming - scriptEngine->callEntityScriptMethod(entityID, "mouseMoveEvent", event); + scriptManager->callEntityScriptMethod(entityID, "mouseMoveEvent", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "clickDownOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "clickDownOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "holdingClickOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "holdingClickOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "hoverEnterEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "hoverEnterEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "hoverOverEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "hoverOverEntity", event); }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) { - scriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, scriptManager.get(), + [&](const EntityItemID& entityID, const PointerEvent& event) { + scriptManager->callEntityScriptMethod(entityID, "hoverLeaveEntity", event); }); - connect(scriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) { + connect(scriptManager.get(), &ScriptManager::entityScriptPreloadFinished, [&](const EntityItemID& entityID) { EntityItemPointer entity = getTree()->findEntityByID(entityID); if (entity) { entity->setScriptHasFinishedPreload(true); @@ -206,51 +216,51 @@ void EntityTreeRenderer::setupEntityScriptEngineSignals(const ScriptEnginePointe } void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() { - if (_persistentEntitiesScriptEngine) { - _persistentEntitiesScriptEngine->unloadAllEntityScripts(true); - _persistentEntitiesScriptEngine->stop(); - _persistentEntitiesScriptEngine->waitTillDoneRunning(); - _persistentEntitiesScriptEngine->disconnectNonEssentialSignals(); + if (_persistentEntitiesScriptManager) { + _persistentEntitiesScriptManager->unloadAllEntityScripts(true); + _persistentEntitiesScriptManager->stop(); + _persistentEntitiesScriptManager->waitTillDoneRunning(); + _persistentEntitiesScriptManager->disconnectNonEssentialSignals(); } - _persistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, + _persistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("about:Entities %1").arg(++_entitiesScriptEngineCount)); - DependencyManager::get()->runScriptInitializers(_persistentEntitiesScriptEngine); - _persistentEntitiesScriptEngine->runInThread(); - auto entitiesScriptEngineProvider = qSharedPointerCast(_persistentEntitiesScriptEngine); + DependencyManager::get()->runScriptInitializers(_persistentEntitiesScriptManager); + _persistentEntitiesScriptManager->runInThread(); + std::shared_ptr entitiesScriptEngineProvider = _persistentEntitiesScriptManager; auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->setPersistentEntitiesScriptEngine(entitiesScriptEngineProvider); - setupEntityScriptEngineSignals(_persistentEntitiesScriptEngine); + setupEntityScriptEngineSignals(_persistentEntitiesScriptManager); } void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() { - if (_nonPersistentEntitiesScriptEngine) { - _nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true); - _nonPersistentEntitiesScriptEngine->stop(); - _nonPersistentEntitiesScriptEngine->waitTillDoneRunning(); - _nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals(); + if (_nonPersistentEntitiesScriptManager) { + _nonPersistentEntitiesScriptManager->unloadAllEntityScripts(true); + _nonPersistentEntitiesScriptManager->stop(); + _nonPersistentEntitiesScriptManager->waitTillDoneRunning(); + _nonPersistentEntitiesScriptManager->disconnectNonEssentialSignals(); } - _nonPersistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, + _nonPersistentEntitiesScriptManager = scriptManagerFactory(ScriptManager::ENTITY_CLIENT_SCRIPT, NO_SCRIPT, QString("about:Entities %1").arg(++_entitiesScriptEngineCount)); - DependencyManager::get()->runScriptInitializers(_nonPersistentEntitiesScriptEngine); - _nonPersistentEntitiesScriptEngine->runInThread(); - auto entitiesScriptEngineProvider = qSharedPointerCast(_nonPersistentEntitiesScriptEngine); + DependencyManager::get()->runScriptInitializers(_nonPersistentEntitiesScriptManager); + _nonPersistentEntitiesScriptManager->runInThread(); + std::shared_ptr entitiesScriptEngineProvider = _nonPersistentEntitiesScriptManager; DependencyManager::get()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider); - setupEntityScriptEngineSignals(_nonPersistentEntitiesScriptEngine); + setupEntityScriptEngineSignals(_nonPersistentEntitiesScriptManager); } void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { leaveDomainAndNonOwnedEntities(); // unload and stop the engine - if (_nonPersistentEntitiesScriptEngine) { - QList entitiesWithEntityScripts = _nonPersistentEntitiesScriptEngine->getListOfEntityScriptIDs(); + if (_nonPersistentEntitiesScriptManager) { + QList entitiesWithEntityScripts = _nonPersistentEntitiesScriptManager->getListOfEntityScriptIDs(); foreach (const EntityItemID& entityID, entitiesWithEntityScripts) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem && !entityItem->getScript().isEmpty()) { if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) { - _nonPersistentEntitiesScriptEngine->unloadEntityScript(entityID, true); + _nonPersistentEntitiesScriptManager->unloadEntityScript(entityID, true); } } } @@ -298,15 +308,15 @@ void EntityTreeRenderer::clear() { auto scene = _viewState->getMain3DScene(); if (_shuttingDown) { // unload and stop the engines - if (_nonPersistentEntitiesScriptEngine) { + if (_nonPersistentEntitiesScriptManager) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread - _nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true); - _nonPersistentEntitiesScriptEngine->stop(); + _nonPersistentEntitiesScriptManager->unloadAllEntityScripts(true); + _nonPersistentEntitiesScriptManager->stop(); } - if (_persistentEntitiesScriptEngine) { + if (_persistentEntitiesScriptManager) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread - _persistentEntitiesScriptEngine->unloadAllEntityScripts(true); - _persistentEntitiesScriptEngine->stop(); + _persistentEntitiesScriptManager->unloadAllEntityScripts(true); + _persistentEntitiesScriptManager->stop(); } if (scene) { @@ -344,16 +354,16 @@ void EntityTreeRenderer::clear() { } void EntityTreeRenderer::reloadEntityScripts() { - _persistentEntitiesScriptEngine->unloadAllEntityScripts(); - _persistentEntitiesScriptEngine->resetModuleCache(); - _nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(); - _nonPersistentEntitiesScriptEngine->resetModuleCache(); + _persistentEntitiesScriptManager->unloadAllEntityScripts(); + _persistentEntitiesScriptManager->resetModuleCache(); + _nonPersistentEntitiesScriptManager->unloadAllEntityScripts(); + _nonPersistentEntitiesScriptManager->resetModuleCache(); for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const auto& entity = renderer->getEntity(); if (entity && !entity->getScript().isEmpty()) { - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; scriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true); } } @@ -377,11 +387,11 @@ void EntityTreeRenderer::init() { } void EntityTreeRenderer::shutdown() { - if (_persistentEntitiesScriptEngine) { - _persistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential + if (_persistentEntitiesScriptManager) { + _persistentEntitiesScriptManager->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential } - if (_nonPersistentEntitiesScriptEngine) { - _nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential + if (_nonPersistentEntitiesScriptManager) { + _nonPersistentEntitiesScriptManager->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential } _shuttingDown = true; @@ -697,14 +707,14 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { // EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts // for entity IDs that no longer exist. - if (_persistentEntitiesScriptEngine && _nonPersistentEntitiesScriptEngine) { + if (_persistentEntitiesScriptManager && _nonPersistentEntitiesScriptManager) { // 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); auto entity = getTree()->findEntityByEntityItemID(entityID); if (entity) { - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; scriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } @@ -716,7 +726,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { emit enterEntity(entityID); auto entity = getTree()->findEntityByEntityItemID(entityID); if (entity) { - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; scriptEngine->callEntityScriptMethod(entityID, "enterEntity"); } } @@ -734,8 +744,8 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem && !(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) { emit leaveEntity(entityID); - if (_nonPersistentEntitiesScriptEngine) { - _nonPersistentEntitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_nonPersistentEntitiesScriptManager) { + _nonPersistentEntitiesScriptManager->callEntityScriptMethod(entityID, "leaveEntity"); } } else { currentEntitiesInsideToSave.insert(entityID); @@ -755,7 +765,7 @@ void EntityTreeRenderer::leaveAllEntities() { emit leaveEntity(entityID); EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem) { - auto& scriptEngine = (entityItem->isLocalEntity() || entityItem->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entityItem->isLocalEntity() || entityItem->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (scriptEngine) { scriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } @@ -1055,7 +1065,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { return; } - auto& scriptEngine = (itr->second->getEntity()->isLocalEntity() || itr->second->getEntity()->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (itr->second->getEntity()->isLocalEntity() || itr->second->getEntity()->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (_tree && !_shuttingDown && scriptEngine && !itr->second->getEntity()->getScript().isEmpty()) { if (_currentEntitiesInside.contains(entityID)) { scriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); @@ -1105,7 +1115,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool if (!entity) { return; } - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; bool shouldLoad = entity->shouldPreloadScript() && scriptEngine; QString scriptUrl = entity->getScript(); if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) { @@ -1226,7 +1236,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons if ((myNodeID == entityASimulatorID && entityAIsDynamic) || (myNodeID == entityBSimulatorID && (!entityAIsDynamic || entityASimulatorID.isNull()))) { playEntityCollisionSound(entityA, collision); emit collisionWithEntity(idA, idB, collision); - auto& scriptEngine = (entityA->isLocalEntity() || entityA->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entityA->isLocalEntity() || entityA->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (scriptEngine) { scriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); } @@ -1238,7 +1248,7 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons Collision invertedCollision(collision); invertedCollision.invert(); emit collisionWithEntity(idB, idA, invertedCollision); - auto& scriptEngine = (entityB->isLocalEntity() || entityB->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entityB->isLocalEntity() || entityB->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (scriptEngine) { scriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 4bbc826d1e8..08c1ca6ba8e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -13,6 +13,8 @@ #ifndef hifi_EntityTreeRenderer_h #define hifi_EntityTreeRenderer_h +#include + #include #include #include @@ -34,6 +36,10 @@ class Model; class ScriptEngine; class ZoneEntityItem; class EntityItem; +class ScriptEngine; +class ScriptManager; +using ScriptEnginePointer = std::shared_ptr; +using ScriptManagerPointer = std::shared_ptr; namespace render { namespace entities { class EntityRenderer; @@ -180,7 +186,7 @@ public slots: void resetPersistentEntitiesScriptEngine(); void resetNonPersistentEntitiesScriptEngine(); - void setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine); + void setupEntityScriptEngineSignals(const ScriptManagerPointer& scriptManager); void findBestZoneAndMaybeContainingEntities(QSet& entitiesContainingAvatar); @@ -192,7 +198,7 @@ public slots: EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; - QScriptValueList createEntityArgs(const EntityItemID& entityID); + ScriptValueList createEntityArgs(const EntityItemID& entityID); void checkEnterLeaveEntities(); void leaveDomainAndNonOwnedEntities(); void leaveAllEntities(); @@ -203,8 +209,8 @@ public slots: QSet _currentEntitiesInside; bool _wantScripts; - ScriptEnginePointer _nonPersistentEntitiesScriptEngine; // used for domain + non-owned avatar entities, cleared on domain switch - ScriptEnginePointer _persistentEntitiesScriptEngine; // used for local + owned avatar entities, persists on domain switch, cleared on reload content + ScriptManagerPointer _nonPersistentEntitiesScriptManager; // used for domain + non-owned avatar entities, cleared on domain switch + ScriptManagerPointer _persistentEntitiesScriptManager; // used for local + owned avatar entities, persists on domain switch, cleared on reload content void playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision); diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/entities-renderer/src/ModelScriptingInterface.cpp similarity index 85% rename from libraries/script-engine/src/ModelScriptingInterface.cpp rename to libraries/entities-renderer/src/ModelScriptingInterface.cpp index 499678ff06a..5d14eba0987 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ b/libraries/entities-renderer/src/ModelScriptingInterface.cpp @@ -1,6 +1,6 @@ // // ModelScriptingInterface.cpp -// libraries/script-engine/src +// libraries/entities-renderer/src // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. @@ -10,20 +10,26 @@ // #include "ModelScriptingInterface.h" -#include -#include -#include #include -#include "ScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" +#include +#include +#include +#include +#include +#include + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine(); + + scriptEngine->registerGlobalObject("Model", new ModelScriptingInterface(manager)); +}); ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); + _modelScriptEngine = qobject_cast(parent)->engine(); - qScriptRegisterSequenceMetaType>(_modelScriptEngine); - qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); + scriptRegisterSequenceMetaType>(_modelScriptEngine.get()); + scriptRegisterMetaType(_modelScriptEngine.get(), meshFaceToScriptValue, meshFaceFromScriptValue); + scriptRegisterMetaType(_modelScriptEngine.get(), qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); } QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { @@ -35,7 +41,7 @@ QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { return writeOBJToString(meshes); } -QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { +ScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { // figure out the size of the resulting mesh size_t totalVertexCount { 0 }; size_t totalColorCount { 0 }; @@ -140,16 +146,16 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); + return meshToScriptValue(_modelScriptEngine.get(), resultProxy); } -QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { +ScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshProxy* meshProxy) { if (!meshProxy) { - return QScriptValue(false); + return ScriptValue(); } MeshPointer mesh = meshProxy->getMeshPointer(); if (!mesh) { - return QScriptValue(false); + return ScriptValue(); } const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); @@ -158,45 +164,45 @@ QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshPro [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); MeshProxy* resultProxy = new SimpleMeshProxy(result); - return meshToScriptValue(_modelScriptEngine, resultProxy); + return meshToScriptValue(_modelScriptEngine.get(), resultProxy); } -QScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { +ScriptValue ModelScriptingInterface::getVertexCount(MeshProxy* meshProxy) { if (!meshProxy) { - return QScriptValue(false); + return ScriptValue(); } MeshPointer mesh = meshProxy->getMeshPointer(); if (!mesh) { - return QScriptValue(false); + return ScriptValue(); } gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); - return numVertices; + return _modelScriptEngine->newValue(numVertices); } -QScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { +ScriptValue ModelScriptingInterface::getVertex(MeshProxy* meshProxy, int vertexIndex) { if (!meshProxy) { - return QScriptValue(false); + return ScriptValue(); } MeshPointer mesh = meshProxy->getMeshPointer(); if (!mesh) { - return QScriptValue(false); + return ScriptValue(); } const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer(); gpu::BufferView::Index numVertices = (gpu::BufferView::Index)mesh->getNumVertices(); if (vertexIndex < 0 || vertexIndex >= numVertices) { - return QScriptValue(false); + return ScriptValue(); } glm::vec3 pos = vertexBufferView.get(vertexIndex); - return vec3ToScriptValue(_modelScriptEngine, pos); + return vec3ToScriptValue(_modelScriptEngine.get(), pos); } -QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, +ScriptValue ModelScriptingInterface::newMesh(const QVector& vertices, const QVector& normals, const QVector& faces) { graphics::MeshPointer mesh(std::make_shared()); @@ -247,5 +253,5 @@ QScriptValue ModelScriptingInterface::newMesh(const QVector& vertices MeshProxy* meshProxy = new SimpleMeshProxy(mesh); - return meshToScriptValue(_modelScriptEngine, meshProxy); + return meshToScriptValue(_modelScriptEngine.get(), meshProxy); } diff --git a/libraries/script-engine/src/ModelScriptingInterface.h b/libraries/entities-renderer/src/ModelScriptingInterface.h similarity index 84% rename from libraries/script-engine/src/ModelScriptingInterface.h rename to libraries/entities-renderer/src/ModelScriptingInterface.h index 5bd8d089441..9bc5529a5ad 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ b/libraries/entities-renderer/src/ModelScriptingInterface.h @@ -1,6 +1,6 @@ // // ModelScriptingInterface.h -// libraries/script-engine/src +// libraries/entities-renderer/src // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. @@ -15,10 +15,15 @@ #ifndef hifi_ModelScriptingInterface_h #define hifi_ModelScriptingInterface_h +#include + #include #include -class QScriptEngine; +#include + +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; /*@jsdoc * The Model API provides the ability to manipulate meshes. You can get the meshes for an entity using @@ -56,7 +61,7 @@ class ModelScriptingInterface : public QObject { * @param {MeshProxy[]} meshes - The meshes to combine. * @returns {MeshProxy} The combined mesh. */ - Q_INVOKABLE QScriptValue appendMeshes(MeshProxyList in); + Q_INVOKABLE ScriptValue appendMeshes(MeshProxyList in); /*@jsdoc * Transforms the vertices in a mesh. @@ -65,7 +70,7 @@ class ModelScriptingInterface : public QObject { * @param {MeshProxy} mesh - The mesh to apply the transform to. * @returns {MeshProxy|boolean} The transformed mesh, if valid. false if an error. */ - Q_INVOKABLE QScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); + Q_INVOKABLE ScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); /*@jsdoc * Creates a new mesh. @@ -75,7 +80,7 @@ class ModelScriptingInterface : public QObject { * @param {MeshFace[]} faces - The faces in the mesh. * @returns {MeshProxy} A new mesh. */ - Q_INVOKABLE QScriptValue newMesh(const QVector& vertices, + Q_INVOKABLE ScriptValue newMesh(const QVector& vertices, const QVector& normals, const QVector& faces); @@ -85,7 +90,7 @@ class ModelScriptingInterface : public QObject { * @param {MeshProxy} mesh - The mesh to count the vertices in. * @returns {number|boolean} The number of vertices in the mesh, if valid. false if an error. */ - Q_INVOKABLE QScriptValue getVertexCount(MeshProxy* meshProxy); + Q_INVOKABLE ScriptValue getVertexCount(MeshProxy* meshProxy); /*@jsdoc * Gets the position of a vertex in a mesh. @@ -94,10 +99,10 @@ class ModelScriptingInterface : public QObject { * @param {number} index - The index of the vertex to get. * @returns {Vec3|boolean} The local position of the vertex relative to the mesh, if valid. false if an error. */ - Q_INVOKABLE QScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); + Q_INVOKABLE ScriptValue getVertex(MeshProxy* meshProxy, int vertexIndex); private: - QScriptEngine* _modelScriptEngine { nullptr }; + ScriptEnginePointer _modelScriptEngine; }; #endif // hifi_ModelScriptingInterface_h diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 089c6e476a6..403e72940c8 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include "ModelScriptingInterface.h" #include #include #include diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index b6ed62c15a7..bf612b156e5 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME entities) -setup_hifi_library(Network Script) +setup_hifi_library(Network) target_include_directories(${TARGET_NAME} PRIVATE "${OPENSSL_INCLUDE_DIR}") include_hifi_library_headers(hfm) include_hifi_library_headers(model-serializers) @@ -8,7 +8,7 @@ include_hifi_library_headers(image) include_hifi_library_headers(ktx) include_hifi_library_headers(material-networking) include_hifi_library_headers(procedural) -link_hifi_libraries(shared shaders networking octree avatars graphics model-networking) +link_hifi_libraries(shared shaders networking octree avatars graphics model-networking script-engine) if (WIN32) add_compile_definitions(_USE_MATH_DEFINES) diff --git a/libraries/entities/src/AmbientLightPropertyGroup.cpp b/libraries/entities/src/AmbientLightPropertyGroup.cpp index 38017a684b1..b88e7904b1b 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.cpp +++ b/libraries/entities/src/AmbientLightPropertyGroup.cpp @@ -19,14 +19,14 @@ const float AmbientLightPropertyGroup::DEFAULT_AMBIENT_LIGHT_INTENSITY = 0.5f; -void AmbientLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void AmbientLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_INTENSITY, AmbientLight, ambientLight, AmbientIntensity, ambientIntensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_URL, AmbientLight, ambientLight, AmbientURL, ambientURL); } -void AmbientLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void AmbientLightPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientIntensity, float, setAmbientIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientURL, QString, setAmbientURL); diff --git a/libraries/entities/src/AmbientLightPropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h index 07f253d5683..4eb447cb77c 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -17,7 +17,6 @@ #include -#include #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -26,6 +25,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * Ambient light is defined by the following properties: @@ -38,10 +39,10 @@ class ReadBitstreamToTreeParams; class AmbientLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const AmbientLightPropertyGroup& other); diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 0666c7317fd..fd19e4e9b83 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -65,7 +65,7 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b * @property {boolean} hold=false - true if the rotations and translations of the last frame played are * maintained when the animation stops playing, false if they aren't. */ -void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, Animation, animation, URL, url); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); @@ -78,7 +78,7 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire } -void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void AnimationPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, url, QString, setURL); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, allowTranslation, bool, setAllowTranslation); diff --git a/libraries/entities/src/AnimationPropertyGroup.h b/libraries/entities/src/AnimationPropertyGroup.h index bebfe2c1946..4f316568c8a 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -17,8 +17,6 @@ #include -#include - #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -27,16 +25,18 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; class AnimationPropertyGroup : public PropertyGroup { public: static const float MAXIMUM_POSSIBLE_FRAME; // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const AnimationPropertyGroup& other); diff --git a/libraries/entities/src/BloomPropertyGroup.cpp b/libraries/entities/src/BloomPropertyGroup.cpp index 2c4d46ab35f..83151499257 100644 --- a/libraries/entities/src/BloomPropertyGroup.cpp +++ b/libraries/entities/src/BloomPropertyGroup.cpp @@ -16,13 +16,13 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" -void BloomPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void BloomPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_INTENSITY, Bloom, bloom, BloomIntensity, bloomIntensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); } -void BloomPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void BloomPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomIntensity, float, setBloomIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomThreshold, float, setBloomThreshold); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomSize, float, setBloomSize); diff --git a/libraries/entities/src/BloomPropertyGroup.h b/libraries/entities/src/BloomPropertyGroup.h index 98711820e55..22e2fb3a9eb 100644 --- a/libraries/entities/src/BloomPropertyGroup.h +++ b/libraries/entities/src/BloomPropertyGroup.h @@ -15,8 +15,6 @@ #include #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -25,6 +23,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; static const float INITIAL_BLOOM_INTENSITY { 0.25f }; static const float INITIAL_BLOOM_THRESHOLD { 0.7f }; @@ -40,10 +40,10 @@ static const float INITIAL_BLOOM_SIZE { 0.9f }; class BloomPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const BloomPropertyGroup& other); diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 16dace0fc8f..563f62d3802 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -15,6 +15,9 @@ #include #include +#include +#include +#include QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { QList zones; @@ -76,19 +79,19 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper auto oldProperties = propertiesIn.getDesiredProperties(); auto specifiedProperties = propertiesIn.getChangedProperties(); propertiesIn.setDesiredProperties(specifiedProperties); - QScriptValue inputValues = propertiesIn.copyToScriptValue(filterData.engine, false, true, true); + ScriptValue inputValues = propertiesIn.copyToScriptValue(filterData.engine.get(), false, true, true); propertiesIn.setDesiredProperties(oldProperties); auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter. - QScriptValueList args; + ScriptValueList args; args << inputValues; - args << filterType; + args << filterData.engine->newValue(filterType); // get the current properties for then entity and include them for the filter call if (existingEntity && filterData.wantsOriginalProperties) { auto currentProperties = existingEntity->getProperties(filterData.includedOriginalProperties); - QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true); + ScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine.get(), false, true, true); args << currentValues; } @@ -98,17 +101,17 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper auto zoneEntity = _tree->findEntityByEntityItemID(id); if (zoneEntity) { auto zoneProperties = zoneEntity->getProperties(filterData.includedZoneProperties); - QScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine, false, true, true); + ScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine.get(), false, true, true); if (filterData.wantsZoneBoundingBox) { bool success = true; AABox aaBox = zoneEntity->getAABox(success); if (success) { - QScriptValue boundingBox = filterData.engine->newObject(); - QScriptValue bottomRightNear = vec3ToScriptValue(filterData.engine, aaBox.getCorner()); - QScriptValue topFarLeft = vec3ToScriptValue(filterData.engine, aaBox.calcTopFarLeft()); - QScriptValue center = vec3ToScriptValue(filterData.engine, aaBox.calcCenter()); - QScriptValue boundingBoxDimensions = vec3ToScriptValue(filterData.engine, aaBox.getDimensions()); + ScriptValue boundingBox = filterData.engine->newObject(); + ScriptValue bottomRightNear = vec3ToScriptValue(filterData.engine.get(), aaBox.getCorner()); + ScriptValue topFarLeft = vec3ToScriptValue(filterData.engine.get(), aaBox.calcTopFarLeft()); + ScriptValue center = vec3ToScriptValue(filterData.engine.get(), aaBox.calcCenter()); + ScriptValue boundingBoxDimensions = vec3ToScriptValue(filterData.engine.get(), aaBox.getDimensions()); boundingBox.setProperty("brn", bottomRightNear); boundingBox.setProperty("tfl", topFarLeft); boundingBox.setProperty("center", center); @@ -122,14 +125,14 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // to be the fourth parameter, so we need to pad the args accordingly int EXPECTED_ARGS = 3; if (args.length() < EXPECTED_ARGS) { - args << QScriptValue(); + args << ScriptValue(); } assert(args.length() == EXPECTED_ARGS); // we MUST have 3 args by now! args << zoneValues; } } - QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); + ScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args); if (filterData.uncaughtExceptions()) { return false; @@ -166,10 +169,6 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper void EntityEditFilters::removeFilter(EntityItemID entityID) { QWriteLocker writeLock(&_lock); - FilterData filterData = _filterDataMap.value(entityID); - if (filterData.valid()) { - delete filterData.engine; - } _filterDataMap.remove(entityID); } @@ -216,20 +215,20 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { } // Copied from ScriptEngine.cpp. We should make this a class method for reuse. -// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point. -static bool hasCorrectSyntax(const QScriptProgram& program) { - const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - const auto error = syntaxCheck.errorMessage(); - const auto line = QString::number(syntaxCheck.errorLineNumber()); - const auto column = QString::number(syntaxCheck.errorColumnNumber()); - const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); +// Note: I've deliberately stopped short of using ScriptEngine instead of ScriptEngine, as that is out of project scope at this point. +static bool hasCorrectSyntax(const ScriptProgramPointer& program) { + const auto syntaxCheck = program->checkSyntax(); + if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { + const auto error = syntaxCheck->errorMessage(); + const auto line = QString::number(syntaxCheck->errorLineNumber()); + const auto column = QString::number(syntaxCheck->errorColumnNumber()); + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program->fileName(), line, column); qCritical() << qPrintable(message); return false; } return true; } -static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) { +static bool hadUncaughtExceptions(ScriptEngine& engine, const QString& fileName) { if (engine.hasUncaughtException()) { const auto backtrace = engine.uncaughtExceptionBacktrace(); const auto exception = engine.uncaughtException().toString(); @@ -255,16 +254,17 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { const QString urlString = scriptRequest->getUrl().toString(); auto scriptContents = scriptRequest->getData(); qInfo() << "Downloaded script:" << scriptContents; - QScriptProgram program(scriptContents, urlString); + // create a ScriptEngine for this script + ScriptManagerPointer manager = newScriptManager(ScriptManager::ENTITY_SERVER_SCRIPT, "", urlString); + ScriptEnginePointer engine = manager->engine(); + ScriptProgramPointer program = engine->newProgram(scriptContents, urlString); if (hasCorrectSyntax(program)) { - // create a QScriptEngine for this script - QScriptEngine* engine = new QScriptEngine(); engine->setObjectName("filter:" + entityID.toString()); engine->setProperty("type", "edit_filter"); engine->setProperty("fileName", urlString); engine->setProperty("entityID", entityID); - engine->globalObject().setProperty("Script", engine->newQObject(engine)); - DependencyManager::get()->runScriptInitializers(engine); + engine->globalObject().setProperty("Script", engine->newQObject(manager.get())); + DependencyManager::get()->runScriptInitializers(engine.get()); engine->evaluate(scriptContents, urlString); if (!hadUncaughtExceptions(*engine, urlString)) { // put the engine in the engine map (so we don't leak them, etc...) @@ -273,7 +273,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { filterData.rejectAll = false; // define the uncaughtException function - QScriptEngine& engineRef = *engine; + ScriptEngine& engineRef = *engine; filterData.uncaughtExceptions = [&engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; // now get the filter function @@ -287,28 +287,28 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { filterData.filterFn = global.property("filter"); if (!filterData.filterFn.isFunction()) { qDebug() << "Filter function specified but not found. Will reject all edits for those without lock rights."; - delete engine; + engine.reset(); filterData.rejectAll=true; } // if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true - QScriptValue wantsToFilterAddValue = filterData.filterFn.property("wantsToFilterAdd"); + ScriptValue wantsToFilterAddValue = filterData.filterFn.property("wantsToFilterAdd"); filterData.wantsToFilterAdd = wantsToFilterAddValue.isBool() ? wantsToFilterAddValue.toBool() : true; // if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true - QScriptValue wantsToFilterEditValue = filterData.filterFn.property("wantsToFilterEdit"); + ScriptValue wantsToFilterEditValue = filterData.filterFn.property("wantsToFilterEdit"); filterData.wantsToFilterEdit = wantsToFilterEditValue.isBool() ? wantsToFilterEditValue.toBool() : true; // if the wantsToFilterPhysics is a boolean evaluate as a boolean, otherwise assume true - QScriptValue wantsToFilterPhysicsValue = filterData.filterFn.property("wantsToFilterPhysics"); + ScriptValue wantsToFilterPhysicsValue = filterData.filterFn.property("wantsToFilterPhysics"); filterData.wantsToFilterPhysics = wantsToFilterPhysicsValue.isBool() ? wantsToFilterPhysicsValue.toBool() : true; // if the wantsToFilterDelete is a boolean evaluate as a boolean, otherwise assume false - QScriptValue wantsToFilterDeleteValue = filterData.filterFn.property("wantsToFilterDelete"); + ScriptValue wantsToFilterDeleteValue = filterData.filterFn.property("wantsToFilterDelete"); filterData.wantsToFilterDelete = wantsToFilterDeleteValue.isBool() ? wantsToFilterDeleteValue.toBool() : false; // check to see if the filterFn has properties asking for Original props - QScriptValue wantsOriginalPropertiesValue = filterData.filterFn.property("wantsOriginalProperties"); + ScriptValue wantsOriginalPropertiesValue = filterData.filterFn.property("wantsOriginalProperties"); // if the wantsOriginalProperties is a boolean, or a string, or list of strings, then evaluate as follows: // - boolean - true - include all original properties // false - no properties at all @@ -329,7 +329,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { } // check to see if the filterFn has properties asking for Zone props - QScriptValue wantsZonePropertiesValue = filterData.filterFn.property("wantsZoneProperties"); + ScriptValue wantsZonePropertiesValue = filterData.filterFn.property("wantsZoneProperties"); // if the wantsZoneProperties is a boolean, or a string, or list of strings, then evaluate as follows: // - boolean - true - include all Zone properties // false - no properties at all diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 69fd9209981..cc44ff47fbe 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -13,21 +13,23 @@ #include #include -#include -#include #include #include +#include + #include "EntityItemID.h" #include "EntityItemProperties.h" #include "EntityTree.h" +class ScriptEngine; + class EntityEditFilters : public QObject, public Dependency { Q_OBJECT public: struct FilterData { - QScriptValue filterFn; + ScriptValue filterFn; bool wantsOriginalProperties { false }; bool wantsZoneProperties { false }; @@ -41,10 +43,10 @@ class EntityEditFilters : public QObject, public Dependency { bool wantsZoneBoundingBox { false }; std::function uncaughtExceptions; - QScriptEngine* engine; + ScriptEnginePointer engine; bool rejectAll; - FilterData(): engine(nullptr), rejectAll(false) {}; + FilterData(): rejectAll(false) {}; bool valid() { return (rejectAll || (engine != nullptr && filterFn.isFunction() && uncaughtExceptions)); } }; @@ -68,7 +70,7 @@ private slots: EntityTreePointer _tree {}; bool _rejectAll {false}; - QScriptValue _nullObjectForFilter{}; + ScriptValue _nullObjectForFilter{}; QReadWriteLock _lock; QMap _filterDataMap; diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp deleted file mode 100644 index 28b8e109ca1..00000000000 --- a/libraries/entities/src/EntityItemID.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// -// EntityItemID.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "EntityItemID.h" -#include -#include - -#include -#include -#include - -#include "RegisteredMetaTypes.h" - -int entityItemIDTypeID = qRegisterMetaType(); - -EntityItemID::EntityItemID() : QUuid() -{ -} - - -EntityItemID::EntityItemID(const QUuid& id) : QUuid(id) -{ -} - -// EntityItemID::EntityItemID(const EntityItemID& other) : QUuid(other) -// { -// } - -EntityItemID EntityItemID::readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead) { - EntityItemID result; - if (bytesLeftToRead >= NUM_BYTES_RFC4122_UUID) { - BufferParser(data, bytesLeftToRead).readUuid(result); - } - return result; -} - -QScriptValue EntityItemID::toScriptValue(QScriptEngine* engine) const { - return EntityItemIDtoScriptValue(engine, *this); -} - -QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) { - return quuidToScriptValue(engine, id); -} - -void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& id) { - quuidFromScriptValue(object, id); -} - -QVector qVectorEntityItemIDFromScriptValue(const QScriptValue& array) { - if (!array.isArray()) { - return QVector(); - } - QVector newVector; - int length = array.property("length").toInteger(); - newVector.reserve(length); - for (int i = 0; i < length; i++) { - QString uuidAsString = array.property(i).toString(); - EntityItemID fromString(uuidAsString); - newVector << fromString; - } - return newVector; -} - -size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 8df85bcfe62..fcafd75ea9e 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "EntitiesLogging.h" #include "EntityItem.h" @@ -1571,14 +1572,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.RingGizmo} ring - The ring gizmo properties. */ -QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, +ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const { // If strictSemantics is true and skipDefaults is false, then all and only those properties are copied for which the property flag // is included in _desiredProperties, or is one of the specially enumerated ALWAYS properties below. // (There may be exceptions, but if so, they are bugs.) // In all other cases, you are welcome to inspect the code and try to figure out what was intended. I wish you luck. -HRS 1/18/17 - QScriptValue properties = engine->newObject(); + ScriptValue properties = engine->newObject(); EntityItemProperties defaultEntityProperties; const bool psuedoPropertyFlagsActive = psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FlagsActive); @@ -1922,7 +1923,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Handle conversions to old 'textures' property from "imageURL" if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(PROP_IMAGE_URL)) && (!skipDefaults || defaultEntityProperties._imageURL != _imageURL)) { - QScriptValue textures = engine->newObject(); + ScriptValue textures = engine->newObject(); textures.setProperty("tex.picture", _imageURL); properties.setProperty("textures", textures); } @@ -1957,11 +1958,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::BoundingBox))) { AABox aaBox = getAABox(); - QScriptValue boundingBox = engine->newObject(); - QScriptValue bottomRightNear = vec3ToScriptValue(engine, aaBox.getCorner()); - QScriptValue topFarLeft = vec3ToScriptValue(engine, aaBox.calcTopFarLeft()); - QScriptValue center = vec3ToScriptValue(engine, aaBox.calcCenter()); - QScriptValue boundingBoxDimensions = vec3ToScriptValue(engine, aaBox.getDimensions()); + ScriptValue boundingBox = engine->newObject(); + ScriptValue bottomRightNear = vec3ToScriptValue(engine, aaBox.getCorner()); + ScriptValue topFarLeft = vec3ToScriptValue(engine, aaBox.calcTopFarLeft()); + ScriptValue center = vec3ToScriptValue(engine, aaBox.calcCenter()); + ScriptValue boundingBoxDimensions = vec3ToScriptValue(engine, aaBox.getDimensions()); boundingBox.setProperty("brn", bottomRightNear); boundingBox.setProperty("tfl", topFarLeft); boundingBox.setProperty("center", center); @@ -1978,7 +1979,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (!skipDefaults && !strictSemantics && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::RenderInfo))) { - QScriptValue renderInfo = engine->newObject(); + ScriptValue renderInfo = engine->newObject(); /*@jsdoc * Information on how an entity is rendered. Properties are only filled in for Model entities; other @@ -2023,8 +2024,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool return properties; } -void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool honorReadOnly) { - QScriptValue typeScriptValue = object.property("type"); +void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool honorReadOnly) { + ScriptValue typeScriptValue = object.property("type"); if (typeScriptValue.isValid()) { setType(typeScriptValue.toVariant().toString()); } @@ -2279,7 +2280,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // Handle conversions from old 'textures' property to "imageURL" { - QScriptValue V = object.property("textures"); + ScriptValue V = object.property("textures"); if (_type == EntityTypes::Image && V.isValid() && !object.property("imageURL").isValid()) { bool isValid = false; QString textures = QString_convertFromScriptValue(V, isValid); @@ -2298,7 +2299,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // Handle old "faceCamera" and "isFacingAvatar" props if (_type != EntityTypes::PolyLine) { - QScriptValue P = object.property("faceCamera"); + ScriptValue P = object.property("faceCamera"); if (P.isValid() && !object.property("billboardMode").isValid()) { bool newValue = P.toVariant().toBool(); bool oldValue = getBillboardMode() == BillboardMode::YAW; @@ -2308,7 +2309,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } } { - QScriptValue P = object.property("isFacingAvatar"); + ScriptValue P = object.property("isFacingAvatar"); if (P.isValid() && !object.property("billboardMode").isValid() && !object.property("faceCamera").isValid()) { bool newValue = P.toVariant().toBool(); bool oldValue = getBillboardMode() == BillboardMode::FULL; @@ -2321,13 +2322,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _lastEdited = usecTimestampNow(); } -void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString) { +void EntityItemProperties::copyFromJSONString(ScriptEngine& scriptEngine, const QString& jsonString) { // DANGER: this method is expensive QJsonDocument propertiesDoc = QJsonDocument::fromJson(jsonString.toUtf8()); QJsonObject propertiesObj = propertiesDoc.object(); QVariant propertiesVariant(propertiesObj); QVariantMap propertiesMap = propertiesVariant.toMap(); - QScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine); + ScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine); bool honorReadOnly = true; copyFromScriptValue(propertiesScriptValue, honorReadOnly); } @@ -2575,37 +2576,39 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { _lastEdited = usecTimestampNow(); } -QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) { +ScriptValue EntityItemPropertiesToScriptValue(ScriptEngine* engine, const EntityItemProperties& properties) { return properties.copyToScriptValue(engine, false); } -QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties) { +ScriptValue EntityItemNonDefaultPropertiesToScriptValue(ScriptEngine* engine, const EntityItemProperties& properties) { return properties.copyToScriptValue(engine, true); } -void EntityItemPropertiesFromScriptValueIgnoreReadOnly(const QScriptValue &object, EntityItemProperties& properties) { +bool EntityItemPropertiesFromScriptValueIgnoreReadOnly(const ScriptValue &object, EntityItemProperties& properties) { properties.copyFromScriptValue(object, false); + return true; } -void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object, EntityItemProperties& properties) { +bool EntityItemPropertiesFromScriptValueHonorReadOnly(const ScriptValue &object, EntityItemProperties& properties) { properties.copyFromScriptValue(object, true); + return true; } -QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { +ScriptValue EntityPropertyFlagsToScriptValue(ScriptEngine* engine, const EntityPropertyFlags& flags) { return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags); } -void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { - EntityItemProperties::entityPropertyFlagsFromScriptValue(object, flags); +bool EntityPropertyFlagsFromScriptValue(const ScriptValue& object, EntityPropertyFlags& flags) { + return EntityItemProperties::entityPropertyFlagsFromScriptValue(object, flags); } -QScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { - QScriptValue result = engine->newObject(); +ScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(ScriptEngine* engine, const EntityPropertyFlags& flags) { + ScriptValue result = engine->newObject(); return result; } -void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { +bool EntityItemProperties::entityPropertyFlagsFromScriptValue(const ScriptValue& object, EntityPropertyFlags& flags) { if (object.isString()) { EntityPropertyInfo propertyInfo; if (getPropertyInfo(object.toString(), propertyInfo)) { @@ -2622,6 +2625,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue } } } + return true; } static QHash _propertyInfos; @@ -3014,18 +3018,19 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr * @property {string} minimum - The minimum numerical value the property may have, if available, otherwise "". * @property {string} maximum - The maximum numerical value the property may have, if available, otherwise "". */ -QScriptValue EntityPropertyInfoToScriptValue(QScriptEngine* engine, const EntityPropertyInfo& propertyInfo) { - QScriptValue obj = engine->newObject(); +ScriptValue EntityPropertyInfoToScriptValue(ScriptEngine* engine, const EntityPropertyInfo& propertyInfo) { + ScriptValue obj = engine->newObject(); obj.setProperty("propertyEnum", propertyInfo.propertyEnum); obj.setProperty("minimum", propertyInfo.minimum.toString()); obj.setProperty("maximum", propertyInfo.maximum.toString()); return obj; } -void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropertyInfo& propertyInfo) { +bool EntityPropertyInfoFromScriptValue(const ScriptValue& object, EntityPropertyInfo& propertyInfo) { propertyInfo.propertyEnum = (EntityPropertyList)object.property("propertyEnum").toVariant().toUInt(); propertyInfo.minimum = object.property("minimum").toVariant(); propertyInfo.maximum = object.property("maximum").toVariant(); + return true; } // TODO: Implement support for edit packets that can span an MTU sized buffer. We need to implement a mechanism for the @@ -5199,7 +5204,7 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); } -bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) { +bool EntityItemProperties::blobToProperties(ScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) { // DANGER: this method is NOT efficient. // begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob); @@ -5209,17 +5214,17 @@ bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const Q } QVariant variant = jsonProperties.toVariant(); QVariantMap variantMap = variant.toMap(); - QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine); + ScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine); EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptValue, properties); // end recipe return true; } -void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, +void EntityItemProperties::propertiesToBlob(ScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob, bool allProperties) { // DANGER: this method is NOT efficient. // begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem - QScriptValue scriptValue = allProperties + ScriptValue scriptValue = allProperties ? EntityItemPropertiesToScriptValue(&scriptEngine, properties) : EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties); QVariant variantProperties = scriptValue.toVariant(); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 91de5400627..678bd9680a5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -34,8 +33,9 @@ #include #include #include "FontFamilies.h" +#include -#include "EntityItemID.h" +#include #include "EntityItemPropertiesDefaults.h" #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" @@ -69,6 +69,8 @@ #include "TextEffect.h" #include "TextAlignment.h" +class ScriptEngine; + const quint64 UNKNOWN_CREATED_TIME = 0; using vec3Color = glm::vec3; @@ -96,7 +98,7 @@ EntityPropertyInfo makePropertyInfo(EntityPropertyList p, typename std::enable_i } /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an -/// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete +/// entity and a JavaScript style hash/ScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues /// all units for SI units (meter, second, radian, etc) class EntityItemProperties { @@ -119,8 +121,8 @@ class EntityItemProperties { friend class ZoneEntityItem; friend class MaterialEntityItem; public: - static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); - static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, + static bool blobToProperties(ScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); + static void propertiesToBlob(ScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob, bool allProperties = false); EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); @@ -133,13 +135,13 @@ class EntityItemProperties { EntityTypes::EntityType getType() const { return _type; } void setType(EntityTypes::EntityType type) { _type = type; } - virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, + virtual ScriptValue copyToScriptValue(ScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const; - virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly); - void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString); + virtual void copyFromScriptValue(const ScriptValue& object, bool honorReadOnly); + void copyFromJSONString(ScriptEngine& scriptEngine, const QString& jsonString); - static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); - static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); + static ScriptValue entityPropertyFlagsToScriptValue(ScriptEngine* engine, const EntityPropertyFlags& flags); + static bool entityPropertyFlagsFromScriptValue(const ScriptValue& object, EntityPropertyFlags& flags); static bool getPropertyInfo(const QString& propertyName, EntityPropertyInfo& propertyInfo); @@ -539,18 +541,18 @@ class EntityItemProperties { }; Q_DECLARE_METATYPE(EntityItemProperties); -QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); -QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); -void EntityItemPropertiesFromScriptValueIgnoreReadOnly(const QScriptValue& object, EntityItemProperties& properties); -void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue& object, EntityItemProperties& properties); +ScriptValue EntityItemPropertiesToScriptValue(ScriptEngine* engine, const EntityItemProperties& properties); +ScriptValue EntityItemNonDefaultPropertiesToScriptValue(ScriptEngine* engine, const EntityItemProperties& properties); +bool EntityItemPropertiesFromScriptValueIgnoreReadOnly(const ScriptValue& object, EntityItemProperties& properties); +bool EntityItemPropertiesFromScriptValueHonorReadOnly(const ScriptValue& object, EntityItemProperties& properties); Q_DECLARE_METATYPE(EntityPropertyFlags); -QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); -void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); +ScriptValue EntityPropertyFlagsToScriptValue(ScriptEngine* engine, const EntityPropertyFlags& flags); +bool EntityPropertyFlagsFromScriptValue(const ScriptValue& object, EntityPropertyFlags& flags); Q_DECLARE_METATYPE(EntityPropertyInfo); -QScriptValue EntityPropertyInfoToScriptValue(QScriptEngine* engine, const EntityPropertyInfo& propertyInfo); -void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropertyInfo& propertyInfo); +ScriptValue EntityPropertyInfoToScriptValue(ScriptEngine* engine, const EntityPropertyInfo& propertyInfo); +bool EntityPropertyInfoFromScriptValue(const ScriptValue& object, EntityPropertyInfo& propertyInfo); // define these inline here so the macros work inline void EntityItemProperties::setPosition(const glm::vec3& value) diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index c25eb21e6c5..7f08d5b03c9 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -13,8 +13,11 @@ #ifndef hifi_EntityItemPropertiesMacros_h #define hifi_EntityItemPropertiesMacros_h -#include "EntityItemID.h" +#include #include +#include +#include +#include #define APPEND_ENTITY_PROPERTY(P,V) \ if (requestedProperties.getHasProperty(P)) { \ @@ -99,47 +102,47 @@ changedProperties += P; \ } -inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec2& v) { return vec2ToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3ToScriptValue(e, v); } -inline QScriptValue vec3Color_convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3ColorToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::u8vec3& v) { return u8vec3ToScriptValue(e, v); } -inline QScriptValue u8vec3Color_convertScriptValue(QScriptEngine* e, const glm::u8vec3& v) { return u8vec3ColorToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, bool v) { return QScriptValue(v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, quint16 v) { return QScriptValue(v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, quint32 v) { return QScriptValue(v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, quint64 v) { return QScriptValue((qsreal)v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QString& v) { return QScriptValue(v); } - -inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::quat& v) { return quatToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QScriptValue& v) { return v; } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorVec3ToScriptValue(e, v); } -inline QScriptValue qVectorVec3Color_convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorVec3ColorToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorQuatToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorBoolToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) { return qVectorFloatToScriptValue(e, v); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) { return qVectorQUuidToScriptValue(e, v); } - -inline QScriptValue convertScriptValue(QScriptEngine* e, const QRect& v) { return qRectToScriptValue(e, v); } - -inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { +inline ScriptValue convertScriptValue(ScriptEngine* e, const glm::vec2& v) { return vec2ToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const glm::vec3& v) { return vec3ToScriptValue(e, v); } +inline ScriptValue vec3Color_convertScriptValue(ScriptEngine* e, const glm::vec3& v) { return vec3ColorToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const glm::u8vec3& v) { return u8vec3ToScriptValue(e, v); } +inline ScriptValue u8vec3Color_convertScriptValue(ScriptEngine* e, const glm::u8vec3& v) { return u8vec3ColorToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, float v) { return e->newValue(v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, int v) { return e->newValue(v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, bool v) { return e->newValue(v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, quint16 v) { return e->newValue(v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, quint32 v) { return e->newValue(v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, quint64 v) { return e->newValue((double)v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QString& v) { return e->newValue(v); } + +inline ScriptValue convertScriptValue(ScriptEngine* e, const glm::quat& v) { return quatToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const ScriptValue& v) { return v; } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QVector& v) {return qVectorVec3ToScriptValue(e, v); } +inline ScriptValue qVectorVec3Color_convertScriptValue(ScriptEngine* e, const QVector& v) {return qVectorVec3ColorToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QVector& v) {return qVectorQuatToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QVector& v) {return qVectorBoolToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QVector& v) { return qVectorFloatToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const QVector& v) { return qVectorQUuidToScriptValue(e, v); } + +inline ScriptValue convertScriptValue(ScriptEngine* e, const QRect& v) { return qRectToScriptValue(e, v); } + +inline ScriptValue convertScriptValue(ScriptEngine* e, const QByteArray& v) { QByteArray b64 = v.toBase64(); - return QScriptValue(QString(b64)); + return e->newValue(QString(b64)); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const EntityItemID& v) { return QScriptValue(QUuid(v).toString()); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const EntityItemID& v) { return e->newValue(QUuid(v).toString()); } -inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { return aaCubeToScriptValue(e, v); } +inline ScriptValue convertScriptValue(ScriptEngine* e, const AACube& v) { return aaCubeToScriptValue(e, v); } #define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(X,G,g,P,p) \ if ((desiredProperties.isEmpty() || desiredProperties.getHasProperty(X)) && \ (!skipDefaults || defaultEntityProperties.get##G().get##P() != get##P())) { \ - QScriptValue groupProperties = properties.property(#g); \ + ScriptValue groupProperties = properties.property(#g); \ if (!groupProperties.isValid()) { \ groupProperties = engine->newObject(); \ } \ - QScriptValue V = convertScriptValue(engine, get##P()); \ + ScriptValue V = convertScriptValue(engine, get##P()); \ groupProperties.setProperty(#p, V); \ properties.setProperty(#g, groupProperties); \ } @@ -147,11 +150,11 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu #define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(X,G,g,P,p,T) \ if ((desiredProperties.isEmpty() || desiredProperties.getHasProperty(X)) && \ (!skipDefaults || defaultEntityProperties.get##G().get##P() != get##P())) { \ - QScriptValue groupProperties = properties.property(#g); \ + ScriptValue groupProperties = properties.property(#g); \ if (!groupProperties.isValid()) { \ groupProperties = engine->newObject(); \ } \ - QScriptValue V = T##_convertScriptValue(engine, get##P()); \ + ScriptValue V = T##_convertScriptValue(engine, get##P()); \ groupProperties.setProperty(#p, V); \ properties.setProperty(#g, groupProperties); \ } @@ -159,11 +162,11 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu #define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(X,G,g,P,p,M) \ if ((desiredProperties.isEmpty() || desiredProperties.getHasProperty(X)) && \ (!skipDefaults || defaultEntityProperties.get##G().get##P() != get##P())) { \ - QScriptValue groupProperties = properties.property(#g); \ + ScriptValue groupProperties = properties.property(#g); \ if (!groupProperties.isValid()) { \ groupProperties = engine->newObject(); \ } \ - QScriptValue V = convertScriptValue(engine, M()); \ + ScriptValue V = convertScriptValue(engine, M()); \ groupProperties.setProperty(#p, V); \ properties.setProperty(#g, groupProperties); \ } @@ -171,14 +174,14 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu #define COPY_PROPERTY_TO_QSCRIPTVALUE(p,P) \ if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = convertScriptValue(engine, _##P); \ + ScriptValue V = convertScriptValue(engine, _##P); \ properties.setProperty(#P, V); \ } #define COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(p,P,T) \ if ((_desiredProperties.isEmpty() || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = T##_convertScriptValue(engine, _##P); \ + ScriptValue V = T##_convertScriptValue(engine, _##P); \ properties.setProperty(#P, V); \ } @@ -188,14 +191,14 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu #define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, G) \ if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = convertScriptValue(engine, G); \ + ScriptValue V = convertScriptValue(engine, G); \ properties.setProperty(#P, V); \ } #define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(p, P, G, T) \ if ((_desiredProperties.isEmpty() || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = T##_convertScriptValue(engine, G); \ + ScriptValue V = T##_convertScriptValue(engine, G); \ properties.setProperty(#P, V); \ } @@ -203,13 +206,13 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu #define COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, X, G) \ if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = convertScriptValue(engine, G); \ + ScriptValue V = convertScriptValue(engine, G); \ properties.setProperty(#X, V); \ } #define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(P, G) \ if (!skipDefaults || defaultEntityProperties._##P != _##P) { \ - QScriptValue V = convertScriptValue(engine, G); \ + ScriptValue V = convertScriptValue(engine, G); \ properties.setProperty(#P, V); \ } @@ -218,94 +221,94 @@ typedef QVector qVectorQuat; typedef QVector qVectorBool; typedef QVector qVectorFloat; typedef QVector qVectorQUuid; -inline float float_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } -inline quint64 quint64_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } -inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline float float_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } +inline quint64 quint64_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } +inline quint32 quint32_convertFromScriptValue(const ScriptValue& v, bool& isValid) { // Use QString::toUInt() so that isValid is set to false if the number is outside the quint32 range. return v.toString().toUInt(&isValid); } -inline quint16 quint16_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } -inline uint16_t uint16_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } -inline uint32_t uint32_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } -inline int int_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } -inline bool bool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); } -inline uint8_t uint8_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return (uint8_t)(0xff & v.toVariant().toInt(&isValid)); } -inline QString QString_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toString().trimmed(); } -inline QUuid QUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } -inline EntityItemID EntityItemID_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } - -inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline quint16 quint16_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } +inline uint16_t uint16_t_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } +inline uint32_t uint32_t_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } +inline int int_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } +inline bool bool_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); } +inline uint8_t uint8_t_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return (uint8_t)(0xff & v.toVariant().toInt(&isValid)); } +inline QString QString_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toString().trimmed(); } +inline QUuid QUuid_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } +inline EntityItemID EntityItemID_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } + +inline QByteArray QByteArray_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; QString b64 = v.toVariant().toString().trimmed(); return QByteArray::fromBase64(b64.toUtf8()); } -inline glm::vec2 vec2_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::vec2 vec2_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; glm::vec2 vec2; vec2FromScriptValue(v, vec2); return vec2; } -inline glm::vec3 vec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::vec3 vec3_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; glm::vec3 vec3; vec3FromScriptValue(v, vec3); return vec3; } -inline glm::vec3 vec3Color_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::vec3 vec3Color_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; glm::vec3 vec3; vec3FromScriptValue(v, vec3); return vec3; } -inline glm::u8vec3 u8vec3Color_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::u8vec3 u8vec3Color_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; glm::u8vec3 vec3; u8vec3FromScriptValue(v, vec3); return vec3; } -inline AACube AACube_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline AACube AACube_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; AACube result; aaCubeFromScriptValue(v, result); return result; } -inline qVectorFloat qVectorFloat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline qVectorFloat qVectorFloat_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return qVectorFloatFromScriptValue(v); } -inline qVectorVec3 qVectorVec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline qVectorVec3 qVectorVec3_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return qVectorVec3FromScriptValue(v); } -inline qVectorQuat qVectorQuat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline qVectorQuat qVectorQuat_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return qVectorQuatFromScriptValue(v); } -inline qVectorBool qVectorBool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline qVectorBool qVectorBool_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return qVectorBoolFromScriptValue(v); } -inline qVectorQUuid qVectorQUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline qVectorQUuid qVectorQUuid_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; return qVectorQUuidFromScriptValue(v); } -inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::quat quat_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted - QScriptValue x = v.property("x"); - QScriptValue y = v.property("y"); - QScriptValue z = v.property("z"); - QScriptValue w = v.property("w"); + ScriptValue x = v.property("x"); + ScriptValue y = v.property("y"); + ScriptValue z = v.property("z"); + ScriptValue w = v.property("w"); if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { glm::quat newValue; newValue.x = x.toVariant().toFloat(); @@ -323,7 +326,7 @@ inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isVali return glm::quat(); } -inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline QRect QRect_convertFromScriptValue(const ScriptValue& v, bool& isValid) { isValid = true; QRect rect; qRectFromScriptValue(v, rect); @@ -341,7 +344,7 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_PROPERTY_FROM_QSCRIPTVALUE(P, T, S) \ { \ - QScriptValue V = object.property(#P); \ + ScriptValue V = object.property(#P); \ if (V.isValid()) { \ bool isValid = false; \ T newValue = T##_convertFromScriptValue(V, isValid); \ @@ -353,7 +356,7 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(P, T, S, G) \ { \ - QScriptValue V = object.property(#P); \ + ScriptValue V = object.property(#P); \ if (V.isValid()) { \ bool isValid = false; \ T newValue = T##_convertFromScriptValue(V, isValid); \ @@ -365,7 +368,7 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_PROPERTY_FROM_QSCRIPTVALUE_NOCHECK(P, T, S) \ { \ - QScriptValue V = object.property(#P); \ + ScriptValue V = object.property(#P); \ if (V.isValid()) { \ bool isValid = false; \ T newValue = T##_convertFromScriptValue(V, isValid); \ @@ -377,9 +380,9 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(G, P, T, S) \ { \ - QScriptValue G = object.property(#G); \ + ScriptValue G = object.property(#G); \ if (G.isValid()) { \ - QScriptValue V = G.property(#P); \ + ScriptValue V = G.property(#P); \ if (V.isValid()) { \ bool isValid = false; \ T newValue = T##_convertFromScriptValue(V, isValid); \ @@ -392,7 +395,7 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ { \ - QScriptValue P = object.property(#P); \ + ScriptValue P = object.property(#P); \ if (P.isValid()) { \ QString newValue = P.toVariant().toString(); \ if (_defaultSettings || newValue != get##S##AsString()) { \ @@ -403,9 +406,9 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) #define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(G, P, S) \ { \ - QScriptValue G = object.property(#G); \ + ScriptValue G = object.property(#G); \ if (G.isValid()) { \ - QScriptValue P = G.property(#P); \ + ScriptValue P = G.property(#P); \ if (P.isValid()) { \ QString newValue = P.toVariant().toString(); \ if (_defaultSettings || newValue != get##S##AsString()) { \ diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 90c521215e9..f93ad4ef97b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -41,10 +41,43 @@ #include #include #include "GrabPropertyGroup.h" +#include +#include +#include const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; +void staticEntityScriptInitializer(ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + auto entityScriptingInterface = DependencyManager::get(); + entityScriptingInterface->init(); + auto interfacePtr = entityScriptingInterface.data(); // using this when we don't want to leak a reference + + registerMetaTypes(scriptEngine); + + scriptRegisterMetaType(scriptEngine, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue); + scriptRegisterMetaType(scriptEngine, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); + scriptRegisterMetaType(scriptEngine, EntityPropertyInfoToScriptValue, EntityPropertyInfoFromScriptValue); + scriptRegisterMetaType(scriptEngine, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); + scriptRegisterMetaType(scriptEngine, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); + + scriptEngine->registerGlobalObject("Entities", entityScriptingInterface.data()); + scriptEngine->registerFunction("Entities", "getMultipleEntityProperties", EntityScriptingInterface::getMultipleEntityProperties); + + // "The return value of QObject::sender() is not valid when the slot is called via a Qt::DirectConnection from a thread + // different from this object's thread. Do not use this function in this type of scenario." + // so... yay lambdas everywhere to get the sender + manager->connect( + manager, &ScriptManager::attachDefaultEventHandlers, entityScriptingInterface.data(), + [interfacePtr, manager] { interfacePtr->attachDefaultEventHandlers(manager); }, + Qt::DirectConnection); + manager->connect(manager, &ScriptManager::releaseEntityPacketSenderMessages, entityScriptingInterface.data(), + &EntityScriptingInterface::releaseEntityPacketSenderMessages, Qt::DirectConnection); +} +STATIC_SCRIPT_INITIALIZER(staticEntityScriptInitializer); + EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(nullptr), _bidOnSimulationOwnership(bidOnSimulationOwnership) @@ -64,6 +97,148 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership PacketReceiver::makeSourcedListenerReference(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket)); } +void EntityScriptingInterface::releaseEntityPacketSenderMessages(bool wait) { + EntityEditPacketSender* entityPacketSender = getEntityPacketSender(); + if (entityPacketSender && entityPacketSender->serversExist()) { + // release the queue of edit entity messages. + entityPacketSender->releaseQueuedMessages(); + + // since we're in non-threaded mode, call process so that the packets are sent + if (!entityPacketSender->isThreaded()) { + if (!wait) { + entityPacketSender->process(); + } else { + // wait here till the edit packet sender is completely done sending + while (entityPacketSender->hasPacketsToSend()) { + entityPacketSender->process(); + QCoreApplication::processEvents(); + } + } + } else { + // FIXME - do we need to have a similar "wait here" loop for non-threaded packet senders? + } + } +} + + +void EntityScriptingInterface::attachDefaultEventHandlers(ScriptManager* manager) { + // 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.) + + // Bug? These handlers are deleted when entityID is deleted, which is nice. + // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? + // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, + // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded? + // Also, what about when the entity script is REloaded? + // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the + // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS + connect(this, &EntityScriptingInterface::deletingEntity, manager, + [manager](const EntityItemID& entityID) { manager->removeAllEventHandlers(entityID); }); + + // Two common cases of event handler, differing only in argument signature. + + /*@jsdoc + * Called when an entity event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~entityEventCallback + * @param {Uuid} entityID - The ID of the entity the event has occured on. + */ + using SingleEntityHandler = std::function; + auto makeSingleEntityHandler = [manager](QString eventName) -> SingleEntityHandler { + return [manager, eventName](const EntityItemID& entityItemID) { + manager->forwardHandlerCall(entityItemID, eventName, + { EntityItemIDtoScriptValue(manager->engine().get(), entityItemID) }); + }; + }; + + /*@jsdoc + * Called when a pointer event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~pointerEventCallback + * @param {Uuid} entityID - The ID of the entity the event has occurred on. + * @param {PointerEvent} pointerEvent - Details of the event. + */ + using PointerHandler = std::function; + auto makePointerHandler = [manager](QString eventName) -> PointerHandler { + return [manager, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { + if (!EntityTree::areEntityClicksCaptured()) { + ScriptEngine* engine = manager->engine().get(); + manager->forwardHandlerCall(entityItemID, eventName, + { EntityItemIDtoScriptValue(engine, entityItemID), event.toScriptValue(engine) }); + } + }; + }; + + /*@jsdoc + * Called when a collision event occurs on an entity as registered with {@link Script.addEventHandler}. + * @callback Script~collisionEventCallback + * @param {Uuid} entityA - The ID of one entity in the collision. + * @param {Uuid} entityB - The ID of the other entity in the collision. + * @param {Collision} collisionEvent - Details of the collision. + */ + using CollisionHandler = std::function; + auto makeCollisionHandler = [manager](QString eventName) -> CollisionHandler { + return [manager, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { + ScriptEngine* engine = manager->engine().get(); + manager->forwardHandlerCall(idA, eventName, + { EntityItemIDtoScriptValue(engine, idA), + EntityItemIDtoScriptValue(engine, idB), + collisionToScriptValue(engine, collision) }); + }; + }; + + /*@jsdoc + *

The name of an entity event. When the entity event occurs, any function that has been registered for that event + * via {@link Script.addEventHandler} is called with parameters per the entity event.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Event NameCallback TypeEntity Event
"enterEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.enterEntity}
"leaveEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.leaveEntity}
"mousePressOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mousePressOnEntity}
"mouseMoveOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseMoveOnEntity}
"mouseReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseReleaseOnEntity}
"clickDownOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickDownOnEntity}
"holdingClickOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.holdingClickOnEntity}
"clickReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickReleaseOnEntity}
"hoverEnterEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverEnterEntity}
"hoverOverEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverOverEntity}
"hoverLeaveEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverLeaveEntity}
"collisionWithEntity"{@link Script~collisionEventCallback|collisionEventCallback}{@link Entities.collisionWithEntity}
+ * @typedef {string} Script.EntityEvent + */ + connect(this, &EntityScriptingInterface::enterEntity, manager, makeSingleEntityHandler("enterEntity")); + connect(this, &EntityScriptingInterface::leaveEntity, manager, makeSingleEntityHandler("leaveEntity")); + + connect(this, &EntityScriptingInterface::mousePressOnEntity, manager, makePointerHandler("mousePressOnEntity")); + connect(this, &EntityScriptingInterface::mouseMoveOnEntity, manager, makePointerHandler("mouseMoveOnEntity")); + connect(this, &EntityScriptingInterface::mouseReleaseOnEntity, manager, makePointerHandler("mouseReleaseOnEntity")); + + connect(this, &EntityScriptingInterface::clickDownOnEntity, manager, makePointerHandler("clickDownOnEntity")); + connect(this, &EntityScriptingInterface::holdingClickOnEntity, manager, makePointerHandler("holdingClickOnEntity")); + connect(this, &EntityScriptingInterface::clickReleaseOnEntity, manager, makePointerHandler("clickReleaseOnEntity")); + + connect(this, &EntityScriptingInterface::hoverEnterEntity, manager, makePointerHandler("hoverEnterEntity")); + connect(this, &EntityScriptingInterface::hoverOverEntity, manager, makePointerHandler("hoverOverEntity")); + connect(this, &EntityScriptingInterface::hoverLeaveEntity, manager, makePointerHandler("hoverLeaveEntity")); + + connect(this, &EntityScriptingInterface::collisionWithEntity, manager, makeCollisionHandler("collisionWithEntity")); +} + void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); @@ -672,20 +847,20 @@ struct EntityPropertiesResult { // Static method to make sure that we have the right script engine. // Using sender() or QtScriptable::engine() does not work for classes used by multiple threads (script-engines) -QScriptValue EntityScriptingInterface::getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine) { +ScriptValue EntityScriptingInterface::getMultipleEntityProperties(ScriptContext* context, ScriptEngine* engine) { const int ARGUMENT_ENTITY_IDS = 0; const int ARGUMENT_EXTENDED_DESIRED_PROPERTIES = 1; auto entityScriptingInterface = DependencyManager::get(); - const auto entityIDs = qscriptvalue_cast>(context->argument(ARGUMENT_ENTITY_IDS)); + const auto entityIDs = scriptvalue_cast>(context->argument(ARGUMENT_ENTITY_IDS)); return entityScriptingInterface->getMultipleEntityPropertiesInternal(engine, entityIDs, context->argument(ARGUMENT_EXTENDED_DESIRED_PROPERTIES)); } -QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector entityIDs, const QScriptValue& extendedDesiredProperties) { +ScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector entityIDs, const ScriptValue& extendedDesiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); EntityPsuedoPropertyFlags psuedoPropertyFlags; - const auto readExtendedPropertyStringValue = [&](QScriptValue extendedProperty) { + const auto readExtendedPropertyStringValue = [&](ScriptValue extendedProperty) { const auto extendedPropertyString = extendedProperty.toString(); if (extendedPropertyString == "id") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID); @@ -727,7 +902,7 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); } - EntityPropertyFlags desiredProperties = qscriptvalue_cast(extendedDesiredProperties); + EntityPropertyFlags desiredProperties = scriptvalue_cast(extendedDesiredProperties); bool needsScriptSemantics = desiredProperties.getHasProperty(PROP_POSITION) || desiredProperties.getHasProperty(PROP_ROTATION) || desiredProperties.getHasProperty(PROP_LOCAL_POSITION) || @@ -774,7 +949,7 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri }); } } - QScriptValue finalResult = engine->newArray(resultProperties.size()); + ScriptValue finalResult = engine->newArray(resultProperties.size()); quint32 i = 0; if (needsScriptSemantics) { PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Script Semantics"); @@ -1060,14 +1235,14 @@ QSizeF EntityScriptingInterface::textSize(const QUuid& id, const QString& text) return EntityTree::textSize(id, text); } -void EntityScriptingInterface::setPersistentEntitiesScriptEngine(QSharedPointer engine) { +void EntityScriptingInterface::setPersistentEntitiesScriptEngine(std::shared_ptr manager) { std::lock_guard lock(_entitiesScriptEngineLock); - _persistentEntitiesScriptEngine = engine; + _persistentEntitiesScriptManager = manager; } -void EntityScriptingInterface::setNonPersistentEntitiesScriptEngine(QSharedPointer engine) { +void EntityScriptingInterface::setNonPersistentEntitiesScriptEngine(std::shared_ptr manager) { std::lock_guard lock(_entitiesScriptEngineLock); - _nonPersistentEntitiesScriptEngine = engine; + _nonPersistentEntitiesScriptManager = manager; } void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) { @@ -1076,7 +1251,7 @@ void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& auto entity = getEntityTree()->findEntityByEntityItemID(id); if (entity) { std::lock_guard lock(_entitiesScriptEngineLock); - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (scriptEngine) { scriptEngine->callEntityScriptMethod(id, method, params); } @@ -1124,7 +1299,7 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer auto entity = getEntityTree()->findEntityByEntityItemID(entityID); if (entity) { std::lock_guard lock(_entitiesScriptEngineLock); - auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine; + auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager; if (scriptEngine) { scriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID()); } @@ -1258,7 +1433,7 @@ QVector EntityScriptingInterface::findEntitiesByName(const QString entity } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, - const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) const { + const ScriptValue& entityIdsToInclude, const ScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) const { PROFILE_RANGE(script_entities, __FUNCTION__); QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); @@ -1334,46 +1509,47 @@ bool EntityScriptingInterface::reloadServerScripts(const QUuid& entityID) { return client->reloadServerScript(entityID); } -bool EntityPropertyMetadataRequest::script(EntityItemID entityID, QScriptValue handler) { +bool EntityPropertyMetadataRequest::script(EntityItemID entityID, const ScriptValue& handler) { using LocalScriptStatusRequest = QFutureWatcher; LocalScriptStatusRequest* request = new LocalScriptStatusRequest; - QObject::connect(request, &LocalScriptStatusRequest::finished, _engine, [=]() mutable { + QObject::connect(request, &LocalScriptStatusRequest::finished, _scriptManager, [=]() mutable { auto details = request->result().toMap(); - QScriptValue err, result; + ScriptValue err, result; if (details.contains("isError")) { if (!details.contains("message")) { details["message"] = details["errorInfo"]; } - err = _engine->makeError(_engine->toScriptValue(details)); + err = _scriptManager->engine()->makeError(_scriptManager->engine()->toScriptValue(details)); } else { details["success"] = true; - result = _engine->toScriptValue(details); + result = _scriptManager->engine()->toScriptValue(details); } callScopedHandlerObject(handler, err, result); request->deleteLater(); }); auto entityScriptingInterface = DependencyManager::get(); - entityScriptingInterface->withEntitiesScriptEngine([&](QSharedPointer entitiesScriptEngine) { + entityScriptingInterface->withEntitiesScriptEngine([&](std::shared_ptr entitiesScriptEngine) { if (entitiesScriptEngine) { request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID)); } }, entityID); if (!request->isStarted()) { request->deleteLater(); - callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue()); + auto engine = handler.engine(); + callScopedHandlerObject(handler, engine->makeError(engine->newValue("Entities Scripting Provider unavailable"), "InternalError"), ScriptValue()); return false; } return true; } -bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScriptValue handler) { +bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, const ScriptValue& handler) { auto client = DependencyManager::get(); auto request = client->createScriptStatusRequest(entityID); - QPointer engine = _engine; - QObject::connect(request, &GetScriptStatusRequest::finished, _engine, [=](GetScriptStatusRequest* request) mutable { - auto engine = _engine; - if (!engine) { + QPointer manager = _scriptManager; + QObject::connect(request, &GetScriptStatusRequest::finished, _scriptManager, [=](GetScriptStatusRequest* request) mutable { + auto manager = _scriptManager; + if (!manager) { qCDebug(entities) << __FUNCTION__ << " -- engine destroyed while inflight" << entityID; return; } @@ -1383,7 +1559,7 @@ bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScript details["status"] = EntityScriptStatus_::valueToKey(request->getStatus()).toLower(); details["errorInfo"] = request->getErrorInfo(); - QScriptValue err, result; + ScriptValue err, result; if (!details["success"].toBool()) { if (!details.contains("message") && details.contains("errorInfo")) { details["message"] = details["errorInfo"]; @@ -1391,9 +1567,9 @@ bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScript if (details["message"].toString().isEmpty()) { details["message"] = "entity server script details not found"; } - err = engine->makeError(engine->toScriptValue(details)); + err = manager->engine()->makeError(manager->engine()->toScriptValue(details)); } else { - result = engine->toScriptValue(details); + result = manager->engine()->toScriptValue(details); } callScopedHandlerObject(handler, err, result); request->deleteLater(); @@ -1402,22 +1578,26 @@ bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScript return true; } -bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName) { +bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID, + const ScriptValue& property, + const ScriptValue& scopeOrCallback, + const ScriptValue& methodOrName) { auto name = property.toString(); auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); - QPointer engine = dynamic_cast(handler.engine()); - if (!engine) { - qCDebug(entities) << "queryPropertyMetadata without detectable engine" << entityID << name; + QPointer manager = handler.engine()->manager(); + if (!manager) { + qCDebug(entities) << "queryPropertyMetadata without detectable script manager" << entityID << name; return false; } + auto engine = manager->engine(); #ifdef DEBUG_ENGINE_STATE connect(engine, &QObject::destroyed, this, [=]() { qDebug() << "queryPropertyMetadata -- engine destroyed!" << (!engine ? "nullptr" : "engine"); }); #endif if (!handler.property("callback").isFunction()) { - qDebug() << "!handler.callback.isFunction" << engine; - engine->raiseException(engine->makeError("callback is not a function", "TypeError")); + qDebug() << "!handler.callback.isFunction" << manager; + engine->raiseException(engine->makeError(engine->newValue("callback is not a function"), "TypeError")); return false; } @@ -1433,26 +1613,36 @@ bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID, QScr // This is an async callback pattern -- so if needed C++ can easily throttle or restrict queries later. - EntityPropertyMetadataRequest request(engine); + EntityPropertyMetadataRequest request(manager); if (name == "script") { return request.script(entityID, handler); } else if (name == "serverScripts") { return request.serverScripts(entityID, handler); } else { - engine->raiseException(engine->makeError("metadata for property " + name + " is not yet queryable")); + engine->raiseException(engine->makeError(engine->newValue("metadata for property " + name + " is not yet queryable"))); engine->maybeEmitUncaughtException(__FUNCTION__); return false; } } -bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, QScriptValue callback) { +bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, const ScriptValue& callback) { auto client = DependencyManager::get(); auto request = client->createScriptStatusRequest(entityID); - connect(request, &GetScriptStatusRequest::finished, callback.engine(), [callback](GetScriptStatusRequest* request) mutable { - QString statusString = EntityScriptStatus_::valueToKey(request->getStatus());; - QScriptValueList args { request->getResponseReceived(), request->getIsRunning(), statusString.toLower(), request->getErrorInfo() }; - callback.call(QScriptValue(), args); + + auto engine = callback.engine(); + auto manager = engine->manager(); + if (!manager) { + engine->raiseException(engine->makeError(engine->newValue("This script does not belong to a ScriptManager"))); + engine->maybeEmitUncaughtException(__FUNCTION__); + return false; + } + + connect(request, &GetScriptStatusRequest::finished, manager, [callback](GetScriptStatusRequest* request) mutable { + QString statusString = EntityScriptStatus_::valueToKey(request->getStatus()); + auto engine = callback.engine(); + ScriptValueList args { engine->newValue(request->getResponseReceived()), engine->newValue(request->getIsRunning()), engine->newValue(statusString.toLower()), engine->newValue(request->getErrorInfo()) }; + callback.call(ScriptValue(), args); request->deleteLater(); }); request->start(); @@ -1483,40 +1673,41 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } -QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { - QScriptValue obj = engine->newObject(); +ScriptValue RayToEntityIntersectionResultToScriptValue(ScriptEngine* engine, const RayToEntityIntersectionResult& value) { + ScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); - QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); + ScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); obj.setProperty("distance", value.distance); obj.setProperty("face", boxFaceToString(value.face)); - QScriptValue intersection = vec3ToScriptValue(engine, value.intersection); + ScriptValue intersection = vec3ToScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); + ScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { +bool RayToEntityIntersectionResultFromScriptValue(const ScriptValue& object, RayToEntityIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); - QScriptValue entityIDValue = object.property("entityID"); + ScriptValue entityIDValue = object.property("entityID"); quuidFromScriptValue(entityIDValue, value.entityID); value.distance = object.property("distance").toVariant().toFloat(); value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QScriptValue intersection = object.property("intersection"); + ScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } - QScriptValue surfaceNormal = object.property("surfaceNormal"); + ScriptValue surfaceNormal = object.property("surfaceNormal"); if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } value.extraInfo = object.property("extraInfo").toVariant().toMap(); + return true; } bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function actor) { @@ -2237,14 +2428,15 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons return aaBox.findCapsulePenetration(start, end, radius, penetration); } -void EntityScriptingInterface::getMeshes(const QUuid& entityID, QScriptValue callback) { +void EntityScriptingInterface::getMeshes(const QUuid& entityID, const ScriptValue& callback) { PROFILE_RANGE(script_entities, __FUNCTION__); + auto engine = callback.engine(); EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::getMeshes no entity with ID" << entityID; - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); + ScriptValueList args{ engine->undefinedValue(), engine->newValue(false) }; + callback.call(ScriptValue(), args); return; } @@ -2252,12 +2444,12 @@ void EntityScriptingInterface::getMeshes(const QUuid& entityID, QScriptValue cal bool success = entity->getMeshes(result); if (success) { - QScriptValue resultAsScriptValue = meshesToScriptValue(callback.engine(), result); - QScriptValueList args { resultAsScriptValue, true }; - callback.call(QScriptValue(), args); + ScriptValue resultAsScriptValue = meshesToScriptValue(engine.get(), result); + ScriptValueList args{ resultAsScriptValue, engine->newValue(true) }; + callback.call(ScriptValue(), args); } else { - QScriptValueList args { callback.engine()->undefinedValue(), false }; - callback.call(QScriptValue(), args); + ScriptValueList args{ engine->undefinedValue(), engine->newValue(false) }; + callback.call(ScriptValue(), args); } } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 8a3b92f31fa..46208966f1a 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -15,18 +15,23 @@ #ifndef hifi_EntityScriptingInterface_h #define hifi_EntityScriptingInterface_h +#include + #include #include #include #include #include +#include #include #include #include #include -#include +#include "PointerEvent.h" #include +#include +#include #include "PolyVoxEntityItem.h" #include "LineEntityItem.h" @@ -37,10 +42,10 @@ #include "EntitiesScriptEngineProvider.h" #include "EntityItemProperties.h" -#include "BaseScriptEngine.h" - class EntityTree; class MeshProxy; +class ScriptContext; +class ScriptEngine; extern const QString GRABBABLE_USER_DATA; extern const QString NOT_GRABBABLE_USER_DATA; @@ -51,11 +56,11 @@ extern const QString NOT_GRABBABLE_USER_DATA; // problems with their own Entity scripts. class EntityPropertyMetadataRequest { public: - EntityPropertyMetadataRequest(BaseScriptEngine* engine) : _engine(engine) {}; - bool script(EntityItemID entityID, QScriptValue handler); - bool serverScripts(EntityItemID entityID, QScriptValue handler); + EntityPropertyMetadataRequest(ScriptManager* manager) : _scriptManager(manager){}; + bool script(EntityItemID entityID, const ScriptValue& handler); + bool serverScripts(EntityItemID entityID, const ScriptValue& handler); private: - QPointer _engine; + QPointer _scriptManager; }; /*@jsdoc @@ -86,8 +91,8 @@ class RayToEntityIntersectionResult { QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToEntityIntersectionResult) -QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results); -void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results); +ScriptValue RayToEntityIntersectionResultToScriptValue(ScriptEngine* engine, const RayToEntityIntersectionResult& results); +bool RayToEntityIntersectionResultFromScriptValue(const ScriptValue& object, RayToEntityIntersectionResult& results); class ParabolaToEntityIntersectionResult { public: @@ -182,8 +187,8 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } - void setPersistentEntitiesScriptEngine(QSharedPointer engine); - void setNonPersistentEntitiesScriptEngine(QSharedPointer engine); + void setPersistentEntitiesScriptEngine(std::shared_ptr manager); + void setNonPersistentEntitiesScriptEngine(std::shared_ptr manager); void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } @@ -209,8 +214,8 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende * var propertySets = Entities.getMultipleEntityProperties(entityIDs, "name"); * print("Nearby entity names: " + JSON.stringify(propertySets)); */ - static QScriptValue getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine); - QScriptValue getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector entityIDs, const QScriptValue& extendedDesiredProperties); + static ScriptValue getMultipleEntityProperties(ScriptContext* context, ScriptEngine* engine); + ScriptValue getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector entityIDs, const ScriptValue& extendedDesiredProperties); QUuid addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType); @@ -835,7 +840,7 @@ public slots: /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false, - const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue(), + const ScriptValue& entityIdsToInclude = ScriptValue(), const ScriptValue& entityIdsToDiscard = ScriptValue(), bool visibleOnly = false, bool collidableOnly = false) const; /*@jsdoc @@ -864,7 +869,7 @@ public slots: * @param {string} errorInfo - "" if there is a server entity script running, otherwise it may contain extra * information on the error. */ - Q_INVOKABLE bool getServerScriptStatus(const QUuid& entityID, QScriptValue callback); + Q_INVOKABLE bool getServerScriptStatus(const QUuid& entityID, const ScriptValue& callback); /*@jsdoc * Gets metadata for certain entity properties such as script and serverScripts. @@ -894,8 +899,8 @@ public slots: * @param {object} result - The metadata for the requested entity property if there was no error, otherwise * undefined. */ - Q_INVOKABLE bool queryPropertyMetadata(const QUuid& entityID, QScriptValue property, QScriptValue scopeOrCallback, - QScriptValue methodOrName = QScriptValue()); + Q_INVOKABLE bool queryPropertyMetadata(const QUuid& entityID, const ScriptValue& property, const ScriptValue& scopeOrCallback, + const ScriptValue& methodOrName = ScriptValue()); /*@jsdoc @@ -1908,7 +1913,7 @@ public slots: * {@link Graphics} API instead. */ // FIXME move to a renderable entity interface - Q_INVOKABLE void getMeshes(const QUuid& entityID, QScriptValue callback); + Q_INVOKABLE void getMeshes(const QUuid& entityID, const ScriptValue& callback); /*@jsdoc * Gets the object to world transform, excluding scale, of an entity. @@ -2529,15 +2534,21 @@ public slots: void webEventReceived(const EntityItemID& entityItemID, const QVariant& message); protected: - void withEntitiesScriptEngine(std::function)> function, const EntityItemID& id) { + void withEntitiesScriptEngine(std::function)> function, const EntityItemID& id) { auto entity = getEntityTree()->findEntityByEntityItemID(id); if (entity) { std::lock_guard lock(_entitiesScriptEngineLock); - function((entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine); + function((entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptManager : _nonPersistentEntitiesScriptManager); } }; +private: + void attachDefaultEventHandlers(ScriptManager* manager); // called on first call to Script.addEventHandler + friend void staticEntityScriptInitializer(ScriptManager* manager); + private slots: + void releaseEntityPacketSenderMessages(bool wait); + void handleEntityScriptCallMethodPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode); void onAddingEntity(EntityItem* entity); void onDeletingEntity(EntityItem* entity); @@ -2564,8 +2575,8 @@ private slots: EntityTreePointer _entityTree; std::recursive_mutex _entitiesScriptEngineLock; - QSharedPointer _persistentEntitiesScriptEngine; - QSharedPointer _nonPersistentEntitiesScriptEngine; + std::shared_ptr _persistentEntitiesScriptManager; + std::shared_ptr _nonPersistentEntitiesScriptManager; bool _bidOnSimulationOwnership { false }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 1c544f24f06..0bb59095a59 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -23,8 +23,6 @@ #include #include -#include - #include #include #include @@ -2944,8 +2942,8 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer } entityDescription["DataVersion"] = _persistDataVersion; entityDescription["Id"] = _persistID; - QScriptEngine scriptEngine; - RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, + ScriptEnginePointer engine = newScriptEngine(); + RecurseOctreeToMapOperator theOperator(entityDescription, element, engine.get(), skipDefaultValues, skipThoseWithBadParents, _myAvatar); withReadLock([&] { recurseTreeWithOperator(&theOperator); @@ -3091,10 +3089,10 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { // map will have a top-level list keyed as "Entities". This will be extracted // and iterated over. Each member of this list is converted to a QVariantMap, then - // to a QScriptValue, and then to EntityItemProperties. These properties are used + // to a ScriptValue, and then to EntityItemProperties. These properties are used // to add the new entity to the EntityTree. QVariantList entitiesQList = map["Entities"].toList(); - QScriptEngine scriptEngine; + ScriptEnginePointer scriptEngine = newScriptEngine(); if (entitiesQList.length() == 0) { // Empty map or invalidly formed file. @@ -3105,7 +3103,7 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { bool success = true; foreach (QVariant entityVariant, entitiesQList) { - // QVariantMap --> QScriptValue --> EntityItemProperties --> Entity + // QVariantMap --> ScriptValue --> EntityItemProperties --> Entity QVariantMap entityMap = entityVariant.toMap(); // handle parentJointName for wearables @@ -3118,7 +3116,7 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { " mapped it to parentJointIndex " << entityMap["parentJointIndex"].toInt(); } - QScriptValue entityScriptValue = variantMapToScriptValue(entityMap, scriptEngine); + ScriptValue entityScriptValue = variantMapToScriptValue(entityMap, *scriptEngine); EntityItemProperties properties; EntityItemPropertiesFromScriptValueIgnoreReadOnly(entityScriptValue, properties); @@ -3269,8 +3267,8 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { } bool EntityTree::writeToJSON(QString& jsonString, const OctreeElementPointer& element) { - QScriptEngine scriptEngine; - RecurseOctreeToJSONOperator theOperator(element, &scriptEngine, jsonString); + ScriptEnginePointer engine = newScriptEngine(); + RecurseOctreeToJSONOperator theOperator(element, engine.get(), jsonString); withReadLock([&] { recurseTreeWithOperator(&theOperator); }); diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index 7a9ba147d9b..10572dbcb8a 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -16,8 +16,8 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" -void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, +void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); @@ -43,7 +43,7 @@ void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp } -void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void GrabPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, grabbable, bool, setGrabbable); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, grabKinematic, bool, setGrabKinematic); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, grabFollowsController, bool, setGrabFollowsController); diff --git a/libraries/entities/src/GrabPropertyGroup.h b/libraries/entities/src/GrabPropertyGroup.h index 6f0bc9d48b2..ebf0175ea33 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -16,8 +16,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -25,6 +23,7 @@ class EntityItemProperties; class EncodeBitstreamParams; class OctreePacketData; class ReadBitstreamToTreeParams; +class ScriptValue; static const bool INITIAL_GRABBABLE { true }; static const bool INITIAL_KINEMATIC { true }; @@ -72,10 +71,10 @@ static const glm::vec3 INITIAL_EQUIPPABLE_INDICATOR_OFFSET { glm::vec3(0.0f) }; class GrabPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const GrabPropertyGroup& other); diff --git a/libraries/entities/src/HazePropertyGroup.cpp b/libraries/entities/src/HazePropertyGroup.cpp index 632f73ced6e..04595789e59 100644 --- a/libraries/entities/src/HazePropertyGroup.cpp +++ b/libraries/entities/src/HazePropertyGroup.cpp @@ -16,7 +16,7 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" -void HazePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void HazePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_RANGE, Haze, haze, HazeRange, hazeRange); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_HAZE_COLOR, Haze, haze, HazeColor, hazeColor, u8vec3Color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_HAZE_GLARE_COLOR, Haze, haze, HazeGlareColor, hazeGlareColor, u8vec3Color); @@ -34,7 +34,7 @@ void HazePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); } -void HazePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void HazePropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(haze, hazeRange, float, setHazeRange); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(haze, hazeColor, u8vec3Color, setHazeColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(haze, hazeGlareColor, u8vec3Color, setHazeGlareColor); diff --git a/libraries/entities/src/HazePropertyGroup.h b/libraries/entities/src/HazePropertyGroup.h index 2828692a783..249d4c65bce 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -16,8 +16,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -26,6 +24,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; static const float INITIAL_HAZE_RANGE{ 1000.0f }; static const glm::u8vec3 initialHazeGlareColor { 255, 229, 179 }; @@ -76,10 +76,10 @@ static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; class HazePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const HazePropertyGroup& other); diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index b70e94504df..b96f34d07c0 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -25,8 +25,8 @@ const bool KeyLightPropertyGroup::DEFAULT_KEYLIGHT_CAST_SHADOWS { false }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_SHADOW_BIAS { 0.5f }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_SHADOW_MAX_DISTANCE { 40.0f }; -void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color, u8vec3Color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); @@ -36,7 +36,7 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_SHADOW_MAX_DISTANCE, KeyLight, keyLight, ShadowMaxDistance, shadowMaxDistance); } -void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void KeyLightPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, u8vec3Color, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, vec3, setDirection); diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index f65c6b6fb9e..b04f76a0980 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -17,7 +17,6 @@ #include -#include #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -26,6 +25,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * A key light is defined by the following properties: @@ -45,10 +46,10 @@ class ReadBitstreamToTreeParams; class KeyLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const KeyLightPropertyGroup& other); diff --git a/libraries/entities/src/PropertyGroup.h b/libraries/entities/src/PropertyGroup.h index 8a9d7e9158c..68f3bfb5698 100644 --- a/libraries/entities/src/PropertyGroup.h +++ b/libraries/entities/src/PropertyGroup.h @@ -12,8 +12,6 @@ #ifndef hifi_PropertyGroup_h #define hifi_PropertyGroup_h -#include - #include #include "EntityPropertyFlags.h" @@ -24,6 +22,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr; @@ -32,8 +32,8 @@ class PropertyGroup { virtual ~PropertyGroup() = default; // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const = 0; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) = 0; + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const = 0; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) = 0; virtual void debugDump() const { } virtual void listChangedProperties(QList& out) { } diff --git a/libraries/entities/src/PulsePropertyGroup.cpp b/libraries/entities/src/PulsePropertyGroup.cpp index 54f81750da9..b5ebd0cb2e8 100644 --- a/libraries/entities/src/PulsePropertyGroup.cpp +++ b/libraries/entities/src/PulsePropertyGroup.cpp @@ -57,8 +57,8 @@ void PulsePropertyGroup::setAlphaModeFromString(const QString& pulseMode) { } } -void PulsePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, +void PulsePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MIN, Pulse, pulse, Min, min); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MAX, Pulse, pulse, Max, max); @@ -67,7 +67,7 @@ void PulsePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredPro COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode, getAlphaModeAsString); } -void PulsePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void PulsePropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, min, float, setMin); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, max, float, setMax); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, period, float, setPeriod); diff --git a/libraries/entities/src/PulsePropertyGroup.h b/libraries/entities/src/PulsePropertyGroup.h index 665713d1281..58452fdeead 100644 --- a/libraries/entities/src/PulsePropertyGroup.h +++ b/libraries/entities/src/PulsePropertyGroup.h @@ -13,8 +13,6 @@ #include -#include - #include #include "PropertyGroup.h" @@ -24,6 +22,8 @@ class EntityItemProperties; class EncodeBitstreamParams; class OctreePacketData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * A color and alpha pulse that an entity may have. @@ -40,10 +40,10 @@ class ReadBitstreamToTreeParams; class PulsePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const PulsePropertyGroup& other); diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp index b64a700abcc..855085cbca4 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -11,8 +11,9 @@ #include "RecurseOctreeToJSONOperator.h" #include "EntityItemProperties.h" +#include -RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, +RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer&, ScriptEngine* engine, QString jsonPrefix, bool skipDefaults, bool skipThoseWithBadParents): _engine(engine), _json(jsonPrefix), @@ -34,7 +35,7 @@ void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) return; // we weren't able to resolve a parent from _parentID, so don't save this entity. } - QScriptValue qScriptValues = _skipDefaults + ScriptValue qScriptValues = _skipDefaults ? EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties()) : EntityItemPropertiesToScriptValue(_engine, entity->getProperties()); diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.h b/libraries/entities/src/RecurseOctreeToJSONOperator.h index a1d388ed222..09d383d5a72 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.h +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -11,9 +11,13 @@ #include "EntityTree.h" +#include + +class ScriptEngine; + class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { public: - RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true, + RecurseOctreeToJSONOperator(const OctreeElementPointer&, ScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true, bool skipThoseWithBadParents = false); virtual bool preRecursion(const OctreeElementPointer& element) override { return true; }; virtual bool postRecursion(const OctreeElementPointer& element) override; @@ -23,8 +27,8 @@ class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { private: void processEntity(const EntityItemPointer& entity); - QScriptEngine* _engine; - QScriptValue _toStringMethod; + ScriptEngine* _engine; + ScriptValue _toStringMethod; QString _json; const bool _skipDefaults; diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp index 5be921112fc..deac281d49b 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -12,10 +12,11 @@ #include "RecurseOctreeToMapOperator.h" #include "EntityItemProperties.h" +#include RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, const OctreeElementPointer& top, - QScriptEngine* engine, + ScriptEngine* engine, bool skipDefaultValues, bool skipThoseWithBadParents, std::shared_ptr myAvatar) : @@ -56,7 +57,7 @@ bool RecurseOctreeToMapOperator::postRecursion(const OctreeElementPointer& eleme } EntityItemProperties properties = entityItem->getProperties(); - QScriptValue qScriptValues; + ScriptValue qScriptValues; if (_skipDefaultValues) { qScriptValues = EntityItemNonDefaultPropertiesToScriptValue(_engine, properties); } else { diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.h b/libraries/entities/src/RecurseOctreeToMapOperator.h index 985ec9de35c..9fbd9f41b29 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.h +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -11,16 +11,18 @@ #include "EntityTree.h" +class ScriptEngine; + class RecurseOctreeToMapOperator : public RecurseOctreeOperator { public: - RecurseOctreeToMapOperator(QVariantMap& map, const OctreeElementPointer& top, QScriptEngine* engine, bool skipDefaultValues, + RecurseOctreeToMapOperator(QVariantMap& map, const OctreeElementPointer& top, ScriptEngine* engine, bool skipDefaultValues, bool skipThoseWithBadParents, std::shared_ptr myAvatar); bool preRecursion(const OctreeElementPointer& element) override; bool postRecursion(const OctreeElementPointer& element) override; private: QVariantMap& _map; OctreeElementPointer _top; - QScriptEngine* _engine; + ScriptEngine* _engine; bool _withinTop; bool _skipDefaultValues; bool _skipThoseWithBadParents; diff --git a/libraries/entities/src/RingGizmoPropertyGroup.cpp b/libraries/entities/src/RingGizmoPropertyGroup.cpp index 387cb1d688e..b9bb3a2b7e7 100644 --- a/libraries/entities/src/RingGizmoPropertyGroup.cpp +++ b/libraries/entities/src/RingGizmoPropertyGroup.cpp @@ -20,8 +20,8 @@ const float RingGizmoPropertyGroup::MAX_ALPHA = 1.0f; const float RingGizmoPropertyGroup::MIN_RADIUS = 0.0f; const float RingGizmoPropertyGroup::MAX_RADIUS = 0.5f; -void RingGizmoPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, +void RingGizmoPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_START_ANGLE, Ring, ring, StartAngle, startAngle); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_END_ANGLE, Ring, ring, EndAngle, endAngle); @@ -46,7 +46,7 @@ void RingGizmoPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_MINOR_TICK_MARKS_COLOR, Ring, ring, MinorTickMarksColor, minorTickMarksColor, u8vec3Color); } -void RingGizmoPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void RingGizmoPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, startAngle, float, setStartAngle); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, endAngle, float, setEndAngle); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerRadius, float, setInnerRadius); diff --git a/libraries/entities/src/RingGizmoPropertyGroup.h b/libraries/entities/src/RingGizmoPropertyGroup.h index 1d315622eb9..d34f01c9b06 100644 --- a/libraries/entities/src/RingGizmoPropertyGroup.h +++ b/libraries/entities/src/RingGizmoPropertyGroup.h @@ -11,8 +11,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" #include "EntityItemPropertiesDefaults.h" @@ -21,6 +19,8 @@ class EntityItemProperties; class EncodeBitstreamParams; class OctreePacketData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; using u8vec3Color = glm::u8vec3; @@ -56,10 +56,10 @@ using u8vec3Color = glm::u8vec3; class RingGizmoPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const RingGizmoPropertyGroup& other); diff --git a/libraries/entities/src/SkyboxPropertyGroup.cpp b/libraries/entities/src/SkyboxPropertyGroup.cpp index 89ffa95dbe9..43ffc878f4c 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.cpp +++ b/libraries/entities/src/SkyboxPropertyGroup.cpp @@ -18,12 +18,12 @@ const glm::u8vec3 SkyboxPropertyGroup::DEFAULT_COLOR = { 0, 0, 0 }; -void SkyboxPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { +void SkyboxPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color, u8vec3Color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_SKYBOX_URL, Skybox, skybox, URL, url); } -void SkyboxPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void SkyboxPropertyGroup::copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(skybox, color, u8vec3Color, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(skybox, url, QString, setURL); } diff --git a/libraries/entities/src/SkyboxPropertyGroup.h b/libraries/entities/src/SkyboxPropertyGroup.h index 36cea0a52a4..89b2016802c 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.h +++ b/libraries/entities/src/SkyboxPropertyGroup.h @@ -16,8 +16,6 @@ #include -#include - #include #include "PropertyGroup.h" @@ -28,6 +26,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * A skybox is defined by the following properties: @@ -39,10 +39,10 @@ class ReadBitstreamToTreeParams; class SkyboxPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers - virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, - QScriptEngine* engine, bool skipDefaults, + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, ScriptValue& properties, + ScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const override; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + virtual void copyFromScriptValue(const ScriptValue& object, bool& _defaultSettings) override; void merge(const SkyboxPropertyGroup& other); diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 8804b63de9c..6acce99af54 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -22,7 +22,6 @@ using ModelPointer = std::shared_ptr; namespace gpu { class BufferView; } -class QScriptEngine; namespace scriptable { using Mesh = graphics::Mesh; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 12c07d336d3..aa59a8f445d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -17,15 +17,16 @@ #include "ScriptableMeshPart.h" #include #include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include -GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { +GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), Scriptable() { } void GraphicsScriptingInterface::jsThrowError(const QString& error) { @@ -314,24 +315,24 @@ namespace { } namespace scriptable { - template int registerQPointerMetaType(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>>(engine); - return qScriptRegisterMetaType>( + template int registerQPointerMetaType(ScriptEngine* engine) { + scriptRegisterSequenceMetaType>>(engine); + return scriptRegisterMetaType>( engine, - [](QScriptEngine* engine, const QPointer& object) -> QScriptValue { + [](ScriptEngine* engine, const QPointer& object) -> ScriptValue { if (!object) { - return QScriptValue::NullValue; + return engine->nullValue(); } - return engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::AutoCreateDynamicProperties); + return engine->newQObject(object, ScriptEngine::QtOwnership, ScriptEngine::AutoCreateDynamicProperties); }, - [](const QScriptValue& value, QPointer& out) { + [](const ScriptValue& value, QPointer& out) -> bool { auto obj = value.toQObject(); #ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "qpointer_qobject_cast" << obj << value.toString(); #endif if (auto tmp = qobject_cast(obj)) { out = QPointer(tmp); - return; + return true; } #if 0 if (auto tmp = static_cast(obj)) { @@ -339,20 +340,22 @@ namespace scriptable { qCInfo(graphics_scripting) << "qpointer_qobject_cast -- via static_cast" << obj << tmp << value.toString(); #endif out = QPointer(tmp); - return; + return true; } #endif out = nullptr; + return false; } ); } - QScriptValue qVectorScriptableMaterialLayerToScriptValue(QScriptEngine* engine, const QVector& vector) { - return qScriptValueFromSequence(engine, vector); + ScriptValue qVectorScriptableMaterialLayerToScriptValue(ScriptEngine* engine, const QVector& vector) { + return scriptValueFromSequence(engine, vector); } - void qVectorScriptableMaterialLayerFromScriptValue(const QScriptValue& array, QVector& result) { - qScriptValueToSequence(array, result); + bool qVectorScriptableMaterialLayerFromScriptValue(const ScriptValue& array, QVector& result) { + scriptValueToSequence(array, result); + return true; } /*@jsdoc @@ -469,14 +472,14 @@ namespace scriptable { * @property {boolean} defaultFallthrough - true if all properties fall through to the material below unless * they are set, false if properties respect their individual fall-through settings. */ - QScriptValue scriptableMaterialToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterial &material) { - QScriptValue obj = engine->newObject(); + ScriptValue scriptableMaterialToScriptValue(ScriptEngine* engine, const scriptable::ScriptableMaterial &material) { + ScriptValue obj = engine->newObject(); obj.setProperty("name", material.name); obj.setProperty("model", material.model); bool hasPropertyFallthroughs = !material.propertyFallthroughs.empty(); - const QScriptValue FALLTHROUGH("fallthrough"); + const ScriptValue FALLTHROUGH(engine->newValue("fallthrough")); if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT)) { obj.setProperty("opacity", FALLTHROUGH); } else if (material.key.isTranslucentFactor()) { @@ -624,49 +627,53 @@ namespace scriptable { return obj; } - void scriptableMaterialFromScriptValue(const QScriptValue &object, scriptable::ScriptableMaterial& material) { - // No need to convert from QScriptValue to ScriptableMaterial + bool scriptableMaterialFromScriptValue(const ScriptValue& object, scriptable::ScriptableMaterial& material) { + // No need to convert from ScriptValue to ScriptableMaterial + return false; } - QScriptValue scriptableMaterialLayerToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterialLayer &materialLayer) { - QScriptValue obj = engine->newObject(); + ScriptValue scriptableMaterialLayerToScriptValue(ScriptEngine* engine, const scriptable::ScriptableMaterialLayer &materialLayer) { + ScriptValue obj = engine->newObject(); obj.setProperty("material", scriptableMaterialToScriptValue(engine, materialLayer.material)); obj.setProperty("priority", materialLayer.priority); return obj; } - void scriptableMaterialLayerFromScriptValue(const QScriptValue &object, scriptable::ScriptableMaterialLayer& materialLayer) { - // No need to convert from QScriptValue to ScriptableMaterialLayer + bool scriptableMaterialLayerFromScriptValue(const ScriptValue& object, scriptable::ScriptableMaterialLayer& materialLayer) { + // No need to convert from ScriptValue to ScriptableMaterialLayer + return false; } - QScriptValue multiMaterialMapToScriptValue(QScriptEngine* engine, const scriptable::MultiMaterialMap& map) { - QScriptValue obj = engine->newObject(); + ScriptValue multiMaterialMapToScriptValue(ScriptEngine* engine, const scriptable::MultiMaterialMap& map) { + ScriptValue obj = engine->newObject(); for (auto key : map.keys()) { obj.setProperty(key, qVectorScriptableMaterialLayerToScriptValue(engine, map[key])); } return obj; } - void multiMaterialMapFromScriptValue(const QScriptValue& map, scriptable::MultiMaterialMap& result) { - // No need to convert from QScriptValue to MultiMaterialMap + bool multiMaterialMapFromScriptValue(const ScriptValue& map, scriptable::MultiMaterialMap& result) { + // No need to convert from ScriptValue to MultiMaterialMap + return false; } - template int registerDebugEnum(QScriptEngine* engine, const DebugEnums& debugEnums) { + template int registerDebugEnum(ScriptEngine* engine, const DebugEnums& debugEnums) { static const DebugEnums& instance = debugEnums; - return qScriptRegisterMetaType( + return scriptRegisterMetaType( engine, - [](QScriptEngine* engine, const T& topology) -> QScriptValue { - return instance.value(topology); + [](ScriptEngine* engine, const T& topology) -> ScriptValue { + return engine->newValue(instance.value(topology)); }, - [](const QScriptValue& value, T& topology) { + [](const ScriptValue& value, T& topology) -> bool { topology = instance.key(value.toString()); + return true; } ); } } -void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>(engine); +void GraphicsScriptingInterface::registerMetaTypes(ScriptEngine* engine) { + scriptRegisterSequenceMetaType>(engine); scriptable::registerQPointerMetaType(engine); scriptable::registerQPointerMetaType(engine); @@ -677,10 +684,10 @@ void GraphicsScriptingInterface::registerMetaTypes(QScriptEngine* engine) { scriptable::registerDebugEnum(engine, gpu::SEMANTICS); scriptable::registerDebugEnum(engine, gpu::DIMENSIONS); - qScriptRegisterMetaType(engine, scriptable::scriptableMaterialToScriptValue, scriptable::scriptableMaterialFromScriptValue); - qScriptRegisterMetaType(engine, scriptable::scriptableMaterialLayerToScriptValue, scriptable::scriptableMaterialLayerFromScriptValue); - qScriptRegisterMetaType(engine, scriptable::qVectorScriptableMaterialLayerToScriptValue, scriptable::qVectorScriptableMaterialLayerFromScriptValue); - qScriptRegisterMetaType(engine, scriptable::multiMaterialMapToScriptValue, scriptable::multiMaterialMapFromScriptValue); + scriptRegisterMetaType(engine, scriptable::scriptableMaterialToScriptValue, scriptable::scriptableMaterialFromScriptValue); + scriptRegisterMetaType(engine, scriptable::scriptableMaterialLayerToScriptValue, scriptable::scriptableMaterialLayerFromScriptValue); + scriptRegisterMetaType(engine, scriptable::qVectorScriptableMaterialLayerToScriptValue, scriptable::qVectorScriptableMaterialLayerFromScriptValue); + scriptRegisterMetaType(engine, scriptable::multiMaterialMapToScriptValue, scriptable::multiMaterialMapFromScriptValue); Q_UNUSED(metaTypeIds); } diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index beb5b340e88..a2ffb9184b7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -14,13 +14,13 @@ #include #include -#include -#include - #include "ScriptableMesh.h" #include #include "RegisteredMetaTypes.h" +#include +#include +class ScriptEngine; /*@jsdoc * The Graphics API enables you to access and manipulate avatar, entity, and overlay models in the rendered scene. @@ -34,11 +34,11 @@ * @hifi-avatar */ -class GraphicsScriptingInterface : public QObject, public QScriptable, public Dependency { +class GraphicsScriptingInterface : public QObject, public Scriptable, public Dependency { Q_OBJECT public: - static void registerMetaTypes(QScriptEngine* engine); + static void registerMetaTypes(ScriptEngine* engine); GraphicsScriptingInterface(QObject* parent = nullptr); public slots: @@ -149,7 +149,7 @@ public slots: }; namespace scriptable { - QScriptValue scriptableMaterialToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterial &material); + ScriptValue scriptableMaterialToScriptValue(ScriptEngine* engine, const scriptable::ScriptableMaterial &material); }; Q_DECLARE_METATYPE(NestableType) diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp index 2db34258c97..9730fd0ec87 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -7,11 +7,12 @@ #include "GraphicsScriptingUtil.h" -#include - #include #include #include +#include +#include +#include using buffer_helpers::glmVecToVariant; @@ -77,7 +78,7 @@ QVariant toVariant(const gpu::Element& element) { }; } -QScriptValue jsBindCallback(QScriptValue value) { +ScriptValue jsBindCallback(const ScriptValue& value) { if (value.isObject() && value.property("callback").isFunction()) { // value is already a bound callback return value; @@ -85,8 +86,8 @@ QScriptValue jsBindCallback(QScriptValue value) { auto engine = value.engine(); auto context = engine ? engine->currentContext() : nullptr; auto length = context ? context->argumentCount() : 0; - QScriptValue scope = context ? context->thisObject() : QScriptValue::NullValue; - QScriptValue method; + ScriptValue scope = context ? context->thisObject() : engine->nullValue(); + ScriptValue method; #ifdef SCRIPTABLE_MESH_DEBUG qCInfo(graphics_scripting) << "jsBindCallback" << engine << length << scope.toQObject() << method.toString(); #endif @@ -111,9 +112,9 @@ QScriptValue jsBindCallback(QScriptValue value) { } template -T this_qobject_cast(QScriptEngine* engine) { +T this_qobject_cast(ScriptEngine* engine) { auto context = engine ? engine->currentContext() : nullptr; - return qscriptvalue_cast(context ? context->thisObject() : QScriptValue::NullValue); + return scriptvalue_cast(context ? context->thisObject() : engine ? engine->nullValue() : ScriptValue()); } QString toDebugString(QObject* tmp) { QString s; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h index 1ca62277ff5..53b49d1bd34 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include #include @@ -9,6 +7,9 @@ #include #include #include +#include + +class ScriptEngine; class Extents; class AABox; @@ -24,15 +25,15 @@ namespace scriptable { QVariant toVariant(const glm::mat4& mat4); // helper that automatically resolves Qt-signal-like scoped callbacks - // ... C++ side: `void MyClass::asyncMethod(..., QScriptValue callback)` + // ... C++ side: `void MyClass::asyncMethod(..., const ScriptValue& callback)` // ... JS side: // * `API.asyncMethod(..., function(){})` // * `API.asyncMethod(..., scope, function(){})` // * `API.asyncMethod(..., scope, "methodName")` - QScriptValue jsBindCallback(QScriptValue callback); + ScriptValue jsBindCallback(const ScriptValue& callback); // cast engine->thisObject() => C++ class instance - template T this_qobject_cast(QScriptEngine* engine); + template T this_qobject_cast(ScriptEngine* engine); QString toDebugString(QObject* tmp); template QString toDebugString(std::shared_ptr tmp); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp index f7d40f19f29..f19e58eca9d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -7,17 +7,17 @@ #include "ScriptableMesh.h" -#include - #include #include #include -#include #include #include #include #include +#include +#include +#include #include "Forward.h" #include "ScriptableMeshPart.h" @@ -27,7 +27,7 @@ // #define SCRIPTABLE_MESH_DEBUG 1 scriptable::ScriptableMesh::ScriptableMesh(const ScriptableMeshBase& other) - : ScriptableMeshBase(other), QScriptable() { + : ScriptableMeshBase(other), Scriptable() { auto mesh = getMeshPointer(); QString name = mesh ? QString::fromStdString(mesh->modelName) : ""; if (name.isEmpty()) { @@ -264,7 +264,7 @@ bool scriptable::ScriptableMesh::setVertexProperty(glm::uint32 vertexIndex, cons * @param {number} index - The vertex index. * @param {object} properties - The properties of the mesh, per {@link GraphicsMesh}. */ -glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { +glm::uint32 scriptable::ScriptableMesh::forEachVertex(const ScriptValue& _callback) { auto mesh = getMeshPointer(); if (!mesh) { return 0; @@ -278,10 +278,10 @@ glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { if (!js) { return 0; } - auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + auto meshPart = js ? js->toScriptValue(getSelf()) : js->nullValue(); int numProcessed = 0; buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) { - auto result = callback.call(scope, { js->toScriptValue(values), index, meshPart }); + auto result = callback.call(scope, { js->toScriptValue(values), js->newValue(index), meshPart }); if (js->hasUncaughtException()) { js->currentContext()->throwValue(js->uncaughtException()); return false; @@ -302,7 +302,7 @@ glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { * @returns {Object|boolean} The attribute values to update the vertex with, or * false to not update the vertex. */ -glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(QScriptValue _callback) { +glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(const ScriptValue& _callback) { auto mesh = getMeshPointer(); if (!mesh) { return 0; @@ -316,12 +316,12 @@ glm::uint32 scriptable::ScriptableMesh::updateVertexAttributes(QScriptValue _cal if (!js) { return 0; } - auto meshPart = js ? js->toScriptValue(getSelf()) : QScriptValue::NullValue; + auto meshPart = js ? js->toScriptValue(getSelf()) : js->nullValue(); int numProcessed = 0; auto attributeViews = buffer_helpers::mesh::getAllBufferViews(mesh); buffer_helpers::mesh::forEachVertex(mesh, [&](glm::uint32 index, const QVariantMap& values) { auto obj = js->toScriptValue(values); - auto result = callback.call(scope, { obj, index, meshPart }); + auto result = callback.call(scope, { obj, js->newValue(index), meshPart }); if (js->hasUncaughtException()) { js->currentContext()->throwValue(js->uncaughtException()); return false; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index a6ebbe85363..4a1419dd45a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -20,12 +20,13 @@ #include #include #include -#include -#include #include "GraphicsScriptingUtil.h" #include +#include + +class ScriptValue; namespace scriptable { /*@jsdoc @@ -65,7 +66,7 @@ namespace scriptable { * @borrows GraphicsMesh.getVertextAttributes as getVertextAttributes * @borrows GraphicsMesh.setVertextAttributes as setVertextAttributes */ - class ScriptableMesh : public ScriptableMeshBase, QScriptable { + class ScriptableMesh : public ScriptableMeshBase, Scriptable { Q_OBJECT public: Q_PROPERTY(glm::uint32 numParts READ getNumParts) @@ -83,11 +84,11 @@ namespace scriptable { operator const ScriptableMeshBase*() const { return (qobject_cast(this)); } ScriptableMesh(WeakModelProviderPointer provider, ScriptableModelBasePointer model, MeshPointer mesh, QObject* parent) - : ScriptableMeshBase(provider, model, mesh, parent), QScriptable() { strongMesh = mesh; } + : ScriptableMeshBase(provider, model, mesh, parent), Scriptable() { strongMesh = mesh; } ScriptableMesh(MeshPointer mesh, QObject* parent) - : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, parent), QScriptable() { strongMesh = mesh; } + : ScriptableMeshBase(WeakModelProviderPointer(), nullptr, mesh, parent), Scriptable() { strongMesh = mesh; } ScriptableMesh(const ScriptableMeshBase& other); - ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other), QScriptable() {}; + ScriptableMesh(const ScriptableMesh& other) : ScriptableMeshBase(other), Scriptable() {}; virtual ~ScriptableMesh(); const scriptable::MeshPointer getOwnedMeshPointer() const { return strongMesh; } @@ -224,7 +225,7 @@ namespace scriptable { */ scriptable::ScriptableMeshPointer cloneMesh(); - // QScriptEngine-specific wrappers + // ScriptEngine-specific wrappers /*@jsdoc * Updates vertex attributes by calling a function for each vertex. The function can return modified attributes to @@ -233,7 +234,7 @@ namespace scriptable { * @param {GraphicsMesh~updateVertexAttributesCallback} callback - The function to call for each vertex. * @returns {number} The number of vertices the callback was called for. */ - glm::uint32 updateVertexAttributes(QScriptValue callback); + glm::uint32 updateVertexAttributes(const ScriptValue& callback); /*@jsdoc * Calls a function for each vertex. @@ -241,7 +242,7 @@ namespace scriptable { * @param {GraphicsMesh~forEachVertexCallback} callback - The function to call for each vertex. * @returns {number} The number of vertices the callback was called for. */ - glm::uint32 forEachVertex(QScriptValue callback); + glm::uint32 forEachVertex(const ScriptValue& callback); /*@jsdoc * Checks if an index is valid and, optionally, that vertex has a particular attribute. diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp index 9914aca1772..1b93705dc86 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -11,8 +11,7 @@ #include #include -#include -#include +#include #include #include #include @@ -78,12 +77,12 @@ QVariantList scriptable::ScriptableMeshPart::queryVertexAttributes(QVariant sele return parentMesh->queryVertexAttributes(selector); } -glm::uint32 scriptable::ScriptableMeshPart::forEachVertex(QScriptValue _callback) { +glm::uint32 scriptable::ScriptableMeshPart::forEachVertex(const ScriptValue& _callback) { // TODO: limit to vertices within the part's indexed range? return isValid() ? parentMesh->forEachVertex(_callback) : 0; } -glm::uint32 scriptable::ScriptableMeshPart::updateVertexAttributes(QScriptValue _callback) { +glm::uint32 scriptable::ScriptableMeshPart::updateVertexAttributes(const ScriptValue& _callback) { // TODO: limit to vertices within the part's indexed range? return isValid() ? parentMesh->updateVertexAttributes(_callback) : 0; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 716081a4b8c..009d7b257cf 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -8,6 +8,9 @@ #pragma once #include "ScriptableMesh.h" +#include + +class ScriptValue; namespace scriptable { /*@jsdoc @@ -55,7 +58,7 @@ namespace scriptable { * @borrows GraphicsMesh.getVertexAttributes as getVertextAttributes * @borrows GraphicsMesh.setVertexAttributes as setVertextAttributes */ - class ScriptableMeshPart : public QObject, QScriptable { + class ScriptableMeshPart : public QObject, Scriptable { Q_OBJECT Q_PROPERTY(bool valid READ isValid) Q_PROPERTY(glm::uint32 partIndex MEMBER partIndex CONSTANT) @@ -78,7 +81,7 @@ namespace scriptable { public: ScriptableMeshPart(scriptable::ScriptableMeshPointer parentMesh, int partIndex); ScriptableMeshPart& operator=(const ScriptableMeshPart& view) { parentMesh=view.parentMesh; return *this; }; - ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), QScriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} + ScriptableMeshPart(const ScriptableMeshPart& other) : QObject(other.parent()), Scriptable(), parentMesh(other.parentMesh), partIndex(other.partIndex) {} bool isValid() const { auto mesh = getMeshPointer(); return mesh && partIndex < mesh->getNumParts(); } public slots: @@ -273,7 +276,7 @@ namespace scriptable { QString toOBJ(); - // QScriptEngine-specific wrappers + // ScriptEngine-specific wrappers /*@jsdoc * Updates vertex attributes by calling a function for each vertex in the whole mesh (i.e., the parent and @@ -282,7 +285,7 @@ namespace scriptable { * @param {GraphicsMesh~updateVertexAttributesCallback} callback - The function to call for each vertex. * @returns {number} The number of vertices the callback was called for. */ - glm::uint32 updateVertexAttributes(QScriptValue callback); + glm::uint32 updateVertexAttributes(const ScriptValue& callback); /*@jsdoc * Calls a function for each vertex in the whole mesh (i.e., parent and mesh parts). @@ -290,7 +293,7 @@ namespace scriptable { * @param {GraphicsMesh~forEachVertexCallback} callback - The function to call for each vertex. * @returns {number} The number of vertices the callback was called for. */ - glm::uint32 forEachVertex(QScriptValue callback); + glm::uint32 forEachVertex(const ScriptValue& callback); /*@jsdoc * Checks if an index is valid and, optionally, that vertex has a particular attribute. diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 28cd49e7c47..ed61fa049f5 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -10,7 +10,7 @@ #include "ScriptableModel.h" -#include +#include #include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" @@ -262,7 +262,7 @@ scriptable::ScriptableMeshes scriptable::ScriptableModel::getMeshes() { } #if 0 -glm::uint32 scriptable::ScriptableModel::forEachVertexAttribute(QScriptValue callback) { +glm::uint32 scriptable::ScriptableModel::forEachVertexAttribute(const ScriptValue& callback) { glm::uint32 result = 0; scriptable::ScriptableMeshes in = getMeshes(); if (in.size()) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h index 637b5378001..fb6f1dc0ca7 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -10,8 +10,6 @@ #include "Forward.h" #include "GraphicsScriptingUtil.h" -class QScriptValue; - namespace scriptable { using ScriptableMeshes = QVector; diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index b1fcc4076a0..bc48c10b3a7 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -1,5 +1,6 @@ set(TARGET_NAME input-plugins) setup_hifi_library() link_hifi_libraries(shared plugins ui-plugins controllers ui) +include_hifi_library_headers(script-engine) GroupSources("src/input-plugins") diff --git a/libraries/midi/CMakeLists.txt b/libraries/midi/CMakeLists.txt index dc54819c2b9..66757566408 100644 --- a/libraries/midi/CMakeLists.txt +++ b/libraries/midi/CMakeLists.txt @@ -1,3 +1,8 @@ set(TARGET_NAME midi) setup_hifi_library(Network) link_hifi_libraries(shared networking) +include_hifi_library_headers(script-engine) + +if (WIN32) + target_link_libraries(${TARGET_NAME} winmm.lib) +endif () diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 02d47198e96..8544a809810 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -15,11 +15,13 @@ #include -#if defined Q_OS_WIN32 -#include "Windows.h" -#endif +#include +#include #if defined Q_OS_WIN32 +#include +#include + const int MIDI_BYTE_MASK = 0x0FF; const int MIDI_NIBBLE_MASK = 0x00F; const int MIDI_PITCH_BEND_MASK = 0x3F80; @@ -131,6 +133,12 @@ void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD } } +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine(); + + scriptEngine->registerGlobalObject("Midi", DependencyManager::get().data()); +}); + void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { switch (wMsg) { case MOM_OPEN: diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h index 073eb1c00fc..58c55a5e1f9 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -12,10 +12,6 @@ #ifndef hifi_SimpleMeshProxy_h #define hifi_SimpleMeshProxy_h -#include -#include -#include - #include class SimpleMeshProxy : public MeshProxy { diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 9c0bb846f76..dfa2bf8f359 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 1dcc5ea2ef1..2a4cf779584 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -9,7 +9,7 @@ // // BaseAssetScriptingInterface contains the engine-agnostic support code that can be used from -// both QML JS and QScriptEngine JS engine implementations +// both QML JS and ScriptEngine JS engine implementations #ifndef hifi_BaseAssetScriptingInterface_h #define hifi_BaseAssetScriptingInterface_h diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index b78665a7a69..45fd7c1db82 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -28,8 +28,6 @@ #include #include -#include - #include #include "ResourceManager.h" @@ -234,7 +232,7 @@ protected slots: void updateTotalSize(const qint64& deltaSize); - // Prefetches a resource to be held by the QScriptEngine. + // Prefetches a resource to be held by the ScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); @@ -252,10 +250,10 @@ private slots: void clearATPAssets(); protected: - // Prefetches a resource to be held by the QScriptEngine. + // Prefetches a resource to be held by the ScriptEngine. // Pointers created through this method should be owned by the caller, - // which should be a QScriptEngine with ScriptableResource registered, so that - // the QScriptEngine will delete the pointer when it is garbage collected. + // which should be a ScriptEngine with ScriptableResource registered, so that + // the ScriptEngine will delete the pointer when it is garbage collected. // JSDoc is provided on more general function signature. Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index ad4900e4ba1..2d6bbe27b3d 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -14,5 +14,6 @@ include_hifi_library_headers(gpu) include_hifi_library_headers(hfm) include_hifi_library_headers(model-serializers) include_hifi_library_headers(graphics) +include_hifi_library_headers(script-engine) target_bullet() diff --git a/libraries/physics/src/ObjectDynamic.h b/libraries/physics/src/ObjectDynamic.h index 49fa615b889..96c2f225bbd 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -23,7 +23,7 @@ #include "ObjectMotionState.h" #include "BulletUtil.h" -#include "EntityDynamicInterface.h" +#include class ObjectDynamic : public EntityDynamicInterface, public ReadWriteLockable { diff --git a/libraries/pointers/CMakeLists.txt b/libraries/pointers/CMakeLists.txt index e33c76e2495..184e9ae5549 100644 --- a/libraries/pointers/CMakeLists.txt +++ b/libraries/pointers/CMakeLists.txt @@ -2,4 +2,4 @@ set(TARGET_NAME pointers) setup_hifi_library() GroupSources(src) link_hifi_libraries(shared controllers) - +include_hifi_library_headers(script-engine) diff --git a/libraries/recording/CMakeLists.txt b/libraries/recording/CMakeLists.txt index b42a4018f8c..ef357fdd5d7 100644 --- a/libraries/recording/CMakeLists.txt +++ b/libraries/recording/CMakeLists.txt @@ -1,9 +1,9 @@ set(TARGET_NAME recording) # set a default root dir for each of our optional externals if it was not passed -setup_hifi_library(Script) +setup_hifi_library() # use setup_hifi_library macro to setup our project and link appropriate Qt modules -link_hifi_libraries(shared networking) +link_hifi_libraries(shared networking script-engine) GroupSources("src/recording") diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/recording/src/recording/RecordingScriptingInterface.cpp similarity index 83% rename from libraries/script-engine/src/RecordingScriptingInterface.cpp rename to libraries/recording/src/recording/RecordingScriptingInterface.cpp index cbcf94662e2..82fefe26952 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/recording/src/recording/RecordingScriptingInterface.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -21,13 +20,16 @@ #include #include #include -#include -#include -#include -#include -#include +#include "Deck.h" +#include "Recorder.h" +#include "Clip.h" +#include "Frame.h" +#include "ClipCache.h" -#include "ScriptEngineLogging.h" +#include +#include +#include +#include using namespace recording; @@ -54,18 +56,19 @@ float RecordingScriptingInterface::playerLength() const { return _player->length(); } -void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, const QString& url, QScriptValue callback) { +void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, const QString& url, const ScriptValue& callback) { _player->queueClip(clipLoader->getClip()); if (callback.isFunction()) { - QScriptValueList args { true, url }; - callback.call(QScriptValue(), args); + auto engine = callback.engine(); + ScriptValueList args{ engine->newValue(true), engine->newValue(url) }; + callback.call(ScriptValue(), args); } } -void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue callback) { +void RecordingScriptingInterface::loadRecording(const QString& url, const ScriptValue& callback) { if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "loadRecording", Q_ARG(const QString&, url), Q_ARG(QScriptValue, callback)); + BLOCKING_INVOKE_METHOD(this, "loadRecording", Q_ARG(const QString&, url), Q_ARG(const ScriptValue&, callback)); return; } @@ -82,8 +85,14 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue auto weakClipLoader = clipLoader.toWeakRef(); + auto manager = callback.engine()->manager(); + if (!manager) { + qWarning() << "This script does not belong to a ScriptManager"; + return; + } + // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, callback.engine(), + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, manager, [this, weakClipLoader, url, callback]() mutable { if (auto clipLoader = weakClipLoader.toStrongRef()) { @@ -97,12 +106,14 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue }); // when clip load fails, call the callback with the URL and failure boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { + connect(clipLoader.data(), &recording::NetworkClipLoader::failed, manager, + [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { qCDebug(scriptengine) << "Failed to load recording from\"" << url << '"'; if (callback.isFunction()) { - QScriptValueList args { false, url }; - callback.call(QScriptValue(), args); + auto engine = callback.engine(); + ScriptValueList args{ engine->newValue(false), engine->newValue(url) }; + callback.call(ScriptValue(), args); } if (auto clipLoader = weakClipLoader.toStrongRef()) { @@ -248,7 +259,7 @@ void RecordingScriptingInterface::saveRecording(const QString& filename) { recording::Clip::toFile(filename, _lastClip); } -bool RecordingScriptingInterface::saveRecordingToAsset(QScriptValue getClipAtpUrl) { +bool RecordingScriptingInterface::saveRecordingToAsset(const ScriptValue& getClipAtpUrl) { if (!getClipAtpUrl.isFunction()) { qCWarning(scriptengine) << "The argument is not a function."; return false; @@ -258,7 +269,7 @@ bool RecordingScriptingInterface::saveRecordingToAsset(QScriptValue getClipAtpUr bool result; BLOCKING_INVOKE_METHOD(this, "saveRecordingToAsset", Q_RETURN_ARG(bool, result), - Q_ARG(QScriptValue, getClipAtpUrl)); + Q_ARG(const ScriptValue&, getClipAtpUrl)); return result; } @@ -267,9 +278,14 @@ bool RecordingScriptingInterface::saveRecordingToAsset(QScriptValue getClipAtpUr return false; } + auto manager = getClipAtpUrl.engine()->manager(); + if (!manager) { + qWarning() << "This script does not belong to a ScriptManager"; + return false; + } + if (auto upload = DependencyManager::get()->createUpload(recording::Clip::toBuffer(_lastClip))) { - QObject::connect(upload, &AssetUpload::finished, - getClipAtpUrl.engine(), [=](AssetUpload* upload, const QString& hash) mutable { + QObject::connect(upload, &AssetUpload::finished, manager, [=](AssetUpload* upload, const QString& hash) mutable { QString clip_atp_url = ""; if (upload->getError() == AssetUpload::NoError) { @@ -280,9 +296,9 @@ bool RecordingScriptingInterface::saveRecordingToAsset(QScriptValue getClipAtpUr qCWarning(scriptengine) << "Error during the Asset upload."; } - QScriptValueList args; - args << clip_atp_url; - getClipAtpUrl.call(QScriptValue(), args); + ScriptValueList args; + args << getClipAtpUrl.engine()->newValue(clip_atp_url); + getClipAtpUrl.call(ScriptValue(), args); }); upload->start(); return true; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/recording/src/recording/RecordingScriptingInterface.h similarity index 97% rename from libraries/script-engine/src/RecordingScriptingInterface.h rename to libraries/recording/src/recording/RecordingScriptingInterface.h index a170958a918..126c81e3b43 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/recording/src/recording/RecordingScriptingInterface.h @@ -18,14 +18,12 @@ #include #include -#include #include -#include -#include -#include +#include -class QScriptEngine; -class QScriptValue; +#include "ClipCache.h" +#include "Forward.h" +#include "Frame.h" /*@jsdoc * The Recording API makes and plays back recordings of voice and avatar movements. Playback may be done on a @@ -73,7 +71,7 @@ public slots: * }); * } */ - void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); + void loadRecording(const QString& url, const ScriptValue& callback = ScriptValue()); /*@jsdoc @@ -337,7 +335,7 @@ public slots: * } * }, 5000); */ - bool saveRecordingToAsset(QScriptValue getClipAtpUrl); + bool saveRecordingToAsset(const ScriptValue& getClipAtpUrl); /*@jsdoc * Loads the most recently made recording and plays it back on your avatar. @@ -370,7 +368,7 @@ public slots: QSet _clipLoaders; private: - void playClip(recording::NetworkClipLoaderPointer clipLoader, const QString& url, QScriptValue callback); + void playClip(recording::NetworkClipLoaderPointer clipLoader, const QString& url, const ScriptValue& callback); }; #endif // hifi_RecordingScriptingInterface_h diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 904e7ea94c6..6ac1a3518e1 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -2,11 +2,12 @@ set(TARGET_NAME render-utils) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") -setup_hifi_library(Gui Network Qml Quick Script) +setup_hifi_library(Gui Network Qml Quick) link_hifi_libraries(shared task ktx gpu shaders graphics graphics-scripting material-networking model-networking render animation model-serializers image procedural) include_hifi_library_headers(audio) include_hifi_library_headers(networking) include_hifi_library_headers(octree) +include_hifi_library_headers(script-engine) include_hifi_library_headers(hfm) # tell CMake to exclude qrc_fonts.cpp for policy CMP0071 diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index af477a2f09c..15658b053df 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -45,7 +45,6 @@ #define SKIN_DQ class AbstractViewStateInterface; -class QScriptEngine; class ViewFrustum; diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 6def6c185fa..ec30d52bfc7 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,18 +1,12 @@ set(TARGET_NAME script-engine) # FIXME Move undo scripting interface to application and remove Widgets -setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets) +setup_hifi_library(Network Script WebSockets) target_zlib() if (NOT ANDROID) target_quazip() endif () -link_hifi_libraries(shared networking shaders material-networking model-networking recording avatars model-serializers entities controllers animation audio midi) -include_hifi_library_headers(gl) -include_hifi_library_headers(hfm) -include_hifi_library_headers(gpu) -include_hifi_library_headers(ktx) -include_hifi_library_headers(image) -include_hifi_library_headers(graphics) +link_hifi_libraries(networking) include_hifi_library_headers(octree) -include_hifi_library_headers(procedural) \ No newline at end of file +include_hifi_library_headers(shared) diff --git a/libraries/script-engine/src/AbstractScriptingServicesInterface.h b/libraries/script-engine/src/AbstractScriptingServicesInterface.h index c3acac7c766..5c99240e906 100644 --- a/libraries/script-engine/src/AbstractScriptingServicesInterface.h +++ b/libraries/script-engine/src/AbstractScriptingServicesInterface.h @@ -15,13 +15,16 @@ #ifndef hifi_AbstractScriptingServicesInterface_h #define hifi_AbstractScriptingServicesInterface_h -#include +#include + +class ScriptManager; +using ScriptManagerPointer = std::shared_ptr; /// Interface provided by Application to other objects that need access to scripting services of the application class AbstractScriptingServicesInterface { public: /// Registers application specific services with a script engine. - virtual void registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) = 0; + virtual void registerScriptEngineWithApplicationServices(const ScriptManagerPointer& scriptEngine) = 0; }; diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 8f97f1ce336..99ef12ab62c 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -13,18 +13,20 @@ #include #include -#include #include #include #include -#include #include #include #include #include "ScriptEngine.h" +#include "ScriptEngineCast.h" #include "ScriptEngineLogging.h" +#include "ScriptManager.h" +#include "ScriptValue.h" +#include "ScriptValueUtils.h" #include #include @@ -35,7 +37,9 @@ using Promise = MiniPromise::Promise; AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { qCDebug(scriptengine) << "AssetScriptingInterface::AssetScriptingInterface" << parent; - MiniPromise::registerMetaTypes(parent); + + auto scriptManager = qobject_cast(parent); + scriptRegisterMetaType(scriptManager->engine().get(), promiseToScriptValue, promiseFromScriptValue); } #define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } } @@ -59,16 +63,17 @@ bool AssetScriptingInterface::initializeCache() { } } -void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { +void AssetScriptingInterface::uploadData(QString data, const ScriptValue& callback) { auto handler = jsBindCallback(thisObject(), callback); QByteArray dataByteArray = data.toUtf8(); auto upload = DependencyManager::get()->createUpload(dataByteArray); Promise deferred = makePromise(__FUNCTION__); + auto scriptEngine = engine(); deferred->ready([=](QString error, QVariantMap result) { auto url = result.value("url").toString(); auto hash = result.value("hash").toString(); - jsCallback(handler, url, hash); + jsCallback(handler, scriptEngine->newValue(url), scriptEngine->newValue(hash)); }); connect(upload, &AssetUpload::finished, upload, [deferred](AssetUpload* upload, const QString& hash) { @@ -83,12 +88,13 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { upload->start(); } -void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) { +void AssetScriptingInterface::setMapping(QString path, QString hash, const ScriptValue& callback) { auto handler = jsBindCallback(thisObject(), callback); auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash); Promise deferred = makePromise(__FUNCTION__); + auto scriptEngine = engine(); deferred->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); + jsCallback(handler, scriptEngine->newValue(error), result); }); connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [deferred](SetMappingRequest* request) { @@ -107,7 +113,7 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValu * @typedef {object} Assets.DownloadDataError * @property {string} errorMessage - "" if the download was successful, otherwise a description of the error. */ -void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) { +void AssetScriptingInterface::downloadData(QString urlString, const ScriptValue& callback) { // FIXME: historically this API method failed silently when given a non-atp prefixed // urlString (or if the AssetRequest failed). // .. is that by design or could we update without breaking things to provide better feedback to scripts? @@ -123,9 +129,10 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb auto assetRequest = assetClient->createRequest(hash); Promise deferred = makePromise(__FUNCTION__); + auto scriptEngine = engine(); deferred->ready([=](QString error, QVariantMap result) { // FIXME: to remain backwards-compatible the signature here is "callback(data, n/a)" - jsCallback(handler, result.value("data").toString(), { { "errorMessage", error } }); + jsCallback(handler, scriptEngine->newValue(result.value("data").toString()), { { "errorMessage", error } }); }); connect(assetRequest, &AssetRequest::finished, assetRequest, [deferred](AssetRequest* request) { @@ -149,7 +156,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb assetRequest->start(); } -void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) { +void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, const ScriptValue& callback) { auto setBakingEnabledRequest = DependencyManager::get()->createSetBakingEnabledRequest({ path }, enabled); Promise deferred = jsPromiseReady(makePromise(__FUNCTION__), thisObject(), callback); @@ -179,14 +186,15 @@ void AssetScriptingInterface::sendFakedHandshake() { #endif -void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { +void AssetScriptingInterface::getMapping(QString asset, const ScriptValue& callback) { auto path = AssetUtils::getATPUrl(asset).path(); auto handler = jsBindCallback(thisObject(), callback); JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function"); Promise promise = getAssetInfo(path); + auto scriptEngine = engine(); promise->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result.value("hash").toString()); + jsCallback(handler, scriptEngine->newValue(error), scriptEngine->newValue(result.value("hash").toString())); }); } @@ -202,45 +210,46 @@ bool AssetScriptingInterface::jsVerify(bool condition, const QString& error) { return false; } -QScriptValue AssetScriptingInterface::jsBindCallback(QScriptValue scope, QScriptValue callback) { - QScriptValue handler = ::makeScopedHandlerObject(scope, callback); - QScriptValue value = handler.property("callback"); +ScriptValue AssetScriptingInterface::jsBindCallback(const ScriptValue& scope, const ScriptValue& callback) { + ScriptValue handler = ::makeScopedHandlerObject(scope, callback); + ScriptValue value = handler.property("callback"); if (!jsVerify(handler.isObject() && value.isFunction(), QString("jsBindCallback -- .callback is not a function (%1)").arg(value.toVariant().typeName()))) { - return QScriptValue(); + return ScriptValue(); } return handler; } -Promise AssetScriptingInterface::jsPromiseReady(Promise promise, QScriptValue scope, QScriptValue callback) { +Promise AssetScriptingInterface::jsPromiseReady(Promise promise, const ScriptValue& scope, const ScriptValue& callback) { auto handler = jsBindCallback(scope, callback); if (!jsVerify(handler.isValid(), "jsPromiseReady -- invalid callback handler")) { return nullptr; } - return promise->ready([this, handler](QString error, QVariantMap result) { - jsCallback(handler, error, result); + auto scriptEngine = engine(); + return promise->ready([this, handler, scriptEngine](QString error, QVariantMap result) { + jsCallback(handler, scriptEngine->newValue(error), result); }); } -void AssetScriptingInterface::jsCallback(const QScriptValue& handler, - const QScriptValue& error, const QScriptValue& result) { +void AssetScriptingInterface::jsCallback(const ScriptValue& handler, + const ScriptValue& error, const ScriptValue& result) { Q_ASSERT(thread() == QThread::currentThread()); - auto errorValue = !error.toBool() ? QScriptValue::NullValue : error; + auto errorValue = !error.toBool() ? engine()->nullValue() : error; JS_VERIFY(handler.isObject() && handler.property("callback").isFunction(), QString("jsCallback -- .callback is not a function (%1)") .arg(handler.property("callback").toVariant().typeName())); ::callScopedHandlerObject(handler, errorValue, result); } -void AssetScriptingInterface::jsCallback(const QScriptValue& handler, - const QScriptValue& error, const QVariantMap& result) { +void AssetScriptingInterface::jsCallback(const ScriptValue& handler, + const ScriptValue& error, const QVariantMap& result) { Q_ASSERT(thread() == QThread::currentThread()); Q_ASSERT(handler.engine()); auto engine = handler.engine(); jsCallback(handler, error, engine->toScriptValue(result)); } -void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::deleteAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { jsVerify(false, "TODO: deleteAsset API"); } @@ -270,7 +279,7 @@ void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue sco * @property {boolean} [wasRedirected] - true if the downloaded data is the baked version of the asset, * false if it isn't baked. */ -void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::getAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { JS_VERIFY(options.isObject() || options.isString(), "expected request options Object or URL as first parameter"); auto decompress = options.property("decompress").toBool() || options.property("compressed").toBool(); @@ -336,7 +345,7 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, * @property {boolean} [wasRedirected] - true if the resolved data is for the baked version of the asset, * false if it isn't. */ -void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::resolveAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { const QString& URL{ "url" }; auto url = (options.isString() ? options : options.property(URL)).toString(); @@ -363,9 +372,9 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc * @property {string|object|ArrayBuffer} [response] - The decompressed data. * @property {Assets.ResponseType} [responseType] - The type of the decompressed data in response. */ -void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::decompressData(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { auto data = options.property("data"); - QByteArray dataByteArray = qscriptvalue_cast(data); + QByteArray dataByteArray = scriptvalue_cast(data); auto responseType = options.property("responseType").toString().toLower(); if (responseType.isEmpty()) { responseType = "text"; @@ -404,9 +413,9 @@ namespace { * @property {string} [contentType] - The MIME type of the compressed data, i.e., "application/gzip". * @property {ArrayBuffer} [data] - The compressed data. */ -void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::compressData(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { auto data = options.property("data").isValid() ? options.property("data") : options; - QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); + QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : scriptvalue_cast(data); int level = options.property("level").isNumber() ? options.property("level").toInt32() : DEFAULT_GZIP_COMPRESSION_LEVEL; JS_VERIFY(level >= DEFAULT_GZIP_COMPRESSION_LEVEL || level <= MAX_GZIP_COMPRESSION_LEVEL, QString("invalid .level %1").arg(level)); jsPromiseReady(compressBytes(dataByteArray, level), scope, callback); @@ -433,13 +442,13 @@ void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue sc * @property {string} [url] - The atp: URL of the content: using the path if specified, otherwise the hash. * @property {string} [path] - The uploaded content's mapped path, if specified. */ -void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::putAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { auto compress = options.property("compress").toBool() || options.property("compressed").toBool(); auto data = options.isObject() ? options.property("data") : options; auto rawPath = options.property("path").toString(); auto path = AssetUtils::getATPUrl(rawPath).path(); - QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); + QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : scriptvalue_cast(data); JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path), QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path)); @@ -489,7 +498,7 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, * @property {string} url - The URL of the cached asset to get information on. Must start with "atp:" or * "cache:". */ -void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::queryCacheMeta(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { QString url = options.isString() ? options.toString() : options.property("url").toString(); JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url)); jsPromiseReady(Parent::queryCacheMeta(url), scope, callback); @@ -504,7 +513,7 @@ void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue * @property {string} url - The URL of the asset to load from cache. Must start with "atp:" or * "cache:". */ -void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::loadFromCache(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { QString url, responseType; bool decompress = false; if (options.isString()) { @@ -523,14 +532,14 @@ void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue s } bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) { - auto scriptEngine = qobject_cast(engine()); - if (!scriptEngine) { + auto scriptManager = engine()->manager(); + if (!scriptManager) { return false; } // allow cache writes only from Client, EntityServer and Agent scripts bool isAllowedContext = ( - scriptEngine->isClientScript() || - scriptEngine->isAgentScript() + scriptManager->isClientScript() || + scriptManager->isAgentScript() ); if (!isAllowedContext) { return false; @@ -546,17 +555,21 @@ bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) { * @property {string} [url] - The URL to associate with the cache item. Must start with "atp:" or * "cache:". If not specified, the URL is "atp:" followed by the SHA256 hash of the content. */ -void AssetScriptingInterface::saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::saveToCache(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback) { JS_VERIFY(options.isObject(), QString("expected options object as first parameter not: %1").arg(options.toVariant().typeName())); QString url = options.property("url").toString(); - QByteArray data = qscriptvalue_cast(options.property("data")); - QVariantMap headers = qscriptvalue_cast(options.property("headers")); + QByteArray data = scriptvalue_cast(options.property("data")); + QVariantMap headers = scriptvalue_cast(options.property("headers")); saveToCache(url, data, headers, scope, callback); } -void AssetScriptingInterface::saveToCache(const QUrl& rawURL, const QByteArray& data, const QVariantMap& metadata, QScriptValue scope, QScriptValue callback) { +void AssetScriptingInterface::saveToCache(const QUrl& rawURL, + const QByteArray& data, + const QVariantMap& metadata, + const ScriptValue& scope, + const ScriptValue& callback) { QUrl url = rawURL; if (url.path().isEmpty() && !data.isEmpty()) { // generate a valid ATP URL from the data -- appending any existing fragment or querystring values diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index b9fcb3c1c7b..afec316e9a2 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -19,14 +19,14 @@ #include #include -#include -#include #include #include #include -#include #include +#include "Scriptable.h" +#include "ScriptValue.h" + /*@jsdoc * The Assets API provides facilities for interacting with the domain's asset server and the client cache. *

Assets are stored in the asset server in files with SHA256 names. These files are mapped to user-friendly URLs of the @@ -45,7 +45,7 @@ * @hifi-assignment-client */ /// Provides the Assets scripting API -class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { +class AssetScriptingInterface : public BaseAssetScriptingInterface, Scriptable { Q_OBJECT public: using Parent = BaseAssetScriptingInterface; @@ -76,7 +76,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * }); * }); */ - Q_INVOKABLE void uploadData(QString data, QScriptValue callback); + Q_INVOKABLE void uploadData(QString data, const ScriptValue& callback); /*@jsdoc * Called when an {@link Assets.downloadData} call is complete. @@ -112,7 +112,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * }); * }, 1000); */ - Q_INVOKABLE void downloadData(QString url, QScriptValue callback); + Q_INVOKABLE void downloadData(QString url, const ScriptValue& callback); /*@jsdoc * Called when an {@link Assets.setMapping} call is complete. @@ -126,7 +126,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * @param {string} hash - The hash in the asset server. * @param {Assets~setMappingCallback} callback - The function to call upon completion. */ - Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback); + Q_INVOKABLE void setMapping(QString path, QString hash, const ScriptValue& callback); /*@jsdoc * Called when an {@link Assets.getMapping} call is complete. @@ -150,7 +150,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * }); * } */ - Q_INVOKABLE void getMapping(QString path, QScriptValue callback); + Q_INVOKABLE void getMapping(QString path, const ScriptValue& callback); /*@jsdoc * Called when an {@link Assets.setBakingEnabled} call is complete. @@ -166,7 +166,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * @param {Assets~setBakingEnabledCallback} callback - The function to call upon completion. */ // Note: Second callback parameter not documented because it's always {}. - Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); + Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, const ScriptValue& callback); #if (PR_BUILD || DEV_BUILD) /** @@ -222,7 +222,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void getAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.putAsset} call is complete. @@ -259,7 +259,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void putAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.deleteAsset} call is complete. @@ -276,7 +276,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * @param {object} scope - The scope that the callback function is defined in. * @param {Assets~deleteAssetCallback} callback - The function to call upon completion. */ - Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void deleteAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.resolveAsset} call is complete. @@ -310,7 +310,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void resolveAsset(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.decompressData} call is complete. @@ -331,7 +331,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * in a string. If the name of a function or a function identifier, it must be a member of the scope specified by * scopeOrCallback.

*/ - Q_INVOKABLE void decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void decompressData(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.compressData} call is complete. @@ -353,7 +353,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * in a string. If the name of a function or a function identifier, it must be a member of the scope specified by * scopeOrCallback.

*/ - Q_INVOKABLE void compressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void compressData(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Initializes the cache if it isn't already initialized. @@ -398,7 +398,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * print("- Status: " + JSON.stringify(status)); * }); */ - Q_INVOKABLE void getCacheStatus(QScriptValue scope, QScriptValue callback = QScriptValue()) { + Q_INVOKABLE void getCacheStatus(const ScriptValue& scope, const ScriptValue& callback = ScriptValue()) { jsPromiseReady(Parent::getCacheStatus(), scope, callback); } @@ -438,7 +438,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void queryCacheMeta(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.loadFromCache} call is complete. @@ -478,7 +478,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void loadFromCache(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Called when an {@link Assets.saveToCache} call is complete. @@ -517,7 +517,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * } * ); */ - Q_INVOKABLE void saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void saveToCache(const ScriptValue& options, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); /*@jsdoc * Saves asset data to the cache directly, without downloading it from a URL. @@ -537,13 +537,13 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable * scopeOrCallback.

*/ Q_INVOKABLE void saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata, - QScriptValue scope, QScriptValue callback = QScriptValue()); + const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); protected: - QScriptValue jsBindCallback(QScriptValue scope, QScriptValue callback = QScriptValue()); - Promise jsPromiseReady(Promise promise, QScriptValue scope, QScriptValue callback = QScriptValue()); + ScriptValue jsBindCallback(const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); + Promise jsPromiseReady(Promise promise, const ScriptValue& scope, const ScriptValue& callback = ScriptValue()); - void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QVariantMap& result); - void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result); + void jsCallback(const ScriptValue& handler, const ScriptValue& error, const QVariantMap& result); + void jsCallback(const ScriptValue& handler, const ScriptValue& error, const ScriptValue& result); bool jsVerify(bool condition, const QString& error); }; diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp index 60de04aa9ea..e8a4de24ad1 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.cpp +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -19,7 +19,10 @@ #include +#include "ScriptContext.h" #include "ScriptEngine.h" +#include "ScriptManager.h" +#include "ScriptValue.h" #define INDENTATION 4 // 1 Tab - 4 spaces const QString LINE_SEPARATOR = "\n "; @@ -27,71 +30,71 @@ const QString SPACE_SEPARATOR = " "; const QString STACK_TRACE_FORMAT = "\n[Stacktrace]%1%2"; QList ConsoleScriptingInterface::_groupDetails = QList(); -QScriptValue ConsoleScriptingInterface::info(QScriptContext* context, QScriptEngine* engine) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptInfoMessage(appendArguments(context)); +ScriptValue ConsoleScriptingInterface::info(ScriptContext* context, ScriptEngine* engine) { + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptInfoMessage(appendArguments(context)); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::log(QScriptContext* context, QScriptEngine* engine) { +ScriptValue ConsoleScriptingInterface::log(ScriptContext* context, ScriptEngine* engine) { QString message = appendArguments(context); if (_groupDetails.count() == 0) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptPrintedMessage(message); + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptPrintedMessage(message); } } else { logGroupMessage(message, engine); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::debug(QScriptContext* context, QScriptEngine* engine) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptPrintedMessage(appendArguments(context)); +ScriptValue ConsoleScriptingInterface::debug(ScriptContext* context, ScriptEngine* engine) { + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptPrintedMessage(appendArguments(context)); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::warn(QScriptContext* context, QScriptEngine* engine) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptWarningMessage(appendArguments(context)); +ScriptValue ConsoleScriptingInterface::warn(ScriptContext* context, ScriptEngine* engine) { + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptWarningMessage(appendArguments(context)); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::error(QScriptContext* context, QScriptEngine* engine) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptErrorMessage(appendArguments(context)); +ScriptValue ConsoleScriptingInterface::error(ScriptContext* context, ScriptEngine* engine) { + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptErrorMessage(appendArguments(context)); } - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::exception(QScriptContext* context, QScriptEngine* engine) { - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptErrorMessage(appendArguments(context)); +ScriptValue ConsoleScriptingInterface::exception(ScriptContext* context, ScriptEngine* engine) { + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptErrorMessage(appendArguments(context)); } - return QScriptValue::NullValue; + return engine->nullValue(); } void ConsoleScriptingInterface::time(QString labelName) { _timerDetails.insert(labelName, QDateTime::currentDateTime().toUTC()); QString message = QString("%1: Timer started").arg(labelName); - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->scriptPrintedMessage(message); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->scriptPrintedMessage(message); } } void ConsoleScriptingInterface::timeEnd(QString labelName) { - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + if (ScriptManager* scriptManager = engine()->manager()) { if (!_timerDetails.contains(labelName)) { - scriptEngine->scriptErrorMessage("No such label found " + labelName); + scriptManager->scriptErrorMessage("No such label found " + labelName); return; } if (_timerDetails.value(labelName).isNull()) { _timerDetails.remove(labelName); - scriptEngine->scriptErrorMessage("Invalid start time for " + labelName); + scriptManager->scriptErrorMessage("Invalid start time for " + labelName); return; } QDateTime _startTime = _timerDetails.value(labelName); @@ -101,11 +104,11 @@ void ConsoleScriptingInterface::timeEnd(QString labelName) { QString message = QString("%1: %2ms").arg(labelName).arg(QString::number(diffInMS)); _timerDetails.remove(labelName); - scriptEngine->scriptPrintedMessage(message); + scriptManager->scriptPrintedMessage(message); } } -QScriptValue ConsoleScriptingInterface::assertion(QScriptContext* context, QScriptEngine* engine) { +ScriptValue ConsoleScriptingInterface::assertion(ScriptContext* context, ScriptEngine* engine) { QString message; bool condition = false; for (int i = 0; i < context->argumentCount(); i++) { @@ -123,45 +126,46 @@ QScriptValue ConsoleScriptingInterface::assertion(QScriptContext* context, QScri } else { assertionResult = QString("Assertion failed : %1").arg(message); } - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptErrorMessage(assertionResult); + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptErrorMessage(assertionResult); } } - return QScriptValue::NullValue; + return engine->nullValue(); } void ConsoleScriptingInterface::trace() { - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->scriptPrintedMessage + ScriptEnginePointer scriptEngine = engine(); + if (ScriptManager* scriptManager = scriptEngine->manager()) { + scriptManager->scriptPrintedMessage (QString(STACK_TRACE_FORMAT).arg(LINE_SEPARATOR, scriptEngine->currentContext()->backtrace().join(LINE_SEPARATOR))); } } void ConsoleScriptingInterface::clear() { - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->clearDebugLogWindow(); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->clearDebugLogWindow(); } } -QScriptValue ConsoleScriptingInterface::group(QScriptContext* context, QScriptEngine* engine) { +ScriptValue ConsoleScriptingInterface::group(ScriptContext* context, ScriptEngine* engine) { logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label _groupDetails.push_back(context->argument(0).toString()); - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::groupCollapsed(QScriptContext* context, QScriptEngine* engine) { +ScriptValue ConsoleScriptingInterface::groupCollapsed(ScriptContext* context, ScriptEngine* engine) { logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label _groupDetails.push_back(context->argument(0).toString()); - return QScriptValue::NullValue; + return engine->nullValue(); } -QScriptValue ConsoleScriptingInterface::groupEnd(QScriptContext* context, QScriptEngine* engine) { +ScriptValue ConsoleScriptingInterface::groupEnd(ScriptContext* context, ScriptEngine* engine) { ConsoleScriptingInterface::_groupDetails.removeLast(); - return QScriptValue::NullValue; + return engine->nullValue(); } -QString ConsoleScriptingInterface::appendArguments(QScriptContext* context) { +QString ConsoleScriptingInterface::appendArguments(ScriptContext* context) { QString message; for (int i = 0; i < context->argumentCount(); i++) { if (i > 0) { @@ -172,14 +176,14 @@ QString ConsoleScriptingInterface::appendArguments(QScriptContext* context) { return message; } -void ConsoleScriptingInterface::logGroupMessage(QString message, QScriptEngine* engine) { +void ConsoleScriptingInterface::logGroupMessage(QString message, ScriptEngine* engine) { int _addSpaces = _groupDetails.count() * INDENTATION; QString logMessage; for (int i = 0; i < _addSpaces; i++) { logMessage.append(SPACE_SEPARATOR); } logMessage.append(message); - if (ScriptEngine* scriptEngine = qobject_cast(engine)) { - scriptEngine->scriptPrintedMessage(logMessage); + if (ScriptManager* scriptManager = engine->manager()) { + scriptManager->scriptPrintedMessage(logMessage); } } diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.h b/libraries/script-engine/src/ConsoleScriptingInterface.h index 591d44b22d1..09a44259c5f 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.h +++ b/libraries/script-engine/src/ConsoleScriptingInterface.h @@ -27,7 +27,12 @@ #include #include #include -#include + +#include "Scriptable.h" +#include "ScriptValue.h" + +class ScriptContext; +class ScriptEngine; /*@jsdoc * The console API provides program logging facilities. @@ -41,7 +46,7 @@ * @hifi-assignment-client */ /// Provides the console scripting API -class ConsoleScriptingInterface : public QObject, protected QScriptable { +class ConsoleScriptingInterface : public QObject, protected Scriptable { Q_OBJECT public: @@ -51,7 +56,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.info * @param {...*} [message] - The message values to log. */ - static QScriptValue info(QScriptContext* context, QScriptEngine* engine); + static ScriptValue info(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs a message to the program log and triggers {@link Script.printedMessage}. @@ -69,7 +74,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * // string 7 true * // INFO - Console.log message: "string 7 true" in [console.js] */ - static QScriptValue log(QScriptContext* context, QScriptEngine* engine); + static ScriptValue log(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs a message to the program log and triggers {@link Script.printedMessage}. @@ -77,7 +82,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.debug * @param {...*} [message] - The message values to log. */ - static QScriptValue debug(QScriptContext* context, QScriptEngine* engine); + static ScriptValue debug(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs a "WARNING" message to the program log and triggers {@link Script.warningMessage}. @@ -85,7 +90,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.warn * @param {...*} [message] - The message values to log. */ - static QScriptValue warn(QScriptContext* context, QScriptEngine* engine); + static ScriptValue warn(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs an "ERROR" message to the program log and triggers {@link Script.errorMessage}. @@ -93,7 +98,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.error * @param {...*} [message] - The message values to log. */ - static QScriptValue error(QScriptContext* context, QScriptEngine* engine); + static ScriptValue error(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * A synonym of {@link console.error}. @@ -102,7 +107,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.exception * @param {...*} [message] - The message values to log. */ - static QScriptValue exception(QScriptContext* context, QScriptEngine* engine); + static ScriptValue exception(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs an "ERROR" message to the program log and triggers {@link Script.errorMessage}, if a test condition fails. @@ -125,7 +130,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * // INFO - Script continues running. */ // Note: Is registered in the script engine as "assert" - static QScriptValue assertion(QScriptContext* context, QScriptEngine* engine); + static ScriptValue assertion(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Logs a message to the program log and triggers {@link Script.printedMessage}, then starts indenting subsequent @@ -153,7 +158,7 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * // Sentence 5 * //Sentence 6 */ - static QScriptValue group(QScriptContext* context, QScriptEngine* engine); + static ScriptValue group(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Has the same behavior as {@link console.group}. @@ -162,13 +167,13 @@ class ConsoleScriptingInterface : public QObject, protected QScriptable { * @function console.groupCollapsed * @param {*} message - The message value to log. */ - static QScriptValue groupCollapsed(QScriptContext* context, QScriptEngine* engine); + static ScriptValue groupCollapsed(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * Finishes a group of indented {@link console.log} messages. * @function console.groupEnd */ - static QScriptValue groupEnd(QScriptContext* context, QScriptEngine* engine); + static ScriptValue groupEnd(ScriptContext* context, ScriptEngine* engine); public slots: @@ -218,8 +223,8 @@ public slots: private: QHash _timerDetails; static QList _groupDetails; - static void logGroupMessage(QString message, QScriptEngine* engine); - static QString appendArguments(QScriptContext* context); + static void logGroupMessage(QString message, ScriptEngine* engine); + static QString appendArguments(ScriptContext* context); }; #endif // hifi_ConsoleScriptingInterface_h diff --git a/libraries/entities/src/EntitiesScriptEngineProvider.h b/libraries/script-engine/src/EntitiesScriptEngineProvider.h similarity index 96% rename from libraries/entities/src/EntitiesScriptEngineProvider.h rename to libraries/script-engine/src/EntitiesScriptEngineProvider.h index 100c17df5f3..fffdbe4c766 100644 --- a/libraries/entities/src/EntitiesScriptEngineProvider.h +++ b/libraries/script-engine/src/EntitiesScriptEngineProvider.h @@ -1,6 +1,6 @@ // // EntitiesScriptEngineProvider.h -// libraries/entities/src +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on Sept. 18, 2015 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/script-engine/src/EntityScriptClient.cpp similarity index 99% rename from libraries/networking/src/EntityScriptClient.cpp rename to libraries/script-engine/src/EntityScriptClient.cpp index fb98e8042bb..93406da9531 100644 --- a/libraries/networking/src/EntityScriptClient.cpp +++ b/libraries/script-engine/src/EntityScriptClient.cpp @@ -1,6 +1,6 @@ #include "EntityScriptClient.h" -#include "NodeList.h" -#include "NetworkLogging.h" +#include +#include #include "EntityScriptUtils.h" #include diff --git a/libraries/networking/src/EntityScriptClient.h b/libraries/script-engine/src/EntityScriptClient.h similarity index 94% rename from libraries/networking/src/EntityScriptClient.h rename to libraries/script-engine/src/EntityScriptClient.h index 1fddc6b9767..e8eedbb9d0a 100644 --- a/libraries/networking/src/EntityScriptClient.h +++ b/libraries/script-engine/src/EntityScriptClient.h @@ -1,6 +1,6 @@ // // EntityScriptClient.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 2017/01/13 // Copyright 2017 High Fidelity, Inc. @@ -14,10 +14,10 @@ #include -#include "ClientServerUtils.h" -#include "LimitedNodeList.h" -#include "ReceivedMessage.h" -#include "AssetUtils.h" +#include +#include +#include +#include #include "EntityScriptUtils.h" #include diff --git a/libraries/networking/src/EntityScriptUtils.h b/libraries/script-engine/src/EntityScriptUtils.h similarity index 96% rename from libraries/networking/src/EntityScriptUtils.h rename to libraries/script-engine/src/EntityScriptUtils.h index 15b056f0d24..4dad88f8924 100644 --- a/libraries/networking/src/EntityScriptUtils.h +++ b/libraries/script-engine/src/EntityScriptUtils.h @@ -1,6 +1,6 @@ // // EntityScriptUtils.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 2017/01/13 // Copyright 2017 High Fidelity, Inc. diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 94c074d44e1..2715d6252b5 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -13,16 +13,18 @@ #include "KeyEvent.h" #include "MouseEvent.h" -#include "SpatialEvent.h" #include "PointerEvent.h" +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "SpatialEvent.h" #include "TouchEvent.h" #include "WheelEvent.h" -void registerEventTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, KeyEvent::toScriptValue, KeyEvent::fromScriptValue); - qScriptRegisterMetaType(engine, MouseEvent::toScriptValue, MouseEvent::fromScriptValue); - qScriptRegisterMetaType(engine, PointerEvent::toScriptValue, PointerEvent::fromScriptValue); - qScriptRegisterMetaType(engine, TouchEvent::toScriptValue, TouchEvent::fromScriptValue); - qScriptRegisterMetaType(engine, WheelEvent::toScriptValue, WheelEvent::fromScriptValue); - qScriptRegisterMetaType(engine, SpatialEvent::toScriptValue, SpatialEvent::fromScriptValue); +void registerEventTypes(ScriptEngine* engine) { + scriptRegisterMetaType(engine, KeyEvent::toScriptValue, KeyEvent::fromScriptValue); + scriptRegisterMetaType(engine, MouseEvent::toScriptValue, MouseEvent::fromScriptValue); + scriptRegisterMetaType(engine, PointerEvent::toScriptValue, PointerEvent::fromScriptValue); + scriptRegisterMetaType(engine, TouchEvent::toScriptValue, TouchEvent::fromScriptValue); + scriptRegisterMetaType(engine, WheelEvent::toScriptValue, WheelEvent::fromScriptValue); + scriptRegisterMetaType(engine, SpatialEvent::toScriptValue, SpatialEvent::fromScriptValue); } diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index 052d736d489..118a765b17a 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -15,9 +15,9 @@ #ifndef hifi_EventTypes_h #define hifi_EventTypes_h -#include +class ScriptEngine; -void registerEventTypes(QScriptEngine* engine); +void registerEventTypes(ScriptEngine* engine); #endif // hifi_EventTypes_h diff --git a/libraries/script-engine/src/KeyEvent.cpp b/libraries/script-engine/src/KeyEvent.cpp index b565b1f3aa9..1c60ba7e1e6 100644 --- a/libraries/script-engine/src/KeyEvent.cpp +++ b/libraries/script-engine/src/KeyEvent.cpp @@ -12,9 +12,10 @@ #include "KeyEvent.h" #include -#include #include "ScriptEngineLogging.h" +#include "ScriptEngine.h" +#include "ScriptValue.h" KeyEvent::KeyEvent() : key(0), @@ -173,8 +174,8 @@ KeyEvent::operator QKeySequence() const { * print(JSON.stringify(event)); * }); */ -QScriptValue KeyEvent::toScriptValue(QScriptEngine* engine, const KeyEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue KeyEvent::toScriptValue(ScriptEngine* engine, const KeyEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty("key", event.key); obj.setProperty("text", event.text); obj.setProperty("isShifted", event.isShifted); @@ -186,7 +187,7 @@ QScriptValue KeyEvent::toScriptValue(QScriptEngine* engine, const KeyEvent& even return obj; } -void KeyEvent::fromScriptValue(const QScriptValue& object, KeyEvent& event) { +bool KeyEvent::fromScriptValue(const ScriptValue& object, KeyEvent& event) { event.isValid = false; // assume the worst event.isMeta = object.property("isMeta").toVariant().toBool(); @@ -195,13 +196,13 @@ void KeyEvent::fromScriptValue(const QScriptValue& object, KeyEvent& event) { event.isKeypad = object.property("isKeypad").toVariant().toBool(); event.isAutoRepeat = object.property("isAutoRepeat").toVariant().toBool(); - QScriptValue key = object.property("key"); + ScriptValue key = object.property("key"); if (key.isValid()) { event.key = key.toVariant().toInt(); event.text = QString(QChar(event.key)); event.isValid = true; } else { - QScriptValue text = object.property("text"); + ScriptValue text = object.property("text"); if (text.isValid()) { event.text = object.property("text").toVariant().toString(); @@ -280,9 +281,10 @@ void KeyEvent::fromScriptValue(const QScriptValue& object, KeyEvent& event) { } event.isValid = true; } + return true; } - QScriptValue isShifted = object.property("isShifted"); + ScriptValue isShifted = object.property("isShifted"); if (isShifted.isValid()) { event.isShifted = isShifted.toVariant().toBool(); } else { diff --git a/libraries/script-engine/src/KeyEvent.h b/libraries/script-engine/src/KeyEvent.h index 10c5fde404d..ceccf86336f 100644 --- a/libraries/script-engine/src/KeyEvent.h +++ b/libraries/script-engine/src/KeyEvent.h @@ -16,7 +16,10 @@ #define hifi_KeyEvent_h #include -#include + +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a keyboard event to the scripting engine. Exposed as KeyEvent class KeyEvent { @@ -26,8 +29,8 @@ class KeyEvent { bool operator==(const KeyEvent& other) const; operator QKeySequence() const; - static QScriptValue toScriptValue(QScriptEngine* engine, const KeyEvent& event); - static void fromScriptValue(const QScriptValue& object, KeyEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const KeyEvent& event); + static bool fromScriptValue(const ScriptValue& object, KeyEvent& event); int key; QString text; diff --git a/libraries/networking/src/LocationScriptingInterface.cpp b/libraries/script-engine/src/LocationScriptingInterface.cpp similarity index 70% rename from libraries/networking/src/LocationScriptingInterface.cpp rename to libraries/script-engine/src/LocationScriptingInterface.cpp index 39845558a8a..505af4bbefc 100644 --- a/libraries/networking/src/LocationScriptingInterface.cpp +++ b/libraries/script-engine/src/LocationScriptingInterface.cpp @@ -1,6 +1,6 @@ // // LocationScriptingInterface.cpp -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. @@ -11,23 +11,26 @@ #include "LocationScriptingInterface.h" -#include "AddressManager.h" +#include +#include "ScriptContext.h" +#include "ScriptEngine.h" +#include "ScriptValue.h" LocationScriptingInterface* LocationScriptingInterface::getInstance() { static LocationScriptingInterface sharedInstance; return &sharedInstance; } -QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) { +ScriptValue LocationScriptingInterface::locationGetter(ScriptContext* context, ScriptEngine* engine) { return engine->newQObject(DependencyManager::get().data()); } -QScriptValue LocationScriptingInterface::locationSetter(QScriptContext* context, QScriptEngine* engine) { +ScriptValue LocationScriptingInterface::locationSetter(ScriptContext* context, ScriptEngine* engine) { const QVariant& argumentVariant = context->argument(0).toVariant(); // just try and convert the argument to a string, should be a hifi:// address QMetaObject::invokeMethod(DependencyManager::get().data(), "handleLookupString", Q_ARG(const QString&, argumentVariant.toString())); - return QScriptValue::UndefinedValue; + return engine->undefinedValue(); } diff --git a/libraries/networking/src/LocationScriptingInterface.h b/libraries/script-engine/src/LocationScriptingInterface.h similarity index 68% rename from libraries/networking/src/LocationScriptingInterface.h rename to libraries/script-engine/src/LocationScriptingInterface.h index 987c4ccd0d3..0f7f4ded18c 100644 --- a/libraries/networking/src/LocationScriptingInterface.h +++ b/libraries/script-engine/src/LocationScriptingInterface.h @@ -1,6 +1,6 @@ // // LocationScriptingInterface.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. @@ -12,15 +12,18 @@ #ifndef hifi_LocationScriptingInterface_h #define hifi_LocationScriptingInterface_h -#include +#include "ScriptValue.h" + +class ScriptContext; +class ScriptEngine; class LocationScriptingInterface : public QObject { Q_OBJECT public: static LocationScriptingInterface* getInstance(); - static QScriptValue locationGetter(QScriptContext* context, QScriptEngine* engine); - static QScriptValue locationSetter(QScriptContext* context, QScriptEngine* engine); + static ScriptValue locationGetter(ScriptContext* context, ScriptEngine* engine); + static ScriptValue locationSetter(ScriptContext* context, ScriptEngine* engine); private: LocationScriptingInterface() {}; }; diff --git a/libraries/script-engine/src/MIDIEvent.cpp b/libraries/script-engine/src/MIDIEvent.cpp index b32c5d9d87e..2070e7bbc9c 100644 --- a/libraries/script-engine/src/MIDIEvent.cpp +++ b/libraries/script-engine/src/MIDIEvent.cpp @@ -11,8 +11,14 @@ #include "MIDIEvent.h" -void registerMIDIMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, midiEventToScriptValue, midiEventFromScriptValue); +#include + +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValue.h" + +void registerMIDIMetaTypes(ScriptEngine* engine) { + scriptRegisterMetaType(engine, midiEventToScriptValue, midiEventFromScriptValue); } const QString MIDI_DELTA_TIME_PROP_NAME = "deltaTime"; @@ -20,8 +26,8 @@ const QString MIDI_EVENT_TYPE_PROP_NAME = "type"; const QString MIDI_DATA_1_PROP_NAME = "data1"; const QString MIDI_DATA_2_PROP_NAME = "data2"; -QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue midiEventToScriptValue(ScriptEngine* engine, const MIDIEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty(MIDI_DELTA_TIME_PROP_NAME, event.deltaTime); obj.setProperty(MIDI_EVENT_TYPE_PROP_NAME, event.type); obj.setProperty(MIDI_DATA_1_PROP_NAME, event.data1); @@ -29,9 +35,10 @@ QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& even return obj; } -void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event) { +bool midiEventFromScriptValue(const ScriptValue &object, MIDIEvent& event) { event.deltaTime = object.property(MIDI_DELTA_TIME_PROP_NAME).toVariant().toDouble(); event.type = object.property(MIDI_EVENT_TYPE_PROP_NAME).toVariant().toUInt(); event.data1 = object.property(MIDI_DATA_1_PROP_NAME).toVariant().toUInt(); event.data2 = object.property(MIDI_DATA_2_PROP_NAME).toVariant().toUInt(); + return true; } \ No newline at end of file diff --git a/libraries/script-engine/src/MIDIEvent.h b/libraries/script-engine/src/MIDIEvent.h index fd8f83d9cba..46b1ffb8eea 100644 --- a/libraries/script-engine/src/MIDIEvent.h +++ b/libraries/script-engine/src/MIDIEvent.h @@ -15,7 +15,9 @@ #ifndef hifi_MIDIEvent_h #define hifi_MIDIEvent_h -#include +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a MIDI protocol event to the scripting engine. class MIDIEvent { @@ -28,10 +30,10 @@ class MIDIEvent { Q_DECLARE_METATYPE(MIDIEvent) -void registerMIDIMetaTypes(QScriptEngine* engine); +void registerMIDIMetaTypes(ScriptEngine* engine); -QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event); -void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event); +ScriptValue midiEventToScriptValue(ScriptEngine* engine, const MIDIEvent& event); +bool midiEventFromScriptValue(const ScriptValue &object, MIDIEvent& event); #endif // hifi_MIDIEvent_h diff --git a/libraries/script-engine/src/Mat4.cpp b/libraries/script-engine/src/Mat4.cpp index d4d73a46ccd..755744777da 100644 --- a/libraries/script-engine/src/Mat4.cpp +++ b/libraries/script-engine/src/Mat4.cpp @@ -20,6 +20,7 @@ #include "ScriptEngineLogging.h" #include "ScriptEngine.h" +#include "ScriptManager.h" glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const { return m1 * m2; @@ -87,7 +88,7 @@ void Mat4::print(const QString& label, const glm::mat4& m, bool transpose) const QString message = QString("%1 %2").arg(qPrintable(label)); message = message.arg(glm::to_string(out).c_str()); qCDebug(scriptengine) << message; - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->print(message); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->print(message); } } diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 204e87c57db..85b499724f5 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -19,10 +19,10 @@ #include #include -#include #include #include #include "RegisteredMetaTypes.h" +#include "Scriptable.h" /*@jsdoc * The Mat4 API provides facilities for generating and using 4 x 4 matrices. These matrices are typically used to @@ -39,7 +39,7 @@ * @hifi-assignment-client */ /// Provides the Mat4 scripting interface -class Mat4 : public QObject, protected QScriptable { +class Mat4 : public QObject, protected Scriptable { Q_OBJECT public slots: diff --git a/libraries/script-engine/src/MenuItemProperties.cpp b/libraries/script-engine/src/MenuItemProperties.cpp index bff1609db56..f25d05ddf99 100644 --- a/libraries/script-engine/src/MenuItemProperties.cpp +++ b/libraries/script-engine/src/MenuItemProperties.cpp @@ -14,6 +14,9 @@ #include #include +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValue.h" MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& menuItemName, const QString& shortcutKey, bool checkable, bool checked, bool separator) : @@ -40,12 +43,12 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m { } -void registerMenuItemProperties(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, menuItemPropertiesToScriptValue, menuItemPropertiesFromScriptValue); +void registerMenuItemProperties(ScriptEngine* engine) { + scriptRegisterMetaType(engine, menuItemPropertiesToScriptValue, menuItemPropertiesFromScriptValue); } -QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuItemProperties& properties) { - QScriptValue obj = engine->newObject(); +ScriptValue menuItemPropertiesToScriptValue(ScriptEngine* engine, const MenuItemProperties& properties) { + ScriptValue obj = engine->newObject(); // not supported return obj; } @@ -70,7 +73,7 @@ QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuIt * @property {string} [afterItem] - The name of the menu item to place this menu item after. * @property {string} [grouping] - The name of grouping to add this menu item to. */ -void menuItemPropertiesFromScriptValue(const QScriptValue& object, MenuItemProperties& properties) { +bool menuItemPropertiesFromScriptValue(const ScriptValue& object, MenuItemProperties& properties) { properties.menuName = object.property("menuName").toVariant().toString(); properties.menuItemName = object.property("menuItemName").toVariant().toString(); properties.isCheckable = object.property("isCheckable").toVariant().toBool(); @@ -78,12 +81,12 @@ void menuItemPropertiesFromScriptValue(const QScriptValue& object, MenuItemPrope properties.isSeparator = object.property("isSeparator").toVariant().toBool(); // handle the shortcut key options in order... - QScriptValue shortcutKeyValue = object.property("shortcutKey"); + ScriptValue shortcutKeyValue = object.property("shortcutKey"); if (shortcutKeyValue.isValid()) { properties.shortcutKey = shortcutKeyValue.toVariant().toString(); properties.shortcutKeySequence = properties.shortcutKey; } else { - QScriptValue shortcutKeyEventValue = object.property("shortcutKeyEvent"); + ScriptValue shortcutKeyEventValue = object.property("shortcutKeyEvent"); if (shortcutKeyEventValue.isValid()) { KeyEvent::fromScriptValue(shortcutKeyEventValue, properties.shortcutKeyEvent); properties.shortcutKeySequence = properties.shortcutKeyEvent; @@ -96,6 +99,7 @@ void menuItemPropertiesFromScriptValue(const QScriptValue& object, MenuItemPrope properties.beforeItem = object.property("beforeItem").toVariant().toString(); properties.afterItem = object.property("afterItem").toVariant().toString(); properties.grouping = object.property("grouping").toVariant().toString(); + return true; } diff --git a/libraries/script-engine/src/MenuItemProperties.h b/libraries/script-engine/src/MenuItemProperties.h index 30b78cd55bc..2cdfbad142a 100644 --- a/libraries/script-engine/src/MenuItemProperties.h +++ b/libraries/script-engine/src/MenuItemProperties.h @@ -15,10 +15,11 @@ #ifndef hifi_MenuItemProperties_h #define hifi_MenuItemProperties_h -#include - #include "KeyEvent.h" +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a menu item a script may declare and bind events to. Exposed as MenuItemProperties class MenuItemProperties { @@ -53,9 +54,9 @@ class MenuItemProperties { static const int UNSPECIFIED_POSITION = -1; }; Q_DECLARE_METATYPE(MenuItemProperties) -QScriptValue menuItemPropertiesToScriptValue(QScriptEngine* engine, const MenuItemProperties& props); -void menuItemPropertiesFromScriptValue(const QScriptValue& object, MenuItemProperties& props); -void registerMenuItemProperties(QScriptEngine* engine); +ScriptValue menuItemPropertiesToScriptValue(ScriptEngine* engine, const MenuItemProperties& props); +bool menuItemPropertiesFromScriptValue(const ScriptValue& object, MenuItemProperties& props); +void registerMenuItemProperties(ScriptEngine* engine); diff --git a/libraries/script-engine/src/MouseEvent.cpp b/libraries/script-engine/src/MouseEvent.cpp index 6adb39a29bd..a0726e0aedf 100644 --- a/libraries/script-engine/src/MouseEvent.cpp +++ b/libraries/script-engine/src/MouseEvent.cpp @@ -11,8 +11,8 @@ #include "MouseEvent.h" -#include -#include +#include "ScriptEngine.h" +#include "ScriptValue.h" MouseEvent::MouseEvent() : x(0.0f), @@ -86,8 +86,8 @@ MouseEvent::MouseEvent(const QMouseEvent& event) : * print(JSON.stringify(event)); * }); */ -QScriptValue MouseEvent::toScriptValue(QScriptEngine* engine, const MouseEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue MouseEvent::toScriptValue(ScriptEngine* engine, const MouseEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty("x", event.x); obj.setProperty("y", event.y); obj.setProperty("button", event.button); @@ -102,6 +102,7 @@ QScriptValue MouseEvent::toScriptValue(QScriptEngine* engine, const MouseEvent& return obj; } -void MouseEvent::fromScriptValue(const QScriptValue& object, MouseEvent& event) { +bool MouseEvent::fromScriptValue(const ScriptValue& object, MouseEvent& event) { // nothing for now... + return false; } diff --git a/libraries/script-engine/src/MouseEvent.h b/libraries/script-engine/src/MouseEvent.h index 7c668a69014..ffbc25854c8 100644 --- a/libraries/script-engine/src/MouseEvent.h +++ b/libraries/script-engine/src/MouseEvent.h @@ -16,9 +16,10 @@ #define hifi_MouseEvent_h #include -#include -class QScriptEngine; +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a mouse event to the scripting engine. Exposed as MouseEvent class MouseEvent { @@ -26,10 +27,10 @@ class MouseEvent { MouseEvent(); MouseEvent(const QMouseEvent& event); - static QScriptValue toScriptValue(QScriptEngine* engine, const MouseEvent& event); - static void fromScriptValue(const QScriptValue& object, MouseEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const MouseEvent& event); + static bool fromScriptValue(const ScriptValue& object, MouseEvent& event); - QScriptValue toScriptValue(QScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); } + ScriptValue toScriptValue(ScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); } int x; int y; diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/script-engine/src/PointerEvent.cpp similarity index 93% rename from libraries/shared/src/PointerEvent.cpp rename to libraries/script-engine/src/PointerEvent.cpp index be237442c01..8107640a876 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/script-engine/src/PointerEvent.cpp @@ -11,10 +11,10 @@ #include "PointerEvent.h" -#include -#include - #include "RegisteredMetaTypes.h" +#include "ScriptEngine.h" +#include "ScriptValue.h" +#include "ScriptValueUtils.h" static bool areFlagsSet(uint32_t flags, uint32_t mask) { return (flags & mask) != 0; @@ -125,8 +125,8 @@ void PointerEvent::setButton(Button button) { * * @typedef {number} KeyboardModifiers */ -QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue PointerEvent::toScriptValue(ScriptEngine* engine, const PointerEvent& event) { + ScriptValue obj = engine->newObject(); switch (event._type) { case Press: @@ -146,24 +146,24 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve obj.setProperty("id", event._id); - QScriptValue pos2D = engine->newObject(); + ScriptValue pos2D = engine->newObject(); pos2D.setProperty("x", event._pos2D.x); pos2D.setProperty("y", event._pos2D.y); obj.setProperty("pos2D", pos2D); - QScriptValue pos3D = engine->newObject(); + ScriptValue pos3D = engine->newObject(); pos3D.setProperty("x", event._pos3D.x); pos3D.setProperty("y", event._pos3D.y); pos3D.setProperty("z", event._pos3D.z); obj.setProperty("pos3D", pos3D); - QScriptValue normal = engine->newObject(); + ScriptValue normal = engine->newObject(); normal.setProperty("x", event._normal.x); normal.setProperty("y", event._normal.y); normal.setProperty("z", event._normal.z); obj.setProperty("normal", normal); - QScriptValue direction = engine->newObject(); + ScriptValue direction = engine->newObject(); direction.setProperty("x", event._direction.x); direction.setProperty("y", event._direction.y); direction.setProperty("z", event._direction.z); @@ -207,14 +207,14 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve obj.setProperty("isSecondaryHeld", areFlagsSet(event._buttons, SecondaryButton)); obj.setProperty("isTertiaryHeld", areFlagsSet(event._buttons, TertiaryButton)); - obj.setProperty("keyboardModifiers", QScriptValue(event.getKeyboardModifiers())); + obj.setProperty("keyboardModifiers", engine->newValue(event.getKeyboardModifiers())); return obj; } -void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& event) { +bool PointerEvent::fromScriptValue(const ScriptValue& object, PointerEvent& event) { if (object.isObject()) { - QScriptValue type = object.property("type"); + ScriptValue type = object.property("type"); QString typeStr = type.isString() ? type.toString() : "Move"; if (typeStr == "Press") { event._type = Press; @@ -226,7 +226,7 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve event._type = Move; } - QScriptValue id = object.property("id"); + ScriptValue id = object.property("id"); event._id = id.isNumber() ? (uint32_t)id.toNumber() : 0; vec2FromScriptValue(object.property("pos2D"), event._pos2D); @@ -234,7 +234,7 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve vec3FromScriptValue(object.property("normal"), event._normal); vec3FromScriptValue(object.property("direction"), event._direction); - QScriptValue button = object.property("button"); + ScriptValue button = object.property("button"); QString buttonStr = type.isString() ? button.toString() : "NoButtons"; if (buttonStr == "Primary") { @@ -263,6 +263,7 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve event._keyboardModifiers = (Qt::KeyboardModifiers)(object.property("keyboardModifiers").toUInt32()); } + return true; } static const char* typeToStringMap[PointerEvent::NumEventTypes] = { "Press", "DoublePress", "Release", "Move" }; diff --git a/libraries/shared/src/PointerEvent.h b/libraries/script-engine/src/PointerEvent.h similarity index 87% rename from libraries/shared/src/PointerEvent.h rename to libraries/script-engine/src/PointerEvent.h index 23f435a67c7..258cdd22e23 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/script-engine/src/PointerEvent.h @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/// @addtogroup ScriptEngine +/// @{ + #ifndef hifi_PointerEvent_h #define hifi_PointerEvent_h @@ -16,8 +19,12 @@ #include #include -#include +#include "ScriptValue.h" + +class ScriptEngine; + +/// Represents a 2D or 3D pointer to the scripting engine. Exposed as PointerEvent class PointerEvent { public: enum Button { @@ -44,10 +51,10 @@ class PointerEvent { const glm::vec3& normal, const glm::vec3& direction, Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier); - static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); - static void fromScriptValue(const QScriptValue& object, PointerEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const PointerEvent& event); + static bool fromScriptValue(const ScriptValue& object, PointerEvent& event); - QScriptValue toScriptValue(QScriptEngine* engine) const { return PointerEvent::toScriptValue(engine, *this); } + ScriptValue toScriptValue(ScriptEngine* engine) const { return PointerEvent::toScriptValue(engine, *this); } EventType getType() const { return _type; } uint32_t getID() const { return _id; } @@ -91,3 +98,5 @@ QDebug& operator<<(QDebug& dbg, const PointerEvent& p); Q_DECLARE_METATYPE(PointerEvent) #endif // hifi_PointerEvent_h + +/// @} diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 8335cb9adfc..724ad34416c 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -18,6 +18,7 @@ #include "ScriptEngineLogging.h" #include "ScriptEngine.h" +#include "ScriptManager.h" quat Quat::normalize(const glm::quat& q) { return glm::normalize(q); @@ -123,8 +124,8 @@ void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) { message = message.arg(glm::to_string(glm::dquat(q)).c_str()); } qCDebug(scriptengine) << message; - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->print(message); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->print(message); } } diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index cdda723ee09..d4d9967dac9 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -21,10 +21,11 @@ #include #include -#include #include +#include "Scriptable.h" + /*@jsdoc * A quaternion value. See also the {@link Quat(0)|Quat} API. * @typedef {object} Quat @@ -55,7 +56,7 @@ * print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY))); // { x: 0, y: 0, z: 0 } */ /// Provides the Quat scripting interface -class Quat : public QObject, protected QScriptable { +class Quat : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(glm::quat IDENTITY READ IDENTITY CONSTANT) diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index e5cd0ed126c..69ac0c4ed7d 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -16,7 +16,6 @@ #ifndef hifi_SceneScriptingInterface_h #define hifi_SceneScriptingInterface_h -#include #include /*@jsdoc diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 0b63803a333..bd2e5dfa781 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "ScriptEngines.h" diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h index 03d815e4ada..019cdac6f91 100644 --- a/libraries/script-engine/src/ScriptCache.h +++ b/libraries/script-engine/src/ScriptCache.h @@ -16,7 +16,7 @@ #define hifi_ScriptCache_h #include -#include +#include using contentAvailableCallback = std::function; diff --git a/libraries/script-engine/src/ScriptContext.cpp b/libraries/script-engine/src/ScriptContext.cpp new file mode 100644 index 00000000000..16b49fe7054 --- /dev/null +++ b/libraries/script-engine/src/ScriptContext.cpp @@ -0,0 +1,23 @@ +// +// ScriptContext.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 12/5/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptContext.h" + +#include "Scriptable.h" + +ScriptContextGuard::ScriptContextGuard(ScriptContext* context) { + _oldContext = Scriptable::context(); + Scriptable::setContext(context); +} + +ScriptContextGuard::~ScriptContextGuard() { + Scriptable::setContext(_oldContext); +} diff --git a/libraries/script-engine/src/ScriptContext.h b/libraries/script-engine/src/ScriptContext.h new file mode 100644 index 00000000000..33c227b9a3e --- /dev/null +++ b/libraries/script-engine/src/ScriptContext.h @@ -0,0 +1,81 @@ +// +// ScriptContext.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/1/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptContext_h +#define hifi_ScriptContext_h + +#include + +#include +#include + +#include "ScriptValue.h" + +class ScriptContext; +class ScriptEngine; +class ScriptFunctionContext; +using ScriptContextPointer = std::shared_ptr; +using ScriptFunctionContextPointer = std::shared_ptr; +using ScriptEnginePointer = std::shared_ptr; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptContextInfo +class ScriptFunctionContext { +public: + enum FunctionType { + ScriptFunction = 0, + QtFunction = 1, + QtPropertyFunction = 2, + NativeFunction = 3, + }; + +public: + virtual QString fileName() const = 0; + virtual QString functionName() const = 0; + virtual FunctionType functionType() const = 0; + virtual int lineNumber() const = 0; + +protected: + ~ScriptFunctionContext() {} // prevent explicit deletion of base class +}; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptContext +class ScriptContext { +public: + virtual int argumentCount() const = 0; + virtual ScriptValue argument(int index) const = 0; + virtual QStringList backtrace() const = 0; + virtual ScriptValue callee() const = 0; + virtual ScriptEnginePointer engine() const = 0; + virtual ScriptFunctionContextPointer functionContext() const = 0; + virtual ScriptContextPointer parentContext() const = 0; + virtual ScriptValue thisObject() const = 0; + virtual ScriptValue throwError(const QString& text) = 0; + virtual ScriptValue throwValue(const ScriptValue& value) = 0; + +protected: + ~ScriptContext() {} // prevent explicit deletion of base class +}; + +class ScriptContextGuard { +public: + ScriptContextGuard(ScriptContext* context); + ~ScriptContextGuard(); + +private: + ScriptContext* _oldContext; +}; + +#endif // hifi_ScriptContext_h + +/// @} diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c1578265e3a..4bcac85e45a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -11,2834 +12,56 @@ #include "ScriptEngine.h" -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "ArrayBufferViewClass.h" -#include "AssetScriptingInterface.h" -#include "BatchLoader.h" -#include "BaseScriptEngine.h" -#include "DataViewClass.h" -#include "EventTypes.h" -#include "FileScriptingInterface.h" // unzip project -#include "MenuItemProperties.h" -#include "ScriptAudioInjector.h" -#include "ScriptAvatarData.h" -#include "ScriptCache.h" #include "ScriptEngineLogging.h" -#include "TypedArrays.h" -#include "XMLHttpRequestClass.h" -#include "WebSocketClass.h" -#include "RecordingScriptingInterface.h" -#include "ScriptEngines.h" -#include "StackTestScriptingInterface.h" -#include "ModelScriptingInterface.h" - -#include - -#include "../../midi/src/Midi.h" // FIXME why won't a simpler include work? -#include "MIDIEvent.h" - -#include "SettingHandle.h" -#include -#include -#include - -const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { - "com.highfidelity.experimental.enableExtendedJSExceptions" -}; - -static const int MAX_MODULE_ID_LENGTH { 4096 }; -static const int MAX_DEBUG_VALUE_LENGTH { 80 }; - -static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects; -static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable }; -static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration }; - -static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true }; - -Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) -int functionSignatureMetaID = qRegisterMetaType(); - -int scriptEnginePointerMetaID = qRegisterMetaType(); - -Q_DECLARE_METATYPE(ExternalResource::Bucket); - -static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { - // assemble the message by concatenating our arguments - QString message = ""; - for (int i = 0; i < context->argumentCount(); i++) { - if (i > 0) { - message += " "; - } - message += context->argument(i).toString(); - } - - // was this generated by a script engine? If we don't recognize it then send the message and exit - ScriptEngine* scriptEngine = qobject_cast(engine); - if (!scriptEngine) { - qCDebug(scriptengine_script, "%s", qUtf8Printable(message)); - return QScriptValue(); - } - - // This message was sent by one of our script engines, let's try to see if we can find the source. - // Note that the first entry in the backtrace should be "print" and is somewhat useless to us - AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); - if (loggerInterface && loggerInterface->showSourceDebugging()) { - QScriptContext* userContext = context; - while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) { - userContext = userContext->parentContext(); - } - QString location; - if (userContext) { - QScriptContextInfo contextInfo(userContext); - QString fileName = contextInfo.fileName(); - int lineNumber = contextInfo.lineNumber(); - QString functionName = contextInfo.functionName(); - - location = functionName; - if (!fileName.isEmpty()) { - if (location.isEmpty()) { - location = fileName; - } else { - location = QString("%1 at %2").arg(location).arg(fileName); - } - } - if (lineNumber != -1) { - location = QString("%1:%2").arg(location).arg(lineNumber); - } - } - if (location.isEmpty()) { - location = scriptEngine->getFilename(); - } - - // give the script engine a chance to notify the system about this message - scriptEngine->print(message); - - // send the message to debug log - qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message)); - } else { - scriptEngine->print(message); - // prefix the script engine name to help disambiguate messages in the main debug log - qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(scriptEngine->getFilename()), qUtf8Printable(message)); - } - - return QScriptValue(); -} - -Q_DECLARE_METATYPE(controller::InputController*) -//static int inputControllerPointerId = qRegisterMetaType(); - -QScriptValue inputControllerToScriptValue(QScriptEngine *engine, controller::InputController* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS); -} - -void inputControllerFromScriptValue(const QScriptValue &object, controller::InputController* &out) { - out = qobject_cast(object.toQObject()); -} - -// FIXME Come up with a way to properly encode entity IDs in filename -// The purpose of the following two function is to embed entity ids into entity script filenames -// so that they show up in stacktraces -// -// Extract the url portion of a url that has been encoded with encodeEntityIdIntoEntityUrl(...) -QString extractUrlFromEntityUrl(const QString& url) { - auto parts = url.split(' ', Qt::SkipEmptyParts); - if (parts.length() > 0) { - return parts[0]; - } else { - return ""; - } -} - -// Encode an entity id into an entity url -// Example: http://www.example.com/some/path.js [EntityID:{9fdd355f-d226-4887-9484-44432d29520e}] -QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) { - return url + " [EntityID:" + entityID + "]"; -} - -QString ScriptEngine::logException(const QScriptValue& exception) { - auto message = formatException(exception, _enableExtendedJSExceptions.get()); - scriptErrorMessage(message); - return message; -} - -ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, - const QString& scriptContents, - const QString& fileNameString) { - ScriptEngine* engine = new ScriptEngine(context, scriptContents, fileNameString); - ScriptEnginePointer engineSP = ScriptEnginePointer(engine, &QObject::deleteLater); - auto scriptEngines = DependencyManager::get(); - scriptEngines->addScriptEngine(qSharedPointerCast(engineSP)); - engine->setScriptEngines(scriptEngines); - return engineSP; -} - -int ScriptEngine::processLevelMaxRetries { ScriptRequest::MAX_RETRIES }; -ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) : - BaseScriptEngine(), - _context(context), - _scriptContents(scriptContents), - _timerFunctionMap(), - _fileNameString(fileNameString), - _arrayBufferClass(new ArrayBufferClass(this)), - _assetScriptingInterface(new AssetScriptingInterface(this)) -{ - switch (_context) { - case Context::CLIENT_SCRIPT: - _type = Type::CLIENT; - break; - case Context::ENTITY_CLIENT_SCRIPT: - _type = Type::ENTITY_CLIENT; - break; - case Context::ENTITY_SERVER_SCRIPT: - _type = Type::ENTITY_SERVER; - break; - case Context::AGENT_SCRIPT: - _type = Type::AGENT; - break; - } - - connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { - if (hasUncaughtException()) { - // the engine's uncaughtException() seems to produce much better stack traces here - emit unhandledException(cloneUncaughtException("signalHandlerException")); - clearExceptions(); - } else { - // ... but may not always be available -- so if needed we fallback to the passed exception - emit unhandledException(exception); - } - }, Qt::DirectConnection); - - setProcessEventsInterval(MSECS_PER_SECOND); - if (isEntityServerScript()) { - qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1"; - processLevelMaxRetries = 1; - } - - // this is where all unhandled exceptions end up getting logged - connect(this, &BaseScriptEngine::unhandledException, this, [this](const QScriptValue& err) { - auto output = err.engine() == this ? err : makeError(err); - if (!output.property("detail").isValid()) { - output.setProperty("detail", "UnhandledException"); - } - logException(output); - }); - - if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { - QObject::connect(this, &ScriptEngine::update, this, [this]() { - // process pending entity script content - if (!_contentAvailableQueue.empty() && !(_isFinished || _isStopping)) { - EntityScriptContentAvailableMap pending; - std::swap(_contentAvailableQueue, pending); - for (auto& pair : pending) { - auto& args = pair.second; - entityScriptContentAvailable(args.entityID, args.scriptOrURL, args.contents, args.isURL, args.success, args.status); - } - } - }); - } -} - -QString ScriptEngine::getTypeAsString() const { - auto value = QVariant::fromValue(_type).toString(); - return value.isEmpty() ? "unknown" : value.toLower(); -} - -QString ScriptEngine::getContext() const { - switch (_context) { - case CLIENT_SCRIPT: - return "client"; - case ENTITY_CLIENT_SCRIPT: - return "entity_client"; - case ENTITY_SERVER_SCRIPT: - return "entity_server"; - case AGENT_SCRIPT: - return "agent"; - default: - return "unknown"; - } - return "unknown"; -} - -bool ScriptEngine::isDebugMode() const { -#if defined(DEBUG) - return true; -#else - return false; -#endif -} - -ScriptEngine::~ScriptEngine() {} - -void ScriptEngine::disconnectNonEssentialSignals() { - disconnect(); - QThread* workerThread; - // Ensure the thread should be running, and does exist - if (_isRunning && _isThreaded && (workerThread = thread())) { - connect(this, &QObject::destroyed, workerThread, &QThread::quit); - connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); - } -} - -void ScriptEngine::runInThread() { - Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once"); - - if (_isThreaded) { - return; - } - - _isThreaded = true; - - // The thread interface cannot live on itself, and we want to move this into the thread, so - // the thread cannot have this as a parent. - QThread* workerThread = new QThread(); - QString name = QString("js:") + getFilename().replace("about:",""); - workerThread->setObjectName(name); - moveToThread(workerThread); - - // NOTE: If you connect any essential signals for proper shutdown or cleanup of - // the script engine, make sure to add code to "reconnect" them to the - // disconnectNonEssentialSignals() method - connect(workerThread, &QThread::started, this, [this, name] { - setThreadName(name.toStdString()); - run(); - }); - connect(this, &QObject::destroyed, workerThread, &QThread::quit); - connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); - - workerThread->start(); -} - -void ScriptEngine::executeOnScriptThread(std::function function, const Qt::ConnectionType& type ) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function, function)); - return; - } - - function(); -} - -void ScriptEngine::waitTillDoneRunning(bool shutdown) { - // Engine should be stopped already, but be defensive - stop(); - - auto workerThread = thread(); - - if (workerThread == QThread::currentThread()) { - qCWarning(scriptengine) << "ScriptEngine::waitTillDoneRunning called, but the script is on the same thread:" << getFilename(); - return; - } - - if (_isThreaded && workerThread) { - // We should never be waiting (blocking) on our own thread - assert(workerThread != QThread::currentThread()); - -#if 0 - // 26 Feb 2021 - Disabled this OSX-specific code because it causes OSX to crash on shutdown; without this code, OSX - // doesn't crash on shutdown. Qt 5.12.3 and Qt 5.15.2. - // - // On mac, don't call QCoreApplication::processEvents() here. This is to prevent - // [NSApplication terminate:] from prematurely destroying the static destructors - // while we are waiting for the scripts to shutdown. We will pump the message - // queue later in the Application destructor. - if (workerThread->isRunning()) { - workerThread->quit(); - - if (isEvaluating()) { - qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); - abortEvaluation(); - } else { - auto context = currentContext(); - if (context) { - qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename(); - context->throwError("Timed out during shutdown"); - } - } - - // Wait for the scripting thread to stop running, as - // flooding it with aborts/exceptions will persist it longer - static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; - if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { - workerThread->terminate(); - } - } -#else - auto startedWaiting = usecTimestampNow(); - while (workerThread->isRunning()) { - // If the final evaluation takes too long, then tell the script engine to stop running - auto elapsedUsecs = usecTimestampNow() - startedWaiting; - static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; - if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { - workerThread->quit(); - - if (isEvaluating()) { - qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); - abortEvaluation(); - } else { - auto context = currentContext(); - if (context) { - qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename(); - context->throwError("Timed out during shutdown"); - } - } - - // Wait for the scripting thread to stop running, as - // flooding it with aborts/exceptions will persist it longer - static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; - if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { - workerThread->terminate(); - } - } - - if (shutdown) { - // NOTE: This will be called on the main application thread (among other threads) from stopAllScripts. - // The thread will need to continue to process events, because - // the scripts will likely need to marshall messages across to the main thread, e.g. - // if they access Settings or Menu in any of their shutdown code. So: - // Process events for this thread, allowing invokeMethod calls to pass between threads. - QCoreApplication::processEvents(); - } - - // Avoid a pure busy wait - QThread::yieldCurrentThread(); - } -#endif - - scriptInfoMessage("Script Engine has stopped:" + getFilename()); - } -} - -QString ScriptEngine::getFilename() const { - QStringList fileNameParts = _fileNameString.split("/"); - QString lastPart; - if (!fileNameParts.isEmpty()) { - lastPart = fileNameParts.last(); - } - return lastPart; -} - -bool ScriptEngine::hasValidScriptSuffix(const QString& scriptFileName) { - QFileInfo fileInfo(scriptFileName); - QString scriptSuffixToLower = fileInfo.completeSuffix().toLower(); - return scriptSuffixToLower.contains(QString("js"), Qt::CaseInsensitive); -} - -void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { - if (_isRunning) { - return; - } - - QUrl url = expandScriptUrl(scriptURL); - _fileNameString = url.toString(); - _isReloading = reload; - - // Check that script has a supported file extension - if (!hasValidScriptSuffix(_fileNameString)) { - scriptErrorMessage("File extension of file: " + _fileNameString + " is not a currently supported script type"); - emit errorLoadingScript(_fileNameString); - return; - } - - const auto maxRetries = 0; // for consistency with previous scriptCache->getScript() behavior - auto scriptCache = DependencyManager::get(); - scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) { - qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread(); - if (!success) { - scriptErrorMessage("ERROR Loading file (" + status + "):" + url); - emit errorLoadingScript(_fileNameString); - return; - } - - _scriptContents = scriptContents; - - emit scriptLoaded(url); - }, reload, maxRetries); -} - -void ScriptEngine::scriptErrorMessage(const QString& message) { - qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); - emit errorMessage(message, getFilename()); -} - -void ScriptEngine::scriptWarningMessage(const QString& message) { - qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); - emit warningMessage(message, getFilename()); -} - -void ScriptEngine::scriptInfoMessage(const QString& message) { - qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); - emit infoMessage(message, getFilename()); -} - -void ScriptEngine::scriptPrintedMessage(const QString& message) { - qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); - emit printedMessage(message, getFilename()); -} - -void ScriptEngine::clearDebugLogWindow() { - emit clearDebugWindow(); -} - -// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of -// callAnimationStateHandler requires that the type be registered. -// These two are meaningful, if we ever do want to use them... -static QScriptValue animVarMapToScriptValue(QScriptEngine* engine, const AnimVariantMap& parameters) { - QStringList unused; - return parameters.animVariantMapToScriptValue(engine, unused, false); -} -static void animVarMapFromScriptValue(const QScriptValue& value, AnimVariantMap& parameters) { - parameters.animVariantMapFromScriptValue(value); -} -// ... while these two are not. But none of the four are ever used. -static QScriptValue resultHandlerToScriptValue(QScriptEngine* engine, - const AnimVariantResultHandler& resultHandler) { - qCCritical(scriptengine) << "Attempt to marshall result handler to javascript"; - assert(false); - return QScriptValue(); -} -static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantResultHandler& resultHandler) { - qCCritical(scriptengine) << "Attempt to marshall result handler from javascript"; - assert(false); -} - -// Templated qScriptRegisterMetaType fails to compile with raw pointers -using ScriptableResourceRawPtr = ScriptableResource*; - -static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, - const ScriptableResourceRawPtr& resource) { - if (!resource) { - return QScriptValue(); // probably shutting down - } - - // The first script to encounter this resource will track its memory. - // In this way, it will be more likely to GC. - // This fails in the case that the resource is used across many scripts, but - // in that case it would be too difficult to tell which one should track the memory, and - // this serves the common case (use in a single script). - auto data = resource->getResource(); - if (data && !resource->isInScript()) { - resource->setInScript(true); - QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); - } - - auto object = engine->newQObject( - const_cast(resource), - QScriptEngine::ScriptOwnership, - DEFAULT_QOBJECT_WRAP_OPTIONS); - return object; -} - -static void scriptableResourceFromScriptValue(const QScriptValue& value, ScriptableResourceRawPtr& resource) { - resource = static_cast(value.toQObject()); -} - -/*@jsdoc - * The Resource API provides values that define the possible loading states of a resource. - * - * @namespace Resource - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - * @hifi-server-entity - * @hifi-assignment-client - * - * @property {Resource.State} State - The possible loading states of a resource. Read-only. - */ -static QScriptValue createScriptableResourcePrototype(ScriptEnginePointer engine) { - auto prototype = engine->newObject(); - - // Expose enum State to JS/QML via properties - QObject* state = new QObject(engine.data()); - state->setObjectName("ResourceState"); - auto metaEnum = QMetaEnum::fromType(); - for (int i = 0; i < metaEnum.keyCount(); ++i) { - state->setProperty(metaEnum.key(i), metaEnum.value(i)); - } - - auto prototypeState = engine->newQObject(state, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeSlots | QScriptEngine::ExcludeSuperClassMethods); - prototype.setProperty("State", prototypeState); - - return prototype; -} - -QScriptValue avatarDataToScriptValue(QScriptEngine* engine, ScriptAvatarData* const& in) { - return engine->newQObject(in, QScriptEngine::ScriptOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS); -} - -void avatarDataFromScriptValue(const QScriptValue& object, ScriptAvatarData*& out) { - // This is not implemented because there are no slots/properties that take an AvatarSharedPointer from a script - assert(false); - out = nullptr; -} - -QScriptValue externalResourceBucketToScriptValue(QScriptEngine* engine, ExternalResource::Bucket const& in) { - return QScriptValue((int)in); -} - -void externalResourceBucketFromScriptValue(const QScriptValue& object, ExternalResource::Bucket& out) { - out = static_cast(object.toInt32()); -} - -void ScriptEngine::resetModuleCache(bool deleteScriptCache) { - if (QThread::currentThread() != thread()) { - executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); }); - return; - } - auto jsRequire = globalObject().property("Script").property("require"); - auto cache = jsRequire.property("cache"); - auto cacheMeta = jsRequire.data(); - - if (deleteScriptCache) { - QScriptValueIterator it(cache); - while (it.hasNext()) { - it.next(); - if (it.flags() & QScriptValue::SkipInEnumeration) { - continue; - } - qCDebug(scriptengine) << "resetModuleCache(true) -- staging " << it.name() << " for cache reset at next require"; - cacheMeta.setProperty(it.name(), true); - } - } - cache = newObject(); - if (!cacheMeta.isObject()) { - cacheMeta = newObject(); - cacheMeta.setProperty("id", "Script.require.cacheMeta"); - cacheMeta.setProperty("type", "cacheMeta"); - jsRequire.setData(cacheMeta); - } - cache.setProperty("__created__", (double)QDateTime::currentMSecsSinceEpoch(), QScriptValue::SkipInEnumeration); -#if DEBUG_JS_MODULES - cache.setProperty("__meta__", cacheMeta, READONLY_HIDDEN_PROP_FLAGS); -#endif - jsRequire.setProperty("cache", cache, READONLY_PROP_FLAGS); -} - -void ScriptEngine::init() { - if (_isInitialized) { - return; // only initialize once - } - - _isInitialized = true; - - auto entityScriptingInterface = DependencyManager::get(); - entityScriptingInterface->init(); - - // register various meta-types - registerMetaTypes(this); - registerMIDIMetaTypes(this); - registerEventTypes(this); - registerMenuItemProperties(this); - registerAnimationTypes(this); - registerAvatarTypes(this); - registerAudioMetaTypes(this); - - qScriptRegisterMetaType(this, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue); - qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); - qScriptRegisterMetaType(this, EntityPropertyInfoToScriptValue, EntityPropertyInfoFromScriptValue); - qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); - qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); - qScriptRegisterMetaType(this, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue); - qScriptRegisterMetaType(this, AvatarEntityMapToScriptValue, AvatarEntityMapFromScriptValue); - qScriptRegisterSequenceMetaType>(this); - qScriptRegisterSequenceMetaType>(this); - - qScriptRegisterSequenceMetaType>(this); - qScriptRegisterSequenceMetaType>(this); - qScriptRegisterSequenceMetaType>(this); - - QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor); - globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); - - QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); - globalObject().setProperty("WebSocket", webSocketConstructorValue); - - /*@jsdoc - * Prints a message to the program log and emits {@link Script.printedMessage}. - * The message logged is the message values separated by spaces. - *

Alternatively, you can use {@link Script.print} or one of the {@link console} API methods.

- * @function print - * @param {...*} [message] - The message values to print. - */ - globalObject().setProperty("print", newFunction(debugPrint)); - - QScriptValue audioEffectOptionsConstructorValue = newFunction(AudioEffectOptions::constructor); - globalObject().setProperty("AudioEffectOptions", audioEffectOptionsConstructorValue); - - qScriptRegisterMetaType(this, injectorToScriptValue, injectorFromScriptValue); - qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue); - qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue); - qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue); - qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue); - qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue); - qScriptRegisterMetaType(this, wscReadyStateToScriptValue, wscReadyStateFromScriptValue); - - // NOTE: You do not want to end up creating new instances of singletons here. They will be on the ScriptEngine thread - // and are likely to be unusable if we "reset" the ScriptEngine by creating a new one (on a whole new thread). - - registerGlobalObject("Script", this); - - { - // set up Script.require.resolve and Script.require.cache - auto Script = globalObject().property("Script"); - auto require = Script.property("require"); - auto resolve = Script.property("_requireResolve"); - require.setProperty("resolve", resolve, READONLY_PROP_FLAGS); - resetModuleCache(); - } - - qScriptRegisterMetaType(this, externalResourceBucketToScriptValue, externalResourceBucketFromScriptValue); - registerEnum("Script.ExternalPaths", QMetaEnum::fromType()); - - registerGlobalObject("Audio", DependencyManager::get().data()); - - registerGlobalObject("Midi", DependencyManager::get().data()); - - registerGlobalObject("Entities", entityScriptingInterface.data()); - registerFunction("Entities", "getMultipleEntityProperties", EntityScriptingInterface::getMultipleEntityProperties); - registerGlobalObject("Quat", &_quatLibrary); - registerGlobalObject("Vec3", &_vec3Library); - registerGlobalObject("Mat4", &_mat4Library); - registerGlobalObject("Uuid", &_uuidLibrary); - registerGlobalObject("Messages", DependencyManager::get().data()); - registerGlobalObject("File", new FileScriptingInterface(this)); - registerGlobalObject("console", &_consoleScriptingInterface); - registerFunction("console", "info", ConsoleScriptingInterface::info, currentContext()->argumentCount()); - registerFunction("console", "log", ConsoleScriptingInterface::log, currentContext()->argumentCount()); - registerFunction("console", "debug", ConsoleScriptingInterface::debug, currentContext()->argumentCount()); - registerFunction("console", "warn", ConsoleScriptingInterface::warn, currentContext()->argumentCount()); - registerFunction("console", "error", ConsoleScriptingInterface::error, currentContext()->argumentCount()); - registerFunction("console", "exception", ConsoleScriptingInterface::exception, currentContext()->argumentCount()); - registerFunction("console", "assert", ConsoleScriptingInterface::assertion, currentContext()->argumentCount()); - registerFunction("console", "group", ConsoleScriptingInterface::group, 1); - registerFunction("console", "groupCollapsed", ConsoleScriptingInterface::groupCollapsed, 1); - registerFunction("console", "groupEnd", ConsoleScriptingInterface::groupEnd, 0); - - qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); - qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); - - // Scriptable cache access - auto resourcePrototype = createScriptableResourcePrototype(qSharedPointerCast(sharedFromThis())); - globalObject().setProperty("Resource", resourcePrototype); - setDefaultPrototype(qMetaTypeId(), resourcePrototype); - qScriptRegisterMetaType(this, scriptableResourceToScriptValue, scriptableResourceFromScriptValue); - - // constants - globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); - - registerGlobalObject("Assets", _assetScriptingInterface); - registerGlobalObject("Resources", DependencyManager::get().data()); - - registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); - - registerGlobalObject("Model", new ModelScriptingInterface(this)); - qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue); - qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); - - registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); - -#if DEV_BUILD || PR_BUILD - registerGlobalObject("StackTest", new StackTestScriptingInterface(this)); -#endif - - globalObject().setProperty("KALILA", "isWaifu"); - globalObject().setProperty("Kute", newFunction([](QScriptContext* context, QScriptEngine* engine) -> QScriptValue { - return context->argument(0).toString().toLower() == "kalila" ? true : false; - })); -} +#include "ScriptValue.h" +#include "qtscript/ScriptEngineQtScript.h" -void ScriptEngine::registerEnum(const QString& enumName, QMetaEnum newEnum) { - if (!newEnum.isValid()) { - qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; - return; - } - - for (int i = 0; i < newEnum.keyCount(); i++) { - const char* keyName = newEnum.key(i); - QString fullName = enumName + "." + keyName; - registerValue(fullName, newEnum.keyToValue(keyName)); - } +ScriptEnginePointer newScriptEngine(ScriptManager* manager) { + return std::make_shared(manager); } -void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; -#endif - QMetaObject::invokeMethod(this, "registerValue", - Q_ARG(const QString&, valueName), - Q_ARG(QScriptValue, value)); - return; +ScriptValue makeScopedHandlerObject(const ScriptValue& scopeOrCallback, const ScriptValue& methodOrName) { + auto engine = scopeOrCallback.engine(); + if (!engine) { + return scopeOrCallback; } - - QStringList pathToValue = valueName.split("."); - int partsToGo = pathToValue.length(); - QScriptValue partObject = globalObject(); - - for (const auto& pathPart : pathToValue) { - partsToGo--; - if (!partObject.property(pathPart).isValid()) { - if (partsToGo > 0) { - //QObject *object = new QObject; - QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); - partObject.setProperty(pathPart, partValue); - } else { - partObject.setProperty(pathPart, value); + ScriptValue scope; + ScriptValue callback = scopeOrCallback; + if (scopeOrCallback.isObject()) { + if (methodOrName.isString()) { + scope = scopeOrCallback; + callback = scope.property(methodOrName.toString()); + } else if (methodOrName.isFunction()) { + scope = scopeOrCallback; + callback = methodOrName; + } else if (!methodOrName.isValid()) { + // instantiate from an existing scoped handler object + if (scopeOrCallback.property("callback").isFunction()) { + scope = scopeOrCallback.property("scope"); + callback = scopeOrCallback.property("callback"); } } - partObject = partObject.property(pathPart); - } -} - -void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; -#endif - - if (!globalObject().property(name).isValid()) { - if (object) { - QScriptValue value = newQObject(object, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS); - globalObject().setProperty(name, value); - } else { - globalObject().setProperty(name, QScriptValue()); - } - } -} - -void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; -#endif - - QScriptValue scriptFun = newFunction(functionSignature, numArguments); - globalObject().setProperty(name, scriptFun); -} - -void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; -#endif - - 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, const QString& parent) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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), - Q_ARG(QScriptEngine::FunctionSignature, setter), - Q_ARG(const QString&, parent)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; -#endif - - QScriptValue setterFunction = newFunction(setter, 1); - QScriptValue getterFunction = newFunction(getter); - - if (!parent.isNull() && !parent.isEmpty()) { - 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); - } -} - -// 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 - qCDebug(scriptengine) << "*** 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; -#endif - - if (!_registeredHandlers.contains(entityID)) { - return; - } - RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; - CallbackList& handlersForEvent = handlersOnEntity[eventName]; - // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. - for (int i = 0; i < handlersForEvent.count(); ++i) { - if (handlersForEvent[i].function.equals(handler)) { - handlersForEvent.removeAt(i); - return; // Design choice: since comparison is relatively expensive, just remove the first matching handler. - } - } -} -// Register the handler. -void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "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. - // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) - auto entities = DependencyManager::get(); - // Bug? These handlers are deleted when entityID is deleted, which is nice. - // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads? - // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow, - // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded? - // Also, what about when the entity script is REloaded? - // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the - // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS - connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { - _registeredHandlers.remove(entityID); - }); - - // Two common cases of event handler, differing only in argument signature. - - /*@jsdoc - * Called when an entity event occurs on an entity as registered with {@link Script.addEventHandler}. - * @callback Script~entityEventCallback - * @param {Uuid} entityID - The ID of the entity the event has occured on. - */ - using SingleEntityHandler = std::function; - auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { - return [this, eventName](const EntityItemID& entityItemID) { - forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); - }; - }; - - /*@jsdoc - * Called when a pointer event occurs on an entity as registered with {@link Script.addEventHandler}. - * @callback Script~pointerEventCallback - * @param {Uuid} entityID - The ID of the entity the event has occurred on. - * @param {PointerEvent} pointerEvent - Details of the event. - */ - using PointerHandler = std::function; - auto makePointerHandler = [this](QString eventName) -> PointerHandler { - return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { - if (!EntityTree::areEntityClicksCaptured()) { - forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); - } - }; - }; - - /*@jsdoc - * Called when a collision event occurs on an entity as registered with {@link Script.addEventHandler}. - * @callback Script~collisionEventCallback - * @param {Uuid} entityA - The ID of one entity in the collision. - * @param {Uuid} entityB - The ID of the other entity in the collision. - * @param {Collision} collisionEvent - Details of the collision. - */ - using CollisionHandler = std::function; - auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler { - return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { - forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this), - collisionToScriptValue(this, collision) }); - }; - }; - - /*@jsdoc - *

The name of an entity event. When the entity event occurs, any function that has been registered for that event - * via {@link Script.addEventHandler} is called with parameters per the entity event.

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Event NameCallback TypeEntity Event
"enterEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.enterEntity}
"leaveEntity"{@link Script~entityEventCallback|entityEventCallback}{@link Entities.leaveEntity}
"mousePressOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mousePressOnEntity}
"mouseMoveOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseMoveOnEntity}
"mouseReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.mouseReleaseOnEntity}
"clickDownOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickDownOnEntity}
"holdingClickOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.holdingClickOnEntity}
"clickReleaseOnEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.clickReleaseOnEntity}
"hoverEnterEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverEnterEntity}
"hoverOverEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverOverEntity}
"hoverLeaveEntity"{@link Script~pointerEventCallback|pointerEventCallback}{@link Entities.hoverLeaveEntity}
"collisionWithEntity"{@link Script~collisionEventCallback|collisionEventCallback}{@link Entities.collisionWithEntity}
- * @typedef {string} Script.EntityEvent - */ - connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); - connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); - - connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makePointerHandler("mousePressOnEntity")); - connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makePointerHandler("mouseMoveOnEntity")); - connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makePointerHandler("mouseReleaseOnEntity")); - - connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makePointerHandler("clickDownOnEntity")); - connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makePointerHandler("holdingClickOnEntity")); - connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makePointerHandler("clickReleaseOnEntity")); - - connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makePointerHandler("hoverEnterEntity")); - connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makePointerHandler("hoverOverEntity")); - connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makePointerHandler("hoverLeaveEntity")); - - connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity")); } - if (!_registeredHandlers.contains(entityID)) { - _registeredHandlers[entityID] = RegisteredEventHandlers(); - } - CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; - CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL }; - handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). -} - -// this is not redundant -- the version in BaseScriptEngine is specifically not Q_INVOKABLE -QScriptValue ScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) { - return BaseScriptEngine::evaluateInClosure(closure, program); + auto handler = engine->newObject(); + handler.setProperty("scope", scope); + handler.setProperty("callback", callback); + return handler; } -QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - return QScriptValue(); // bail early - } - - if (QThread::currentThread() != thread()) { - QScriptValue result; -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; -#endif - BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(QScriptValue, result), - Q_ARG(const QString&, sourceCode), - Q_ARG(const QString&, fileName), - Q_ARG(int, lineNumber)); - return result; - } - - // Check syntax - auto syntaxError = lintScript(sourceCode, fileName); - if (syntaxError.isError()) { - if (!isEvaluating()) { - syntaxError.setProperty("detail", "evaluate"); - } - raiseException(syntaxError); - maybeEmitUncaughtException("lint"); - return syntaxError; - } - QScriptProgram program { sourceCode, fileName, lineNumber }; - if (program.isNull()) { - // can this happen? - auto err = makeError("could not create QScriptProgram for " + fileName); - raiseException(err); - maybeEmitUncaughtException("compile"); - return err; - } - - QScriptValue result; - { - result = BaseScriptEngine::evaluate(program); - maybeEmitUncaughtException("evaluate"); - } - return result; +ScriptValue callScopedHandlerObject(const ScriptValue& handler, const ScriptValue& err, const ScriptValue& result) { + return handler.property("callback").call(handler.property("scope"), ScriptValueList({ err, result })); } -void ScriptEngine::run() { - if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) { - // Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread - // (because we're a client script) - hifi::scripting::setLocalAccessSafeThread(true); - } - - auto filenameParts = _fileNameString.split("/"); - auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; - PROFILE_SET_THREAD_NAME("Script: " + name); - - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - return; // bail early - avoid setting state in init(), as evaluate() will bail too - } - - scriptInfoMessage("Script Engine starting:" + getFilename()); - - if (!_isInitialized) { - init(); - } - - _isRunning = true; - emit runningStateChanged(); - - { - PROFILE_RANGE(script, _fileNameString); - evaluate(_scriptContents, _fileNameString); - maybeEmitUncaughtException(__FUNCTION__); - } -#ifdef _WIN32 - // VS13 does not sleep_until unless it uses the system_clock, see: - // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/ - using clock = std::chrono::system_clock; -#else - using clock = std::chrono::high_resolution_clock; -#endif - - clock::time_point startTime = clock::now(); - int thisFrame = 0; - - auto nodeList = DependencyManager::get(); - auto entityScriptingInterface = DependencyManager::get(); - - _lastUpdate = usecTimestampNow(); - - std::chrono::microseconds totalUpdates(0); - - // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine - while (!_isFinished) { - auto beforeSleep = clock::now(); - - // Throttle to SCRIPT_FPS - // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will - // calculate a sleepUntil to be the time from our start time until the original target - // sleepUntil for this frame. This approach will allow us to "catch up" in the event - // that some of our script udpates/frames take a little bit longer than the target average - // to execute. - // NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach - const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1); - clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION)); - - // However, if our sleepUntil is not at least our average update and timer execution time - // into the future it means our script is taking too long in its updates, and we want to - // punish the script a little bit. So we will force the sleepUntil to be at least our - // averageUpdate + averageTimerPerFrame time into the future. - auto averageUpdate = totalUpdates / thisFrame; - auto averageTimerPerFrame = _totalTimerExecution / thisFrame; - auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame; - auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate); - - // We don't want to actually sleep for too long, because it causes our scripts to hang - // on shutdown and stop... so we want to loop and sleep until we've spent our time in - // purgatory, constantly checking to see if our script was asked to end - bool processedEvents = false; - if (!_isFinished) { - PROFILE_RANGE(script, "processEvents-sleep"); - std::chrono::milliseconds sleepFor = - std::chrono::duration_cast(sleepUntil - clock::now()); - if (sleepFor > std::chrono::milliseconds(0)) { - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - timer.start(sleepFor.count()); - loop.exec(); - } else { - QCoreApplication::processEvents(); - } - processedEvents = true; - } - - PROFILE_RANGE(script, "ScriptMainLoop"); - -#ifdef SCRIPT_DELAY_DEBUG - { - auto actuallySleptUntil = clock::now(); - uint64_t seconds = std::chrono::duration_cast(actuallySleptUntil - startTime).count(); - if (seconds > 0) { // avoid division by zero and time travel - uint64_t fps = thisFrame / seconds; - // Overreporting artificially reduces the reported rate - if (thisFrame % SCRIPT_FPS == 0) { - qCDebug(scriptengine) << - "Frame:" << thisFrame << - "Slept (us):" << std::chrono::duration_cast(actuallySleptUntil - beforeSleep).count() << - "Avg Updates (us):" << averageUpdate.count() << - "FPS:" << fps; - } - } - } -#endif - if (_isFinished) { - break; - } - - // Only call this if we didn't processEvents as part of waiting for next frame - if (!processedEvents) { - PROFILE_RANGE(script, "processEvents"); - QCoreApplication::processEvents(); - } - - if (_isFinished) { - break; - } - - if (!_isFinished && entityScriptingInterface->getEntityPacketSender()->serversExist()) { - // release the queue of edit entity messages. - entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages(); - - // since we're in non-threaded mode, call process so that the packets are sent - if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) { - entityScriptingInterface->getEntityPacketSender()->process(); - } - } - - qint64 now = usecTimestampNow(); - - // we check for 'now' in the past in case people set their clock back - if (_emitScriptUpdates() && _lastUpdate < now) { - float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; - if (!_isFinished) { - auto preUpdate = clock::now(); - { - PROFILE_RANGE(script, "ScriptUpdate"); - emit update(deltaTime); - } - auto postUpdate = clock::now(); - auto elapsed = (postUpdate - preUpdate); - totalUpdates += std::chrono::duration_cast(elapsed); - } - } - _lastUpdate = now; - - // only clear exceptions if we are not in the middle of evaluating - if (!isEvaluating() && hasUncaughtException()) { - qCWarning(scriptengine) << __FUNCTION__ << "---------- UNCAUGHT EXCEPTION --------"; - qCWarning(scriptengine) << "runInThread" << uncaughtException().toString(); - emit unhandledException(cloneUncaughtException(__FUNCTION__)); - clearExceptions(); - } - } - scriptInfoMessage("Script Engine stopping:" + getFilename()); - - stopAllTimers(); // make sure all our timers are stopped if the script is ending - emit scriptEnding(); - - if (entityScriptingInterface->getEntityPacketSender()->serversExist()) { - // release the queue of edit entity messages. - entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages(); - - // since we're in non-threaded mode, call process so that the packets are sent - if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) { - // wait here till the edit packet sender is completely done sending - while (entityScriptingInterface->getEntityPacketSender()->hasPacketsToSend()) { - entityScriptingInterface->getEntityPacketSender()->process(); - QCoreApplication::processEvents(); - } - } else { - // FIXME - do we need to have a similar "wait here" loop for non-threaded packet senders? - } - } - - emit finished(_fileNameString, qSharedPointerCast(sharedFromThis())); - - // Don't leave our local-file-access flag laying around, reset it to false when the scriptengine - // thread is finished - hifi::scripting::setLocalAccessSafeThread(false); - _isRunning = false; - emit runningStateChanged(); - emit doneRunning(); -} - -// NOTE: This is private because it must be called on the same thread that created the timers, which is why -// we want to only call it in our own run "shutdown" processing. -void ScriptEngine::stopAllTimers() { - QMutableHashIterator i(_timerFunctionMap); - int j {0}; - while (i.hasNext()) { - i.next(); - QTimer* timer = i.key(); - qCDebug(scriptengine) << getFilename() << "stopAllTimers[" << j++ << "]"; - stopTimer(timer); - } -} - -void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { - // We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS - QVector toDelete; - QMutableHashIterator i(_timerFunctionMap); - while (i.hasNext()) { - i.next(); - if (i.value().definingEntityIdentifier != entityID) { - continue; - } - QTimer* timer = i.key(); - toDelete << timer; // don't delete while we're iterating. save it. - } - for (auto timer:toDelete) { // now reap 'em - stopTimer(timer); - } - -} - -void ScriptEngine::stop(bool marshal) { - _isStopping = true; // this can be done on any thread - - if (marshal) { - QMetaObject::invokeMethod(this, "stop"); - return; - } - if (!_isFinished) { - _isFinished = true; - emit runningStateChanged(); - } -} - -// Other threads can invoke this through invokeMethod, which causes the callback to be asynchronously executed in this script's thread. -void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::callAnimationStateHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; -#endif - QMetaObject::invokeMethod(this, "callAnimationStateHandler", - Q_ARG(QScriptValue, callback), - Q_ARG(AnimVariantMap, parameters), - Q_ARG(QStringList, names), - Q_ARG(bool, useNames), - Q_ARG(AnimVariantResultHandler, resultHandler)); - return; - } - QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames); - QScriptValueList callingArguments; - callingArguments << javascriptParameters; - assert(currentEntityIdentifier.isInvalidID()); // No animation state handlers from entity scripts. - QScriptValue result = callback.call(QScriptValue(), callingArguments); - - // validate result from callback function. - if (result.isValid() && result.isObject()) { - resultHandler(result); - } else { - qCWarning(scriptengine) << "ScriptEngine::callAnimationStateHandler invalid return argument from callback, expected an object"; - } -} - -void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { - if (deltaSize > 0) { - // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) - reportAdditionalMemoryCost(deltaSize); -#endif - } -} - -void ScriptEngine::timerFired() { - { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); - return; // bail early - } - } - - QTimer* callingTimer = reinterpret_cast(sender()); - CallbackData timerData = _timerFunctionMap.value(callingTimer); - - if (!callingTimer->isActive()) { - // this timer is done, we can kill it - _timerFunctionMap.remove(callingTimer); - delete callingTimer; - } - - // call the associated JS function, if it exists - if (timerData.function.isValid()) { - PROFILE_RANGE(script, __FUNCTION__); - auto preTimer = p_high_resolution_clock::now(); - callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList()); - auto postTimer = p_high_resolution_clock::now(); - auto elapsed = (postTimer - preTimer); - _totalTimerExecution += std::chrono::duration_cast(elapsed); - } else { - qCWarning(scriptengine) << "timerFired -- invalid function" << timerData.function.toVariant().toString(); - } -} - -QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { - // create the timer, add it to the map, and start it - QTimer* newTimer = new QTimer(this); - newTimer->setSingleShot(isSingleShot); - - // The default timer type is not very accurate below about 200ms http://doc.qt.io/qt-5/qt.html#TimerType-enum - static const int MIN_TIMEOUT_FOR_COARSE_TIMER = 200; - if (intervalMS < MIN_TIMEOUT_FOR_COARSE_TIMER) { - newTimer->setTimerType(Qt::PreciseTimer); - } - - connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); - - // make sure the timer stops when the script does - connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); - - - CallbackData timerData = { function, currentEntityIdentifier, currentSandboxURL }; - _timerFunctionMap.insert(newTimer, timerData); - - newTimer->start(intervalMS); - return newTimer; -} - -QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); - return NULL; // bail early - } - - return setupTimerWithInterval(function, intervalMS, false); -} - -QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); - return NULL; // bail early - } - - return setupTimerWithInterval(function, timeoutMS, true); -} - -void ScriptEngine::stopTimer(QTimer *timer) { - if (_timerFunctionMap.contains(timer)) { - timer->stop(); - _timerFunctionMap.remove(timer); - delete timer; - } else { - qCDebug(scriptengine) << "stopTimer -- not in _timerFunctionMap" << timer; - } -} - -QUrl ScriptEngine::resolvePath(const QString& include) const { - QUrl url(include); - // first lets check to see if it's already a full URL -- or a Windows path like "c:/" - if (include.startsWith("/") || url.scheme().length() == 1) { - url = QUrl::fromLocalFile(include); - } - if (!url.isRelative()) { - return expandScriptUrl(url); - } - - // we apparently weren't a fully qualified url, so, let's assume we're relative - // to the first absolute URL in the JS scope chain - QUrl parentURL; - auto context = currentContext(); - do { - QScriptContextInfo contextInfo { context }; - parentURL = QUrl(contextInfo.fileName()); - context = context->parentContext(); - } while (parentURL.isRelative() && context); - - if (parentURL.isRelative()) { - // fallback to the "include" parent (if defined, this will already be absolute) - parentURL = QUrl(_parentURL); - } - - if (parentURL.isRelative()) { - // fallback to the original script engine URL - parentURL = QUrl(_fileNameString); - - // if still relative and path-like, then this is probably a local file... - if (parentURL.isRelative() && url.path().contains("/")) { - parentURL = QUrl::fromLocalFile(_fileNameString); - } - } - - // at this point we should have a legitimate fully qualified URL for our parent - url = expandScriptUrl(parentURL.resolved(url)); - return url; -} - -QUrl ScriptEngine::resourcesPath() const { - return QUrl(PathUtils::resourcesUrl()); -} - -void ScriptEngine::print(const QString& message) { - emit printedMessage(message, getFilename()); -} - - -void ScriptEngine::beginProfileRange(const QString& label) const { - PROFILE_SYNC_BEGIN(script, label.toStdString().c_str(), label.toStdString().c_str()); -} - -void ScriptEngine::endProfileRange(const QString& label) const { - PROFILE_SYNC_END(script, label.toStdString().c_str(), label.toStdString().c_str()); -} - -// Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js) -QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& relativeTo) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return QString(); - } - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - QUrl url(moduleId); - - auto displayId = moduleId; - if (displayId.length() > MAX_DEBUG_VALUE_LENGTH) { - displayId = displayId.mid(0, MAX_DEBUG_VALUE_LENGTH) + "..."; - } - auto message = QString("Cannot find module '%1' (%2)").arg(displayId); - - auto throwResolveError = [&](const QScriptValue& error) -> QString { - raiseException(error); - maybeEmitUncaughtException("require.resolve"); - return QString(); - }; - - // de-fuzz the input a little by restricting to rational sizes - auto idLength = url.toString().length(); - if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) { - auto details = QString("rejecting invalid module id size (%1 chars [1,%2])") - .arg(idLength).arg(MAX_MODULE_ID_LENGTH); - return throwResolveError(makeError(message.arg(details), "RangeError")); - } - - // this regex matches: absolute, dotted or path-like URLs - // (ie: the kind of stuff ScriptEngine::resolvePath already handles) - QRegularExpression qualified ("^\\w+:|^/|^[.]{1,2}(/|$)"); - - // this is for module.require (which is a bound version of require that's always relative to the module path) - if (!relativeTo.isEmpty()) { - url = QUrl(relativeTo).resolved(moduleId); - url = resolvePath(url.toString()); - } else if (qualified.match(moduleId).hasMatch()) { - url = resolvePath(moduleId); - } else { - // check if the moduleId refers to a "system" module - QString systemPath = defaultScriptsLoc.path(); - QString systemModulePath = QString("%1/modules/%2.js").arg(systemPath).arg(moduleId); - url = defaultScriptsLoc; - url.setPath(systemModulePath); - if (!QFileInfo(url.toLocalFile()).isFile()) { - if (!moduleId.contains("./")) { - // the user might be trying to refer to a relative file without anchoring it - // let's do them a favor and test for that case -- offering specific advice if detected - auto unanchoredUrl = resolvePath("./" + moduleId); - if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) { - auto msg = QString("relative module ids must be anchored; use './%1' instead") - .arg(moduleId); - return throwResolveError(makeError(message.arg(msg))); - } - } - return throwResolveError(makeError(message.arg("system module not found"))); - } - } - - if (url.isRelative()) { - return throwResolveError(makeError(message.arg("could not resolve module id"))); - } - - // if it looks like a local file, verify that it's an allowed path and really a file - if (url.isLocalFile()) { - QFileInfo file(url.toLocalFile()); - QUrl canonical = url; - if (file.exists()) { - canonical.setPath(file.canonicalFilePath()); - } - - bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile(); - if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) { - return throwResolveError(makeError(message.arg( - QString("path '%1' outside of origin script '%2' '%3'") - .arg(PathUtils::stripFilename(url)) - .arg(PathUtils::stripFilename(currentSandboxURL)) - .arg(canonical.toString()) - ))); - } - if (!file.exists()) { - return throwResolveError(makeError(message.arg("path does not exist: " + url.toLocalFile()))); - } - if (!file.isFile()) { - return throwResolveError(makeError(message.arg("path is not a file: " + url.toLocalFile()))); - } - } - - maybeEmitUncaughtException(__FUNCTION__); - return url.toString(); -} - -// retrieves the current parent module from the JS scope chain -QScriptValue ScriptEngine::currentModule() { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - auto jsRequire = globalObject().property("Script").property("require"); - auto cache = jsRequire.property("cache"); - auto candidate = QScriptValue(); - for (auto c = currentContext(); c && !candidate.isObject(); c = c->parentContext()) { - QScriptContextInfo contextInfo { c }; - candidate = cache.property(contextInfo.fileName()); - } - if (!candidate.isObject()) { - return QScriptValue(); - } - return candidate; -} - -// replaces or adds "module" to "parent.children[]" array -// (for consistency with Node.js and userscript cache invalidation without "cache busters") -bool ScriptEngine::registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent) { - auto children = parent.property("children"); - if (children.isArray()) { - auto key = module.property("id"); - auto length = children.property("length").toInt32(); - for (int i = 0; i < length; i++) { - if (children.property(i).property("id").strictlyEquals(key)) { - qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module"; - children.setProperty(i, module); - return true; - } - } - qCDebug(scriptengine_module) << key.toString() << " appending parent.children[" << length << "] = module"; - children.setProperty(length, module); - return true; - } else if (parent.isValid()) { - qCDebug(scriptengine_module) << "registerModuleWithParent -- unrecognized parent" << parent.toVariant().toString(); +bool ScriptEngine::IS_THREADSAFE_INVOCATION(const QString& method) { + QThread* thread = this->thread(); + if (QThread::currentThread() == thread) { + return true; } + qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") + .arg(method) + .arg(thread ? thread->objectName() : "(!thread)") + .arg(QThread::currentThread()->objectName()); + qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; + Q_ASSERT(false); return false; } - -// creates a new JS "module" Object with default metadata properties -QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptValue& parent) { - auto closure = newObject(); - auto exports = newObject(); - auto module = newObject(); - qCDebug(scriptengine_module) << "newModule" << parent.property("filename").toString(); - - closure.setProperty("module", module, READONLY_PROP_FLAGS); - - // note: this becomes the "exports" free variable, so should not be set read only - closure.setProperty("exports", exports); - - // make the closure available to module instantiation - module.setProperty("__closure__", closure, READONLY_HIDDEN_PROP_FLAGS); - - // for consistency with Node.js Module - module.setProperty("id", modulePath, READONLY_PROP_FLAGS); - module.setProperty("filename", modulePath, READONLY_PROP_FLAGS); - module.setProperty("exports", exports); // not readonly - module.setProperty("loaded", false, READONLY_PROP_FLAGS); - module.setProperty("parent", parent, READONLY_PROP_FLAGS); - module.setProperty("children", newArray(), READONLY_PROP_FLAGS); - - // module.require is a bound version of require that always resolves relative to that module's path - auto boundRequire = QScriptEngine::evaluate("(function(id) { return Script.require(Script.require.resolve(id, this.filename)); })", "(boundRequire)"); - module.setProperty("require", boundRequire, READONLY_PROP_FLAGS); - - return module; -} - -// synchronously fetch a module's source code using BatchLoader -QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const bool forceDownload) { - using UrlMap = QMap; - auto scriptCache = DependencyManager::get(); - QVariantMap req; - qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread(); - - auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) { - auto url = modulePath; - auto status = _status[url]; - auto contents = data[url]; - if (isStopping()) { - req["status"] = "Stopped"; - req["success"] = false; - } else { - req["url"] = url; - req["status"] = status; - req["success"] = ScriptCache::isSuccessStatus(status); - req["contents"] = contents; - } - }; - - if (forceDownload) { - qCDebug(scriptengine_module) << "require.requestScript -- clearing cache for" << modulePath; - scriptCache->deleteScript(modulePath); - } - BatchLoader* loader = new BatchLoader(QList({ modulePath })); - connect(loader, &BatchLoader::finished, this, onload); - connect(this, &QObject::destroyed, loader, &QObject::deleteLater); - // fail faster? (since require() blocks the engine thread while resolving dependencies) - const int MAX_RETRIES = 1; - - loader->start(MAX_RETRIES); - - if (!loader->isFinished()) { - // This lambda can get called AFTER this local scope has completed. - // This is why we pass smart ptrs to the lambda instead of references to local variables. - auto monitor = std::make_shared(); - auto loop = std::make_shared(); - QObject::connect(loader, &BatchLoader::finished, this, [monitor, loop] { - monitor->stop(); - loop->quit(); - }); - - // this helps detect the case where stop() is invoked during the download - // but not seen in time to abort processing in onload()... - connect(monitor.get(), &QTimer::timeout, this, [this, loop] { - if (isStopping()) { - loop->exit(-1); - } - }); - monitor->start(500); - loop->exec(); - } - loader->deleteLater(); - return req; -} - -// evaluate a pending module object using the fetched source code -QScriptValue ScriptEngine::instantiateModule(const QScriptValue& module, const QString& sourceCode) { - QScriptValue result; - auto modulePath = module.property("filename").toString(); - auto closure = module.property("__closure__"); - - qCDebug(scriptengine_module) << QString("require.instantiateModule: %1 / %2 bytes") - .arg(QUrl(modulePath).fileName()).arg(sourceCode.length()); - - if (module.property("content-type").toString() == "application/json") { - qCDebug(scriptengine_module) << "... parsing as JSON"; - closure.setProperty("__json", sourceCode); - result = evaluateInClosure(closure, { "module.exports = JSON.parse(__json)", modulePath }); - } else { - // scoped vars for consistency with Node.js - closure.setProperty("require", module.property("require")); - closure.setProperty("__filename", modulePath, READONLY_HIDDEN_PROP_FLAGS); - closure.setProperty("__dirname", QString(modulePath).replace(QRegExp("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS); - result = evaluateInClosure(closure, { sourceCode, modulePath }); - } - maybeEmitUncaughtException(__FUNCTION__); - return result; -} - -// CommonJS/Node.js like require/module support -QScriptValue ScriptEngine::require(const QString& moduleId) { - qCDebug(scriptengine_module) << "ScriptEngine::require(" << moduleId.left(MAX_DEBUG_VALUE_LENGTH) << ")"; - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - - auto jsRequire = globalObject().property("Script").property("require"); - auto cacheMeta = jsRequire.data(); - auto cache = jsRequire.property("cache"); - auto parent = currentModule(); - - auto throwModuleError = [&](const QString& modulePath, const QScriptValue& error) { - cache.setProperty(modulePath, nullValue()); - if (!error.isNull()) { -#ifdef DEBUG_JS_MODULES - qCWarning(scriptengine_module) << "throwing module error:" << error.toString() << modulePath << error.property("stack").toString(); -#endif - raiseException(error); - } - maybeEmitUncaughtException("module"); - return unboundNullValue(); - }; - - // start by resolving the moduleId into a fully-qualified path/URL - QString modulePath = _requireResolve(moduleId); - if (modulePath.isNull() || hasUncaughtException()) { - // the resolver already threw an exception -- bail early - maybeEmitUncaughtException(__FUNCTION__); - return unboundNullValue(); - } - - // check the resolved path against the cache - auto module = cache.property(modulePath); - - // modules get cached in `Script.require.cache` and (similar to Node.js) users can access it - // to inspect particular entries and invalidate them by deleting the key: - // `delete Script.require.cache[Script.require.resolve(moduleId)];` - - // Check to see if we should invalidate the cache based on a user setting. - Setting::Handle getCachebustSetting {"cachebustScriptRequire", false }; - - // cacheMeta is just used right now to tell deleted keys apart from undefined ones - bool invalidateCache = getCachebustSetting.get() || (module.isUndefined() && cacheMeta.property(moduleId).isValid()); - - // reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load - cacheMeta.setProperty(modulePath, QScriptValue()); - - auto exports = module.property("exports"); - if (!invalidateCache && exports.isObject()) { - // we have found a cached module -- just need to possibly register it with current parent - qCDebug(scriptengine_module) << QString("require - using cached module for '%1' (loaded: %2)") - .arg(moduleId).arg(module.property("loaded").toString()); - registerModuleWithParent(module, parent); - maybeEmitUncaughtException("cached module"); - return exports; - } - - // bootstrap / register new empty module - module = newModule(modulePath, parent); - registerModuleWithParent(module, parent); - - // add it to the cache (this is done early so any cyclic dependencies pick up) - cache.setProperty(modulePath, module); - - // download the module source - auto req = fetchModuleSource(modulePath, invalidateCache); - - if (!req.contains("success") || !req["success"].toBool()) { - auto error = QString("error retrieving script (%1)").arg(req["status"].toString()); - return throwModuleError(modulePath, error); - } - -#if DEBUG_JS_MODULES - qCDebug(scriptengine_module) << "require.loaded: " << - QUrl(req["url"].toString()).fileName() << req["status"].toString(); -#endif - - auto sourceCode = req["contents"].toString(); - - if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) { - module.setProperty("content-type", "application/json"); - } else { - module.setProperty("content-type", "application/javascript"); - } - - // evaluate the module - auto result = instantiateModule(module, sourceCode); - - if (result.isError() && !result.strictlyEquals(module.property("exports"))) { - qCWarning(scriptengine_module) << "-- result.isError --" << result.toString(); - return throwModuleError(modulePath, result); - } - - // mark as fully-loaded - module.setProperty("loaded", true, READONLY_PROP_FLAGS); - - // set up a new reference point for detecting cache key deletion - cacheMeta.setProperty(modulePath, module); - - qCDebug(scriptengine_module) << "//ScriptEngine::require(" << moduleId << ")"; - - maybeEmitUncaughtException(__FUNCTION__); - return module.property("exports"); -} - -// If a callback is specified, the included files will be loaded asynchronously and the callback will be called -// when all of the files have finished loading. -// If no callback is specified, the included files will be loaded synchronously and will block execution until -// all of the files have finished loading. -void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return; - } - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" - + includeFiles.join(",") + "parent script:" + getFilename()); - return; // bail early - } - QList urls; - - for (QString includeFile : includeFiles) { - QString file = DependencyManager::get()->normalizeURL(includeFile); - QUrl thisURL; - bool isStandardLibrary = false; - if (file.startsWith("/~/")) { - thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - if (!defaultScriptsLoc.isParentOf(thisURL)) { - scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries"); - continue; - } - isStandardLibrary = true; - } else { - thisURL = resolvePath(file); - } - - bool disallowOutsideFiles = thisURL.isLocalFile() && !isStandardLibrary && !currentSandboxURL.isLocalFile(); - if (disallowOutsideFiles && !PathUtils::isDescendantOf(thisURL, currentSandboxURL)) { - scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString() - + "outside of original entity script" + currentSandboxURL.toString()); - } else { - // We could also check here for CORS, but we don't yet. - // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. - urls.append(thisURL); - } - } - - // If there are no URLs left to download, don't bother attempting to download anything and return early - if (urls.size() == 0) { - return; - } - - BatchLoader* loader = new BatchLoader(urls); - EntityItemID capturedEntityIdentifier = currentEntityIdentifier; - QUrl capturedSandboxURL = currentSandboxURL; - - auto evaluateScripts = [=](const QMap& data, const QMap& status) { - auto parentURL = _parentURL; - for (QUrl url : urls) { - QString contents = data[url]; - if (contents.isNull()) { - scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString()); - } else { - std::lock_guard lock(_lock); - if (!_includedURLs.contains(url)) { - _includedURLs << url; - // Set the parent url so that path resolution will be relative - // to this script's url during its initial evaluation - _parentURL = url.toString(); - auto operation = [&]() { - evaluate(contents, url.toString()); - }; - - doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); - if (hasUncaughtException()) { - emit unhandledException(cloneUncaughtException("evaluateInclude")); - clearExceptions(); - } - } else { - scriptPrintedMessage("Script.include() skipping evaluation of previously included url:" + url.toString()); - } - } - } - _parentURL = parentURL; - - if (callback.isFunction()) { - callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList()); - } - - loader->deleteLater(); - }; - - connect(loader, &BatchLoader::finished, this, evaluateScripts); - - // If we are destroyed before the loader completes, make sure to clean it up - connect(this, &QObject::destroyed, loader, &QObject::deleteLater); - - loader->start(processLevelMaxRetries); - - if (!callback.isFunction() && !loader->isFinished()) { - QEventLoop loop; - QObject::connect(loader, &BatchLoader::finished, &loop, &QEventLoop::quit); - loop.exec(); - } -} - -void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" - + includeFile + "parent script:" + getFilename()); - return; // bail early - } - - QStringList urls; - urls.append(includeFile); - include(urls, callback); -} - -// NOTE: The load() command is similar to the include() command except that it loads the script -// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which -// the Application or other context will connect to in order to know to actually load the script -void ScriptEngine::load(const QString& loadFile) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return; - } - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" - + loadFile + "parent script:" + getFilename()); - return; // bail early - } - if (!currentEntityIdentifier.isInvalidID()) { - scriptWarningMessage("Script.load() from entity script is ignored... loadFile:" - + loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString()); - return; // bail early - } - - QUrl url = resolvePath(loadFile); - if (_isReloading) { - auto scriptCache = DependencyManager::get(); - scriptCache->deleteScript(url.toString()); - emit reloadScript(url.toString(), false); - } else { - emit loadScript(url.toString(), false); - } -} - -// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args -void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) { - if (QThread::currentThread() != thread()) { - qCDebug(scriptengine) << "*** ERROR *** ScriptEngine::forwardHandlerCall() 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; - } - CallbackList handlersForEvent = handlersOnEntity[eventName]; - if (!handlersForEvent.isEmpty()) { - for (int i = 0; i < handlersForEvent.count(); ++i) { - // handlersForEvent[i] can contain many handlers that may have each been added by different interface or entity scripts, - // and the entity scripts may be for entities other than the one this is a handler for. - // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added. - CallbackData& handler = handlersForEvent[i]; - callWithEnvironment(handler.definingEntityIdentifier, handler.definingSandboxURL, handler.function, QScriptValue(), eventHandlerArgs); - } - } -} - -int ScriptEngine::getNumRunningEntityScripts() const { - QReadLocker locker { &_entityScriptsLock }; - int sum = 0; - for (const auto& st : _entityScripts) { - if (st.status == EntityScriptStatus::RUNNING) { - ++sum; - } - } - return sum; -} - -void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) { - { - QWriteLocker locker { &_entityScriptsLock }; - _entityScripts[entityID] = details; - } - emit entityScriptDetailsUpdated(); -} - -void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) { - { - QWriteLocker locker { &_entityScriptsLock }; - EntityScriptDetails& details = _entityScripts[entityID]; - details.status = status; - details.errorInfo = errorInfo; - } - emit entityScriptDetailsUpdated(); -} - -QVariant ScriptEngine::cloneEntityScriptDetails(const EntityItemID& entityID) { - static const QVariant NULL_VARIANT { qVariantFromValue((QObject*)nullptr) }; - QVariantMap map; - if (entityID.isNull()) { - // TODO: find better way to report JS Error across thread/process boundaries - map["isError"] = true; - map["errorInfo"] = "Error: getEntityScriptDetails -- invalid entityID"; - } else { -#ifdef DEBUG_ENTITY_STATES - qDebug() << "cloneEntityScriptDetails" << entityID << QThread::currentThread(); -#endif - EntityScriptDetails scriptDetails; - if (getEntityScriptDetails(entityID, scriptDetails)) { -#ifdef DEBUG_ENTITY_STATES - qDebug() << "gotEntityScriptDetails" << scriptDetails.status << QThread::currentThread(); -#endif - map["isRunning"] = isEntityScriptRunning(entityID); - map["status"] = EntityScriptStatus_::valueToKey(scriptDetails.status).toLower(); - map["errorInfo"] = scriptDetails.errorInfo; - map["entityID"] = entityID.toString(); -#ifdef DEBUG_ENTITY_STATES - { - auto debug = QVariantMap(); - debug["script"] = scriptDetails.scriptText; - debug["scriptObject"] = scriptDetails.scriptObject.toVariant(); - debug["lastModified"] = (qlonglong)scriptDetails.lastModified; - debug["sandboxURL"] = scriptDetails.definingSandboxURL; - map["debug"] = debug; - } -#endif - } else { -#ifdef DEBUG_ENTITY_STATES - qDebug() << "!gotEntityScriptDetails" << QThread::currentThread(); -#endif - map["isError"] = true; - map["errorInfo"] = "Entity script details unavailable"; - map["entityID"] = entityID.toString(); - } - } - return map; -} - -QFuture ScriptEngine::getLocalEntityScriptDetails(const EntityItemID& entityID) { - return QtConcurrent::run(this, &ScriptEngine::cloneEntityScriptDetails, entityID); -} - -bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { - QReadLocker locker { &_entityScriptsLock }; - auto it = _entityScripts.constFind(entityID); - if (it == _entityScripts.constEnd()) { - return false; - } - details = it.value(); - return true; -} - -bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const { - QReadLocker locker { &_entityScriptsLock }; - return _entityScripts.contains(entityID); -} - -void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadEntityScript", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, entityScript), - Q_ARG(bool, forceRedownload) - ); - return; - } - PROFILE_RANGE(script, __FUNCTION__); - - QSharedPointer scriptEngines(_scriptEngines); - if (isStopping() || !scriptEngines || scriptEngines->isStopped()) { - qCDebug(scriptengine) << "loadEntityScript.start " << entityID.toString() - << " but isStopping==" << isStopping() - << " || engines->isStopped==" << scriptEngines->isStopped(); - return; - } - - if (!hasEntityScriptDetails(entityID)) { - // make sure EntityScriptDetails has an entry for this UUID right away - // (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight) - updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); - } - -#ifdef DEBUG_ENTITY_STATES - { - EntityScriptDetails details; - bool hasEntityScript = getEntityScriptDetails(entityID, details); - qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityID.toString() - << "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")"; - } -#endif - - EntityScriptDetails newDetails; - newDetails.scriptText = entityScript; - newDetails.status = EntityScriptStatus::LOADING; - newDetails.definingSandboxURL = currentSandboxURL; - setEntityScriptDetails(entityID, newDetails); - - auto scriptCache = DependencyManager::get(); - // note: see EntityTreeRenderer.cpp for shared pointer lifecycle management - QWeakPointer weakRef(sharedFromThis()); - scriptCache->getScriptContents(entityScript, - [this, weakRef, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { - QSharedPointer strongRef(weakRef); - if (!strongRef) { - qCWarning(scriptengine) << "loadEntityScript.contentAvailable -- ScriptEngine was deleted during getScriptContents!!"; - return; - } - if (isStopping()) { -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- stopping"; -#endif - return; - } - executeOnScriptThread([=]{ -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << entityID.toString(); -#endif - if (!isStopping() && hasEntityScriptDetails(entityID)) { - _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; - } else { -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; -#endif - } - }); - }, forceRedownload); -} - -/*@jsdoc - * Triggered when the script starts for a user. See also, {@link Script.entityScriptPreloadFinished}. - *

Note: Can only be connected to via this.preload = function (...) { ... } in the entity script.

- *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

- * @function Entities.preload - * @param {Uuid} entityID - The ID of the entity that the script is running in. - * @returns {Signal} - * @example Get the ID of the entity that a client entity script is running in. - * var entityScript = (function () { - * this.entityID = Uuid.NULL; - * - * this.preload = function (entityID) { - * this.entityID = entityID; - * print("Entity ID: " + this.entityID); - * }; - * }); - * - * var entityID = Entities.addEntity({ - * type: "Box", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), - * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, - * color: { red: 255, green: 0, blue: 0 }, - * script: "(" + entityScript + ")", // Could host the script on a Web server instead. - * lifetime: 300 // Delete after 5 minutes. - * }); - */ -// The JSDoc is for the callEntityScriptMethod() call in this method. -// 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 , const QString& status) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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), - Q_ARG(const QString&, status)); - return; - } - -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; -#endif - - auto scriptCache = DependencyManager::get(); - bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); - auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript"; - - QString entityScript; - { - QWriteLocker locker { &_entityScriptsLock }; - entityScript = _entityScripts[entityID].scriptText; - } - - EntityScriptDetails newDetails; - newDetails.scriptText = scriptOrURL; - - // If an error happens below, we want to update newDetails with the new status info - // and also abort any pending Entity loads that are waiting on the exact same script URL. - auto setError = [&](const QString &errorInfo, const EntityScriptStatus& status) { - newDetails.errorInfo = errorInfo; - newDetails.status = status; - setEntityScriptDetails(entityID, newDetails); - }; - - // NETWORK / FILESYSTEM ERRORS - if (!success) { - setError("Failed to load script (" + status + ")", EntityScriptStatus::ERROR_LOADING_SCRIPT); - return; - } - - // SYNTAX ERRORS - auto syntaxError = lintScript(contents, fileName); - if (syntaxError.isError()) { - auto message = syntaxError.property("formatted").toString(); - if (message.isEmpty()) { - message = syntaxError.toString(); - } - setError(QString("Bad syntax (%1)").arg(message), EntityScriptStatus::ERROR_RUNNING_SCRIPT); - syntaxError.setProperty("detail", entityID.toString()); - emit unhandledException(syntaxError); - return; - } - QScriptProgram program { contents, fileName }; - if (program.isNull()) { - setError("Bad program (isNull)", EntityScriptStatus::ERROR_RUNNING_SCRIPT); - emit unhandledException(makeError("program.isNull")); - return; // done processing script - } - - if (isURL) { - setParentURL(scriptOrURL); - } - - // SANITY/PERFORMANCE CHECK USING SANDBOX - const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND; - BaseScriptEngine sandbox; - sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT); - QScriptValue testConstructor, exception; - if (atoi(getenv("UNSAFE_ENTITY_SCRIPTS") ? getenv("UNSAFE_ENTITY_SCRIPTS") : "0")) - { - QTimer timeout; - timeout.setSingleShot(true); - timeout.start(SANDBOX_TIMEOUT); - connect(&timeout, &QTimer::timeout, [=, &sandbox]{ - qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout"; - - // Guard against infinite loops and non-performant code - sandbox.raiseException( - sandbox.makeError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT)) - ); - }); - - testConstructor = sandbox.evaluate(program); - - if (sandbox.hasUncaughtException()) { - exception = sandbox.cloneUncaughtException(QString("(preflight %1)").arg(entityID.toString())); - sandbox.clearExceptions(); - } else if (testConstructor.isError()) { - exception = testConstructor; - } - } else { - // ENTITY SCRIPT WHITELIST STARTS HERE - auto nodeList = DependencyManager::get(); - bool passList = false; // assume unsafe - QString whitelistPrefix = "[WHITELIST ENTITY SCRIPTS]"; - QList safeURLPrefixes = { "file:///", "atp:", "cache:" }; - safeURLPrefixes += qEnvironmentVariable("EXTRA_WHITELIST").trimmed().split(QRegExp("\\s*,\\s*"), Qt::SkipEmptyParts); - - // Entity Script Whitelist toggle check. - Setting::Handle whitelistEnabled {"private/whitelistEnabled", false }; - - if (!whitelistEnabled.get()) { - passList = true; - } - - // Pull SAFEURLS from the Interface.JSON settings. - QVariant raw = Setting::Handle("private/settingsSafeURLS").get(); - QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegExp("\\s*[,\r\n]+\\s*"), Qt::SkipEmptyParts); - safeURLPrefixes += settingsSafeURLS; - // END Pull SAFEURLS from the Interface.JSON settings. - - // Get current domain whitelist bypass, in case an entire domain is whitelisted. - QString currentDomain = DependencyManager::get()->getDomainURL().host(); - - QString domainSafeIP = nodeList->getDomainHandler().getHostname(); - QString domainSafeURL = URL_SCHEME_VIRCADIA + "://" + currentDomain; - for (const auto& str : safeURLPrefixes) { - if (domainSafeURL.startsWith(str) || domainSafeIP.startsWith(str)) { - qCDebug(scriptengine) << whitelistPrefix << "Whitelist Bypassed, entire domain is whitelisted. Current Domain Host: " - << nodeList->getDomainHandler().getHostname() - << "Current Domain: " << currentDomain; - passList = true; - } - } - // END bypass whitelist based on current domain. - - // Start processing scripts through the whitelist. - if (ScriptEngine::getContext() == "entity_server") { // If running on the server, do not engage whitelist. - passList = true; - } else if (!passList) { // If waved through, do not engage whitelist. - for (const auto& str : safeURLPrefixes) { - qCDebug(scriptengine) << whitelistPrefix << "Script URL: " << scriptOrURL << "TESTING AGAINST" << str << "RESULTS IN" - << scriptOrURL.startsWith(str); - if (!str.isEmpty() && scriptOrURL.startsWith(str)) { - passList = true; - qCDebug(scriptengine) << whitelistPrefix << "Script approved."; - break; // Bail early since we found a match. - } - } - } - // END processing of scripts through the whitelist. - - if (!passList) { // If the entity failed to pass for any reason, it's blocked and an error is thrown. - qCDebug(scriptengine) << whitelistPrefix << "(disabled entity script)" << entityID.toString() << scriptOrURL; - exception = makeError("UNSAFE_ENTITY_SCRIPTS == 0"); - } else { - QTimer timeout; - timeout.setSingleShot(true); - timeout.start(SANDBOX_TIMEOUT); - connect(&timeout, &QTimer::timeout, [=, &sandbox] { - qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout"; - - // Guard against infinite loops and non-performant code - sandbox.raiseException( - sandbox.makeError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT))); - }); - - testConstructor = sandbox.evaluate(program); - - if (sandbox.hasUncaughtException()) { - exception = sandbox.cloneUncaughtException(QString("(preflight %1)").arg(entityID.toString())); - sandbox.clearExceptions(); - } else if (testConstructor.isError()) { - exception = testConstructor; - } - } - // ENTITY SCRIPT WHITELIST ENDS HERE, uncomment below for original full disabling. - - // qDebug() << "(disabled entity script)" << entityID.toString() << scriptOrURL; - // exception = makeError("UNSAFE_ENTITY_SCRIPTS == 0"); - } - - if (exception.isError()) { - // create a local copy using makeError to decouple from the sandbox engine - exception = makeError(exception); - setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); - emit unhandledException(exception); - return; - } - - // CONSTRUCTOR VIABILITY - if (!testConstructor.isFunction()) { - QString testConstructorType = QString(testConstructor.toVariant().typeName()); - if (testConstructorType == "") { - testConstructorType = "empty"; - } - QString testConstructorValue = testConstructor.toString(); - if (testConstructorValue.size() > MAX_DEBUG_VALUE_LENGTH) { - testConstructorValue = testConstructorValue.mid(0, MAX_DEBUG_VALUE_LENGTH) + "..."; - } - auto message = QString("failed to load entity script -- expected a function, got %1, %2") - .arg(testConstructorType).arg(testConstructorValue); - - auto err = makeError(message); - err.setProperty("fileName", scriptOrURL); - err.setProperty("detail", "(constructor " + entityID.toString() + ")"); - - setError("Could not find constructor (" + testConstructorType + ")", EntityScriptStatus::ERROR_RUNNING_SCRIPT); - emit unhandledException(err); - return; // done processing script - } - - // (this feeds into refreshFileScript) - int64_t lastModified = 0; - if (isFileUrl) { - QString file = QUrl(scriptOrURL).toLocalFile(); - lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); - } - - // THE ACTUAL EVALUATION AND CONSTRUCTION - QScriptValue entityScriptConstructor, entityScriptObject; - QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL; - auto initialization = [&]{ - entityScriptConstructor = evaluate(contents, fileName); - entityScriptObject = entityScriptConstructor.construct(); - - if (hasUncaughtException()) { - entityScriptObject = cloneUncaughtException("(construct " + entityID.toString() + ")"); - clearExceptions(); - } - }; - - doWithEnvironment(entityID, sandboxURL, initialization); - - if (entityScriptObject.isError()) { - auto exception = entityScriptObject; - setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); - emit unhandledException(exception); - return; - } - - // ... AND WE HAVE LIFTOFF - newDetails.status = EntityScriptStatus::RUNNING; - newDetails.scriptObject = entityScriptObject; - newDetails.lastModified = lastModified; - newDetails.definingSandboxURL = sandboxURL; - setEntityScriptDetails(entityID, newDetails); - - if (isURL) { - setParentURL(""); - } - - // if we got this far, then call the preload method - callEntityScriptMethod(entityID, "preload"); - - emit entityScriptPreloadFinished(entityID); -} - -/*@jsdoc - * Triggered when the script terminates for a user. - *

Note: Can only be connected to via this.unoad = function () { ... } in the entity script.

- *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

- * @function Entities.unload - * @param {Uuid} entityID - The ID of the entity that the script is running in. - * @returns {Signal} - */ -// The JSDoc is for the callEntityScriptMethod() call in this method. -void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** 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), - Q_ARG(bool, shouldRemoveFromMap)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] " - "entityID:" << entityID; -#endif - - EntityScriptDetails oldDetails; - if (getEntityScriptDetails(entityID, oldDetails)) { - auto scriptText = oldDetails.scriptText; - - if (isEntityScriptRunning(entityID)) { - callEntityScriptMethod(entityID, "unload"); - } -#ifdef DEBUG_ENTITY_STATES - else { - qCDebug(scriptengine) << "unload called while !running" << entityID << oldDetails.status; - } -#endif - if (shouldRemoveFromMap) { - // this was a deleted entity, we've been asked to remove it from the map - { - QWriteLocker locker { &_entityScriptsLock }; - _entityScripts.remove(entityID); - } - emit entityScriptDetailsUpdated(); - } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { - EntityScriptDetails newDetails; - newDetails.status = EntityScriptStatus::UNLOADED; - newDetails.lastModified = QDateTime::currentMSecsSinceEpoch(); - // keep scriptText populated for the current need to "debouce" duplicate calls to unloadEntityScript - newDetails.scriptText = scriptText; - setEntityScriptDetails(entityID, newDetails); - } - - stopAllTimersForEntityScript(entityID); - } -} - -QList ScriptEngine::getListOfEntityScriptIDs() { - QReadLocker locker{ &_entityScriptsLock }; - return _entityScripts.keys(); -} - -void ScriptEngine::unloadAllEntityScripts(bool blockingCall) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; -#endif - - QMetaObject::invokeMethod(this, "unloadAllEntityScripts", - blockingCall ? Qt::BlockingQueuedConnection : Qt::QueuedConnection); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; -#endif - - QList keys; - { - QReadLocker locker{ &_entityScriptsLock }; - keys = _entityScripts.keys(); - } - foreach(const EntityItemID& entityID, keys) { - unloadEntityScript(entityID); - } - { - QWriteLocker locker{ &_entityScriptsLock }; - _entityScripts.clear(); - } - emit entityScriptDetailsUpdated(); - -#ifdef DEBUG_ENGINE_STATE - _debugDump( - "---- CURRENT STATE OF ENGINE: --------------------------", - globalObject(), - "--------------------------------------------------------" - ); -#endif // DEBUG_ENGINE_STATE -} - -void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { - if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { - return; - } - - static bool recurseGuard = false; - if (recurseGuard) { - return; - } - recurseGuard = true; - - EntityScriptDetails details; - { - QWriteLocker locker { &_entityScriptsLock }; - details = _entityScripts[entityID]; - } - // Check to see if a file based script needs to be reloaded (easier debugging) - if (details.lastModified > 0) { - QString filePath = QUrl(details.scriptText).toLocalFile(); - auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch(); - if (lastModified > details.lastModified) { - scriptInfoMessage("Reloading modified script " + details.scriptText); - loadEntityScript(entityID, details.scriptText, true); - } - } - recurseGuard = false; -} - -// Execute operation in the appropriate context for (the possibly empty) entityID. -// Even if entityID is supplied as currentEntityIdentifier, this still documents the source -// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different -// global values for different entity scripts). -void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation) { - EntityItemID oldIdentifier = currentEntityIdentifier; - QUrl oldSandboxURL = currentSandboxURL; - currentEntityIdentifier = entityID; - currentSandboxURL = sandboxURL; - -#if DEBUG_CURRENT_ENTITY - QScriptValue oldData = this->globalObject().property("debugEntityID"); - this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global. - operation(); - this->globalObject().setProperty("debugEntityID", oldData); -#else - operation(); -#endif - maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__); - currentEntityIdentifier = oldIdentifier; - currentSandboxURL = oldSandboxURL; -} - -void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { - auto operation = [&]() { - function.call(thisObject, args); - }; - doWithEnvironment(entityID, sandboxURL, operation); -} - -void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName; -#endif - - QMetaObject::invokeMethod(this, "callEntityScriptMethod", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName), - Q_ARG(const QStringList&, params), - Q_ARG(const QUuid&, remoteCallerID)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName; -#endif - - if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") { - refreshFileScript(entityID); - } - if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details; - { - QWriteLocker locker { &_entityScriptsLock }; - details = _entityScripts[entityID]; - } - QScriptValue entityScript = details.scriptObject; // previously loaded - - // If this is a remote call, we need to check to see if the function is remotely callable - // we do this by checking for the existance of the 'remotelyCallable' property on the - // entityScript. And we confirm that the method name is included. If this fails, the - // function will not be called. - bool callAllowed = false; - if (remoteCallerID == QUuid()) { - callAllowed = true; - } else { - if (entityScript.property("remotelyCallable").isArray()) { - auto callables = entityScript.property("remotelyCallable"); - auto callableCount = callables.property("length").toInteger(); - for (int i = 0; i < callableCount; i++) { - auto callable = callables.property(i).toString(); - if (callable == methodName) { - callAllowed = true; - break; - } - } - } - if (!callAllowed) { - qDebug() << "Method [" << methodName << "] not remotely callable."; - } - } - - if (callAllowed && entityScript.property(methodName).isFunction()) { - QScriptValueList args; - args << entityID.toScriptValue(this); - args << qScriptValueFromSequence(this, params); - - QScriptValue oldData = this->globalObject().property("Script").property("remoteCallerID"); - this->globalObject().property("Script").setProperty("remoteCallerID", remoteCallerID.toString()); // Make the remoteCallerID available to javascript as a global. - callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); - this->globalObject().property("Script").setProperty("remoteCallerID", oldData); - } - } -} - -void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; -#endif - - QMetaObject::invokeMethod(this, "callEntityScriptMethod", - Q_ARG(const EntityItemID&, entityID), - Q_ARG(const QString&, methodName), - Q_ARG(const PointerEvent&, event)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent"; -#endif - - if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { - refreshFileScript(entityID); - } - if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details; - { - QWriteLocker locker { &_entityScriptsLock }; - details = _entityScripts[entityID]; - } - QScriptValue entityScript = details.scriptObject; // previously loaded - if (entityScript.property(methodName).isFunction()) { - QScriptValueList args; - args << entityID.toScriptValue(this); - args << event.toScriptValue(this); - callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); - } - } -} - -void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { - if (QThread::currentThread() != thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; -#endif - - 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; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] " - "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; -#endif - - if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { - refreshFileScript(entityID); - } - if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details; - { - QWriteLocker locker { &_entityScriptsLock }; - 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); - callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); - } - } -} - -QString ScriptEngine::getExternalPath(ExternalResource::Bucket bucket, const QString& path) { - return ExternalResource::getInstance()->getUrl(bucket, path); -} diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 0933025ee18..9708babf831 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -16,1006 +16,160 @@ #ifndef hifi_ScriptEngine_h #define hifi_ScriptEngine_h -#include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ScriptValue.h" -#include -#include -#include -#include -#include -#include -#include -#include +class QByteArray; +class QLatin1String; +class QString; +class QThread; +class QVariant; +class ScriptContext; +class ScriptEngine; +class ScriptManager; +class ScriptProgram; +using ScriptEnginePointer = std::shared_ptr; +using ScriptProgramPointer = std::shared_ptr; -#include "PointerEvent.h" -#include "ArrayBufferClass.h" -#include "AssetScriptingInterface.h" -#include "AudioScriptingInterface.h" -#include "BaseScriptEngine.h" -#include "ExternalResource.h" -#include "Quat.h" -#include "Mat4.h" -#include "ScriptCache.h" -#include "ScriptUUID.h" -#include "Vec3.h" -#include "ConsoleScriptingInterface.h" -#include "SettingHandle.h" -#include "Profile.h" +Q_DECLARE_METATYPE(ScriptEnginePointer); -static const QString NO_SCRIPT(""); +template +inline ScriptValue scriptValueFromValue(ScriptEngine* engine, const T& t); -static const int SCRIPT_FPS = 60; -static const int DEFAULT_MAX_ENTITY_PPS = 9000; -static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; +template +inline T scriptvalue_cast(const ScriptValue& value); -class ScriptEngines; -Q_DECLARE_METATYPE(ScriptEnginePointer) - -class CallbackData { -public: - QScriptValue function; - EntityItemID definingEntityIdentifier; - QUrl definingSandboxURL; -}; - -class DeferredLoadEntity { -public: - EntityItemID entityID; - QString entityScript; - //bool forceRedownload; -}; - -struct EntityScriptContentAvailable { - EntityItemID entityID; - QString scriptOrURL; - QString contents; - bool isURL; - bool success; - QString status; -}; - -typedef std::unordered_map EntityScriptContentAvailableMap; - -typedef QList CallbackList; -typedef QHash RegisteredEventHandlers; - -class EntityScriptDetails { -public: - EntityScriptStatus status { EntityScriptStatus::PENDING }; - - // If status indicates an error, this contains a human-readable string giving more information about the error. - QString errorInfo { "" }; - - QString scriptText { "" }; - QScriptValue scriptObject { QScriptValue() }; - int64_t lastModified { 0 }; - QUrl definingSandboxURL { QUrl("about:EntityScript") }; -}; - -/*@jsdoc - * The Script API provides facilities for working with scripts. - * - * @namespace Script - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - * @hifi-server-entity - * @hifi-assignment-client - * - * @property {string} context - The context that the script is running in: - *
    - *
  • "client": An Interface or avatar script.
  • - *
  • "entity_client": A client entity script.
  • - *
  • "entity_server": A server entity script.
  • - *
  • "agent": An assignment client script.
  • - *
- * Read-only. - * @property {string} type - The type of script that is running: - *
    - *
  • "client": An Interface script.
  • - *
  • "entity_client": A client entity script.
  • - *
  • "avatar": An avatar script.
  • - *
  • "entity_server": A server entity script.
  • - *
  • "agent": An assignment client script.
  • - *
- * Read-only. - * @property {string} filename - The filename of the script file. - * Read-only. - * @property {Script.ResourceBuckets} ExternalPaths - External resource buckets. - */ -/// The main class managing a scripting engine. Also provides the Script scripting interface -class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { - Q_OBJECT - Q_PROPERTY(QString context READ getContext) - Q_PROPERTY(QString type READ getTypeAsString) - Q_PROPERTY(QString fileName MEMBER _fileNameString CONSTANT) +/// [ScriptInterface] Provides an engine-independent interface for QScriptEngine +class ScriptEngine { public: - - enum Context { - CLIENT_SCRIPT, - ENTITY_CLIENT_SCRIPT, - ENTITY_SERVER_SCRIPT, - AGENT_SCRIPT + typedef ScriptValue (*FunctionSignature)(ScriptContext*, ScriptEngine*); + typedef ScriptValue (*MarshalFunction)(ScriptEngine*, const void*); + typedef bool (*DemarshalFunction)(const ScriptValue&, void*); + + enum ValueOwnership { + QtOwnership = 0, + ScriptOwnership = 1, + AutoOwnership = 2, }; - enum Type { - CLIENT, - ENTITY_CLIENT, - ENTITY_SERVER, - AGENT, - AVATAR + enum QObjectWrapOption { + //ExcludeChildObjects = 0x0001, // The script object will not expose child objects as properties. + ExcludeSuperClassMethods = 0x0002, // The script object will not expose signals and slots inherited from the superclass. + ExcludeSuperClassProperties = 0x0004, // The script object will not expose properties inherited from the superclass. + ExcludeSuperClassContents = ExcludeSuperClassMethods | ExcludeSuperClassProperties, + //ExcludeDeleteLater = 0x0010, // The script object will not expose the QObject::deleteLater() slot. + ExcludeSlots = 0x0020, // The script object will not expose the QObject's slots. + AutoCreateDynamicProperties = 0x0100, // Properties that don't already exist in the QObject will be created as dynamic properties of that object, rather than as properties of the script object. + PreferExistingWrapperObject = 0x0200, // If a wrapper object with the requested configuration already exists, return that object. + SkipMethodsInEnumeration = 0x0008, // Don't include methods (signals and slots) when enumerating the object's properties. }; - Q_ENUM(Type) - - static int processLevelMaxRetries; - ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); - ~ScriptEngine(); - - /// 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(); - - QString getFilename() const; - - QList getListOfEntityScriptIDs(); - - /*@jsdoc - * Stops and unloads the current script. - *

Warning: If an assignment client script, the script gets restarted after stopping.

- * @function Script.stop - * @param {boolean} [marshal=false] - Marshal. - *

Deprecated: This parameter is deprecated and will be removed.

- * @example Stop a script after 5s. - * Script.setInterval(function () { - * print("Hello"); - * }, 1000); - * - * Script.setTimeout(function () { - * Script.stop(true); - * }, 5000); - */ - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(bool marshal = false); - - // Stop any evaluating scripts and wait for the scripting thread to finish. - void waitTillDoneRunning(bool shutdown = false); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // 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 - - /*@jsdoc - * @function Script.registerGlobalObject - * @param {string} name - Name. - * @param {object} object - Object. - * @deprecated This function is deprecated and will be removed. - */ - /// registers a global object by name - Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); - - /*@jsdoc - * @function Script.registerGetterSetter - * @param {string} name - Name. - * @param {function} getter - Getter. - * @param {function} setter - Setter. - * @param {string} [parent=""] - Parent. - * @deprecated This function is deprecated and will be removed. - */ - /// registers a global getter/setter - Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); - - /*@jsdoc - * @function Script.registerFunction - * @param {string} name - Name. - * @param {function} function - Function. - * @param {number} [numArguments=-1] - Number of arguments. - * @deprecated This function is deprecated and will be removed. - */ - /// register a global function - Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); - - /*@jsdoc - * @function Script.registerFunction - * @param {string} parent - Parent. - * @param {string} name - Name. - * @param {function} function - Function. - * @param {number} [numArguments=-1] - Number of arguments. - * @deprecated This function is deprecated and will be removed. - */ - /// 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); - - /*@jsdoc - * @function Script.registerEnum - * @param {string} name - Name. - * @param {object} enum - Enum. - * @deprecated This function is deprecated and will be removed. - */ - // WARNING: This function must be called after a registerGlobalObject that creates the namespace this enum is located in, or - // the globalObject won't function. E.g., if you have a Foo object and a Foo.FooType enum, Foo must be registered first. - /// registers a global enum - Q_INVOKABLE void registerEnum(const QString& enumName, QMetaEnum newEnum); - - /*@jsdoc - * @function Script.registerValue - * @param {string} name - Name. - * @param {object} value - Value. - * @deprecated This function is deprecated and will be removed. - */ - /// registers a global object by name - Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); + Q_DECLARE_FLAGS(QObjectWrapOptions, QObjectWrapOption); - /*@jsdoc - * @function Script.evaluate - * @param {string} program - Program. - * @param {string} filename - File name. - * @param {number} [lineNumber=-1] - Line number. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - /// evaluate some code in the context of the ScriptEngine and return the result - Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget - - /*@jsdoc - * @function Script.evaluateInClosure - * @param {object} locals - Locals. - * @param {object} program - Program. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); - - /// 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); - bool hasValidScriptSuffix(const QString& scriptFileName); - - /*@jsdoc - * Gets the context that the script is running in: Interface/avatar, client entity, server entity, or assignment client. - * @function Script.getContext - * @returns {string} The context that the script is running in: - *
    - *
  • "client": An Interface or avatar script.
  • - *
  • "entity_client": A client entity script.
  • - *
  • "entity_server": A server entity script.
  • - *
  • "agent": An assignment client script.
  • - *
- */ - Q_INVOKABLE QString getContext() const; - - /*@jsdoc - * Checks whether the script is running as an Interface or avatar script. - * @function Script.isClientScript - * @returns {boolean} true if the script is running as an Interface or avatar script, false if it - * isn't. - */ - Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } - - /*@jsdoc - * Checks whether the application was compiled as a debug build. - * @function Script.isDebugMode - * @returns {boolean} true if the application was compiled as a debug build, false if it was - * compiled as a release build. - */ - Q_INVOKABLE bool isDebugMode() const; - - /*@jsdoc - * Checks whether the script is running as a client entity script. - * @function Script.isEntityClientScript - * @returns {boolean} true if the script is running as a client entity script, false if it isn't. - */ - Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } - - /*@jsdoc - * Checks whether the script is running as a server entity script. - * @function Script.isEntityServerScript - * @returns {boolean} true if the script is running as a server entity script, false if it isn't. - */ - Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } - - /*@jsdoc - * Checks whether the script is running as an assignment client script. - * @function Script.isAgentScript - * @returns {boolean} true if the script is running as an assignment client script, false if it - * isn't. - */ - Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // NOTE - these are intended to be public interfaces available to scripts - - /*@jsdoc - * Adds a function to the list of functions called when a particular event occurs on a particular entity. - *

See also, the {@link Entities} API.

- * @function Script.addEventHandler - * @param {Uuid} entityID - The ID of the entity. - * @param {Script.EntityEvent} eventName - The name of the event. - * @param {Script~entityEventCallback|Script~pointerEventCallback|Script~collisionEventCallback} handler - The function to - * call when the event occurs on the entity. It can be either the name of a function or an in-line definition. - * @example Report when a mouse press occurs on a particular entity. - * var entityID = Entities.addEntity({ - * type: "Box", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), - * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, - * lifetime: 300 // Delete after 5 minutes. - * }); - * - * function reportMousePress(entityID, event) { - * print("Mouse pressed on entity: " + JSON.stringify(event)); - * } - * - * Script.addEventHandler(entityID, "mousePressOnEntity", reportMousePress); - */ - Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); - - /*@jsdoc - * Removes a function from the list of functions called when an entity event occurs on a particular entity. - *

See also, the {@link Entities} API.

- * @function Script.removeEventHandler - * @param {Uuid} entityID - The ID of the entity. - * @param {Script.EntityEvent} eventName - The name of the entity event. - * @param {function} handler - The name of the function to no longer call when the entity event occurs on the entity. - */ - Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); - - /*@jsdoc - * Starts running another script in Interface, if it isn't already running. The script is not automatically loaded next - * time Interface starts. - *

Supported Script Types: Interface Scripts • Avatar Scripts

- *

See also, {@link ScriptDiscoveryService.loadScript}.

- * @function Script.load - * @param {string} filename - The URL of the script to load. This can be relative to the current script's URL. - * @example Load a script from another script. - * // First file: scriptA.js - * print("This is script A"); - * - * // Second file: scriptB.js - * print("This is script B"); - * Script.load("scriptA.js"); - * - * // If you run scriptB.js you should see both scripts in the Running Scripts dialog. - * // And you should see the following output: - * // This is script B - * // This is script A - */ - Q_INVOKABLE void load(const QString& loadfile); - - /*@jsdoc - * Includes JavaScript from other files in the current script. If a callback is specified, the files are loaded and - * included asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are - * included). - * @function Script.include - * @variation 0 - * @param {string[]} filenames - The URLs of the scripts to include. Each can be relative to the current script. - * @param {function} [callback=null] - The function to call back when the scripts have been included. It can be either the - * name of a function or an in-line definition. - */ - Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); - - /*@jsdoc - * Includes JavaScript from another file in the current script. If a callback is specified, the file is loaded and included - * asynchronously, otherwise it is included synchronously (i.e., script execution blocks while the file is included). - * @function Script.include - * @param {string} filename - The URL of the script to include. It can be relative to the current script. - * @param {function} [callback=null] - The function to call back when the script has been included. It can be either the - * name of a function or an in-line definition. - * @example Include a script file asynchronously. - * // First file: scriptA.js - * print("This is script A"); - * - * // Second file: scriptB.js - * print("This is script B"); - * Script.include("scriptA.js", function () { - * print("Script A has been included"); - * }); - * - * // If you run scriptB.js you should see only scriptB.js in the running scripts list. - * // And you should see the following output: - * // This is script B - * // This is script A - * // Script A has been included - */ - Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue()); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // MODULE related methods - - /*@jsdoc - * Provides access to methods or objects provided in an external JavaScript or JSON file. - * See {@link https://docs.vircadia.com/script/js-tips.html} for further details. - * @function Script.require - * @param {string} module - The module to use. May be a JavaScript file, a JSON file, or the name of a system module such - * as "appUi" (i.e., the "appUi.js" system module JavaScript file). - * @returns {object|array} The value assigned to module.exports in the JavaScript file, or the value defined - * in the JSON file. - */ - Q_INVOKABLE QScriptValue require(const QString& moduleId); - - /*@jsdoc - * @function Script.resetModuleCache - * @param {boolean} [deleteScriptCache=false] - Delete script cache. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); - - QScriptValue currentModule(); - bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent); - QScriptValue newModule(const QString& modulePath, const QScriptValue& parent = QScriptValue()); - QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); - QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode); - - /*@jsdoc - * Calls a function repeatedly, at a set interval. - * @function Script.setInterval - * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. - * @param {number} interval - The interval at which to call the function, in ms. - * @returns {object} A handle to the interval timer. This can be used in {@link Script.clearInterval}. - * @example Print a message every second. - * Script.setInterval(function () { - * print("Interval timer fired"); - * }, 1000); - */ - Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); - - /*@jsdoc - * Calls a function once, after a delay. - * @function Script.setTimeout - * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. - * @param {number} timeout - The delay after which to call the function, in ms. - * @returns {object} A handle to the timeout timer. This can be used in {@link Script.clearTimeout}. - * @example Print a message once, after a second. - * Script.setTimeout(function () { - * print("Timeout timer fired"); - * }, 1000); - */ - Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); - - /*@jsdoc - * Stops an interval timer set by {@link Script.setInterval|setInterval}. - * @function Script.clearInterval - * @param {object} timer - The interval timer to stop. - * @example Stop an interval timer. - * // Print a message every second. - * var timer = Script.setInterval(function () { - * print("Interval timer fired"); - * }, 1000); - * - * // Stop the timer after 10 seconds. - * Script.setTimeout(function () { - * print("Stop interval timer"); - * Script.clearInterval(timer); - * }, 10000); - */ - Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } - - /*@jsdoc - * Stops a timeout timer set by {@link Script.setTimeout|setTimeout}. - * @function Script.clearTimeout - * @param {object} timer - The timeout timer to stop. - * @example Stop a timeout timer. - * // Print a message after two seconds. - * var timer = Script.setTimeout(function () { - * print("Timer fired"); - * }, 2000); - * - * // Uncomment the following line to stop the timer from firing. - * //Script.clearTimeout(timer); - */ - Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } - - /*@jsdoc - * Prints a message to the program log and emits {@link Script.printedMessage}. - *

Alternatively, you can use {@link print} or one of the {@link console} API methods.

- * @function Script.print - * @param {string} message - The message to print. - */ - Q_INVOKABLE void print(const QString& message); - - /*@jsdoc - * Resolves a relative path to an absolute path. The relative path is relative to the script's location. - * @function Script.resolvePath - * @param {string} path - The relative path to resolve. - * @returns {string} The absolute path. - * @example Report the directory and filename of the running script. - * print(Script.resolvePath("")); - * @example Report the directory of the running script. - * print(Script.resolvePath(".")); - * @example Report the path to a file located relative to the running script. - * print(Script.resolvePath("../assets/sounds/hello.wav")); - */ - Q_INVOKABLE QUrl resolvePath(const QString& path) const; - - /*@jsdoc - * Gets the path to the resources directory for QML files. - * @function Script.resourcesPath - * @returns {string} The path to the resources directory for QML files. - */ - Q_INVOKABLE QUrl resourcesPath() const; - - /*@jsdoc - * Starts timing a section of code in order to send usage data about it to Vircadia. Shouldn't be used outside of the - * standard scripts. - * @function Script.beginProfileRange - * @param {string} label - A name that identifies the section of code. - */ - Q_INVOKABLE void beginProfileRange(const QString& label) const; - - /*@jsdoc - * Finishes timing a section of code in order to send usage data about it to Vircadia. Shouldn't be used outside of - * the standard scripts. - * @function Script.endProfileRange - * @param {string} label - A name that identifies the section of code. - */ - Q_INVOKABLE void endProfileRange(const QString& label) const; +public: + virtual void abortEvaluation() = 0; + virtual void clearExceptions() = 0; + virtual ScriptValue cloneUncaughtException(const QString& detail = QString()) = 0; + virtual ScriptContext* currentContext() const = 0; + virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) = 0; + virtual ScriptValue evaluate(const ScriptProgramPointer &program) = 0; + virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) = 0; + virtual ScriptValue globalObject() const = 0; + virtual bool hasUncaughtException() const = 0; + virtual bool isEvaluating() const = 0; + virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) = 0; + virtual ScriptValue makeError(const ScriptValue& other = ScriptValue(), const QString& type = "Error") = 0; + virtual ScriptManager* manager() const = 0; + virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()) = 0; + virtual ScriptValue newArray(uint length = 0) = 0; + virtual ScriptValue newArrayBuffer(const QByteArray& message) = 0; + virtual ScriptValue newFunction(FunctionSignature fun, int length = 0) = 0; + virtual ScriptValue newObject() = 0; + virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) = 0; + virtual ScriptValue newQObject(QObject *object, ValueOwnership ownership = QtOwnership, const QObjectWrapOptions &options = QObjectWrapOptions()) = 0; + virtual ScriptValue newValue(bool value) = 0; + virtual ScriptValue newValue(int value) = 0; + virtual ScriptValue newValue(uint value) = 0; + virtual ScriptValue newValue(double value) = 0; + virtual ScriptValue newValue(const QString& value) = 0; + virtual ScriptValue newValue(const QLatin1String& value) = 0; + virtual ScriptValue newValue(const char* value) = 0; + virtual ScriptValue newVariant(const QVariant& value) = 0; + virtual ScriptValue nullValue() = 0; + virtual bool raiseException(const ScriptValue& exception) = 0; + virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) = 0; + virtual void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1) = 0; + virtual void registerFunction(const QString& parent, const QString& name, FunctionSignature fun, int numArguments = -1) = 0; + virtual void registerGetterSetter(const QString& name, FunctionSignature getter, FunctionSignature setter, const QString& parent = QString("")) = 0; + virtual void registerGlobalObject(const QString& name, QObject* object) = 0; + virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) = 0; + virtual void setObjectName(const QString& name) = 0; + virtual bool setProperty(const char* name, const QVariant& value) = 0; + virtual void setProcessEventsInterval(int interval) = 0; + virtual QThread* thread() const = 0; + virtual void setThread(QThread* thread) = 0; + virtual ScriptValue undefinedValue() = 0; + virtual ScriptValue uncaughtException() const = 0; + virtual QStringList uncaughtExceptionBacktrace() const = 0; + virtual int uncaughtExceptionLineNumber() const = 0; + virtual void updateMemoryCost(const qint64& deltaSize) = 0; + virtual void requestCollectGarbage() = 0; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Entity Script Related methods +public: + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + bool IS_THREADSAFE_INVOCATION(const QString& method); - /*@jsdoc - * Checks whether an entity has an entity script running. - * @function Script.isEntityScriptRunning - * @param {Uuid} entityID - The ID of the entity. - * @returns {boolean} true if the entity has an entity script running, false if it doesn't. - */ - Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { - QReadLocker locker { &_entityScriptsLock }; - auto it = _entityScripts.constFind(entityID); - return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; +public: + template + inline T fromScriptValue(const ScriptValue& value) { + return scriptvalue_cast(value); } - QVariant cloneEntityScriptDetails(const EntityItemID& entityID); - QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; - /*@jsdoc - * @function Script.loadEntityScript - * @param {Uuid} entityID - Entity ID. - * @param {string} script - Script. - * @param {boolean} forceRedownload - Force re-download. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); - - /*@jsdoc - * @function Script.unloadEntityScript - * @param {Uuid} entityID - Entity ID. - * @param {boolean} [shouldRemoveFromMap=false] - Should remove from map. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method - - /*@jsdoc - * @function Script.unloadAllEntityScripts - * @param {boolean} [blockingCall=false] - Wait for completion if call moved to another thread. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void unloadAllEntityScripts(bool blockingCall = false); - - /*@jsdoc - * Calls a method in an entity script. - * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - The ID of the entity running the entity script. - * @param {string} methodName - The name of the method to call. - * @param {string[]} [parameters=[]] - The parameters to call the specified method with. - * @param {Uuid} [remoteCallerID=Uuid.NULL] - An ID that identifies the caller. - */ - Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, - const QStringList& params = QStringList(), - const QUuid& remoteCallerID = QUuid()) override; - - /*@jsdoc - * Calls a method in an entity script. - * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - Entity ID. - * @param {string} methodName - Method name. - * @param {PointerEvent} event - Pointer event. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); - - /*@jsdoc - * Calls a method in an entity script. - * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - Entity ID. - * @param {string} methodName - Method name. - * @param {Uuid} otherID - Other entity ID. - * @param {Collision} collision - Collision. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); - - /*@jsdoc - * Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer - * reachable. - * @function Script.requestGarbageCollection - */ - Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } - - /*@jsdoc - * @function Script.generateUUID - * @returns {Uuid} A new UUID. - * @deprecated This function is deprecated and will be removed. Use {@link Uuid(0).generate|Uuid.generate} instead. - */ - Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } - - void setType(Type type) { _type = type; }; - Type getType() { return _type; }; - QString getTypeAsString() const; - - bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget - bool isRunning() const { return _isRunning; } // used by ScriptWidget - - // this is used by code in ScriptEngines.cpp during the "reload all" operation - bool isStopping() const { return _isStopping; } - - void disconnectNonEssentialSignals(); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // 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; } - - void setQuitWhenFinished(const bool quitWhenFinished) { _quitWhenFinished = quitWhenFinished; } - bool isQuitWhenFinished() const { return _quitWhenFinished; } - - // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety - ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } - - void setEmitScriptUpdatesFunction(std::function func) { _emitScriptUpdates = func; } - - void scriptErrorMessage(const QString& message); - void scriptWarningMessage(const QString& message); - void scriptInfoMessage(const QString& message); - void scriptPrintedMessage(const QString& message); - void clearDebugLogWindow(); - int getNumRunningEntityScripts() const; - bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; - bool hasEntityScriptDetails(const EntityItemID& entityID) const; - - void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } - - /*@jsdoc - * Gets the URL for an asset in an external resource bucket. (The location where the bucket is hosted may change over time - * but this method will return the asset's current URL.) - * @function Script.getExternalPath - * @param {Script.ResourceBucket} bucket - The external resource bucket that the asset is in. - * @param {string} path - The path within the external resource bucket where the asset is located. - *

Normally, this should start with a path or filename to be appended to the bucket URL. - * Alternatively, it can be a relative path starting with ./ or ../, to navigate within the - * resource bucket's URL.

- * @Returns {string} The URL of an external asset. - * @example Report the URL of a default particle. - * print(Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png")); - * @example Report the root directory where the Vircadia assets are located. - * print(Script.getExternalPath(Script.ExternalPaths.Assets, ".")); - */ - Q_INVOKABLE QString getExternalPath(ExternalResource::Bucket bucket, const QString& path); - -public slots: - - /*@jsdoc - * @function Script.callAnimationStateHandler - * @param {function} callback - Callback function. - * @param {object} parameters - Parameters. - * @param {string[]} names - Names. - * @param {boolean} useNames - Use names. - * @param {function} resultHandler - Result handler. - * @deprecated This function is deprecated and will be removed. - */ - void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); - - /*@jsdoc - * @function Script.updateMemoryCost - * @param {number} deltaSize - Delta size. - * @deprecated This function is deprecated and will be removed. - */ - void updateMemoryCost(const qint64&); - -signals: - - /*@jsdoc - * @function Script.scriptLoaded - * @param {string} filename - File name. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void scriptLoaded(const QString& scriptFilename); - - /*@jsdoc - * @function Script.errorLoadingScript - * @param {string} filename - File name. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void errorLoadingScript(const QString& scriptFilename); - - /*@jsdoc - * Triggered frequently at a system-determined interval. - * @function Script.update - * @param {number} deltaTime - The time since the last update, in s. - * @returns {Signal} - * @example Report script update intervals. - * Script.update.connect(function (deltaTime) { - * print("Update: " + deltaTime); - * }); - */ - void update(float deltaTime); - - /*@jsdoc - * Triggered when the script is stopping. - * @function Script.scriptEnding - * @returns {Signal} - * @example Report when a script is stopping. - * print("Script started"); - * - * Script.scriptEnding.connect(function () { - * print("Script ending"); - * }); - * - * Script.setTimeout(function () { - * print("Stopping script"); - * Script.stop(); - * }, 1000); - */ - void scriptEnding(); - - /*@jsdoc - * @function Script.finished - * @param {string} filename - File name. - * @param {object} engine - Engine. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void finished(const QString& fileNameString, ScriptEnginePointer); - - /*@jsdoc - * @function Script.cleanupMenuItem - * @param {string} menuItem - Menu item. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void cleanupMenuItem(const QString& menuItemString); - - /*@jsdoc - * Triggered when the script prints a message to the program log via {@link print}, {@link Script.print}, - * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or - * {@link console.timeEnd}. - * @function Script.printedMessage - * @param {string} message - The message. - * @param {string} scriptName - The name of the script that generated the message. - * @returns {Signal} - */ - void printedMessage(const QString& message, const QString& scriptName); - - /*@jsdoc - * Triggered when the script generates an error, {@link console.error} or {@link console.exception} is called, or - * {@link console.assert} is called and fails. - * @function Script.errorMessage - * @param {string} message - The error message. - * @param {string} scriptName - The name of the script that generated the error message. - * @returns {Signal} - */ - void errorMessage(const QString& message, const QString& scriptName); - - /*@jsdoc - * Triggered when the script generates a warning or {@link console.warn} is called. - * @function Script.warningMessage - * @param {string} message - The warning message. - * @param {string} scriptName - The name of the script that generated the warning message. - * @returns {Signal} - */ - void warningMessage(const QString& message, const QString& scriptName); - - /*@jsdoc - * Triggered when the script generates an information message or {@link console.info} is called. - * @function Script.infoMessage - * @param {string} message - The information message. - * @param {string} scriptName - The name of the script that generated the information message. - * @returns {Signal} - */ - void infoMessage(const QString& message, const QString& scriptName); - - /*@jsdoc - * Triggered when the running state of the script changes, e.g., from running to stopping. - * @function Script.runningStateChanged - * @returns {Signal} - */ - void runningStateChanged(); - - /*@jsdoc - * @function Script.clearDebugWindow - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void clearDebugWindow(); - - /*@jsdoc - * @function Script.loadScript - * @param {string} scriptName - Script name. - * @param {boolean} isUserLoaded - Is user loaded. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void loadScript(const QString& scriptName, bool isUserLoaded); - - /*@jsdoc - * @function Script.reloadScript - * @param {string} scriptName - Script name. - * @param {boolean} isUserLoaded - Is user loaded. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - void reloadScript(const QString& scriptName, bool isUserLoaded); - - /*@jsdoc - * Triggered when the script has stopped. - * @function Script.doneRunning - * @returns {Signal} - */ - void doneRunning(); - - /*@jsdoc - * @function Script.entityScriptDetailsUpdated - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - // Emitted when an entity script is added or removed, or when the status of an entity - // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) - void entityScriptDetailsUpdated(); + template + inline ScriptValue toScriptValue(const T& value) { + return scriptValueFromValue(this, value); + } - /*@jsdoc - * Triggered when the script starts for the user. See also, {@link Entities.preload}. - *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

- * @function Script.entityScriptPreloadFinished - * @param {Uuid} entityID - The ID of the entity that the script is running in. - * @returns {Signal} - * @example Get the ID of the entity that a client entity script is running in. - * var entityScript = function () { - * this.entityID = Uuid.NULL; - * }; - * - * Script.entityScriptPreloadFinished.connect(function (entityID) { - * this.entityID = entityID; - * print("Entity ID: " + this.entityID); - * }); - * - * var entityID = Entities.addEntity({ - * type: "Box", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), - * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, - * color: { red: 255, green: 0, blue: 0 }, - * script: "(" + entityScript + ")", // Could host the script on a Web server instead. - * lifetime: 300 // Delete after 5 minutes. - * }); - */ - // Emitted when an entity script has finished running preload - void entityScriptPreloadFinished(const EntityItemID& entityID); +public: // not for public use, but I don't like how Qt strings this along with private friend functions + virtual ScriptValue create(int type, const void* ptr) = 0; + virtual QVariant convert(const ScriptValue& value, int type) = 0; + virtual void registerCustomType(int type, MarshalFunction mf, DemarshalFunction df) = 0; protected: - void init(); - - /*@jsdoc - * @function Script.executeOnScriptThread - * @param {function} function - Function. - * @param {ConnectionType} [type=2] - Connection type. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); - - /*@jsdoc - * @function Script._requireResolve - * @param {string} module - Module. - * @param {string} [relativeTo=""] - Relative to. - * @returns {string} Result. - * @deprecated This function is deprecated and will be removed. - */ - // note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general; - // then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;" - Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString()); - - QString logException(const QScriptValue& exception); - void timerFired(); - void stopAllTimers(); - void stopAllTimersForEntityScript(const EntityItemID& entityID); - void refreshFileScript(const EntityItemID& entityID); - void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString()); - void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details); - void setParentURL(const QString& parentURL) { _parentURL = parentURL; } - - QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); - void stopTimer(QTimer* timer); - - QHash _registeredHandlers; - void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); - - /*@jsdoc - * @function Script.entityScriptContentAvailable - * @param {Uuid} entityID - Entity ID. - * @param {string} scriptOrURL - Path. - * @param {string} contents - Contents. - * @param {boolean} isURL - Is a URL. - * @param {boolean} success - Success. - * @param {string} status - Status. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status); - - EntityItemID currentEntityIdentifier; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. - QUrl currentSandboxURL; // The toplevel url string for the entity script that loaded the code being executed, else empty. - void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); - void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); - - Context _context; - Type _type; - QString _scriptContents; - QString _parentURL; - std::atomic _isFinished { false }; - std::atomic _isRunning { false }; - std::atomic _isStopping { false }; - bool _isInitialized { false }; - QHash _timerFunctionMap; - QSet _includedURLs; - mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; - QHash _entityScripts; - EntityScriptContentAvailableMap _contentAvailableQueue; - - bool _isThreaded { false }; - qint64 _lastUpdate; - - QString _fileNameString; - Quat _quatLibrary; - Vec3 _vec3Library; - Mat4 _mat4Library; - ScriptUUID _uuidLibrary; - ConsoleScriptingInterface _consoleScriptingInterface; - std::atomic _isUserLoaded { false }; - bool _isReloading { false }; - - std::atomic _quitWhenFinished; - - ArrayBufferClass* _arrayBufferClass; - - AssetScriptingInterface* _assetScriptingInterface; - - std::function _emitScriptUpdates{ []() { return true; } }; - - std::recursive_mutex _lock; - - std::chrono::microseconds _totalTimerExecution { 0 }; - - static const QString _SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT; - static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; - - Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; - - QWeakPointer _scriptEngines; + ~ScriptEngine() {} // prevent explicit deletion of base class }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ScriptEngine::QObjectWrapOptions); -ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, - const QString& scriptContents, - const QString& fileNameString); +ScriptEnginePointer newScriptEngine(ScriptManager* manager = nullptr); + +// Standardized CPS callback helpers (see: http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/) +// These two helpers allow async JS APIs that use a callback parameter to be more friendly to scripters by accepting thisObject +// context and adopting a consistent and intuitable callback signature: +// function callback(err, result) { if (err) { ... } else { /* do stuff with result */ } } +// +// To use, first pass the user-specified callback args in the same order used with optionally-scoped Qt signal connections: +// auto handler = makeScopedHandlerObject(scopeOrCallback, optionalMethodOrName); +// And then invoke the scoped handler later per CPS conventions: +// auto result = callScopedHandlerObject(handler, err, result); +ScriptValue makeScopedHandlerObject(const ScriptValue& scopeOrCallback, const ScriptValue& methodOrName); +ScriptValue callScopedHandlerObject(const ScriptValue& handler, const ScriptValue& err, const ScriptValue& result); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Inline implementations +/* +QThread* ScriptEngine::thread() const { + QObject* qobject = toQObject(); + if (qobject == nullptr) { + return nullptr; + } + return qobject->thread(); +} +*/ -#endif // hifi_ScriptEngine_h +#endif // hifi_ScriptEngine_h /// @} diff --git a/libraries/script-engine/src/ScriptEngineCast.h b/libraries/script-engine/src/ScriptEngineCast.h new file mode 100644 index 00000000000..7ab6b02f38d --- /dev/null +++ b/libraries/script-engine/src/ScriptEngineCast.h @@ -0,0 +1,109 @@ +// +// ScriptEngineCast.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/9/2021. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptEngineCast_h +#define hifi_ScriptEngineCast_h + +// Object conversion helpers (copied from QScriptEngine) + +#include + +#include "ScriptEngine.h" +#include "ScriptValue.h" + +template +inline ScriptValue scriptValueFromValue(ScriptEngine* engine, const T& t) { + if (!engine) { + return ScriptValue(); + } + + return engine->create(qMetaTypeId(), &t); +} + +template <> +inline ScriptValue scriptValueFromValue(ScriptEngine* engine, const QVariant& v) { + if (!engine) { + return ScriptValue(); + } + + return engine->create(v.userType(), v.data()); +} + +template +inline T scriptvalue_cast(const ScriptValue& value) { + const int id = qMetaTypeId(); + + auto engine = value.engine(); + if (engine) { + QVariant varValue = engine->convert(value, id); + if (varValue.isValid()) { + return varValue.value(); + } + } + if (value.isVariant()) { + return qvariant_cast(value.toVariant()); + } + + return T(); +} + +template <> +inline QVariant scriptvalue_cast(const ScriptValue& value) { + return value.toVariant(); +} + +template +int scriptRegisterMetaType(ScriptEngine* eng, + ScriptValue (*toScriptValue)(ScriptEngine*, const T& t), + bool (*fromScriptValue)(const ScriptValue&, T& t), + T* = 0) +{ + const int id = qRegisterMetaType(); // make sure it's registered + eng->registerCustomType(id, reinterpret_cast(toScriptValue), + reinterpret_cast(fromScriptValue)); + return id; +} + +template +ScriptValue scriptValueFromSequence(ScriptEngine* eng, const Container& cont) { + ScriptValue a = eng->newArray(); + typename Container::const_iterator begin = cont.begin(); + typename Container::const_iterator end = cont.end(); + typename Container::const_iterator it; + quint32 i; + for (it = begin, i = 0; it != end; ++it, ++i) { + a.setProperty(i, eng->toScriptValue(*it)); + } + return a; +} + +template +bool scriptValueToSequence(const ScriptValue& value, Container& cont) { + quint32 len = value.property(QLatin1String("length")).toUInt32(); + for (quint32 i = 0; i < len; ++i) { + ScriptValue item = value.property(i); + cont.push_back(scriptvalue_cast(item)); + } + return true; +} + +template +int scriptRegisterSequenceMetaType(ScriptEngine* engine, + T* = 0) { + return scriptRegisterMetaType(engine, scriptValueFromSequence, scriptValueToSequence); +} + +#endif // hifi_ScriptEngineCast_h + +/// @} diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 0e62297905f..ee6e6ffc2f8 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -19,6 +20,7 @@ #include #include +#include "ScriptCache.h" #include "ScriptEngine.h" #include "ScriptEngineLogging.h" @@ -65,7 +67,7 @@ void ScriptEngines::onErrorLoadingScript(const QString& url) { emit errorLoadingScript(url); } -ScriptEngines::ScriptEngines(ScriptEngine::Context context, const QUrl& defaultScriptsOverride) +ScriptEngines::ScriptEngines(ScriptManager::Context context, const QUrl& defaultScriptsOverride) : _context(context), _defaultScriptsOverride(defaultScriptsOverride) { scriptGatekeeper.initialize(); @@ -138,53 +140,53 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { QObject* scriptsModel(); -void ScriptEngines::addScriptEngine(ScriptEnginePointer engine) { +void ScriptEngines::addScriptEngine(ScriptManagerPointer manager) { if (!_isStopped) { QMutexLocker locker(&_allScriptsMutex); - _allKnownScriptEngines.insert(engine); + _allKnownScriptManagers.insert(manager); } } -void ScriptEngines::removeScriptEngine(ScriptEnginePointer engine) { +void ScriptEngines::removeScriptEngine(ScriptManagerPointer manager) { // 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 if (!_isStopped) { QMutexLocker locker(&_allScriptsMutex); - _allKnownScriptEngines.remove(engine); + _allKnownScriptManagers.remove(manager); } } void ScriptEngines::shutdownScripting() { _isStopped = true; QMutexLocker locker(&_allScriptsMutex); - qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size(); + qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptManagers.size(); - QMutableSetIterator i(_allKnownScriptEngines); + QMutableSetIterator i(_allKnownScriptManagers); while (i.hasNext()) { - ScriptEnginePointer scriptEngine = i.next(); - QString scriptName = scriptEngine->getFilename(); + ScriptManagerPointer scriptManager = i.next(); + QString scriptName = scriptManager->getFilename(); // NOTE: typically all script engines are running. But there's at least one known exception to this, the // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using // them. We don't need to stop scripts that aren't running. // TODO: Scripts could be shut down faster if we spread them across a threadpool. - if (scriptEngine->isRunning()) { + if (scriptManager->isRunning()) { qCDebug(scriptengine) << "about to shutdown script:" << scriptName; // We disconnect any script engine signals from the application because we don't want to do any // extra stopScript/loadScript processing that the Application normally does when scripts start // and stop. We can safely short circuit this because we know we're in the "quitting" process - scriptEngine->disconnect(this); + scriptManager->disconnect(this); // Gracefully stop the engine's scripting thread - scriptEngine->stop(); + scriptManager->stop(); // We need to wait for the engine to be done running before we proceed, because we don't // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing // any application state after we leave this stopAllScripts() method qCDebug(scriptengine) << "waiting on script:" << scriptName; - scriptEngine->waitTillDoneRunning(true); + scriptManager->waitTillDoneRunning(true); qCDebug(scriptengine) << "done waiting on script:" << scriptName; } // Once the script is stopped, we can remove it from our set @@ -372,8 +374,8 @@ void ScriptEngines::saveScripts() { QVariantList list; { - QReadLocker lock(&_scriptEnginesHashLock); - for (auto it = _scriptEnginesHash.begin(); it != _scriptEnginesHash.end(); ++it) { + QReadLocker lock(&_scriptManagersHashLock); + for (auto it = _scriptManagersHash.begin(); it != _scriptManagersHash.end(); ++it) { // Save user-loaded scripts, only if they are set to quit when finished if (it.value() && it.value()->isUserLoaded() && !it.value()->isQuitWhenFinished()) { auto normalizedUrl = normalizeScriptURL(it.key()); @@ -390,8 +392,8 @@ void ScriptEngines::saveScripts() { } QStringList ScriptEngines::getRunningScripts() { - QReadLocker lock(&_scriptEnginesHashLock); - QList urls = _scriptEnginesHash.keys(); + QReadLocker lock(&_scriptManagersHashLock); + QList urls = _scriptManagersHash.keys(); QStringList result; for (auto url : urls) { result.append(url.toString()); @@ -400,33 +402,33 @@ QStringList ScriptEngines::getRunningScripts() { } void ScriptEngines::stopAllScripts(bool restart) { - QReadLocker lock(&_scriptEnginesHashLock); + QReadLocker lock(&_scriptManagersHashLock); if (_isReloading) { return; } - for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); - it != _scriptEnginesHash.constEnd(); it++) { - ScriptEnginePointer scriptEngine = it.value(); + for (QHash::const_iterator it = _scriptManagersHash.constBegin(); + it != _scriptManagersHash.constEnd(); it++) { + ScriptManagerPointer scriptManager = it.value(); // skip already stopped scripts - if (scriptEngine->isFinished() || scriptEngine->isStopping()) { + if (scriptManager->isFinished() || scriptManager->isStopping()) { continue; } bool isOverrideScript = it.key().toString().compare(this->_defaultScriptsOverride.toString()) == 0; // queue user scripts if restarting - if (restart && (scriptEngine->isUserLoaded() || isOverrideScript)) { + if (restart && (scriptManager->isUserLoaded() || isOverrideScript)) { _isReloading = true; - ScriptEngine::Type type = scriptEngine->getType(); + ScriptManager::Type type = scriptManager->getType(); - connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type, isOverrideScript] (QString scriptName) { + connect(scriptManager.get(), &ScriptManager::finished, this, [this, type, isOverrideScript](QString scriptName) { reloadScript(scriptName, !isOverrideScript)->setType(type); }); } // stop all scripts - scriptEngine->stop(); + scriptManager->stop(); } if (restart) { @@ -446,23 +448,23 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { scriptURL = normalizeScriptURL(QUrl::fromLocalFile(rawScriptURL)); } - QReadLocker lock(&_scriptEnginesHashLock); - if (_scriptEnginesHash.contains(scriptURL)) { - ScriptEnginePointer scriptEngine = _scriptEnginesHash.value(scriptURL); + QReadLocker lock(&_scriptManagersHashLock); + if (_scriptManagersHash.contains(scriptURL)) { + ScriptManagerPointer scriptManager = _scriptManagersHash.value(scriptURL); if (restart) { - bool isUserLoaded = scriptEngine->isUserLoaded(); - ScriptEngine::Type type = scriptEngine->getType(); + bool isUserLoaded = scriptManager->isUserLoaded(); + ScriptManager::Type type = scriptManager->getType(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); - if (!scriptEngine->isStopping()) { - connect(scriptEngine.data(), &ScriptEngine::finished, - this, [this, isUserLoaded, type](QString scriptName, ScriptEnginePointer engine) { + if (!scriptManager->isStopping()) { + connect(scriptManager.get(), &ScriptManager::finished, + this, [this, isUserLoaded, type](QString scriptName, ScriptManagerPointer manager) { reloadScript(scriptName, isUserLoaded)->setType(type); }); } } - scriptEngine->stop(); + scriptManager->stop(); stoppedScript = true; } } @@ -480,11 +482,11 @@ void ScriptEngines::reloadAllScripts() { stopAllScripts(true); } -ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, +ScriptManagerPointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, bool activateMainWindow, bool reload, bool quitWhenFinished) { if (thread() != QThread::currentThread()) { - ScriptEnginePointer result { nullptr }; - BLOCKING_INVOKE_METHOD(this, "loadScript", Q_RETURN_ARG(ScriptEnginePointer, result), + ScriptManagerPointer result { nullptr }; + BLOCKING_INVOKE_METHOD(this, "loadScript", Q_RETURN_ARG(ScriptManagerPointer, result), Q_ARG(QUrl, scriptFilename), Q_ARG(bool, isUserLoaded), Q_ARG(bool, loadScriptFromEditor), @@ -508,41 +510,41 @@ ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool i scriptUrl = QUrl(FileUtils::selectFile(scriptUrl.toString())); - auto scriptEngine = getScriptEngine(scriptUrl); - if (scriptEngine && !scriptEngine->isStopping()) { - return scriptEngine; + auto scriptManager = getScriptEngine(scriptUrl); + if (scriptManager && !scriptManager->isStopping()) { + return scriptManager; } - scriptEngine = scriptEngineFactory(_context, NO_SCRIPT, "about:" + scriptFilename.fileName()); - scriptEngine->setUserLoaded(isUserLoaded); - scriptEngine->setQuitWhenFinished(quitWhenFinished); + scriptManager = scriptManagerFactory(_context, NO_SCRIPT, "about:" + scriptFilename.fileName()); + scriptManager->setUserLoaded(isUserLoaded); + scriptManager->setQuitWhenFinished(quitWhenFinished); if (scriptFilename.isEmpty() || !scriptUrl.isValid()) { - launchScriptEngine(scriptEngine); + launchScriptEngine(scriptManager); } else { // connect to the appropriate signals of this script engine - connect(scriptEngine.data(), &ScriptEngine::scriptLoaded, this, &ScriptEngines::onScriptEngineLoaded); - connect(scriptEngine.data(), &ScriptEngine::errorLoadingScript, this, &ScriptEngines::onScriptEngineError); + connect(scriptManager.get(), &ScriptManager::scriptLoaded, this, &ScriptEngines::onScriptEngineLoaded); + connect(scriptManager.get(), &ScriptManager::errorLoadingScript, this, &ScriptEngines::onScriptEngineError); // Shutdown Interface when script finishes, if requested if (quitWhenFinished) { - connect(scriptEngine.data(), &ScriptEngine::finished, this, &ScriptEngines::quitWhenFinished); + connect(scriptManager.get(), &ScriptManager::finished, this, &ScriptEngines::quitWhenFinished); } // get the script engine object to load the script at the designated script URL - scriptEngine->loadURL(scriptUrl, reload); + scriptManager->loadURL(scriptUrl, reload); } - return scriptEngine; + return scriptManager; } -ScriptEnginePointer ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { - ScriptEnginePointer result; +ScriptManagerPointer ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { + ScriptManagerPointer result; { - QReadLocker lock(&_scriptEnginesHashLock); + QReadLocker lock(&_scriptManagersHashLock); const QUrl scriptURL = normalizeScriptURL(rawScriptURL); - auto it = _scriptEnginesHash.find(scriptURL); - if (it != _scriptEnginesHash.end()) { + auto it = _scriptManagersHash.find(scriptURL); + if (it != _scriptManagersHash.end()) { result = it.value(); } } @@ -552,16 +554,15 @@ ScriptEnginePointer ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { // FIXME - change to new version of ScriptCache loading notification void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { UserActivityLogger::getInstance().loadedScript(rawScriptURL); - QSharedPointer baseScriptEngine = qobject_cast(sender())->sharedFromThis(); - ScriptEnginePointer scriptEngine = qSharedPointerCast(baseScriptEngine); + ScriptManagerPointer scriptEngine = qobject_cast(sender())->shared_from_this(); launchScriptEngine(scriptEngine); { - QWriteLocker lock(&_scriptEnginesHashLock); + QWriteLocker lock(&_scriptManagersHashLock); QUrl url = QUrl(rawScriptURL); QUrl normalized = normalizeScriptURL(url); - _scriptEnginesHash.insert(normalized, scriptEngine); + _scriptManagersHash.insert(normalized, scriptEngine); } // Update settings with new script @@ -573,40 +574,42 @@ void ScriptEngines::quitWhenFinished() { qApp->quit(); } -int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { - auto nativeCount = DependencyManager::get()->runScriptInitializers(scriptEngine.data()); - return nativeCount + ScriptInitializerMixin::runScriptInitializers(scriptEngine); +int ScriptEngines::runScriptInitializers(ScriptManagerPointer scriptManager) { + auto nativeCount = DependencyManager::get()->runScriptInitializers(scriptManager->engine().get()); + return nativeCount + ScriptInitializerMixin::runScriptInitializers(scriptManager); } -void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { - connect(scriptEngine.data(), &ScriptEngine::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection); - connect(scriptEngine.data(), &ScriptEngine::loadScript, [this](const QString& scriptName, bool userLoaded) { +void ScriptEngines::launchScriptEngine(ScriptManagerPointer scriptManager) { + connect(scriptManager.get(), &ScriptManager::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection); + connect(scriptManager.get(), &ScriptManager::loadScript, + [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded); }); - connect(scriptEngine.data(), &ScriptEngine::reloadScript, [this](const QString& scriptName, bool userLoaded) { + connect(scriptManager.get(), &ScriptManager::reloadScript, + [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded, false, false, true); }); // register our application services and set it off on its own thread - runScriptInitializers(scriptEngine); - scriptEngine->runInThread(); + runScriptInitializers(scriptManager); + scriptManager->runInThread(); } -void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEnginePointer engine) { +void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptManagerPointer manager) { bool removed = false; { - QWriteLocker lock(&_scriptEnginesHashLock); + QWriteLocker lock(&_scriptManagersHashLock); const QUrl scriptURL = normalizeScriptURL(QUrl(rawScriptURL)); - for (auto it = _scriptEnginesHash.find(scriptURL); it != _scriptEnginesHash.end(); ++it) { - if (it.value() == engine) { - _scriptEnginesHash.erase(it); + for (auto it = _scriptManagersHash.find(scriptURL); it != _scriptManagersHash.end(); ++it) { + if (it.value() == manager) { + _scriptManagersHash.erase(it); removed = true; break; } } } - removeScriptEngine(engine); + removeScriptEngine(manager); if (removed && !_isReloading) { // Update settings with removed script diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index bc41fb8ab42..f444a32e2d9 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -24,14 +24,12 @@ #include #include #include +#include "ScriptManager.h" -#include "ScriptEngine.h" #include "ScriptsModel.h" #include "ScriptsModelFilter.h" #include "ScriptGatekeeper.h" -class ScriptEngine; - /*@jsdoc * The ScriptDiscoveryService API provides facilities to work with Interface scripts. * @@ -54,7 +52,7 @@ class ScriptEngine; * Read-only. */ /// Provides the ScriptDiscoveryService scripting interface -class ScriptEngines : public QObject, public Dependency, public ScriptInitializerMixin { +class ScriptEngines : public QObject, public Dependency, public ScriptInitializerMixin { Q_OBJECT Q_PROPERTY(ScriptsModel* scriptsModel READ scriptsModel CONSTANT) @@ -62,8 +60,8 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize Q_PROPERTY(QString debugScriptUrl READ getDebugScriptUrl WRITE setDebugScriptUrl) public: - ScriptEngines(ScriptEngine::Context context, const QUrl& defaultScriptsOverride = QUrl()); - int runScriptInitializers(ScriptEnginePointer engine) override; + ScriptEngines(ScriptManager::Context context, const QUrl& defaultScriptsOverride = QUrl()); + int runScriptInitializers(ScriptManagerPointer manager) override; void loadScripts(); void saveScripts(); @@ -75,7 +73,7 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize void reloadLocalFiles(); QStringList getRunningScripts(); - ScriptEnginePointer getScriptEngine(const QUrl& scriptHash); + ScriptManagerPointer getScriptEngine(const QUrl& scriptHash); ScriptsModel* scriptsModel() { return &_scriptsModel; }; ScriptsModelFilter* scriptsModelFilter() { return &_scriptsModelFilter; }; @@ -111,7 +109,7 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize * false to not close Interface. * @returns {object} An empty object, {}. */ - Q_INVOKABLE ScriptEnginePointer loadScript(const QUrl& scriptFilename = QString(), + Q_INVOKABLE ScriptManagerPointer loadScript(const QUrl& scriptFilename = QString(), bool isUserLoaded = true, bool loadScriptFromEditor = false, bool activateMainWindow = false, bool reload = false, bool quitWhenFinished = false); /*@jsdoc @@ -180,7 +178,7 @@ class ScriptEngines : public QObject, public Dependency, public ScriptInitialize void shutdownScripting(); bool isStopped() const { return _isStopped; } - void addScriptEngine(ScriptEnginePointer); + void addScriptEngine(ScriptManagerPointer); ScriptGatekeeper scriptGatekeeper; @@ -326,26 +324,24 @@ protected slots: /*@jsdoc * @function ScriptDiscoveryService.onScriptFinished * @param {string} scriptName - Script name. - * @param {object} engine - Engine. + * @param {object} manager - Script manager. * @deprecated This function is deprecated and will be removed. */ // Deprecated because it wasn't intended to be in the API. - void onScriptFinished(const QString& fileNameString, ScriptEnginePointer engine); + void onScriptFinished(const QString& fileNameString, ScriptManagerPointer manager); protected: - friend class ScriptEngine; - - ScriptEnginePointer reloadScript(const QString& scriptName, bool isUserLoaded = true) { return loadScript(scriptName, isUserLoaded, false, false, true); } - void removeScriptEngine(ScriptEnginePointer); + ScriptManagerPointer reloadScript(const QString& scriptName, bool isUserLoaded = true) { return loadScript(scriptName, isUserLoaded, false, false, true); } + void removeScriptEngine(ScriptManagerPointer); void onScriptEngineLoaded(const QString& scriptFilename); void quitWhenFinished(); void onScriptEngineError(const QString& scriptFilename); - void launchScriptEngine(ScriptEnginePointer); + void launchScriptEngine(ScriptManagerPointer); - ScriptEngine::Context _context; - QReadWriteLock _scriptEnginesHashLock; - QMultiHash _scriptEnginesHash; - QSet _allKnownScriptEngines; + ScriptManager::Context _context; + QReadWriteLock _scriptManagersHashLock; + QMultiHash _scriptManagersHash; + QSet _allKnownScriptManagers; QMutex _allScriptsMutex; ScriptsModel _scriptsModel; ScriptsModelFilter _scriptsModelFilter; diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp new file mode 100644 index 00000000000..7addbe080a3 --- /dev/null +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -0,0 +1,2478 @@ +// +// ScriptManager.cpp +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptManager.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AssetScriptingInterface.h" +#include "BatchLoader.h" +#include "EventTypes.h" +#include "FileScriptingInterface.h" // unzip project +#include "MenuItemProperties.h" +#include "ScriptCache.h" +#include "ScriptContext.h" +#include "XMLHttpRequestClass.h" +#include "WebSocketClass.h" +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptEngineLogging.h" +#include "ScriptEngines.h" +#include "StackTestScriptingInterface.h" +#include "ScriptValue.h" +#include "ScriptValueIterator.h" +#include "ScriptValueUtils.h" + +#include + +#include "MIDIEvent.h" + +#include "SettingHandle.h" +#include +#include +#include + +const QString ScriptManager::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { + "com.highfidelity.experimental.enableExtendedJSExceptions" +}; + +const QString ScriptManager::SCRIPT_EXCEPTION_FORMAT{ "[%0] %1 in %2:%3" }; +const QString ScriptManager::SCRIPT_BACKTRACE_SEP{ "\n " }; + +static const int MAX_MODULE_ID_LENGTH { 4096 }; +static const int MAX_DEBUG_VALUE_LENGTH { 80 }; + +static const ScriptValue::PropertyFlags READONLY_PROP_FLAGS{ ScriptValue::ReadOnly | ScriptValue::Undeletable }; +static const ScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS{ READONLY_PROP_FLAGS | ScriptValue::SkipInEnumeration }; + +static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true }; + +int scriptManagerPointerMetaID = qRegisterMetaType(); + +Q_DECLARE_METATYPE(ExternalResource::Bucket); + +Q_DECLARE_METATYPE(ScriptValue); + +// --- Static script initialization registry + +static ScriptManager::StaticInitializerNode* rootInitializer = nullptr; + +void ScriptManager::registerNewStaticInitializer(StaticInitializerNode* dest) { + // this function is assumed to be called on LoadLibrary, where we are explicitly operating in single-threaded mode + // Therefore there is no mutex or threadsafety here and the structure is assumed not to change after loading + dest->prev = rootInitializer; + rootInitializer = dest; +} +static void runStaticInitializers(ScriptManager* manager) { + ScriptManager::StaticInitializerNode* here = rootInitializer; + while (here != nullptr) { + (*here->init)(manager); + here = here->prev; + } +} + +// --- + +static ScriptValue debugPrint(ScriptContext* context, ScriptEngine* engine) { + // assemble the message by concatenating our arguments + QString message = ""; + for (int i = 0; i < context->argumentCount(); i++) { + if (i > 0) { + message += " "; + } + message += context->argument(i).toString(); + } + + // was this generated by a script engine? If we don't recognize it then send the message and exit + ScriptManager* scriptManager = engine->manager(); + if (!scriptManager) { + qCDebug(scriptengine_script, "%s", qUtf8Printable(message)); + return ScriptValue(); + } + + // This message was sent by one of our script engines, let's try to see if we can find the source. + // Note that the first entry in the backtrace should be "print" and is somewhat useless to us + AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); + if (loggerInterface && loggerInterface->showSourceDebugging()) { + ScriptContext* userContext = context; + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan + while (userContext && userContext->functionContext()->functionType() == ScriptFunctionContext::NativeFunction) { + parentContext = userContext->parentContext(); + userContext = parentContext.get(); + } + QString location; + if (userContext) { + auto contextInfo = userContext->functionContext(); + QString fileName = contextInfo->fileName(); + int lineNumber = contextInfo->lineNumber(); + QString functionName = contextInfo->functionName(); + + location = functionName; + if (!fileName.isEmpty()) { + if (location.isEmpty()) { + location = fileName; + } else { + location = QString("%1 at %2").arg(location).arg(fileName); + } + } + if (lineNumber != -1) { + location = QString("%1:%2").arg(location).arg(lineNumber); + } + } + if (location.isEmpty()) { + location = scriptManager->getFilename(); + } + + // give the script engine a chance to notify the system about this message + scriptManager->print(message); + + // send the message to debug log + qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message)); + } else { + scriptManager->print(message); + // prefix the script engine name to help disambiguate messages in the main debug log + qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(scriptManager->getFilename()), qUtf8Printable(message)); + } + + return ScriptValue(); +} + +// FIXME Come up with a way to properly encode entity IDs in filename +// The purpose of the following two function is to embed entity ids into entity script filenames +// so that they show up in stacktraces +// +// Extract the url portion of a url that has been encoded with encodeEntityIdIntoEntityUrl(...) +QString extractUrlFromEntityUrl(const QString& url) { + auto parts = url.split(' ', Qt::SkipEmptyParts); + if (parts.length() > 0) { + return parts[0]; + } else { + return ""; + } +} + +// Encode an entity id into an entity url +// Example: http://www.example.com/some/path.js [EntityID:{9fdd355f-d226-4887-9484-44432d29520e}] +QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) { + return url + " [EntityID:" + entityID + "]"; +} + +QString ScriptManager::logException(const ScriptValue& exception) { + auto message = formatException(exception, _enableExtendedJSExceptions.get()); + scriptErrorMessage(message); + return message; +} + +ScriptManagerPointer scriptManagerFactory(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString) { + ScriptManagerPointer manager = newScriptManager(context, scriptContents, fileNameString); + auto scriptEngines = DependencyManager::get(); + scriptEngines->addScriptEngine(manager); + manager->setScriptEngines(scriptEngines); + return manager; +} + +ScriptManagerPointer newScriptManager(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString) { + ScriptManagerPointer manager(new ScriptManager(context, scriptContents, fileNameString), + [](ScriptManager* obj) { obj->deleteLater(); }); + ScriptEnginePointer engine = newScriptEngine(manager.get()); + manager->_engine = engine; + return manager; +} + +int ScriptManager::processLevelMaxRetries { ScriptRequest::MAX_RETRIES }; +ScriptManager::ScriptManager(Context context, const QString& scriptContents, const QString& fileNameString) : + QObject(), + _context(context), + _scriptContents(scriptContents), + _timerFunctionMap(), + _fileNameString(fileNameString), + _assetScriptingInterface(new AssetScriptingInterface(this)), + _engine(newScriptEngine(this)) +{ + switch (_context) { + case Context::CLIENT_SCRIPT: + _type = Type::CLIENT; + break; + case Context::ENTITY_CLIENT_SCRIPT: + _type = Type::ENTITY_CLIENT; + break; + case Context::ENTITY_SERVER_SCRIPT: + _type = Type::ENTITY_SERVER; + break; + case Context::AGENT_SCRIPT: + _type = Type::AGENT; + break; + } + + if (isEntityServerScript()) { + qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1"; + processLevelMaxRetries = 1; + } + + // this is where all unhandled exceptions end up getting logged + connect(this, &ScriptManager::unhandledException, this, [this](const ScriptValue& err) { + auto output = err.engine() == _engine ? err : _engine->makeError(err); + if (!output.property("detail").isValid()) { + output.setProperty("detail", "UnhandledException"); + } + logException(output); + }); + + if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { + QObject::connect(this, &ScriptManager::update, this, [this]() { + // process pending entity script content + if (!_contentAvailableQueue.empty() && !(_isFinished || _isStopping)) { + EntityScriptContentAvailableMap pending; + std::swap(_contentAvailableQueue, pending); + for (auto& pair : pending) { + auto& args = pair.second; + entityScriptContentAvailable(args.entityID, args.scriptOrURL, args.contents, args.isURL, args.success, args.status); + } + } + }); + } +} + +QString ScriptManager::getTypeAsString() const { + auto value = QVariant::fromValue(_type).toString(); + return value.isEmpty() ? "unknown" : value.toLower(); +} + +QString ScriptManager::getContext() const { + switch (_context) { + case CLIENT_SCRIPT: + return "client"; + case ENTITY_CLIENT_SCRIPT: + return "entity_client"; + case ENTITY_SERVER_SCRIPT: + return "entity_server"; + case AGENT_SCRIPT: + return "agent"; + default: + return "unknown"; + } + return "unknown"; +} + +bool ScriptManager::isDebugMode() const { +#if defined(DEBUG) + return true; +#else + return false; +#endif +} + +ScriptManager::~ScriptManager() {} + +void ScriptManager::disconnectNonEssentialSignals() { + disconnect(); + QThread* workerThread; + // Ensure the thread should be running, and does exist + if (_isRunning && _isThreaded && (workerThread = thread())) { + connect(this, &QObject::destroyed, workerThread, &QThread::quit); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + } +} + +void ScriptManager::runInThread() { + Q_ASSERT_X(!_isThreaded, "ScriptManager::runInThread()", "runInThread should not be called more than once"); + + if (_isThreaded) { + return; + } + + _isThreaded = true; + + // The thread interface cannot live on itself, and we want to move this into the thread, so + // the thread cannot have this as a parent. + QThread* workerThread = new QThread(); + QString name = QString("js:") + getFilename().replace("about:",""); + workerThread->setObjectName(name); + _engine->setThread(workerThread); + moveToThread(workerThread); + + // NOTE: If you connect any essential signals for proper shutdown or cleanup of + // the script engine, make sure to add code to "reconnect" them to the + // disconnectNonEssentialSignals() method + connect(workerThread, &QThread::started, this, [this, name] { + setThreadName(name.toStdString()); + run(); + }); + connect(this, &QObject::destroyed, workerThread, &QThread::quit); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + + workerThread->start(); +} + +void ScriptManager::executeOnScriptThread(std::function function, const Qt::ConnectionType& type ) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function, function)); + return; + } + + function(); +} + +void ScriptManager::waitTillDoneRunning(bool shutdown) { + // Engine should be stopped already, but be defensive + stop(); + + auto workerThread = thread(); + + if (workerThread == QThread::currentThread()) { + qCWarning(scriptengine) << "ScriptManager::waitTillDoneRunning called, but the script is on the same thread:" << getFilename(); + return; + } + + if (_isThreaded && workerThread) { + // We should never be waiting (blocking) on our own thread + assert(workerThread != QThread::currentThread()); + +#if 0 + // 26 Feb 2021 - Disabled this OSX-specific code because it causes OSX to crash on shutdown; without this code, OSX + // doesn't crash on shutdown. Qt 5.12.3 and Qt 5.15.2. + // + // On mac, don't call QCoreApplication::processEvents() here. This is to prevent + // [NSApplication terminate:] from prematurely destroying the static destructors + // while we are waiting for the scripts to shutdown. We will pump the message + // queue later in the Application destructor. + if (workerThread->isRunning()) { + workerThread->quit(); + + if (_engine->isEvaluating()) { + qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); + _engine->abortEvaluation(); + } else { + auto context = _engine->currentContext(); + if (context) { + qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename(); + context->throwError("Timed out during shutdown"); + } + } + + // Wait for the scripting thread to stop running, as + // flooding it with aborts/exceptions will persist it longer + static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; + if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { + workerThread->terminate(); + } + } +#else + auto startedWaiting = usecTimestampNow(); + while (workerThread->isRunning()) { + // If the final evaluation takes too long, then tell the script engine to stop running + auto elapsedUsecs = usecTimestampNow() - startedWaiting; + static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; + if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { + workerThread->quit(); + + if (_engine->isEvaluating()) { + qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); + _engine->abortEvaluation(); + } else { + auto context = _engine->currentContext(); + if (context) { + qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename(); + context->throwError("Timed out during shutdown"); + } + } + + // Wait for the scripting thread to stop running, as + // flooding it with aborts/exceptions will persist it longer + static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; + if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { + workerThread->terminate(); + } + } + + if (shutdown) { + // NOTE: This will be called on the main application thread (among other threads) from stopAllScripts. + // The thread will need to continue to process events, because + // the scripts will likely need to marshall messages across to the main thread, e.g. + // if they access Settings or Menu in any of their shutdown code. So: + // Process events for this thread, allowing invokeMethod calls to pass between threads. + QCoreApplication::processEvents(); + } + + // Avoid a pure busy wait + QThread::yieldCurrentThread(); + } +#endif + + scriptInfoMessage("Script Engine has stopped:" + getFilename()); + } +} + +QString ScriptManager::getFilename() const { + QStringList fileNameParts = _fileNameString.split("/"); + QString lastPart; + if (!fileNameParts.isEmpty()) { + lastPart = fileNameParts.last(); + } + return lastPart; +} + +bool ScriptManager::hasValidScriptSuffix(const QString& scriptFileName) { + QFileInfo fileInfo(scriptFileName); + QString scriptSuffixToLower = fileInfo.completeSuffix().toLower(); + return scriptSuffixToLower.contains(QString("js"), Qt::CaseInsensitive); +} + +void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) { + if (_isRunning) { + return; + } + + QUrl url = expandScriptUrl(scriptURL); + _fileNameString = url.toString(); + _isReloading = reload; + + // Check that script has a supported file extension + if (!hasValidScriptSuffix(_fileNameString)) { + scriptErrorMessage("File extension of file: " + _fileNameString + " is not a currently supported script type"); + emit errorLoadingScript(_fileNameString); + return; + } + + const auto maxRetries = 0; // for consistency with previous scriptCache->getScript() behavior + auto scriptCache = DependencyManager::get(); + scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) { + qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread(); + if (!success) { + scriptErrorMessage("ERROR Loading file (" + status + "):" + url); + emit errorLoadingScript(_fileNameString); + return; + } + + _scriptContents = scriptContents; + + emit scriptLoaded(url); + }, reload, maxRetries); +} + +void ScriptManager::scriptErrorMessage(const QString& message) { + qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); + emit errorMessage(message, getFilename()); +} + +void ScriptManager::scriptWarningMessage(const QString& message) { + qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); + emit warningMessage(message, getFilename()); +} + +void ScriptManager::scriptInfoMessage(const QString& message) { + qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); + emit infoMessage(message, getFilename()); +} + +void ScriptManager::scriptPrintedMessage(const QString& message) { + qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); + emit printedMessage(message, getFilename()); +} + +void ScriptManager::clearDebugLogWindow() { + emit clearDebugWindow(); +} + +// Templated qScriptRegisterMetaType fails to compile with raw pointers +using ScriptableResourceRawPtr = ScriptableResource*; + +static ScriptValue scriptableResourceToScriptValue(ScriptEngine* engine, + const ScriptableResourceRawPtr& resource) { + if (!resource) { + return ScriptValue(); // probably shutting down + } + + // The first script to encounter this resource will track its memory. + // In this way, it will be more likely to GC. + // This fails in the case that the resource is used across many scripts, but + // in that case it would be too difficult to tell which one should track the memory, and + // this serves the common case (use in a single script). + auto data = resource->getResource(); + auto manager = engine->manager(); + if (data && manager && !resource->isInScript()) { + resource->setInScript(true); + QObject::connect(data.data(), &Resource::updateSize, manager, &ScriptManager::updateMemoryCost); + } + + auto object = engine->newQObject(const_cast(resource), ScriptEngine::ScriptOwnership); + return object; +} + +static bool scriptableResourceFromScriptValue(const ScriptValue& value, ScriptableResourceRawPtr& resource) { + resource = static_cast(value.toQObject()); + return true; +} + +/*@jsdoc + * The Resource API provides values that define the possible loading states of a resource. + * + * @namespace Resource + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {Resource.State} State - The possible loading states of a resource. Read-only. + */ +static ScriptValue createScriptableResourcePrototype(ScriptManagerPointer manager) { + auto engine = manager->engine(); + auto prototype = engine->newObject(); + + // Expose enum State to JS/QML via properties + QObject* state = new QObject(manager.get()); + state->setObjectName("ResourceState"); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + state->setProperty(metaEnum.key(i), metaEnum.value(i)); + } + + auto prototypeState = engine->newQObject(state, ScriptEngine::QtOwnership, + ScriptEngine::ExcludeSlots | ScriptEngine::ExcludeSuperClassMethods); + prototype.setProperty("State", prototypeState); + + return prototype; +} + +ScriptValue externalResourceBucketToScriptValue(ScriptEngine* engine, ExternalResource::Bucket const& in) { + return engine->newValue((int)in); +} + +bool externalResourceBucketFromScriptValue(const ScriptValue& object, ExternalResource::Bucket& out) { + out = static_cast(object.toInt32()); + return true; +} + +void ScriptManager::resetModuleCache(bool deleteScriptCache) { + if (QThread::currentThread() != thread()) { + executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); }); + return; + } + auto jsRequire = _engine->globalObject().property("Script").property("require"); + auto cache = jsRequire.property("cache"); + auto cacheMeta = jsRequire.data(); + + if (deleteScriptCache) { + auto it = cache.newIterator(); + while (it->hasNext()) { + it->next(); + if (it->flags() & ScriptValue::SkipInEnumeration) { + continue; + } + qCDebug(scriptengine) << "resetModuleCache(true) -- staging " << it->name() << " for cache reset at next require"; + cacheMeta.setProperty(it->name(), true); + } + } + cache = _engine->newObject(); + if (!cacheMeta.isObject()) { + cacheMeta = _engine->newObject(); + cacheMeta.setProperty("id", "Script.require.cacheMeta"); + cacheMeta.setProperty("type", "cacheMeta"); + jsRequire.setData(cacheMeta); + } + cache.setProperty("__created__", (double)QDateTime::currentMSecsSinceEpoch(), ScriptValue::SkipInEnumeration); +#if DEBUG_JS_MODULES + cache.setProperty("__meta__", cacheMeta, READONLY_HIDDEN_PROP_FLAGS); +#endif + jsRequire.setProperty("cache", cache, READONLY_PROP_FLAGS); +} + +void ScriptManager::init() { + if (_isInitialized) { + return; // only initialize once + } + + _isInitialized = true; + runStaticInitializers(this); + + auto scriptEngine = _engine.get(); + + // register various meta-types + registerMIDIMetaTypes(scriptEngine); + registerEventTypes(scriptEngine); + registerMenuItemProperties(scriptEngine); + + scriptRegisterSequenceMetaType>(scriptEngine); + scriptRegisterSequenceMetaType>(scriptEngine); + + scriptRegisterSequenceMetaType>(scriptEngine); + scriptRegisterSequenceMetaType>(scriptEngine); + scriptRegisterSequenceMetaType>(scriptEngine); + + ScriptValue xmlHttpRequestConstructorValue = scriptEngine->newFunction(XMLHttpRequestClass::constructor); + scriptEngine->globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); + + ScriptValue webSocketConstructorValue = scriptEngine->newFunction(WebSocketClass::constructor); + scriptEngine->globalObject().setProperty("WebSocket", webSocketConstructorValue); + + /*@jsdoc + * Prints a message to the program log and emits {@link Script.printedMessage}. + * The message logged is the message values separated by spaces. + *

Alternatively, you can use {@link Script.print} or one of the {@link console} API methods.

+ * @function print + * @param {...*} [message] - The message values to print. + */ + scriptEngine->globalObject().setProperty("print", scriptEngine->newFunction(debugPrint)); + + scriptRegisterMetaType(scriptEngine, animationDetailsToScriptValue, animationDetailsFromScriptValue); + scriptRegisterMetaType(scriptEngine, webSocketToScriptValue, webSocketFromScriptValue); + scriptRegisterMetaType(scriptEngine, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue); + scriptRegisterMetaType(scriptEngine, wscReadyStateToScriptValue, wscReadyStateFromScriptValue); + + // NOTE: You do not want to end up creating new instances of singletons here. They will be on the ScriptManager thread + // and are likely to be unusable if we "reset" the ScriptManager by creating a new one (on a whole new thread). + + scriptEngine->registerGlobalObject("Script", this); + + { + // set up Script.require.resolve and Script.require.cache + auto Script = scriptEngine->globalObject().property("Script"); + auto require = Script.property("require"); + auto resolve = Script.property("_requireResolve"); + require.setProperty("resolve", resolve, READONLY_PROP_FLAGS); + resetModuleCache(); + } + + scriptRegisterMetaType(scriptEngine, externalResourceBucketToScriptValue, externalResourceBucketFromScriptValue); + scriptEngine->registerEnum("Script.ExternalPaths", QMetaEnum::fromType()); + + scriptEngine->registerGlobalObject("Quat", &_quatLibrary); + scriptEngine->registerGlobalObject("Vec3", &_vec3Library); + scriptEngine->registerGlobalObject("Mat4", &_mat4Library); + scriptEngine->registerGlobalObject("Uuid", &_uuidLibrary); + scriptEngine->registerGlobalObject("Messages", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("File", new FileScriptingInterface(this)); + scriptEngine->registerGlobalObject("console", &_consoleScriptingInterface); + scriptEngine->registerFunction("console", "info", ConsoleScriptingInterface::info, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "log", ConsoleScriptingInterface::log, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "debug", ConsoleScriptingInterface::debug, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "warn", ConsoleScriptingInterface::warn, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "error", ConsoleScriptingInterface::error, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "exception", ConsoleScriptingInterface::exception, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "assert", ConsoleScriptingInterface::assertion, scriptEngine->currentContext()->argumentCount()); + scriptEngine->registerFunction("console", "group", ConsoleScriptingInterface::group, 1); + scriptEngine->registerFunction("console", "groupCollapsed", ConsoleScriptingInterface::groupCollapsed, 1); + scriptEngine->registerFunction("console", "groupEnd", ConsoleScriptingInterface::groupEnd, 0); + + // Scriptable cache access + auto resourcePrototype = createScriptableResourcePrototype(shared_from_this()); + scriptEngine->globalObject().setProperty("Resource", resourcePrototype); + scriptEngine->setDefaultPrototype(qMetaTypeId(), resourcePrototype); + scriptRegisterMetaType(scriptEngine, scriptableResourceToScriptValue, scriptableResourceFromScriptValue); + + // constants + scriptEngine->globalObject().setProperty("TREE_SCALE", scriptEngine->newValue(TREE_SCALE)); + + scriptEngine->registerGlobalObject("Assets", _assetScriptingInterface); + scriptEngine->registerGlobalObject("Resources", DependencyManager::get().data()); + + scriptEngine->registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); + + scriptRegisterMetaType(scriptEngine, meshToScriptValue, meshFromScriptValue); + scriptRegisterMetaType(scriptEngine, meshesToScriptValue, meshesFromScriptValue); + + scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); + +#if DEV_BUILD || PR_BUILD + scriptEngine->registerGlobalObject("StackTest", new StackTestScriptingInterface(this)); +#endif + +} + +// registers a global object by name +void ScriptManager::registerValue(const QString& valueName, ScriptValue value) { + _engine->globalObject().setProperty(valueName, value); +} + +// Unregister the handlers for this eventName and entityID. +void ScriptManager::removeEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::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(const ScriptValue&, handler)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; +#endif + + if (!_registeredHandlers.contains(entityID)) { + return; + } + RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; + CallbackList& handlersForEvent = handlersOnEntity[eventName]; + // ScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate. + for (int i = 0; i < handlersForEvent.count(); ++i) { + if (handlersForEvent[i].function.equals(handler)) { + handlersForEvent.removeAt(i); + return; // Design choice: since comparison is relatively expensive, just remove the first matching handler. + } + } +} + +// Unregister all event handlers for the specified entityID (i.e. the entity is being removed) +void ScriptManager::removeAllEventHandlers(const EntityItemID& entityID) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::removeAllEventHandlers() called on wrong thread [" << QThread::currentThread() << ", correct thread is " << thread() << " ], ignoring " + "entityID:" << entityID; +#endif + return; + } + + if (_registeredHandlers.contains(entityID)) { + _registeredHandlers.remove(entityID); + } +} + +// Register the handler. +void ScriptManager::addEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::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(const ScriptValue&, handler)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::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... + emit attachDefaultEventHandlers(); + } + if (!_registeredHandlers.contains(entityID)) { + _registeredHandlers[entityID] = RegisteredEventHandlers(); + } + CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; + CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL }; + handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). +} + +bool ScriptManager::isStopped() const { + QSharedPointer scriptEngines(_scriptEngines); + return !scriptEngines || scriptEngines->isStopped(); +} + +void ScriptManager::run() { + if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) { + // Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread + // (because we're a client script) + hifi::scripting::setLocalAccessSafeThread(true); + } + + auto filenameParts = _fileNameString.split("/"); + auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; + PROFILE_SET_THREAD_NAME("Script: " + name); + + if (isStopped()) { + return; // bail early - avoid setting state in init(), as evaluate() will bail too + } + + scriptInfoMessage("Script Engine starting:" + getFilename()); + + if (!_isInitialized) { + init(); + } + + _isRunning = true; + emit runningStateChanged(); + + { + PROFILE_RANGE(script, _fileNameString); + _engine->evaluate(_scriptContents, _fileNameString); + _engine->maybeEmitUncaughtException(__FUNCTION__); + } +#ifdef _WIN32 + // VS13 does not sleep_until unless it uses the system_clock, see: + // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/ + using clock = std::chrono::system_clock; +#else + using clock = std::chrono::high_resolution_clock; +#endif + + clock::time_point startTime = clock::now(); + int thisFrame = 0; + + _lastUpdate = usecTimestampNow(); + + std::chrono::microseconds totalUpdates(0); + + // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptManager + while (!_isFinished) { + auto beforeSleep = clock::now(); + + // Throttle to SCRIPT_FPS + // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will + // calculate a sleepUntil to be the time from our start time until the original target + // sleepUntil for this frame. This approach will allow us to "catch up" in the event + // that some of our script udpates/frames take a little bit longer than the target average + // to execute. + // NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach + const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1); + clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION)); + + // However, if our sleepUntil is not at least our average update and timer execution time + // into the future it means our script is taking too long in its updates, and we want to + // punish the script a little bit. So we will force the sleepUntil to be at least our + // averageUpdate + averageTimerPerFrame time into the future. + auto averageUpdate = totalUpdates / thisFrame; + auto averageTimerPerFrame = _totalTimerExecution / thisFrame; + auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame; + auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate); + + // We don't want to actually sleep for too long, because it causes our scripts to hang + // on shutdown and stop... so we want to loop and sleep until we've spent our time in + // purgatory, constantly checking to see if our script was asked to end + bool processedEvents = false; + if (!_isFinished) { + PROFILE_RANGE(script, "processEvents-sleep"); + std::chrono::milliseconds sleepFor = + std::chrono::duration_cast(sleepUntil - clock::now()); + if (sleepFor > std::chrono::milliseconds(0)) { + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + timer.start(sleepFor.count()); + loop.exec(); + } else { + QCoreApplication::processEvents(); + } + processedEvents = true; + } + + PROFILE_RANGE(script, "ScriptMainLoop"); + +#ifdef SCRIPT_DELAY_DEBUG + { + auto actuallySleptUntil = clock::now(); + uint64_t seconds = std::chrono::duration_cast(actuallySleptUntil - startTime).count(); + if (seconds > 0) { // avoid division by zero and time travel + uint64_t fps = thisFrame / seconds; + // Overreporting artificially reduces the reported rate + if (thisFrame % SCRIPT_FPS == 0) { + qCDebug(scriptengine) << + "Frame:" << thisFrame << + "Slept (us):" << std::chrono::duration_cast(actuallySleptUntil - beforeSleep).count() << + "Avg Updates (us):" << averageUpdate.count() << + "FPS:" << fps; + } + } + } +#endif + if (_isFinished) { + break; + } + + // Only call this if we didn't processEvents as part of waiting for next frame + if (!processedEvents) { + PROFILE_RANGE(script, "processEvents"); + QCoreApplication::processEvents(); + } + + if (_isFinished) { + break; + } + + if (!_isFinished) { + emit releaseEntityPacketSenderMessages(false); + } + + qint64 now = usecTimestampNow(); + + // we check for 'now' in the past in case people set their clock back + if (_emitScriptUpdates() && _lastUpdate < now) { + float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; + if (!_isFinished) { + auto preUpdate = clock::now(); + { + PROFILE_RANGE(script, "ScriptUpdate"); + emit update(deltaTime); + } + auto postUpdate = clock::now(); + auto elapsed = (postUpdate - preUpdate); + totalUpdates += std::chrono::duration_cast(elapsed); + } + } + _lastUpdate = now; + + // only clear exceptions if we are not in the middle of evaluating + if (!_engine->isEvaluating() && _engine->hasUncaughtException()) { + qCWarning(scriptengine) << __FUNCTION__ << "---------- UNCAUGHT EXCEPTION --------"; + qCWarning(scriptengine) << "runInThread" << _engine->uncaughtException().toString(); + emit unhandledException(_engine->cloneUncaughtException(__FUNCTION__)); + _engine->clearExceptions(); + } + } + scriptInfoMessage("Script Engine stopping:" + getFilename()); + + stopAllTimers(); // make sure all our timers are stopped if the script is ending + emit scriptEnding(); + + emit releaseEntityPacketSenderMessages(true); + + emit finished(_fileNameString, shared_from_this()); + + // Don't leave our local-file-access flag laying around, reset it to false when the scriptengine + // thread is finished + hifi::scripting::setLocalAccessSafeThread(false); + _isRunning = false; + emit runningStateChanged(); + emit doneRunning(); +} + +// NOTE: This is private because it must be called on the same thread that created the timers, which is why +// we want to only call it in our own run "shutdown" processing. +void ScriptManager::stopAllTimers() { + QMutableHashIterator i(_timerFunctionMap); + int j {0}; + while (i.hasNext()) { + i.next(); + QTimer* timer = i.key(); + qCDebug(scriptengine) << getFilename() << "stopAllTimers[" << j++ << "]"; + stopTimer(timer); + } +} + +void ScriptManager::stopAllTimersForEntityScript(const EntityItemID& entityID) { + // We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS + QVector toDelete; + QMutableHashIterator i(_timerFunctionMap); + while (i.hasNext()) { + i.next(); + if (i.value().definingEntityIdentifier != entityID) { + continue; + } + QTimer* timer = i.key(); + toDelete << timer; // don't delete while we're iterating. save it. + } + for (auto timer:toDelete) { // now reap 'em + stopTimer(timer); + } + +} + +void ScriptManager::stop(bool marshal) { + _isStopping = true; // this can be done on any thread + + if (marshal) { + QMetaObject::invokeMethod(this, "stop"); + return; + } + if (!_isFinished) { + _isFinished = true; + emit runningStateChanged(); + } +} + +void ScriptManager::updateMemoryCost(const qint64& deltaSize) { + _engine->updateMemoryCost(deltaSize); +} + +void ScriptManager::timerFired() { + if (isStopped()) { + scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); + return; // bail early + } + + QTimer* callingTimer = reinterpret_cast(sender()); + CallbackData timerData = _timerFunctionMap.value(callingTimer); + + if (!callingTimer->isActive()) { + // this timer is done, we can kill it + _timerFunctionMap.remove(callingTimer); + delete callingTimer; + } + + // call the associated JS function, if it exists + if (timerData.function.isValid()) { + PROFILE_RANGE(script, __FUNCTION__); + auto preTimer = p_high_resolution_clock::now(); + callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, ScriptValueList()); + auto postTimer = p_high_resolution_clock::now(); + auto elapsed = (postTimer - preTimer); + _totalTimerExecution += std::chrono::duration_cast(elapsed); + } else { + qCWarning(scriptengine) << "timerFired -- invalid function" << timerData.function.toVariant().toString(); + } +} + +QTimer* ScriptManager::setupTimerWithInterval(const ScriptValue& function, int intervalMS, bool isSingleShot) { + // create the timer, add it to the map, and start it + QTimer* newTimer = new QTimer(this); + newTimer->setSingleShot(isSingleShot); + + // The default timer type is not very accurate below about 200ms http://doc.qt.io/qt-5/qt.html#TimerType-enum + static const int MIN_TIMEOUT_FOR_COARSE_TIMER = 200; + if (intervalMS < MIN_TIMEOUT_FOR_COARSE_TIMER) { + newTimer->setTimerType(Qt::PreciseTimer); + } + + connect(newTimer, &QTimer::timeout, this, &ScriptManager::timerFired); + + // make sure the timer stops when the script does + connect(this, &ScriptManager::scriptEnding, newTimer, &QTimer::stop); + + + CallbackData timerData = { function, currentEntityIdentifier, currentSandboxURL }; + _timerFunctionMap.insert(newTimer, timerData); + + newTimer->start(intervalMS); + return newTimer; +} + +QTimer* ScriptManager::setInterval(const ScriptValue& function, int intervalMS) { + if (isStopped()) { + scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); + return NULL; // bail early + } + + return setupTimerWithInterval(function, intervalMS, false); +} + +QTimer* ScriptManager::setTimeout(const ScriptValue& function, int timeoutMS) { + if (isStopped()) { + scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); + return NULL; // bail early + } + + return setupTimerWithInterval(function, timeoutMS, true); +} + +void ScriptManager::stopTimer(QTimer *timer) { + if (_timerFunctionMap.contains(timer)) { + timer->stop(); + _timerFunctionMap.remove(timer); + delete timer; + } else { + qCDebug(scriptengine) << "stopTimer -- not in _timerFunctionMap" << timer; + } +} + +QUrl ScriptManager::resolvePath(const QString& include) const { + QUrl url(include); + // first lets check to see if it's already a full URL -- or a Windows path like "c:/" + if (include.startsWith("/") || url.scheme().length() == 1) { + url = QUrl::fromLocalFile(include); + } + if (!url.isRelative()) { + return expandScriptUrl(url); + } + + // we apparently weren't a fully qualified url, so, let's assume we're relative + // to the first absolute URL in the JS scope chain + QUrl parentURL; + auto context = _engine->currentContext(); + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan + do { + auto contextInfo = context->functionContext(); + parentURL = QUrl(contextInfo->fileName()); + parentContext = context->parentContext(); + context = parentContext.get(); + } while (parentURL.isRelative() && context); + + if (parentURL.isRelative()) { + // fallback to the "include" parent (if defined, this will already be absolute) + parentURL = QUrl(_parentURL); + } + + if (parentURL.isRelative()) { + // fallback to the original script engine URL + parentURL = QUrl(_fileNameString); + + // if still relative and path-like, then this is probably a local file... + if (parentURL.isRelative() && url.path().contains("/")) { + parentURL = QUrl::fromLocalFile(_fileNameString); + } + } + + // at this point we should have a legitimate fully qualified URL for our parent + url = expandScriptUrl(parentURL.resolved(url)); + return url; +} + +QUrl ScriptManager::resourcesPath() const { + return QUrl(PathUtils::resourcesUrl()); +} + +void ScriptManager::print(const QString& message) { + emit printedMessage(message, getFilename()); +} + + +void ScriptManager::beginProfileRange(const QString& label) const { + PROFILE_SYNC_BEGIN(script, label.toStdString().c_str(), label.toStdString().c_str()); +} + +void ScriptManager::endProfileRange(const QString& label) const { + PROFILE_SYNC_END(script, label.toStdString().c_str(), label.toStdString().c_str()); +} + +// Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js) +QString ScriptManager::_requireResolve(const QString& moduleId, const QString& relativeTo) { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return QString(); + } + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + QUrl url(moduleId); + + auto displayId = moduleId; + if (displayId.length() > MAX_DEBUG_VALUE_LENGTH) { + displayId = displayId.mid(0, MAX_DEBUG_VALUE_LENGTH) + "..."; + } + auto message = QString("Cannot find module '%1' (%2)").arg(displayId); + + auto throwResolveError = [&](const ScriptValue& error) -> QString { + _engine->raiseException(error); + _engine->maybeEmitUncaughtException("require.resolve"); + return QString(); + }; + + // de-fuzz the input a little by restricting to rational sizes + auto idLength = url.toString().length(); + if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) { + auto details = QString("rejecting invalid module id size (%1 chars [1,%2])") + .arg(idLength).arg(MAX_MODULE_ID_LENGTH); + return throwResolveError(_engine->makeError(_engine->newValue(message.arg(details)), "RangeError")); + } + + // this regex matches: absolute, dotted or path-like URLs + // (ie: the kind of stuff ScriptManager::resolvePath already handles) + QRegularExpression qualified ("^\\w+:|^/|^[.]{1,2}(/|$)"); + + // this is for module.require (which is a bound version of require that's always relative to the module path) + if (!relativeTo.isEmpty()) { + url = QUrl(relativeTo).resolved(moduleId); + url = resolvePath(url.toString()); + } else if (qualified.match(moduleId).hasMatch()) { + url = resolvePath(moduleId); + } else { + // check if the moduleId refers to a "system" module + QString systemPath = defaultScriptsLoc.path(); + QString systemModulePath = QString("%1/modules/%2.js").arg(systemPath).arg(moduleId); + url = defaultScriptsLoc; + url.setPath(systemModulePath); + if (!QFileInfo(url.toLocalFile()).isFile()) { + if (!moduleId.contains("./")) { + // the user might be trying to refer to a relative file without anchoring it + // let's do them a favor and test for that case -- offering specific advice if detected + auto unanchoredUrl = resolvePath("./" + moduleId); + if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) { + auto msg = QString("relative module ids must be anchored; use './%1' instead") + .arg(moduleId); + return throwResolveError(_engine->makeError(_engine->newValue(message.arg(msg)))); + } + } + return throwResolveError(_engine->makeError(_engine->newValue(message.arg("system module not found")))); + } + } + + if (url.isRelative()) { + return throwResolveError(_engine->makeError(_engine->newValue(message.arg("could not resolve module id")))); + } + + // if it looks like a local file, verify that it's an allowed path and really a file + if (url.isLocalFile()) { + QFileInfo file(url.toLocalFile()); + QUrl canonical = url; + if (file.exists()) { + canonical.setPath(file.canonicalFilePath()); + } + + bool disallowOutsideFiles = !PathUtils::defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile(); + if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) { + return throwResolveError(_engine->makeError(_engine->newValue(message.arg( + QString("path '%1' outside of origin script '%2' '%3'") + .arg(PathUtils::stripFilename(url)) + .arg(PathUtils::stripFilename(currentSandboxURL)) + .arg(canonical.toString()) + )))); + } + if (!file.exists()) { + return throwResolveError(_engine->makeError(_engine->newValue(message.arg("path does not exist: " + url.toLocalFile())))); + } + if (!file.isFile()) { + return throwResolveError(_engine->makeError(_engine->newValue(message.arg("path is not a file: " + url.toLocalFile())))); + } + } + + _engine->maybeEmitUncaughtException(__FUNCTION__); + return url.toString(); +} + +// retrieves the current parent module from the JS scope chain +ScriptValue ScriptManager::currentModule() { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return _engine->nullValue(); + } + auto jsRequire = _engine->globalObject().property("Script").property("require"); + auto cache = jsRequire.property("cache"); + ScriptValue candidate; + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan + for (auto context = _engine->currentContext(); context && !candidate.isObject(); + parentContext = context->parentContext(), context = parentContext.get()) { + auto contextInfo = context->functionContext(); + candidate = cache.property(contextInfo->fileName()); + } + if (!candidate.isObject()) { + return ScriptValue(); + } + return candidate; +} + +// replaces or adds "module" to "parent.children[]" array +// (for consistency with Node.js and userscript cache invalidation without "cache busters") +bool ScriptManager::registerModuleWithParent(const ScriptValue& module, const ScriptValue& parent) { + auto children = parent.property("children"); + if (children.isArray()) { + auto key = module.property("id"); + auto length = children.property("length").toInt32(); + for (int i = 0; i < length; i++) { + if (children.property(i).property("id").strictlyEquals(key)) { + qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module"; + children.setProperty(i, module); + return true; + } + } + qCDebug(scriptengine_module) << key.toString() << " appending parent.children[" << length << "] = module"; + children.setProperty(length, module); + return true; + } else if (parent.isValid()) { + qCDebug(scriptengine_module) << "registerModuleWithParent -- unrecognized parent" << parent.toVariant().toString(); + } + return false; +} + +// creates a new JS "module" Object with default metadata properties +ScriptValue ScriptManager::newModule(const QString& modulePath, const ScriptValue& parent) { + auto closure = _engine->newObject(); + auto exports = _engine->newObject(); + auto module = _engine->newObject(); + qCDebug(scriptengine_module) << "newModule" << parent.property("filename").toString(); + + closure.setProperty("module", module, READONLY_PROP_FLAGS); + + // note: this becomes the "exports" free variable, so should not be set read only + closure.setProperty("exports", exports); + + // make the closure available to module instantiation + module.setProperty("__closure__", closure, READONLY_HIDDEN_PROP_FLAGS); + + // for consistency with Node.js Module + module.setProperty("id", modulePath, READONLY_PROP_FLAGS); + module.setProperty("filename", modulePath, READONLY_PROP_FLAGS); + module.setProperty("exports", exports); // not readonly + module.setProperty("loaded", false, READONLY_PROP_FLAGS); + module.setProperty("parent", parent, READONLY_PROP_FLAGS); + module.setProperty("children", _engine->newArray(), READONLY_PROP_FLAGS); + + // module.require is a bound version of require that always resolves relative to that module's path + auto boundRequire = _engine->evaluate("(function(id) { return Script.require(Script.require.resolve(id, this.filename)); })", "(boundRequire)"); + module.setProperty("require", boundRequire, READONLY_PROP_FLAGS); + + return module; +} + +// synchronously fetch a module's source code using BatchLoader +QVariantMap ScriptManager::fetchModuleSource(const QString& modulePath, const bool forceDownload) { + using UrlMap = QMap; + auto scriptCache = DependencyManager::get(); + QVariantMap req; + qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread(); + + auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) { + auto url = modulePath; + auto status = _status[url]; + auto contents = data[url]; + if (isStopping()) { + req["status"] = "Stopped"; + req["success"] = false; + } else { + req["url"] = url; + req["status"] = status; + req["success"] = ScriptCache::isSuccessStatus(status); + req["contents"] = contents; + } + }; + + if (forceDownload) { + qCDebug(scriptengine_module) << "require.requestScript -- clearing cache for" << modulePath; + scriptCache->deleteScript(modulePath); + } + BatchLoader* loader = new BatchLoader(QList({ modulePath })); + connect(loader, &BatchLoader::finished, this, onload); + connect(this, &QObject::destroyed, loader, &QObject::deleteLater); + // fail faster? (since require() blocks the engine thread while resolving dependencies) + const int MAX_RETRIES = 1; + + loader->start(MAX_RETRIES); + + if (!loader->isFinished()) { + // This lambda can get called AFTER this local scope has completed. + // This is why we pass smart ptrs to the lambda instead of references to local variables. + auto monitor = std::make_shared(); + auto loop = std::make_shared(); + QObject::connect(loader, &BatchLoader::finished, this, [monitor, loop] { + monitor->stop(); + loop->quit(); + }); + + // this helps detect the case where stop() is invoked during the download + // but not seen in time to abort processing in onload()... + connect(monitor.get(), &QTimer::timeout, this, [this, loop] { + if (isStopping()) { + loop->exit(-1); + } + }); + monitor->start(500); + loop->exec(); + } + loader->deleteLater(); + return req; +} + +// evaluate a pending module object using the fetched source code +ScriptValue ScriptManager::instantiateModule(const ScriptValue& module, const QString& sourceCode) { + ScriptValue result; + auto modulePath = module.property("filename").toString(); + auto closure = module.property("__closure__"); + + qCDebug(scriptengine_module) << QString("require.instantiateModule: %1 / %2 bytes") + .arg(QUrl(modulePath).fileName()).arg(sourceCode.length()); + + if (module.property("content-type").toString() == "application/json") { + qCDebug(scriptengine_module) << "... parsing as JSON"; + closure.setProperty("__json", sourceCode); + result = _engine->evaluateInClosure(closure, _engine->newProgram( "module.exports = JSON.parse(__json)", modulePath )); + } else { + // scoped vars for consistency with Node.js + closure.setProperty("require", module.property("require")); + closure.setProperty("__filename", modulePath, READONLY_HIDDEN_PROP_FLAGS); + closure.setProperty("__dirname", QString(modulePath).replace(QRegExp("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS); + result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath )); + } + _engine->maybeEmitUncaughtException(__FUNCTION__); + return result; +} + +// CommonJS/Node.js like require/module support +ScriptValue ScriptManager::require(const QString& moduleId) { + qCDebug(scriptengine_module) << "ScriptManager::require(" << moduleId.left(MAX_DEBUG_VALUE_LENGTH) << ")"; + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return _engine->nullValue(); + } + + auto jsRequire = _engine->globalObject().property("Script").property("require"); + auto cacheMeta = jsRequire.data(); + auto cache = jsRequire.property("cache"); + auto parent = currentModule(); + + auto throwModuleError = [&](const QString& modulePath, const ScriptValue& error) { + cache.setProperty(modulePath, _engine->nullValue()); + if (!error.isNull()) { +#ifdef DEBUG_JS_MODULES + qCWarning(scriptengine_module) << "throwing module error:" << error.toString() << modulePath << error.property("stack").toString(); +#endif + _engine->raiseException(error); + } + _engine->maybeEmitUncaughtException("module"); + return _engine->nullValue(); + }; + + // start by resolving the moduleId into a fully-qualified path/URL + QString modulePath = _requireResolve(moduleId); + if (modulePath.isNull() || _engine->hasUncaughtException()) { + // the resolver already threw an exception -- bail early + _engine->maybeEmitUncaughtException(__FUNCTION__); + return _engine->nullValue(); + } + + // check the resolved path against the cache + auto module = cache.property(modulePath); + + // modules get cached in `Script.require.cache` and (similar to Node.js) users can access it + // to inspect particular entries and invalidate them by deleting the key: + // `delete Script.require.cache[Script.require.resolve(moduleId)];` + + // Check to see if we should invalidate the cache based on a user setting. + Setting::Handle getCachebustSetting {"cachebustScriptRequire", false }; + + // cacheMeta is just used right now to tell deleted keys apart from undefined ones + bool invalidateCache = getCachebustSetting.get() || (module.isUndefined() && cacheMeta.property(moduleId).isValid()); + + // reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load + cacheMeta.setProperty(modulePath, ScriptValue()); + + auto exports = module.property("exports"); + if (!invalidateCache && exports.isObject()) { + // we have found a cached module -- just need to possibly register it with current parent + qCDebug(scriptengine_module) << QString("require - using cached module for '%1' (loaded: %2)") + .arg(moduleId).arg(module.property("loaded").toString()); + registerModuleWithParent(module, parent); + _engine->maybeEmitUncaughtException("cached module"); + return exports; + } + + // bootstrap / register new empty module + module = newModule(modulePath, parent); + registerModuleWithParent(module, parent); + + // add it to the cache (this is done early so any cyclic dependencies pick up) + cache.setProperty(modulePath, module); + + // download the module source + auto req = fetchModuleSource(modulePath, invalidateCache); + + if (!req.contains("success") || !req["success"].toBool()) { + auto error = QString("error retrieving script (%1)").arg(req["status"].toString()); + return throwModuleError(modulePath, _engine->newValue(error)); + } + +#if DEBUG_JS_MODULES + qCDebug(scriptengine_module) << "require.loaded: " << + QUrl(req["url"].toString()).fileName() << req["status"].toString(); +#endif + + auto sourceCode = req["contents"].toString(); + + if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) { + module.setProperty("content-type", "application/json"); + } else { + module.setProperty("content-type", "application/javascript"); + } + + // evaluate the module + auto result = instantiateModule(module, sourceCode); + + if (result.isError() && !result.strictlyEquals(module.property("exports"))) { + qCWarning(scriptengine_module) << "-- result.isError --" << result.toString(); + return throwModuleError(modulePath, result); + } + + // mark as fully-loaded + module.setProperty("loaded", true, READONLY_PROP_FLAGS); + + // set up a new reference point for detecting cache key deletion + cacheMeta.setProperty(modulePath, module); + + qCDebug(scriptengine_module) << "//ScriptManager::require(" << moduleId << ")"; + + _engine->maybeEmitUncaughtException(__FUNCTION__); + return module.property("exports"); +} + +// If a callback is specified, the included files will be loaded asynchronously and the callback will be called +// when all of the files have finished loading. +// If no callback is specified, the included files will be loaded synchronously and will block execution until +// all of the files have finished loading. +void ScriptManager::include(const QStringList& includeFiles, const ScriptValue& callback) { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return; + } + if (isStopped()) { + scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" + + includeFiles.join(",") + "parent script:" + getFilename()); + return; // bail early + } + QList urls; + + for (QString includeFile : includeFiles) { + QString file = DependencyManager::get()->normalizeURL(includeFile); + QUrl thisURL; + bool isStandardLibrary = false; + if (file.startsWith("/~/")) { + thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + if (!defaultScriptsLoc.isParentOf(thisURL)) { + scriptWarningMessage("Script.include() -- skipping" + file + "-- outside of standard libraries"); + continue; + } + isStandardLibrary = true; + } else { + thisURL = resolvePath(file); + } + + bool disallowOutsideFiles = thisURL.isLocalFile() && !isStandardLibrary && !currentSandboxURL.isLocalFile(); + if (disallowOutsideFiles && !PathUtils::isDescendantOf(thisURL, currentSandboxURL)) { + scriptWarningMessage("Script.include() ignoring file path" + thisURL.toString() + + "outside of original entity script" + currentSandboxURL.toString()); + } else { + // We could also check here for CORS, but we don't yet. + // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. + urls.append(thisURL); + } + } + + // If there are no URLs left to download, don't bother attempting to download anything and return early + if (urls.size() == 0) { + return; + } + + BatchLoader* loader = new BatchLoader(urls); + EntityItemID capturedEntityIdentifier = currentEntityIdentifier; + QUrl capturedSandboxURL = currentSandboxURL; + + auto evaluateScripts = [=](const QMap& data, const QMap& status) { + auto parentURL = _parentURL; + for (QUrl url : urls) { + QString contents = data[url]; + if (contents.isNull()) { + scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString()); + } else { + std::lock_guard lock(_lock); + if (!_includedURLs.contains(url)) { + _includedURLs << url; + // Set the parent url so that path resolution will be relative + // to this script's url during its initial evaluation + _parentURL = url.toString(); + auto operation = [&]() { + _engine->evaluate(contents, url.toString()); + }; + + doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); + if(_engine->hasUncaughtException()) { + emit unhandledException(_engine->cloneUncaughtException("evaluateInclude")); + _engine->clearExceptions(); + } + } else { + scriptPrintedMessage("Script.include() skipping evaluation of previously included url:" + url.toString()); + } + } + } + _parentURL = parentURL; + + if (callback.isFunction()) { + callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, callback, ScriptValue(), ScriptValueList()); + } + + loader->deleteLater(); + }; + + connect(loader, &BatchLoader::finished, this, evaluateScripts); + + // If we are destroyed before the loader completes, make sure to clean it up + connect(this, &QObject::destroyed, loader, &QObject::deleteLater); + + loader->start(processLevelMaxRetries); + + if (!callback.isFunction() && !loader->isFinished()) { + QEventLoop loop; + QObject::connect(loader, &BatchLoader::finished, &loop, &QEventLoop::quit); + loop.exec(); + } +} + +void ScriptManager::include(const QString& includeFile, const ScriptValue& callback) { + if (isStopped()) { + scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" + + includeFile + "parent script:" + getFilename()); + return; // bail early + } + + QStringList urls; + urls.append(includeFile); + include(urls, callback); +} + +// NOTE: The load() command is similar to the include() command except that it loads the script +// as a stand-alone script. To accomplish this, the ScriptManager class just emits a signal which +// the Application or other context will connect to in order to know to actually load the script +void ScriptManager::load(const QString& loadFile) { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return; + } + if (isStopped()) { + scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" + + loadFile + "parent script:" + getFilename()); + return; // bail early + } + if (!currentEntityIdentifier.isInvalidID()) { + scriptWarningMessage("Script.load() from entity script is ignored... loadFile:" + + loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString()); + return; // bail early + } + + QUrl url = resolvePath(loadFile); + if (_isReloading) { + auto scriptCache = DependencyManager::get(); + scriptCache->deleteScript(url.toString()); + emit reloadScript(url.toString(), false); + } else { + emit loadScript(url.toString(), false); + } +} + +// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args +void ScriptManager::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, const ScriptValueList& eventHandlerArgs) { + if (QThread::currentThread() != thread()) { + qCDebug(scriptengine) << "*** ERROR *** ScriptManager::forwardHandlerCall() 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; + } + CallbackList handlersForEvent = handlersOnEntity[eventName]; + if (!handlersForEvent.isEmpty()) { + for (int i = 0; i < handlersForEvent.count(); ++i) { + // handlersForEvent[i] can contain many handlers that may have each been added by different interface or entity scripts, + // and the entity scripts may be for entities other than the one this is a handler for. + // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added. + CallbackData& handler = handlersForEvent[i]; + callWithEnvironment(handler.definingEntityIdentifier, handler.definingSandboxURL, handler.function, ScriptValue(), eventHandlerArgs); + } + } +} + +int ScriptManager::getNumRunningEntityScripts() const { + QReadLocker locker { &_entityScriptsLock }; + int sum = 0; + for (const auto& st : _entityScripts) { + if (st.status == EntityScriptStatus::RUNNING) { + ++sum; + } + } + return sum; +} + +void ScriptManager::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) { + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts[entityID] = details; + } + emit entityScriptDetailsUpdated(); +} + +void ScriptManager::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) { + { + QWriteLocker locker { &_entityScriptsLock }; + EntityScriptDetails& details = _entityScripts[entityID]; + details.status = status; + details.errorInfo = errorInfo; + } + emit entityScriptDetailsUpdated(); +} + +QVariant ScriptManager::cloneEntityScriptDetails(const EntityItemID& entityID) { + static const QVariant NULL_VARIANT { qVariantFromValue((QObject*)nullptr) }; + QVariantMap map; + if (entityID.isNull()) { + // TODO: find better way to report JS Error across thread/process boundaries + map["isError"] = true; + map["errorInfo"] = "Error: getEntityScriptDetails -- invalid entityID"; + } else { +#ifdef DEBUG_ENTITY_STATES + qDebug() << "cloneEntityScriptDetails" << entityID << QThread::currentThread(); +#endif + EntityScriptDetails scriptDetails; + if (getEntityScriptDetails(entityID, scriptDetails)) { +#ifdef DEBUG_ENTITY_STATES + qDebug() << "gotEntityScriptDetails" << scriptDetails.status << QThread::currentThread(); +#endif + map["isRunning"] = isEntityScriptRunning(entityID); + map["status"] = EntityScriptStatus_::valueToKey(scriptDetails.status).toLower(); + map["errorInfo"] = scriptDetails.errorInfo; + map["entityID"] = entityID.toString(); +#ifdef DEBUG_ENTITY_STATES + { + auto debug = QVariantMap(); + debug["script"] = scriptDetails.scriptText; + debug["scriptObject"] = scriptDetails.scriptObject.toVariant(); + debug["lastModified"] = (qlonglong)scriptDetails.lastModified; + debug["sandboxURL"] = scriptDetails.definingSandboxURL; + map["debug"] = debug; + } +#endif + } else { +#ifdef DEBUG_ENTITY_STATES + qDebug() << "!gotEntityScriptDetails" << QThread::currentThread(); +#endif + map["isError"] = true; + map["errorInfo"] = "Entity script details unavailable"; + map["entityID"] = entityID.toString(); + } + } + return map; +} + +QFuture ScriptManager::getLocalEntityScriptDetails(const EntityItemID& entityID) { + return QtConcurrent::run(this, &ScriptManager::cloneEntityScriptDetails, entityID); +} + +bool ScriptManager::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + if (it == _entityScripts.constEnd()) { + return false; + } + details = it.value(); + return true; +} + +bool ScriptManager::hasEntityScriptDetails(const EntityItemID& entityID) const { + QReadLocker locker { &_entityScriptsLock }; + return _entityScripts.contains(entityID); +} + +void ScriptManager::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadEntityScript", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, entityScript), + Q_ARG(bool, forceRedownload) + ); + return; + } + PROFILE_RANGE(script, __FUNCTION__); + + QSharedPointer scriptEngines(_scriptEngines); + if (isStopping() || !scriptEngines || scriptEngines->isStopped()) { + qCDebug(scriptengine) << "loadEntityScript.start " << entityID.toString() + << " but isStopping==" << isStopping() + << " || engines->isStopped==" << scriptEngines->isStopped(); + return; + } + + if (!hasEntityScriptDetails(entityID)) { + // make sure EntityScriptDetails has an entry for this UUID right away + // (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight) + updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); + } + +#ifdef DEBUG_ENTITY_STATES + { + EntityScriptDetails details; + bool hasEntityScript = getEntityScriptDetails(entityID, details); + qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityID.toString() + << "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")"; + } +#endif + + EntityScriptDetails newDetails; + newDetails.scriptText = entityScript; + newDetails.status = EntityScriptStatus::LOADING; + newDetails.definingSandboxURL = currentSandboxURL; + setEntityScriptDetails(entityID, newDetails); + + auto scriptCache = DependencyManager::get(); + // note: see EntityTreeRenderer.cpp for shared pointer lifecycle management + std::weak_ptr weakRef(shared_from_this()); + scriptCache->getScriptContents(entityScript, + [this, weakRef, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { + std::shared_ptr strongRef(weakRef); + if (!strongRef) { + qCWarning(scriptengine) << "loadEntityScript.contentAvailable -- ScriptManager was deleted during getScriptContents!!"; + return; + } + if (isStopping()) { +#ifdef DEBUG_ENTITY_STATES + qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- stopping"; +#endif + return; + } + executeOnScriptThread([=]{ +#ifdef DEBUG_ENTITY_STATES + qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << entityID.toString(); +#endif + if (!isStopping() && hasEntityScriptDetails(entityID)) { + _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; + } else { +#ifdef DEBUG_ENTITY_STATES + qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; +#endif + } + }); + }, forceRedownload); +} + +/*@jsdoc + * Triggered when the script starts for a user. See also, {@link Script.entityScriptPreloadFinished}. + *

Note: Can only be connected to via this.preload = function (...) { ... } in the entity script.

+ *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

+ * @function Entities.preload + * @param {Uuid} entityID - The ID of the entity that the script is running in. + * @returns {Signal} + * @example Get the ID of the entity that a client entity script is running in. + * var entityScript = (function () { + * this.entityID = Uuid.NULL; + * + * this.preload = function (entityID) { + * this.entityID = entityID; + * print("Entity ID: " + this.entityID); + * }; + * }); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 255, green: 0, blue: 0 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ +// The JSDoc is for the callEntityScriptMethod() call in this method. +// since all of these operations can be asynch we will always do the actual work in the response handler +// for the download +void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::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), + Q_ARG(const QString&, status)); + return; + } + +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; +#endif + + auto scriptCache = DependencyManager::get(); + bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); + auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript"; + + QString entityScript; + { + QWriteLocker locker { &_entityScriptsLock }; + entityScript = _entityScripts[entityID].scriptText; + } + + EntityScriptDetails newDetails; + newDetails.scriptText = scriptOrURL; + + // If an error happens below, we want to update newDetails with the new status info + // and also abort any pending Entity loads that are waiting on the exact same script URL. + auto setError = [&](const QString &errorInfo, const EntityScriptStatus& status) { + newDetails.errorInfo = errorInfo; + newDetails.status = status; + setEntityScriptDetails(entityID, newDetails); + }; + + // NETWORK / FILESYSTEM ERRORS + if (!success) { + setError("Failed to load script (" + status + ")", EntityScriptStatus::ERROR_LOADING_SCRIPT); + return; + } + + // SYNTAX ERRORS + auto syntaxError = _engine->lintScript(contents, fileName); + if (syntaxError.isError()) { + auto message = syntaxError.property("formatted").toString(); + if (message.isEmpty()) { + message = syntaxError.toString(); + } + setError(QString("Bad syntax (%1)").arg(message), EntityScriptStatus::ERROR_RUNNING_SCRIPT); + syntaxError.setProperty("detail", entityID.toString()); + emit unhandledException(syntaxError); + return; + } + auto program = _engine->newProgram( contents, fileName ); + if (!program) { + setError("Bad program (isNull)", EntityScriptStatus::ERROR_RUNNING_SCRIPT); + emit unhandledException(_engine->makeError(_engine->newValue("program.isNull"))); + return; // done processing script + } + + if (isURL) { + setParentURL(scriptOrURL); + } + + // SANITY/PERFORMANCE CHECK USING SANDBOX + const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND; + ScriptEnginePointer sandbox = newScriptEngine(); + sandbox->setProcessEventsInterval(SANDBOX_TIMEOUT); + ScriptValue testConstructor, exception; + if (atoi(getenv("UNSAFE_ENTITY_SCRIPTS") ? getenv("UNSAFE_ENTITY_SCRIPTS") : "0")) + { + QTimer timeout; + timeout.setSingleShot(true); + timeout.start(SANDBOX_TIMEOUT); + connect(&timeout, &QTimer::timeout, [=, &sandbox]{ + qCDebug(scriptengine) << "ScriptManager::entityScriptContentAvailable timeout"; + + // Guard against infinite loops and non-performant code + sandbox->raiseException( + sandbox->makeError(sandbox->newValue(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT))) + ); + }); + + testConstructor = sandbox->evaluate(program); + + if (sandbox->hasUncaughtException()) { + exception = sandbox->cloneUncaughtException(QString("(preflight %1)").arg(entityID.toString())); + sandbox->clearExceptions(); + } else if (testConstructor.isError()) { + exception = testConstructor; + } + } else { + // ENTITY SCRIPT WHITELIST STARTS HERE + auto nodeList = DependencyManager::get(); + bool passList = false; // assume unsafe + QString whitelistPrefix = "[WHITELIST ENTITY SCRIPTS]"; + QList safeURLPrefixes = { "file:///", "atp:", "cache:" }; + safeURLPrefixes += qEnvironmentVariable("EXTRA_WHITELIST").trimmed().split(QRegExp("\\s*,\\s*"), Qt::SkipEmptyParts); + + // Entity Script Whitelist toggle check. + Setting::Handle whitelistEnabled {"private/whitelistEnabled", false }; + + if (!whitelistEnabled.get()) { + passList = true; + } + + // Pull SAFEURLS from the Interface.JSON settings. + QVariant raw = Setting::Handle("private/settingsSafeURLS").get(); + QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegExp("\\s*[,\r\n]+\\s*"), Qt::SkipEmptyParts); + safeURLPrefixes += settingsSafeURLS; + // END Pull SAFEURLS from the Interface.JSON settings. + + // Get current domain whitelist bypass, in case an entire domain is whitelisted. + QString currentDomain = DependencyManager::get()->getDomainURL().host(); + + QString domainSafeIP = nodeList->getDomainHandler().getHostname(); + QString domainSafeURL = URL_SCHEME_VIRCADIA + "://" + currentDomain; + for (const auto& str : safeURLPrefixes) { + if (domainSafeURL.startsWith(str) || domainSafeIP.startsWith(str)) { + qCDebug(scriptengine) << whitelistPrefix << "Whitelist Bypassed, entire domain is whitelisted. Current Domain Host: " + << nodeList->getDomainHandler().getHostname() + << "Current Domain: " << currentDomain; + passList = true; + } + } + // END bypass whitelist based on current domain. + + // Start processing scripts through the whitelist. + if (ScriptManager::getContext() == "entity_server") { // If running on the server, do not engage whitelist. + passList = true; + } else if (!passList) { // If waved through, do not engage whitelist. + for (const auto& str : safeURLPrefixes) { + qCDebug(scriptengine) << whitelistPrefix << "Script URL: " << scriptOrURL << "TESTING AGAINST" << str << "RESULTS IN" + << scriptOrURL.startsWith(str); + if (!str.isEmpty() && scriptOrURL.startsWith(str)) { + passList = true; + qCDebug(scriptengine) << whitelistPrefix << "Script approved."; + break; // Bail early since we found a match. + } + } + } + // END processing of scripts through the whitelist. + + if (!passList) { // If the entity failed to pass for any reason, it's blocked and an error is thrown. + qCDebug(scriptengine) << whitelistPrefix << "(disabled entity script)" << entityID.toString() << scriptOrURL; + exception = _engine->makeError(_engine->newValue("UNSAFE_ENTITY_SCRIPTS == 0")); + } else { + QTimer timeout; + timeout.setSingleShot(true); + timeout.start(SANDBOX_TIMEOUT); + connect(&timeout, &QTimer::timeout, [=, &sandbox] { + qCDebug(scriptengine) << "ScriptManager::entityScriptContentAvailable timeout"; + + // Guard against infinite loops and non-performant code + sandbox->raiseException( + sandbox->makeError(sandbox->newValue(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT)))); + }); + + testConstructor = sandbox->evaluate(program); + + if (sandbox->hasUncaughtException()) { + exception = sandbox->cloneUncaughtException(QString("(preflight %1)").arg(entityID.toString())); + sandbox->clearExceptions(); + } else if (testConstructor.isError()) { + exception = testConstructor; + } + } + // ENTITY SCRIPT WHITELIST ENDS HERE, uncomment below for original full disabling. + + // qDebug() << "(disabled entity script)" << entityID.toString() << scriptOrURL; + // exception = makeError("UNSAFE_ENTITY_SCRIPTS == 0"); + } + + if (exception.isError()) { + // create a local copy using makeError to decouple from the sandbox engine + exception = _engine->makeError(exception); + setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); + emit unhandledException(exception); + return; + } + + // CONSTRUCTOR VIABILITY + if (!testConstructor.isFunction()) { + QString testConstructorType = QString(testConstructor.toVariant().typeName()); + if (testConstructorType == "") { + testConstructorType = "empty"; + } + QString testConstructorValue = testConstructor.toString(); + if (testConstructorValue.size() > MAX_DEBUG_VALUE_LENGTH) { + testConstructorValue = testConstructorValue.mid(0, MAX_DEBUG_VALUE_LENGTH) + "..."; + } + auto message = QString("failed to load entity script -- expected a function, got %1, %2") + .arg(testConstructorType).arg(testConstructorValue); + + auto err = _engine->makeError(_engine->newValue(message)); + err.setProperty("fileName", scriptOrURL); + err.setProperty("detail", "(constructor " + entityID.toString() + ")"); + + setError("Could not find constructor (" + testConstructorType + ")", EntityScriptStatus::ERROR_RUNNING_SCRIPT); + emit unhandledException(err); + return; // done processing script + } + + // (this feeds into refreshFileScript) + int64_t lastModified = 0; + if (isFileUrl) { + QString file = QUrl(scriptOrURL).toLocalFile(); + lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); + } + + // THE ACTUAL EVALUATION AND CONSTRUCTION + ScriptValue entityScriptConstructor, entityScriptObject; + QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL; + auto initialization = [&]{ + entityScriptConstructor = _engine->evaluate(contents, fileName); + entityScriptObject = entityScriptConstructor.construct(); + + if (_engine->hasUncaughtException()) { + entityScriptObject = _engine->cloneUncaughtException("(construct " + entityID.toString() + ")"); + _engine->clearExceptions(); + } + }; + + doWithEnvironment(entityID, sandboxURL, initialization); + + if (entityScriptObject.isError()) { + auto exception = entityScriptObject; + setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); + emit unhandledException(exception); + return; + } + + // ... AND WE HAVE LIFTOFF + newDetails.status = EntityScriptStatus::RUNNING; + newDetails.scriptObject = entityScriptObject; + newDetails.lastModified = lastModified; + newDetails.definingSandboxURL = sandboxURL; + setEntityScriptDetails(entityID, newDetails); + + if (isURL) { + setParentURL(""); + } + + // if we got this far, then call the preload method + callEntityScriptMethod(entityID, "preload"); + + emit entityScriptPreloadFinished(entityID); +} + +/*@jsdoc + * Triggered when the script terminates for a user. + *

Note: Can only be connected to via this.unoad = function () { ... } in the entity script.

+ *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

+ * @function Entities.unload + * @param {Uuid} entityID - The ID of the entity that the script is running in. + * @returns {Signal} + */ +// The JSDoc is for the callEntityScriptMethod() call in this method. +void ScriptManager::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID; +#endif + + QMetaObject::invokeMethod(this, "unloadEntityScript", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(bool, shouldRemoveFromMap)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::unloadEntityScript() called on correct thread [" << thread() << "] " + "entityID:" << entityID; +#endif + + EntityScriptDetails oldDetails; + if (getEntityScriptDetails(entityID, oldDetails)) { + auto scriptText = oldDetails.scriptText; + + if (isEntityScriptRunning(entityID)) { + callEntityScriptMethod(entityID, "unload"); + } +#ifdef DEBUG_ENTITY_STATES + else { + qCDebug(scriptengine) << "unload called while !running" << entityID << oldDetails.status; + } +#endif + if (shouldRemoveFromMap) { + // this was a deleted entity, we've been asked to remove it from the map + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts.remove(entityID); + } + emit entityScriptDetailsUpdated(); + } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { + EntityScriptDetails newDetails; + newDetails.status = EntityScriptStatus::UNLOADED; + newDetails.lastModified = QDateTime::currentMSecsSinceEpoch(); + // keep scriptText populated for the current need to "debouce" duplicate calls to unloadEntityScript + newDetails.scriptText = scriptText; + setEntityScriptDetails(entityID, newDetails); + } + + stopAllTimersForEntityScript(entityID); + } +} + +QList ScriptManager::getListOfEntityScriptIDs() { + QReadLocker locker{ &_entityScriptsLock }; + return _entityScripts.keys(); +} + +void ScriptManager::unloadAllEntityScripts(bool blockingCall) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + + QMetaObject::invokeMethod(this, "unloadAllEntityScripts", + blockingCall ? Qt::BlockingQueuedConnection : Qt::QueuedConnection); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; +#endif + + QList keys; + { + QReadLocker locker{ &_entityScriptsLock }; + keys = _entityScripts.keys(); + } + foreach(const EntityItemID& entityID, keys) { + unloadEntityScript(entityID); + } + { + QWriteLocker locker{ &_entityScriptsLock }; + _entityScripts.clear(); + } + emit entityScriptDetailsUpdated(); + +#ifdef DEBUG_ENGINE_STATE + _debugDump( + "---- CURRENT STATE OF ENGINE: --------------------------", + globalObject(), + "--------------------------------------------------------" + ); +#endif // DEBUG_ENGINE_STATE +} + +void ScriptManager::refreshFileScript(const EntityItemID& entityID) { + if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { + return; + } + + static bool recurseGuard = false; + if (recurseGuard) { + return; + } + recurseGuard = true; + + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } + // Check to see if a file based script needs to be reloaded (easier debugging) + if (details.lastModified > 0) { + QString filePath = QUrl(details.scriptText).toLocalFile(); + auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch(); + if (lastModified > details.lastModified) { + scriptInfoMessage("Reloading modified script " + details.scriptText); + loadEntityScript(entityID, details.scriptText, true); + } + } + recurseGuard = false; +} + +// Execute operation in the appropriate context for (the possibly empty) entityID. +// Even if entityID is supplied as currentEntityIdentifier, this still documents the source +// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different +// global values for different entity scripts). +void ScriptManager::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation) { + EntityItemID oldIdentifier = currentEntityIdentifier; + QUrl oldSandboxURL = currentSandboxURL; + currentEntityIdentifier = entityID; + currentSandboxURL = sandboxURL; + +#if DEBUG_CURRENT_ENTITY + ScriptValue oldData = this->globalObject().property("debugEntityID"); + this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global. + operation(); + this->globalObject().setProperty("debugEntityID", oldData); +#else + operation(); +#endif + _engine->maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__); + currentEntityIdentifier = oldIdentifier; + currentSandboxURL = oldSandboxURL; +} + +void ScriptManager::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, const ScriptValue& function, const ScriptValue& thisObject, const ScriptValueList& args) { + auto operation = [&]() { + function.call(thisObject, args); + }; + doWithEnvironment(entityID, sandboxURL, operation); +} + +void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName; +#endif + + QMetaObject::invokeMethod(this, "callEntityScriptMethod", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const QStringList&, params), + Q_ARG(const QUuid&, remoteCallerID)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName; +#endif + + if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") { + refreshFileScript(entityID); + } + if (isEntityScriptRunning(entityID)) { + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } + ScriptValue entityScript = details.scriptObject; // previously loaded + + // If this is a remote call, we need to check to see if the function is remotely callable + // we do this by checking for the existance of the 'remotelyCallable' property on the + // entityScript. And we confirm that the method name is included. If this fails, the + // function will not be called. + bool callAllowed = false; + if (remoteCallerID == QUuid()) { + callAllowed = true; + } else { + if (entityScript.property("remotelyCallable").isArray()) { + auto callables = entityScript.property("remotelyCallable"); + auto callableCount = callables.property("length").toInteger(); + for (int i = 0; i < callableCount; i++) { + auto callable = callables.property(i).toString(); + if (callable == methodName) { + callAllowed = true; + break; + } + } + } + if (!callAllowed) { + qDebug() << "Method [" << methodName << "] not remotely callable."; + } + } + + if (callAllowed && entityScript.property(methodName).isFunction()) { + auto scriptEngine = engine().get(); + + ScriptValueList args; + args << EntityItemIDtoScriptValue(scriptEngine, entityID); + args << scriptValueFromSequence(scriptEngine, params); + + ScriptValue oldData = scriptEngine->globalObject().property("Script").property("remoteCallerID"); + scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", remoteCallerID.toString()); // Make the remoteCallerID available to javascript as a global. + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); + scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", oldData); + } + } +} + +void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; +#endif + + QMetaObject::invokeMethod(this, "callEntityScriptMethod", + Q_ARG(const EntityItemID&, entityID), + Q_ARG(const QString&, methodName), + Q_ARG(const PointerEvent&, event)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent"; +#endif + + if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { + refreshFileScript(entityID); + } + if (isEntityScriptRunning(entityID)) { + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } + ScriptValue entityScript = details.scriptObject; // previously loaded + if (entityScript.property(methodName).isFunction()) { + auto scriptEngine = engine().get(); + + ScriptValueList args; + args << EntityItemIDtoScriptValue(scriptEngine, entityID); + args << event.toScriptValue(scriptEngine); + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); + } + } +} + +void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; +#endif + + 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; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " + "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; +#endif + + if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { + refreshFileScript(entityID); + } + if (isEntityScriptRunning(entityID)) { + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } + ScriptValue entityScript = details.scriptObject; // previously loaded + if (entityScript.property(methodName).isFunction()) { + auto scriptEngine = engine().get(); + + ScriptValueList args; + args << EntityItemIDtoScriptValue(scriptEngine, entityID); + args << EntityItemIDtoScriptValue(scriptEngine, otherID); + args << collisionToScriptValue(scriptEngine, collision); + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); + } + } +} + +QString ScriptManager::getExternalPath(ExternalResource::Bucket bucket, const QString& path) { + return ExternalResource::getInstance()->getUrl(bucket, path); +} + +QString ScriptManager::formatException(const ScriptValue& exception, bool includeExtendedDetails) { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return QString(); + } + QString note{ "UncaughtException" }; + QString result; + + if (!exception.isObject()) { + return result; + } + const auto message = exception.toString(); + const auto fileName = exception.property("fileName").toString(); + const auto lineNumber = exception.property("lineNumber").toString(); + const auto stacktrace = exception.property("stack").toString(); + + if (includeExtendedDetails) { + // Display additional exception / troubleshooting hints that can be added via the custom Error .detail property + // Example difference: + // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n... + // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n... + if (exception.property("detail").isValid()) { + note += " " + exception.property("detail").toString(); + } + } + + result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); + if (!stacktrace.isEmpty()) { + result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); + } + return result; +} + +ScriptValue ScriptManager::evaluate(const QString& program, const QString& fileName) { + return _engine->evaluate(program, fileName); +} + +void ScriptManager::requestGarbageCollection() { + _engine->requestCollectGarbage(); +} diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h new file mode 100644 index 00000000000..ae9571cb026 --- /dev/null +++ b/libraries/script-engine/src/ScriptManager.h @@ -0,0 +1,988 @@ +// +// ScriptManager.h +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptManager_h +#define hifi_ScriptManager_h + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "EntityItemID.h" +#include "EntitiesScriptEngineProvider.h" +#include "EntityScriptUtils.h" +#include +#include + +#include "AssetScriptingInterface.h" +#include "ConsoleScriptingInterface.h" +#include "Mat4.h" +#include "PointerEvent.h" +#include "Quat.h" +#include "ScriptUUID.h" +#include "ScriptValue.h" +#include "Vec3.h" + +static const QString NO_SCRIPT(""); + +static const int SCRIPT_FPS = 60; +static const int DEFAULT_MAX_ENTITY_PPS = 9000; +static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; + +class ScriptEngine; +class ScriptEngines; +class ScriptManager; +using ScriptEnginePointer = std::shared_ptr; +using ScriptManagerPointer = std::shared_ptr; +using ScriptValueList = QList; + +Q_DECLARE_METATYPE(ScriptManagerPointer) + +const int QTREGISTER_QTimerStar = qRegisterMetaType(); + +class CallbackData { +public: + ScriptValue function; + EntityItemID definingEntityIdentifier; + QUrl definingSandboxURL; +}; + +class DeferredLoadEntity { +public: + EntityItemID entityID; + QString entityScript; + //bool forceRedownload; +}; + +struct EntityScriptContentAvailable { + EntityItemID entityID; + QString scriptOrURL; + QString contents; + bool isURL; + bool success; + QString status; +}; + +typedef std::unordered_map EntityScriptContentAvailableMap; + +typedef QList CallbackList; +typedef QHash RegisteredEventHandlers; + +class EntityScriptDetails { +public: + EntityScriptStatus status { EntityScriptStatus::PENDING }; + + // If status indicates an error, this contains a human-readable string giving more information about the error. + QString errorInfo { "" }; + + QString scriptText { "" }; + ScriptValue scriptObject{ ScriptValue() }; + int64_t lastModified { 0 }; + QUrl definingSandboxURL { QUrl("about:EntityScript") }; +}; + +// declare a static script initializer +#define STATIC_SCRIPT_INITIALIZER(init) \ + static ScriptManager::StaticInitializerNode static_script_initializer_(init); + +/*@jsdoc + * The Script API provides facilities for working with scripts. + * + * @namespace Script + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {string} context - The context that the script is running in: + *
    + *
  • "client": An Interface or avatar script.
  • + *
  • "entity_client": A client entity script.
  • + *
  • "entity_server": A server entity script.
  • + *
  • "agent": An assignment client script.
  • + *
+ * Read-only. + * @property {string} type - The type of script that is running: + *
    + *
  • "client": An Interface script.
  • + *
  • "entity_client": A client entity script.
  • + *
  • "avatar": An avatar script.
  • + *
  • "entity_server": A server entity script.
  • + *
  • "agent": An assignment client script.
  • + *
+ * Read-only. + * @property {string} filename - The filename of the script file. + * Read-only. + * @property {Script.ResourceBuckets} ExternalPaths - External resource buckets. + */ +/// The main class managing a scripting engine. Also provides the Script scripting interface +class ScriptManager : public QObject, public EntitiesScriptEngineProvider, public std::enable_shared_from_this { + Q_OBJECT + Q_PROPERTY(QString context READ getContext) + Q_PROPERTY(QString type READ getTypeAsString) + Q_PROPERTY(QString fileName MEMBER _fileNameString CONSTANT) +public: + static const QString SCRIPT_EXCEPTION_FORMAT; + static const QString SCRIPT_BACKTRACE_SEP; + + enum Context { + CLIENT_SCRIPT, + ENTITY_CLIENT_SCRIPT, + ENTITY_SERVER_SCRIPT, + AGENT_SCRIPT + }; + + enum Type { + CLIENT, + ENTITY_CLIENT, + ENTITY_SERVER, + AGENT, + AVATAR + }; + Q_ENUM(Type); + + static int processLevelMaxRetries; + ScriptManager(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); + ~ScriptManager(); + + // static initialization support + typedef void (*ScriptManagerInitializer)(ScriptManager*); + class StaticInitializerNode { + public: + ScriptManagerInitializer init; + StaticInitializerNode* prev; + inline StaticInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticInitializer(this); } + }; + static void registerNewStaticInitializer(StaticInitializerNode* dest); + + /// 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(); + + QString getFilename() const; + + inline ScriptEnginePointer engine() { return _engine; } + + QList getListOfEntityScriptIDs(); + + bool isStopped() const; + + /*@jsdoc + * Stops and unloads the current script. + *

Warning: If an assignment client script, the script gets restarted after stopping.

+ * @function Script.stop + * @param {boolean} [marshal=false] - Marshal. + *

Deprecated: This parameter is deprecated and will be removed.

+ * @example Stop a script after 5s. + * Script.setInterval(function () { + * print("Hello"); + * }, 1000); + * + * Script.setTimeout(function () { + * Script.stop(true); + * }, 5000); + */ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts + Q_INVOKABLE void stop(bool marshal = false); + + // Stop any evaluating scripts and wait for the scripting thread to finish. + void waitTillDoneRunning(bool shutdown = false); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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 + + /// 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); + bool hasValidScriptSuffix(const QString& scriptFileName); + + /*@jsdoc + * Gets the context that the script is running in: Interface/avatar, client entity, server entity, or assignment client. + * @function Script.getContext + * @returns {string} The context that the script is running in: + *
    + *
  • "client": An Interface or avatar script.
  • + *
  • "entity_client": A client entity script.
  • + *
  • "entity_server": A server entity script.
  • + *
  • "agent": An assignment client script.
  • + *
+ */ + Q_INVOKABLE QString getContext() const; + + /*@jsdoc + * Checks whether the script is running as an Interface or avatar script. + * @function Script.isClientScript + * @returns {boolean} true if the script is running as an Interface or avatar script, false if it + * isn't. + */ + Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } + + /*@jsdoc + * Checks whether the application was compiled as a debug build. + * @function Script.isDebugMode + * @returns {boolean} true if the application was compiled as a debug build, false if it was + * compiled as a release build. + */ + Q_INVOKABLE bool isDebugMode() const; + + /*@jsdoc + * Checks whether the script is running as a client entity script. + * @function Script.isEntityClientScript + * @returns {boolean} true if the script is running as a client entity script, false if it isn't. + */ + Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } + + /*@jsdoc + * Checks whether the script is running as a server entity script. + * @function Script.isEntityServerScript + * @returns {boolean} true if the script is running as a server entity script, false if it isn't. + */ + Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } + + /*@jsdoc + * Checks whether the script is running as an assignment client script. + * @function Script.isAgentScript + * @returns {boolean} true if the script is running as an assignment client script, false if it + * isn't. + */ + Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } + + /*@jsdoc + * registers a global object by name. + * @function Script.registerValue + * @param {string} valueName + * @param {value} value + */ + /// registers a global object by name + Q_INVOKABLE void registerValue(const QString& valueName, ScriptValue value); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - these are intended to be public interfaces available to scripts + + /*@jsdoc + * @function Script.formatExecption + * @param {object} exception - Exception. + * @param {boolean} inludeExtendeDetails - Include extended details. + * @returns {string} String. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE QString formatException(const ScriptValue& exception, bool includeExtendedDetails); + + /*@jsdoc + * Adds a function to the list of functions called when a particular event occurs on a particular entity. + *

See also, the {@link Entities} API.

+ * @function Script.addEventHandler + * @param {Uuid} entityID - The ID of the entity. + * @param {Script.EntityEvent} eventName - The name of the event. + * @param {Script~entityEventCallback|Script~pointerEventCallback|Script~collisionEventCallback} handler - The function to + * call when the event occurs on the entity. It can be either the name of a function or an in-line definition. + * @example Report when a mouse press occurs on a particular entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * function reportMousePress(entityID, event) { + * print("Mouse pressed on entity: " + JSON.stringify(event)); + * } + * + * Script.addEventHandler(entityID, "mousePressOnEntity", reportMousePress); + */ + Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler); + + /*@jsdoc + * Removes a function from the list of functions called when an entity event occurs on a particular entity. + *

See also, the {@link Entities} API.

+ * @function Script.removeEventHandler + * @param {Uuid} entityID - The ID of the entity. + * @param {Script.EntityEvent} eventName - The name of the entity event. + * @param {function} handler - The name of the function to no longer call when the entity event occurs on the entity. + */ + Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler); + + /*@jsdoc + * Starts running another script in Interface, if it isn't already running. The script is not automatically loaded next + * time Interface starts. + *

Supported Script Types: Interface Scripts • Avatar Scripts

+ *

See also, {@link ScriptDiscoveryService.loadScript}.

+ * @function Script.load + * @param {string} filename - The URL of the script to load. This can be relative to the current script's URL. + * @example Load a script from another script. + * // First file: scriptA.js + * print("This is script A"); + * + * // Second file: scriptB.js + * print("This is script B"); + * Script.load("scriptA.js"); + * + * // If you run scriptB.js you should see both scripts in the Running Scripts dialog. + * // And you should see the following output: + * // This is script B + * // This is script A + */ + Q_INVOKABLE void load(const QString& loadfile); + + /*@jsdoc + * Includes JavaScript from other files in the current script. If a callback is specified, the files are loaded and + * included asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are + * included). + * @function Script.include + * @variation 0 + * @param {string[]} filenames - The URLs of the scripts to include. Each can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the scripts have been included. It can be either the + * name of a function or an in-line definition. + */ + Q_INVOKABLE void include(const QStringList& includeFiles, const ScriptValue& callback = ScriptValue()); + + /*@jsdoc + * Includes JavaScript from another file in the current script. If a callback is specified, the file is loaded and included + * asynchronously, otherwise it is included synchronously (i.e., script execution blocks while the file is included). + * @function Script.include + * @param {string} filename - The URL of the script to include. It can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the script has been included. It can be either the + * name of a function or an in-line definition. + * @example Include a script file asynchronously. + * // First file: scriptA.js + * print("This is script A"); + * + * // Second file: scriptB.js + * print("This is script B"); + * Script.include("scriptA.js", function () { + * print("Script A has been included"); + * }); + * + * // If you run scriptB.js you should see only scriptB.js in the running scripts list. + * // And you should see the following output: + * // This is script B + * // This is script A + * // Script A has been included + */ + Q_INVOKABLE void include(const QString& includeFile, const ScriptValue& callback = ScriptValue()); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MODULE related methods + + /*@jsdoc + * Provides access to methods or objects provided in an external JavaScript or JSON file. + * See {@link https://docs.vircadia.com/script/js-tips.html} for further details. + * @function Script.require + * @param {string} module - The module to use. May be a JavaScript file, a JSON file, or the name of a system module such + * as "appUi" (i.e., the "appUi.js" system module JavaScript file). + * @returns {object|array} The value assigned to module.exports in the JavaScript file, or the value defined + * in the JSON file. + */ + Q_INVOKABLE ScriptValue require(const QString& moduleId); + + /*@jsdoc + * @function Script.resetModuleCache + * @param {boolean} [deleteScriptCache=false] - Delete script cache. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); + + ScriptValue currentModule(); + bool registerModuleWithParent(const ScriptValue& module, const ScriptValue& parent); + ScriptValue newModule(const QString& modulePath, const ScriptValue& parent = ScriptValue()); + QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); + ScriptValue instantiateModule(const ScriptValue& module, const QString& sourceCode); + + ScriptValue evaluate(const QString& program, const QString& fileName = QString()); + + /*@jsdoc + * Calls a function repeatedly, at a set interval. + * @function Script.setInterval + * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. + * @param {number} interval - The interval at which to call the function, in ms. + * @returns {object} A handle to the interval timer. This can be used in {@link Script.clearInterval}. + * @example Print a message every second. + * Script.setInterval(function () { + * print("Interval timer fired"); + * }, 1000); + */ + Q_INVOKABLE QTimer* setInterval(const ScriptValue& function, int intervalMS); + + /*@jsdoc + * Calls a function once, after a delay. + * @function Script.setTimeout + * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. + * @param {number} timeout - The delay after which to call the function, in ms. + * @returns {object} A handle to the timeout timer. This can be used in {@link Script.clearTimeout}. + * @example Print a message once, after a second. + * Script.setTimeout(function () { + * print("Timeout timer fired"); + * }, 1000); + */ + Q_INVOKABLE QTimer* setTimeout(const ScriptValue& function, int timeoutMS); + + /*@jsdoc + * Stops an interval timer set by {@link Script.setInterval|setInterval}. + * @function Script.clearInterval + * @param {object} timer - The interval timer to stop. + * @example Stop an interval timer. + * // Print a message every second. + * var timer = Script.setInterval(function () { + * print("Interval timer fired"); + * }, 1000); + * + * // Stop the timer after 10 seconds. + * Script.setTimeout(function () { + * print("Stop interval timer"); + * Script.clearInterval(timer); + * }, 10000); + */ + Q_INVOKABLE void clearInterval(QTimer* timer) { stopTimer(timer); } + + /*@jsdoc + * Stops a timeout timer set by {@link Script.setTimeout|setTimeout}. + * @function Script.clearTimeout + * @param {object} timer - The timeout timer to stop. + * @example Stop a timeout timer. + * // Print a message after two seconds. + * var timer = Script.setTimeout(function () { + * print("Timer fired"); + * }, 2000); + * + * // Uncomment the following line to stop the timer from firing. + * //Script.clearTimeout(timer); + */ + Q_INVOKABLE void clearTimeout(QTimer* timer) { stopTimer(timer); } + + /*@jsdoc + * Prints a message to the program log and emits {@link Script.printedMessage}. + *

Alternatively, you can use {@link print} or one of the {@link console} API methods.

+ * @function Script.print + * @param {string} message - The message to print. + */ + Q_INVOKABLE void print(const QString& message); + + /*@jsdoc + * Resolves a relative path to an absolute path. The relative path is relative to the script's location. + * @function Script.resolvePath + * @param {string} path - The relative path to resolve. + * @returns {string} The absolute path. + * @example Report the directory and filename of the running script. + * print(Script.resolvePath("")); + * @example Report the directory of the running script. + * print(Script.resolvePath(".")); + * @example Report the path to a file located relative to the running script. + * print(Script.resolvePath("../assets/sounds/hello.wav")); + */ + Q_INVOKABLE QUrl resolvePath(const QString& path) const; + + /*@jsdoc + * Gets the path to the resources directory for QML files. + * @function Script.resourcesPath + * @returns {string} The path to the resources directory for QML files. + */ + Q_INVOKABLE QUrl resourcesPath() const; + + /*@jsdoc + * Starts timing a section of code in order to send usage data about it to Vircadia. Shouldn't be used outside of the + * standard scripts. + * @function Script.beginProfileRange + * @param {string} label - A name that identifies the section of code. + */ + Q_INVOKABLE void beginProfileRange(const QString& label) const; + + /*@jsdoc + * Finishes timing a section of code in order to send usage data about it to Vircadia. Shouldn't be used outside of + * the standard scripts. + * @function Script.endProfileRange + * @param {string} label - A name that identifies the section of code. + */ + Q_INVOKABLE void endProfileRange(const QString& label) const; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Entity Script Related methods + + /*@jsdoc + * Checks whether an entity has an entity script running. + * @function Script.isEntityScriptRunning + * @param {Uuid} entityID - The ID of the entity. + * @returns {boolean} true if the entity has an entity script running, false if it doesn't. + */ + Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; + } + QVariant cloneEntityScriptDetails(const EntityItemID& entityID); + QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; + + /*@jsdoc + * Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer + * reachable. + * @function Script.requestGarbageCollection + */ + Q_INVOKABLE void requestGarbageCollection(); + + /*@jsdoc + * @function Script.loadEntityScript + * @param {Uuid} entityID - Entity ID. + * @param {string} script - Script. + * @param {boolean} forceRedownload - Force re-download. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); + + /*@jsdoc + * @function Script.unloadEntityScript + * @param {Uuid} entityID - Entity ID. + * @param {boolean} [shouldRemoveFromMap=false] - Should remove from map. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method + + /*@jsdoc + * @function Script.unloadAllEntityScripts + * @param {boolean} [blockingCall=false] - Wait for completion if call moved to another thread. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void unloadAllEntityScripts(bool blockingCall = false); + + /*@jsdoc + * Calls a method in an entity script. + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID - The ID of the entity running the entity script. + * @param {string} methodName - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + * @param {Uuid} [remoteCallerID=Uuid.NULL] - An ID that identifies the caller. + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, + const QStringList& params = QStringList(), + const QUuid& remoteCallerID = QUuid()) override; + + /*@jsdoc + * Calls a method in an entity script. + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID - Entity ID. + * @param {string} methodName - Method name. + * @param {PointerEvent} event - Pointer event. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); + + /*@jsdoc + * Calls a method in an entity script. + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID - Entity ID. + * @param {string} methodName - Method name. + * @param {Uuid} otherID - Other entity ID. + * @param {Collision} collision - Collision. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + + /*@jsdoc + * @function Script.generateUUID + * @returns {Uuid} A new UUID. + * @deprecated This function is deprecated and will be removed. Use {@link Uuid(0).generate|Uuid.generate} instead. + */ + Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } + + void setType(Type type) { _type = type; }; + Type getType() { return _type; }; + QString getTypeAsString() const; + + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget + bool isRunning() const { return _isRunning; } // used by ScriptWidget + + // this is used by code in ScriptEngines.cpp during the "reload all" operation + bool isStopping() const { return _isStopping; } + + void disconnectNonEssentialSignals(); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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 ScriptManager class is not polluted by this notion + void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } + bool isUserLoaded() const { return _isUserLoaded; } + + void setQuitWhenFinished(const bool quitWhenFinished) { _quitWhenFinished = quitWhenFinished; } + bool isQuitWhenFinished() const { return _quitWhenFinished; } + + void setEmitScriptUpdatesFunction(std::function func) { _emitScriptUpdates = func; } + + void scriptErrorMessage(const QString& message); + void scriptWarningMessage(const QString& message); + void scriptInfoMessage(const QString& message); + void scriptPrintedMessage(const QString& message); + void clearDebugLogWindow(); + int getNumRunningEntityScripts() const; + bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + bool hasEntityScriptDetails(const EntityItemID& entityID) const; + + void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } + + // call all the registered event handlers on an entity for the specified name. + void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, const ScriptValueList& eventHanderArgs); + + // remove all event handlers for the specified entityID (i.e. the entity is being removed) + void removeAllEventHandlers(const EntityItemID& entityID); + + + /*@jsdoc + * Gets the URL for an asset in an external resource bucket. (The location where the bucket is hosted may change over time + * but this method will return the asset's current URL.) + * @function Script.getExternalPath + * @param {Script.ResourceBucket} bucket - The external resource bucket that the asset is in. + * @param {string} path - The path within the external resource bucket where the asset is located. + *

Normally, this should start with a path or filename to be appended to the bucket URL. + * Alternatively, it can be a relative path starting with ./ or ../, to navigate within the + * resource bucket's URL.

+ * @Returns {string} The URL of an external asset. + * @example Report the URL of a default particle. + * print(Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png")); + * @example Report the root directory where the Vircadia assets are located. + * print(Script.getExternalPath(Script.ExternalPaths.Assets, ".")); + */ + Q_INVOKABLE QString getExternalPath(ExternalResource::Bucket bucket, const QString& path); + +public slots: + + /*@jsdoc + * @function Script.updateMemoryCost + * @param {number} deltaSize - Delta size. + * @deprecated This function is deprecated and will be removed. + */ + void updateMemoryCost(const qint64&); + +signals: + + /*@jsdoc + * @function Script.scriptLoaded + * @param {string} filename - File name. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void scriptLoaded(const QString& scriptFilename); + + /*@jsdoc + * @function Script.errorLoadingScript + * @param {string} filename - File name. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void errorLoadingScript(const QString& scriptFilename); + + /*@jsdoc + * Triggered frequently at a system-determined interval. + * @function Script.update + * @param {number} deltaTime - The time since the last update, in s. + * @returns {Signal} + * @example Report script update intervals. + * Script.update.connect(function (deltaTime) { + * print("Update: " + deltaTime); + * }); + */ + void update(float deltaTime); + + /*@jsdoc + * Triggered when the script is stopping. + * @function Script.scriptEnding + * @returns {Signal} + * @example Report when a script is stopping. + * print("Script started"); + * + * Script.scriptEnding.connect(function () { + * print("Script ending"); + * }); + * + * Script.setTimeout(function () { + * print("Stopping script"); + * Script.stop(); + * }, 1000); + */ + void scriptEnding(); + + /*@jsdoc + * @function Script.finished + * @param {string} filename - File name. + * @param {object} engine - Engine. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void finished(const QString& fileNameString, ScriptManagerPointer); + + /*@jsdoc + * Triggered when the script prints a message to the program log via {@link print}, {@link Script.print}, + * {@link console.log}, {@link console.debug}, {@link console.group}, {@link console.groupEnd}, {@link console.time}, or + * {@link console.timeEnd}. + * @function Script.printedMessage + * @param {string} message - The message. + * @param {string} scriptName - The name of the script that generated the message. + * @returns {Signal} + */ + void printedMessage(const QString& message, const QString& scriptName); + + /*@jsdoc + * Triggered when the script generates an error, {@link console.error} or {@link console.exception} is called, or + * {@link console.assert} is called and fails. + * @function Script.errorMessage + * @param {string} message - The error message. + * @param {string} scriptName - The name of the script that generated the error message. + * @returns {Signal} + */ + void errorMessage(const QString& message, const QString& scriptName); + + /*@jsdoc + * Triggered when the script generates a warning or {@link console.warn} is called. + * @function Script.warningMessage + * @param {string} message - The warning message. + * @param {string} scriptName - The name of the script that generated the warning message. + * @returns {Signal} + */ + void warningMessage(const QString& message, const QString& scriptName); + + /*@jsdoc + * Triggered when the script generates an information message or {@link console.info} is called. + * @function Script.infoMessage + * @param {string} message - The information message. + * @param {string} scriptName - The name of the script that generated the information message. + * @returns {Signal} + */ + void infoMessage(const QString& message, const QString& scriptName); + + /*@jsdoc + * Triggered when the running state of the script changes, e.g., from running to stopping. + * @function Script.runningStateChanged + * @returns {Signal} + */ + void runningStateChanged(); + + /*@jsdoc + * @function Script.clearDebugWindow + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void clearDebugWindow(); + + /*@jsdoc + * @function Script.loadScript + * @param {string} scriptName - Script name. + * @param {boolean} isUserLoaded - Is user loaded. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void loadScript(const QString& scriptName, bool isUserLoaded); + + /*@jsdoc + * @function Script.reloadScript + * @param {string} scriptName - Script name. + * @param {boolean} isUserLoaded - Is user loaded. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + void reloadScript(const QString& scriptName, bool isUserLoaded); + + /*@jsdoc + * Triggered when the script has stopped. + * @function Script.doneRunning + * @returns {Signal} + */ + void doneRunning(); + + /*@jsdoc + * @function Script.entityScriptDetailsUpdated + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + // Emitted when an entity script is added or removed, or when the status of an entity + // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) + void entityScriptDetailsUpdated(); + + /*@jsdoc + * Triggered when the script starts for the user. See also, {@link Entities.preload}. + *

Supported Script Types: Client Entity Scripts • Server Entity Scripts

+ * @function Script.entityScriptPreloadFinished + * @param {Uuid} entityID - The ID of the entity that the script is running in. + * @returns {Signal} + * @example Get the ID of the entity that a client entity script is running in. + * var entityScript = function () { + * this.entityID = Uuid.NULL; + * }; + * + * Script.entityScriptPreloadFinished.connect(function (entityID) { + * this.entityID = entityID; + * print("Entity ID: " + this.entityID); + * }); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 255, green: 0, blue: 0 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + // Emitted when an entity script has finished running preload + void entityScriptPreloadFinished(const EntityItemID& entityID); + + /*@jsdoc + * Triggered when a script generates an unhandled exception. + * @function Script.unhandledException + * @param {object} exception - The details of the exception. + * @returns {Signal} + * @example Report the details of an unhandled exception. + * Script.unhandledException.connect(function (exception) { + * print("Unhandled exception: " + JSON.stringify(exception)); + * }); + * var properties = JSON.parse("{ x: 1"); // Invalid JSON string. + */ + void unhandledException(const ScriptValue& exception); + + // Triggered once before the first call to Script.addEventHandler happens on this ScriptManager + // connections assumed to use Qt::DirectConnection; not for use by scripts + void attachDefaultEventHandlers(); + + // Triggered repeatedly in the scripting loop to ensure entity edit messages get processed properly + // connections assumed to use Qt::DirectConnection; not for use by scripts + void releaseEntityPacketSenderMessages(bool wait); + +protected: + void init(); + + /*@jsdoc + * @function Script.executeOnScriptThread + * @param {function} function - Function. + * @param {ConnectionType} [type=2] - Connection type. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); + + /*@jsdoc + * @function Script._requireResolve + * @param {string} module - Module. + * @param {string} [relativeTo=""] - Relative to. + * @returns {string} Result. + * @deprecated This function is deprecated and will be removed. + */ + // note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general; + // then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;" + Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString()); + + QString logException(const ScriptValue& exception); + void timerFired(); + void stopAllTimers(); + void stopAllTimersForEntityScript(const EntityItemID& entityID); + void refreshFileScript(const EntityItemID& entityID); + void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString()); + void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details); + void setParentURL(const QString& parentURL) { _parentURL = parentURL; } + + QTimer* setupTimerWithInterval(const ScriptValue& function, int intervalMS, bool isSingleShot); + void stopTimer(QTimer* timer); + + QHash _registeredHandlers; + + /*@jsdoc + * @function Script.entityScriptContentAvailable + * @param {Uuid} entityID - Entity ID. + * @param {string} scriptOrURL - Path. + * @param {string} contents - Contents. + * @param {boolean} isURL - Is a URL. + * @param {boolean} success - Success. + * @param {string} status - Status. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status); + + EntityItemID currentEntityIdentifier; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. + QUrl currentSandboxURL; // The toplevel url string for the entity script that loaded the code being executed, else empty. + void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); + void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, const ScriptValue& function, const ScriptValue& thisObject, const ScriptValueList& args); + + Context _context; + Type _type; + ScriptEnginePointer _engine; + QString _scriptContents; + QString _parentURL; + std::atomic _isFinished { false }; + std::atomic _isRunning { false }; + std::atomic _isStopping { false }; + bool _isInitialized { false }; + QHash _timerFunctionMap; + QSet _includedURLs; + mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; + QHash _entityScripts; + EntityScriptContentAvailableMap _contentAvailableQueue; + + bool _isThreaded { false }; + qint64 _lastUpdate; + + QString _fileNameString; + Quat _quatLibrary; + Vec3 _vec3Library; + Mat4 _mat4Library; + ScriptUUID _uuidLibrary; + ConsoleScriptingInterface _consoleScriptingInterface; + std::atomic _isUserLoaded { false }; + bool _isReloading { false }; + + std::atomic _quitWhenFinished; + + AssetScriptingInterface* _assetScriptingInterface; + + std::function _emitScriptUpdates{ []() { return true; } }; + + std::recursive_mutex _lock; + + std::chrono::microseconds _totalTimerExecution { 0 }; + + static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; + + Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; + + QWeakPointer _scriptEngines; + + friend ScriptManagerPointer newScriptManager(Context context, const QString& scriptContents, const QString& fileNameString); +}; + +ScriptManagerPointer newScriptManager(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString); +ScriptManagerPointer scriptManagerFactory(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString); + +#endif // hifi_ScriptManager_h + +/// @} diff --git a/libraries/script-engine/src/ScriptProgram.h b/libraries/script-engine/src/ScriptProgram.h new file mode 100644 index 00000000000..ae26abace66 --- /dev/null +++ b/libraries/script-engine/src/ScriptProgram.h @@ -0,0 +1,58 @@ +// +// ScriptProgram.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/2/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptProgram_h +#define hifi_ScriptProgram_h + +#include + +class ScriptProgram; +class ScriptSyntaxCheckResult; +using ScriptProgramPointer = std::shared_ptr; +using ScriptSyntaxCheckResultPointer = std::shared_ptr; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptProgram +class ScriptProgram { +public: + virtual ScriptSyntaxCheckResultPointer checkSyntax() const = 0; + virtual QString fileName() const = 0; + virtual QString sourceCode() const = 0; + +protected: + ~ScriptProgram() {} // prevent explicit deletion of base class +}; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptSyntaxCheckResult +class ScriptSyntaxCheckResult { +public: + enum State + { + Error = 0, + Intermediate = 1, + Valid = 2 + }; + +public: + virtual int errorColumnNumber() const = 0; + virtual int errorLineNumber() const = 0; + virtual QString errorMessage() const = 0; + virtual State state() const = 0; + +protected: + ~ScriptSyntaxCheckResult() {} // prevent explicit deletion of base class +}; + +#endif // hifi_ScriptProgram_h + +/// @} diff --git a/libraries/script-engine/src/ScriptUUID.cpp b/libraries/script-engine/src/ScriptUUID.cpp index f88803c87c3..b9661e31eda 100644 --- a/libraries/script-engine/src/ScriptUUID.cpp +++ b/libraries/script-engine/src/ScriptUUID.cpp @@ -17,6 +17,7 @@ #include "ScriptEngineLogging.h" #include "ScriptEngine.h" +#include "ScriptManager.h" QUuid ScriptUUID::fromString(const QString& s) { return QUuid(s); @@ -42,7 +43,7 @@ void ScriptUUID::print(const QString& label, const QUuid& id) { QString message = QString("%1 %2").arg(qPrintable(label)); message = message.arg(id.toString()); qCDebug(scriptengine) << message; - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->print(message); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->print(message); } } diff --git a/libraries/script-engine/src/ScriptUUID.h b/libraries/script-engine/src/ScriptUUID.h index 24edb91184b..7f30036d7a0 100644 --- a/libraries/script-engine/src/ScriptUUID.h +++ b/libraries/script-engine/src/ScriptUUID.h @@ -19,7 +19,8 @@ #include #include -#include + +#include "Scriptable.h" /*@jsdoc * The Uuid API provides facilities for working with UUIDs. @@ -36,7 +37,7 @@ * @property {Uuid} NULL - The null UUID, "{00000000-0000-0000-0000-000000000000}". */ /// Provides the Uuid scripting interface -class ScriptUUID : public QObject, protected QScriptable { +class ScriptUUID : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(QString NULL READ NULL_UUID CONSTANT) // String for use in scripts. diff --git a/libraries/script-engine/src/ScriptValue.cpp b/libraries/script-engine/src/ScriptValue.cpp new file mode 100644 index 00000000000..df9729e3ae9 --- /dev/null +++ b/libraries/script-engine/src/ScriptValue.cpp @@ -0,0 +1,236 @@ +// +// ScriptValue.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 9/2/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValue.h" + +#include "ScriptEngineLogging.h" + + +class ScriptValueProxyNull final : public ScriptValueProxy { +public: + virtual void release() override; + virtual ScriptValueProxy* copy() const override; + +public: + virtual ScriptValue call(const ScriptValue& thisObject = ScriptValue(), const ScriptValueList& args = ScriptValueList()) override; + virtual ScriptValue call(const ScriptValue& thisObject, const ScriptValue& arguments) override; + virtual ScriptValue construct(const ScriptValueList& args = ScriptValueList()) override; + virtual ScriptValue construct(const ScriptValue& arguments) override; + virtual ScriptValue data() const override; + virtual ScriptEnginePointer engine() const override; + virtual bool equals(const ScriptValue& other) const override; + virtual bool isArray() const override; + virtual bool isBool() const override; + virtual bool isError() const override; + virtual bool isFunction() const override; + virtual bool isNumber() const override; + virtual bool isNull() const override; + virtual bool isObject() const override; + virtual bool isString() const override; + virtual bool isUndefined() const override; + virtual bool isValid() const override; + virtual bool isVariant() const override; + virtual ScriptValueIteratorPointer newIterator() const override; + virtual ScriptValue property(const QString& name, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const override; + virtual ScriptValue property(quint32 arrayIndex, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const override; + virtual void setData(const ScriptValue& val) override; + virtual void setProperty(const QString& name, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) override; + virtual void setProperty(quint32 arrayIndex, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) override; + virtual void setPrototype(const ScriptValue& prototype) override; + virtual bool strictlyEquals(const ScriptValue& other) const override; + + virtual bool toBool() const override; + virtual qint32 toInt32() const override; + virtual double toInteger() const override; + virtual double toNumber() const override; + virtual QString toString() const override; + virtual quint16 toUInt16() const override; + virtual quint32 toUInt32() const override; + virtual QVariant toVariant() const override; + virtual QObject* toQObject() const override; +}; + +static ScriptValueProxyNull SCRIPT_VALUE_NULL; + +ScriptValue::ScriptValue() : _proxy(&SCRIPT_VALUE_NULL) {} + +void ScriptValueProxyNull::release() { + // do nothing, we're a singlet +} + +ScriptValueProxy* ScriptValueProxyNull::copy() const { + // return ourselves, we're a singlet + return const_cast (this); +} + +ScriptValue ScriptValueProxyNull::call(const ScriptValue& thisObject, const ScriptValueList& args) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::call made to empty value"); + return ScriptValue(); +} + +ScriptValue ScriptValueProxyNull::call(const ScriptValue& thisObject, const ScriptValue& arguments) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::call made to empty value"); + return ScriptValue(); +} + +ScriptValue ScriptValueProxyNull::construct(const ScriptValueList& args) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::construct called on empty value"); + return ScriptValue(); +} + +ScriptValue ScriptValueProxyNull::construct(const ScriptValue& arguments) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::construct called on empty value"); + return ScriptValue(); +} + +ScriptValue ScriptValueProxyNull::data() const { + return ScriptValue(); +} + +ScriptEnginePointer ScriptValueProxyNull::engine() const { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::engine called on empty value"); + return ScriptEnginePointer(); +} + +bool ScriptValueProxyNull::equals(const ScriptValue& other) const { + return other.isUndefined(); +} + +bool ScriptValueProxyNull::isArray() const { + return false; +} + +bool ScriptValueProxyNull::isBool() const { + return false; +} + +bool ScriptValueProxyNull::isError() const { + return false; +} + +bool ScriptValueProxyNull::isFunction() const { + return false; +} + +bool ScriptValueProxyNull::isNumber() const { + return false; +} + +bool ScriptValueProxyNull::isNull() const { + return false; +} + +bool ScriptValueProxyNull::isObject() const { + return false; +} + +bool ScriptValueProxyNull::isString() const { + return false; +} + +bool ScriptValueProxyNull::isUndefined() const { + return true; +} + +bool ScriptValueProxyNull::isValid() const { + return false; +} + +bool ScriptValueProxyNull::isVariant() const { + return false; +} + +ScriptValueIteratorPointer ScriptValueProxyNull::newIterator() const { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::newIterator called on empty value"); + return ScriptValueIteratorPointer(); +} + +ScriptValue ScriptValueProxyNull::property(const QString& name, const ScriptValue::ResolveFlags& mode) const { + return ScriptValue(); +} + +ScriptValue ScriptValueProxyNull::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const { + return ScriptValue(); +} + +void ScriptValueProxyNull::setData(const ScriptValue& val) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::setData called on empty value"); +} + +void ScriptValueProxyNull::setProperty(const QString& name, + const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::setProperty called on empty value"); +} + +void ScriptValueProxyNull::setProperty(quint32 arrayIndex, + const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::setProperty called on empty value"); +} + +void ScriptValueProxyNull::setPrototype(const ScriptValue& prototype) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::setPrototype called on empty value"); +} + +bool ScriptValueProxyNull::strictlyEquals(const ScriptValue& other) const { + return !other.isValid(); +} + +bool ScriptValueProxyNull::toBool() const { + return false; +} + +qint32 ScriptValueProxyNull::toInt32() const { + return 0; +} + +double ScriptValueProxyNull::toInteger() const { + return 0; +} + +double ScriptValueProxyNull::toNumber() const { + return 0; +} + +QString ScriptValueProxyNull::toString() const { + return QString(); +} + +quint16 ScriptValueProxyNull::toUInt16() const { + return 0; +} + +quint32 ScriptValueProxyNull::toUInt32() const { + return 0; +} + +QVariant ScriptValueProxyNull::toVariant() const { + return QVariant(); +} + +QObject* ScriptValueProxyNull::toQObject() const { + return nullptr; +} diff --git a/libraries/script-engine/src/ScriptValue.h b/libraries/script-engine/src/ScriptValue.h new file mode 100644 index 00000000000..d5a057e0ad0 --- /dev/null +++ b/libraries/script-engine/src/ScriptValue.h @@ -0,0 +1,377 @@ +// +// ScriptValue.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 4/25/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValue_h +#define hifi_ScriptValue_h + +#include + +#include +#include +#include +#include +#include + +class ScriptEngine; +class ScriptValue; +class ScriptValueIterator; +class ScriptValueProxy; +using ScriptEnginePointer = std::shared_ptr; +using ScriptValueList = QList; +using ScriptValueIteratorPointer = std::shared_ptr; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptValue +class ScriptValue { +public: + enum ResolveFlag + { + ResolveLocal = 0, + ResolvePrototype = 1, + }; + using ResolveFlags = QFlags; + + enum PropertyFlag + { + ReadOnly = 0x00000001, + Undeletable = 0x00000002, + SkipInEnumeration = 0x00000004, + PropertyGetter = 0x00000008, + PropertySetter = 0x00000010, + KeepExistingFlags = 0x00000800, + }; + Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag); + +public: + ScriptValue(); + inline ScriptValue(const ScriptValue& src); + inline ~ScriptValue(); + inline ScriptValue(ScriptValueProxy* proxy) : _proxy(proxy) {} + inline ScriptValueProxy* ptr() const { return _proxy; } + inline ScriptValue& operator=(const ScriptValue& other); + +public: + inline ScriptValue call(const ScriptValue& thisObject = ScriptValue(), + const ScriptValueList& args = ScriptValueList()) const; + inline ScriptValue call(const ScriptValue& thisObject, const ScriptValue& arguments) const; + inline ScriptValue construct(const ScriptValueList& args = ScriptValueList()) const; + inline ScriptValue construct(const ScriptValue& arguments) const; + inline ScriptValue data() const; + inline ScriptEnginePointer engine() const; + inline bool equals(const ScriptValue& other) const; + inline bool isArray() const; + inline bool isBool() const; + inline bool isError() const; + inline bool isFunction() const; + inline bool isNumber() const; + inline bool isNull() const; + inline bool isObject() const; + inline bool isString() const; + inline bool isUndefined() const; + inline bool isValid() const; + inline bool isVariant() const; + inline ScriptValueIteratorPointer newIterator() const; + inline ScriptValue property(const QString& name, const ResolveFlags& mode = ResolvePrototype) const; + inline ScriptValue property(quint32 arrayIndex, const ResolveFlags& mode = ResolvePrototype) const; + inline void setData(const ScriptValue& val); + inline void setProperty(const QString& name, + const ScriptValue& value, + const PropertyFlags& flags = KeepExistingFlags); + inline void setProperty(quint32 arrayIndex, + const ScriptValue& value, + const PropertyFlags& flags = KeepExistingFlags); + template + inline void setProperty(const QString& name, const TYP& value, + const PropertyFlags& flags = KeepExistingFlags); + template + inline void setProperty(quint32 arrayIndex, const TYP& value, + const PropertyFlags& flags = KeepExistingFlags); + inline void setPrototype(const ScriptValue& prototype); + inline bool strictlyEquals(const ScriptValue& other) const; + + inline bool toBool() const; + inline qint32 toInt32() const; + inline double toInteger() const; + inline double toNumber() const; + inline QString toString() const; + inline quint16 toUInt16() const; + inline quint32 toUInt32() const; + inline QVariant toVariant() const; + inline QObject* toQObject() const; + +protected: + ScriptValueProxy* _proxy; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(ScriptValue::PropertyFlags); + +/// [ScriptInterface] Provides an engine-independent interface for QScriptValue +class ScriptValueProxy { +public: + virtual void release() = 0; + virtual ScriptValueProxy* copy() const = 0; + +public: + virtual ScriptValue call(const ScriptValue& thisObject = ScriptValue(), + const ScriptValueList& args = ScriptValueList()) = 0; + virtual ScriptValue call(const ScriptValue& thisObject, const ScriptValue& arguments) = 0; + virtual ScriptValue construct(const ScriptValueList& args = ScriptValueList()) = 0; + virtual ScriptValue construct(const ScriptValue& arguments) = 0; + virtual ScriptValue data() const = 0; + virtual ScriptEnginePointer engine() const = 0; + virtual bool equals(const ScriptValue& other) const = 0; + virtual bool isArray() const = 0; + virtual bool isBool() const = 0; + virtual bool isError() const = 0; + virtual bool isFunction() const = 0; + virtual bool isNumber() const = 0; + virtual bool isNull() const = 0; + virtual bool isObject() const = 0; + virtual bool isString() const = 0; + virtual bool isUndefined() const = 0; + virtual bool isValid() const = 0; + virtual bool isVariant() const = 0; + virtual ScriptValueIteratorPointer newIterator() const = 0; + virtual ScriptValue property(const QString& name, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const = 0; + virtual ScriptValue property(quint32 arrayIndex, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const = 0; + virtual void setData(const ScriptValue& val) = 0; + virtual void setProperty(const QString& name, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) = 0; + virtual void setProperty(quint32 arrayIndex, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) = 0; + virtual void setPrototype(const ScriptValue& prototype) = 0; + virtual bool strictlyEquals(const ScriptValue& other) const = 0; + + virtual bool toBool() const = 0; + virtual qint32 toInt32() const = 0; + virtual double toInteger() const = 0; + virtual double toNumber() const = 0; + virtual QString toString() const = 0; + virtual quint16 toUInt16() const = 0; + virtual quint32 toUInt32() const = 0; + virtual QVariant toVariant() const = 0; + virtual QObject* toQObject() const = 0; + +protected: + ~ScriptValueProxy() {} // prevent explicit deletion of base class +}; + +// the second template parameter is used to defer evaluation of calls to the engine until ScriptEngine isn't forward-declared +template +void ScriptValue::setProperty(const QString& name, const TYP& value, const PropertyFlags& flags) { + setProperty(name, static_cast(engine())->newValue(value), flags); +} + +// the second template parameter is used to defer evaluation of calls to the engine until ScriptEngine isn't forward-declared +template +void ScriptValue::setProperty(quint32 arrayIndex, const TYP& value, const PropertyFlags& flags) { + setProperty(arrayIndex, static_cast(engine())->newValue(value), flags); +} + +ScriptValue::ScriptValue(const ScriptValue& src) : _proxy(src.ptr()->copy()) { + Q_ASSERT(_proxy != nullptr); +} + +ScriptValue::~ScriptValue() { + Q_ASSERT(_proxy != nullptr); + _proxy->release(); +} + +ScriptValue& ScriptValue::operator=(const ScriptValue& other) { + Q_ASSERT(_proxy != nullptr); + _proxy->release(); + _proxy = other.ptr()->copy(); + return *this; +} + +ScriptValue ScriptValue::call(const ScriptValue& thisObject, const ScriptValueList& args) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->call(thisObject, args); +} + +ScriptValue ScriptValue::call(const ScriptValue& thisObject, const ScriptValue& arguments) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->call(thisObject, arguments); +} + +ScriptValue ScriptValue::construct(const ScriptValueList& args) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->construct(args); +} + +ScriptValue ScriptValue::construct(const ScriptValue& arguments) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->construct(arguments); +} + +ScriptValue ScriptValue::data() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->data(); +} + +ScriptEnginePointer ScriptValue::engine() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->engine(); +} + +bool ScriptValue::equals(const ScriptValue& other) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->equals(other); +} + +bool ScriptValue::isArray() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isArray(); +} + +bool ScriptValue::isBool() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isBool(); +} + +bool ScriptValue::isError() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isError(); +} + +bool ScriptValue::isFunction() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isFunction(); +} + +bool ScriptValue::isNumber() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isNumber(); +} + +bool ScriptValue::isNull() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isNull(); +} + +bool ScriptValue::isObject() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isObject(); +} + +bool ScriptValue::isString() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isString(); +} + +bool ScriptValue::isUndefined() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isUndefined(); +} + +bool ScriptValue::isValid() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isValid(); +} + +bool ScriptValue::isVariant() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->isVariant(); +} + +ScriptValueIteratorPointer ScriptValue::newIterator() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->newIterator(); +} + +ScriptValue ScriptValue::property(const QString& name, const ResolveFlags& mode) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->property(name, mode); +} + +ScriptValue ScriptValue::property(quint32 arrayIndex, const ResolveFlags& mode) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->property(arrayIndex, mode); +} + +void ScriptValue::setData(const ScriptValue& val) { + Q_ASSERT(_proxy != nullptr); + return _proxy->setData(val); +} + +void ScriptValue::setProperty(const QString& name, const ScriptValue& value, const PropertyFlags& flags) { + Q_ASSERT(_proxy != nullptr); + return _proxy->setProperty(name, value, flags); +} + +void ScriptValue::setProperty(quint32 arrayIndex, const ScriptValue& value, const PropertyFlags& flags) { + Q_ASSERT(_proxy != nullptr); + return _proxy->setProperty(arrayIndex, value, flags); +} + +void ScriptValue::setPrototype(const ScriptValue& prototype) { + Q_ASSERT(_proxy != nullptr); + return _proxy->setPrototype(prototype); +} + +bool ScriptValue::strictlyEquals(const ScriptValue& other) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->strictlyEquals(other); +} + +bool ScriptValue::toBool() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toBool(); +} + +qint32 ScriptValue::toInt32() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toInt32(); +} + +double ScriptValue::toInteger() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toInteger(); +} + +double ScriptValue::toNumber() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toNumber(); +} + +QString ScriptValue::toString() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toString(); +} + +quint16 ScriptValue::toUInt16() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toUInt16(); +} + +quint32 ScriptValue::toUInt32() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toUInt32(); +} + +QVariant ScriptValue::toVariant() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toVariant(); +} + +QObject* ScriptValue::toQObject() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->toQObject(); +} + +#endif // hifi_ScriptValue_h + +/// @} diff --git a/libraries/script-engine/src/ScriptValueIterator.h b/libraries/script-engine/src/ScriptValueIterator.h new file mode 100644 index 00000000000..657bef6fbbf --- /dev/null +++ b/libraries/script-engine/src/ScriptValueIterator.h @@ -0,0 +1,42 @@ +// +// ScriptValueIterator.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/2/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueIterator_h +#define hifi_ScriptValueIterator_h + +#include + +#include + +#include "ScriptValue.h" + +class ScriptValueIterator; +using ScriptValueIteratorPointer = std::shared_ptr; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptValueIterator +class ScriptValueIterator { +public: + virtual ScriptValue::PropertyFlags flags() const = 0; + virtual bool hasNext() const = 0; + virtual QString name() const = 0; + virtual void next() = 0; + virtual ScriptValue value() const = 0; + +protected: + ~ScriptValueIterator() {} // prevent explicit deletion of base class +}; + +#endif // hifi_ScriptValueIterator_h + +/// @} diff --git a/libraries/script-engine/src/ScriptValueUtils.cpp b/libraries/script-engine/src/ScriptValueUtils.cpp new file mode 100644 index 00000000000..da54182a9dc --- /dev/null +++ b/libraries/script-engine/src/ScriptValueUtils.cpp @@ -0,0 +1,938 @@ +// +// ScriptValueUtils.cpp +// libraries/shared/src +// +// Created by Anthony Thibault on 4/15/16. +// Copyright 2016 High Fidelity, Inc. +// +// Utilities for working with QtScriptValues +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueUtils.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValueIterator.h" + +bool isListOfStrings(const ScriptValue& arg) { + if (!arg.isArray()) { + return false; + } + + auto lengthProperty = arg.property("length"); + if (!lengthProperty.isNumber()) { + return false; + } + + int length = lengthProperty.toInt32(); + for (int i = 0; i < length; i++) { + if (!arg.property(i).isString()) { + return false; + } + } + + return true; +} + +void registerMetaTypes(ScriptEngine* engine) { + scriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue); + scriptRegisterMetaType(engine, vec3ToScriptValue, vec3FromScriptValue); + scriptRegisterMetaType(engine, u8vec3ToScriptValue, u8vec3FromScriptValue); + scriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue); + scriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); + scriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); + + scriptRegisterMetaType(engine, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue); + scriptRegisterMetaType(engine, qVectorQuatToScriptValue, qVectorQuatFromScriptValue); + scriptRegisterMetaType(engine, qVectorBoolToScriptValue, qVectorBoolFromScriptValue); + scriptRegisterMetaType(engine, qVectorFloatToScriptValue, qVectorFloatFromScriptValue); + scriptRegisterMetaType(engine, qVectorIntToScriptValue, qVectorIntFromScriptValue); + scriptRegisterMetaType(engine, qVectorQUuidToScriptValue, qVectorQUuidFromScriptValue); + + scriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue); + scriptRegisterMetaType(engine, qRectToScriptValue, qRectFromScriptValue); + scriptRegisterMetaType(engine, qURLToScriptValue, qURLFromScriptValue); + scriptRegisterMetaType(engine, qColorToScriptValue, qColorFromScriptValue); + + scriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); + scriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); + scriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); + scriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue); + + scriptRegisterMetaType(engine, stencilMaskModeToScriptValue, stencilMaskModeFromScriptValue); + + scriptRegisterMetaType(engine, promiseToScriptValue, promiseFromScriptValue); + + scriptRegisterSequenceMetaType>(engine); +} + +ScriptValue vec2ToScriptValue(ScriptEngine* engine, const glm::vec2& vec2) { + auto prototype = engine->globalObject().property("__hifi_vec2__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "__hifi_vec2__ = Object.defineProperties({}, { " + "defined: { value: true }," + "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "u: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "v: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }" + "})"); + } + ScriptValue value = engine->newObject(); + value.setProperty("x", vec2.x); + value.setProperty("y", vec2.y); + value.setPrototype(prototype); + return value; +} + +bool vec2FromScriptValue(const ScriptValue& object, glm::vec2& vec2) { + if (object.isNumber()) { + vec2 = glm::vec2(object.toVariant().toFloat()); + } else if (object.isArray()) { + QVariantList list = object.toVariant().toList(); + if (list.length() == 2) { + vec2.x = list[0].toFloat(); + vec2.y = list[1].toFloat(); + } + } else { + ScriptValue x = object.property("x"); + if (!x.isValid()) { + x = object.property("u"); + } + + ScriptValue y = object.property("y"); + if (!y.isValid()) { + y = object.property("v"); + } + + vec2.x = x.toVariant().toFloat(); + vec2.y = y.toVariant().toFloat(); + } + return true; +} + +ScriptValue vec3ToScriptValue(ScriptEngine* engine, const glm::vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_vec3__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "__hifi_vec3__ = Object.defineProperties({}, { " + "defined: { value: true }," + "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "2: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," + "r: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "g: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "b: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," + "red: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "green: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "blue: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }" + "})"); + } + ScriptValue value = engine->newObject(); + value.setProperty("x", vec3.x); + value.setProperty("y", vec3.y); + value.setProperty("z", vec3.z); + value.setPrototype(prototype); + return value; +} + +ScriptValue vec3ColorToScriptValue(ScriptEngine* engine, const glm::vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_vec3_color__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "__hifi_vec3_color__ = Object.defineProperties({}, { " + "defined: { value: true }," + "0: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "1: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "2: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," + "r: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "g: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "b: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," + "x: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "y: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "z: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }" + "})"); + } + ScriptValue value = engine->newObject(); + value.setProperty("red", vec3.x); + value.setProperty("green", vec3.y); + value.setProperty("blue", vec3.z); + value.setPrototype(prototype); + return value; +} + +bool vec3FromScriptValue(const ScriptValue& object, glm::vec3& vec3) { + if (object.isNumber()) { + vec3 = glm::vec3(object.toVariant().toFloat()); + } else if (object.isString()) { + QColor qColor(object.toString()); + if (qColor.isValid()) { + vec3.x = qColor.red(); + vec3.y = qColor.green(); + vec3.z = qColor.blue(); + } + } else if (object.isArray()) { + QVariantList list = object.toVariant().toList(); + if (list.length() == 3) { + vec3.x = list[0].toFloat(); + vec3.y = list[1].toFloat(); + vec3.z = list[2].toFloat(); + } + } else { + ScriptValue x = object.property("x"); + if (!x.isValid()) { + x = object.property("r"); + } + if (!x.isValid()) { + x = object.property("red"); + } + + ScriptValue y = object.property("y"); + if (!y.isValid()) { + y = object.property("g"); + } + if (!y.isValid()) { + y = object.property("green"); + } + + ScriptValue z = object.property("z"); + if (!z.isValid()) { + z = object.property("b"); + } + if (!z.isValid()) { + z = object.property("blue"); + } + + vec3.x = x.toVariant().toFloat(); + vec3.y = y.toVariant().toFloat(); + vec3.z = z.toVariant().toFloat(); + } + return true; +} + +ScriptValue u8vec3ToScriptValue(ScriptEngine* engine, const glm::u8vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_u8vec3__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "__hifi_u8vec3__ = Object.defineProperties({}, { " + "defined: { value: true }," + "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "2: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," + "r: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "g: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "b: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," + "red: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," + "green: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," + "blue: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }" + "})"); + } + ScriptValue value = engine->newObject(); + value.setProperty("x", vec3.x); + value.setProperty("y", vec3.y); + value.setProperty("z", vec3.z); + value.setPrototype(prototype); + return value; +} + +ScriptValue u8vec3ColorToScriptValue(ScriptEngine* engine, const glm::u8vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_u8vec3_color__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "__hifi_u8vec3_color__ = Object.defineProperties({}, { " + "defined: { value: true }," + "0: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "1: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "2: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," + "r: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "g: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "b: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," + "x: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," + "y: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," + "z: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }" + "})"); + } + ScriptValue value = engine->newObject(); + value.setProperty("red", vec3.x); + value.setProperty("green", vec3.y); + value.setProperty("blue", vec3.z); + value.setPrototype(prototype); + return value; +} + +bool u8vec3FromScriptValue(const ScriptValue& object, glm::u8vec3& vec3) { + if (object.isNumber()) { + vec3 = glm::vec3(object.toVariant().toUInt()); + } else if (object.isString()) { + QColor qColor(object.toString()); + if (qColor.isValid()) { + vec3.x = (uint8_t)qColor.red(); + vec3.y = (uint8_t)qColor.green(); + vec3.z = (uint8_t)qColor.blue(); + } + } else if (object.isArray()) { + QVariantList list = object.toVariant().toList(); + if (list.length() == 3) { + vec3.x = list[0].toUInt(); + vec3.y = list[1].toUInt(); + vec3.z = list[2].toUInt(); + } + } else { + ScriptValue x = object.property("x"); + if (!x.isValid()) { + x = object.property("r"); + } + if (!x.isValid()) { + x = object.property("red"); + } + + ScriptValue y = object.property("y"); + if (!y.isValid()) { + y = object.property("g"); + } + if (!y.isValid()) { + y = object.property("green"); + } + + ScriptValue z = object.property("z"); + if (!z.isValid()) { + z = object.property("b"); + } + if (!z.isValid()) { + z = object.property("blue"); + } + + vec3.x = x.toVariant().toUInt(); + vec3.y = y.toVariant().toUInt(); + vec3.z = z.toVariant().toUInt(); + } + return true; +} + +ScriptValue vec4toScriptValue(ScriptEngine* engine, const glm::vec4& vec4) { + ScriptValue obj = engine->newObject(); + obj.setProperty("x", vec4.x); + obj.setProperty("y", vec4.y); + obj.setProperty("z", vec4.z); + obj.setProperty("w", vec4.w); + return obj; +} + +bool vec4FromScriptValue(const ScriptValue& object, glm::vec4& vec4) { + vec4.x = object.property("x").toVariant().toFloat(); + vec4.y = object.property("y").toVariant().toFloat(); + vec4.z = object.property("z").toVariant().toFloat(); + vec4.w = object.property("w").toVariant().toFloat(); + return true; +} + +ScriptValue mat4toScriptValue(ScriptEngine* engine, const glm::mat4& mat4) { + ScriptValue obj = engine->newObject(); + obj.setProperty("r0c0", mat4[0][0]); + obj.setProperty("r1c0", mat4[0][1]); + obj.setProperty("r2c0", mat4[0][2]); + obj.setProperty("r3c0", mat4[0][3]); + obj.setProperty("r0c1", mat4[1][0]); + obj.setProperty("r1c1", mat4[1][1]); + obj.setProperty("r2c1", mat4[1][2]); + obj.setProperty("r3c1", mat4[1][3]); + obj.setProperty("r0c2", mat4[2][0]); + obj.setProperty("r1c2", mat4[2][1]); + obj.setProperty("r2c2", mat4[2][2]); + obj.setProperty("r3c2", mat4[2][3]); + obj.setProperty("r0c3", mat4[3][0]); + obj.setProperty("r1c3", mat4[3][1]); + obj.setProperty("r2c3", mat4[3][2]); + obj.setProperty("r3c3", mat4[3][3]); + return obj; +} + +bool mat4FromScriptValue(const ScriptValue& object, glm::mat4& mat4) { + mat4[0][0] = object.property("r0c0").toVariant().toFloat(); + mat4[0][1] = object.property("r1c0").toVariant().toFloat(); + mat4[0][2] = object.property("r2c0").toVariant().toFloat(); + mat4[0][3] = object.property("r3c0").toVariant().toFloat(); + mat4[1][0] = object.property("r0c1").toVariant().toFloat(); + mat4[1][1] = object.property("r1c1").toVariant().toFloat(); + mat4[1][2] = object.property("r2c1").toVariant().toFloat(); + mat4[1][3] = object.property("r3c1").toVariant().toFloat(); + mat4[2][0] = object.property("r0c2").toVariant().toFloat(); + mat4[2][1] = object.property("r1c2").toVariant().toFloat(); + mat4[2][2] = object.property("r2c2").toVariant().toFloat(); + mat4[2][3] = object.property("r3c2").toVariant().toFloat(); + mat4[3][0] = object.property("r0c3").toVariant().toFloat(); + mat4[3][1] = object.property("r1c3").toVariant().toFloat(); + mat4[3][2] = object.property("r2c3").toVariant().toFloat(); + mat4[3][3] = object.property("r3c3").toVariant().toFloat(); + return true; +} + +ScriptValue qVectorVec3ColorToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, vec3ColorToScriptValue(engine, vector.at(i))); + } + return array; +} + +ScriptValue qVectorVec3ToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, vec3ToScriptValue(engine, vector.at(i))); + } + return array; +} + +QVector qVectorVec3FromScriptValue(const ScriptValue& array) { + QVector newVector; + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::vec3 newVec3 = glm::vec3(); + vec3FromScriptValue(array.property(i), newVec3); + newVector << newVec3; + } + return newVector; +} + +bool qVectorVec3FromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::vec3 newVec3 = glm::vec3(); + vec3FromScriptValue(array.property(i), newVec3); + vector << newVec3; + } + return true; +} + +ScriptValue quatToScriptValue(ScriptEngine* engine, const glm::quat& quat) { + ScriptValue obj = engine->newObject(); + if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) { + // if quat contains a NaN don't try to convert it + return obj; + } + obj.setProperty("x", quat.x); + obj.setProperty("y", quat.y); + obj.setProperty("z", quat.z); + obj.setProperty("w", quat.w); + return obj; +} + +bool quatFromScriptValue(const ScriptValue& object, glm::quat& quat) { + quat.x = object.property("x").toVariant().toFloat(); + quat.y = object.property("y").toVariant().toFloat(); + quat.z = object.property("z").toVariant().toFloat(); + quat.w = object.property("w").toVariant().toFloat(); + + // enforce normalized quaternion + float length = glm::length(quat); + if (length > FLT_EPSILON) { + quat /= length; + } else { + quat = glm::quat(); + } + return true; +} + +ScriptValue qVectorQuatToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, quatToScriptValue(engine, vector.at(i))); + } + return array; +} + +ScriptValue qVectorBoolToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, vector.at(i)); + } + return array; +} + +QVector qVectorFloatFromScriptValue(const ScriptValue& array) { + if (!array.isArray()) { + return QVector(); + } + QVector newVector; + int length = array.property("length").toInteger(); + newVector.reserve(length); + for (int i = 0; i < length; i++) { + if (array.property(i).isNumber()) { + newVector << array.property(i).toNumber(); + } + } + + return newVector; +} + +ScriptValue qVectorQUuidToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, quuidToScriptValue(engine, vector.at(i))); + } + return array; +} + +bool qVectorQUuidFromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + vector << array.property(i).toVariant().toUuid(); + } + return true; +} + +QVector qVectorQUuidFromScriptValue(const ScriptValue& array) { + if (!array.isArray()) { + return QVector(); + } + QVector newVector; + int length = array.property("length").toInteger(); + newVector.reserve(length); + for (int i = 0; i < length; i++) { + QString uuidAsString = array.property(i).toString(); + QUuid fromString(uuidAsString); + newVector << fromString; + } + return newVector; +} + +ScriptValue qVectorFloatToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + float num = vector.at(i); + array.setProperty(i, engine->newValue(num)); + } + return array; +} + +ScriptValue qVectorIntToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + int num = vector.at(i); + array.setProperty(i, engine->newValue(num)); + } + return array; +} + +bool qVectorFloatFromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + vector << array.property(i).toVariant().toFloat(); + } + return true; +} + +bool qVectorIntFromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + vector << array.property(i).toVariant().toInt(); + } + return true; +} + +QVector qVectorQuatFromScriptValue(const ScriptValue& array) { + QVector newVector; + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::quat newQuat = glm::quat(); + quatFromScriptValue(array.property(i), newQuat); + newVector << newQuat; + } + return newVector; +} + +bool qVectorQuatFromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::quat newQuat = glm::quat(); + quatFromScriptValue(array.property(i), newQuat); + vector << newQuat; + } + return true; +} + +QVector qVectorBoolFromScriptValue(const ScriptValue& array) { + QVector newVector; + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + newVector << array.property(i).toBool(); + } + return newVector; +} + +bool qVectorBoolFromScriptValue(const ScriptValue& array, QVector& vector) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + vector << array.property(i).toBool(); + } + return true; +} + +ScriptValue qRectToScriptValue(ScriptEngine* engine, const QRect& rect) { + ScriptValue obj = engine->newObject(); + obj.setProperty("x", rect.x()); + obj.setProperty("y", rect.y()); + obj.setProperty("width", rect.width()); + obj.setProperty("height", rect.height()); + return obj; +} + +bool qRectFromScriptValue(const ScriptValue& object, QRect& rect) { + rect.setX(object.property("x").toVariant().toInt()); + rect.setY(object.property("y").toVariant().toInt()); + rect.setWidth(object.property("width").toVariant().toInt()); + rect.setHeight(object.property("height").toVariant().toInt()); + return true; +} + +ScriptValue qRectFToScriptValue(ScriptEngine* engine, const QRectF& rect) { + ScriptValue obj = engine->newObject(); + obj.setProperty("x", rect.x()); + obj.setProperty("y", rect.y()); + obj.setProperty("width", rect.width()); + obj.setProperty("height", rect.height()); + return obj; +} + +bool qRectFFromScriptValue(const ScriptValue& object, QRectF& rect) { + rect.setX(object.property("x").toVariant().toFloat()); + rect.setY(object.property("y").toVariant().toFloat()); + rect.setWidth(object.property("width").toVariant().toFloat()); + rect.setHeight(object.property("height").toVariant().toFloat()); + return true; +} + +ScriptValue qColorToScriptValue(ScriptEngine* engine, const QColor& color) { + ScriptValue object = engine->newObject(); + object.setProperty("red", color.red()); + object.setProperty("green", color.green()); + object.setProperty("blue", color.blue()); + object.setProperty("alpha", color.alpha()); + return object; +} + +/*@jsdoc + * An axis-aligned cube, defined as the bottom right near (minimum axes values) corner of the cube plus the dimension of its + * sides. + * @typedef {object} AACube + * @property {number} x - X coordinate of the brn corner of the cube. + * @property {number} y - Y coordinate of the brn corner of the cube. + * @property {number} z - Z coordinate of the brn corner of the cube. + * @property {number} scale - The dimensions of each side of the cube. + */ +ScriptValue aaCubeToScriptValue(ScriptEngine* engine, const AACube& aaCube) { + ScriptValue obj = engine->newObject(); + const glm::vec3& corner = aaCube.getCorner(); + obj.setProperty("x", corner.x); + obj.setProperty("y", corner.y); + obj.setProperty("z", corner.z); + obj.setProperty("scale", aaCube.getScale()); + return obj; +} + +bool aaCubeFromScriptValue(const ScriptValue& object, AACube& aaCube) { + glm::vec3 corner; + corner.x = object.property("x").toVariant().toFloat(); + corner.y = object.property("y").toVariant().toFloat(); + corner.z = object.property("z").toVariant().toFloat(); + float scale = object.property("scale").toVariant().toFloat(); + + aaCube.setBox(corner, scale); + return true; +} + +bool qColorFromScriptValue(const ScriptValue& object, QColor& color) { + if (object.isNumber()) { + color.setRgb(object.toUInt32()); + + } else if (object.isString()) { + color.setNamedColor(object.toString()); + + } else { + ScriptValue alphaValue = object.property("alpha"); + color.setRgb(object.property("red").toInt32(), object.property("green").toInt32(), object.property("blue").toInt32(), + alphaValue.isNumber() ? alphaValue.toInt32() : 255); + } + return true; +} + +ScriptValue qURLToScriptValue(ScriptEngine* engine, const QUrl& url) { + return engine->newValue(url.toString()); +} + +bool qURLFromScriptValue(const ScriptValue& object, QUrl& url) { + url = object.toString(); + return true; +} + +ScriptValue pickRayToScriptValue(ScriptEngine* engine, const PickRay& pickRay) { + ScriptValue obj = engine->newObject(); + ScriptValue origin = vec3ToScriptValue(engine, pickRay.origin); + obj.setProperty("origin", origin); + ScriptValue direction = vec3ToScriptValue(engine, pickRay.direction); + obj.setProperty("direction", direction); + return obj; +} + +bool pickRayFromScriptValue(const ScriptValue& object, PickRay& pickRay) { + ScriptValue originValue = object.property("origin"); + if (originValue.isValid()) { + auto x = originValue.property("x"); + auto y = originValue.property("y"); + auto z = originValue.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + pickRay.origin.x = x.toVariant().toFloat(); + pickRay.origin.y = y.toVariant().toFloat(); + pickRay.origin.z = z.toVariant().toFloat(); + } + } + ScriptValue directionValue = object.property("direction"); + if (directionValue.isValid()) { + auto x = directionValue.property("x"); + auto y = directionValue.property("y"); + auto z = directionValue.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + pickRay.direction.x = x.toVariant().toFloat(); + pickRay.direction.y = y.toVariant().toFloat(); + pickRay.direction.z = z.toVariant().toFloat(); + } + } + return true; +} + +/*@jsdoc + * Details of a collision between avatars and entities. + * @typedef {object} Collision + * @property {ContactEventType} type - The contact type of the collision event. + * @property {Uuid} idA - The ID of one of the avatars or entities in the collision. + * @property {Uuid} idB - The ID of the other of the avatars or entities in the collision. + * @property {Vec3} penetration - The amount of penetration between the two items. + * @property {Vec3} contactPoint - The point of contact. + * @property {Vec3} velocityChange - The change in relative velocity of the two items, in m/s. + */ +ScriptValue collisionToScriptValue(ScriptEngine* engine, const Collision& collision) { + ScriptValue obj = engine->newObject(); + obj.setProperty("type", collision.type); + obj.setProperty("idA", quuidToScriptValue(engine, collision.idA)); + obj.setProperty("idB", quuidToScriptValue(engine, collision.idB)); + obj.setProperty("penetration", vec3ToScriptValue(engine, collision.penetration)); + obj.setProperty("contactPoint", vec3ToScriptValue(engine, collision.contactPoint)); + obj.setProperty("velocityChange", vec3ToScriptValue(engine, collision.velocityChange)); + return obj; +} + +bool collisionFromScriptValue(const ScriptValue& object, Collision& collision) { + // TODO: implement this when we know what it means to accept collision events from JS + return false; +} + +ScriptValue quuidToScriptValue(ScriptEngine* engine, const QUuid& uuid) { + if (uuid.isNull()) { + return engine->nullValue(); + } + ScriptValue obj(engine->newValue(uuid.toString())); + return obj; +} + +bool quuidFromScriptValue(const ScriptValue& object, QUuid& uuid) { + if (object.isNull()) { + uuid = QUuid(); + return true; + } + QString uuidAsString = object.toVariant().toString(); + QUuid fromString(uuidAsString); + uuid = fromString; + return true; +} + +/*@jsdoc + * A 2D size value. + * @typedef {object} Size + * @property {number} height - The height value. + * @property {number} width - The width value. + */ +ScriptValue qSizeFToScriptValue(ScriptEngine* engine, const QSizeF& qSizeF) { + ScriptValue obj = engine->newObject(); + obj.setProperty("width", qSizeF.width()); + obj.setProperty("height", qSizeF.height()); + return obj; +} + +bool qSizeFFromScriptValue(const ScriptValue& object, QSizeF& qSizeF) { + qSizeF.setWidth(object.property("width").toVariant().toFloat()); + qSizeF.setHeight(object.property("height").toVariant().toFloat()); + return true; +} + +/*@jsdoc + * The details of an animation that is playing. + * @typedef {object} Avatar.AnimationDetails + * @property {string} role - Not used. + * @property {string} url - The URL to the animation file. Animation files need to be in glTF or FBX format but only need to + * contain the avatar skeleton and animation data. glTF models may be in JSON or binary format (".gltf" or ".glb" URLs + * respectively). + *

Warning: glTF animations currently do not always animate correctly.

+ * @property {number} fps - The frames per second(FPS) rate for the animation playback. 30 FPS is normal speed. + * @property {number} priority - Not used. + * @property {boolean} loop - true if the animation should loop, false if it shouldn't. + * @property {boolean} hold - Not used. + * @property {number} firstFrame - The frame the animation should start at. + * @property {number} lastFrame - The frame the animation should stop at. + * @property {boolean} running - Not used. + * @property {number} currentFrame - The current frame being played. + * @property {boolean} startAutomatically - Not used. + * @property {boolean} allowTranslation - Not used. + */ +ScriptValue animationDetailsToScriptValue(ScriptEngine* engine, const AnimationDetails& details) { + ScriptValue obj = engine->newObject(); + obj.setProperty("role", details.role); + obj.setProperty("url", details.url.toString()); + obj.setProperty("fps", details.fps); + obj.setProperty("priority", details.priority); + obj.setProperty("loop", details.loop); + obj.setProperty("hold", details.hold); + obj.setProperty("startAutomatically", details.startAutomatically); + obj.setProperty("firstFrame", details.firstFrame); + obj.setProperty("lastFrame", details.lastFrame); + obj.setProperty("running", details.running); + obj.setProperty("currentFrame", details.currentFrame); + obj.setProperty("allowTranslation", details.allowTranslation); + return obj; +} + +bool animationDetailsFromScriptValue(const ScriptValue& object, AnimationDetails& details) { + // nothing for now... + return false; +} + +ScriptValue meshToScriptValue(ScriptEngine* engine, MeshProxy* const& in) { + return engine->newQObject(in, ScriptEngine::QtOwnership); +} + +bool meshFromScriptValue(const ScriptValue& value, MeshProxy*& out) { + out = qobject_cast(value.toQObject()); + return true; +} + +ScriptValue meshesToScriptValue(ScriptEngine* engine, const MeshProxyList& in) { + // ScriptValueList result; + ScriptValue result = engine->newArray(); + int i = 0; + foreach (MeshProxy* const meshProxy, in) { result.setProperty(i++, meshToScriptValue(engine, meshProxy)); } + return result; +} + +bool meshesFromScriptValue(const ScriptValue& value, MeshProxyList& out) { + ScriptValueIteratorPointer itr(value.newIterator()); + + qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); + + while (itr->hasNext()) { + itr->next(); + MeshProxy* meshProxy = scriptvalue_cast(itr->value()); + if (meshProxy) { + out.append(meshProxy); + } else { + qDebug() << "null meshProxy"; + } + } + return true; +} + +/*@jsdoc + * A triangle in a mesh. + * @typedef {object} MeshFace + * @property {number[]} vertices - The indexes of the three vertices that make up the face. + */ +ScriptValue meshFaceToScriptValue(ScriptEngine* engine, const MeshFace& meshFace) { + ScriptValue obj = engine->newObject(); + obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); + return obj; +} + +bool meshFaceFromScriptValue(const ScriptValue& object, MeshFace& meshFaceResult) { + return qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); +} + +ScriptValue qVectorMeshFaceToScriptValue(ScriptEngine* engine, const QVector& vector) { + ScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); + } + return array; +} + +bool qVectorMeshFaceFromScriptValue(const ScriptValue& array, QVector& result) { + int length = array.property("length").toInteger(); + result.clear(); + + for (int i = 0; i < length; i++) { + MeshFace meshFace = MeshFace(); + meshFaceFromScriptValue(array.property(i), meshFace); + result << meshFace; + } + return true; +} + +ScriptValue stencilMaskModeToScriptValue(ScriptEngine* engine, const StencilMaskMode& stencilMode) { + return engine->newValue((int)stencilMode); +} + +bool stencilMaskModeFromScriptValue(const ScriptValue& object, StencilMaskMode& stencilMode) { + stencilMode = StencilMaskMode(object.toVariant().toInt()); + return true; +} + +bool promiseFromScriptValue(const ScriptValue& object, std::shared_ptr& promise) { + Q_ASSERT(false); + return false; +} +ScriptValue promiseToScriptValue(ScriptEngine* engine, const std::shared_ptr& promise) { + return engine->newQObject(promise.get()); +} + +ScriptValue EntityItemIDtoScriptValue(ScriptEngine* engine, const EntityItemID& id) { + return quuidToScriptValue(engine, id); +} + +bool EntityItemIDfromScriptValue(const ScriptValue& object, EntityItemID& id) { + return quuidFromScriptValue(object, id); +} + +QVector qVectorEntityItemIDFromScriptValue(const ScriptValue& array) { + if (!array.isArray()) { + return QVector(); + } + QVector newVector; + int length = array.property("length").toInteger(); + newVector.reserve(length); + for (int i = 0; i < length; i++) { + QString uuidAsString = array.property(i).toString(); + EntityItemID fromString(uuidAsString); + newVector << fromString; + } + return newVector; +} diff --git a/libraries/script-engine/src/ScriptValueUtils.h b/libraries/script-engine/src/ScriptValueUtils.h new file mode 100644 index 00000000000..9773a0e58b8 --- /dev/null +++ b/libraries/script-engine/src/ScriptValueUtils.h @@ -0,0 +1,272 @@ +// +// ScriptValueUtils.h +// libraries/shared/src +// +// Created by Anthony Thibault on 4/15/16. +// Copyright 2016 High Fidelity, Inc. +// +// Utilities for working with QtScriptValues +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueUtils_h +#define hifi_ScriptValueUtils_h + +#include + +#include +#include + +#include "ScriptValue.h" + +bool isListOfStrings(const ScriptValue& value); + + +void registerMetaTypes(ScriptEngine* engine); + +// Mat4 +/*@jsdoc + * A 4 x 4 matrix, typically containing a scale, rotation, and translation transform. See also the {@link Mat4(0)|Mat4} object. + * + * @typedef {object} Mat4 + * @property {number} r0c0 - Row 0, column 0 value. + * @property {number} r1c0 - Row 1, column 0 value. + * @property {number} r2c0 - Row 2, column 0 value. + * @property {number} r3c0 - Row 3, column 0 value. + * @property {number} r0c1 - Row 0, column 1 value. + * @property {number} r1c1 - Row 1, column 1 value. + * @property {number} r2c1 - Row 2, column 1 value. + * @property {number} r3c1 - Row 3, column 1 value. + * @property {number} r0c2 - Row 0, column 2 value. + * @property {number} r1c2 - Row 1, column 2 value. + * @property {number} r2c2 - Row 2, column 2 value. + * @property {number} r3c2 - Row 3, column 2 value. + * @property {number} r0c3 - Row 0, column 3 value. + * @property {number} r1c3 - Row 1, column 3 value. + * @property {number} r2c3 - Row 2, column 3 value. + * @property {number} r3c3 - Row 3, column 3 value. + */ +ScriptValue mat4toScriptValue(ScriptEngine* engine, const glm::mat4& mat4); +bool mat4FromScriptValue(const ScriptValue& object, glm::mat4& mat4); + +/*@jsdoc +* A 2-dimensional vector. +* +* @typedef {object} Vec2 +* @property {number} x - X-coordinate of the vector. Synonyms: u. +* @property {number} y - Y-coordinate of the vector. Synonyms: v. +* @example Vec2s can be set in multiple ways and modified with their aliases, but still stringify in the same way +* Entities.editEntity(, { materialMappingPos: { x: 0.1, y: 0.2 }}); // { x: 0.1, y: 0.2 } +* Entities.editEntity(, { materialMappingPos: { u: 0.3, v: 0.4 }}); // { x: 0.3, y: 0.4 } +* Entities.editEntity(, { materialMappingPos: [0.5, 0.6] }); // { x: 0.5, y: 0.6 } +* Entities.editEntity(, { materialMappingPos: 0.7 }); // { x: 0.7, y: 0.7 } +* var color = Entities.getEntityProperties().materialMappingPos; // { x: 0.7, y: 0.7 } +* color.v = 0.8; // { x: 0.7, y: 0.8 } +*/ +ScriptValue vec2ToScriptValue(ScriptEngine* engine, const glm::vec2& vec2); +bool vec2FromScriptValue(const ScriptValue& object, glm::vec2& vec2); + +/*@jsdoc +* A 3-dimensional vector. See also the {@link Vec3(0)|Vec3} object. +* +* @typedef {object} Vec3 +* @property {number} x - X-coordinate of the vector. Synonyms: r, red. +* @property {number} y - Y-coordinate of the vector. Synonyms: g, green. +* @property {number} z - Z-coordinate of the vector. Synonyms: b, blue. +* @example Vec3 values can be set in multiple ways and modified with their aliases, but still stringify in the same +* way. +* Entities.editEntity(, { position: { x: 1, y: 2, z: 3 }}); // { x: 1, y: 2, z: 3 } +* Entities.editEntity(, { position: { r: 4, g: 5, b: 6 }}); // { x: 4, y: 5, z: 6 } +* Entities.editEntity(, { position: { red: 7, green: 8, blue: 9 }}); // { x: 7, y: 8, z: 9 } +* Entities.editEntity(, { position: [10, 11, 12] }); // { x: 10, y: 11, z: 12 } +* Entities.editEntity(, { position: 13 }); // { x: 13, y: 13, z: 13 } +* var position = Entities.getEntityProperties().position; // { x: 13, y: 13, z: 13 } +* position.g = 14; // { x: 13, y: 14, z: 13 } +* position.blue = 15; // { x: 13, y: 14, z: 15 } +* Entities.editEntity(, { position: "red"}); // { x: 255, y: 0, z: 0 } +* Entities.editEntity(, { position: "#00FF00"}); // { x: 0, y: 255, z: 0 } +*/ +ScriptValue vec3ToScriptValue(ScriptEngine* engine, const glm::vec3& vec3); +ScriptValue vec3ColorToScriptValue(ScriptEngine* engine, const glm::vec3& vec3); +bool vec3FromScriptValue(const ScriptValue& object, glm::vec3& vec3); + +/*@jsdoc + * A color vector. See also the {@link Vec3(0)|Vec3} object. + * + * @typedef {object} Color + * @property {number} red - Red component value. Integer in the range 0 - 255. Synonyms: r, x. + * @property {number} green - Green component value. Integer in the range 0 - 255. Synonyms: g, y. + * @property {number} blue - Blue component value. Integer in the range 0 - 255. Synonyms: b, z. + * @example Colors can be set in multiple ways and modified with their aliases, but still stringify in the same way + * Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } + * Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } + * Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } + * Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } + * Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } + * var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } + * color.g = 14; // { red: 13, green: 14, blue: 13 } + * color.blue = 15; // { red: 13, green: 14, blue: 15 } + * Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } + * Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } + */ +/*@jsdoc + * A color vector with real values. Values may also be null. See also the {@link Vec3(0)|Vec3} object. + * + * @typedef {object} ColorFloat + * @property {number} red - Red component value. Real in the range 0 - 255. Synonyms: r, x. + * @property {number} green - Green component value. Real in the range 0 - 255. Synonyms: g, y. + * @property {number} blue - Blue component value. Real in the range 0 - 255. Synonyms: b, z. + * @example ColorFloats can be set in multiple ways and modified with their aliases, but still stringify in the same way + * Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } + * Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } + * Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } + * Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } + * Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } + * var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } + * color.g = 14; // { red: 13, green: 14, blue: 13 } + * color.blue = 15; // { red: 13, green: 14, blue: 15 } + * Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } + * Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } + */ +ScriptValue u8vec3ToScriptValue(ScriptEngine* engine, const glm::u8vec3& vec3); +ScriptValue u8vec3ColorToScriptValue(ScriptEngine* engine, const glm::u8vec3& vec3); +bool u8vec3FromScriptValue(const ScriptValue& object, glm::u8vec3& vec3); + +/*@jsdoc + * A 4-dimensional vector. + * + * @typedef {object} Vec4 + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. + * @property {number} z - Z-coordinate of the vector. + * @property {number} w - W-coordinate of the vector. + */ +ScriptValue vec4toScriptValue(ScriptEngine* engine, const glm::vec4& vec4); +bool vec4FromScriptValue(const ScriptValue& object, glm::vec4& vec4); + +// Quaternions +ScriptValue quatToScriptValue(ScriptEngine* engine, const glm::quat& quat); +bool quatFromScriptValue(const ScriptValue& object, glm::quat& quat); + +/*@jsdoc + * Defines a rectangular portion of an image or screen, or similar. + * @typedef {object} Rect + * @property {number} x - Left, x-coordinate value. + * @property {number} y - Top, y-coordinate value. + * @property {number} width - Width of the rectangle. + * @property {number} height - Height of the rectangle. + */ +class QRect; +ScriptValue qRectToScriptValue(ScriptEngine* engine, const QRect& rect); +bool qRectFromScriptValue(const ScriptValue& object, QRect& rect); + +class QRectF; +ScriptValue qRectFToScriptValue(ScriptEngine* engine, const QRectF& rect); +bool qRectFFromScriptValue(const ScriptValue& object, QRectF& rect); + +// QColor +class QColor; +ScriptValue qColorToScriptValue(ScriptEngine* engine, const QColor& color); +bool qColorFromScriptValue(const ScriptValue& object, QColor& color); + +class QUrl; +ScriptValue qURLToScriptValue(ScriptEngine* engine, const QUrl& url); +bool qURLFromScriptValue(const ScriptValue& object, QUrl& url); + +// vector +Q_DECLARE_METATYPE(QVector) +ScriptValue qVectorVec3ToScriptValue(ScriptEngine* engine, const QVector& vector); +ScriptValue qVectorVec3ColorToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorVec3FromScriptValue(const ScriptValue& array, QVector& vector); +QVector qVectorVec3FromScriptValue(const ScriptValue& array); + +// vector +Q_DECLARE_METATYPE(QVector) +ScriptValue qVectorQuatToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorQuatFromScriptValue(const ScriptValue& array, QVector& vector); +QVector qVectorQuatFromScriptValue(const ScriptValue& array); + +// vector +ScriptValue qVectorBoolToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorBoolFromScriptValue(const ScriptValue& array, QVector& vector); +QVector qVectorBoolFromScriptValue(const ScriptValue& array); + +// vector +ScriptValue qVectorFloatToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorFloatFromScriptValue(const ScriptValue& array, QVector& vector); +QVector qVectorFloatFromScriptValue(const ScriptValue& array); + +// vector +ScriptValue qVectorIntToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorIntFromScriptValue(const ScriptValue& array, QVector& vector); + +ScriptValue qVectorQUuidToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorQUuidFromScriptValue(const ScriptValue& array, QVector& vector); +QVector qVectorQUuidFromScriptValue(const ScriptValue& array); + +class AACube; +ScriptValue aaCubeToScriptValue(ScriptEngine* engine, const AACube& aaCube); +bool aaCubeFromScriptValue(const ScriptValue& object, AACube& aaCube); + +class PickRay; +ScriptValue pickRayToScriptValue(ScriptEngine* engine, const PickRay& pickRay); +bool pickRayFromScriptValue(const ScriptValue& object, PickRay& pickRay); + +class Collision; +ScriptValue collisionToScriptValue(ScriptEngine* engine, const Collision& collision); +bool collisionFromScriptValue(const ScriptValue& object, Collision& collision); + +/*@jsdoc + * UUIDs (Universally Unique IDentifiers) are used to uniquely identify entities, avatars, and the like. They are represented + * in JavaScript as strings in the format, "{nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}", where the "n"s are + * hexadecimal digits. + * @typedef {string} Uuid + */ +//Q_DECLARE_METATYPE(QUuid) // don't need to do this for QUuid since it's already a meta type +ScriptValue quuidToScriptValue(ScriptEngine* engine, const QUuid& uuid); +bool quuidFromScriptValue(const ScriptValue& object, QUuid& uuid); + +//Q_DECLARE_METATYPE(QSizeF) // Don't need to to this becase it's arleady a meta type +class QSizeF; +ScriptValue qSizeFToScriptValue(ScriptEngine* engine, const QSizeF& qSizeF); +bool qSizeFFromScriptValue(const ScriptValue& object, QSizeF& qSizeF); + +class AnimationDetails; +ScriptValue animationDetailsToScriptValue(ScriptEngine* engine, const AnimationDetails& event); +bool animationDetailsFromScriptValue(const ScriptValue& object, AnimationDetails& event); + +class MeshProxy; +ScriptValue meshToScriptValue(ScriptEngine* engine, MeshProxy* const& in); +bool meshFromScriptValue(const ScriptValue& value, MeshProxy*& out); + +class MeshProxyList; +ScriptValue meshesToScriptValue(ScriptEngine* engine, const MeshProxyList& in); +bool meshesFromScriptValue(const ScriptValue& value, MeshProxyList& out); + +class MeshFace; +ScriptValue meshFaceToScriptValue(ScriptEngine* engine, const MeshFace& meshFace); +bool meshFaceFromScriptValue(const ScriptValue& object, MeshFace& meshFaceResult); +ScriptValue qVectorMeshFaceToScriptValue(ScriptEngine* engine, const QVector& vector); +bool qVectorMeshFaceFromScriptValue(const ScriptValue& array, QVector& result); + +enum class StencilMaskMode; +ScriptValue stencilMaskModeToScriptValue(ScriptEngine* engine, const StencilMaskMode& stencilMode); +bool stencilMaskModeFromScriptValue(const ScriptValue& object, StencilMaskMode& stencilMode); + +class MiniPromise; +bool promiseFromScriptValue(const ScriptValue& object, std::shared_ptr& promise); +ScriptValue promiseToScriptValue(ScriptEngine* engine, const std::shared_ptr& promise); + +class EntityItemID; +ScriptValue EntityItemIDtoScriptValue(ScriptEngine* engine, const EntityItemID& properties); +bool EntityItemIDfromScriptValue(const ScriptValue& object, EntityItemID& properties); +QVector qVectorEntityItemIDFromScriptValue(const ScriptValue& array); + +#endif // #define hifi_ScriptValueUtils_h + +/// @} diff --git a/libraries/script-engine/src/Scriptable.cpp b/libraries/script-engine/src/Scriptable.cpp new file mode 100644 index 00000000000..07860ce6df1 --- /dev/null +++ b/libraries/script-engine/src/Scriptable.cpp @@ -0,0 +1,22 @@ +// +// Scriptable.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Scriptable.h" + +static thread_local ScriptContext* scriptContextStore; + +ScriptContext* Scriptable::context() { + return scriptContextStore; +} + +void Scriptable::setContext(ScriptContext* context) { + scriptContextStore = context; +} diff --git a/libraries/script-engine/src/Scriptable.h b/libraries/script-engine/src/Scriptable.h new file mode 100644 index 00000000000..d2ca04ac69e --- /dev/null +++ b/libraries/script-engine/src/Scriptable.h @@ -0,0 +1,60 @@ +// +// Scriptable.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/1/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_Scriptable_h +#define hifi_Scriptable_h + +#include + +#include "ScriptContext.h" +#include "ScriptValue.h" + +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; + +/// [ScriptInterface] Provides an engine-independent interface for QScriptable +class Scriptable { +public: + static inline ScriptEnginePointer engine(); + static ScriptContext* context(); + static inline ScriptValue thisObject(); + static inline int argumentCount(); + static inline ScriptValue argument(int index); + + static void setContext(ScriptContext* context); +}; + +ScriptEnginePointer Scriptable::engine() { + ScriptContext* scriptContext = context(); + return scriptContext ? scriptContext->engine() : nullptr; +} + +ScriptValue Scriptable::thisObject() { + ScriptContext* scriptContext = context(); + return scriptContext ? scriptContext->thisObject() : ScriptValue(); +} + +int Scriptable::argumentCount() { + ScriptContext* scriptContext = context(); + return scriptContext ? scriptContext->argumentCount() : 0; +} + +ScriptValue Scriptable::argument(int index) { + ScriptContext* scriptContext = context(); + return scriptContext ? scriptContext->argument(index) : ScriptValue(); +} + +#endif // hifi_Scriptable_h + +/// @} diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index 2c90a73d2d8..74289420914 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -144,10 +144,10 @@ class ScriptsModel : public QAbstractItemModel { // No JSDoc because the particulars of the parent class is provided in the @class description. int columnCount(const QModelIndex& parent = QModelIndex()) const override; - // Not exposed in the API because no conversion between TreeNodeBase and QScriptValue is provided. + // Not exposed in the API because no conversion between TreeNodeBase and ScriptValue is provided. TreeNodeBase* getTreeNodeFromIndex(const QModelIndex& index) const; - // Not exposed in the API because no conversion between TreeNodeBase and QScriptValue is provided. + // Not exposed in the API because no conversion between TreeNodeBase and ScriptValue is provided. QList getFolderNodes(TreeNodeFolder* parent) const; enum Role { diff --git a/libraries/script-engine/src/SpatialEvent.cpp b/libraries/script-engine/src/SpatialEvent.cpp index 8520c0c485b..d663fae306b 100644 --- a/libraries/script-engine/src/SpatialEvent.cpp +++ b/libraries/script-engine/src/SpatialEvent.cpp @@ -12,6 +12,9 @@ #include "SpatialEvent.h" #include +#include "ScriptEngine.h" +#include "ScriptValueUtils.h" +#include "ScriptValue.h" SpatialEvent::SpatialEvent() : locTranslation(0.0f), @@ -30,17 +33,18 @@ SpatialEvent::SpatialEvent(const SpatialEvent& event) { } -QScriptValue SpatialEvent::toScriptValue(QScriptEngine* engine, const SpatialEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue SpatialEvent::toScriptValue(ScriptEngine* engine, const SpatialEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty("locTranslation", vec3ToScriptValue(engine, event.locTranslation) ); obj.setProperty("locRotation", quatToScriptValue(engine, event.locRotation) ); - obj.setProperty("absTranslation", vec3ToScriptValue(engine, event.absTranslation) ); - obj.setProperty("absRotation", quatToScriptValue(engine, event.absRotation) ); + obj.setProperty("absTranslation", vec3ToScriptValue(engine, event.absTranslation)); + obj.setProperty("absRotation", quatToScriptValue(engine, event.absRotation)); return obj; } -void SpatialEvent::fromScriptValue(const QScriptValue& object,SpatialEvent& event) { +bool SpatialEvent::fromScriptValue(const ScriptValue& object, SpatialEvent& event) { // nothing for now... + return false; } diff --git a/libraries/script-engine/src/SpatialEvent.h b/libraries/script-engine/src/SpatialEvent.h index 1ea31f1ed32..bfd9142b932 100644 --- a/libraries/script-engine/src/SpatialEvent.h +++ b/libraries/script-engine/src/SpatialEvent.h @@ -18,7 +18,9 @@ #include #include -#include +#include "ScriptValue.h" + +class ScriptEngine; /// [unused] Represents a spatial event to the scripting engine class SpatialEvent { @@ -26,8 +28,8 @@ class SpatialEvent { SpatialEvent(); SpatialEvent(const SpatialEvent& other); - static QScriptValue toScriptValue(QScriptEngine* engine, const SpatialEvent& event); - static void fromScriptValue(const QScriptValue& object, SpatialEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const SpatialEvent& event); + static bool fromScriptValue(const ScriptValue& object, SpatialEvent& event); glm::vec3 locTranslation; glm::quat locRotation; diff --git a/libraries/script-engine/src/TouchEvent.cpp b/libraries/script-engine/src/TouchEvent.cpp index 9e9f8bd34a1..d65b585b0ae 100644 --- a/libraries/script-engine/src/TouchEvent.cpp +++ b/libraries/script-engine/src/TouchEvent.cpp @@ -11,12 +11,12 @@ #include "TouchEvent.h" -#include -#include - #include #include "RegisteredMetaTypes.h" +#include "ScriptEngine.h" +#include "ScriptValue.h" +#include "ScriptValueUtils.h" TouchEvent::TouchEvent() : x(0.0f), @@ -204,8 +204,8 @@ void TouchEvent::calculateMetaAttributes(const TouchEvent& other) { * print(JSON.stringify(event)); * }); */ -QScriptValue TouchEvent::toScriptValue(QScriptEngine* engine, const TouchEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue TouchEvent::toScriptValue(ScriptEngine* engine, const TouchEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty("x", event.x); obj.setProperty("y", event.y); obj.setProperty("isPressed", event.isPressed); @@ -218,10 +218,10 @@ QScriptValue TouchEvent::toScriptValue(QScriptEngine* engine, const TouchEvent& obj.setProperty("isAlt", event.isAlt); obj.setProperty("touchPoints", event.touchPoints); - QScriptValue pointsObj = engine->newArray(); + ScriptValue pointsObj = engine->newArray(); int index = 0; foreach (glm::vec2 point, event.points) { - QScriptValue thisPoint = vec2ToScriptValue(engine, point); + ScriptValue thisPoint = vec2ToScriptValue(engine, point); pointsObj.setProperty(index, thisPoint); index++; } @@ -232,7 +232,7 @@ QScriptValue TouchEvent::toScriptValue(QScriptEngine* engine, const TouchEvent& obj.setProperty("angle", event.angle); obj.setProperty("deltaAngle", event.deltaAngle); - QScriptValue anglesObj = engine->newArray(); + ScriptValue anglesObj = engine->newArray(); index = 0; foreach (float angle, event.angles) { anglesObj.setProperty(index, angle); @@ -245,6 +245,7 @@ QScriptValue TouchEvent::toScriptValue(QScriptEngine* engine, const TouchEvent& return obj; } -void TouchEvent::fromScriptValue(const QScriptValue& object, TouchEvent& event) { +bool TouchEvent::fromScriptValue(const ScriptValue& object, TouchEvent& event) { // nothing for now... + return false; } diff --git a/libraries/script-engine/src/TouchEvent.h b/libraries/script-engine/src/TouchEvent.h index d084514ff6d..81d093f4e1e 100644 --- a/libraries/script-engine/src/TouchEvent.h +++ b/libraries/script-engine/src/TouchEvent.h @@ -20,8 +20,9 @@ #include #include -class QScriptValue; -class QScriptEngine; +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a display or device event to the scripting engine. Exposed as TouchEvent class TouchEvent { @@ -30,8 +31,8 @@ class TouchEvent { TouchEvent(const QTouchEvent& event); TouchEvent(const QTouchEvent& event, const TouchEvent& other); - static QScriptValue toScriptValue(QScriptEngine* engine, const TouchEvent& event); - static void fromScriptValue(const QScriptValue& object, TouchEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const TouchEvent& event); + static bool fromScriptValue(const ScriptValue& object, TouchEvent& event); float x; float y; diff --git a/libraries/shared/src/VariantMapToScriptValue.cpp b/libraries/script-engine/src/VariantMapToScriptValue.cpp similarity index 69% rename from libraries/shared/src/VariantMapToScriptValue.cpp rename to libraries/script-engine/src/VariantMapToScriptValue.cpp index 156a438bd74..a1a50ed207d 100644 --- a/libraries/shared/src/VariantMapToScriptValue.cpp +++ b/libraries/script-engine/src/VariantMapToScriptValue.cpp @@ -15,20 +15,20 @@ #include "SharedLogging.h" -QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine) { +ScriptValue variantToScriptValue(QVariant& qValue, ScriptEngine& scriptEngine) { switch(qValue.type()) { case QVariant::Bool: - return qValue.toBool(); + return scriptEngine.newValue(qValue.toBool()); break; case QVariant::Int: - return qValue.toInt(); + return scriptEngine.newValue(qValue.toInt()); break; case QVariant::Double: - return qValue.toDouble(); + return scriptEngine.newValue(qValue.toDouble()); break; case QVariant::String: case QVariant::Url: - return qValue.toString(); + return scriptEngine.newValue(qValue.toString()); break; case QVariant::Map: { QVariantMap childMap = qValue.toMap(); @@ -42,18 +42,18 @@ QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine) } default: if (qValue.canConvert()) { - return qValue.toFloat(); + return scriptEngine.newValue(qValue.toFloat()); } //qCDebug(shared) << "unhandled QScript type" << qValue.type(); break; } - return QScriptValue(); + return ScriptValue(); } -QScriptValue variantMapToScriptValue(QVariantMap& variantMap, QScriptEngine& scriptEngine) { - QScriptValue scriptValue = scriptEngine.newObject(); +ScriptValue variantMapToScriptValue(QVariantMap& variantMap, ScriptEngine& scriptEngine) { + ScriptValue scriptValue = scriptEngine.newObject(); for (QVariantMap::const_iterator iter = variantMap.begin(); iter != variantMap.end(); ++iter) { QString key = iter.key(); @@ -65,9 +65,9 @@ QScriptValue variantMapToScriptValue(QVariantMap& variantMap, QScriptEngine& scr } -QScriptValue variantListToScriptValue(QVariantList& variantList, QScriptEngine& scriptEngine) { +ScriptValue variantListToScriptValue(QVariantList& variantList, ScriptEngine& scriptEngine) { - QScriptValue scriptValue = scriptEngine.newArray(); + ScriptValue scriptValue = scriptEngine.newArray(); for (int i = 0; i < variantList.size(); i++) { scriptValue.setProperty(i, variantToScriptValue(variantList[i], scriptEngine)); diff --git a/libraries/script-engine/src/VariantMapToScriptValue.h b/libraries/script-engine/src/VariantMapToScriptValue.h new file mode 100644 index 00000000000..956f7c2f152 --- /dev/null +++ b/libraries/script-engine/src/VariantMapToScriptValue.h @@ -0,0 +1,23 @@ +// +// VariantMapToScriptValue.h +// libraries/shared/src/ +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#include +#include "ScriptValue.h" +#include "ScriptEngine.h" + +ScriptValue variantToScriptValue(QVariant& qValue, ScriptEngine& scriptEngine); +ScriptValue variantMapToScriptValue(QVariantMap& variantMap, ScriptEngine& scriptEngine); +ScriptValue variantListToScriptValue(QVariantList& variantList, ScriptEngine& scriptEngine); + +/// @} diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index 2d3d4454c34..84df545bd2c 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -21,6 +21,7 @@ #include "NumericalConstants.h" #include "ScriptEngine.h" #include "ScriptEngineLogging.h" +#include "ScriptManager.h" float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { @@ -32,8 +33,8 @@ void Vec3::print(const QString& label, const glm::vec3& v) { QString message = QString("%1 %2").arg(qPrintable(label)); message = message.arg(glm::to_string(glm::dvec3(v)).c_str()); qCDebug(scriptengine) << message; - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { - scriptEngine->print(message); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->print(message); } } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 3c357ae6fc8..c1eecde45a2 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -20,9 +20,9 @@ #include #include -#include #include "GLMHelpers.h" +#include "Scriptable.h" /*@jsdoc * The Vec3 API provides facilities for generating and manipulating 3-dimensional vectors. Vircadia uses a @@ -75,7 +75,7 @@ * UNIT_NEG_Z. Read-only. */ /// Provides the Vec3 scripting interface -class Vec3 : public QObject, protected QScriptable { +class Vec3 : public QObject, protected Scriptable { Q_OBJECT Q_PROPERTY(glm::vec3 UNIT_X READ UNIT_X CONSTANT) Q_PROPERTY(glm::vec3 UNIT_Y READ UNIT_Y CONSTANT) diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index a001e2b2c27..fbe2b7fc063 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -14,11 +14,13 @@ #include "WebSocketClass.h" +#include "ScriptContext.h" #include "ScriptEngine.h" - +#include "ScriptEngineCast.h" #include "ScriptEngineLogging.h" +#include "ScriptValue.h" -WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : +WebSocketClass::WebSocketClass(ScriptEngine* engine, QString url) : _webSocket(new QWebSocket()), _engine(engine) { @@ -26,7 +28,7 @@ WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : _webSocket->open(url); } -WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) : +WebSocketClass::WebSocketClass(ScriptEngine* engine, QWebSocket* qWebSocket) : _webSocket(qWebSocket), _engine(engine) { @@ -43,21 +45,21 @@ void WebSocketClass::initialize() { _binaryType = QStringLiteral("arraybuffer"); } -QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { +ScriptValue WebSocketClass::constructor(ScriptContext* context, ScriptEngine* engine) { QString url; if (context->argumentCount() > 0) { url = context->argument(0).toString(); } - return engine->newQObject(new WebSocketClass(engine, url), QScriptEngine::ScriptOwnership); + return engine->newQObject(new WebSocketClass(engine, url), ScriptEngine::ScriptOwnership); } WebSocketClass::~WebSocketClass() { _webSocket->deleteLater(); } -void WebSocketClass::send(QScriptValue message) { +void WebSocketClass::send(const ScriptValue& message) { if (message.isObject()) { - QByteArray ba = qscriptvalue_cast(message); + QByteArray ba = scriptvalue_cast(message); _webSocket->sendBinaryMessage(ba); } else { _webSocket->sendTextMessage(message.toString()); @@ -91,13 +93,13 @@ void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reas void WebSocketClass::handleOnClose() { bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError); if (_onCloseEvent.isFunction()) { - QScriptValueList args; - QScriptValue arg = _engine->newObject(); + ScriptValueList args; + ScriptValue arg = _engine->newObject(); arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCodeAbnormalDisconnection : _webSocket->closeCode()); arg.setProperty("reason", _webSocket->closeReason()); arg.setProperty("wasClean", !hasError); args << arg; - _onCloseEvent.call(QScriptValue(), args); + _onCloseEvent.call(ScriptValue(), args); } } @@ -170,30 +172,25 @@ void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { */ void WebSocketClass::handleOnMessage(const QString& message) { if (_onMessageEvent.isFunction()) { - QScriptValueList args; - QScriptValue arg = _engine->newObject(); + ScriptValueList args; + ScriptValue arg = _engine->newObject(); arg.setProperty("data", message); args << arg; - _onMessageEvent.call(QScriptValue(), args); + _onMessageEvent.call(ScriptValue(), args); } } void WebSocketClass::handleOnBinaryMessage(const QByteArray& message) { if (_onMessageEvent.isFunction()) { - QScriptValueList args; - QScriptValue arg = _engine->newObject(); - QScriptValue data = _engine->newVariant(QVariant::fromValue(message)); - QScriptValue ctor = _engine->globalObject().property("ArrayBuffer"); - auto array = qscriptvalue_cast(ctor.data()); - QScriptValue arrayBuffer; - if (!array) { + ScriptValueList args; + ScriptValue arg = _engine->newObject(); + ScriptValue arrayBuffer = _engine->newArrayBuffer(message); + if (arrayBuffer.isUndefined()) { qCWarning(scriptengine) << "WebSocketClass::handleOnBinaryMessage !ArrayBuffer"; - } else { - arrayBuffer = _engine->newObject(array, data); } arg.setProperty("data", arrayBuffer); args << arg; - _onMessageEvent.call(QScriptValue(), args); + _onMessageEvent.call(ScriptValue(), args); } } @@ -207,26 +204,29 @@ void WebSocketClass::handleOnOpen() { } } -QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) { - return closeCode; +ScriptValue qWSCloseCodeToScriptValue(ScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) { + return engine->newValue(closeCode); } -void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) { +bool qWSCloseCodeFromScriptValue(const ScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) { closeCode = (QWebSocketProtocol::CloseCode)object.toUInt16(); + return true; } -QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) { - return engine->newQObject(in, QScriptEngine::ScriptOwnership); +ScriptValue webSocketToScriptValue(ScriptEngine* engine, WebSocketClass* const &in) { + return engine->newQObject(in, ScriptEngine::ScriptOwnership); } -void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) { +bool webSocketFromScriptValue(const ScriptValue &object, WebSocketClass* &out) { out = qobject_cast(object.toQObject()); + return true; } -QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState) { - return readyState; +ScriptValue wscReadyStateToScriptValue(ScriptEngine* engine, const WebSocketClass::ReadyState& readyState) { + return engine->newValue(readyState); } -void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState) { +bool wscReadyStateFromScriptValue(const ScriptValue& object, WebSocketClass::ReadyState& readyState) { readyState = (WebSocketClass::ReadyState)object.toUInt16(); + return true; } diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h index 457c955dc8f..f0b37579e1a 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -16,9 +16,13 @@ #define hifi_WebSocketClass_h #include -#include #include +#include "ScriptValue.h" + +class ScriptContext; +class ScriptEngine; + /*@jsdoc * Provides a bi-directional, event-driven communication session between the script and another WebSocket connection. It is a * near-complete implementation of the WebSocket API described in the Mozilla docs: @@ -88,10 +92,10 @@ class WebSocketClass : public QObject { Q_PROPERTY(ulong bufferedAmount READ getBufferedAmount) Q_PROPERTY(QString extensions READ getExtensions) - Q_PROPERTY(QScriptValue onclose READ getOnClose WRITE setOnClose) - Q_PROPERTY(QScriptValue onerror READ getOnError WRITE setOnError) - Q_PROPERTY(QScriptValue onmessage READ getOnMessage WRITE setOnMessage) - Q_PROPERTY(QScriptValue onopen READ getOnOpen WRITE setOnOpen) + Q_PROPERTY(ScriptValue onclose READ getOnClose WRITE setOnClose) + Q_PROPERTY(ScriptValue onerror READ getOnError WRITE setOnError) + Q_PROPERTY(ScriptValue onmessage READ getOnMessage WRITE setOnMessage) + Q_PROPERTY(ScriptValue onopen READ getOnOpen WRITE setOnOpen) Q_PROPERTY(QString protocol READ getProtocol) Q_PROPERTY(WebSocketClass::ReadyState readyState READ getReadyState) @@ -103,11 +107,11 @@ class WebSocketClass : public QObject { Q_PROPERTY(WebSocketClass::ReadyState CLOSED READ getClosed CONSTANT) public: - WebSocketClass(QScriptEngine* engine, QString url); - WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket); + WebSocketClass(ScriptEngine* engine, QString url); + WebSocketClass(ScriptEngine* engine, QWebSocket* qWebSocket); ~WebSocketClass(); - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine); /*@jsdoc * The state of a WebSocket connection. @@ -168,17 +172,17 @@ class WebSocketClass : public QObject { } } - void setOnClose(QScriptValue eventFunction) { _onCloseEvent = eventFunction; } - QScriptValue getOnClose() { return _onCloseEvent; } + void setOnClose(const ScriptValue& eventFunction) { _onCloseEvent = eventFunction; } + ScriptValue getOnClose() { return _onCloseEvent; } - void setOnError(QScriptValue eventFunction) { _onErrorEvent = eventFunction; } - QScriptValue getOnError() { return _onErrorEvent; } + void setOnError(const ScriptValue& eventFunction) { _onErrorEvent = eventFunction; } + ScriptValue getOnError() { return _onErrorEvent; } - void setOnMessage(QScriptValue eventFunction) { _onMessageEvent = eventFunction; } - QScriptValue getOnMessage() { return _onMessageEvent; } + void setOnMessage(const ScriptValue& eventFunction) { _onMessageEvent = eventFunction; } + ScriptValue getOnMessage() { return _onMessageEvent; } - void setOnOpen(QScriptValue eventFunction) { _onOpenEvent = eventFunction; } - QScriptValue getOnOpen() { return _onOpenEvent; } + void setOnOpen(const ScriptValue& eventFunction) { _onOpenEvent = eventFunction; } + ScriptValue getOnOpen() { return _onOpenEvent; } public slots: @@ -187,7 +191,7 @@ public slots: * @function WebSocket.send * @param {string|object} message - The message to send. If an object, it is converted to a string. */ - void send(QScriptValue message); + void send(const ScriptValue& message); /*@jsdoc * Closes the connection. @@ -225,12 +229,12 @@ public slots: private: QWebSocket* _webSocket; - QScriptEngine* _engine; + ScriptEngine* _engine; - QScriptValue _onCloseEvent; - QScriptValue _onErrorEvent; - QScriptValue _onMessageEvent; - QScriptValue _onOpenEvent; + ScriptValue _onCloseEvent; + ScriptValue _onErrorEvent; + ScriptValue _onMessageEvent; + ScriptValue _onOpenEvent; QString _binaryType; @@ -248,14 +252,14 @@ private slots: Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode); Q_DECLARE_METATYPE(WebSocketClass::ReadyState); -QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode); -void qWSCloseCodeFromScriptValue(const QScriptValue& object, QWebSocketProtocol::CloseCode& closeCode); +ScriptValue qWSCloseCodeToScriptValue(ScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode); +bool qWSCloseCodeFromScriptValue(const ScriptValue& object, QWebSocketProtocol::CloseCode& closeCode); -QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in); -void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out); +ScriptValue webSocketToScriptValue(ScriptEngine* engine, WebSocketClass* const &in); +bool webSocketFromScriptValue(const ScriptValue& object, WebSocketClass*& out); -QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState); -void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState); +ScriptValue wscReadyStateToScriptValue(ScriptEngine* engine, const WebSocketClass::ReadyState& readyState); +bool wscReadyStateFromScriptValue(const ScriptValue& object, WebSocketClass::ReadyState& readyState); #endif // hifi_WebSocketClass_h diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp index 860170a3f98..304219952cb 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -13,9 +13,11 @@ #include "WebSocketServerClass.h" +#include "ScriptContext.h" #include "ScriptEngine.h" +#include "ScriptValue.h" -WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port) : +WebSocketServerClass::WebSocketServerClass(ScriptEngine* engine, const QString& serverName, const quint16 port) : _webSocketServer(serverName, QWebSocketServer::SslMode::NonSecureMode), _engine(engine) { @@ -24,24 +26,24 @@ WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& } } -QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine) { +ScriptValue WebSocketServerClass::constructor(ScriptContext* context, ScriptEngine* engine) { // the serverName is used in handshakes QString serverName = QStringLiteral("HighFidelity - Scripted WebSocket Listener"); // port 0 will auto-assign a free port quint16 port = 0; - QScriptValue callee = context->callee(); + ScriptValue callee = context->callee(); if (context->argumentCount() > 0) { - QScriptValue options = context->argument(0); - QScriptValue portOption = options.property(QStringLiteral("port")); + ScriptValue options = context->argument(0); + ScriptValue portOption = options.property(QStringLiteral("port")); if (portOption.isValid() && portOption.isNumber()) { port = portOption.toNumber(); } - QScriptValue serverNameOption = options.property(QStringLiteral("serverName")); + ScriptValue serverNameOption = options.property(QStringLiteral("serverName")); if (serverNameOption.isValid() && serverNameOption.isString()) { serverName = serverNameOption.toString(); } } - return engine->newQObject(new WebSocketServerClass(engine, serverName, port), QScriptEngine::ScriptOwnership); + return engine->newQObject(new WebSocketServerClass(engine, serverName, port), ScriptEngine::ScriptOwnership); } WebSocketServerClass::~WebSocketServerClass() { diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h index 7cf03d7b41d..2ff2b98a953 100644 --- a/libraries/script-engine/src/WebSocketServerClass.h +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -16,10 +16,14 @@ #define hifi_WebSocketServerClass_h #include -#include #include #include "WebSocketClass.h" +#include "ScriptValue.h" + +class ScriptContext; +class ScriptEngine; + /*@jsdoc * Manages {@link WebSocket}s in server entity and assignment client scripts. * @@ -84,14 +88,14 @@ class WebSocketServerClass : public QObject { Q_PROPERTY(bool listening READ isListening) public: - WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port); + WebSocketServerClass(ScriptEngine* engine, const QString& serverName, const quint16 port); ~WebSocketServerClass(); QString getURL() { return _webSocketServer.serverUrl().toDisplayString(); } quint16 getPort() { return _webSocketServer.serverPort(); } bool isListening() { return _webSocketServer.isListening(); } - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine); public slots: @@ -103,7 +107,7 @@ public slots: private: QWebSocketServer _webSocketServer; - QScriptEngine* _engine; + ScriptEngine* _engine; QList _clients; private slots: diff --git a/libraries/script-engine/src/WheelEvent.cpp b/libraries/script-engine/src/WheelEvent.cpp index 565c2ddb501..e22e04d078b 100644 --- a/libraries/script-engine/src/WheelEvent.cpp +++ b/libraries/script-engine/src/WheelEvent.cpp @@ -11,8 +11,8 @@ #include "WheelEvent.h" -#include -#include +#include "ScriptEngine.h" +#include "ScriptValue.h" WheelEvent::WheelEvent() : x(0.0f), @@ -81,8 +81,8 @@ WheelEvent::WheelEvent(const QWheelEvent& event) { * print(JSON.stringify(event)); * }); */ -QScriptValue WheelEvent::toScriptValue(QScriptEngine* engine, const WheelEvent& event) { - QScriptValue obj = engine->newObject(); +ScriptValue WheelEvent::toScriptValue(ScriptEngine* engine, const WheelEvent& event) { + ScriptValue obj = engine->newObject(); obj.setProperty("x", event.x); obj.setProperty("y", event.y); obj.setProperty("delta", event.delta); @@ -97,6 +97,7 @@ QScriptValue WheelEvent::toScriptValue(QScriptEngine* engine, const WheelEvent& return obj; } -void WheelEvent::fromScriptValue(const QScriptValue& object, WheelEvent& event) { +bool WheelEvent::fromScriptValue(const ScriptValue& object, WheelEvent& event) { // nothing for now... + return false; } diff --git a/libraries/script-engine/src/WheelEvent.h b/libraries/script-engine/src/WheelEvent.h index a2b14ac29df..35a3dc3c04a 100644 --- a/libraries/script-engine/src/WheelEvent.h +++ b/libraries/script-engine/src/WheelEvent.h @@ -18,8 +18,9 @@ #include #include -class QScriptValue; -class QScriptEngine; +#include "ScriptValue.h" + +class ScriptEngine; /// Represents a mouse wheel event to the scripting engine. Exposed as WheelEvent class WheelEvent { @@ -27,8 +28,8 @@ class WheelEvent { WheelEvent(); WheelEvent(const QWheelEvent& event); - static QScriptValue toScriptValue(QScriptEngine* engine, const WheelEvent& event); - static void fromScriptValue(const QScriptValue& object, WheelEvent& event); + static ScriptValue toScriptValue(ScriptEngine* engine, const WheelEvent& event); + static bool fromScriptValue(const ScriptValue& object, WheelEvent& event); int x; int y; diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index e8acba83666..fbac5120345 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -23,12 +23,17 @@ #include #include "ResourceRequestObserver.h" +#include "ScriptContext.h" #include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValue.h" Q_DECLARE_METATYPE(QByteArray*) -XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : +XMLHttpRequestClass::XMLHttpRequestClass(ScriptEngine* engine) : _engine(engine), + _onTimeout(engine->nullValue()), + _onReadyStateChange(engine->nullValue()), _timer(this) { _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -39,15 +44,15 @@ XMLHttpRequestClass::~XMLHttpRequestClass() { if (_reply) { _reply->deleteLater(); } } -QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) { - return engine->newQObject(new XMLHttpRequestClass(engine), QScriptEngine::ScriptOwnership); +ScriptValue XMLHttpRequestClass::constructor(ScriptContext* context, ScriptEngine* engine) { + return engine->newQObject(new XMLHttpRequestClass(engine), ScriptEngine::ScriptOwnership); } -QScriptValue XMLHttpRequestClass::getStatus() const { +ScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { - return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + return _engine->newValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); } - return QScriptValue(0); + return ScriptValue(_engine->newValue(0)); } QString XMLHttpRequestClass::getStatusText() const { @@ -87,7 +92,7 @@ void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 b } } -QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { +ScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { if (_reply) { QList headerList = _reply->rawHeaderPairs(); QByteArray headers; @@ -97,16 +102,16 @@ QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { headers.append(headerList[i].second); headers.append("\n"); } - return QString(headers.data()); + return _engine->newValue(QString(headers.data())); } - return QScriptValue(""); + return _engine->newValue(""); } -QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { +ScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { if (_reply && _reply->hasRawHeader(name.toLatin1())) { - return QScriptValue(QString(_reply->rawHeader(name.toLatin1()))); + return _engine->newValue(QString(_reply->rawHeader(name.toLatin1()))); } - return QScriptValue::NullValue; + return _engine->nullValue(); } /*@jsdoc @@ -117,7 +122,7 @@ void XMLHttpRequestClass::setReadyState(ReadyState readyState) { if (readyState != _readyState) { _readyState = readyState; if (_onReadyStateChange.isFunction()) { - _onReadyStateChange.call(QScriptValue::NullValue); + _onReadyStateChange.call(_onReadyStateChange.engine()->nullValue()); } } } @@ -153,15 +158,15 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } void XMLHttpRequestClass::send() { - send(QScriptValue::NullValue); + send(_engine->nullValue()); } -void XMLHttpRequestClass::send(const QScriptValue& data) { +void XMLHttpRequestClass::send(const ScriptValue& data) { if (_readyState == OPENED && !_reply) { if (!data.isNull()) { if (data.isObject()) { - _sendData = qscriptvalue_cast(data); + _sendData = scriptvalue_cast(data); } else { _sendData = data.toString().toUtf8(); } @@ -194,7 +199,7 @@ void XMLHttpRequestClass::doSend() { */ void XMLHttpRequestClass::requestTimeout() { if (_onTimeout.isFunction()) { - _onTimeout.call(QScriptValue::NullValue); + _onTimeout.call(_engine->nullValue()); } abortRequest(); _errorCode = QNetworkReply::TimeoutError; @@ -217,13 +222,12 @@ void XMLHttpRequestClass::requestFinished() { _responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")"); if (_responseData.isError()) { _engine->clearExceptions(); - _responseData = QScriptValue::NullValue; + _responseData = _engine->nullValue(); } } else if (_responseType == "arraybuffer") { - QScriptValue data = _engine->newVariant(QVariant::fromValue(_rawResponseData)); - _responseData = _engine->newObject(reinterpret_cast(_engine)->getArrayBufferClass(), data); + _responseData = _engine->newArrayBuffer(_rawResponseData); } else { - _responseData = QScriptValue(QString(_rawResponseData.data())); + _responseData = _engine->newValue(QString(_rawResponseData.data())); } } diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index 013f888be82..7e38a2155bd 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -19,11 +19,13 @@ #include #include #include -#include -#include -#include #include +#include "ScriptEngine.h" +#include "ScriptValue.h" + +class ScriptContext; + /* XMlHttpRequest object XMlHttpRequest.objectName string @@ -54,7 +56,7 @@ XMlHttpRequest.open(QString,QString,bool,QString) function XMlHttpRequest.open(QString,QString,bool) function XMlHttpRequest.open(QString,QString) function XMlHttpRequest.send() function -XMlHttpRequest.send(QScriptValue) function +XMlHttpRequest.send(ScriptValue) function XMlHttpRequest.getAllResponseHeaders() function XMlHttpRequest.getResponseHeader(QString) function */ @@ -155,13 +157,13 @@ XMlHttpRequest.getResponseHeader(QString) function /// Provides the XMLHttpRequest scripting interface class XMLHttpRequestClass : public QObject { Q_OBJECT - Q_PROPERTY(QScriptValue response READ getResponse) - Q_PROPERTY(QScriptValue responseText READ getResponseText) + Q_PROPERTY(ScriptValue response READ getResponse) + Q_PROPERTY(ScriptValue responseText READ getResponseText) Q_PROPERTY(QString responseType READ getResponseType WRITE setResponseType) - Q_PROPERTY(QScriptValue status READ getStatus) + Q_PROPERTY(ScriptValue status READ getStatus) Q_PROPERTY(QString statusText READ getStatusText) - Q_PROPERTY(QScriptValue readyState READ getReadyState) - Q_PROPERTY(QScriptValue errorCode READ getError) + Q_PROPERTY(ScriptValue readyState READ getReadyState) + Q_PROPERTY(ScriptValue errorCode READ getError) Q_PROPERTY(int timeout READ getTimeout WRITE setTimeout) Q_PROPERTY(int UNSENT READ getUnsent) @@ -171,10 +173,10 @@ class XMLHttpRequestClass : public QObject { Q_PROPERTY(int DONE READ getDone) // Callbacks - Q_PROPERTY(QScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout) - Q_PROPERTY(QScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange) + Q_PROPERTY(ScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout) + Q_PROPERTY(ScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange) public: - XMLHttpRequestClass(QScriptEngine* engine); + XMLHttpRequestClass(ScriptEngine* engine); ~XMLHttpRequestClass(); static const int MAXIMUM_REDIRECTS = 5; @@ -212,23 +214,23 @@ class XMLHttpRequestClass : public QObject { int getLoading() const { return LOADING; }; int getDone() const { return DONE; }; - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine); int getTimeout() const { return _timeout; } void setTimeout(int timeout) { _timeout = timeout; } - QScriptValue getResponse() const { return _responseData; } - QScriptValue getResponseText() const { return QScriptValue(QString(_rawResponseData.data())); } + ScriptValue getResponse() const { return _responseData; } + ScriptValue getResponseText() const { return _engine->newValue(QString(_rawResponseData.data())); } QString getResponseType() const { return _responseType; } void setResponseType(const QString& responseType) { _responseType = responseType; } - QScriptValue getReadyState() const { return QScriptValue(_readyState); } - QScriptValue getError() const { return QScriptValue(_errorCode); } - QScriptValue getStatus() const; + ScriptValue getReadyState() const { return _engine->newValue(_readyState); } + ScriptValue getError() const { return _engine->newValue(_errorCode); } + ScriptValue getStatus() const; QString getStatusText() const; - QScriptValue getOnTimeout() const { return _onTimeout; } - void setOnTimeout(QScriptValue function) { _onTimeout = function; } - QScriptValue getOnReadyStateChange() const { return _onReadyStateChange; } - void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } + ScriptValue getOnTimeout() const { return _onTimeout; } + void setOnTimeout(const ScriptValue& function) { _onTimeout = function; } + ScriptValue getOnReadyStateChange() const { return _onReadyStateChange; } + void setOnReadyStateChange(const ScriptValue& function) { _onReadyStateChange = function; } public slots: @@ -267,14 +269,14 @@ public slots: * @param {*} [data] - The data to send. */ void send(); - void send(const QScriptValue& data); + void send(const ScriptValue& data); /*@jsdoc * Gets the response headers. * @function XMLHttpRequest.getAllResponseHeaders * @returns {string} The response headers, separated by "\n" characters. */ - QScriptValue getAllResponseHeaders() const; + ScriptValue getAllResponseHeaders() const; /*@jsdoc * Gets a response header. @@ -282,7 +284,7 @@ public slots: * @param {string} name - * @returns {string} The response header. */ - QScriptValue getResponseHeader(const QString& name) const; + ScriptValue getResponseHeader(const QString& name) const; signals: @@ -300,7 +302,7 @@ public slots: void disconnectFromReply(QNetworkReply* reply); void abortRequest(); - QScriptEngine* _engine { nullptr }; + ScriptEngine* _engine { nullptr }; bool _async { true }; QUrl _url; QString _method; @@ -309,9 +311,9 @@ public slots: QNetworkReply* _reply { nullptr }; QByteArray _sendData; QByteArray _rawResponseData; - QScriptValue _responseData; - QScriptValue _onTimeout { QScriptValue::NullValue }; - QScriptValue _onReadyStateChange { QScriptValue::NullValue }; + ScriptValue _responseData; + ScriptValue _onTimeout; + ScriptValue _onReadyStateChange; ReadyState _readyState { XMLHttpRequestClass::UNSENT }; /*@jsdoc diff --git a/libraries/script-engine/src/ArrayBufferClass.cpp b/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp similarity index 94% rename from libraries/script-engine/src/ArrayBufferClass.cpp rename to libraries/script-engine/src/qtscript/ArrayBufferClass.cpp index 6734114932f..ef4b3567440 100644 --- a/libraries/script-engine/src/ArrayBufferClass.cpp +++ b/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp @@ -15,7 +15,7 @@ #include "ArrayBufferPrototype.h" #include "DataViewClass.h" -#include "ScriptEngine.h" +#include "ScriptEngineQtScript.h" #include "TypedArrays.h" @@ -23,12 +23,9 @@ static const QString CLASS_NAME = "ArrayBuffer"; // FIXME: Q_DECLARE_METATYPE is global and really belongs in a shared header file, not per .cpp like this // (see DataViewClass.cpp, etc. which would also have to be updated to resolve) -Q_DECLARE_METATYPE(QScriptClass*) Q_DECLARE_METATYPE(QByteArray*) -int qScriptClassPointerMetaTypeId = qRegisterMetaType(); -int qByteArrayPointerMetaTypeId = qRegisterMetaType(); -ArrayBufferClass::ArrayBufferClass(ScriptEngine* scriptEngine) : +ArrayBufferClass::ArrayBufferClass(ScriptEngineQtScript* scriptEngine) : QObject(scriptEngine), QScriptClass(scriptEngine) { qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); @@ -87,8 +84,9 @@ QScriptValue ArrayBufferClass::newInstance(qint32 size) { } QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { - QScriptValue data = engine()->newVariant(QVariant::fromValue(ba)); - return engine()->newObject(this, data); + QScriptEngine* eng = engine(); + QScriptValue data = eng->newVariant(QVariant::fromValue(ba)); + return eng->newObject(this, data); } QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) { diff --git a/libraries/script-engine/src/ArrayBufferClass.h b/libraries/script-engine/src/qtscript/ArrayBufferClass.h similarity index 85% rename from libraries/script-engine/src/ArrayBufferClass.h rename to libraries/script-engine/src/qtscript/ArrayBufferClass.h index 6438272f5b7..c9efce1ad49 100644 --- a/libraries/script-engine/src/ArrayBufferClass.h +++ b/libraries/script-engine/src/qtscript/ArrayBufferClass.h @@ -15,7 +15,7 @@ #ifndef hifi_ArrayBufferClass_h #define hifi_ArrayBufferClass_h -#include +#include #include #include #include @@ -24,13 +24,13 @@ #include #include -class ScriptEngine; +class ScriptEngineQtScript; -/// Implements the ArrayBuffer scripting class +/// [QtScript] Implements the ArrayBuffer scripting class class ArrayBufferClass : public QObject, public QScriptClass { Q_OBJECT public: - ArrayBufferClass(ScriptEngine* scriptEngine); + ArrayBufferClass(ScriptEngineQtScript* scriptEngine); QScriptValue newInstance(qint32 size); QScriptValue newInstance(const QByteArray& ba); diff --git a/libraries/script-engine/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp similarity index 97% rename from libraries/script-engine/src/ArrayBufferPrototype.cpp rename to libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp index d75482aa2e6..ad5a68ec796 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp @@ -13,10 +13,9 @@ #include -#include -#include - -#include "ArrayBufferClass.h" +#include +#include +#include static const int QCOMPRESS_HEADER_POSITION = 0; static const int QCOMPRESS_HEADER_SIZE = 4; diff --git a/libraries/script-engine/src/ArrayBufferPrototype.h b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.h similarity index 81% rename from libraries/script-engine/src/ArrayBufferPrototype.h rename to libraries/script-engine/src/qtscript/ArrayBufferPrototype.h index 324f3176609..1fc5948f6bd 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.h +++ b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.h @@ -18,7 +18,7 @@ #include #include -/// The javascript functions associated with an ArrayBuffer instance prototype +/// [QtScript] The javascript functions associated with an ArrayBuffer instance prototype class ArrayBufferPrototype : public QObject, public QScriptable { Q_OBJECT public: diff --git a/libraries/script-engine/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp similarity index 89% rename from libraries/script-engine/src/ArrayBufferViewClass.cpp rename to libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp index cf776ed8340..f58cbeb9433 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.cpp +++ b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp @@ -10,13 +10,15 @@ // #include "ArrayBufferViewClass.h" +#include "ScriptEngineQtScript.h" Q_DECLARE_METATYPE(QByteArray*) -ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) : -QObject(scriptEngine), -QScriptClass(scriptEngine), -_scriptEngine(scriptEngine) { +ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngineQtScript* scriptEngine) : + QObject(scriptEngine), + QScriptClass(scriptEngine), + _scriptEngine(scriptEngine) +{ // Save string handles for quick lookup _bufferName = engine()->toStringHandle(BUFFER_PROPERTY_NAME.toLatin1()); _byteOffsetName = engine()->toStringHandle(BYTE_OFFSET_PROPERTY_NAME.toLatin1()); diff --git a/libraries/script-engine/src/ArrayBufferViewClass.h b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h similarity index 83% rename from libraries/script-engine/src/ArrayBufferViewClass.h rename to libraries/script-engine/src/qtscript/ArrayBufferViewClass.h index 173f6073342..e5c40d8e440 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.h +++ b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h @@ -15,7 +15,7 @@ #ifndef hifi_ArrayBufferViewClass_h #define hifi_ArrayBufferViewClass_h -#include +#include #include #include #include @@ -23,19 +23,19 @@ #include #include -#include "ScriptEngine.h" +class ScriptEngineQtScript; static const QString BUFFER_PROPERTY_NAME = "buffer"; static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset"; static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; -/// The base class containing common code for ArrayBuffer views +/// [QtScript] The base class containing common code for ArrayBuffer views class ArrayBufferViewClass : public QObject, public QScriptClass { Q_OBJECT public: - ArrayBufferViewClass(ScriptEngine* scriptEngine); + ArrayBufferViewClass(ScriptEngineQtScript* scriptEngine); - ScriptEngine* getScriptEngine() { return _scriptEngine; } + ScriptEngineQtScript* getScriptEngine() { return _scriptEngine; } virtual QueryFlags queryProperty(const QScriptValue& object, const QScriptString& name, @@ -50,7 +50,7 @@ class ArrayBufferViewClass : public QObject, public QScriptClass { QScriptString _byteOffsetName; QScriptString _byteLengthName; - ScriptEngine* _scriptEngine; + ScriptEngineQtScript* _scriptEngine; }; #endif // hifi_ArrayBufferViewClass_h diff --git a/libraries/script-engine/src/DataViewClass.cpp b/libraries/script-engine/src/qtscript/DataViewClass.cpp similarity index 97% rename from libraries/script-engine/src/DataViewClass.cpp rename to libraries/script-engine/src/qtscript/DataViewClass.cpp index 3cc5443973a..7d466ea42c5 100644 --- a/libraries/script-engine/src/DataViewClass.cpp +++ b/libraries/script-engine/src/qtscript/DataViewClass.cpp @@ -17,7 +17,7 @@ Q_DECLARE_METATYPE(QByteArray*) static const QString DATA_VIEW_NAME = "DataView"; -DataViewClass::DataViewClass(ScriptEngine* scriptEngine) : ArrayBufferViewClass(scriptEngine) { +DataViewClass::DataViewClass(ScriptEngineQtScript* scriptEngine) : ArrayBufferViewClass(scriptEngine) { QScriptValue global = engine()->globalObject(); // Save string handles for quick lookup diff --git a/libraries/script-engine/src/DataViewClass.h b/libraries/script-engine/src/qtscript/DataViewClass.h similarity index 78% rename from libraries/script-engine/src/DataViewClass.h rename to libraries/script-engine/src/qtscript/DataViewClass.h index 179aad87d17..8b587bbd06a 100644 --- a/libraries/script-engine/src/DataViewClass.h +++ b/libraries/script-engine/src/qtscript/DataViewClass.h @@ -17,11 +17,11 @@ #include "ArrayBufferViewClass.h" -/// Implements the DataView scripting class +/// [QtScript] Implements the DataView scripting class class DataViewClass : public ArrayBufferViewClass { Q_OBJECT public: - DataViewClass(ScriptEngine* scriptEngine); + DataViewClass(ScriptEngineQtScript* scriptEngine); QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLength); QString name() const override; diff --git a/libraries/script-engine/src/DataViewPrototype.cpp b/libraries/script-engine/src/qtscript/DataViewPrototype.cpp similarity index 98% rename from libraries/script-engine/src/DataViewPrototype.cpp rename to libraries/script-engine/src/qtscript/DataViewPrototype.cpp index ef757a5cb4d..7770e3bce50 100644 --- a/libraries/script-engine/src/DataViewPrototype.cpp +++ b/libraries/script-engine/src/qtscript/DataViewPrototype.cpp @@ -12,10 +12,15 @@ #include "DataViewPrototype.h" #include +#include +#include +#include #include -#include "DataViewClass.h" +#include + +#include "ArrayBufferViewClass.h" Q_DECLARE_METATYPE(QByteArray*) diff --git a/libraries/script-engine/src/DataViewPrototype.h b/libraries/script-engine/src/qtscript/DataViewPrototype.h similarity index 93% rename from libraries/script-engine/src/DataViewPrototype.h rename to libraries/script-engine/src/qtscript/DataViewPrototype.h index b4d9462e8f6..3f395516c13 100644 --- a/libraries/script-engine/src/DataViewPrototype.h +++ b/libraries/script-engine/src/qtscript/DataViewPrototype.h @@ -18,7 +18,7 @@ #include #include -/// The javascript functions associated with a DataView instance prototype +/// [QtScript] The javascript functions associated with a DataView instance prototype class DataViewPrototype : public QObject, public QScriptable { Q_OBJECT public: diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp new file mode 100644 index 00000000000..72f6ea41b13 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp @@ -0,0 +1,92 @@ +// +// ScriptContextQtWrapper.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptContextQtWrapper.h" + +#include + +#include "ScriptEngineQtScript.h" +#include "ScriptValueQtWrapper.h" + +ScriptContextQtWrapper* ScriptContextQtWrapper::unwrap(ScriptContext* val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val); +} + +int ScriptContextQtWrapper::argumentCount() const { + return _context->argumentCount(); +} + +ScriptValue ScriptContextQtWrapper::argument(int index) const { + QScriptValue result = _context->argument(index); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +QStringList ScriptContextQtWrapper::backtrace() const { + return _context->backtrace(); +} + +ScriptValue ScriptContextQtWrapper::callee() const { + QScriptValue result = _context->callee(); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptEnginePointer ScriptContextQtWrapper::engine() const { + return _engine->shared_from_this(); +} + +ScriptFunctionContextPointer ScriptContextQtWrapper::functionContext() const { + return std::make_shared(_context); +} + +ScriptContextPointer ScriptContextQtWrapper::parentContext() const { + QScriptContext* result = _context->parentContext(); + return result ? std::make_shared(_engine, result) : ScriptContextPointer(); +} + +ScriptValue ScriptContextQtWrapper::thisObject() const { + QScriptValue result = _context->thisObject(); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptContextQtWrapper::throwError(const QString& text) { + QScriptValue result = _context->throwError(text); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptContextQtWrapper::throwValue(const ScriptValue& value) { + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value); + if (!unwrapped) { + return _engine->undefinedValue(); + } + QScriptValue result = _context->throwValue(unwrapped->toQtValue()); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + + +QString ScriptFunctionContextQtWrapper::fileName() const { + return _value.fileName(); +} + +QString ScriptFunctionContextQtWrapper::functionName() const { + return _value.functionName(); +} + +ScriptFunctionContext::FunctionType ScriptFunctionContextQtWrapper::functionType() const { + return static_cast(_value.functionType()); +} + +int ScriptFunctionContextQtWrapper::lineNumber() const { + return _value.lineNumber(); +} diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h new file mode 100644 index 00000000000..bbf9f0465cf --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h @@ -0,0 +1,67 @@ +// +// ScriptContextQtWrapper.h +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptContextQtWrapper_h +#define hifi_ScriptContextQtWrapper_h + +#include +#include + +#include "../ScriptContext.h" +#include "../ScriptValue.h" + +class QScriptContext; +class ScriptEngineQtScript; + +/// [QtScript] Implements ScriptContext for QtScript and translates calls for QScriptContextInfo +class ScriptContextQtWrapper final : public ScriptContext { +public: // construction + inline ScriptContextQtWrapper(ScriptEngineQtScript* engine, QScriptContext* context) : _context(context) , _engine(engine) {} + static ScriptContextQtWrapper* unwrap(ScriptContext* val); + inline QScriptContext* toQtValue() const { return _context; } + +public: // ScriptContext implementation + virtual int argumentCount() const override; + virtual ScriptValue argument(int index) const override; + virtual QStringList backtrace() const override; + virtual ScriptValue callee() const override; + virtual ScriptEnginePointer engine() const override; + virtual ScriptFunctionContextPointer functionContext() const override; + virtual ScriptContextPointer parentContext() const override; + virtual ScriptValue thisObject() const override; + virtual ScriptValue throwError(const QString& text) override; + virtual ScriptValue throwValue(const ScriptValue& value) override; + +private: // storage + QScriptContext* _context; + ScriptEngineQtScript* _engine; +}; + +class ScriptFunctionContextQtWrapper final : public ScriptFunctionContext { +public: // construction + inline ScriptFunctionContextQtWrapper(QScriptContext* context) : _value(context) {} + +public: // ScriptFunctionContext implementation + virtual QString fileName() const override; + virtual QString functionName() const override; + virtual FunctionType functionType() const override; + virtual int lineNumber() const override; + +private: // storage + QScriptContextInfo _value; +}; + +#endif // hifi_ScriptContextQtWrapper_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp new file mode 100644 index 00000000000..7b4a39f9428 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp @@ -0,0 +1,919 @@ +// +// ScriptEngineQtScript.cpp +// libraries/script-engine/src/qtscript +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngineQtScript.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include "../ScriptEngineLogging.h" +#include "../ScriptProgram.h" +#include "../ScriptValue.h" + +#include "ScriptContextQtWrapper.h" +#include "ScriptObjectQtProxy.h" +#include "ScriptProgramQtWrapper.h" +#include "ScriptValueQtWrapper.h" + +static const int MAX_DEBUG_VALUE_LENGTH { 80 }; + +bool ScriptEngineQtScript::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { + const QThread* currentThread = QThread::currentThread(); + if (currentThread == thread) { + return true; + } + qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") + .arg(method) + .arg(thread ? thread->objectName() : "(!thread)") + .arg(QThread::currentThread()->objectName()); + qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; + Q_ASSERT(false); + return false; +} + +// engine-aware JS Error copier and factory +QScriptValue ScriptEngineQtScript::makeError(const QScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return QScriptEngine::nullValue(); + } + auto other = _other; + if (other.isString()) { + other = QScriptEngine::newObject(); + other.setProperty("message", _other.toString()); + } + auto proto = QScriptEngine::globalObject().property(type); + if (!proto.isFunction()) { + proto = QScriptEngine::globalObject().property(other.prototype().property("constructor").property("name").toString()); + } + if (!proto.isFunction()) { +#ifdef DEBUG_JS_EXCEPTIONS + qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead"; +#endif + proto = QScriptEngine::globalObject().property("Error"); + } + if (other.engine() != this) { + // JS Objects are parented to a specific script engine instance + // -- this effectively ~clones it locally by routing through a QVariant and back + other = QScriptEngine::toScriptValue(other.toVariant()); + } + // ~ var err = new Error(other.message) + auto err = proto.construct(QScriptValueList({ other.property("message") })); + + // transfer over any existing properties + QScriptValueIterator it(other); + while (it.hasNext()) { + it.next(); + err.setProperty(it.name(), it.value()); + } + return err; +} + +ScriptValue ScriptEngineQtScript::makeError(const ScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(_other); + QScriptValue other; + if (_other.isString()) { + other = QScriptEngine::newObject(); + other.setProperty("message", _other.toString()); + } else if (unwrapped) { + other = unwrapped->toQtValue(); + } else { + other = QScriptEngine::newVariant(_other.toVariant()); + } + QScriptValue result = makeError(other, type); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +// check syntax and when there are issues returns an actual "SyntaxError" with the details +ScriptValue ScriptEngineQtScript::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + const auto syntaxCheck = checkSyntax(sourceCode); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + auto err = QScriptEngine::globalObject().property("SyntaxError").construct(QScriptValueList({ syntaxCheck.errorMessage() })); + err.setProperty("fileName", fileName); + err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); + err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); + err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); + { + const auto error = syntaxCheck.errorMessage(); + const auto line = QString::number(syntaxCheck.errorLineNumber()); + const auto column = QString::number(syntaxCheck.errorColumnNumber()); + // for compatibility with legacy reporting + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); + err.setProperty("formatted", message); + } + return ScriptValue(new ScriptValueQtWrapper(this, std::move(err))); + } + return undefinedValue(); +} + +// this pulls from the best available information to create a detailed snapshot of the current exception +ScriptValue ScriptEngineQtScript::cloneUncaughtException(const QString& extraDetail) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + if (!hasUncaughtException()) { + return nullValue(); + } + auto exception = uncaughtException(); + // ensure the error object is engine-local + auto err = makeError(exception); + + // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number + // on its own is often useless/wrong if arbitrarily married to a filename. + // when the error object already has this info, it seems to be the most reliable + auto fileName = exception.property("fileName").toString(); + auto lineNumber = exception.property("lineNumber").toInt32(); + + // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace + auto backtrace = uncaughtExceptionBacktrace(); + if (backtrace.isEmpty()) { + // fallback to the error object + backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); + } + // the ad hoc "detail" property can be used now to embed additional clues + auto detail = exception.property("detail").toString(); + if (detail.isEmpty()) { + detail = extraDetail; + } else if (!extraDetail.isEmpty()) { + detail += "(" + extraDetail + ")"; + } + if (lineNumber <= 0) { + lineNumber = uncaughtExceptionLineNumber(); + } + if (fileName.isEmpty()) { + // climb the stack frames looking for something useful to display + for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { + QScriptContextInfo info{ c }; + if (!info.fileName().isEmpty()) { + // take fileName:lineNumber as a pair + fileName = info.fileName(); + lineNumber = info.lineNumber(); + if (backtrace.isEmpty()) { + backtrace = c->backtrace(); + } + break; + } + } + } + err.setProperty("fileName", fileName); + err.setProperty("lineNumber", lineNumber); + err.setProperty("detail", detail); + err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); + +#ifdef DEBUG_JS_EXCEPTIONS + err.setProperty("_fileName", exception.property("fileName").toString()); + err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); + err.setProperty("_lineNumber", uncaughtExceptionLineNumber()); +#endif + return err; +} + +bool ScriptEngineQtScript::raiseException(const QScriptValue& exception) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + if (QScriptEngine::currentContext()) { + // we have an active context / JS stack frame so throw the exception per usual + QScriptEngine::currentContext()->throwValue(makeError(exception)); + return true; + } else if (_scriptManager) { + // we are within a pure C++ stack frame (ie: being called directly by other C++ code) + // in this case no context information is available so just emit the exception for reporting + QScriptValue thrown = makeError(exception); + emit _scriptManager->unhandledException(ScriptValue(new ScriptValueQtWrapper(this, std::move(thrown)))); + } + return false; +} + +bool ScriptEngineQtScript::maybeEmitUncaughtException(const QString& debugHint) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + if (!isEvaluating() && hasUncaughtException() && _scriptManager) { + emit _scriptManager->unhandledException(cloneUncaughtException(debugHint)); + clearExceptions(); + return true; + } + return false; +} + +// Lambda +QScriptValue ScriptEngineQtScript::newLambdaFunction(std::function operation, + const QScriptValue& data, + const QScriptEngine::ValueOwnership& ownership) { + auto lambda = new Lambda(this, operation, data); + auto object = QScriptEngine::newQObject(lambda, ownership); + auto call = object.property("call"); + call.setPrototype(object); // context->callee().prototype() === Lambda QObject + call.setData(data); // context->callee().data() will === data param + return call; +} +QString Lambda::toString() const { + return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString()); +} + +Lambda::~Lambda() { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "~Lambda" + << "this" << this; +#endif +} + +Lambda::Lambda(ScriptEngineQtScript* engine, + std::function operation, + QScriptValue data) : + engine(engine), + operation(operation), data(data) { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "Lambda" << data.toString(); +#endif +} +QScriptValue Lambda::call() { + if (!engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return static_cast(engine)->nullValue(); + } + return operation(static_cast(engine)->currentContext(), engine); +} + +#ifdef DEBUG_JS +void ScriptEngineQtScript::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return; + } + if (!header.isEmpty()) { + qCDebug(shared) << header; + } + if (!object.isObject()) { + qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); + return; + } + QScriptValueIterator it(object); + while (it.hasNext()) { + it.next(); + qCDebug(shared) << it.name() << ":" << it.value().toString(); + } + if (!footer.isEmpty()) { + qCDebug(shared) << footer; + } +} +#endif + +ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) : + QScriptEngine(), + _scriptManager(scriptManager), + _arrayBufferClass(new ArrayBufferClass(this)) +{ + registerSystemTypes(); + + if (_scriptManager) { + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { + if (hasUncaughtException()) { + // the engine's uncaughtException() seems to produce much better stack traces here + emit _scriptManager->unhandledException(cloneUncaughtException("signalHandlerException")); + clearExceptions(); + } else { + // ... but may not always be available -- so if needed we fallback to the passed exception + QScriptValue thrown = makeError(exception); + emit _scriptManager->unhandledException(ScriptValue(new ScriptValueQtWrapper(this, std::move(thrown)))); + } + }, Qt::DirectConnection); + moveToThread(scriptManager->thread()); + } + + QScriptValue null = QScriptEngine::nullValue(); + _nullValue = ScriptValue(new ScriptValueQtWrapper(this, std::move(null))); + + QScriptValue undefined = QScriptEngine::undefinedValue(); + _undefinedValue = ScriptValue(new ScriptValueQtWrapper(this, std::move(undefined))); + + QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); +} + +void ScriptEngineQtScript::registerEnum(const QString& enumName, QMetaEnum newEnum) { + if (!newEnum.isValid()) { + qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; + return; + } + + for (int i = 0; i < newEnum.keyCount(); i++) { + const char* keyName = newEnum.key(i); + QString fullName = enumName + "." + keyName; + registerValue(fullName, newEnum.keyToValue(keyName)); + } +} + +void ScriptEngineQtScript::registerValue(const QString& valueName, QScriptValue value) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + QMetaObject::invokeMethod(this, "registerValue", + Q_ARG(const QString&, valueName), + Q_ARG(QScriptValue, value)); + return; + } + + QStringList pathToValue = valueName.split("."); + int partsToGo = pathToValue.length(); + QScriptValue partObject = QScriptEngine::globalObject(); + + for (const auto& pathPart : pathToValue) { + partsToGo--; + if (!partObject.property(pathPart).isValid()) { + if (partsToGo > 0) { + //QObject *object = new QObject; + QScriptValue partValue = QScriptEngine::newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); + partObject.setProperty(pathPart, partValue); + } else { + partObject.setProperty(pathPart, value); + } + } + partObject = partObject.property(pathPart); + } +} + +void ScriptEngineQtScript::registerGlobalObject(const QString& name, QObject* object) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + if (!QScriptEngine::globalObject().property(name).isValid()) { + if (object) { + QScriptValue value = ScriptObjectQtProxy::newQObject(this, object, ScriptEngine::QtOwnership); + QScriptEngine::globalObject().setProperty(name, value); + } else { + QScriptEngine::globalObject().setProperty(name, QScriptValue()); + } + } +} + +void ScriptEngineQtScript::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments); + QScriptEngine::globalObject().setProperty(name, scriptFun); +} + +void ScriptEngineQtScript::registerFunction(const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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(ScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + auto scriptFun = newFunction(functionSignature, numArguments); + globalObject().setProperty(name, scriptFun); +} + +void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; +#endif + + QScriptValue object = QScriptEngine::globalObject().property(parent); + if (object.isValid()) { + QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } +} + +void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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(ScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; +#endif + + auto object = globalObject().property(parent); + if (object.isValid()) { + auto scriptFun = newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } +} + +void ScriptEngineQtScript::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, const QString& parent) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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), + Q_ARG(QScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; +#endif + + QScriptValue setterFunction = QScriptEngine::newFunction(setter, 1); + QScriptValue getterFunction = QScriptEngine::newFunction(getter); + + if (!parent.isNull() && !parent.isEmpty()) { + QScriptValue object = QScriptEngine::globalObject().property(parent); + if (object.isValid()) { + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } + } else { + QScriptEngine::globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); + QScriptEngine::globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } +} + +void ScriptEngineQtScript::registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, + ScriptEngine::FunctionSignature setter, const QString& parent) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::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(ScriptEngine::FunctionSignature, getter), + Q_ARG(ScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; +#endif + + auto setterFunction = newFunction(setter, 1); + auto getterFunction = newFunction(getter); + + if (!parent.isNull() && !parent.isEmpty()) { + auto object = globalObject().property(parent); + if (object.isValid()) { + object.setProperty(name, setterFunction, ScriptValue::PropertySetter); + object.setProperty(name, getterFunction, ScriptValue::PropertyGetter); + } + } else { + globalObject().setProperty(name, setterFunction, ScriptValue::PropertySetter); + globalObject().setProperty(name, getterFunction, ScriptValue::PropertyGetter); + } +} + +ScriptValue ScriptEngineQtScript::evaluateInClosure(const ScriptValue& _closure, + const ScriptProgramPointer& _program) { + PROFILE_RANGE(script, "evaluateInClosure"); + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptProgramQtWrapper* unwrappedProgram = ScriptProgramQtWrapper::unwrap(_program); + if (unwrappedProgram == nullptr) { + return nullValue(); + } + const QScriptProgram& program = unwrappedProgram->toQtValue(); + + const auto fileName = program.fileName(); + const auto shortName = QUrl(fileName).fileName(); + + ScriptValueQtWrapper* unwrappedClosure = ScriptValueQtWrapper::unwrap(_closure); + if (unwrappedClosure == nullptr) { + return nullValue(); + } + const QScriptValue& closure = unwrappedClosure->toQtValue(); + + QScriptValue oldGlobal; + auto global = closure.property("global"); + if (global.isObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting global = closure.global" << shortName; +#endif + oldGlobal = QScriptEngine::globalObject(); + setGlobalObject(global); + } + + auto context = pushContext(); + + auto thiz = closure.property("this"); + if (thiz.isObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting this = closure.this" << shortName; +#endif + context->setThisObject(thiz); + } + + context->pushScope(closure); +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + ScriptValue result; + { + auto qResult = QScriptEngine::evaluate(program); + + if (hasUncaughtException()) { + auto err = cloneUncaughtException(__FUNCTION__); +#ifdef DEBUG_JS_EXCEPTIONS + qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); + err.setProperty("_result", result); +#endif + result = err; + } else { + result = ScriptValue(new ScriptValueQtWrapper(this, std::move(qResult))); + } + } +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + popContext(); + + if (oldGlobal.isValid()) { +#ifdef DEBUG_JS + qCDebug(shared) << " restoring global" << shortName; +#endif + setGlobalObject(oldGlobal); + } + + return result; +} + +ScriptValue ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName) { + if (_scriptManager && _scriptManager->isStopped()) { + return undefinedValue(); // bail early + } + + if (QThread::currentThread() != QScriptEngine::thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "sourceCode:" << sourceCode << " fileName:" << fileName; +#endif + BLOCKING_INVOKE_METHOD(this, "evaluate", + Q_RETURN_ARG(ScriptValue, result), + Q_ARG(const QString&, sourceCode), + Q_ARG(const QString&, fileName)); + return result; + } + + // Check syntax + auto syntaxError = lintScript(sourceCode, fileName); + if (syntaxError.isError()) { + if (!isEvaluating()) { + syntaxError.setProperty("detail", "evaluate"); + } + raiseException(syntaxError); + maybeEmitUncaughtException("lint"); + return syntaxError; + } + QScriptProgram program { sourceCode, fileName, 1 }; + if (program.isNull()) { + // can this happen? + auto err = makeError(newValue("could not create QScriptProgram for " + fileName)); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + QScriptValue result = QScriptEngine::evaluate(program); + maybeEmitUncaughtException("evaluate"); + + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +Q_INVOKABLE ScriptValue ScriptEngineQtScript::evaluate(const ScriptProgramPointer& program) { + if (_scriptManager && _scriptManager->isStopped()) { + return undefinedValue(); // bail early + } + + if (QThread::currentThread() != QScriptEngine::thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "sourceCode:" << sourceCode << " fileName:" << fileName; +#endif + BLOCKING_INVOKE_METHOD(this, "evaluate", + Q_RETURN_ARG(ScriptValue, result), + Q_ARG(const ScriptProgramPointer&, program)); + return result; + } + + ScriptProgramQtWrapper* unwrapped = ScriptProgramQtWrapper::unwrap(program); + if (!unwrapped) { + auto err = makeError(newValue("could not unwrap program")); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + const QScriptProgram& qProgram = unwrapped->toQtValue(); + if (qProgram.isNull()) { + // can this happen? + auto err = makeError(newValue("requested program is empty")); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + QScriptValue result = QScriptEngine::evaluate(qProgram); + maybeEmitUncaughtException("evaluate"); + + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + + +void ScriptEngineQtScript::updateMemoryCost(const qint64& deltaSize) { + if (deltaSize > 0) { + // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + reportAdditionalMemoryCost(deltaSize); +#endif + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ScriptEngine implementation + +ScriptValue ScriptEngineQtScript::globalObject() const { + QScriptValue global = QScriptEngine::globalObject(); // can't cache the value as it may change + return ScriptValue(new ScriptValueQtWrapper(const_cast(this), std::move(global))); +} + +ScriptManager* ScriptEngineQtScript::manager() const { + return _scriptManager; +} + +ScriptValue ScriptEngineQtScript::newArray(uint length) { + QScriptValue result = QScriptEngine::newArray(length); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newArrayBuffer(const QByteArray& message) { + QScriptValue data = QScriptEngine::newVariant(QVariant::fromValue(message)); + QScriptValue ctor = QScriptEngine::globalObject().property("ArrayBuffer"); + auto array = qscriptvalue_cast(ctor.data()); + if (!array) { + return undefinedValue(); + } + QScriptValue result = QScriptEngine::newObject(array, data); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newObject() { + QScriptValue result = QScriptEngine::newObject(); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) { + QScriptProgram result(sourceCode, fileName); + return std::make_shared(this, result); +} + +ScriptValue ScriptEngineQtScript::newQObject(QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + QScriptValue result = ScriptObjectQtProxy::newQObject(this, object, ownership, options); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(bool value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(int value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(uint value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(double value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(const QString& value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(const QLatin1String& value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newValue(const char* value) { + QScriptValue result(this, value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::newVariant(const QVariant& value) { + QScriptValue result = castVariantToValue(value); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineQtScript::nullValue() { + return _nullValue; +} + +ScriptValue ScriptEngineQtScript::undefinedValue() { + return _undefinedValue; +} + +void ScriptEngineQtScript::abortEvaluation() { + QScriptEngine::abortEvaluation(); +} + +void ScriptEngineQtScript::clearExceptions() { + QScriptEngine::clearExceptions(); +} + +ScriptContext* ScriptEngineQtScript::currentContext() const { + QScriptContext* localCtx = QScriptEngine::currentContext(); + if (!localCtx) { + return nullptr; + } + if (!_currContext || _currContext->toQtValue() != localCtx) { + _currContext = std::make_shared(const_cast(this), localCtx); + } + return _currContext.get(); +} + +bool ScriptEngineQtScript::hasUncaughtException() const { + return QScriptEngine::hasUncaughtException(); +} + +bool ScriptEngineQtScript::isEvaluating() const { + return QScriptEngine::isEvaluating(); +} + +ScriptValue ScriptEngineQtScript::newFunction(ScriptEngine::FunctionSignature fun, int length) { + auto innerFunc = [](QScriptContext* _context, QScriptEngine* _engine) -> QScriptValue { + auto callee = _context->callee(); + QVariant funAddr = callee.property("_func").toVariant(); + ScriptEngine::FunctionSignature fun = reinterpret_cast(funAddr.toULongLong()); + ScriptEngineQtScript* engine = static_cast(_engine); + ScriptContextQtWrapper context(engine, _context); + ScriptValue result = fun(&context, engine); + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(result); + return unwrapped ? unwrapped->toQtValue() : QScriptValue(); + }; + + QScriptValue result = QScriptEngine::newFunction(innerFunc, length); + auto funAddr = QScriptEngine::newVariant(QVariant(reinterpret_cast(fun))); + result.setProperty("_func", funAddr, QScriptValue::PropertyFlags(QScriptValue::ReadOnly + QScriptValue::Undeletable + QScriptValue::SkipInEnumeration)); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); +} + +void ScriptEngineQtScript::setObjectName(const QString& name) { + QScriptEngine::setObjectName(name); +} + +bool ScriptEngineQtScript::setProperty(const char* name, const QVariant& value) { + return QScriptEngine::setProperty(name, value); +} + +void ScriptEngineQtScript::setProcessEventsInterval(int interval) { + QScriptEngine::setProcessEventsInterval(interval); +} + +QThread* ScriptEngineQtScript::thread() const { + return QScriptEngine::thread(); +} + +void ScriptEngineQtScript::setThread(QThread* thread) { + moveToThread(thread); +} + +ScriptValue ScriptEngineQtScript::uncaughtException() const { + QScriptValue result = QScriptEngine::uncaughtException(); + return ScriptValue(new ScriptValueQtWrapper(const_cast(this), std::move(result))); +} + +QStringList ScriptEngineQtScript::uncaughtExceptionBacktrace() const { + return QScriptEngine::uncaughtExceptionBacktrace(); +} + +int ScriptEngineQtScript::uncaughtExceptionLineNumber() const { + return QScriptEngine::uncaughtExceptionLineNumber(); +} + +bool ScriptEngineQtScript::raiseException(const ScriptValue& exception) { + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(exception); + QScriptValue qException = unwrapped ? unwrapped->toQtValue() : QScriptEngine::newVariant(exception.toVariant()); + return raiseException(qException); +} + +ScriptValue ScriptEngineQtScript::create(int type, const void* ptr) { + QVariant variant(type, ptr); + QScriptValue scriptValue = castVariantToValue(variant); + return ScriptValue(new ScriptValueQtWrapper(this, std::move(scriptValue))); +} + +QVariant ScriptEngineQtScript::convert(const ScriptValue& value, int typeId) { + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value); + if (unwrapped == nullptr) { + return QVariant(); + } + + QVariant var; + if (!castValueToVariant(unwrapped->toQtValue(), var, typeId)) { + return QVariant(); + } + + int destType = var.userType(); + if (destType != typeId) { + var.convert(typeId); // if conversion fails then var is set to QVariant() + } + + return var; +} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h new file mode 100644 index 00000000000..dcc87207c80 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h @@ -0,0 +1,215 @@ +// +// ScriptEngineQtScript.h +// libraries/script-engine/src/qtscript +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptEngineQtScript_h +#define hifi_ScriptEngineQtScript_h + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../ScriptEngine.h" +#include "../ScriptManager.h" + +#include "ArrayBufferClass.h" + +class ScriptContextQtWrapper; +class ScriptEngineQtScript; +class ScriptManager; +class ScriptObjectQtProxy; +using ScriptContextQtPointer = std::shared_ptr; + +Q_DECLARE_METATYPE(ScriptEngine::FunctionSignature) + +/// [QtScript] Implements ScriptEngine for QtScript and translates calls for QScriptEngine +class ScriptEngineQtScript final : public QScriptEngine, + public ScriptEngine, + public std::enable_shared_from_this { + Q_OBJECT + +public: // construction + ScriptEngineQtScript(ScriptManager* scriptManager = nullptr); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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 + +public: // ScriptEngine implementation + virtual void abortEvaluation() override; + virtual void clearExceptions() override; + virtual ScriptValue cloneUncaughtException(const QString& detail = QString()) override; + virtual ScriptContext* currentContext() const override; + Q_INVOKABLE virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) override; + Q_INVOKABLE virtual ScriptValue evaluate(const ScriptProgramPointer& program) override; + Q_INVOKABLE virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) override; + virtual ScriptValue globalObject() const override; + virtual bool hasUncaughtException() const override; + virtual bool isEvaluating() const override; + virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) override; + virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override; + virtual ScriptManager* manager() const override; + + // if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it + virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()) override; + + virtual ScriptValue newArray(uint length = 0) override; + virtual ScriptValue newArrayBuffer(const QByteArray& message) override; + virtual ScriptValue newFunction(ScriptEngine::FunctionSignature fun, int length = 0) override; + virtual ScriptValue newObject() override; + virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) override; + virtual ScriptValue newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, + const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()) override; + virtual ScriptValue newValue(bool value) override; + virtual ScriptValue newValue(int value) override; + virtual ScriptValue newValue(uint value) override; + virtual ScriptValue newValue(double value) override; + virtual ScriptValue newValue(const QString& value) override; + virtual ScriptValue newValue(const QLatin1String& value) override; + virtual ScriptValue newValue(const char* value) override; + virtual ScriptValue newVariant(const QVariant& value) override; + virtual ScriptValue nullValue() override; + virtual bool raiseException(const ScriptValue& exception) override; + Q_INVOKABLE virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) override; + Q_INVOKABLE virtual void registerFunction(const QString& name, + ScriptEngine::FunctionSignature fun, + int numArguments = -1) override; + Q_INVOKABLE virtual void registerFunction(const QString& parent, + const QString& name, + ScriptEngine::FunctionSignature fun, + int numArguments = -1) override; + Q_INVOKABLE virtual void registerGetterSetter(const QString& name, + ScriptEngine::FunctionSignature getter, + ScriptEngine::FunctionSignature setter, + const QString& parent = QString("")) override; + Q_INVOKABLE virtual void registerGlobalObject(const QString& name, QObject* object) override; + virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) override; + virtual void setObjectName(const QString& name) override; + virtual bool setProperty(const char* name, const QVariant& value) override; + virtual void setProcessEventsInterval(int interval) override; + virtual QThread* thread() const override; + virtual void setThread(QThread* thread) override; + virtual ScriptValue undefinedValue() override; + virtual ScriptValue uncaughtException() const override; + virtual QStringList uncaughtExceptionBacktrace() const override; + virtual int uncaughtExceptionLineNumber() const override; + virtual void updateMemoryCost(const qint64& deltaSize) override; + virtual void requestCollectGarbage() override { collectGarbage(); } + + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } + +protected: // brought over from BaseScriptEngine + QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); + + // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it. + // note: this is used in cases where C++ code might call into JS API methods directly + bool raiseException(const QScriptValue& exception); + + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + static bool IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method); + +public: // public non-interface methods for other QtScript-specific classes to use + /// registers a global getter/setter + Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); + + /// register a global function + Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); + + /// 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); + + /// registers a global object by name + Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); + + // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety + inline ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + +public: // not for public use, but I don't like how Qt strings this along with private friend functions + virtual ScriptValue create(int type, const void* ptr) override; + virtual QVariant convert(const ScriptValue& value, int typeId) override; + virtual void registerCustomType(int type, ScriptEngine::MarshalFunction marshalFunc, + ScriptEngine::DemarshalFunction demarshalFunc) override; + bool castValueToVariant(const QScriptValue& val, QVariant& dest, int destTypeId); + QScriptValue castVariantToValue(const QVariant& val); + static QString valueType(const QScriptValue& val); + + using ObjectWrapperMap = QMap>; + mutable QMutex _qobjectWrapperMapProtect; + ObjectWrapperMap _qobjectWrapperMap; + +protected: + // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues + // even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction` + // anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming + // permanent more easily promoted into regular static newFunction scenarios. + QScriptValue newLambdaFunction(std::function operation, + const QScriptValue& data = QScriptValue(), + const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership); + + void registerSystemTypes(); + +protected: + struct CustomMarshal { + ScriptEngine::MarshalFunction marshalFunc; + ScriptEngine::DemarshalFunction demarshalFunc; + }; + using CustomMarshalMap = QHash; + using CustomPrototypeMap = QHash; + + QPointer _scriptManager; + + mutable QMutex _customTypeProtect; + CustomMarshalMap _customTypes; + CustomPrototypeMap _customPrototypes; + ScriptValue _nullValue; + ScriptValue _undefinedValue; + mutable ScriptContextQtPointer _currContext; + + ArrayBufferClass* _arrayBufferClass; +}; + +// Lambda helps create callable QScriptValues out of std::functions: +// (just meant for use from within the script engine itself) +class Lambda : public QObject { + Q_OBJECT +public: + Lambda(ScriptEngineQtScript* engine, + std::function operation, + QScriptValue data); + ~Lambda(); +public slots: + QScriptValue call(); + QString toString() const; + +private: + ScriptEngineQtScript* engine; + std::function operation; + QScriptValue data; +}; + +#endif // hifi_ScriptEngineQtScript_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp b/libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp new file mode 100644 index 00000000000..7b799d168fd --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp @@ -0,0 +1,447 @@ +// +// ScriptEngineQtScript_cast.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson 12/9/2021 +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngineQtScript.h" + +#include +#include +#include +#include + +#include "../ScriptEngineCast.h" +#include "../ScriptValueIterator.h" + +#include "ScriptObjectQtProxy.h" +#include "ScriptValueQtWrapper.h" + +void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) { + ScriptValueQtWrapper* unwrappedPrototype = ScriptValueQtWrapper::unwrap(prototype); + if (unwrappedPrototype) { + const QScriptValue& scriptPrototype = unwrappedPrototype->toQtValue(); + QMutexLocker guard(&_customTypeProtect); + _customPrototypes.insert(metaTypeId, scriptPrototype); + } +} + +void ScriptEngineQtScript::registerCustomType(int type, + ScriptEngine::MarshalFunction marshalFunc, + ScriptEngine::DemarshalFunction demarshalFunc) +{ + QMutexLocker guard(&_customTypeProtect); + + // storing it in a map for our own benefit + CustomMarshal& customType = _customTypes.insert(type, CustomMarshal()).value(); + customType.demarshalFunc = demarshalFunc; + customType.marshalFunc = marshalFunc; +} + +Q_DECLARE_METATYPE(ScriptValue); + +static QScriptValue ScriptValueToQScriptValue(QScriptEngine* engine, const ScriptValue& src) { + return ScriptValueQtWrapper::fullUnwrap(static_cast(engine), src); +} + +static void ScriptValueFromQScriptValue(const QScriptValue& src, ScriptValue& dest) { + ScriptEngineQtScript* engine = static_cast(src.engine()); + dest = ScriptValue(new ScriptValueQtWrapper(engine, src)); +} + +static ScriptValue StringListToScriptValue(ScriptEngine* engine, const QStringList& src) { + int len = src.length(); + ScriptValue dest = engine->newArray(len); + for (int idx = 0; idx < len; ++idx) { + dest.setProperty(idx, engine->newValue(src.at(idx))); + } + return dest; +} + +static bool StringListFromScriptValue(const ScriptValue& src, QStringList& dest) { + if(!src.isArray()) return false; + int len = src.property("length").toInteger(); + dest.clear(); + for (int idx = 0; idx < len; ++idx) { + dest.append(src.property(idx).toString()); + } + return true; +} + +static ScriptValue VariantListToScriptValue(ScriptEngine* engine, const QVariantList& src) { + int len = src.length(); + ScriptValue dest = engine->newArray(len); + for (int idx = 0; idx < len; ++idx) { + dest.setProperty(idx, engine->newVariant(src.at(idx))); + } + return dest; +} + +static bool VariantListFromScriptValue(const ScriptValue& src, QVariantList& dest) { + if(!src.isArray()) return false; + int len = src.property("length").toInteger(); + dest.clear(); + for (int idx = 0; idx < len; ++idx) { + dest.append(src.property(idx).toVariant()); + } + return true; +} + +static ScriptValue VariantMapToScriptValue(ScriptEngine* engine, const QVariantMap& src) { + ScriptValue dest = engine->newObject(); + for (QVariantMap::const_iterator iter = src.cbegin(); iter != src.cend(); ++iter) { + dest.setProperty(iter.key(), engine->newVariant(iter.value())); + } + return dest; +} + +static bool VariantMapFromScriptValue(const ScriptValue& src, QVariantMap& dest) { + dest.clear(); + ScriptValueIteratorPointer iter = src.newIterator(); + while (iter->hasNext()) { + iter->next(); + dest.insert(iter->name(), iter->value().toVariant()); + } + return true; +} + +static ScriptValue VariantHashToScriptValue(ScriptEngine* engine, const QVariantHash& src) { + ScriptValue dest = engine->newObject(); + for (QVariantHash::const_iterator iter = src.cbegin(); iter != src.cend(); ++iter) { + dest.setProperty(iter.key(), engine->newVariant(iter.value())); + } + return dest; +} + +static bool VariantHashFromScriptValue(const ScriptValue& src, QVariantHash& dest) { + dest.clear(); + ScriptValueIteratorPointer iter = src.newIterator(); + while (iter->hasNext()) { + iter->next(); + dest.insert(iter->name(), iter->value().toVariant()); + } + return true; +} + +static ScriptValue JsonValueToScriptValue(ScriptEngine* engine, const QJsonValue& src) { + return engine->newVariant(src.toVariant()); +} + +static bool JsonValueFromScriptValue(const ScriptValue& src, QJsonValue& dest) { + dest = QJsonValue::fromVariant(src.toVariant()); + return true; +} + +static ScriptValue JsonObjectToScriptValue(ScriptEngine* engine, const QJsonObject& src) { + QVariantMap map = src.toVariantMap(); + ScriptValue dest = engine->newObject(); + for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) { + dest.setProperty(iter.key(), engine->newVariant(iter.value())); + } + return dest; +} + +static bool JsonObjectFromScriptValue(const ScriptValue& src, QJsonObject& dest) { + QVariantMap map; + ScriptValueIteratorPointer iter = src.newIterator(); + while (iter->hasNext()) { + iter->next(); + map.insert(iter->name(), iter->value().toVariant()); + } + dest = QJsonObject::fromVariantMap(map); + return true; +} + +static ScriptValue JsonArrayToScriptValue(ScriptEngine* engine, const QJsonArray& src) { + QVariantList list = src.toVariantList(); + int len = list.length(); + ScriptValue dest = engine->newArray(len); + for (int idx = 0; idx < len; ++idx) { + dest.setProperty(idx, engine->newVariant(list.at(idx))); + } + return dest; +} + +static bool JsonArrayFromScriptValue(const ScriptValue& src, QJsonArray& dest) { + if(!src.isArray()) return false; + QVariantList list; + int len = src.property("length").toInteger(); + for (int idx = 0; idx < len; ++idx) { + list.append(src.property(idx).toVariant()); + } + dest = QJsonArray::fromVariantList(list); + return true; +} + +// QMetaType::QJsonArray + +void ScriptEngineQtScript::registerSystemTypes() { + qScriptRegisterMetaType(this, ScriptValueToQScriptValue, ScriptValueFromQScriptValue); + + scriptRegisterMetaType(this, StringListToScriptValue, StringListFromScriptValue); + scriptRegisterMetaType(this, VariantListToScriptValue, VariantListFromScriptValue); + scriptRegisterMetaType(this, VariantMapToScriptValue, VariantMapFromScriptValue); + scriptRegisterMetaType(this, VariantHashToScriptValue, VariantHashFromScriptValue); + scriptRegisterMetaType(this, JsonValueToScriptValue, JsonValueFromScriptValue); + scriptRegisterMetaType(this, JsonObjectToScriptValue, JsonObjectFromScriptValue); + scriptRegisterMetaType(this, JsonArrayToScriptValue, JsonArrayFromScriptValue); +} + +bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& dest, int destTypeId) { + + // if we're not particularly interested in a specific type, try to detect if we're dealing with a registered type + if (destTypeId == QMetaType::UnknownType) { + QObject* obj = ScriptObjectQtProxy::unwrap(val); + if (obj) { + for (const QMetaObject* metaObject = obj->metaObject(); metaObject; metaObject = metaObject->superClass()) { + QByteArray typeName = QByteArray(metaObject->className()) + "*"; + int typeId = QMetaType::type(typeName.constData()); + if (typeId != QMetaType::UnknownType) { + destTypeId = typeId; + break; + } + } + } + } + + if (destTypeId == qMetaTypeId()) { + dest = QVariant::fromValue(ScriptValue(new ScriptValueQtWrapper(this, val))); + return true; + } + + // do we have a registered handler for this type? + ScriptEngine::DemarshalFunction demarshalFunc = nullptr; + { + QMutexLocker guard(&_customTypeProtect); + CustomMarshalMap::const_iterator lookup = _customTypes.find(destTypeId); + if (lookup != _customTypes.cend()) { + demarshalFunc = lookup.value().demarshalFunc; + } + } + if (demarshalFunc) { + dest = QVariant(destTypeId, static_cast(NULL)); + ScriptValue wrappedVal(new ScriptValueQtWrapper(this, val)); + bool success = demarshalFunc(wrappedVal, const_cast(dest.constData())); + if(!success) dest = QVariant(); + return success; + } else { + switch (destTypeId) { + case QMetaType::UnknownType: + if (val.isUndefined()) { + dest = QVariant(); + break; + } + if (val.isNull()) { + dest = QVariant::fromValue(nullptr); + break; + } + if (val.isBool()) { + dest = QVariant::fromValue(val.toBool()); + break; + } + if (val.isString()) { + dest = QVariant::fromValue(val.toString()); + break; + } + if (val.isNumber()) { + dest = QVariant::fromValue(val.toNumber()); + break; + } + { + QObject* obj = ScriptObjectQtProxy::unwrap(val); + if (obj) { + dest = QVariant::fromValue(obj); + break; + } + } + { + QVariant var = ScriptVariantQtProxy::unwrap(val); + if (var.isValid()) { + dest = var; + break; + } + } + dest = val.toVariant(); + break; + case QMetaType::Bool: + dest = QVariant::fromValue(val.toBool()); + break; + case QMetaType::QDateTime: + case QMetaType::QDate: + Q_ASSERT(val.isDate()); + dest = QVariant::fromValue(val.toDateTime()); + break; + case QMetaType::UInt: + case QMetaType::ULong: + dest = QVariant::fromValue(val.toUInt32()); + break; + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + dest = QVariant::fromValue(val.toInt32()); + break; + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::ULongLong: + case QMetaType::LongLong: + dest = QVariant::fromValue(val.toNumber()); + break; + case QMetaType::QString: + case QMetaType::QByteArray: + dest = QVariant::fromValue(val.toString()); + break; + case QMetaType::UShort: + dest = QVariant::fromValue(val.toUInt16()); + break; + case QMetaType::QObjectStar: + dest = QVariant::fromValue(ScriptObjectQtProxy::unwrap(val)); + break; + default: + // check to see if this is a pointer to a QObject-derived object + if (QMetaType::typeFlags(destTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { + /* Do we really want to permit regular passing of nullptr to native functions? + if (!val.isValid() || val.isUndefined() || val.isNull()) { + dest = QVariant::fromValue(nullptr); + break; + }*/ + QObject* obj = ScriptObjectQtProxy::unwrap(val); + if (!obj) return false; + const QMetaObject* destMeta = QMetaType::metaObjectForType(destTypeId); + Q_ASSERT(destMeta); + obj = destMeta->cast(obj); + if (!obj) return false; + dest = QVariant::fromValue(obj); + break; + } + // check to see if we have a registered prototype + { + QVariant var = ScriptVariantQtProxy::unwrap(val); + if (var.isValid()) { + dest = var; + break; + } + } + // last chance, just convert it to a variant + dest = val.toVariant(); + break; + } + } + + return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(destTypeId); +} + +QString ScriptEngineQtScript::valueType(const QScriptValue& val) { + if (val.isUndefined()) { + return "undefined"; + } + if (val.isNull()) { + return "null"; + } + if (val.isBool()) { + return "boolean"; + } + if (val.isString()) { + return "string"; + } + if (val.isNumber()) { + return "number"; + } + { + QObject* obj = ScriptObjectQtProxy::unwrap(val); + if (obj) { + QString objectName = obj->objectName(); + if (!objectName.isEmpty()) return objectName; + return obj->metaObject()->className(); + } + } + { + QVariant var = ScriptVariantQtProxy::unwrap(val); + if (var.isValid()) { + return var.typeName(); + } + } + return val.toVariant().typeName(); +} + +QScriptValue ScriptEngineQtScript::castVariantToValue(const QVariant& val) { + int valTypeId = val.userType(); + + if (valTypeId == qMetaTypeId()) { + // this is a wrapped ScriptValue, so just unwrap it and call it good + ScriptValue innerVal = val.value(); + return ScriptValueQtWrapper::fullUnwrap(this, innerVal); + } + + // do we have a registered handler for this type? + ScriptEngine::MarshalFunction marshalFunc = nullptr; + { + QMutexLocker guard(&_customTypeProtect); + CustomMarshalMap::const_iterator lookup = _customTypes.find(valTypeId); + if (lookup != _customTypes.cend()) { + marshalFunc = lookup.value().marshalFunc; + } + } + if (marshalFunc) { + ScriptValue wrappedVal = marshalFunc(this, val.constData()); + return ScriptValueQtWrapper::fullUnwrap(this, wrappedVal); + } + + switch (valTypeId) { + case QMetaType::UnknownType: + case QMetaType::Void: + return QScriptValue(this, QScriptValue::UndefinedValue); + case QMetaType::Nullptr: + return QScriptValue(this, QScriptValue::NullValue); + case QMetaType::Bool: + return QScriptValue(this, val.toBool()); + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + return QScriptValue(this, val.toInt()); + case QMetaType::UInt: + case QMetaType::ULong: + case QMetaType::UShort: + return QScriptValue(this, val.toUInt()); + case QMetaType::Float: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::Double: + return QScriptValue(this, val.toFloat()); + case QMetaType::QString: + case QMetaType::QByteArray: + return QScriptValue(this, val.toString()); + case QMetaType::QVariant: + return castVariantToValue(val.value()); + case QMetaType::QObjectStar: { + QObject* obj = val.value(); + if (obj == nullptr) return QScriptValue(this, QScriptValue::NullValue); + return ScriptObjectQtProxy::newQObject(this, obj); + } + case QMetaType::QDateTime: + return static_cast(this)->newDate(val.value()); + case QMetaType::QDate: + return static_cast(this)->newDate(val.value().startOfDay()); + default: + // check to see if this is a pointer to a QObject-derived object + if (QMetaType::typeFlags(valTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { + QObject* obj = val.value(); + if (obj == nullptr) return QScriptValue(this, QScriptValue::NullValue); + return ScriptObjectQtProxy::newQObject(this, obj); + } + // have we set a prototype'd variant? + { + QMutexLocker guard(&_customTypeProtect); + CustomPrototypeMap::const_iterator lookup = _customPrototypes.find(valTypeId); + if (lookup != _customPrototypes.cend()) { + return ScriptVariantQtProxy::newVariant(this, val, lookup.value()); + } + } + // just do a generic variant + return QScriptEngine::newVariant(val); + } +} \ No newline at end of file diff --git a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp new file mode 100644 index 00000000000..7f09479ee49 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp @@ -0,0 +1,731 @@ +// +// ScriptObjectQtProxy.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 12/5/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptObjectQtProxy.h" + +#include +#include + +#include + +#include "ScriptContextQtWrapper.h" +#include "ScriptValueQtWrapper.h" + +Q_DECLARE_METATYPE(QScriptContext*) +Q_DECLARE_METATYPE(ScriptValue) +Q_DECLARE_METATYPE(QScriptValue) + +Q_DECLARE_METATYPE(QSharedPointer) +Q_DECLARE_METATYPE(QSharedPointer) + +// Used strictly to replace the "this" object value for property access. May expand to a full context element +// if we find it necessary to, but hopefully not needed +class ScriptPropertyContextQtWrapper final : public ScriptContext { +public: // construction + inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) : + _parent(parentContext), _object(object) {} + +public: // ScriptContext implementation + virtual int argumentCount() const override { return _parent->argumentCount(); } + virtual ScriptValue argument(int index) const override { return _parent->argument(index); } + virtual QStringList backtrace() const override { return _parent->backtrace(); } + virtual ScriptValue callee() const override { return _parent->callee(); } + virtual ScriptEnginePointer engine() const override { return _parent->engine(); } + virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } + virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } + virtual ScriptValue thisObject() const override { return _object; } + virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } + virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } + +private: // storage + ScriptContext* _parent; + const ScriptValue& _object; +}; + +QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + QScriptEngine* qengine = static_cast(engine); + + // do we already have a valid wrapper for this QObject? + { + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); + ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; + } + } + + bool ownsObject; + switch (ownership) { + case ScriptEngine::QtOwnership: + ownsObject = false; + break; + case ScriptEngine::ScriptOwnership: + ownsObject = true; + break; + case ScriptEngine::AutoOwnership: + ownsObject = !object->parent(); + break; + } + + // create the wrapper + auto proxy = QSharedPointer::create(engine, object, ownsObject, options); + + { + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); + + // check again to see if someone else created the wrapper while we were busy + ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; + } + + // register the wrapper with the engine and make sure it cleans itself up + engine->_qobjectWrapperMap.insert(object, proxy); + QPointer enginePtr = engine; + object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { + if (!enginePtr) return; + QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); + ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); + if (lookup != enginePtr->_qobjectWrapperMap.end()) { + enginePtr->_qobjectWrapperMap.erase(lookup); + } + }); + } + + return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); +} + +ScriptObjectQtProxy* ScriptObjectQtProxy::unwrapProxy(const QScriptValue& val) { + QScriptClass* scriptClass = val.scriptClass(); + return scriptClass ? dynamic_cast(scriptClass) : nullptr; +} + +QObject* ScriptObjectQtProxy::unwrap(const QScriptValue& val) { + if (val.isQObject()) { + return val.toQObject(); + } + ScriptObjectQtProxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQtValue() : nullptr; +} + +ScriptObjectQtProxy::~ScriptObjectQtProxy() { + if (_ownsObject) { + QObject* qobject = _object; + if(qobject) qobject->deleteLater(); + } +} + +void ScriptObjectQtProxy::investigate() { + QObject* qobject = _object; + Q_ASSERT(qobject); + if (!qobject) return; + + const QMetaObject* metaObject = qobject->metaObject(); + + // discover properties + int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; + int num = metaObject->propertyCount(); + for (int idx = startIdx; idx < num; ++idx) { + QMetaProperty prop = metaObject->property(idx); + if (!prop.isScriptable()) continue; + + // always exclude child objects (at least until we decide otherwise) + int metaTypeId = prop.userType(); + if (metaTypeId != QMetaType::UnknownType) { + QMetaType metaType(metaTypeId); + if (metaType.flags() & QMetaType::PointerToQObject) { + continue; + } + } + + PropertyDef& propDef = _props.insert(idx, PropertyDef()).value(); + propDef.name = _engine->toStringHandle(QString::fromLatin1(prop.name())); + propDef.flags = QScriptValue::Undeletable | QScriptValue::PropertyGetter | QScriptValue::PropertySetter | + QScriptValue::QObjectMember; + if (prop.isConstant()) propDef.flags |= QScriptValue::ReadOnly; + } + + // discover methods + startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0; + num = metaObject->methodCount(); + QHash methodNames; + for (int idx = startIdx; idx < num; ++idx) { + QMetaMethod method = metaObject->method(idx); + + // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible + /* if (method.access() != QMetaMethod::Public) continue;*/ + + bool isSignal = false; + QByteArray szName = method.name(); + + switch (method.methodType()) { + case QMetaMethod::Constructor: + continue; + case QMetaMethod::Signal: + isSignal = true; + break; + case QMetaMethod::Slot: + if (_wrapOptions & ScriptEngine::ExcludeSlots) { + continue; + } + if (szName == "deleteLater") { + continue; + } + break; + } + + QScriptString name = _engine->toStringHandle(QString::fromLatin1(szName)); + auto nameLookup = methodNames.find(name); + if (isSignal) { + if (nameLookup == methodNames.end()) { + SignalDef& signalDef = _signals.insert(idx, SignalDef()).value(); + signalDef.name = name; + signalDef.signal = method; + methodNames.insert(name, idx); + } else { + int originalMethodId = nameLookup.value(); + SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); + Q_ASSERT(signalLookup != _signals.end()); + SignalDef& signalDef = signalLookup.value(); + Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); + if (signalDef.signal.parameterCount() < method.parameterCount()) { + signalDef.signal = method; + } + } + } else { + int parameterCount = method.parameterCount(); + if (nameLookup == methodNames.end()) { + MethodDef& methodDef = _methods.insert(idx, MethodDef()).value(); + methodDef.name = name; + methodDef.numMaxParms = parameterCount; + methodDef.methods.append(method); + methodNames.insert(name, idx); + } else { + int originalMethodId = nameLookup.value(); + MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); + Q_ASSERT(methodLookup != _methods.end()); + MethodDef& methodDef = methodLookup.value(); + if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount; + methodDef.methods.append(method); + } + } + } +} + +QString ScriptObjectQtProxy::name() const { + Q_ASSERT(_object); + if (!_object) return ""; + return _object ? _object->objectName() : ""; + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) return objectName; + return _object->metaObject()->className(); +} + +QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) { + // check for properties + for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { + const PropertyDef& propDef = trans.value(); + if (propDef.name != name) continue; + *id = trans.key() | PROPERTY_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for methods + for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { + if (trans.value().name != name) continue; + *id = trans.key() | METHOD_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for signals + for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { + if (trans.value().name != name) continue; + *id = trans.key() | SIGNAL_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + return QueryFlags(); +} + +QScriptValue::PropertyFlags ScriptObjectQtProxy::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) { + QObject* qobject = _object; + if (!qobject) { + return QScriptValue::PropertyFlags(); + } + + switch (id & TYPE_MASK) { + case PROPERTY_TYPE: { + PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); + if (lookup == _props.cend()) return QScriptValue::PropertyFlags(); + const PropertyDef& propDef = lookup.value(); + return propDef.flags; + } + case METHOD_TYPE: { + MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); + if (lookup == _methods.cend()) return QScriptValue::PropertyFlags(); + return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; + } + case SIGNAL_TYPE: { + SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); + if (lookup == _signals.cend()) return QScriptValue::PropertyFlags(); + return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; + } + } + return QScriptValue::PropertyFlags(); +} + +QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QScriptString& name, uint id) { + QObject* qobject = _object; + if (!qobject) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); + return QScriptValue(); + } + + const QMetaObject* metaObject = qobject->metaObject(); + + switch (id & TYPE_MASK) { + case PROPERTY_TYPE: { + int propId = id & ~TYPE_MASK; + PropertyDefMap::const_iterator lookup = _props.find(propId); + if (lookup == _props.cend()) return QScriptValue(); + const PropertyDef& propDef = lookup.value(); + + QMetaProperty prop = metaObject->property(propId); + ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); + ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); + ScriptContextGuard guard(&ourContext); + + QVariant varValue = prop.read(qobject); + return _engine->castVariantToValue(varValue); + } + case METHOD_TYPE: { + int methodId = id & ~TYPE_MASK; + MethodDefMap::const_iterator lookup = _methods.find(methodId); + if (lookup == _methods.cend()) return QScriptValue(); + const MethodDef& methodDef = lookup.value(); + return static_cast(_engine)->newObject( + new ScriptMethodQtProxy(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms)); + } + case SIGNAL_TYPE: { + int signalId = id & ~TYPE_MASK; + SignalDefMap::const_iterator defLookup = _signals.find(signalId); + if (defLookup == _signals.cend()) return QScriptValue(); + + InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); + if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { + instLookup = _signalInstances.insert(signalId, + new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal)); + Q_ASSERT(instLookup != _signalInstances.cend()); + } + ScriptSignalQtProxy* proxy = instLookup.value(); + + QScriptEngine::QObjectWrapOptions options = QScriptEngine::ExcludeSuperClassContents | + QScriptEngine::ExcludeDeleteLater | + QScriptEngine::PreferExistingWrapperObject; + return static_cast(_engine)->newQObject(proxy, QScriptEngine::ScriptOwnership, options); + } + } + return QScriptValue(); +} + +void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) { + if (!(id & PROPERTY_TYPE)) return; + QObject* qobject = _object; + if (!qobject) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); + return; + } + + int propId = id & ~TYPE_MASK; + PropertyDefMap::const_iterator lookup = _props.find(propId); + if (lookup == _props.cend()) return; + const PropertyDef& propDef = lookup.value(); + if (propDef.flags & QScriptValue::ReadOnly) return; + + const QMetaObject* metaObject = qobject->metaObject(); + QMetaProperty prop = metaObject->property(propId); + + ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); + ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); + ScriptContextGuard guard(&ourContext); + + int propTypeId = prop.userType(); + Q_ASSERT(propTypeId != QMetaType::UnknownType); + QVariant varValue; + if(!_engine->castValueToVariant(value, varValue, propTypeId)) { + QByteArray propTypeName = QMetaType(propTypeId).name(); + QByteArray valTypeName = _engine->valueType(value).toLatin1(); + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName)); + return; + } + prop.write(qobject, varValue); +} + +ScriptVariantQtProxy::ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto) : + QScriptClass(engine), _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { + _name = QString::fromLatin1(variant.typeName()); +} + +QScriptValue ScriptVariantQtProxy::newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto) { + QScriptEngine* qengine = static_cast(engine); + ScriptObjectQtProxy* protoProxy = ScriptObjectQtProxy::unwrapProxy(proto); + if (!protoProxy) { + Q_ASSERT(protoProxy); + return qengine->newVariant(variant); + } + auto proxy = QSharedPointer::create(engine, variant, proto, protoProxy); + return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); +} + +ScriptVariantQtProxy* ScriptVariantQtProxy::unwrapProxy(const QScriptValue& val) { + QScriptClass* scriptClass = val.scriptClass(); + return scriptClass ? dynamic_cast(scriptClass) : nullptr; +} + +QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { + ScriptVariantQtProxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQtValue() : QVariant(); +} + +QString ScriptMethodQtProxy::fullName() const { + Q_ASSERT(_object); + if (!_object) return ""; + Q_ASSERT(!_metas.isEmpty()); + const QMetaMethod& firstMethod = _metas.front(); + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) { + return QString("%1.%2").arg(objectName, firstMethod.name()); + } + return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); +} + +bool ScriptMethodQtProxy::supportsExtension(Extension extension) const { + switch (extension) { + case Callable: + return true; + default: + return false; + } +} + +QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& argument) { + if (extension != Callable) return QVariant(); + QScriptContext* context = qvariant_cast(argument); + + QObject* qobject = _object; + if (!qobject) { + context->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); + return QVariant(); + } + + int scriptNumArgs = context->argumentCount(); + int numArgs = std::min(scriptNumArgs, _numMaxParms); + + const int scriptValueTypeId = qMetaTypeId(); + + int parameterConversionFailureId = 0; + int parameterConversionFailureCount = 0; + + for (auto iter = _metas.cbegin(); iter != _metas.end(); ++iter) { + const QMetaMethod& meta = *iter; + int methodNumArgs = meta.parameterCount(); + if (methodNumArgs != numArgs) { + continue; + } + + QList qScriptArgList; + QList qVarArgList; + QGenericArgument qGenArgs[10]; + int conversionFailures = 0; + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + QScriptValue argVal = context->argument(arg); + if (methodArgTypeId == scriptValueTypeId) { + qScriptArgList.append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal))); + qGenArgs[arg] = Q_ARG(ScriptValue, qScriptArgList.back()); + } else if (methodArgTypeId == QMetaType::QVariant) { + qVarArgList.append(argVal.toVariant()); + qGenArgs[arg] = Q_ARG(QVariant, qVarArgList.back()); + } else { + QVariant varArgVal; + if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgList.append(varArgVal); + const QVariant& converted = qVarArgList.back(); + + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + // A const_cast is needed because calling data() would detach the QVariant. + qGenArgs[arg] = + QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); + } + } + } + if (conversionFailures) { + if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { + parameterConversionFailureCount = conversionFailures; + parameterConversionFailureId = meta.methodIndex(); + } + continue; + } + + ScriptContextQtWrapper ourContext(_engine, context); + ScriptContextGuard guard(&ourContext); + + int returnTypeId = meta.returnType(); + + // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's + // nothing in there for return values so these need to be explicitly runtime-registered! + Q_ASSERT(returnTypeId != QMetaType::UnknownType); + if (returnTypeId == QMetaType::UnknownType) { + context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName())); + return QVariant(); + } else if (returnTypeId == QMetaType::Void) { + bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); + } + return QVariant(); + } else if (returnTypeId == scriptValueTypeId) { + ScriptValue result; + bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], + qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], + qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); + return QVariant(); + } + QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); + return QVariant::fromValue(qResult); + } else { + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + const char* typeName = meta.typeName(); + QVariant qRetVal(returnTypeId, static_cast(NULL)); + QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); + + bool success = + meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); + return QVariant(); + } + QScriptValue qResult = _engine->castVariantToValue(qRetVal); + return QVariant::fromValue(qResult); + } + } + + // we failed to convert the call to C++, try to create a somewhat sane error message + if (parameterConversionFailureCount == 0) { + context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName())); + return QVariant(); + } + + const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); + int methodNumArgs = meta.parameterCount(); + Q_ASSERT(methodNumArgs == numArgs); + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + QScriptValue argVal = context->argument(arg); + if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { + QVariant varArgVal; + if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { + QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); + QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); + context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); + return QVariant(); + } + } + } + + Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? + return QVariant(); + context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); +} + +QString ScriptSignalQtProxy::fullName() const { + Q_ASSERT(_object); + if (!_object) return ""; + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) { + return QString("%1.%2").arg(objectName, _meta.name()); + } + return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); +} + +// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it +int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { + id = ScriptSignalQtProxyBase::qt_metacall(call, id, arguments); + if (id != 0 || call != QMetaObject::InvokeMetaMethod) { + return id; + } + + QScriptValueList args; + int numArgs = _meta.parameterCount(); + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = _meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + QVariant argValue(methodArgTypeId, arguments[arg+1]); + args.append(_engine->castVariantToValue(argValue)); + } + + for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { + Connection& conn = *iter; + conn.callback.call(conn.thisValue, args); + } + + return -1; +} + +int ScriptSignalQtProxy::discoverMetaCallIdx() { + const QMetaObject* ourMeta = metaObject(); + return ourMeta->methodCount(); +} + +ScriptSignalQtProxy::ConnectionList::iterator ScriptSignalQtProxy::findConnection(QScriptValue thisObject, + QScriptValue callback) { + for (ConnectionList::iterator iter = _connections.begin(); iter != _connections.end(); ++iter) { + Connection& conn = *iter; + if (conn.callback.strictlyEquals(callback) && conn.thisValue.strictlyEquals(thisObject)) { + return iter; + } + } + return _connections.end(); +} + + +void ScriptSignalQtProxy::connect(QScriptValue arg0, QScriptValue arg1) { + QObject* qobject = _object; + if (!qobject) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); + return; + } + + // untangle the arguments + QScriptValue callback; + QScriptValue callbackThis; + if (arg1.isFunction()) { + callbackThis = arg0; + callback = arg1; + } else { + callback = arg0; + } + if (!callback.isFunction()) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'connect'"); + return; + } + + // are we already connected? + ConnectionList::iterator lookup = findConnection(callbackThis, callback); + if (lookup != _connections.end()) { + return; // already exists + } + + // add a reference to ourselves to the destination callback + QScriptValue destData = callback.data(); + Q_ASSERT(!destData.isValid() || destData.isArray()); + if (!destData.isArray()) { + destData = static_cast(_engine)->newArray(); + } + { + QScriptValueList args; + args << thisObject(); + destData.property("push").call(destData, args); + } + callback.setData(destData); + + // add this to our internal list of connections + Connection newConn; + newConn.callback = callback; + newConn.thisValue = callbackThis; + _connections.append(newConn); + + // inform Qt that we're connecting to this signal + if (!_isConnected) { + auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); + Q_ASSERT(result); + _isConnected = true; + } +} + +void ScriptSignalQtProxy::disconnect(QScriptValue arg0, QScriptValue arg1) { + QObject* qobject = _object; + if (!qobject) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); + return; + } + + // untangle the arguments + QScriptValue callback; + QScriptValue callbackThis; + if (arg1.isFunction()) { + callbackThis = arg0; + callback = arg1; + } else { + callback = arg0; + } + if (!callback.isFunction()) { + QScriptContext* currentContext = static_cast(_engine)->currentContext(); + currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'disconnect'"); + return; + } + + // locate this connection in our list of connections + ConnectionList::iterator lookup = findConnection(callbackThis, callback); + if (lookup == _connections.end()) { + return; // not here + } + + // remove it from our internal list of connections + _connections.erase(lookup); + + // remove a reference to ourselves from the destination callback + QScriptValue destData = callback.data(); + Q_ASSERT(destData.isArray()); + if (destData.isArray()) { + QScriptValue qThis = thisObject(); + int len = destData.property("length").toInteger(); + bool foundIt = false; + for (int idx = 0; idx < len && !foundIt; ++idx) { + QScriptValue entry = destData.property(idx); + if (entry.strictlyEquals(qThis)) { + foundIt = true; + QScriptValueList args; + args << idx << 1; + destData.property("splice").call(destData, args); + } + } + Q_ASSERT(foundIt); + } + + // inform Qt that we're no longer connected to this signal + if (_connections.empty()) { + Q_ASSERT(_isConnected); + bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); + Q_ASSERT(result); + _isConnected = false; + } +} diff --git a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h new file mode 100644 index 00000000000..34ee8526119 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h @@ -0,0 +1,210 @@ +// +// ScriptObjectQtProxy.h +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 12/5/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptObjectQtProxy_h +#define hifi_ScriptObjectQtProxy_h + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../ScriptEngine.h" +#include "ScriptEngineQtScript.h" + +class ScriptEngineQtScript; +class ScriptSignalQtProxy; + +/// [QtScript] (re-)implements the translation layer between ScriptValue and QObject. This object +/// will focus exclusively on property get/set until function calls appear to be a problem +class ScriptObjectQtProxy final : public QScriptClass { +private: // implementation + struct PropertyDef { + QScriptString name; + QScriptValue::PropertyFlags flags; + }; + struct MethodDef { + QScriptString name; + int numMaxParms; + QList methods; + }; + struct SignalDef { + QScriptString name; + QMetaMethod signal; + }; + using PropertyDefMap = QHash; + using MethodDefMap = QHash; + using SignalDefMap = QHash; + using InstanceMap = QHash >; + + static constexpr uint PROPERTY_TYPE = 0x1000; + static constexpr uint METHOD_TYPE = 0x2000; + static constexpr uint SIGNAL_TYPE = 0x3000; + static constexpr uint TYPE_MASK = 0xF000; + +public: // construction + inline ScriptObjectQtProxy(ScriptEngineQtScript* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : + QScriptClass(engine), _engine(engine), _object(object), _wrapOptions(options), _ownsObject(ownsObject) { + investigate(); + } + virtual ~ScriptObjectQtProxy(); + + static QScriptValue newQObject(ScriptEngineQtScript* engine, + QObject* object, + ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, + const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()); + static ScriptObjectQtProxy* unwrapProxy(const QScriptValue& val); + static QObject* unwrap(const QScriptValue& val); + inline QObject* toQtValue() const { return _object; } + +public: // QScriptClass implementation + virtual QString name() const override; + + virtual QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; + virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) override; + virtual QueryFlags queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) override; + virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; + +private: // implementation + void investigate(); + +private: // storage + ScriptEngineQtScript* _engine; + const ScriptEngine::QObjectWrapOptions _wrapOptions; + PropertyDefMap _props; + MethodDefMap _methods; + SignalDefMap _signals; + InstanceMap _signalInstances; + const bool _ownsObject; + QPointer _object; + + Q_DISABLE_COPY(ScriptObjectQtProxy) +}; + +/// [QtScript] (re-)implements the translation layer between ScriptValue and QVariant where a prototype is set. +/// This object depends on a ScriptObjectQtProxy to provide the prototype's behavior +class ScriptVariantQtProxy final : public QScriptClass { +public: // construction + ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto); + + static QScriptValue newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto); + static ScriptVariantQtProxy* unwrapProxy(const QScriptValue& val); + static QVariant unwrap(const QScriptValue& val); + inline QVariant toQtValue() const { return _variant; } + +public: // QScriptClass implementation + virtual QString name() const override { return _name; } + + virtual QScriptValue prototype() const override { return _scriptProto; } + + virtual QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override { + return _proto->property(object, name, id); + } + virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) override { + return _proto->propertyFlags(object, name, id); + } + virtual QueryFlags queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) override { + return _proto->queryProperty(object, name, flags, id); + } + virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override { + return _proto->setProperty(object, name, id, value); + } + +private: // storage + ScriptEngineQtScript* _engine; + QVariant _variant; + QScriptValue _scriptProto; + ScriptObjectQtProxy* _proto; + QString _name; + + Q_DISABLE_COPY(ScriptVariantQtProxy) +}; + +class ScriptMethodQtProxy final : public QScriptClass { +public: // construction + inline ScriptMethodQtProxy(ScriptEngineQtScript* engine, QObject* object, QScriptValue lifetime, + const QList& metas, int numMaxParms) : + QScriptClass(engine), + _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas), _numMaxParms(numMaxParms) {} + +public: // QScriptClass implementation + virtual QString name() const override { return fullName(); } + virtual bool supportsExtension(Extension extension) const override; + virtual QVariant extension(Extension extension, const QVariant& argument = QVariant()) override; + +private: + QString fullName() const; + +private: // storage + const int _numMaxParms; + ScriptEngineQtScript* _engine; + QPointer _object; + QScriptValue _objectLifetime; + const QList _metas; + + Q_DISABLE_COPY(ScriptMethodQtProxy) +}; + +// This abstract base class serves solely to declare the Q_INVOKABLE methods for ScriptSignalQtProxy +// as we're overriding qt_metacall later for the signal callback yet still want to support +// metacalls for the connect/disconnect API +class ScriptSignalQtProxyBase : public QObject, protected QScriptable { + Q_OBJECT +public: // API + Q_INVOKABLE virtual void connect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) = 0; + Q_INVOKABLE virtual void disconnect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) = 0; +}; + +class ScriptSignalQtProxy final : public ScriptSignalQtProxyBase { +private: // storage + struct Connection { + QScriptValue thisValue; + QScriptValue callback; + }; + using ConnectionList = QList; + +public: // construction + inline ScriptSignalQtProxy(ScriptEngineQtScript* engine, QObject* object, QScriptValue lifetime, const QMetaMethod& meta) : + _engine(engine), _object(object), _objectLifetime(lifetime), _meta(meta), _metaCallId(discoverMetaCallIdx()) {} + +private: // implementation + virtual int qt_metacall(QMetaObject::Call call, int id, void** arguments); + int discoverMetaCallIdx(); + ConnectionList::iterator findConnection(QScriptValue thisObject, QScriptValue callback); + QString fullName() const; + +public: // API + virtual void connect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) override; + virtual void disconnect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) override; + +private: // storage + ScriptEngineQtScript* _engine; + QPointer _object; + QScriptValue _objectLifetime; + const QMetaMethod _meta; + const int _metaCallId; + ConnectionList _connections; + bool _isConnected{ false }; + + Q_DISABLE_COPY(ScriptSignalQtProxy) +}; + +#endif // hifi_ScriptObjectQtProxy_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp new file mode 100644 index 00000000000..3a5b2ae68e6 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp @@ -0,0 +1,55 @@ +// +// ScriptProgramQtWrapper.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 8/24/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptProgramQtWrapper.h" + +#include + +#include "ScriptEngineQtScript.h" +#include "ScriptValueQtWrapper.h" + +ScriptProgramQtWrapper* ScriptProgramQtWrapper::unwrap(ScriptProgramPointer val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val.get()); +} + +ScriptSyntaxCheckResultPointer ScriptProgramQtWrapper::checkSyntax() const { + QScriptSyntaxCheckResult result = _engine->checkSyntax(_value.sourceCode()); + return std::make_shared(std::move(result)); +} + +QString ScriptProgramQtWrapper::fileName() const { + return _value.fileName(); +} + +QString ScriptProgramQtWrapper::sourceCode() const { + return _value.sourceCode(); +} + + +int ScriptSyntaxCheckResultQtWrapper::errorColumnNumber() const { + return _value.errorColumnNumber(); +} + +int ScriptSyntaxCheckResultQtWrapper::errorLineNumber() const { + return _value.errorLineNumber(); +} + +QString ScriptSyntaxCheckResultQtWrapper::errorMessage() const { + return _value.errorMessage(); +} + +ScriptSyntaxCheckResult::State ScriptSyntaxCheckResultQtWrapper::state() const { + return static_cast(_value.state()); +} diff --git a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h new file mode 100644 index 00000000000..6849b71339e --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h @@ -0,0 +1,61 @@ +// +// ScriptProgramQtWrapper.h +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 5/21/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptProgramQtWrapper_h +#define hifi_ScriptProgramQtWrapper_h + +#include +#include + +#include "../ScriptProgram.h" +#include "ScriptEngineQtScript.h" + +/// [QtScript] Implements ScriptProgram for QtScript and translates calls for QScriptProgram +class ScriptProgramQtWrapper final : public ScriptProgram { +public: // construction + inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, const QScriptProgram& value) : + _engine(engine), _value(value) {} + inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, QScriptProgram&& value) : + _engine(engine), _value(std::move(value)) {} + static ScriptProgramQtWrapper* unwrap(ScriptProgramPointer val); + inline const QScriptProgram& toQtValue() const { return _value; } + +public: // ScriptProgram implementation + virtual ScriptSyntaxCheckResultPointer checkSyntax() const override; + virtual QString fileName() const override; + virtual QString sourceCode() const override; + +private: // storage + QPointer _engine; + QScriptProgram _value; +}; + +class ScriptSyntaxCheckResultQtWrapper final : public ScriptSyntaxCheckResult { +public: // construction + inline ScriptSyntaxCheckResultQtWrapper(QScriptSyntaxCheckResult&& value) : + _value(std::move(value)) {} + +public: // ScriptSyntaxCheckResult implementation + virtual int errorColumnNumber() const override; + virtual int errorLineNumber() const override; + virtual QString errorMessage() const override; + virtual State state() const override; + +private: // storage + QScriptSyntaxCheckResult _value; +}; + +#endif // hifi_ScriptValueQtWrapper_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp new file mode 100644 index 00000000000..ea3a4fe542e --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp @@ -0,0 +1,33 @@ +// +// ScriptValueIteratorQtWrapper.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 8/29/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueIteratorQtWrapper.h" + +ScriptValue::PropertyFlags ScriptValueIteratorQtWrapper::flags() const { + return (ScriptValue::PropertyFlags)(int)_value.flags(); +} + +bool ScriptValueIteratorQtWrapper::hasNext() const { + return _value.hasNext(); +} + +QString ScriptValueIteratorQtWrapper::name() const { + return _value.name(); +} + +void ScriptValueIteratorQtWrapper::next() { + _value.next(); +} + +ScriptValue ScriptValueIteratorQtWrapper::value() const { + QScriptValue result = _value.value(); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} diff --git a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h new file mode 100644 index 00000000000..00b618c6ded --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h @@ -0,0 +1,47 @@ +// +// ScriptValueIteratorQtWrapper.h +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 8/29/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueIteratorQtWrapper_h +#define hifi_ScriptValueIteratorQtWrapper_h + +#include +#include + +#include "../ScriptValueIterator.h" +#include "ScriptEngineQtScript.h" +#include "ScriptValueQtWrapper.h" + +/// [QtScript] Implements ScriptValueIterator for QtScript and translates calls for QScriptValueIterator +class ScriptValueIteratorQtWrapper final : public ScriptValueIterator { +public: // construction + inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const ScriptValue& object) : + _engine(engine), _value(ScriptValueQtWrapper::fullUnwrap(engine, object)) {} + inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& object) : + _engine(engine), _value(object) {} + +public: // ScriptValueIterator implementation + virtual ScriptValue::PropertyFlags flags() const override; + virtual bool hasNext() const override; + virtual QString name() const override; + virtual void next() override; + virtual ScriptValue value() const override; + +private: // storage + QPointer _engine; + QScriptValueIterator _value; +}; + +#endif // hifi_ScriptValueIteratorQtWrapper_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp new file mode 100644 index 00000000000..cae3de30f89 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp @@ -0,0 +1,234 @@ +// +// ScriptValueQtWrapper.cpp +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 5/16/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueQtWrapper.h" + +#include "ScriptValueIteratorQtWrapper.h" + +void ScriptValueQtWrapper::release() { + delete this; +} + +ScriptValueProxy* ScriptValueQtWrapper::copy() const { + return new ScriptValueQtWrapper(_engine, _value); +} + +ScriptValueQtWrapper* ScriptValueQtWrapper::unwrap(const ScriptValue& val) { + return dynamic_cast(val.ptr()); +} + +QScriptValue ScriptValueQtWrapper::fullUnwrap(const ScriptValue& value) const { + ScriptValueQtWrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != _engine) { + return static_cast(_engine)->toScriptValue(unwrapped->toVariant()); + } else { + return unwrapped->toQtValue(); + } + } + QVariant varValue = value.toVariant(); + return _engine->castVariantToValue(varValue); +} + +QScriptValue ScriptValueQtWrapper::fullUnwrap(ScriptEngineQtScript* engine, const ScriptValue& value) { + ScriptValueQtWrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != engine) { + return static_cast(engine)->toScriptValue(unwrapped->toVariant()); + } else { + return unwrapped->toQtValue(); + } + } + QVariant varValue = value.toVariant(); + return engine->castVariantToValue(varValue); +} + +ScriptValue ScriptValueQtWrapper::call(const ScriptValue& thisObject, const ScriptValueList& args) { + QScriptValue qThis = fullUnwrap(thisObject); + QScriptValueList qArgs; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + qArgs.push_back(fullUnwrap(*iter)); + } + QScriptValue result = _value.call(qThis, qArgs); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueQtWrapper::call(const ScriptValue& thisObject, const ScriptValue& arguments) { + QScriptValue qThis = fullUnwrap(thisObject); + QScriptValue qArgs = fullUnwrap(arguments); + QScriptValue result = _value.call(qThis, qArgs); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueQtWrapper::construct(const ScriptValueList& args) { + QScriptValueList qArgs; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + qArgs.push_back(fullUnwrap(*iter)); + } + QScriptValue result = _value.construct(qArgs); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueQtWrapper::construct(const ScriptValue& arguments) { + QScriptValue unwrapped = fullUnwrap(arguments); + QScriptValue result = _value.construct(unwrapped); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueQtWrapper::data() const { + QScriptValue result = _value.data(); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptEnginePointer ScriptValueQtWrapper::engine() const { + if (!_engine) { + return ScriptEnginePointer(); + } + return _engine->shared_from_this(); +} + +ScriptValueIteratorPointer ScriptValueQtWrapper::newIterator() const { + return std::make_shared(_engine, _value); +} + +ScriptValue ScriptValueQtWrapper::property(const QString& name, const ScriptValue::ResolveFlags& mode) const { + QScriptValue result = _value.property(name, (QScriptValue::ResolveFlags)(int)mode); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueQtWrapper::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const { + QScriptValue result = _value.property(arrayIndex, (QScriptValue::ResolveFlags)(int)mode); + return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +void ScriptValueQtWrapper::setData(const ScriptValue& value) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setData(unwrapped); +} + +void ScriptValueQtWrapper::setProperty(const QString& name, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setProperty(name, unwrapped, (QScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueQtWrapper::setProperty(quint32 arrayIndex, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setProperty(arrayIndex, unwrapped, (QScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueQtWrapper::setPrototype(const ScriptValue& prototype) { + ScriptValueQtWrapper* unwrappedPrototype = unwrap(prototype); + if (unwrappedPrototype) { + _value.setPrototype(unwrappedPrototype->toQtValue()); + } +} + +bool ScriptValueQtWrapper::strictlyEquals(const ScriptValue& other) const { + ScriptValueQtWrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.strictlyEquals(unwrappedOther->toQtValue()) : false; +} + +bool ScriptValueQtWrapper::toBool() const { + return _value.toBool(); +} + +qint32 ScriptValueQtWrapper::toInt32() const { + return _value.toInt32(); +} + +double ScriptValueQtWrapper::toInteger() const { + return _value.toInteger(); +} + +double ScriptValueQtWrapper::toNumber() const { + return _value.toNumber(); +} + +QString ScriptValueQtWrapper::toString() const { + return _value.toString(); +} + +quint16 ScriptValueQtWrapper::toUInt16() const { + return _value.toUInt16(); +} + +quint32 ScriptValueQtWrapper::toUInt32() const { + return _value.toUInt32(); +} + +QVariant ScriptValueQtWrapper::toVariant() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::UnknownType)) { + return dest; + } else { + Q_ASSERT(false); + return QVariant(); + } +} + +QObject* ScriptValueQtWrapper::toQObject() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::QObjectStar)) { + return dest.value(); + } else { + Q_ASSERT(false); + return nullptr; + } +} + +bool ScriptValueQtWrapper::equals(const ScriptValue& other) const { + ScriptValueQtWrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.equals(unwrappedOther->toQtValue()) : false; +} + +bool ScriptValueQtWrapper::isArray() const { + return _value.isArray(); +} + +bool ScriptValueQtWrapper::isBool() const { + return _value.isBool(); +} + +bool ScriptValueQtWrapper::isError() const { + return _value.isError(); +} + +bool ScriptValueQtWrapper::isFunction() const { + return _value.isFunction(); +} + +bool ScriptValueQtWrapper::isNumber() const { + return _value.isNumber(); +} + +bool ScriptValueQtWrapper::isNull() const { + return _value.isNull(); +} + +bool ScriptValueQtWrapper::isObject() const { + return _value.isObject(); +} + +bool ScriptValueQtWrapper::isString() const { + return _value.isString(); +} + +bool ScriptValueQtWrapper::isUndefined() const { + return _value.isUndefined(); +} + +bool ScriptValueQtWrapper::isValid() const { + return _value.isValid(); +} + +bool ScriptValueQtWrapper::isVariant() const { + return _value.isVariant(); +} diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h new file mode 100644 index 00000000000..bce207ddbf1 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h @@ -0,0 +1,96 @@ +// +// ScriptValueQtWrapper.h +// libraries/script-engine/src/qtscript +// +// Created by Heather Anderson on 5/16/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueQtWrapper_h +#define hifi_ScriptValueQtWrapper_h + +#include +#include + +#include + +#include "../ScriptValue.h" +#include "ScriptEngineQtScript.h" + +/// [QtScript] Implements ScriptValue for QtScript and translates calls for QScriptValue +class ScriptValueQtWrapper final : public ScriptValueProxy { +public: // construction + inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& value) : + _engine(engine), _value(value) {} + inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, QScriptValue&& value) : + _engine(engine), _value(std::move(value)) {} + static ScriptValueQtWrapper* unwrap(const ScriptValue& val); + inline const QScriptValue& toQtValue() const { return _value; } + static QScriptValue fullUnwrap(ScriptEngineQtScript* engine, const ScriptValue& value); + +public: + virtual void release() override; + virtual ScriptValueProxy* copy() const override; + +public: // ScriptValue implementation + virtual ScriptValue call(const ScriptValue& thisObject = ScriptValue(), + const ScriptValueList& args = ScriptValueList()) override; + virtual ScriptValue call(const ScriptValue& thisObject, const ScriptValue& arguments) override; + virtual ScriptValue construct(const ScriptValueList& args = ScriptValueList()) override; + virtual ScriptValue construct(const ScriptValue& arguments) override; + virtual ScriptValue data() const override; + virtual ScriptEnginePointer engine() const override; + virtual ScriptValueIteratorPointer newIterator() const override; + virtual ScriptValue property(const QString& name, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const override; + virtual ScriptValue property(quint32 arrayIndex, + const ScriptValue::ResolveFlags& mode = ScriptValue::ResolvePrototype) const override; + virtual void setData(const ScriptValue& val) override; + virtual void setProperty(const QString& name, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) override; + virtual void setProperty(quint32 arrayIndex, + const ScriptValue& value, + const ScriptValue::PropertyFlags& flags = ScriptValue::KeepExistingFlags) override; + virtual void setPrototype(const ScriptValue& prototype) override; + virtual bool strictlyEquals(const ScriptValue& other) const override; + + virtual bool equals(const ScriptValue& other) const override; + virtual bool isArray() const override; + virtual bool isBool() const override; + virtual bool isError() const override; + virtual bool isFunction() const override; + virtual bool isNumber() const override; + virtual bool isNull() const override; + virtual bool isObject() const override; + virtual bool isString() const override; + virtual bool isUndefined() const override; + virtual bool isValid() const override; + virtual bool isVariant() const override; + virtual bool toBool() const override; + virtual qint32 toInt32() const override; + virtual double toInteger() const override; + virtual double toNumber() const override; + virtual QString toString() const override; + virtual quint16 toUInt16() const override; + virtual quint32 toUInt32() const override; + virtual QVariant toVariant() const override; + virtual QObject* toQObject() const override; + +private: // helper functions + QScriptValue fullUnwrap(const ScriptValue& value) const; + +private: // storage + QPointer _engine; + QScriptValue _value; +}; + +#endif // hifi_ScriptValueQtWrapper_h + +/// @} diff --git a/libraries/script-engine/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp similarity index 99% rename from libraries/script-engine/src/TypedArrayPrototype.cpp rename to libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp index a1f3ff87e8d..17da12b845c 100644 --- a/libraries/script-engine/src/TypedArrayPrototype.cpp +++ b/libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp @@ -11,6 +11,8 @@ #include "TypedArrayPrototype.h" +#include + #include "TypedArrays.h" Q_DECLARE_METATYPE(QByteArray*) diff --git a/libraries/script-engine/src/TypedArrayPrototype.h b/libraries/script-engine/src/qtscript/TypedArrayPrototype.h similarity index 74% rename from libraries/script-engine/src/TypedArrayPrototype.h rename to libraries/script-engine/src/qtscript/TypedArrayPrototype.h index adcc9f3abf9..ff0ea2fec65 100644 --- a/libraries/script-engine/src/TypedArrayPrototype.h +++ b/libraries/script-engine/src/qtscript/TypedArrayPrototype.h @@ -15,9 +15,11 @@ #ifndef hifi_TypedArrayPrototype_h #define hifi_TypedArrayPrototype_h -#include "ArrayBufferViewClass.h" +#include +#include +#include -/// The javascript functions associated with a TypedArray instance prototype +/// [QtScript] The javascript functions associated with a TypedArray instance prototype class TypedArrayPrototype : public QObject, public QScriptable { Q_OBJECT public: diff --git a/libraries/script-engine/src/TypedArrays.cpp b/libraries/script-engine/src/qtscript/TypedArrays.cpp similarity index 92% rename from libraries/script-engine/src/TypedArrays.cpp rename to libraries/script-engine/src/qtscript/TypedArrays.cpp index f2c3d3fd3db..8143dfb1fa7 100644 --- a/libraries/script-engine/src/TypedArrays.cpp +++ b/libraries/script-engine/src/qtscript/TypedArrays.cpp @@ -13,12 +13,17 @@ #include -#include "ScriptEngine.h" +#include + +#include + +#include "ArrayBufferClass.h" +#include "ScriptEngineQtScript.h" #include "TypedArrayPrototype.h" Q_DECLARE_METATYPE(QByteArray*) -TypedArray::TypedArray(ScriptEngine* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { +TypedArray::TypedArray(ScriptEngineQtScript* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { _bytesPerElementName = engine()->toStringHandle(BYTES_PER_ELEMENT_PROPERTY_NAME.toLatin1()); _lengthName = engine()->toStringHandle(LENGTH_PROPERTY_NAME.toLatin1()); _name = engine()->toStringHandle(name.toLatin1()); @@ -229,7 +234,7 @@ void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint } } -Int8ArrayClass::Int8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { +Int8ArrayClass::Int8ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint8)); } @@ -245,7 +250,7 @@ void Int8ArrayClass::setProperty(QScriptValue &object, const QScriptString &name setPropertyHelper(ba, name, id, value); } -Uint8ArrayClass::Uint8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { +Uint8ArrayClass::Uint8ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } @@ -261,7 +266,7 @@ void Uint8ArrayClass::setProperty(QScriptValue& object, const QScriptString& nam setPropertyHelper(ba, name, id, value); } -Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { +Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } @@ -287,7 +292,7 @@ void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptStri } } -Int16ArrayClass::Int16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { +Int16ArrayClass::Int16ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint16)); } @@ -303,7 +308,7 @@ void Int16ArrayClass::setProperty(QScriptValue& object, const QScriptString& nam setPropertyHelper(ba, name, id, value); } -Uint16ArrayClass::Uint16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { +Uint16ArrayClass::Uint16ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint16)); } @@ -319,7 +324,7 @@ void Uint16ArrayClass::setProperty(QScriptValue& object, const QScriptString& na setPropertyHelper(ba, name, id, value); } -Int32ArrayClass::Int32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { +Int32ArrayClass::Int32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint32)); } @@ -335,7 +340,7 @@ void Int32ArrayClass::setProperty(QScriptValue& object, const QScriptString& nam setPropertyHelper(ba, name, id, value); } -Uint32ArrayClass::Uint32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { +Uint32ArrayClass::Uint32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint32)); } @@ -352,7 +357,7 @@ void Uint32ArrayClass::setProperty(QScriptValue& object, const QScriptString& na setPropertyHelper(ba, name, id, value); } -Float32ArrayClass::Float32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { +Float32ArrayClass::Float32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(float)); } @@ -389,7 +394,7 @@ void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& n } } -Float64ArrayClass::Float64ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { +Float64ArrayClass::Float64ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(double)); } diff --git a/libraries/script-engine/src/TypedArrays.h b/libraries/script-engine/src/qtscript/TypedArrays.h similarity index 86% rename from libraries/script-engine/src/TypedArrays.h rename to libraries/script-engine/src/qtscript/TypedArrays.h index 948b9677f14..0595ba3c630 100644 --- a/libraries/script-engine/src/TypedArrays.h +++ b/libraries/script-engine/src/qtscript/TypedArrays.h @@ -30,11 +30,11 @@ static const QString UINT_32_ARRAY_CLASS_NAME = "Uint32Array"; static const QString FLOAT_32_ARRAY_CLASS_NAME = "Float32Array"; static const QString FLOAT_64_ARRAY_CLASS_NAME = "Float64Array"; -/// Implements the TypedArray scripting class +/// [QtScript] Implements the TypedArray scripting class class TypedArray : public ArrayBufferViewClass { Q_OBJECT public: - TypedArray(ScriptEngine* scriptEngine, QString name); + TypedArray(ScriptEngineQtScript* scriptEngine, QString name); virtual QScriptValue newInstance(quint32 length); virtual QScriptValue newInstance(QScriptValue array); virtual QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length); @@ -71,7 +71,7 @@ class TypedArray : public ArrayBufferViewClass { class Int8ArrayClass : public TypedArray { Q_OBJECT public: - Int8ArrayClass(ScriptEngine* scriptEngine); + Int8ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -80,7 +80,7 @@ class Int8ArrayClass : public TypedArray { class Uint8ArrayClass : public TypedArray { Q_OBJECT public: - Uint8ArrayClass(ScriptEngine* scriptEngine); + Uint8ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -89,7 +89,7 @@ class Uint8ArrayClass : public TypedArray { class Uint8ClampedArrayClass : public TypedArray { Q_OBJECT public: - Uint8ClampedArrayClass(ScriptEngine* scriptEngine); + Uint8ClampedArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -98,7 +98,7 @@ class Uint8ClampedArrayClass : public TypedArray { class Int16ArrayClass : public TypedArray { Q_OBJECT public: - Int16ArrayClass(ScriptEngine* scriptEngine); + Int16ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -107,7 +107,7 @@ class Int16ArrayClass : public TypedArray { class Uint16ArrayClass : public TypedArray { Q_OBJECT public: - Uint16ArrayClass(ScriptEngine* scriptEngine); + Uint16ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -116,7 +116,7 @@ class Uint16ArrayClass : public TypedArray { class Int32ArrayClass : public TypedArray { Q_OBJECT public: - Int32ArrayClass(ScriptEngine* scriptEngine); + Int32ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -125,7 +125,7 @@ class Int32ArrayClass : public TypedArray { class Uint32ArrayClass : public TypedArray { Q_OBJECT public: - Uint32ArrayClass(ScriptEngine* scriptEngine); + Uint32ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -134,7 +134,7 @@ class Uint32ArrayClass : public TypedArray { class Float32ArrayClass : public TypedArray { Q_OBJECT public: - Float32ArrayClass(ScriptEngine* scriptEngine); + Float32ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; @@ -143,7 +143,7 @@ class Float32ArrayClass : public TypedArray { class Float64ArrayClass : public TypedArray { Q_OBJECT public: - Float64ArrayClass(ScriptEngine* scriptEngine); + Float64ArrayClass(ScriptEngineQtScript* scriptEngine); QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 59fb4d81abe..7e3be4c2292 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -3,7 +3,7 @@ set(TARGET_NAME shared) include_directories("${QT_DIR}/include/QtCore/${QT_VERSION}/QtCore" "${QT_DIR}/include/QtCore/${QT_VERSION}") # TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp) -setup_hifi_library(Gui Network Script) +setup_hifi_library(Gui Network) if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) diff --git a/libraries/shared/src/BaseScriptEngine.cpp b/libraries/shared/src/BaseScriptEngine.cpp deleted file mode 100644 index 22ae01d72f0..00000000000 --- a/libraries/shared/src/BaseScriptEngine.cpp +++ /dev/null @@ -1,367 +0,0 @@ -// -// BaseScriptEngine.cpp -// libraries/script-engine/src -// -// Created by Timothy Dedischew on 02/01/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "BaseScriptEngine.h" -#include "SharedLogging.h" - -#include -#include -#include -#include -#include -#include - -#include "Profile.h" - -const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" }; -const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " }; - -bool BaseScriptEngine::IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method) { - if (QThread::currentThread() == thread) { - return true; - } - qCCritical(shared) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") - .arg(method).arg(thread ? thread->objectName() : "(!thread)").arg(QThread::currentThread()->objectName()); - qCDebug(shared) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; - Q_ASSERT(false); - return false; -} - -// engine-aware JS Error copier and factory -QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - auto other = _other; - if (other.isString()) { - other = newObject(); - other.setProperty("message", _other.toString()); - } - auto proto = globalObject().property(type); - if (!proto.isFunction()) { - proto = globalObject().property(other.prototype().property("constructor").property("name").toString()); - } - if (!proto.isFunction()) { -#ifdef DEBUG_JS_EXCEPTIONS - qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead"; -#endif - proto = globalObject().property("Error"); - } - if (other.engine() != this) { - // JS Objects are parented to a specific script engine instance - // -- this effectively ~clones it locally by routing through a QVariant and back - other = toScriptValue(other.toVariant()); - } - // ~ var err = new Error(other.message) - auto err = proto.construct(QScriptValueList({other.property("message")})); - - // transfer over any existing properties - QScriptValueIterator it(other); - while (it.hasNext()) { - it.next(); - err.setProperty(it.name(), it.value()); - } - return err; -} - -// check syntax and when there are issues returns an actual "SyntaxError" with the details -QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - const auto syntaxCheck = checkSyntax(sourceCode); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - auto err = globalObject().property("SyntaxError") - .construct(QScriptValueList({syntaxCheck.errorMessage()})); - err.setProperty("fileName", fileName); - err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); - err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); - err.setProperty("stack", currentContext()->backtrace().join(SCRIPT_BACKTRACE_SEP)); - { - const auto error = syntaxCheck.errorMessage(); - const auto line = QString::number(syntaxCheck.errorLineNumber()); - const auto column = QString::number(syntaxCheck.errorColumnNumber()); - // for compatibility with legacy reporting - const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); - err.setProperty("formatted", message); - } - return err; - } - return QScriptValue(); -} - -// this pulls from the best available information to create a detailed snapshot of the current exception -QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - if (!hasUncaughtException()) { - return unboundNullValue(); - } - auto exception = uncaughtException(); - // ensure the error object is engine-local - auto err = makeError(exception); - - // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number - // on its own is often useless/wrong if arbitrarily married to a filename. - // when the error object already has this info, it seems to be the most reliable - auto fileName = exception.property("fileName").toString(); - auto lineNumber = exception.property("lineNumber").toInt32(); - - // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace - auto backtrace = uncaughtExceptionBacktrace(); - if (backtrace.isEmpty()) { - // fallback to the error object - backtrace = exception.property("stack").toString().split(SCRIPT_BACKTRACE_SEP); - } - // the ad hoc "detail" property can be used now to embed additional clues - auto detail = exception.property("detail").toString(); - if (detail.isEmpty()) { - detail = extraDetail; - } else if (!extraDetail.isEmpty()) { - detail += "(" + extraDetail + ")"; - } - if (lineNumber <= 0) { - lineNumber = uncaughtExceptionLineNumber(); - } - if (fileName.isEmpty()) { - // climb the stack frames looking for something useful to display - for (auto c = currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { - QScriptContextInfo info { c }; - if (!info.fileName().isEmpty()) { - // take fileName:lineNumber as a pair - fileName = info.fileName(); - lineNumber = info.lineNumber(); - if (backtrace.isEmpty()) { - backtrace = c->backtrace(); - } - break; - } - } - } - err.setProperty("fileName", fileName); - err.setProperty("lineNumber", lineNumber ); - err.setProperty("detail", detail); - err.setProperty("stack", backtrace.join(SCRIPT_BACKTRACE_SEP)); - -#ifdef DEBUG_JS_EXCEPTIONS - err.setProperty("_fileName", exception.property("fileName").toString()); - err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); - err.setProperty("_lineNumber", uncaughtExceptionLineNumber()); -#endif - return err; -} - -QString BaseScriptEngine::formatException(const QScriptValue& exception, bool includeExtendedDetails) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return QString(); - } - QString note { "UncaughtException" }; - QString result; - - if (!exception.isObject()) { - return result; - } - const auto message = exception.toString(); - const auto fileName = exception.property("fileName").toString(); - const auto lineNumber = exception.property("lineNumber").toString(); - const auto stacktrace = exception.property("stack").toString(); - - if (includeExtendedDetails) { - // Display additional exception / troubleshooting hints that can be added via the custom Error .detail property - // Example difference: - // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n... - // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n... - if (exception.property("detail").isValid()) { - note += " " + exception.property("detail").toString(); - } - } - - result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); - if (!stacktrace.isEmpty()) { - result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); - } - return result; -} - -bool BaseScriptEngine::raiseException(const QScriptValue& exception) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return false; - } - if (currentContext()) { - // we have an active context / JS stack frame so throw the exception per usual - currentContext()->throwValue(makeError(exception)); - return true; - } else { - // we are within a pure C++ stack frame (ie: being called directly by other C++ code) - // in this case no context information is available so just emit the exception for reporting - emit unhandledException(makeError(exception)); - } - return false; -} - -bool BaseScriptEngine::maybeEmitUncaughtException(const QString& debugHint) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return false; - } - if (!isEvaluating() && hasUncaughtException()) { - emit unhandledException(cloneUncaughtException(debugHint)); - clearExceptions(); - return true; - } - return false; -} - -QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) { - PROFILE_RANGE(script, "evaluateInClosure"); - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - const auto fileName = program.fileName(); - const auto shortName = QUrl(fileName).fileName(); - - QScriptValue result; - QScriptValue oldGlobal; - auto global = closure.property("global"); - if (global.isObject()) { -#ifdef DEBUG_JS - qCDebug(shared) << " setting global = closure.global" << shortName; -#endif - oldGlobal = globalObject(); - setGlobalObject(global); - } - - auto context = pushContext(); - - auto thiz = closure.property("this"); - if (thiz.isObject()) { -#ifdef DEBUG_JS - qCDebug(shared) << " setting this = closure.this" << shortName; -#endif - context->setThisObject(thiz); - } - - context->pushScope(closure); -#ifdef DEBUG_JS - qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); -#endif - { - result = BaseScriptEngine::evaluate(program); - if (hasUncaughtException()) { - auto err = cloneUncaughtException(__FUNCTION__); -#ifdef DEBUG_JS_EXCEPTIONS - qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); - err.setProperty("_result", result); -#endif - result = err; - } - } -#ifdef DEBUG_JS - qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); -#endif - popContext(); - - if (oldGlobal.isValid()) { -#ifdef DEBUG_JS - qCDebug(shared) << " restoring global" << shortName; -#endif - setGlobalObject(oldGlobal); - } - - return result; -} - -// Lambda -QScriptValue BaseScriptEngine::newLambdaFunction(std::function operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) { - auto lambda = new Lambda(this, operation, data); - auto object = newQObject(lambda, ownership); - auto call = object.property("call"); - call.setPrototype(object); // context->callee().prototype() === Lambda QObject - call.setData(data); // context->callee().data() will === data param - return call; -} -QString Lambda::toString() const { - return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString()); -} - -Lambda::~Lambda() { -#ifdef DEBUG_JS_LAMBDA_FUNCS - qDebug() << "~Lambda" << "this" << this; -#endif -} - -Lambda::Lambda(QScriptEngine *engine, std::function operation, QScriptValue data) - : engine(engine), operation(operation), data(data) { -#ifdef DEBUG_JS_LAMBDA_FUNCS - qDebug() << "Lambda" << data.toString(); -#endif -} -QScriptValue Lambda::call() { - if (!BaseScriptEngine::IS_THREADSAFE_INVOCATION(engine->thread(), __FUNCTION__)) { - return BaseScriptEngine::unboundNullValue(); - } - return operation(engine->currentContext(), engine); -} - -QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto engine = scopeOrCallback.engine(); - if (!engine) { - return scopeOrCallback; - } - auto scope = QScriptValue(); - auto callback = scopeOrCallback; - if (scopeOrCallback.isObject()) { - if (methodOrName.isString()) { - scope = scopeOrCallback; - callback = scope.property(methodOrName.toString()); - } else if (methodOrName.isFunction()) { - scope = scopeOrCallback; - callback = methodOrName; - } else if (!methodOrName.isValid()) { - // instantiate from an existing scoped handler object - if (scopeOrCallback.property("callback").isFunction()) { - scope = scopeOrCallback.property("scope"); - callback = scopeOrCallback.property("callback"); - } - } - } - auto handler = engine->newObject(); - handler.setProperty("scope", scope); - handler.setProperty("callback", callback); - return handler; -} - -QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) { - return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result })); -} - -#ifdef DEBUG_JS -void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return; - } - if (!header.isEmpty()) { - qCDebug(shared) << header; - } - if (!object.isObject()) { - qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); - return; - } - QScriptValueIterator it(object); - while (it.hasNext()) { - it.next(); - qCDebug(shared) << it.name() << ":" << it.value().toString(); - } - if (!footer.isEmpty()) { - qCDebug(shared) << footer; - } -} -#endif diff --git a/libraries/shared/src/BaseScriptEngine.h b/libraries/shared/src/BaseScriptEngine.h deleted file mode 100644 index 73914dc6899..00000000000 --- a/libraries/shared/src/BaseScriptEngine.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// BaseScriptEngine.h -// libraries/script-engine/src -// -// Created by Timothy Dedischew on 02/01/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_BaseScriptEngine_h -#define hifi_BaseScriptEngine_h - -#include -#include -#include -#include - -class ScriptEngine; -using ScriptEnginePointer = QSharedPointer; - -// common base class for extending QScriptEngine itself -class BaseScriptEngine : public QScriptEngine, public QEnableSharedFromThis { - Q_OBJECT -public: - static const QString SCRIPT_EXCEPTION_FORMAT; - static const QString SCRIPT_BACKTRACE_SEP; - - // threadsafe "unbound" version of QScriptEngine::nullValue() - static const QScriptValue unboundNullValue() { return QScriptValue(0, QScriptValue::NullValue); } - - BaseScriptEngine() {} - - /*@jsdoc - * @function Script.lintScript - * @param {string} sourceCode - Source code. - * @param {string} fileName - File name. - * @param {number} [lineNumber=1] - Line number. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1); - - /*@jsdoc - * @function Script.makeError - * @param {object} [other] - Other. - * @param {string} [type="Error"] - Error. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); - - /*@jsdoc - * @function Script.formatExecption - * @param {object} exception - Exception. - * @param {boolean} inludeExtendeDetails - Include extended details. - * @returns {string} String. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QString formatException(const QScriptValue& exception, bool includeExtendedDetails); - - QScriptValue cloneUncaughtException(const QString& detail = QString()); - QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); - - // if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it - bool maybeEmitUncaughtException(const QString& debugHint = QString()); - - // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it. - // note: this is used in cases where C++ code might call into JS API methods directly - bool raiseException(const QScriptValue& exception); - - // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways - static bool IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method); -signals: - /*@jsdoc - * @function Script.signalHandlerException - * @param {object} exception - Exception. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - // Script.signalHandlerException is exposed by QScriptEngine. - - /*@jsdoc - * Triggered when a script generates an unhandled exception. - * @function Script.unhandledException - * @param {object} exception - The details of the exception. - * @returns {Signal} - * @example Report the details of an unhandled exception. - * Script.unhandledException.connect(function (exception) { - * print("Unhandled exception: " + JSON.stringify(exception)); - * }); - * var properties = JSON.parse("{ x: 1"); // Invalid JSON string. - */ - void unhandledException(const QScriptValue& exception); - -protected: - // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues - // even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction` - // anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming - // permanent more easily promoted into regular static newFunction scenarios. - QScriptValue newLambdaFunction(std::function operation, const QScriptValue& data = QScriptValue(), const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership); - -#ifdef DEBUG_JS - static void _debugDump(const QString& header, const QScriptValue& object, const QString& footer = QString()); -#endif -}; - -// Standardized CPS callback helpers (see: http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/) -// These two helpers allow async JS APIs that use a callback parameter to be more friendly to scripters by accepting thisObject -// context and adopting a consistent and intuitable callback signature: -// function callback(err, result) { if (err) { ... } else { /* do stuff with result */ } } -// -// To use, first pass the user-specified callback args in the same order used with optionally-scoped Qt signal connections: -// auto handler = makeScopedHandlerObject(scopeOrCallback, optionalMethodOrName); -// And then invoke the scoped handler later per CPS conventions: -// auto result = callScopedHandlerObject(handler, err, result); -QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName); -QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result); - -// Lambda helps create callable QScriptValues out of std::functions: -// (just meant for use from within the script engine itself) -class Lambda : public QObject { - Q_OBJECT -public: - Lambda(QScriptEngine *engine, std::function operation, QScriptValue data); - ~Lambda(); - public slots: - QScriptValue call(); - QString toString() const; -private: - QScriptEngine* engine; - std::function operation; - QScriptValue data; -}; - -#endif // hifi_BaseScriptEngine_h diff --git a/libraries/shared/src/EntityItemID.cpp b/libraries/shared/src/EntityItemID.cpp new file mode 100644 index 00000000000..8cb34c13db8 --- /dev/null +++ b/libraries/shared/src/EntityItemID.cpp @@ -0,0 +1,42 @@ +// +// EntityItemID.cpp +// libraries/shared/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntityItemID.h" +#include +#include + +#include "BufferParser.h" +#include "UUID.h" + +int entityItemIDTypeID = qRegisterMetaType(); + +EntityItemID::EntityItemID() : QUuid() +{ +} + + +EntityItemID::EntityItemID(const QUuid& id) : QUuid(id) +{ +} + +// EntityItemID::EntityItemID(const EntityItemID& other) : QUuid(other) +// { +// } + +EntityItemID EntityItemID::readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead) { + EntityItemID result; + if (bytesLeftToRead >= NUM_BYTES_RFC4122_UUID) { + BufferParser(data, bytesLeftToRead).readUuid(result); + } + return result; +} + +size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } diff --git a/libraries/entities/src/EntityItemID.h b/libraries/shared/src/EntityItemID.h similarity index 76% rename from libraries/entities/src/EntityItemID.h rename to libraries/shared/src/EntityItemID.h index c9ffa13941c..51ea1871ae3 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/shared/src/EntityItemID.h @@ -1,6 +1,6 @@ // // EntityItemID.h -// libraries/entities/src +// libraries/shared/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include const QUuid UNKNOWN_ENTITY_ID; // null uuid @@ -29,7 +29,6 @@ class EntityItemID : public QUuid { EntityItemID(const QUuid& id); // EntityItemID(const EntityItemID& other); static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead); - QScriptValue toScriptValue(QScriptEngine* engine) const; bool isInvalidID() const { return *this == UNKNOWN_ENTITY_ID; } }; @@ -41,9 +40,6 @@ inline QDebug operator<<(QDebug debug, const EntityItemID& id) { Q_DECLARE_METATYPE(EntityItemID); Q_DECLARE_METATYPE(QVector); -QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties); -void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties); -QVector qVectorEntityItemIDFromScriptValue(const QScriptValue& array); // Allow the use of std::unordered_map with QUuid keys namespace std { template<> struct hash { size_t operator()(const EntityItemID& id) const; }; } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index c8b03694eba..d05d21afac3 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include int uint32MetaTypeId = qRegisterMetaType("uint32"); @@ -46,81 +44,6 @@ int voidLambdaType = qRegisterMetaType>(); int variantLambdaType = qRegisterMetaType>(); int stencilModeMetaTypeId = qRegisterMetaType(); -void registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue); - qScriptRegisterMetaType(engine, vec3ToScriptValue, vec3FromScriptValue); - qScriptRegisterMetaType(engine, u8vec3ToScriptValue, u8vec3FromScriptValue); - qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue); - qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); - qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); - - qScriptRegisterMetaType(engine, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue); - qScriptRegisterMetaType(engine, qVectorQuatToScriptValue, qVectorQuatFromScriptValue); - qScriptRegisterMetaType(engine, qVectorBoolToScriptValue, qVectorBoolFromScriptValue); - qScriptRegisterMetaType(engine, qVectorFloatToScriptValue, qVectorFloatFromScriptValue); - qScriptRegisterMetaType(engine, qVectorIntToScriptValue, qVectorIntFromScriptValue); - qScriptRegisterMetaType(engine, qVectorQUuidToScriptValue, qVectorQUuidFromScriptValue); - - qScriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue); - qScriptRegisterMetaType(engine, qRectToScriptValue, qRectFromScriptValue); - qScriptRegisterMetaType(engine, qURLToScriptValue, qURLFromScriptValue); - qScriptRegisterMetaType(engine, qColorToScriptValue, qColorFromScriptValue); - - qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); - qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); - qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); - qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue); - - qScriptRegisterMetaType(engine, stencilMaskModeToScriptValue, stencilMaskModeFromScriptValue); - - qScriptRegisterSequenceMetaType>(engine); -} - -QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) { - auto prototype = engine->globalObject().property("__hifi_vec2__"); - if (!prototype.property("defined").toBool()) { - prototype = engine->evaluate( - "__hifi_vec2__ = Object.defineProperties({}, { " - "defined: { value: true }," - "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "u: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "v: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }" - "})" - ); - } - QScriptValue value = engine->newObject(); - value.setProperty("x", vec2.x); - value.setProperty("y", vec2.y); - value.setPrototype(prototype); - return value; -} - -void vec2FromScriptValue(const QScriptValue& object, glm::vec2& vec2) { - if (object.isNumber()) { - vec2 = glm::vec2(object.toVariant().toFloat()); - } else if (object.isArray()) { - QVariantList list = object.toVariant().toList(); - if (list.length() == 2) { - vec2.x = list[0].toFloat(); - vec2.y = list[1].toFloat(); - } - } else { - QScriptValue x = object.property("x"); - if (!x.isValid()) { - x = object.property("u"); - } - - QScriptValue y = object.property("y"); - if (!y.isValid()) { - y = object.property("v"); - } - - vec2.x = x.toVariant().toFloat(); - vec2.y = y.toVariant().toFloat(); - } -} - QVariant vec2ToVariant(const glm::vec2 &vec2) { if (vec2.x != vec2.x || vec2.y != vec2.y) { // if vec2 contains a NaN don't try to convert it @@ -170,206 +93,6 @@ glm::vec2 vec2FromVariant(const QVariant &object) { return vec2FromVariant(object, valid); } -QScriptValue vec3ToScriptValue(QScriptEngine* engine, const glm::vec3& vec3) { - auto prototype = engine->globalObject().property("__hifi_vec3__"); - if (!prototype.property("defined").toBool()) { - prototype = engine->evaluate( - "__hifi_vec3__ = Object.defineProperties({}, { " - "defined: { value: true }," - "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "2: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," - "r: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "g: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "b: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," - "red: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "green: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "blue: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }" - "})" - ); - } - QScriptValue value = engine->newObject(); - value.setProperty("x", vec3.x); - value.setProperty("y", vec3.y); - value.setProperty("z", vec3.z); - value.setPrototype(prototype); - return value; -} - -QScriptValue vec3ColorToScriptValue(QScriptEngine* engine, const glm::vec3& vec3) { - auto prototype = engine->globalObject().property("__hifi_vec3_color__"); - if (!prototype.property("defined").toBool()) { - prototype = engine->evaluate( - "__hifi_vec3_color__ = Object.defineProperties({}, { " - "defined: { value: true }," - "0: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "1: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "2: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," - "r: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "g: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "b: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," - "x: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "y: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "z: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }" - "})" - ); - } - QScriptValue value = engine->newObject(); - value.setProperty("red", vec3.x); - value.setProperty("green", vec3.y); - value.setProperty("blue", vec3.z); - value.setPrototype(prototype); - return value; -} - -void vec3FromScriptValue(const QScriptValue& object, glm::vec3& vec3) { - if (object.isNumber()) { - vec3 = glm::vec3(object.toVariant().toFloat()); - } else if (object.isString()) { - QColor qColor(object.toString()); - if (qColor.isValid()) { - vec3.x = qColor.red(); - vec3.y = qColor.green(); - vec3.z = qColor.blue(); - } - } else if (object.isArray()) { - QVariantList list = object.toVariant().toList(); - if (list.length() == 3) { - vec3.x = list[0].toFloat(); - vec3.y = list[1].toFloat(); - vec3.z = list[2].toFloat(); - } - } else { - QScriptValue x = object.property("x"); - if (!x.isValid()) { - x = object.property("r"); - } - if (!x.isValid()) { - x = object.property("red"); - } - - QScriptValue y = object.property("y"); - if (!y.isValid()) { - y = object.property("g"); - } - if (!y.isValid()) { - y = object.property("green"); - } - - QScriptValue z = object.property("z"); - if (!z.isValid()) { - z = object.property("b"); - } - if (!z.isValid()) { - z = object.property("blue"); - } - - vec3.x = x.toVariant().toFloat(); - vec3.y = y.toVariant().toFloat(); - vec3.z = z.toVariant().toFloat(); - } -} - -QScriptValue u8vec3ToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3) { - auto prototype = engine->globalObject().property("__hifi_u8vec3__"); - if (!prototype.property("defined").toBool()) { - prototype = engine->evaluate( - "__hifi_u8vec3__ = Object.defineProperties({}, { " - "defined: { value: true }," - "0: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "1: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "2: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," - "r: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "g: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "b: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }," - "red: { set: function(nv) { return this.x = nv; }, get: function() { return this.x; } }," - "green: { set: function(nv) { return this.y = nv; }, get: function() { return this.y; } }," - "blue: { set: function(nv) { return this.z = nv; }, get: function() { return this.z; } }" - "})" - ); - } - QScriptValue value = engine->newObject(); - value.setProperty("x", vec3.x); - value.setProperty("y", vec3.y); - value.setProperty("z", vec3.z); - value.setPrototype(prototype); - return value; -} - -QScriptValue u8vec3ColorToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3) { - auto prototype = engine->globalObject().property("__hifi_u8vec3_color__"); - if (!prototype.property("defined").toBool()) { - prototype = engine->evaluate( - "__hifi_u8vec3_color__ = Object.defineProperties({}, { " - "defined: { value: true }," - "0: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "1: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "2: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," - "r: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "g: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "b: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }," - "x: { set: function(nv) { return this.red = nv; }, get: function() { return this.red; } }," - "y: { set: function(nv) { return this.green = nv; }, get: function() { return this.green; } }," - "z: { set: function(nv) { return this.blue = nv; }, get: function() { return this.blue; } }" - "})" - ); - } - QScriptValue value = engine->newObject(); - value.setProperty("red", vec3.x); - value.setProperty("green", vec3.y); - value.setProperty("blue", vec3.z); - value.setPrototype(prototype); - return value; -} - -void u8vec3FromScriptValue(const QScriptValue& object, glm::u8vec3& vec3) { - if (object.isNumber()) { - vec3 = glm::vec3(object.toVariant().toUInt()); - } else if (object.isString()) { - QColor qColor(object.toString()); - if (qColor.isValid()) { - vec3.x = (uint8_t)qColor.red(); - vec3.y = (uint8_t)qColor.green(); - vec3.z = (uint8_t)qColor.blue(); - } - } else if (object.isArray()) { - QVariantList list = object.toVariant().toList(); - if (list.length() == 3) { - vec3.x = list[0].toUInt(); - vec3.y = list[1].toUInt(); - vec3.z = list[2].toUInt(); - } - } else { - QScriptValue x = object.property("x"); - if (!x.isValid()) { - x = object.property("r"); - } - if (!x.isValid()) { - x = object.property("red"); - } - - QScriptValue y = object.property("y"); - if (!y.isValid()) { - y = object.property("g"); - } - if (!y.isValid()) { - y = object.property("green"); - } - - QScriptValue z = object.property("z"); - if (!z.isValid()) { - z = object.property("b"); - } - if (!z.isValid()) { - z = object.property("blue"); - } - - vec3.x = x.toVariant().toUInt(); - vec3.y = y.toVariant().toUInt(); - vec3.z = z.toVariant().toUInt(); - } -} - QVariant vec3toVariant(const glm::vec3& vec3) { if (vec3.x != vec3.x || vec3.y != vec3.y || vec3.z != vec3.z) { // if vec3 contains a NaN don't try to convert it @@ -540,22 +263,6 @@ glm::u8vec3 u8vec3FromVariant(const QVariant& object) { return u8vec3FromVariant(object, valid); } -QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4) { - QScriptValue obj = engine->newObject(); - obj.setProperty("x", vec4.x); - obj.setProperty("y", vec4.y); - obj.setProperty("z", vec4.z); - obj.setProperty("w", vec4.w); - return obj; -} - -void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4) { - vec4.x = object.property("x").toVariant().toFloat(); - vec4.y = object.property("y").toVariant().toFloat(); - vec4.z = object.property("z").toVariant().toFloat(); - vec4.w = object.property("w").toVariant().toFloat(); -} - QVariant vec4toVariant(const glm::vec4& vec4) { if (isNaN(vec4.x) || isNaN(vec4.y) || isNaN(vec4.z) || isNaN(vec4.w)) { // if vec4 contains a NaN don't try to convert it @@ -606,46 +313,6 @@ glm::vec4 vec4FromVariant(const QVariant& object) { return vec4FromVariant(object, valid); } -QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4) { - QScriptValue obj = engine->newObject(); - obj.setProperty("r0c0", mat4[0][0]); - obj.setProperty("r1c0", mat4[0][1]); - obj.setProperty("r2c0", mat4[0][2]); - obj.setProperty("r3c0", mat4[0][3]); - obj.setProperty("r0c1", mat4[1][0]); - obj.setProperty("r1c1", mat4[1][1]); - obj.setProperty("r2c1", mat4[1][2]); - obj.setProperty("r3c1", mat4[1][3]); - obj.setProperty("r0c2", mat4[2][0]); - obj.setProperty("r1c2", mat4[2][1]); - obj.setProperty("r2c2", mat4[2][2]); - obj.setProperty("r3c2", mat4[2][3]); - obj.setProperty("r0c3", mat4[3][0]); - obj.setProperty("r1c3", mat4[3][1]); - obj.setProperty("r2c3", mat4[3][2]); - obj.setProperty("r3c3", mat4[3][3]); - return obj; -} - -void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4) { - mat4[0][0] = object.property("r0c0").toVariant().toFloat(); - mat4[0][1] = object.property("r1c0").toVariant().toFloat(); - mat4[0][2] = object.property("r2c0").toVariant().toFloat(); - mat4[0][3] = object.property("r3c0").toVariant().toFloat(); - mat4[1][0] = object.property("r0c1").toVariant().toFloat(); - mat4[1][1] = object.property("r1c1").toVariant().toFloat(); - mat4[1][2] = object.property("r2c1").toVariant().toFloat(); - mat4[1][3] = object.property("r3c1").toVariant().toFloat(); - mat4[2][0] = object.property("r0c2").toVariant().toFloat(); - mat4[2][1] = object.property("r1c2").toVariant().toFloat(); - mat4[2][2] = object.property("r2c2").toVariant().toFloat(); - mat4[2][3] = object.property("r3c2").toVariant().toFloat(); - mat4[3][0] = object.property("r0c3").toVariant().toFloat(); - mat4[3][1] = object.property("r1c3").toVariant().toFloat(); - mat4[3][2] = object.property("r2c3").toVariant().toFloat(); - mat4[3][3] = object.property("r3c3").toVariant().toFloat(); -} - QVariant mat4ToVariant(const glm::mat4& mat4) { if (mat4 != mat4) { // NaN @@ -721,72 +388,6 @@ glm::mat4 mat4FromVariant(const QVariant& object) { return mat4FromVariant(object, valid); } -QScriptValue qVectorVec3ColorToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, vec3ColorToScriptValue(engine, vector.at(i))); - } - return array; -} - -QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, vec3ToScriptValue(engine, vector.at(i))); - } - return array; -} - -QVector qVectorVec3FromScriptValue(const QScriptValue& array) { - QVector newVector; - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - glm::vec3 newVec3 = glm::vec3(); - vec3FromScriptValue(array.property(i), newVec3); - newVector << newVec3; - } - return newVector; -} - -void qVectorVec3FromScriptValue(const QScriptValue& array, QVector& vector) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - glm::vec3 newVec3 = glm::vec3(); - vec3FromScriptValue(array.property(i), newVec3); - vector << newVec3; - } -} - -QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat) { - QScriptValue obj = engine->newObject(); - if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) { - // if quat contains a NaN don't try to convert it - return obj; - } - obj.setProperty("x", quat.x); - obj.setProperty("y", quat.y); - obj.setProperty("z", quat.z); - obj.setProperty("w", quat.w); - return obj; -} - -void quatFromScriptValue(const QScriptValue& object, glm::quat &quat) { - quat.x = object.property("x").toVariant().toFloat(); - quat.y = object.property("y").toVariant().toFloat(); - quat.z = object.property("z").toVariant().toFloat(); - quat.w = object.property("w").toVariant().toFloat(); - - // enforce normalized quaternion - float length = glm::length(quat); - if (length > FLT_EPSILON) { - quat /= length; - } else { - quat = glm::quat(); - } -} - glm::quat quatFromVariant(const QVariant &object, bool& isValid) { glm::quat q; if (object.canConvert()) { @@ -844,159 +445,6 @@ QVariant quatToVariant(const glm::quat& quat) { return result; } -QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, quatToScriptValue(engine, vector.at(i))); - } - return array; -} - -QScriptValue qVectorBoolToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, vector.at(i)); - } - return array; -} - -QVector qVectorFloatFromScriptValue(const QScriptValue& array) { - if(!array.isArray()) { - return QVector(); - } - QVector newVector; - int length = array.property("length").toInteger(); - newVector.reserve(length); - for (int i = 0; i < length; i++) { - if(array.property(i).isNumber()) { - newVector << array.property(i).toNumber(); - } - } - - return newVector; -} - -QScriptValue qVectorQUuidToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, quuidToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorQUuidFromScriptValue(const QScriptValue& array, QVector& vector) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - vector << array.property(i).toVariant().toUuid(); - } -} - -QVector qVectorQUuidFromScriptValue(const QScriptValue& array) { - if (!array.isArray()) { - return QVector(); - } - QVector newVector; - int length = array.property("length").toInteger(); - newVector.reserve(length); - for (int i = 0; i < length; i++) { - QString uuidAsString = array.property(i).toString(); - QUuid fromString(uuidAsString); - newVector << fromString; - } - return newVector; -} - -QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - float num = vector.at(i); - array.setProperty(i, QScriptValue(num)); - } - return array; -} - -QScriptValue qVectorIntToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - int num = vector.at(i); - array.setProperty(i, QScriptValue(num)); - } - return array; -} - -void qVectorFloatFromScriptValue(const QScriptValue& array, QVector& vector) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - vector << array.property(i).toVariant().toFloat(); - } -} - -void qVectorIntFromScriptValue(const QScriptValue& array, QVector& vector) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - vector << array.property(i).toVariant().toInt(); - } -} - -QVector qVectorQuatFromScriptValue(const QScriptValue& array){ - QVector newVector; - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - glm::quat newQuat = glm::quat(); - quatFromScriptValue(array.property(i), newQuat); - newVector << newQuat; - } - return newVector; -} - -void qVectorQuatFromScriptValue(const QScriptValue& array, QVector& vector ) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - glm::quat newQuat = glm::quat(); - quatFromScriptValue(array.property(i), newQuat); - vector << newQuat; - } -} - -QVector qVectorBoolFromScriptValue(const QScriptValue& array){ - QVector newVector; - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - newVector << array.property(i).toBool(); - } - return newVector; -} - -void qVectorBoolFromScriptValue(const QScriptValue& array, QVector& vector ) { - int length = array.property("length").toInteger(); - - for (int i = 0; i < length; i++) { - vector << array.property(i).toBool(); - } -} - -QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect) { - QScriptValue obj = engine->newObject(); - obj.setProperty("x", rect.x()); - obj.setProperty("y", rect.y()); - obj.setProperty("width", rect.width()); - obj.setProperty("height", rect.height()); - return obj; -} - -void qRectFromScriptValue(const QScriptValue &object, QRect& rect) { - rect.setX(object.property("x").toVariant().toInt()); - rect.setY(object.property("y").toVariant().toInt()); - rect.setWidth(object.property("width").toVariant().toInt()); - rect.setHeight(object.property("height").toVariant().toInt()); -} - QVariant qRectToVariant(const QRect& rect) { QVariantMap obj; obj["x"] = rect.x(); @@ -1028,22 +476,6 @@ QRect qRectFromVariant(const QVariant& object) { return qRectFromVariant(object, valid); } -QScriptValue qRectFToScriptValue(QScriptEngine* engine, const QRectF& rect) { - QScriptValue obj = engine->newObject(); - obj.setProperty("x", rect.x()); - obj.setProperty("y", rect.y()); - obj.setProperty("width", rect.width()); - obj.setProperty("height", rect.height()); - return obj; -} - -void qRectFFromScriptValue(const QScriptValue &object, QRectF& rect) { - rect.setX(object.property("x").toVariant().toFloat()); - rect.setY(object.property("y").toVariant().toFloat()); - rect.setWidth(object.property("width").toVariant().toFloat()); - rect.setHeight(object.property("height").toVariant().toFloat()); -} - QVariant qRectFToVariant(const QRectF& rect) { QVariantMap obj; obj["x"] = rect.x(); @@ -1075,100 +507,6 @@ QRectF qRectFFromVariant(const QVariant& object) { return qRectFFromVariant(object, valid); } -QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { - QScriptValue object = engine->newObject(); - object.setProperty("red", color.red()); - object.setProperty("green", color.green()); - object.setProperty("blue", color.blue()); - object.setProperty("alpha", color.alpha()); - return object; -} - -/*@jsdoc - * An axis-aligned cube, defined as the bottom right near (minimum axes values) corner of the cube plus the dimension of its - * sides. - * @typedef {object} AACube - * @property {number} x - X coordinate of the brn corner of the cube. - * @property {number} y - Y coordinate of the brn corner of the cube. - * @property {number} z - Z coordinate of the brn corner of the cube. - * @property {number} scale - The dimensions of each side of the cube. - */ -QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube) { - QScriptValue obj = engine->newObject(); - const glm::vec3& corner = aaCube.getCorner(); - obj.setProperty("x", corner.x); - obj.setProperty("y", corner.y); - obj.setProperty("z", corner.z); - obj.setProperty("scale", aaCube.getScale()); - return obj; -} - -void aaCubeFromScriptValue(const QScriptValue &object, AACube& aaCube) { - glm::vec3 corner; - corner.x = object.property("x").toVariant().toFloat(); - corner.y = object.property("y").toVariant().toFloat(); - corner.z = object.property("z").toVariant().toFloat(); - float scale = object.property("scale").toVariant().toFloat(); - - aaCube.setBox(corner, scale); -} - -void qColorFromScriptValue(const QScriptValue& object, QColor& color) { - if (object.isNumber()) { - color.setRgb(object.toUInt32()); - - } else if (object.isString()) { - color.setNamedColor(object.toString()); - - } else { - QScriptValue alphaValue = object.property("alpha"); - color.setRgb(object.property("red").toInt32(), object.property("green").toInt32(), object.property("blue").toInt32(), - alphaValue.isNumber() ? alphaValue.toInt32() : 255); - } -} - -QScriptValue qURLToScriptValue(QScriptEngine* engine, const QUrl& url) { - return url.toString(); -} - -void qURLFromScriptValue(const QScriptValue& object, QUrl& url) { - url = object.toString(); -} - -QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay) { - QScriptValue obj = engine->newObject(); - QScriptValue origin = vec3ToScriptValue(engine, pickRay.origin); - obj.setProperty("origin", origin); - QScriptValue direction = vec3ToScriptValue(engine, pickRay.direction); - obj.setProperty("direction", direction); - return obj; -} - -void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { - QScriptValue originValue = object.property("origin"); - if (originValue.isValid()) { - auto x = originValue.property("x"); - auto y = originValue.property("y"); - auto z = originValue.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - pickRay.origin.x = x.toVariant().toFloat(); - pickRay.origin.y = y.toVariant().toFloat(); - pickRay.origin.z = z.toVariant().toFloat(); - } - } - QScriptValue directionValue = object.property("direction"); - if (directionValue.isValid()) { - auto x = directionValue.property("x"); - auto y = directionValue.property("y"); - auto z = directionValue.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - pickRay.direction.x = x.toVariant().toFloat(); - pickRay.direction.y = y.toVariant().toFloat(); - pickRay.direction.z = z.toVariant().toFloat(); - } - } -} - /*@jsdoc * Details of a collision between avatars and entities. * @typedef {object} Collision @@ -1179,63 +517,12 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { * @property {Vec3} contactPoint - The point of contact. * @property {Vec3} velocityChange - The change in relative velocity of the two items, in m/s. */ -QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { - QScriptValue obj = engine->newObject(); - obj.setProperty("type", collision.type); - obj.setProperty("idA", quuidToScriptValue(engine, collision.idA)); - obj.setProperty("idB", quuidToScriptValue(engine, collision.idB)); - obj.setProperty("penetration", vec3ToScriptValue(engine, collision.penetration)); - obj.setProperty("contactPoint", vec3ToScriptValue(engine, collision.contactPoint)); - obj.setProperty("velocityChange", vec3ToScriptValue(engine, collision.velocityChange)); - return obj; -} - -void collisionFromScriptValue(const QScriptValue &object, Collision& collision) { - // TODO: implement this when we know what it means to accept collision events from JS -} - void Collision::invert() { std::swap(idA, idB); contactPoint += penetration; penetration *= -1.0f; } -QScriptValue quuidToScriptValue(QScriptEngine* engine, const QUuid& uuid) { - if (uuid.isNull()) { - return QScriptValue::NullValue; - } - QScriptValue obj(uuid.toString()); - return obj; -} - -void quuidFromScriptValue(const QScriptValue& object, QUuid& uuid) { - if (object.isNull()) { - uuid = QUuid(); - return; - } - QString uuidAsString = object.toVariant().toString(); - QUuid fromString(uuidAsString); - uuid = fromString; -} - -/*@jsdoc - * A 2D size value. - * @typedef {object} Size - * @property {number} height - The height value. - * @property {number} width - The width value. - */ -QScriptValue qSizeFToScriptValue(QScriptEngine* engine, const QSizeF& qSizeF) { - QScriptValue obj = engine->newObject(); - obj.setProperty("width", qSizeF.width()); - obj.setProperty("height", qSizeF.height()); - return obj; -} - -void qSizeFFromScriptValue(const QScriptValue& object, QSizeF& qSizeF) { - qSizeF.setWidth(object.property("width").toVariant().toFloat()); - qSizeF.setHeight(object.property("height").toVariant().toFloat()); -} - AnimationDetails::AnimationDetails() : role(), url(), fps(0.0f), priority(0.0f), loop(false), hold(false), startAutomatically(false), firstFrame(0.0f), lastFrame(0.0f), running(false), currentFrame(0.0f) { @@ -1248,116 +535,6 @@ AnimationDetails::AnimationDetails(QString role, QUrl url, float fps, float prio running(running), currentFrame(currentFrame), allowTranslation(allowTranslation) { } -/*@jsdoc - * The details of an animation that is playing. - * @typedef {object} Avatar.AnimationDetails - * @property {string} role - Not used. - * @property {string} url - The URL to the animation file. Animation files need to be in glTF or FBX format but only need to - * contain the avatar skeleton and animation data. glTF models may be in JSON or binary format (".gltf" or ".glb" URLs - * respectively). - *

Warning: glTF animations currently do not always animate correctly.

- * @property {number} fps - The frames per second(FPS) rate for the animation playback. 30 FPS is normal speed. - * @property {number} priority - Not used. - * @property {boolean} loop - true if the animation should loop, false if it shouldn't. - * @property {boolean} hold - Not used. - * @property {number} firstFrame - The frame the animation should start at. - * @property {number} lastFrame - The frame the animation should stop at. - * @property {boolean} running - Not used. - * @property {number} currentFrame - The current frame being played. - * @property {boolean} startAutomatically - Not used. - * @property {boolean} allowTranslation - Not used. - */ -QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& details) { - QScriptValue obj = engine->newObject(); - obj.setProperty("role", details.role); - obj.setProperty("url", details.url.toString()); - obj.setProperty("fps", details.fps); - obj.setProperty("priority", details.priority); - obj.setProperty("loop", details.loop); - obj.setProperty("hold", details.hold); - obj.setProperty("startAutomatically", details.startAutomatically); - obj.setProperty("firstFrame", details.firstFrame); - obj.setProperty("lastFrame", details.lastFrame); - obj.setProperty("running", details.running); - obj.setProperty("currentFrame", details.currentFrame); - obj.setProperty("allowTranslation", details.allowTranslation); - return obj; -} - -void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) { - // nothing for now... -} - -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) { - return engine->newQObject(in, QScriptEngine::QtOwnership, - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); -} - -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) { - out = qobject_cast(value.toQObject()); -} - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) { - // QScriptValueList result; - QScriptValue result = engine->newArray(); - int i = 0; - foreach(MeshProxy* const meshProxy, in) { - result.setProperty(i++, meshToScriptValue(engine, meshProxy)); - } - return result; -} - -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) { - QScriptValueIterator itr(value); - - qDebug() << "in meshesFromScriptValue, value.length =" << value.property("length").toInt32(); - - while (itr.hasNext()) { - itr.next(); - MeshProxy* meshProxy = qscriptvalue_cast(itr.value()); - if (meshProxy) { - out.append(meshProxy); - } else { - qDebug() << "null meshProxy"; - } - } -} - - -/*@jsdoc - * A triangle in a mesh. - * @typedef {object} MeshFace - * @property {number[]} vertices - The indexes of the three vertices that make up the face. - */ -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) { - QScriptValue obj = engine->newObject(); - obj.setProperty("vertices", qVectorIntToScriptValue(engine, meshFace.vertexIndices)); - return obj; -} - -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult) { - qVectorIntFromScriptValue(object.property("vertices"), meshFaceResult.vertexIndices); -} - -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector) { - QScriptValue array = engine->newArray(); - for (int i = 0; i < vector.size(); i++) { - array.setProperty(i, meshFaceToScriptValue(engine, vector.at(i))); - } - return array; -} - -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result) { - int length = array.property("length").toInteger(); - result.clear(); - - for (int i = 0; i < length; i++) { - MeshFace meshFace = MeshFace(); - meshFaceFromScriptValue(array.property(i), meshFace); - result << meshFace; - } -} - QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTextures) { // If textures are unset, revert to original textures if (newTextures.isEmpty()) { @@ -1392,11 +569,3 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe return toReturn; } - -QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode) { - return engine->newVariant((int)stencilMode); -} - -void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode) { - stencilMode = StencilMaskMode(object.toVariant().toInt()); -} \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 39245b5a491..e09d2e248ff 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -12,7 +12,6 @@ #ifndef hifi_RegisteredMetaTypes_h #define hifi_RegisteredMetaTypes_h -#include #include #include @@ -43,8 +42,6 @@ Q_DECLARE_METATYPE(AACube) Q_DECLARE_METATYPE(std::function); Q_DECLARE_METATYPE(std::function); -void registerMetaTypes(QScriptEngine* engine); - // Mat4 /*@jsdoc * A 4 x 4 matrix, typically containing a scale, rotation, and translation transform. See also the {@link Mat4(0)|Mat4} object. @@ -67,9 +64,6 @@ void registerMetaTypes(QScriptEngine* engine); * @property {number} r2c3 - Row 2, column 3 value. * @property {number} r3c3 - Row 3, column 3 value. */ -QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4); -void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4); - QVariant mat4ToVariant(const glm::mat4& mat4); glm::mat4 mat4FromVariant(const QVariant& object, bool& valid); glm::mat4 mat4FromVariant(const QVariant& object); @@ -88,9 +82,6 @@ glm::mat4 mat4FromVariant(const QVariant& object); * var color = Entities.getEntityProperties().materialMappingPos; // { x: 0.7, y: 0.7 } * color.v = 0.8; // { x: 0.7, y: 0.8 } */ -QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2); -void vec2FromScriptValue(const QScriptValue& object, glm::vec2& vec2); - QVariant vec2ToVariant(const glm::vec2& vec2); glm::vec2 vec2FromVariant(const QVariant& object, bool& valid); glm::vec2 vec2FromVariant(const QVariant& object); @@ -115,10 +106,6 @@ glm::vec2 vec2FromVariant(const QVariant& object); * Entities.editEntity(, { position: "red"}); // { x: 255, y: 0, z: 0 } * Entities.editEntity(, { position: "#00FF00"}); // { x: 0, y: 255, z: 0 } */ -QScriptValue vec3ToScriptValue(QScriptEngine* engine, const glm::vec3& vec3); -QScriptValue vec3ColorToScriptValue(QScriptEngine* engine, const glm::vec3& vec3); -void vec3FromScriptValue(const QScriptValue& object, glm::vec3& vec3); - QVariant vec3toVariant(const glm::vec3& vec3); glm::vec3 vec3FromVariant(const QVariant &object, bool& valid); glm::vec3 vec3FromVariant(const QVariant &object); @@ -161,10 +148,6 @@ glm::vec3 vec3FromVariant(const QVariant &object); * Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } * Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } */ -QScriptValue u8vec3ToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3); -QScriptValue u8vec3ColorToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3); -void u8vec3FromScriptValue(const QScriptValue& object, glm::u8vec3& vec3); - QVariant u8vec3toVariant(const glm::u8vec3& vec3); QVariant u8vec3ColortoVariant(const glm::u8vec3& vec3); glm::u8vec3 u8vec3FromVariant(const QVariant &object, bool& valid); @@ -179,16 +162,11 @@ glm::u8vec3 u8vec3FromVariant(const QVariant &object); * @property {number} z - Z-coordinate of the vector. * @property {number} w - W-coordinate of the vector. */ -QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4); -void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4); QVariant vec4toVariant(const glm::vec4& vec4); glm::vec4 vec4FromVariant(const QVariant &object, bool& valid); glm::vec4 vec4FromVariant(const QVariant &object); // Quaternions -QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat); -void quatFromScriptValue(const QScriptValue &object, glm::quat& quat); - QVariant quatToVariant(const glm::quat& quat); glm::quat quatFromVariant(const QVariant &object, bool& isValid); glm::quat quatFromVariant(const QVariant &object); @@ -201,59 +179,14 @@ glm::quat quatFromVariant(const QVariant &object); * @property {number} width - Width of the rectangle. * @property {number} height - Height of the rectangle. */ -QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect); -void qRectFromScriptValue(const QScriptValue& object, QRect& rect); QRect qRectFromVariant(const QVariant& object, bool& isValid); QRect qRectFromVariant(const QVariant& object); QVariant qRectToVariant(const QRect& rect); -QScriptValue qRectFToScriptValue(QScriptEngine* engine, const QRectF& rect); -void qRectFFromScriptValue(const QScriptValue& object, QRectF& rect); QRectF qRectFFromVariant(const QVariant& object, bool& isValid); QRectF qRectFFromVariant(const QVariant& object); QVariant qRectFToVariant(const QRectF& rect); -// QColor -QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color); -void qColorFromScriptValue(const QScriptValue& object, QColor& color); - -QScriptValue qURLToScriptValue(QScriptEngine* engine, const QUrl& url); -void qURLFromScriptValue(const QScriptValue& object, QUrl& url); - -// vector -Q_DECLARE_METATYPE(QVector) -QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector); -QScriptValue qVectorVec3ColorToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorVec3FromScriptValue(const QScriptValue& array, QVector& vector); -QVector qVectorVec3FromScriptValue(const QScriptValue& array); - -// vector -Q_DECLARE_METATYPE(QVector) -QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorQuatFromScriptValue(const QScriptValue& array, QVector& vector); -QVector qVectorQuatFromScriptValue(const QScriptValue& array); - -// vector -QScriptValue qVectorBoolToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorBoolFromScriptValue(const QScriptValue& array, QVector& vector); -QVector qVectorBoolFromScriptValue(const QScriptValue& array); - -// vector -QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorFloatFromScriptValue(const QScriptValue& array, QVector& vector); -QVector qVectorFloatFromScriptValue(const QScriptValue& array); - -// vector -QScriptValue qVectorIntToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorIntFromScriptValue(const QScriptValue& array, QVector& vector); - -QScriptValue qVectorQUuidToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorQUuidFromScriptValue(const QScriptValue& array, QVector& vector); -QVector qVectorQUuidFromScriptValue(const QScriptValue& array); - -QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube); -void aaCubeFromScriptValue(const QScriptValue &object, AACube& aaCube); - // MathPicks also have to overide operator== for their type class MathPick { public: @@ -292,8 +225,6 @@ class PickRay : public MathPick { } }; Q_DECLARE_METATYPE(PickRay) -QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); -void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); /*@jsdoc * The tip of a stylus. @@ -644,22 +575,6 @@ class Collision { glm::vec3 velocityChange; }; Q_DECLARE_METATYPE(Collision) -QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision); -void collisionFromScriptValue(const QScriptValue &object, Collision& collision); - -/*@jsdoc - * UUIDs (Universally Unique IDentifiers) are used to uniquely identify entities, avatars, and the like. They are represented - * in JavaScript as strings in the format, "{nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}", where the "n"s are - * hexadecimal digits. - * @typedef {string} Uuid - */ -//Q_DECLARE_METATYPE(QUuid) // don't need to do this for QUuid since it's already a meta type -QScriptValue quuidToScriptValue(QScriptEngine* engine, const QUuid& uuid); -void quuidFromScriptValue(const QScriptValue& object, QUuid& uuid); - -//Q_DECLARE_METATYPE(QSizeF) // Don't need to to this becase it's arleady a meta type -QScriptValue qSizeFToScriptValue(QScriptEngine* engine, const QSizeF& qSizeF); -void qSizeFFromScriptValue(const QScriptValue& object, QSizeF& qSizeF); class AnimationDetails { public: @@ -681,8 +596,6 @@ class AnimationDetails { bool allowTranslation; }; Q_DECLARE_METATYPE(AnimationDetails); -QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event); -void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event); namespace graphics { class Mesh; @@ -733,12 +646,6 @@ class MeshProxyList : public QList {}; // typedef and using fight wi Q_DECLARE_METATYPE(MeshProxyList); -QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in); -void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out); - -QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in); -void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out); - class MeshFace { public: @@ -752,15 +659,8 @@ class MeshFace { Q_DECLARE_METATYPE(MeshFace) Q_DECLARE_METATYPE(QVector) -QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace); -void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResult); -QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); -void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); - QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures); Q_DECLARE_METATYPE(StencilMaskMode) -QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode); -void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode); #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/ScriptValueUtils.cpp b/libraries/shared/src/ScriptValueUtils.cpp deleted file mode 100644 index e352c0546d9..00000000000 --- a/libraries/shared/src/ScriptValueUtils.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// ScriptValueUtils.cpp -// libraries/shared/src -// -// Created by Anthony Thibault on 4/15/16. -// Copyright 2016 High Fidelity, Inc. -// -// Utilities for working with QtScriptValues -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ScriptValueUtils.h" - -bool isListOfStrings(const QScriptValue& arg) { - if (!arg.isArray()) { - return false; - } - - auto lengthProperty = arg.property("length"); - if (!lengthProperty.isNumber()) { - return false; - } - - int length = lengthProperty.toInt32(); - for (int i = 0; i < length; i++) { - if (!arg.property(i).isString()) { - return false; - } - } - - return true; -} diff --git a/libraries/shared/src/ScriptValueUtils.h b/libraries/shared/src/ScriptValueUtils.h deleted file mode 100644 index 2e120a7217a..00000000000 --- a/libraries/shared/src/ScriptValueUtils.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ScriptValueUtils.h -// libraries/shared/src -// -// Created by Anthony Thibault on 4/15/16. -// Copyright 2016 High Fidelity, Inc. -// -// Utilities for working with QtScriptValues -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_ScriptValueUtils_h -#define hifi_ScriptValueUtils_h - -#include - -bool isListOfStrings(const QScriptValue& value); - -#endif // #define hifi_ScriptValueUtils_h diff --git a/libraries/shared/src/VariantMapToScriptValue.h b/libraries/shared/src/VariantMapToScriptValue.h deleted file mode 100644 index ea65cccb3d4..00000000000 --- a/libraries/shared/src/VariantMapToScriptValue.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// VariantMapToScriptValue.h -// libraries/shared/src/ -// -// Created by Brad Hefta-Gaub on 12/6/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include - -QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine); -QScriptValue variantMapToScriptValue(QVariantMap& variantMap, QScriptEngine& scriptEngine); -QScriptValue variantListToScriptValue(QVariantList& variantList, QScriptEngine& scriptEngine); diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp index 21a3f44d50f..57b4496b96f 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -7,20 +7,5 @@ // #include "MiniPromises.h" -#include -#include int MiniPromise::metaTypeID = qRegisterMetaType("MiniPromise::Promise"); - -namespace { - void promiseFromScriptValue(const QScriptValue& object, MiniPromise::Promise& promise) { - Q_ASSERT(false); - } - QScriptValue promiseToScriptValue(QScriptEngine *engine, const MiniPromise::Promise& promise) { - return engine->newQObject(promise.get()); - } -} -void MiniPromise::registerMetaTypes(QObject* engine) { - auto scriptEngine = qobject_cast(engine); - qScriptRegisterMetaType(scriptEngine, promiseToScriptValue, promiseFromScriptValue); -} diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index 30b57ad7b84..e49bc48d18e 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -42,7 +42,6 @@ class MiniPromise : public QObject, public std::enable_shared_from_this; using Promise = std::shared_ptr; - static void registerMetaTypes(QObject* engine); static int metaTypeID; MiniPromise() {} diff --git a/libraries/shared/src/shared/QtHelpers.cpp b/libraries/shared/src/shared/QtHelpers.cpp index ed387a97634..477176ecb9a 100644 --- a/libraries/shared/src/shared/QtHelpers.cpp +++ b/libraries/shared/src/shared/QtHelpers.cpp @@ -40,6 +40,16 @@ void addBlockingForbiddenThread(const QString& name, QThread* thread) { threadHash[thread] = name; } +QString isBlockingForbiddenThread(QThread* currentThread) { + QReadLocker locker(&threadHashLock); + for (const auto& thread : threadHash.keys()) { + if (currentThread == thread) { + return threadHash[thread]; + } + } + return QString(); +} + bool blockingInvokeMethod( const char* function, QObject *obj, const char *member, diff --git a/libraries/shared/src/shared/QtHelpers.h b/libraries/shared/src/shared/QtHelpers.h index 9a9d33a3ceb..5e317727fd1 100644 --- a/libraries/shared/src/shared/QtHelpers.h +++ b/libraries/shared/src/shared/QtHelpers.h @@ -11,14 +11,22 @@ #define hifi_Shared_QtHelpers_h #include +#include +#include + +#include "../Profile.h" #if defined(Q_OS_WIN) // Enable event queue debugging #define DEBUG_EVENT_QUEUE #endif +class QLoggingCategory; +const QLoggingCategory& thread_safety(); + namespace hifi { namespace qt { void addBlockingForbiddenThread(const QString& name, QThread* thread = nullptr); +QString isBlockingForbiddenThread(QThread* currentThread); bool blockingInvokeMethod( const char* function, @@ -49,6 +57,43 @@ bool blockingInvokeMethod( QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()); +// handling unregistered functions +template +typename std::enable_if::value, bool>::type +blockingInvokeMethod(const char* callingFunction, QObject* context, Func function, ReturnType* retVal) { + auto currentThread = QThread::currentThread(); + if (currentThread == qApp->thread()) { + qCWarning(thread_safety) << "BlockingQueuedConnection invoked on main thread from " << callingFunction; + return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection, retVal); + } + + QString forbiddenThread = isBlockingForbiddenThread(currentThread); + if (!forbiddenThread.isEmpty()) { + qCWarning(thread_safety) << "BlockingQueuedConnection invoked on forbidden thread " << forbiddenThread; + } + + PROFILE_RANGE(app, callingFunction); + return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection, retVal); +} + +template +typename std::enable_if::value, bool>::type +blockingInvokeMethod(const char* callingFunction, QObject* context, Func function) { + auto currentThread = QThread::currentThread(); + if (currentThread == qApp->thread()) { + qCWarning(thread_safety) << "BlockingQueuedConnection invoked on main thread from " << callingFunction; + return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection); + } + + QString forbiddenThread = isBlockingForbiddenThread(currentThread); + if (!forbiddenThread.isEmpty()) { + qCWarning(thread_safety) << "BlockingQueuedConnection invoked on forbidden thread " << forbiddenThread; + } + + PROFILE_RANGE(app, callingFunction); + return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection); +} + // Inspecting of the qt event queue // requres access to private Qt datastructures // Querying the event queue should be done with diff --git a/libraries/shared/src/shared/ScriptInitializerMixin.h b/libraries/shared/src/shared/ScriptInitializerMixin.h index 2a7fc10e06e..72f875c392f 100644 --- a/libraries/shared/src/shared/ScriptInitializerMixin.h +++ b/libraries/shared/src/shared/ScriptInitializerMixin.h @@ -12,7 +12,6 @@ #include #include "../DependencyManager.h" -class QScriptEngine; class ScriptEngine; template class ScriptInitializerMixin { @@ -35,11 +34,11 @@ template class ScriptInitializerMixin { std::list _scriptInitializers; }; -class ScriptInitializers : public ScriptInitializerMixin, public Dependency { +class ScriptInitializers : public ScriptInitializerMixin, public Dependency { public: - // Lightweight `QScriptEngine*` initializer (only depends on built-in Qt components) + // Lightweight `ScriptEngine*` initializer (only depends on built-in Qt components) // example registration: - // eg: [&](QScriptEngine* engine) { + // eg: [&](ScriptEngine* engine) { // engine->globalObject().setProperties("API", engine->newQObject(...instance...)) // }; }; diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 6dde4cc1a20..fce94173f63 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME ui) -setup_hifi_library(OpenGL Multimedia Network Qml Quick Script WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS}) -link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers) +setup_hifi_library(OpenGL Multimedia Network Qml Quick WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS}) +link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers script-engine) include_hifi_library_headers(controllers) # Required for some low level GL interaction in the OffscreenQMLSurface diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index 1219094afcb..b0fe2789605 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -9,18 +9,20 @@ #include "QmlFragmentClass.h" #include -#include -#include #include +#include +#include +#include +#include std::mutex QmlFragmentClass::_mutex; -std::map QmlFragmentClass::_fragments; +std::map QmlFragmentClass::_fragments; QmlFragmentClass::QmlFragmentClass(bool restricted, QString id) : QmlWindowClass(restricted), qml(id) { } // Method called by Qt scripts to create a new bottom menu bar in Android -QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { +ScriptValue QmlFragmentClass::internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted) { #ifndef DISABLE_QML std::lock_guard guard(_mutex); auto qml = context->argument(0).toVariant().toMap().value("qml"); @@ -33,7 +35,7 @@ QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QSc } } else { qWarning() << "QmlFragmentClass could not build instance " << qml; - return QScriptValue(); + return ScriptValue(); } auto properties = parseArguments(context); @@ -45,12 +47,13 @@ QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QSc } else { retVal->initQml(properties); } - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); - QScriptValue scriptObject = engine->newQObject(retVal); + auto manager = engine->manager(); + connect(manager, &ScriptManager::destroyed, retVal, &QmlWindowClass::deleteLater); + ScriptValue scriptObject = engine->newQObject(retVal); _fragments[qml.toString()] = scriptObject; return scriptObject; #else - return QScriptValue(); + return ScriptValue(); #endif } diff --git a/libraries/ui/src/QmlFragmentClass.h b/libraries/ui/src/QmlFragmentClass.h index c76bb43513e..9c04fe62633 100644 --- a/libraries/ui/src/QmlFragmentClass.h +++ b/libraries/ui/src/QmlFragmentClass.h @@ -10,18 +10,22 @@ #define hifi_ui_QmlFragmentClass_h #include "QmlWindowClass.h" +#include + +class ScriptContext; +class ScriptEngine; class QmlFragmentClass : public QmlWindowClass { Q_OBJECT private: - static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); + static ScriptValue internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine) { return internal_constructor(context, engine, false); } - static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + static ScriptValue restricted_constructor(ScriptContext* context, ScriptEngine* engine ){ return internal_constructor(context, engine, true); } @@ -46,7 +50,7 @@ public slots: QString qmlSource() const override { return qml; } static std::mutex _mutex; - static std::map _fragments; + static std::map _fragments; private: QString qml; diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index c7851d416fe..9fc5ccdf78b 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -10,8 +10,9 @@ #include -#include -#include +#include +#include +#include #include @@ -19,7 +20,7 @@ static const char* const URL_PROPERTY = "source"; static const char* const SCRIPT_PROPERTY = "scriptUrl"; // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { +ScriptValue QmlWebWindowClass::internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted); Q_ASSERT(retVal); @@ -29,7 +30,8 @@ QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QS } else { retVal->initQml(properties); } - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + auto manager = engine->manager(); + connect(manager, &ScriptManager::destroyed, retVal, &QmlWindowClass::deleteLater); return engine->newQObject(retVal); } diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 384bdadfc42..20dadd98bbd 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -9,8 +9,15 @@ #ifndef hifi_ui_QmlWebWindowClass_h #define hifi_ui_QmlWebWindowClass_h +#include + #include "QmlWindowClass.h" +#include + +class ScriptContext; +class ScriptEngine; + /*@jsdoc * A OverlayWebWindow displays an HTML window inside Interface. * @@ -142,15 +149,15 @@ class QmlWebWindowClass : public QmlWindowClass { Q_PROPERTY(QString url READ getURL CONSTANT) private: - static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); + static ScriptValue internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted); public: QmlWebWindowClass(bool restricted) : QmlWindowClass(restricted) {} - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine) { return internal_constructor(context, engine, false); } - static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + static ScriptValue restricted_constructor(ScriptContext* context, ScriptEngine* engine ){ return internal_constructor(context, engine, true); } diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index a19b9eb7677..f38dd7723d5 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include @@ -27,6 +25,10 @@ #include "OffscreenUi.h" #include "ui/types/HFWebEngineProfile.h" #include "ui/types/FileTypeProfile.h" +#include +#include +#include +#include static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; @@ -37,7 +39,7 @@ static const char* const VISIBILE_PROPERTY = "visible"; static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 }; static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 }; -QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { +QVariantMap QmlWindowClass::parseArguments(ScriptContext* context) { const auto argumentCount = context->argumentCount(); QVariantMap properties; if (argumentCount > 1) { @@ -70,7 +72,7 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { +ScriptValue QmlWindowClass::internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); QmlWindowClass* retVal = new QmlWindowClass(restricted); Q_ASSERT(retVal); @@ -80,7 +82,8 @@ QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScri } else { retVal->initQml(properties); } - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + auto manager = engine->manager(); + connect(manager, &ScriptManager::destroyed, retVal, &QmlWindowClass::deleteLater); return engine->newQObject(retVal); } diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index e911afea6c5..5d1111bd5ab 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -11,13 +11,13 @@ #include #include -#include #include #include +#include -class QScriptEngine; -class QScriptContext; +class ScriptContext; +class ScriptEngine; /*@jsdoc * A OverlayWindow displays a QML window inside Interface. @@ -53,13 +53,13 @@ class QmlWindowClass : public QObject { Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) private: - static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); + static ScriptValue internal_constructor(ScriptContext* context, ScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine) { return internal_constructor(context, engine, false); } - static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + static ScriptValue restricted_constructor(ScriptContext* context, ScriptEngine* engine ){ return internal_constructor(context, engine, true); } @@ -345,8 +345,8 @@ protected slots: void qmlToScript(const QVariant& message); protected: - static QVariantMap parseArguments(QScriptContext* context); - static QScriptValue internalConstructor(QScriptContext* context, QScriptEngine* engine, + static QVariantMap parseArguments(ScriptContext* context); + static ScriptValue internalConstructor(ScriptContext* context, ScriptEngine* engine, std::function function); virtual QString qmlSource() const { return "QmlWindow.qml"; } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 76533a49cbd..7597f7f8fe7 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -13,7 +13,9 @@ #include #include -#include "PointerEvent.h" + +#include +#include using QmlContextCallback = std::function; diff --git a/libraries/ui/src/ui/QmlWrapper.h b/libraries/ui/src/ui/QmlWrapper.h index d77e45c9dc0..42091d55490 100644 --- a/libraries/ui/src/ui/QmlWrapper.h +++ b/libraries/ui/src/ui/QmlWrapper.h @@ -11,8 +11,11 @@ #include #include -#include -#include + +#include +#include + +class ScriptEngine; class QmlWrapper : public QObject { Q_OBJECT @@ -29,16 +32,17 @@ class QmlWrapper : public QObject { }; template -QScriptValue wrapperToScriptValue(QScriptEngine* engine, T* const &in) { +ScriptValue wrapperToScriptValue(ScriptEngine* engine, T* const &in) { if (!in) { return engine->undefinedValue(); } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + return engine->newQObject(in, ScriptEngine::QtOwnership); } template -void wrapperFromScriptValue(const QScriptValue& value, T* &out) { +bool wrapperFromScriptValue(const ScriptValue& value, T* &out) { out = qobject_cast(value.toQObject()); + return !!out; } #endif \ No newline at end of file diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 68730e186fd..81a4e9c7185 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -159,6 +159,8 @@ bool TabletButtonsProxyModel::filterAcceptsRow(int sourceRow, TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); + qRegisterMetaType("TabletScriptingInterface::TabletAudioEvents"); + qRegisterMetaType("TabletScriptingInterface::TabletConstants"); qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletButtonsProxyModel"); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index af0c03de80d..4359f72bf16 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -19,10 +19,6 @@ #include #include -#include -#include -#include - #include #include diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp index d01b538004a..c2e908f8a44 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp @@ -10,31 +10,31 @@ #include #include -#include -#include +#include +#include #include #include "../OffscreenUi.h" -QScriptValue toolbarToScriptValue(QScriptEngine* engine, ToolbarProxy* const &in) { +ScriptValue toolbarToScriptValue(ScriptEngine* engine, ToolbarProxy* const &in) { if (!in) { return engine->undefinedValue(); } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + return engine->newQObject(in, ScriptEngine::QtOwnership); } -void toolbarFromScriptValue(const QScriptValue& value, ToolbarProxy* &out) { +void toolbarFromScriptValue(const ScriptValue& value, ToolbarProxy* &out) { out = qobject_cast(value.toQObject()); } -QScriptValue toolbarButtonToScriptValue(QScriptEngine* engine, ToolbarButtonProxy* const &in) { +ScriptValue toolbarButtonToScriptValue(ScriptEngine* engine, ToolbarButtonProxy* const &in) { if (!in) { return engine->undefinedValue(); } - return engine->newQObject(in, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + return engine->newQObject(in, ScriptEngine::QtOwnership); } -void toolbarButtonFromScriptValue(const QScriptValue& value, ToolbarButtonProxy* &out) { +void toolbarButtonFromScriptValue(const ScriptValue& value, ToolbarButtonProxy* &out) { out = qobject_cast(value.toQObject()); } diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index 5cc847d4428..513d3edbaea 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -12,7 +12,6 @@ #include #include -#include #include #include "QmlWrapper.h" diff --git a/plugins/JSAPIExample/CMakeLists.txt b/plugins/JSAPIExample/CMakeLists.txt index a8fa0a1fd6a..c8858ad2cbb 100644 --- a/plugins/JSAPIExample/CMakeLists.txt +++ b/plugins/JSAPIExample/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME JSAPIExample) setup_hifi_client_server_plugin(scripting) -link_hifi_libraries(shared plugins) +link_hifi_libraries(shared plugins script-engine) +include_hifi_library_headers(networking) diff --git a/plugins/JSAPIExample/src/JSAPIExample.cpp b/plugins/JSAPIExample/src/JSAPIExample.cpp index 34c28bdf8f8..8dec23fd447 100644 --- a/plugins/JSAPIExample/src/JSAPIExample.cpp +++ b/plugins/JSAPIExample/src/JSAPIExample.cpp @@ -17,14 +17,18 @@ #include #include #include +#include #include #include -#include -#include #include // for ::settingsFilename() #include // for ::usecTimestampNow() #include +#include +#include +#include +#include +#include // NOTE: replace this with your own namespace when starting a new plugin (to avoid .so/.dll symbol clashes) namespace REPLACE_ME_WITH_UNIQUE_NAME { @@ -34,9 +38,9 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { QLoggingCategory logger { "jsapiexample" }; - inline QVariant raiseScriptingError(QScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) { + inline QVariant raiseScriptingError(ScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) { if (context) { - // when a QScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side) + // when a ScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side) context->throwError(message); } else { // otherwise just log the error @@ -47,7 +51,7 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error); - class JSAPIExample : public QObject, public QScriptable { + class JSAPIExample : public QObject, public Scriptable { Q_OBJECT Q_PLUGIN_METADATA(IID "JSAPIExample" FILE "plugin.json") Q_PROPERTY(QString version MEMBER _version CONSTANT) @@ -60,8 +64,8 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { return; } qCWarning(logger) << "registering w/ScriptInitializerMixin..." << scriptInit.data(); - scriptInit->registerScriptInitializer([this](QScriptEngine* engine) { - auto value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); + scriptInit->registerScriptInitializer([this](ScriptEngine* engine) { + auto value = engine->newQObject(this, ScriptEngine::QtOwnership); engine->globalObject().setProperty(objectName(), value); // qCDebug(logger) << "setGlobalInstance" << objectName() << engine->property("fileName"); }); @@ -70,7 +74,7 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { // NOTES: everything within the "public slots:" section below will be available from JS via overall plugin QObject // also, to demonstrate future-proofing JS API code, QVariant's are used throughout most of these examples -- - // which still makes them very Qt-specific, but avoids depending directly on deprecated QtScript/QScriptValue APIs. + // which still makes them very Qt-specific, but avoids depending directly on deprecated ScriptValue APIs. // (as such this plugin class and its methods remain forward-compatible with other engines like QML's QJSEngine) public slots: @@ -146,7 +150,7 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { /** * Example of exposing a custom "managed" C++ QObject to JS - * The lifecycle of the created QObject* instance becomes managed by the invoking QScriptEngine -- + * The lifecycle of the created QObject* instance becomes managed by the invoking ScriptEngine -- * it will be automatically cleaned up once no longer reachable from any JS variables/closures. * @example access persistent settings stored in separate .json files * var settings = JSAPIExample.getScopedSettings("example"); @@ -157,18 +161,22 @@ namespace REPLACE_ME_WITH_UNIQUE_NAME { * print("all example::* keys", settings.allKeys()); * settings = null; // optional best pratice; allows the object to be reclaimed ASAP by the JS garbage collector */ - QScriptValue getScopedSettings(const QString& scope) { - auto engine = QScriptable::engine(); + ScriptValue getScopedSettings(const QString& scope) { + auto engine = Scriptable::engine(); if (!engine) { - return QScriptValue::NullValue; + return ScriptValue(); + } + auto manager = engine->manager(); + if (!manager) { + return ScriptValue(); } QString error; - auto cppValue = createScopedSettings(scope, engine, error); + auto cppValue = createScopedSettings(scope, manager, error); if (!cppValue) { raiseScriptingError(context(), "error creating scoped settings instance: " + error); - return QScriptValue::NullValue; + return engine->nullValue(); } - return engine->newQObject(cppValue, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeDeleteLater); + return engine->newQObject(cppValue, ScriptEngine::ScriptOwnership); } private: diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index ad4f78698cd..25fd8ff2e54 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -11,6 +11,7 @@ if (WIN32) set(TARGET_NAME hifiNeuron) setup_hifi_plugin(Qml) link_hifi_libraries(shared controllers qml ui plugins input-plugins) + include_hifi_library_headers(script-engine) target_neuron() endif() diff --git a/plugins/hifiOsc/CMakeLists.txt b/plugins/hifiOsc/CMakeLists.txt index cb8b437ab6a..78514f5888b 100644 --- a/plugins/hifiOsc/CMakeLists.txt +++ b/plugins/hifiOsc/CMakeLists.txt @@ -9,6 +9,7 @@ set(TARGET_NAME hifiOsc) setup_hifi_plugin(Qml) link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins) +include_hifi_library_headers(script-engine) target_liblo() diff --git a/plugins/hifiSdl2/CMakeLists.txt b/plugins/hifiSdl2/CMakeLists.txt index e1f0ee28d8b..16ce6f51b4a 100644 --- a/plugins/hifiSdl2/CMakeLists.txt +++ b/plugins/hifiSdl2/CMakeLists.txt @@ -16,6 +16,7 @@ if (NOT APPLE) link_libraries("-Wl,--allow-multiple-definition") endif() setup_hifi_plugin(Qml) - link_hifi_libraries(shared controllers ui plugins input-plugins script-engine) + link_hifi_libraries(shared controllers ui plugins input-plugins) + include_hifi_library_headers(script-engine) target_sdl2() endif() diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index 6ddc75e1e55..2eb7fb48250 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -22,6 +22,7 @@ if (WIN32 AND (NOT USE_GLES)) ${PLATFORM_GL_BACKEND} ) include_hifi_library_headers(octree) + include_hifi_library_headers(script-engine) add_dependency_external_projects(LibOVR) find_package(LibOVR REQUIRED) diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index 0b9358242dd..f8e818e7fd3 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -11,9 +11,10 @@ if ((WIN32 OR UNIX AND NOT APPLE) AND NOT USE_GLES) set(TARGET_NAME openvr) setup_hifi_plugin(Gui Qml Multimedia) link_hifi_libraries(shared task gl qml networking controllers ui - plugins display-plugins ui-plugins input-plugins script-engine + plugins display-plugins ui-plugins input-plugins audio-client render-utils graphics shaders gpu render material-networking model-networking model-baker hfm model-serializers ktx image procedural ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(octree) + include_hifi_library_headers(script-engine) target_openvr() if (WIN32) diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js index b57f2d4cd77..f51931bb3a7 100644 --- a/scripts/system/request-service.js +++ b/scripts/system/request-service.js @@ -14,7 +14,7 @@ // QML has its own XMLHttpRequest, but: // - npm request is easier to use. // - It is not easy to hack QML's XMLHttpRequest to use our MetaverseServer, and to supply the user's auth when contacting it. - // a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript. + // a. Our custom XMLHttpRequestClass object only works with ScriptEngine, not QML's javascript. // b. We have hacked profiles that intercept requests to our MetavserseServer (providing the correct auth), but those // only work in QML WebEngineView. Setting up communication between ordinary QML and a hiddent WebEngineView is // tantamount to the following anyway, and would still have to duplicate the code from request.js. diff --git a/tests-manual/controllers/CMakeLists.txt b/tests-manual/controllers/CMakeLists.txt index 932826c8de1..c200e2fb9ca 100644 --- a/tests-manual/controllers/CMakeLists.txt +++ b/tests-manual/controllers/CMakeLists.txt @@ -3,7 +3,7 @@ if (NOT APPLE) set(TARGET_NAME controllers-test) # This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Script Qml) +setup_hifi_project(Qml) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") setup_memory_debugger() diff --git a/tests-manual/entities/CMakeLists.txt b/tests-manual/entities/CMakeLists.txt index a6eed4f2344..1aede6ad6b0 100644 --- a/tests-manual/entities/CMakeLists.txt +++ b/tests-manual/entities/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME "entities-test") # This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Network Script) +setup_hifi_project(Network) setup_memory_debugger() setup_thread_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests-manual/gpu-textures/CMakeLists.txt b/tests-manual/gpu-textures/CMakeLists.txt index d148b0cd21e..fcade688f95 100644 --- a/tests-manual/gpu-textures/CMakeLists.txt +++ b/tests-manual/gpu-textures/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu-textures-tests) # This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Quick Gui Script) +setup_hifi_project(Quick Gui) setup_memory_debugger() setup_thread_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests-manual/gpu/CMakeLists.txt b/tests-manual/gpu/CMakeLists.txt index dc7bfbe75e0..e72d1672942 100644 --- a/tests-manual/gpu/CMakeLists.txt +++ b/tests-manual/gpu/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu-test) # This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Quick Gui Script) +setup_hifi_project(Quick Gui) setup_memory_debugger() setup_thread_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt index 287a3b73d87..49a031887d2 100644 --- a/tests/octree/CMakeLists.txt +++ b/tests/octree/CMakeLists.txt @@ -7,4 +7,4 @@ macro (setup_testcase_dependencies) package_libraries_for_deployment() endmacro () -setup_hifi_testcase(Script Network) +setup_hifi_testcase(Network) diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index 87e5350c8f8..f47e27226b4 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -6,4 +6,4 @@ macro (SETUP_TESTCASE_DEPENDENCIES) package_libraries_for_deployment() endmacro () -setup_hifi_testcase(Script) +setup_hifi_testcase() diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 4f48570428b..95206d1ced6 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -3,6 +3,7 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) link_hifi_libraries(shared shaders image gpu ktx model-serializers hfm baking graphics networking procedural material-networking model-baker task) +include_hifi_library_headers(script-engine) setup_memory_debugger() setup_thread_debugger()