diff --git a/.gitignore b/.gitignore index acb34148002..91e80c1fb04 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,16 @@ CMakeCache.txt CMakeFiles/ CMakeScripts/ cmake_install.cmake -build*/ +/build*/ +cmake-build-minsizerelease-visual-studio/ +cmake-build-release-visual-studio/ cmake-build-relwithdebinfo-visual-studio/ -release*/ -debug*/ -gprof*/ -valgrind*/ -ext/ +cmake-build-debug-visual-studio/ +/release*/ +/debug*/ +/gprof*/ +/valgrind*/ +/ext/ Makefile *.user @@ -25,14 +28,14 @@ Makefile *.iml *.class local.properties -android/gradle* -android/.gradle -android/**/src/main/jniLibs -android/**/libs -android/**/bin -android/**/src/main/res/values/libs.xml -android/**/src/main/assets -android/**/gradle* +/android/gradle* +/android/.gradle +/android/**/src/main/jniLibs +/android/**/libs +/android/**/bin +/android/**/src/main/res/values/libs.xml +/android/**/src/main/assets +/android/**/gradle* *.class # Visual Studio @@ -73,19 +76,16 @@ DerivedData *.hmap # ignore interface optional externals -interface/external/*/* +/interface/external/*/* !interface/external/*/readme.txt # Ignore interfaceCache for Linux users -interface/interfaceCache/ +/interface/interfaceCache/ # ignore audio-client externals -libraries/audio-client/external/*/* +/libraries/audio-client/external/*/* !libraries/audio-client/external/*/readme.txt -gvr-interface/assets/oculussig* -gvr-interface/libs/* - # ignore files for various dev environments TAGS *.sw[po] @@ -108,21 +108,22 @@ interface/compiledResources *.rcc # GPUCache -interface/resources/GPUCache/* +/interface/resources/GPUCache/* # package lock file for JSDoc tool -tools/jsdoc/package-lock.json +/tools/jsdoc/package-lock.json # Python compile artifacts **/__pycache__ # ignore local unity project files for avatar exporter -tools/unity-avatar-exporter +/tools/unity-avatar-exporter -server-console/package-lock.json -vcpkg/ +/server-console/package-lock.json +/vcpkg/ /tools/nitpick/compiledResources -qt/ +/qt/ # Act local GitHub Actions .secret + diff --git a/CMakeLists.txt b/CMakeLists.txt index 84f3a5a0602..c53140957f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # Copyright 2013-2019 High Fidelity, Inc. # Copyright 2019-2021 Vircadia contributors. -# Copyright 2020-2022 Overte e.V. +# Copyright 2020-2023 Overte e.V. # SPDX-License-Identifier: Apache-2.0 # If we're running under the gradle build, HIFI_ANDROID will be set here, but @@ -12,6 +12,8 @@ else() cmake_minimum_required(VERSION 3.2) endif() +# 3.14 is the minimum version that supports symlinks on Windows +cmake_minimum_required(VERSION 3.14) # Passing of variables to vcpkg # @@ -192,7 +194,7 @@ else() set(VCPKG_BUILD_TYPE_PARAM --vcpkg-build-type ${VCPKG_BUILD_TYPE}) endif() execute_process( - COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --vcpkg-skip-clean --release-type ${RELEASE_TYPE} --build-root ${CMAKE_BINARY_DIR} ${VCPKG_BUILD_TYPE_PARAM} + COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --release-type ${RELEASE_TYPE} --build-root ${CMAKE_BINARY_DIR} ${VCPKG_BUILD_TYPE_PARAM} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULTS_VARIABLE PREBUILD_RET ) # squelch the Policy CMP0074 warning without requiring an update to cmake 3.12. diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index abb94f95e3c..9b0d0c21c04 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2013-2019 High Fidelity, Inc. +# Copyright 2021-2022 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..246fd22cbb0 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 7/1/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Agent.h" @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +37,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -56,7 +61,7 @@ #include "entities/AssignmentParentFinder.h" #include "AssignmentDynamicFactory.h" -#include "RecordingScriptingInterface.h" +#include #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" @@ -180,7 +185,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 +377,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 +391,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 +499,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 +528,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 +608,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 +644,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 +881,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 +901,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..60ff93f5706 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 7/1/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_Agent_h @@ -15,12 +17,12 @@ #include #include -#include #include #include #include #include #include +#include #include #include @@ -29,11 +31,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 +98,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 b6c9070cf99..37280c5b43f 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -4,9 +4,11 @@ // // Created by Clement on 7/22/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptableAvatar.h" @@ -17,16 +19,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 +315,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 +339,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 +419,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..703a0a9f64c 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -4,9 +4,11 @@ // // Created by Clement on 7/22/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ScriptableAvatar_h @@ -220,7 +222,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..2b1be640f09 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -4,9 +4,11 @@ // // Created by Clément Brisset on 1/5/17. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityScriptServer.h" @@ -14,7 +16,9 @@ #include #include +#include #include +#include #include #include #include @@ -130,7 +134,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 +179,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 +240,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 +254,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 +450,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 +461,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 +508,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 +518,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 +536,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 +548,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 +578,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..3f15f5733c5 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -4,9 +4,11 @@ // // Created by Clément Brisset on 1/5/17. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityScriptServer_h @@ -18,12 +20,14 @@ #include #include #include +#include #include #include -#include #include #include +#include + #include "../entities/EntityTreeHeadlessViewer.h" class EntityScriptServer : public ThreadedAssignment { @@ -76,7 +80,7 @@ private slots: bool _shuttingDown { false }; static int _entitiesScriptEngineCount; - ScriptEnginePointer _entitiesScriptEngine; + ScriptManagerPointer _entitiesScriptManager; SimpleEntitySimulationPointer _entitySimulation; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 00964c1044c..abcede8bee6 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -2,9 +2,11 @@ # MemoryDebugger.cmake # # Copyright 2015 High Fidelity, Inc. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # macro(SETUP_MEMORY_DEBUGGER) @@ -16,7 +18,7 @@ if ("$ENV{OVERTE_MEMORY_DEBUGGING}") SET( OVERTE_MEMORY_DEBUGGING true ) endif () -if (OVERTE_MEMORY_DEBUGGING) +if ( OVERTE_MEMORY_DEBUGGING) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") diff --git a/cmake/macros/TargetV8.cmake b/cmake/macros/TargetV8.cmake new file mode 100644 index 00000000000..2259d7e9bd4 --- /dev/null +++ b/cmake/macros/TargetV8.cmake @@ -0,0 +1,15 @@ +# +# Copyright 2022-2023 Overte e.V. +# Created by dr Karol Suprynowicz on 2022/09/03 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 +# +macro(TARGET_V8) + +find_package(V8 REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${V8_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${V8_LIBRARIES}) + +endmacro() \ No newline at end of file diff --git a/cmake/modules/FindV8.cmake b/cmake/modules/FindV8.cmake new file mode 100644 index 00000000000..519c72edfcd --- /dev/null +++ b/cmake/modules/FindV8.cmake @@ -0,0 +1,481 @@ +# +# CMake Find V8 Google JavaScript Engine by Parra Studios +# CMake script to find V8 JavaScript Engine. +# +# Copyright (C) 2016 - 2022 Vicente Eduardo Ferrer Garcia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Modified by dr Karol Suprynowicz on 2022/09/03 +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# Find V8 library and include paths +# +# V8_FOUND - True if V8 was found +# V8_INCLUDE_DIR - V8 headers path +# V8_LIBRARIES - List of V8 libraries +# V8_VERSION - V8 version +# V8_VERSION_MAJOR - V8 major version +# V8_VERSION_MINOR - V8 minor version +# V8_VERSION_PATCH - V8 patch version +# V8_VERSION_TWEAK - V8 patch version +# V8_VERSION_HEX - V8 version in hexadecimal format +# V8_EXECUTABLE - V8 shell + +# Prevent vervosity if already included +if(V8_INCLUDE_DIR) + set(V8_FIND_QUIETLY TRUE) +endif() + +# Debug flag +set(_V8_CMAKE_DEBUG TRUE) + +# Include package manager +include(FindPackageHandleStandardArgs) + +# V8 search paths +set(V8_PATHS + ${VCPKG_INSTALL_ROOT} + ${V8_HOME} + ${V8_ROOT} + $ENV{ProgramFiles}/v8 + $ENV{SystemDrive}/v8 + $ENV{V8_HOME} + $ENV{EXTERNLIBS}/v8 + ${V8_DIR} + $ENV{V8_DIR} + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + /usr/freeware +) + +# V8 platform dependant paths +if(NOT UNIX) + set(V8_PATHS_DEBUG + ${V8_DIR}/build/Debug + ) + + set(V8_PATHS_RELEASE + ${V8_DIR}/build/Release + ) +else() + set(V8_PATHS_DEBUG + ${V8_DIR}/out/ia32.debug + ${V8_DIR}/out/x64.debug + ${V8_DIR}/out/native + ) + + set(V8_PATHS_RELEASE + ${V8_DIR}/out/ia32.release + ${V8_DIR}/out/x64.release + ${V8_DIR}/out/native + ) +endif() + +# V8 library paths +#set(V8_LIBRARY_PATH_SUFFIXES lib lib64 lib/x86_64-linux-gnu lib.target) + +# Find include path +if(MSVC OR CMAKE_BUILD_TYPE EQUAL "Debug") + set(V8_HEADERS v8.h v8-debug.h v8-profiler.h v8stdint.h) +else() + set(V8_HEADERS v8.h v8stdint.h) +endif() + +find_path(V8_INCLUDE_DIR ${V8_HEADERS} + PATHS ${V8_PATHS} + PATH_SUFFIXES include include/node + DOC "Google V8 JavaScript Engine Headers" +) + +MESSAGE("V8 include dir: ${V8_INCLUDE_DIR}") + +# Define library names +set(V8_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib v8D v8_baseD v8_base.ia32D v8_base.x64D libv8_baseD v8_baseD.lib v8_libbaseD v8_libbase.ia32D v8_libbase.x64D libv8_libbaseD v8_libbaseD.lib) +set(V8_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib v8 v8_base v8_base.ia32 v8_base.x64 libv8_base v8_base.lib v8_libbase v8_libbase.ia32 v8_libbase.x64 libv8_libbase v8_libbase.lib) +set(V8_PLATFORM_NAMES_DEBUG libnode.so.108 v8_libplatform.lib libnode libnode.so libnode.lib v8_libplatformD v8_libplatformD.a v8_libplatformD.lib) +set(V8_PLATFORM_NAMES_RELEASE libnode.so.108 v8_libplatform.lib libnode libnode.so libnode.lib v8_libplatform v8_libplatform.a) +set(V8_SAMPLER_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib v8_libsamplerD v8_libsamplerD.a v8_libsamplerD.lib) +set(V8_SAMPLER_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib v8_libsampler v8_libsampler.a v8_libsampler.lib) +set(V8_SNAPSHOT_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib v8_snapshotD libv8_snapshotD v8_snapshotD.lib) +set(V8_SNAPSHOT_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib v8_snapshot libv8_snapshot v8_snapshot.lib) +set(V8_ICU_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib icudataD icudataD.a icudataD.lib) +set(V8_ICU_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib icudata icudata.a icudata.lib) +set(V8_ICUUC_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib icuucD libicuucD) +set(V8_ICUUC_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib icuuc libicuuc) +set(V8_ICUI18N_NAMES_DEBUG libnode.so.108 libnode libnode.so libnode.lib icui18nD libicui18nD) +set(V8_ICUI18N_NAMES_RELEASE libnode.so.108 libnode libnode.so libnode.lib icui18n libicui18n) + +# Find V8 base library debug +find_library(V8_LIBRARY_DEBUG + NAMES ${V8_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library (Debug)" +) + +MESSAGE("V8_LIBRARY_DEBUG: ${V8_LIBRARY_DEBUG}") + +# Find V8 base library release +find_library(V8_LIBRARY_RELEASE + NAMES ${V8_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library (Release)" +) + +MESSAGE("V8_LIBRARY_RELEASE: ${V8_LIBRARY_RELEASE}") + +# Find V8 platform library debug +find_library(V8_PLATFORM_LIBRARY_DEBUG + NAMES ${V8_PLATFORM_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Platform (Debug)" +) + +MESSAGE("V8_PLATFORM_LIBRARY_DEBUG: ${V8_PLATFORM_LIBRARY_DEBUG}") + +# Find V8 platform library release +find_library(V8_PLATFORM_LIBRARY_RELEASE + NAMES ${V8_PLATFORM_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Platform (Release)" +) + +MESSAGE("V8_PLATFORM_LIBRARY_RELEASE: ${V8_PLATFORM_LIBRARY_RELEASE}") + +# Find V8 platform library debug +find_library(V8_SAMPLER_LIBRARY_DEBUG + NAMES ${V8_SAMPLER_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Sampler (Debug)" +) + +MESSAGE("V8_SAMPLER_LIBRARY_DEBUG: ${V8_SAMPLER_LIBRARY_DEBUG}") + +# Find V8 platform library release +find_library(V8_SAMPLER_LIBRARY_RELEASE + NAMES ${V8_SAMPLER_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Sampler (Release)" +) + +MESSAGE("V8_SAMPLER_LIBRARY_RELEASE: ${V8_SAMPLER_LIBRARY_RELEASE}") + +# Find V8 snapshot library debug +find_library(V8_SNAPSHOT_LIBRARY_DEBUG + NAMES ${V8_SNAPSHOT_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Snapshot (Debug)" +) + +MESSAGE("V8_SNAPSHOT_LIBRARY_DEBUG: ${V8_SNAPSHOT_LIBRARY_DEBUG}") + +# Find V8 snapshot library release +find_library(V8_SNAPSHOT_LIBRARY_RELEASE + NAMES ${V8_SNAPSHOT_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library Snapshot (Release)" +) + +MESSAGE("V8_SNAPSHOT_LIBRARY_RELEASE: ${V8_SNAPSHOT_LIBRARY_RELEASE}") + +# Find V8 icu library debug +find_library(V8_ICU_LIBRARY_DEBUG + NAMES ${V8_ICU_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICU (Debug)" +) + +MESSAGE("V8_ICU_LIBRARY_DEBUG: ${V8_ICU_LIBRARY_DEBUG}") + +# Find V8 icu library release +find_library(V8_ICU_LIBRARY_RELEASE + NAMES ${V8_ICU_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICU (Release)" +) + +MESSAGE("V8_ICU_LIBRARY_RELEASE: ${V8_ICU_LIBRARY_RELEASE}") + +# Find V8 icuuc library debug +find_library(V8_ICUUC_LIBRARY_DEBUG + NAMES ${V8_ICUUC_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICUUC (Debug)" +) + +MESSAGE("V8_ICUUC_LIBRARY_DEBUG: ${V8_ICUUC_LIBRARY_DEBUG}") + +# Find V8 icuuc library release +find_library(V8_ICUUC_LIBRARY_RELEASE + NAMES ${V8_ICUUC_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICUUC (Release)" +) + +MESSAGE("V8_ICUUC_LIBRARY_RELEASE: ${V8_ICUUC_LIBRARY_RELEASE}") + +# Find V8 icui18n library debug +find_library(V8_ICUI18N_LIBRARY_DEBUG + NAMES ${V8_ICUI18N_NAMES_DEBUG} + PATHS ${V8_PATHS} ${V8_PATHS_DEBUG} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICUI18N (Debug)" +) + +MESSAGE("V8_ICUI18N_LIBRARY_DEBUG: ${V8_ICUI18N_LIBRARY_DEBUG}") + +# Find V8 icui18n library release +find_library(V8_ICUI18N_LIBRARY_RELEASE + NAMES ${V8_ICUI18N_NAMES_RELEASE} + PATHS ${V8_PATHS} ${V8_PATHS_RELEASE} + PATH_SUFFIXES ${V8_LIBRARY_PATH_SUFFIXES} + DOC "Google V8 JavaScript Engine Library ICUI18N (Release)" +) + +MESSAGE("V8_ICUI18N_LIBRARY_RELEASE: ${V8_ICUI18N_LIBRARY_RELEASE}") + +# Base build with snapshot +MESSAGE("1") +if(MSVC) + if(V8_LIBRARY_DEBUG AND V8_LIBRARY_RELEASE AND V8_SNAPSHOT_LIBRARY_DEBUG AND V8_SNAPSHOT_LIBRARY_RELEASE) + set(V8_LIBRARY + ${V8_LIBRARY_DEBUG} ${V8_PLATFORM_LIBRARY_DEBUG} ${V8_SNAPSHOT_LIBRARY_DEBUG} # ${V8_ICU_LIBRARY_DEBUG} + ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE} ${V8_SNAPSHOT_LIBRARY_RELEASE} # ${V8_ICU_LIBRARY_RELEASE} + ) + + set(V8_LIBRARIES + optimized ${V8_LIBRARY_RELEASE} debug ${V8_LIBRARY_DEBUG} + optimized ${V8_PLATFORM_LIBRARY_RELEASE} debug ${V8_PLATFORM_LIBRARY_DEBUG} +# optimized ${V8_ICU_LIBRARY_RELEASE} debug ${V8_ICU_LIBRARY_DEBUG} + optimized ${V8_SNAPSHOT_LIBRARY_RELEASE} debug ${V8_SNAPSHOT_LIBRARY_DEBUG} + optimized Winmm.lib debug Winmm.lib + ) + endif() +else() + MESSAGE("2") + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + MESSAGE("3") + if(V8_LIBRARY_DEBUG AND V8_PLATFORM_LIBRARY_DEBUG AND V8_SAMPLER_LIBRARY_DEBUG) + set(V8_LIBRARY ${V8_LIBRARY_DEBUG} ${V8_PLATFORM_LIBRARY_DEBUG} ${V8_SAMPLER_LIBRARY_DEBUG}) # ${V8_ICU_LIBRARY_DEBUG}) + elseif(V8_LIBRARY_DEBUG AND V8_PLATFORM_LIBRARY_DEBUG) + MESSAGE("4") + set(V8_LIBRARY ${V8_LIBRARY_DEBUG} ${V8_PLATFORM_LIBRARY_DEBUG}) # ${V8_SAMPLER_LIBRARY_DEBUG}) # ${V8_ICU_LIBRARY_DEBUG}) + else() + if(V8_LIBRARY_RELEASE AND V8_PLATFORM_LIBRARY_RELEASE AND V8_SAMPLER_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE} ${V8_SAMPLER_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_DEBUG}) + elseif(V8_LIBRARY_RELEASE AND V8_PLATFORM_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE}) # ${V8_SAMPLER_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_DEBUG}) + endif() + endif() + else() + if(V8_LIBRARY_RELEASE AND V8_PLATFORM_LIBRARY_RELEASE AND V8_SAMPLER_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE} ${V8_SAMPLER_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_RELEASE}) + elseif(V8_LIBRARY_RELEASE AND V8_PLATFORM_LIBRARY_RELEASE ) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE} ) # ${V8_SAMPLER_LIBRARY_RELEASE} ${V8_ICU_LIBRARY_RELEASE}) + + endif() + endif() + + set(V8_LIBRARIES ${V8_LIBRARY}) +endif() + +# Set version libraries +set(V8_LIBRARIES_DEPENDS) + +if (V8_LIBRARY_DEBUG) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_LIBRARY_DEBUG}.${V8_FIND_VERSION} + ) +endif() + +if (V8_LIBRARY_RELEASE) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_LIBRARY_RELEASE}.${V8_FIND_VERSION} + ) +endif() + +if(V8_ICU_LIBRARY_DEBUG) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICU_LIBRARY_DEBUG}.${ICU_FIND_VERSION} + ) +endif() + +if(V8_ICU_LIBRARY_RELEASE) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICU_LIBRARY_RELEASE}.${ICU_FIND_VERSION} + ) +endif() + +if(V8_ICUUC_LIBRARY_DEBUG) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICUUC_LIBRARY_DEBUG}.${ICU_FIND_VERSION} + ) +endif() + +if(V8_ICUUC_LIBRARY_RELEASE) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICUUC_LIBRARY_RELEASE}.${ICU_FIND_VERSION} + ) +endif() + +if(V8_ICUI18N_LIBRARY_DEBUG) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICUI18N_LIBRARY_DEBUG}.${ICU_FIND_VERSION} + ) +endif() + +if(V8_ICUI18N_LIBRARY_RELEASE) + set(V8_LIBRARIES_DEPENDS + ${V8_LIBRARIES_DEPENDS} + ${V8_ICUI18N_LIBRARY_RELEASE}.${ICU_FIND_VERSION} + ) +endif() + +MESSAGE("V8_LIBRARY: ${V8_LIBRARY}") +find_package_handle_standard_args(V8 DEFAULT_MSG V8_LIBRARY V8_INCLUDE_DIR) + +# Base build +if(NOT V8_FOUND) + if(MSVC) + if(V8_LIBRARY_DEBUG AND V8_LIBRARY_RELEASE) + set(V8_LIBRARY + ${V8_LIBRARY_DEBUG} ${V8_LIBRARY_RELEASE} + ${V8_PLATFORM_LIBRARY_DEBUG} ${V8_PLATFORM_LIBRARY_RELEASE} +# ${V8_ICU_LIBRARY_DEBUG} ${V8_ICU_LIBRARY_RELEASE} + ) + + set(V8_LIBRARIES + optimized ${V8_LIBRARY_RELEASE} debug ${V8_LIBRARY_DEBUG} + optimized ${V8_PLATFORM_LIBRARY_RELEASE} debug ${V8_PLATFORM_LIBRARY_DEBUG} +# optimized ${V8_ICU_LIBRARY_RELEASE} debug ${V8_ICU_LIBRARY_DEBUG} + optimized Winmm.lib debug Winmm.lib + ) + endif() + else() + if(CMAKE_BUILD_TYPE EQUAL "Debug") + if(V8_LIBRARY_DEBUG) + set(V8_LIBRARY ${V8_LIBRARY_DEBUG} ${V8_PLATFORM_LIBRARY_DEBUG}) # ${V8_ICU_LIBRARY_DEBUG}) + endif() + else() + if(V8_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_RELEASE}) + endif() + endif() + + set(V8_LIBRARIES ${V8_LIBRARY}) + endif() + + find_package_handle_standard_args(V8 DEFAULT_MSG V8_LIBRARY V8_INCLUDE_DIR) +endif() + +# Minimal build +if(NOT V8_FOUND) + if(MSVC) + if(V8_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_RELEASE}) + + set(V8_LIBRARIES + optimized ${V8_LIBRARY_RELEASE} debug ${V8_LIBRARY_RELEASE} + optimized ${V8_PLATFORM_LIBRARY_RELEASE} debug ${V8_PLATFORM_LIBRARY_RELEASE} +# optimized ${V8_ICU_LIBRARY_RELEASE} debug ${V8_ICU_LIBRARY_RELEASE} + optimized Winmm.lib debug Winmm.lib + ) + endif() + else() + if(V8_LIBRARY_RELEASE) + set(V8_LIBRARY ${V8_LIBRARY_RELEASE} ${V8_PLATFORM_LIBRARY_RELEASE}) # ${V8_ICU_LIBRARY_RELEASE}) + endif() + + set(V8_LIBRARIES ${V8_LIBRARY}) + endif() + + find_package_handle_standard_args(V8 DEFAULT_MSG V8_LIBRARY V8_INCLUDE_DIR) +endif() + +# Detect V8 version +if(V8_FOUND AND V8_INCLUDE_DIR) + file(READ ${V8_INCLUDE_DIR}/v8-version.h V8_VERSION_FILE) + + string(REGEX MATCH "#define V8_MAJOR_VERSION ([0-9]+)" V8_VERSION_MAJOR_DEF ${V8_VERSION_FILE}) + string(REGEX MATCH "([0-9]+)$" V8_VERSION_MAJOR ${V8_VERSION_MAJOR_DEF}) + + string(REGEX MATCH "#define V8_MINOR_VERSION ([0-9]+)" V8_VERSION_MINOR_DEF ${V8_VERSION_FILE}) + string(REGEX MATCH "([0-9]+)$" V8_VERSION_MINOR ${V8_VERSION_MINOR_DEF}) + + string(REGEX MATCH "#define V8_BUILD_NUMBER ([0-9]+)" V8_VERSION_PATCH_DEF ${V8_VERSION_FILE}) + string(REGEX MATCH "([0-9]+)$" V8_VERSION_PATCH ${V8_VERSION_PATCH_DEF}) + + string(REGEX MATCH "#define V8_PATCH_LEVEL ([0-9]+)" V8_VERSION_TWEAK_DEF ${V8_VERSION_FILE}) + string(REGEX MATCH "([0-9]+)$" V8_VERSION_TWEAK ${V8_VERSION_TWEAK_DEF}) + + set(V8_VERSION "${V8_VERSION_MAJOR}.${V8_VERSION_MINOR}.${V8_VERSION_PATCH}.${V8_VERSION_TWEAK}") + + set(V8_VERSION_HEX 0x0${V8_VERSION_MAJOR}${V8_VERSION_MINOR}${V8_VERSION_PATCH}${V8_VERSION_TWEAK}) + string(LENGTH "${V8_VERSION_HEX}" V8_VERSION_HEX_LENGTH) + + while(V8_VERSION_HEX_LENGTH LESS 8) + + set(V8_VERSION_HEX "${V8_VERSION_HEX}0") + string(LENGTH "${V8_VERSION_HEX}" V8_VERSION_HEX_LENGTH) + + endwhile() +endif() + +# Detect V8 shell +set(V8_EXECUTABLE_NAMES d8) + +find_program(V8_EXECUTABLE + NAMES ${V8_EXECUTABLE_NAMES} +) + +mark_as_advanced(V8_LIBRARY V8_INCLUDE_DIR) + +if(V8_FOUND) + set(V8_INCLUDE_DIRS ${V8_INCLUDE_DIR}) +endif() + +if(_V8_CMAKE_DEBUG) + message(STATUS "V8_INCLUDE_DIR: ${V8_INCLUDE_DIR}") + message(STATUS "V8_LIBRARIES: ${V8_LIBRARIES}") + message(STATUS "V8_LIBRARIES_DEPENDS: ${V8_LIBRARIES_DEPENDS}") + message(STATUS "V8_VERSION: ${V8_VERSION}") + message(STATUS "V8_VERSION_HEX: ${V8_VERSION_HEX}") + message(STATUS "V8_EXECUTABLE: ${V8_EXECUTABLE}") +endif() diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index 1fed0476c05..ee9f4cf1b3c 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,8 @@ +# Copyright 2018-2019 High Fidelity, Inc. +# Copyright 2020 Vircadia contributors +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 Source: hifi-deps Version: 0.1.5-github-actions Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glad, glm, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android|!(linux&arm)), zlib +Build-Depends: bullet3, draco, etc2comp, glad, glm, node, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android|!(linux&arm)), zlib diff --git a/cmake/ports/node/CONTROL b/cmake/ports/node/CONTROL new file mode 100644 index 00000000000..fa5ca5af7bd --- /dev/null +++ b/cmake/ports/node/CONTROL @@ -0,0 +1,6 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: MIT +Source: node +Version: 18.14.2-1 +Homepage: https://nodejs.org/ +Description: Node.js JavaScript runtime. diff --git a/cmake/ports/node/portfile.cmake b/cmake/ports/node/portfile.cmake new file mode 100644 index 00000000000..5a203e06a95 --- /dev/null +++ b/cmake/ports/node/portfile.cmake @@ -0,0 +1,59 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +set(NODE_VERSION 18.14.2) +set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src) + +file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS) + +if (ANDROID) + # TODO +elseif (WIN32) + vcpkg_download_distfile( + NODE_SOURCE_ARCHIVE + URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/node/node-install-18.15.1-win-x64-release.tar.xz" + SHA512 892608a43ae32b0a82a0e3c7994934d0ce85639ea372c8e7feb7de44220211fa91878bd0744e1488054777807dd5b0c0677b59b44ab5e9fd35ecf222b38d8046 + FILENAME node-install-18.15.1-win-x64-release.tar.xz + ) +elseif (APPLE) + # TODO + vcpkg_download_distfile( + NODE_SOURCE_ARCHIVE + URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/node/node-install-18.14.2-macOSXSDK10.14-macos-amd64-release.tar.xz" + SHA512 TODO + FILENAME node-install-18.14.2-macOSXSDK10.14-macos-amd64-release.tar.xz + ) +else () + # else Linux desktop + if (VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + vcpkg_download_distfile( + NODE_SOURCE_ARCHIVE + URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/node/node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz" + SHA512 ff5ca5c27b811d20ac524346ee122bcd72e9e85c6de6f4799f620bb95dac959ce910cc5bb2162ed741a7f65043aa78173ecd2ce5b92f5a4d91ecb07ce71fa560 + FILENAME node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz + ) + elseif (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64") + vcpkg_download_distfile( + NODE_SOURCE_ARCHIVE + URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/node/node-install-18.16.0-ubuntu-20.04-aarch64-release.tar.xz" + SHA512 aa4814c4ab1a922ec5afd4d7ef08479a32bfd23cb9a745102891bed5a2be13cc912e57e9bf80d856a15a5a9439b67c9a83963c605fdce349236795513090a426 + FILENAME node-install-18.16.0-ubuntu-22.04-aarch64-release.tar.xz + ) + endif () +endif () + +vcpkg_extract_source_archive(MASTER_COPY_SOURCE_PATH ARCHIVE ${NODE_SOURCE_ARCHIVE} NO_REMOVE_ONE_LEVEL) + +# move WIN dll to /bin and WIN .lib to /lib + +if (WIN32) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/include DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/libnode.lib DESTINATION ${CURRENT_PACKAGES_DIR}/lib) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/v8_libplatform.lib DESTINATION ${CURRENT_PACKAGES_DIR}/lib) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/libnode.dll DESTINATION ${CURRENT_PACKAGES_DIR}/bin) +else () + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/include DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/lib DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/share DESTINATION ${CURRENT_PACKAGES_DIR}) + file(COPY ${MASTER_COPY_SOURCE_PATH}/node-install/bin DESTINATION ${CURRENT_PACKAGES_DIR}) +endif () diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index a3a85684b4d..49c9045cfd8 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright 2014-2018 High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + set(TARGET_NAME domain-server) if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32) @@ -25,6 +29,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/hifi_qt.py b/hifi_qt.py index d3d9d0b320b..f163bf6d466 100644 --- a/hifi_qt.py +++ b/hifi_qt.py @@ -169,7 +169,9 @@ def __init__(self, args): if u_major == 18: self.qtUrl = 'http://motofckr9k.ddns.net/vircadia_packages/qt5-install-5.15.2-ubuntu-18.04-aarch64_test.tar.xz' - elif u_major > 19: + elif u_major == 20: + self.qtUrl = self.assets_url + '/dependencies/qt5/qt5-install-5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2-ubuntu-20.04-aarch64.tar.xz' + elif u_major > 20: self.__no_qt_package_error() else: self.__unsupported_error() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 90c32ed2b71..7262ff00058 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -1,3 +1,8 @@ +# Copyright 2013-2019 High Fidelity, Inc. +# Copyright 2019-2021 Vircadia contributors +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + if (APPLE) # Make the OSX DMG app be Overte.app. set(TARGET_NAME Overte) @@ -21,7 +26,7 @@ set(CUSTOM_INTERFACE_QRC_PATHS "") find_package( Qt5 COMPONENTS - Gui Widgets Multimedia Network Qml Quick Script Svg WebEngineCore WebEngineWidgets + Gui Widgets Multimedia Network Qml Quick Svg WebEngineCore WebEngineWidgets ${PLATFORM_QT_COMPONENTS} WebChannel WebSockets ) @@ -217,7 +222,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 +232,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 +297,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 Qt5::WebEngineCore Qt5::WebEngineWidgets ${PLATFORM_QT_LIBRARIES} ) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 76551be906d..6f2d711032d 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -39,8 +39,9 @@ Rectangle { } Component.onCompleted: { - AudioScriptingInterface.noiseGateOpened.connect(function() { micBar.gated = false; }); - AudioScriptingInterface.noiseGateClosed.connect(function() { micBar.gated = true; }); + //TODO: this was causing type errors + //AudioScriptingInterface.noiseGateOpened.connect(function() { micBar.gated = false; }); + //AudioScriptingInterface.noiseGateClosed.connect(function() { micBar.gated = true; }); HMD.displayModeChanged.connect(function() { muted = AudioScriptingInterface.muted; pushToTalk = AudioScriptingInterface.pushToTalk; diff --git a/interface/resources/serverless/Scripts/activator-doppleganger.js b/interface/resources/serverless/Scripts/activator-doppleganger.js index bcb9b7c5093..89661683d3a 100644 --- a/interface/resources/serverless/Scripts/activator-doppleganger.js +++ b/interface/resources/serverless/Scripts/activator-doppleganger.js @@ -1,14 +1,15 @@ -'use strict'; +'no use strict'; // // activator-doppleganger.js // // Created by Alezia Kurdis on February 20th, 2022. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // -// This script is display a doppleganger of the user by entering an entity. +// This script displays a doppleganger of the user when entering an entity. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // (function() { @@ -23,6 +24,8 @@ autoUpdate: true }); + // V8TODO: does this need to be fixed? Right now it refers to global object in non-strict mode, + // and in strict mode it's undefined this.preload = function(entityID) { thisEntityID = entityID; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 85a1df56084..b5984c6b65e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5,10 +5,11 @@ // Created by Andrzej Kapolka on 5/10/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -130,15 +131,19 @@ #include #include #include -#include +#include +#include #include #include #include #include #include #include -#include #include +#include +#include +#include +#include #include #include #include @@ -166,6 +171,7 @@ #include #include #include "recording/ClipCache.h" +#include #include "AudioClient.h" #include "audio/AudioScope.h" @@ -236,7 +242,6 @@ #include #include #include -#include #include #include #include @@ -443,38 +448,38 @@ class DeadlockWatchdogThread : public QThread { if (elapsedMovingAverage > _maxElapsedAverage * 1.1f) { #if !defined(NDEBUG) - qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" +/* qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "maxElapsed:" << _maxElapsed << "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage << "NEW maxElapsedAverage:" << elapsedMovingAverage << "** NEW MAX ELAPSED AVERAGE **" - << "samples:" << _movingAverage.getSamples(); + << "samples:" << _movingAverage.getSamples();*/ #endif _maxElapsedAverage = elapsedMovingAverage; } if (lastHeartbeatAge > _maxElapsed) { #if !defined(NDEBUG) - qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" +/* qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "PREVIOUS maxElapsed:" << _maxElapsed << "NEW maxElapsed:" << lastHeartbeatAge << "** NEW MAX ELAPSED **" << "maxElapsedAverage:" << _maxElapsedAverage - << "samples:" << _movingAverage.getSamples(); + << "samples:" << _movingAverage.getSamples();*/ #endif _maxElapsed = lastHeartbeatAge; } #if !defined(NDEBUG) - if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) { +/* if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) { qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "** OVER EXPECTED VALUE **" << "maxElapsed:" << _maxElapsed << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); - } + }*/ #endif if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { @@ -800,7 +805,6 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b // Set dependencies DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -814,7 +818,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b #endif DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption); + DependencyManager::set(ScriptManager::CLIENT_SCRIPT, defaultScriptsOverrideOption); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -973,6 +977,12 @@ QSharedPointer getOffscreenUI() { #endif } +bool Application::initMenu() { + _isMenuInitialized = false; + qApp->getWindow()->menuBar(); + return true; +} + Application::Application( int& argc, char** argv, const QCommandLineParser& parser, @@ -981,6 +991,8 @@ Application::Application( ) : QApplication(argc, argv), _window(new MainWindow(desktop())), + // Menu needs to be initialized before other initializers. Otherwise deadlock happens on qApp->getWindow()->menuBar(). + _isMenuInitialized(initMenu()), _sessionRunTimer(startupTimer), #ifndef Q_OS_ANDROID _logger(new FileLogger(this)), @@ -1013,7 +1025,6 @@ Application::Application( _snapshotSound(nullptr), _sampleSound(nullptr) { - auto steamClient = PluginManager::getInstance()->getSteamClientPlugin(); setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); setProperty(hifi::properties::CRASHED, _previousSessionCrashed); @@ -1399,8 +1410,8 @@ Application::Application( { 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] { @@ -2403,7 +2414,7 @@ Application::Application( // Setup the mouse ray pick and related operators { - auto mouseRayPick = std::make_shared(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_LOCAL_ENTITIES()), 0.0f, true); + auto mouseRayPick = std::make_shared(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, true); mouseRayPick->parentTransform = std::make_shared(); mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE); auto mouseRayPickID = DependencyManager::get()->addPick(PickQuery::Ray, mouseRayPick); @@ -4221,6 +4232,11 @@ bool Application::event(QEvent* event) { return false; } + // This helps avoid deadlock issue early during Application initialization + if (!_isMenuInitialized) { + return QApplication::event(event); + } + if (!Menu::getInstance()) { return false; } @@ -5835,7 +5851,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); } } } @@ -5846,7 +5862,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); } } @@ -6034,7 +6050,8 @@ void Application::reloadResourceCaches() { getEntities()->clear(); DependencyManager::get()->clearCache(); - DependencyManager::get()->clearCache(); + //It's already cleared in reloadAllScripts so I'm not sure this is necessary. + //DependencyManager::get()->clearCache(); // Clear all the resource caches DependencyManager::get()->clear(); @@ -7436,9 +7453,10 @@ void Application::addingEntityWithCertificate(const QString& certificateID, cons ledger->updateLocation(certificateID, placeName); } -void Application::registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) { +void Application::registerScriptEngineWithApplicationServices(ScriptManagerPointer& scriptManager) { - scriptEngine->setEmitScriptUpdatesFunction([this]() { + auto scriptEngine = scriptManager->engine(); + scriptManager->setEmitScriptUpdatesFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); return !entityServerNode || isPhysicsEnabled(); }); @@ -7456,9 +7474,6 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("PlatformInfo", PlatformInfoScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this)); - // hook our avatar and avatar hash map object into this script engine - getMyAvatar()->registerMetaTypes(scriptEngine); - scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Camera", &_myCamera); @@ -7470,13 +7485,11 @@ 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, - RayToOverlayIntersectionResultFromScriptValue); - bool clientScript = scriptEngine->isClientScript(); + bool clientScript = scriptManager->isClientScript(); #if !defined(DISABLE_QML) scriptEngine->registerGlobalObject("OffscreenFlags", getOffscreenUI()->getFlags()); @@ -7491,14 +7504,8 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine } #endif - qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - qScriptRegisterMetaType(scriptEngine.data(), - wrapperToScriptValue, wrapperFromScriptValue); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); - qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - qScriptRegisterMetaType(scriptEngine.data(), - wrapperToScriptValue, wrapperFromScriptValue); scriptEngine->registerGlobalObject("Tablet", DependencyManager::get().data()); // FIXME remove these deprecated names for the tablet scripting interface scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); @@ -7532,7 +7539,6 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("RayPick", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("LaserPointers", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Picks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Pointers", DependencyManager::get().data()); @@ -7548,12 +7554,10 @@ 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); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data()); - qScriptRegisterMetaType(scriptEngine.data(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue); scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface()); @@ -7568,7 +7572,6 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("Render", RenderScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Workload", _gameWorkload._engine->getConfiguration().get()); - GraphicsScriptingInterface::registerMetaTypes(scriptEngine.data()); scriptEngine->registerGlobalObject("Graphics", DependencyManager::get().data()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get().data()); @@ -7577,14 +7580,20 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get().data()); + //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()); + scriptManager->connect(scriptManager.get(), &ScriptManager::scriptEnding, [scriptManager]() { + // Request removal of controller routes with callbacks to a given script engine + auto userInputMapper = DependencyManager::get(); + userInputMapper->scheduleScriptEndpointCleanup(scriptManager->engine().get()); + // V8TODO: Maybe we should wait until removal is finished if there are still crashes + }); + UserInputMapper::registerControllerTypes(scriptEngine.get()); auto recordingInterface = DependencyManager::get(); scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); @@ -7600,18 +7609,13 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); // Deprecated. scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get().data()); - registerInteractiveWindowMetaType(scriptEngine.data()); - - auto pickScriptingInterface = DependencyManager::get(); - pickScriptingInterface->registerMetaTypes(scriptEngine.data()); - // 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); } @@ -7998,6 +8002,7 @@ void Application::addAssetToWorldFromURL(QString url) { void Application::addAssetToWorldFromURLRequestFinished() { auto request = qobject_cast(sender()); + Q_ASSERT(request != nullptr); auto url = request->getUrl().toString(); auto result = request->getResult(); @@ -8220,7 +8225,7 @@ void Application::addAssetToWorldCheckModelSize() { propertyFlags += PROP_NAME; propertyFlags += PROP_DIMENSIONS; auto entityScriptingInterface = DependencyManager::get(); - auto properties = entityScriptingInterface->getEntityProperties(entityID, propertyFlags); + auto properties = entityScriptingInterface->getEntityPropertiesInternal(entityID, propertyFlags); auto name = properties.getName(); auto dimensions = properties.getDimensions(); @@ -9210,7 +9215,7 @@ void Application::updateLoginDialogPosition() { auto entityScriptingInterface = DependencyManager::get(); EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; - auto properties = entityScriptingInterface->getEntityProperties(_loginDialogID, desiredProperties); + auto properties = entityScriptingInterface->getEntityPropertiesInternal(_loginDialogID, desiredProperties); auto positionVec = properties.getPosition(); auto cameraPositionVec = _myCamera.getPosition(); auto cameraOrientation = cancelOutRollAndPitch(_myCamera.getOrientation()); @@ -9366,6 +9371,7 @@ void Application::setActiveDisplayPlugin(const QString& pluginName) { void Application::handleLocalServerConnection() const { auto server = qobject_cast(sender()); + Q_ASSERT(server != nullptr); qCDebug(interfaceapp) << "Got connection on local server from additional instance - waiting for parameters"; @@ -9379,6 +9385,7 @@ void Application::handleLocalServerConnection() const { void Application::readArgumentsFromLocalSocket() const { auto socket = qobject_cast(sender()); + Q_ASSERT(socket != nullptr); auto message = socket->readAll(); socket->deleteLater(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 222dbd9d404..005ec3cb184 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -5,16 +5,18 @@ // Created by Andrzej Kapolka on 5/10/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_Application_h #define hifi_Application_h #include +#include #include #include @@ -23,11 +25,13 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -42,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -91,6 +94,8 @@ class MainWindow; class AssetUpload; class CompositorHelper; class AudioInjector; +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; namespace controller { class StateController; @@ -249,7 +254,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(ScriptManagerPointer& scriptManager) override; virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { copyDisplayViewFrustum(viewOut); } virtual QThread* getMainThread() override { return thread(); } @@ -557,6 +562,7 @@ private slots: private: void init(); + bool initMenu(); void pauseUntilLoginDetermined(); void resumeAfterLoginDialogActionTaken(); bool handleKeyEventForFocusedEntity(QEvent* event); @@ -621,6 +627,10 @@ private slots: void userKickConfirmation(const QUuid& nodeID, unsigned int banFlags = ModerationFlags::getDefaultBanFlags()); MainWindow* _window; + + // _isMenuInitialized: used to initialize menu early enough before it's needed by other + // initializers. Fixes a deadlock issue with recent Qt versions. + bool _isMenuInitialized; QElapsedTimer& _sessionRunTimer; bool _aboutToQuit { false }; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 0d999b2ff13..256ce2f6fc1 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -5,9 +5,11 @@ // Created by Triplelexx on 23/03/17. // Copyright 2017 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AvatarBookmarks.h" @@ -50,12 +52,13 @@ void addAvatarEntities(const QVariantList& avatarEntities) { EntitySimulationPointer entitySimulation = entityTree->getSimulation(); PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); - QScriptEngine scriptEngine; + // V8TODO: Creating new script engine each time is very inefficient + 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); @@ -302,7 +305,8 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - QScriptEngine scriptEngine; + // V8TODO: Creating new script engine each time is very inefficient + ScriptEnginePointer scriptEngine = newScriptEngine(); auto avatarEntities = myAvatar->getAvatarEntityDataNonDefault(); for (auto entityID : avatarEntities.keys()) { auto entity = entityTree->findEntityByID(entityID); @@ -322,7 +326,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..1eb8580ac7b 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -5,9 +5,11 @@ // Created by Clement on 1/16/15. // Copyright 2015 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "LODManager.h" @@ -15,11 +17,18 @@ #include #include #include +#include #include "Application.h" #include "ui/DialogsManager.h" #include "InterfaceLogging.h" +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); +})); + const QString LOD_SETTINGS_PREFIX { "lodManager/" }; Setting::Handle automaticLODAdjust(LOD_SETTINGS_PREFIX + "automaticLODAdjust", (bool)DEFAULT_LOD_AUTO_ADJUST); @@ -426,13 +435,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..125dd0dd93d 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -5,9 +5,11 @@ // Created by Clement on 1/16/15. // Copyright 2015 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_LODManager_h @@ -22,7 +24,9 @@ #include #include #include +#include +class ScriptEngine; /*@jsdoc *

The world detail quality rendered.

@@ -380,7 +384,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/LocationBookmarks.cpp b/interface/src/LocationBookmarks.cpp index fda70d379d8..4612e5c873e 100644 --- a/interface/src/LocationBookmarks.cpp +++ b/interface/src/LocationBookmarks.cpp @@ -4,9 +4,11 @@ // // Created by Triplelexx on 23/03/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "LocationBookmarks.h" @@ -73,6 +75,7 @@ QString LocationBookmarks::getAddress(const QString& bookmarkName) { void LocationBookmarks::teleportToBookmark() { QAction* action = qobject_cast(sender()); + Q_ASSERT(action != nullptr); QString address = action->data().toString(); DependencyManager::get()->handleLookupString(address); } diff --git a/interface/src/LoginStateManager.cpp b/interface/src/LoginStateManager.cpp index 0a09d33775d..f11f636f034 100644 --- a/interface/src/LoginStateManager.cpp +++ b/interface/src/LoginStateManager.cpp @@ -25,25 +25,16 @@ #include "scripting/ControllerScriptingInterface.h" static const float SEARCH_SPHERE_SIZE = 0.0132f; -static const QVariantMap SEARCH_SPHERE = {{"x", SEARCH_SPHERE_SIZE}, - {"y", SEARCH_SPHERE_SIZE}, - {"z", SEARCH_SPHERE_SIZE}}; -static const int DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? - -static const QVariantMap COLORS_GRAB_SEARCHING_HALF_SQUEEZE = {{"red", 10}, - {"green", 10}, - {"blue", 255}}; +static const glm::vec3 SEARCH_SPHERE(SEARCH_SPHERE_SIZE, SEARCH_SPHERE_SIZE, SEARCH_SPHERE_SIZE); -static const QVariantMap COLORS_GRAB_SEARCHING_FULL_SQUEEZE = {{"red", 250}, - {"green", 10}, - {"blue", 10}}; +static const int DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? -static const QVariantMap COLORS_GRAB_DISTANCE_HOLD = {{"red", 238}, - {"green", 75}, - {"blue", 214}}; +static const glm::u8vec3 COLORS_GRAB_SEARCHING_HALF_SQUEEZE(10, 10, 255); +static const glm::u8vec3 COLORS_GRAB_SEARCHING_FULL_SQUEEZE(250, 10, 10); +static const glm::u8vec3 COLORS_GRAB_DISTANCE_HOLD(238, 75, 215); void LoginStateManager::tearDown() { auto pointers = DependencyManager::get().data(); @@ -60,90 +51,88 @@ void LoginStateManager::tearDown() { } void LoginStateManager::setUp() { - QVariantMap fullPathRenderState { - {"type", "line3d"}, - {"color", COLORS_GRAB_SEARCHING_FULL_SQUEEZE}, - {"visible", true}, - {"alpha", 1.0f}, - {"solid", true}, - {"glow", 1.0f}, - {"ignoreRayIntersection", true}, // always ignore this - {"drawInFront", true}, // Even when burried inside of something, show it. - {"drawHUDLayer", false} - }; - QVariantMap fullEndRenderState { - {"type", "sphere"}, - {"dimensions", SEARCH_SPHERE}, - {"solid", true}, - {"color", COLORS_GRAB_SEARCHING_FULL_SQUEEZE}, - {"alpha", 0.9f}, - {"ignoreRayIntersection", true}, - {"drawInFront", true}, // Even when burried inside of something, show it. - {"drawHUDLayer", false}, - {"visible", true} - }; - QVariantMap halfPathRenderState { - {"type", "line3d"}, - {"color", COLORS_GRAB_SEARCHING_HALF_SQUEEZE}, - {"visible", true}, - {"alpha", 1.0f}, - {"solid", true}, - {"glow", 1.0f}, - {"ignoreRayIntersection", true}, // always ignore this - {"drawInFront", true}, // Even when burried inside of something, show it. - {"drawHUDLayer", false} - }; - QVariantMap halfEndRenderState { - {"type", "sphere"}, - {"dimensions", SEARCH_SPHERE}, - {"solid", true}, - {"color", COLORS_GRAB_SEARCHING_HALF_SQUEEZE}, - {"alpha", 0.9f}, - {"ignoreRayIntersection", true}, - {"drawInFront", true}, // Even when burried inside of something, show it. - {"drawHUDLayer", false}, - {"visible", true} - }; - QVariantMap holdPathRenderState { - {"type", "line3d"}, - {"color", COLORS_GRAB_DISTANCE_HOLD}, - {"visible", true}, - {"alpha", 1.0f}, - {"solid", true}, - {"glow", 1.0f}, - {"ignoreRayIntersection", true}, // always ignore this - {"drawInFront", true}, // Even when burried inside of something, show it. - {"drawHUDLayer", false}, - }; + QList entityProperties; + + //V8TODO: are points and normals needed here + EntityItemProperties fullPathRenderState; + fullPathRenderState.setType(EntityTypes::PolyLine); + fullPathRenderState.setColor(COLORS_GRAB_SEARCHING_FULL_SQUEEZE); + fullPathRenderState.setGlow(true); + fullPathRenderState.setIgnorePickIntersection(true); // always ignore this + fullPathRenderState.setRenderLayer(RenderLayer::FRONT); // Even when buried inside of something, show it. + fullPathRenderState.setFaceCamera(true); + int fullPathRenderStateIndex = entityProperties.length(); + entityProperties.append(fullPathRenderState); + + EntityItemProperties fullEndRenderState; + fullEndRenderState.setType(EntityTypes::Sphere); + fullEndRenderState.setDimensions(SEARCH_SPHERE); + fullEndRenderState.setColor(COLORS_GRAB_SEARCHING_FULL_SQUEEZE); + fullEndRenderState.setAlpha(0.9f); + fullEndRenderState.setIgnorePickIntersection(true); // always ignore this + fullEndRenderState.setRenderLayer(RenderLayer::FRONT); // Even when buried inside of something, show it. + int fullEndRenderStateIndex = entityProperties.length(); + entityProperties.append(fullEndRenderState); + + EntityItemProperties halfPathRenderState; + halfPathRenderState.setType(EntityTypes::PolyLine); + halfPathRenderState.setColor(COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + halfPathRenderState.setGlow(true); + halfPathRenderState.setIgnorePickIntersection(true); // always ignore this + halfPathRenderState.setRenderLayer(RenderLayer::FRONT); // Even when buried inside of something, show it. + halfPathRenderState.setFaceCamera(true); + int halfPathRenderStateIndex = entityProperties.length(); + entityProperties.append(halfPathRenderState); + + EntityItemProperties halfEndRenderState; + halfEndRenderState.setType(EntityTypes::Sphere); + halfEndRenderState.setDimensions(SEARCH_SPHERE); + halfEndRenderState.setColor(COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + halfEndRenderState.setAlpha(0.9f); + halfEndRenderState.setIgnorePickIntersection(true); // always ignore this + halfEndRenderState.setRenderLayer(RenderLayer::FRONT); // Even when buried inside of something, show it. + int halfEndRenderStateIndex = entityProperties.length(); + entityProperties.append(halfEndRenderState); + + EntityItemProperties holdPathRenderState; + holdPathRenderState.setType(EntityTypes::PolyLine); + holdPathRenderState.setColor(COLORS_GRAB_DISTANCE_HOLD); + holdPathRenderState.setGlow(true); + holdPathRenderState.setIgnorePickIntersection(true); // always ignore this + holdPathRenderState.setRenderLayer(RenderLayer::FRONT); // Even when buried inside of something, show it. + holdPathRenderState.setFaceCamera(true); + int holdPathRenderStateIndex = entityProperties.length(); + entityProperties.append(holdPathRenderState); + QVariantMap halfRenderStateIdentifier { {"name", "half"}, - {"path", halfPathRenderState}, - {"end", halfEndRenderState} + {"pathPropertyIndex", halfPathRenderStateIndex}, + {"endPropertyIndex", halfEndRenderStateIndex} }; QVariantMap fullRenderStateIdentifier { {"name", "full"}, - {"path", fullPathRenderState}, - {"end", fullEndRenderState} + {"pathPropertyIndex", fullPathRenderStateIndex}, + {"endPropertyIndex", fullEndRenderStateIndex} }; QVariantMap holdRenderStateIdentifier { {"name", "hold"}, - {"path", holdPathRenderState}, + {"pathPropertyIndex", holdPathRenderStateIndex}, }; QVariantMap halfDefaultRenderStateIdentifier { {"name", "half"}, {"distance", DEFAULT_SEARCH_SPHERE_DISTANCE}, - {"path", halfPathRenderState} + {"pathPropertyIndex", halfPathRenderStateIndex} }; QVariantMap fullDefaultRenderStateIdentifier { {"name", "full"}, {"distance", DEFAULT_SEARCH_SPHERE_DISTANCE}, - {"path", fullPathRenderState} + {"pathPropertyIndex", fullPathRenderStateIndex} }; QVariantMap holdDefaultRenderStateIdentifier { {"name", "hold"}, {"distance", DEFAULT_SEARCH_SPHERE_DISTANCE}, - {"path", holdPathRenderState} + {"pathPropertyIndex", holdPathRenderStateIndex} }; _renderStates = QList({halfRenderStateIdentifier, fullRenderStateIdentifier, holdRenderStateIdentifier}); @@ -168,9 +157,9 @@ void LoginStateManager::setUp() { leftPointerTriggerProperties = QList({ltClick1, ltClick2}); const unsigned int leftHand = 0; - QVariantMap leftPointerProperties { + QVariantMap leftPointerPropertiesMap { { "joint", "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" }, - { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, + { "filter", PickScriptingInterface::getPickLocalEntities() }, { "triggers", leftPointerTriggerProperties }, { "posOffset", vec3toVariant(grabPointSphereOffsetLeft + malletOffset) }, { "hover", true }, @@ -178,9 +167,12 @@ void LoginStateManager::setUp() { { "distanceScaleEnd", true }, { "hand", leftHand } }; - leftPointerProperties["renderStates"] = _renderStates; - leftPointerProperties["defaultRenderStates"] = _defaultRenderStates; - _leftLoginPointerID = pointers->createPointer(PickQuery::PickType::Ray, leftPointerProperties); + leftPointerPropertiesMap["renderStates"] = _renderStates; + leftPointerPropertiesMap["defaultRenderStates"] = _defaultRenderStates; + RayPointerProperties leftPointerProperties; + leftPointerProperties.properties = leftPointerPropertiesMap; + leftPointerProperties.entityProperties = entityProperties; + _leftLoginPointerID = pointers->createRayPointer(leftPointerProperties); pointers->setRenderState(_leftLoginPointerID, ""); pointers->enablePointer(_leftLoginPointerID); const unsigned int rightHand = 1; @@ -195,9 +187,9 @@ void LoginStateManager::setUp() { { "button", "Primary" } }; rightPointerTriggerProperties = QList({rtClick1, rtClick2}); - QVariantMap rightPointerProperties{ + QVariantMap rightPointerPropertiesMap{ { "joint", "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" }, - { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, + { "filter", PickScriptingInterface::getPickLocalEntities() }, { "triggers", rightPointerTriggerProperties }, { "posOffset", vec3toVariant(grabPointSphereOffsetRight + malletOffset) }, { "hover", true }, @@ -205,9 +197,12 @@ void LoginStateManager::setUp() { { "distanceScaleEnd", true }, { "hand", rightHand } }; - rightPointerProperties["renderStates"] = _renderStates; - rightPointerProperties["defaultRenderStates"] = _defaultRenderStates; - _rightLoginPointerID = pointers->createPointer(PickQuery::PickType::Ray, rightPointerProperties); + rightPointerPropertiesMap["renderStates"] = _renderStates; + rightPointerPropertiesMap["defaultRenderStates"] = _defaultRenderStates; + RayPointerProperties rightPointerProperties; + rightPointerProperties.properties = rightPointerPropertiesMap; + rightPointerProperties.entityProperties = entityProperties; + _rightLoginPointerID = pointers->createRayPointer(rightPointerProperties); pointers->setRenderState(_rightLoginPointerID, ""); pointers->enablePointer(_rightLoginPointerID); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 613ad86ae90..cec88827e98 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -5,9 +5,11 @@ // Created by Stephen Birarda on 8/12/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // For happ(ier) development of QML, use these two things: // This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1 @@ -22,6 +24,7 @@ #include #include +#include #include #include #include diff --git a/interface/src/PerformanceManager.cpp b/interface/src/PerformanceManager.cpp index 190071724a6..f74cfab88af 100644 --- a/interface/src/PerformanceManager.cpp +++ b/interface/src/PerformanceManager.cpp @@ -4,9 +4,11 @@ // // Created by Sam Gateau on 2019-05-29. // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PerformanceManager.h" @@ -19,7 +21,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/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index b61f1e944b0..09428da5eac 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -4,16 +4,35 @@ // // Created by Dante Ruiz on 2019-04-15. // Copyright 2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RefreshRateManager.h" #include +#include +#include +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "RefreshRateRegime"); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "UXMode"); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptEngine->registerEnum("RefreshRateRegime",QMetaEnum::fromType()); + + scriptEngine->registerEnum("UXMode",QMetaEnum::fromType()); +}); static const int VR_TARGET_RATE = 90; diff --git a/interface/src/RefreshRateManager.h b/interface/src/RefreshRateManager.h index cab7539823e..4b91f0c45e3 100644 --- a/interface/src/RefreshRateManager.h +++ b/interface/src/RefreshRateManager.h @@ -4,9 +4,11 @@ // // Created by Dante Ruiz on 2019-04-15. // Copyright 2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RefreshRateManager_h @@ -19,9 +21,12 @@ #include #include +#include +#include #include -class RefreshRateManager { +class RefreshRateManager : QObject { + Q_OBJECT public: enum RefreshRateProfile { ECO = 0, @@ -29,6 +34,7 @@ class RefreshRateManager { REALTIME, PROFILE_NUM }; + Q_ENUM(RefreshRateProfile) static bool isValidRefreshRateProfile(RefreshRateProfile value) { return (value >= RefreshRateProfile::ECO && value <= RefreshRateProfile::REALTIME); } /*@jsdoc @@ -57,6 +63,7 @@ class RefreshRateManager { SHUTDOWN, REGIME_NUM }; + Q_ENUM(RefreshRateRegime) static bool isValidRefreshRateRegime(RefreshRateRegime value) { return (value >= RefreshRateRegime::FOCUS_ACTIVE && value <= RefreshRateRegime::SHUTDOWN); } /*@jsdoc @@ -77,6 +84,7 @@ class RefreshRateManager { VR, UX_NUM }; + Q_ENUM(UXMode) static bool isValidUXMode(UXMode value) { return (value >= UXMode::DESKTOP && value <= UXMode::VR); } RefreshRateManager(); @@ -110,7 +118,7 @@ class RefreshRateManager { mutable int _activeRefreshRate { 20 }; RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE}; RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP }; - UXMode _uxMode; + UXMode _uxMode { UXMode::DESKTOP }; mutable ReadWriteLockable _refreshRateProfileSettingLock; Setting::Handle _refreshRateProfileSetting { "refreshRateProfile", RefreshRateProfile::INTERACTIVE }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 943845bda75..1b11850b9d2 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -4,16 +4,19 @@ // // Created by Stephen Birarda on 1/23/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AvatarManager.h" #include -#include +#include +#include #include "AvatarLogging.h" @@ -735,8 +738,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); @@ -860,6 +863,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.avatarID = rayAvatarResult._intersectWithAvatar; result.distance = rayAvatarResult._distance; result.face = face; + Q_ASSERT(face < 7); result.intersection = ray.origin + rayAvatarResult._distance * rayDirection; result.surfaceNormal = rayAvatarResult._intersectionNormal; result.jointIndex = rayAvatarResult._intersectWithJoint; @@ -953,6 +957,7 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector result.avatarID = sortedAvatar.second->getID(); result.parabolicDistance = parabolicDistance; result.face = face; + Q_ASSERT(face < 7); result.surfaceNormal = surfaceNormal; result.extraInfo = extraInfo; } @@ -980,7 +985,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..872224922ff 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 1/23/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AvatarManager_h @@ -26,13 +28,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 +97,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 +188,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 +232,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 9e8852c4233..258685662d2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5,9 +5,11 @@ // Created by Mark Peng on 8/16/13. // Copyright 2012 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MyAvatar.h" @@ -43,13 +45,16 @@ #include #include #include +#include +#include +#include #include #include #include #include #include #include -#include +#include #include #include #include @@ -110,6 +115,23 @@ const QString POINT_BLEND_LINEAR_ALPHA_NAME = "pointBlendAlpha"; const QString POINT_REF_JOINT_NAME = "RightShoulder"; const float POINT_ALPHA_BLENDING = 1.0f; +STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine(); + + MyAvatar::registerMetaTypes(scriptEngine); +}); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine(); + + auto avatarManager = DependencyManager::get(); + if (avatarManager) { + avatarManager->getMyAvatar()->registerProperties(scriptEngine); + } else { + qWarning(scriptengine) << "Cannot register MyAvatar with script engine, AvatarManager instance not available"; + } +}); + const std::array(MyAvatar::AllowAvatarStandingPreference::Count)> MyAvatar::allowAvatarStandingPreferenceStrings = { QStringLiteral("WhenUserIsStanding"), @@ -378,8 +400,6 @@ MyAvatar::MyAvatar(QThread* thread) : MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); - delete _scriptEngine; - _scriptEngine = nullptr; if (_addAvatarEntitiesToTreeTimer.isActive()) { _addAvatarEntitiesToTreeTimer.stop(); } @@ -439,20 +459,24 @@ void MyAvatar::enableHandTouchForID(const QUuid& entityID) { } void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) { - QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); + scriptRegisterMetaType(engine.get()); + scriptRegisterMetaType(engine.get(), "DriveKeys"); + qDebug() << "MyAvatar::registerMetaTypes"; +} + +void MyAvatar::registerProperties(ScriptEnginePointer engine) { + 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); } + void MyAvatar::setOrientationVar(const QVariant& newOrientationVar) { Avatar::setWorldOrientation(quatFromVariant(newOrientationVar)); } @@ -2065,7 +2089,7 @@ void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const { void MyAvatar::loadData() { if (!_scriptEngine) { - _scriptEngine = new QScriptEngine(); + _scriptEngine = newScriptEngine(); } getHead()->setBasePitch(_headPitchSetting.get()); @@ -2673,8 +2697,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)); @@ -5704,20 +5727,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 b1c7762c3be..12104c0ad5b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -5,16 +5,18 @@ // Created by Mark Peng on 8/16/13. // Copyright 2012 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_MyAvatar_h #define hifi_MyAvatar_h #include +#include #include @@ -28,10 +30,10 @@ #include #include #include -#include #include #include #include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -41,6 +43,8 @@ class AvatarActionHold; class ModelItemID; class MyHead; class DetailedMotionState; +class ScriptEngine; +using ScriptEnginePointer = std::shared_ptr; /*@jsdoc *

Locomotion control types.

@@ -581,7 +585,8 @@ class MyAvatar : public Avatar { virtual ~MyAvatar(); void instantiableAvatar() override {}; - void registerMetaTypes(ScriptEnginePointer engine); + static void registerMetaTypes(ScriptEnginePointer engine); + void registerProperties(ScriptEnginePointer engine); virtual void simulateAttachments(float deltaTime) override; @@ -870,7 +875,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. @@ -878,7 +883,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 @@ -1213,8 +1218,6 @@ class MyAvatar : public Avatar { * @function MyAvatar.getTargetAvatar * @returns {ScriptAvatar} Information on the avatar being looked at, null if no avatar is being looked at. */ - // FIXME: The return type doesn't have a conversion to a script value so the function always returns undefined in - // JavaScript. Note: When fixed, JSDoc is needed for the return type. Q_INVOKABLE ScriptAvatarData* getTargetAvatar() const; @@ -2288,13 +2291,6 @@ public slots: */ void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override; - /*@jsdoc - * @function MyAvatar.sanitizeAvatarEntityProperties - * @param {EntityItemProperties} properties - Properties. - * @deprecated This function is deprecated and will be removed. - */ - void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const; - /*@jsdoc * Sets whether your avatar mesh is visible to you. * @function MyAvatar.setEnableMeshVisible @@ -2686,6 +2682,7 @@ private slots: virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; int _skeletonModelChangeCount { 0 }; + void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const; void saveAvatarScale(); @@ -3099,7 +3096,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 }; @@ -3116,11 +3113,13 @@ private slots: QTimer _addAvatarEntitiesToTreeTimer; }; -QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); -void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode); +Q_DECLARE_METATYPE(MyAvatar::DriveKeys) + +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..78962efe323 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -4,9 +4,11 @@ // // Created by Howard Stearns on 8/4/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "QmlCommerce.h" @@ -374,7 +376,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/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index 53c8bd7c184..bf69efd23e8 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -3,9 +3,11 @@ // // Created by Sam Gateau on 29/6/2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "GraphicsEngine.h" @@ -187,7 +189,7 @@ void GraphicsEngine::render_performFrame() { { PROFILE_RANGE(render, "/pluginBeginFrameRender"); - // If a display plugin loses it's underlying support, it + // If a display plugin loses its underlying support, it // needs to be able to signal us to not use it if (!displayPlugin->beginFrameRender(_renderFrameCount)) { QMetaObject::invokeMethod(qApp, "updateDisplayMode"); @@ -267,7 +269,7 @@ void GraphicsEngine::render_performFrame() { PROFILE_RANGE(render, "/renderOverlay"); PerformanceTimer perfTimer("renderOverlay"); // NOTE: There is no batch associated with this renderArgs - // the ApplicationOverlay class assumes it's viewport is setup to be the device size + // the ApplicationOverlay class assumes it's viewport is set up to be the device size renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize()); qApp->getApplicationOverlay().renderOverlay(&renderArgs); } diff --git a/interface/src/networking/CloseEventSender.cpp b/interface/src/networking/CloseEventSender.cpp index 9cfcda108a7..7f41e9ee5b0 100644 --- a/interface/src/networking/CloseEventSender.cpp +++ b/interface/src/networking/CloseEventSender.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 5/31/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "CloseEventSender.h" @@ -74,6 +76,7 @@ void CloseEventSender::handleQuitEventFinished() { _hasFinishedQuitEvent = true; auto reply = qobject_cast(sender()); + Q_ASSERT(reply != nullptr); if (reply->error() == QNetworkReply::NoError) { qCDebug(networking) << "Quit event sent successfully"; } else { diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index b97c9b89cbe..8d184790912 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gondelman 7/11/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -32,6 +33,7 @@ PickQuery::PickType LaserPointer::getType() const { } void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + //V8TODO pathProps are not a thing anymore auto renderState = std::static_pointer_cast(_renderStates[state]); if (renderState) { updateRenderState(renderState->getPathID(), pathProps); @@ -135,12 +137,12 @@ LaserPointer::RenderState::RenderState(const QUuid& startID, const QUuid& pathID { EntityPropertyFlags desiredProperties; desiredProperties += PROP_IGNORE_PICK_INTERSECTION; - _pathIgnorePicks = entityScriptingInterface->getEntityProperties(getPathID(), desiredProperties).getIgnorePickIntersection(); + _pathIgnorePicks = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties).getIgnorePickIntersection(); } { EntityPropertyFlags desiredProperties; desiredProperties += PROP_STROKE_WIDTHS; - auto widths = entityScriptingInterface->getEntityProperties(getPathID(), desiredProperties).getStrokeWidths(); + auto widths = entityScriptingInterface->getEntityPropertiesInternal(getPathID(), desiredProperties).getStrokeWidths(); _lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0]; } } @@ -172,9 +174,14 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& points.append(glm::vec3(0.0f)); points.append(end - origin); properties.setPosition(origin); + properties.setRotation(glm::quat(1.0f, 0.0f ,0.0f ,0.0f)); properties.setLinePoints(points); properties.setVisible(true); properties.setIgnorePickIntersection(doesPathIgnorePicks()); + QVector normals; + normals.append(glm::vec3(0.0f, 0.0f, 1.0f)); + normals.append(glm::vec3(0.0f, 0.0f, 1.0f)); + properties.setNormals(normals); QVector widths; float width = getLineWidth() * parentScale; widths.append(width); @@ -184,33 +191,34 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& } } -std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap) { +std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap, const QList &entityProperties) { // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers QUuid startID; - if (propMap["start"].isValid()) { - QVariantMap startMap = propMap["start"].toMap(); - if (startMap["type"].isValid()) { - startMap.remove("visible"); - startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + if (propMap["startPropertyIndex"].isValid()) { + int startPropertyIndex = propMap["startPropertyIndex"].toInt(); + if (startPropertyIndex >= 0 && startPropertyIndex < entityProperties.length()) { + //startMap.remove("visible"); + startID = DependencyManager::get()->addEntityInternal(entityProperties[startPropertyIndex], entity::HostType::LOCAL); } } QUuid pathID; - if (propMap["path"].isValid()) { - QVariantMap pathMap = propMap["path"].toMap(); - // laser paths must be line3ds - if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { - pathMap.remove("visible"); - pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); + if (propMap["pathPropertyIndex"].isValid()) { + // laser paths must be PolyLine + int pathPropertyIndex = propMap["pathPropertyIndex"].toInt(); + if (pathPropertyIndex >= 0 && pathPropertyIndex < entityProperties.length()) { + //startMap.remove("visible"); + //pathMap["type"].toString() == "PolyLine" + pathID = DependencyManager::get()->addEntityInternal(entityProperties[pathPropertyIndex], entity::HostType::LOCAL); } } QUuid endID; - if (propMap["end"].isValid()) { - QVariantMap endMap = propMap["end"].toMap(); - if (endMap["type"].isValid()) { - endMap.remove("visible"); - endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + if (propMap["endPropertyIndex"].isValid()) { + int endPropertyIndex = propMap["endPropertyIndex"].toInt(); + if (endPropertyIndex >= 0 && endPropertyIndex < entityProperties.length()) { + //startMap.remove("visible"); + endID = DependencyManager::get()->addEntityInternal(entityProperties[endPropertyIndex], entity::HostType::LOCAL); } } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 330449a52d4..74f0fd88d3c 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -13,6 +13,8 @@ #include "PathPointer.h" +#include + class LaserPointer : public PathPointer { using Parent = PathPointer; public: @@ -46,7 +48,7 @@ class LaserPointer : public PathPointer { QVariantMap toVariantMap() const override; - static std::shared_ptr buildRenderState(const QVariantMap& propMap); + static std::shared_ptr buildRenderState(const QVariantMap& propMap, const QList &entityProperties); protected: PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp deleted file mode 100644 index 16fe65a989f..00000000000 --- a/interface/src/raypick/LaserPointerScriptingInterface.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// LaserPointerScriptingInterface.cpp -// interface/src/raypick -// -// Created by Sam Gondelman 7/11/2017 -// 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 "LaserPointerScriptingInterface.h" - -#include "RegisteredMetaTypes.h" -#include "PointerScriptingInterface.h" - -void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { - DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); -} - -void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const { - DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); -} - -unsigned int LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) const { - return DependencyManager::get()->createPointer(PickQuery::PickType::Ray, properties); -} - -void LaserPointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { - DependencyManager::get()->editRenderState(uid, renderState, properties); -} - -QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(unsigned int uid) const { - return DependencyManager::get()->getPrevPickResult(uid); -} diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h deleted file mode 100644 index ab3bca57d62..00000000000 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ /dev/null @@ -1,168 +0,0 @@ -// -// LaserPointerScriptingInterface.h -// interface/src/raypick -// -// Created by Sam Gondelman 7/11/2017 -// 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_LaserPointerScriptingInterface_h -#define hifi_LaserPointerScriptingInterface_h - -#include - -#include "DependencyManager.h" -#include - -class LaserPointerScriptingInterface : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -/*@jsdoc - * The LaserPointers API is a subset of the {@link Pointers} API. It lets you create, manage, and visually - * represent objects for repeatedly calculating ray intersections with avatars, entities, and overlays. Ray pointers can also - * be configured to generate events on entities and overlays intersected. - * - * @namespace LaserPointers - * - * @deprecated This API is deprecated and will be removed. Use {@link Pointers} instead. - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - * - * @borrows Pointers.enablePointer as enableLaserPointer - * @borrows Pointers.disablePointer as disableLaserPointer - * @borrows Pointers.removePointer as removeLaserPointer - * @borrows Pointers.setPrecisionPicking as setPrecisionPicking - */ -public: - - /*@jsdoc - * Creates a new ray pointer. The pointer can have a wide range of behaviors depending on the properties specified. For - * example, it may be a static ray pointer, a mouse ray pointer, or joint ray pointer. - *

Warning: Pointers created using this method currently always intersect at least visible and - * collidable things but this may not always be the case.

- * @function LaserPointers.createLaserPointer - * @param {Pointers.RayPointerProperties} properties - The properties of the pointer, including the properties of the - * underlying pick that the pointer uses to do its picking. - * @returns {number} The ID of the pointer if successfully created, otherwise 0. - */ - Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const; - - // jsdoc @borrows from Pointers - Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); } - - // jsdoc @borrows from Pointers - Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); } - - // jsdoc @borrows from Pointers - Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); } - - /*@jsdoc - * Edits a render state of a pointer, to change its visual appearance for the state when the pointer is intersecting - * something. - *

Note: You can only edit the properties of the existing parts of the pointer; you cannot change the - * type of any part.

- *

Note: You cannot use this method to change the appearance of a default render state.

- * @function LaserPointers.editRenderState - * @param {number} id - The ID of the pointer. - * @param {string} renderState - The name of the render state to edit. - * @param {Pointers.RayPointerRenderState} properties - The new properties for the render state. Only the overlay - * properties to change need be specified. - */ - Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const; - - /*@jsdoc - * Sets the render state of a pointer, to change its visual appearance and possibly disable or enable it. - * @function LaserPointers.setRenderState - * @param {string} renderState -

The name of the render state to set the pointer to. This may be:

- *
    - *
  • The name of one of the render states set in the pointer's properties.
  • - *
  • "", to hide the pointer and disable emitting of events.
  • - *
- * @param {number} id - The ID of the pointer. - */ - Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } - - /*@jsdoc - * Gets the most recent intersection of a pointer. A pointer continues to be updated ready to return a result, as long as - * it is enabled, regardless of the render state. - * @function LaserPointers.getPrevRayPickResult - * @param {number} id - The ID of the pointer. - * @returns {RayPickResult} The most recent intersection of the pointer. - */ - Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const; - - // jsdoc @borrows from Pointers - Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } - - /*@jsdoc - * Sets the length of a pointer. - * @function LaserPointers.setLaserLength - * @param {number} id - The ID of the pointer. - * @param {number} laserLength - The desired length of the pointer. - */ - Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); } - - /*@jsdoc - * Sets a list of entity and avatar IDs that a pointer should ignore during intersection. - * @function LaserPointers.setIgnoreItems - * @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; - - /*@jsdoc - * Sets a list of entity and avatar IDs that a pointer should include during intersection, instead of intersecting with - * everything. - * @function LaserPointers.setIncludeItems - * @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; - - - /*@jsdoc - * Locks a pointer onto a specific entity or avatar. - * @function LaserPointers.setLockEndUUID - * @param {number} id - The ID of the pointer. - * @param {Uuid} targetID - The ID of the entity or avatar to lock the pointer on to. - * @param {boolean} isAvatar - true if the target is an avatar, false if it is an entity. - * @param {Mat4} [offset] - The offset of the target point from the center of the target item. If not specified, the - * pointer locks on to the center of the target item. - */ - Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } - - - /*@jsdoc - * Checks if a pointer is associated with the left hand: a pointer with joint property set to - * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND". - * @function LaserPointers.isLeftHand - * @param {number} id - The ID of the pointer. - * @returns {boolean} true if the pointer is associated with the left hand, false if it isn't. - */ - Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } - - /*@jsdoc - * Checks if a pointer is associated with the right hand: a pointer with joint property set to - * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND". - * @function LaserPointers.isRightHand - * @param {number} id - The ID of the pointer. - * @returns {boolean} true if the pointer is associated with the right hand, false if it isn't. - */ - Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); } - - /*@jsdoc - * Checks if a pointer is associated with the system mouse: a pointer with joint property set to - * "Mouse". - * @function LaserPointers.isMouse - * @param {number} id - The ID of the pointer. - * @returns {boolean} true if the pointer is associated with the system mouse, false if it isn't. - */ - Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } -}; - -#endif // hifi_LaserPointerScriptingInterface_h diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index 7a0eed96a8d..378a46b96b5 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -71,7 +71,7 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) if (getFilter().doesPickLocalEntities()) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_ENTITY_HOST_TYPE; - if (DependencyManager::get()->getEntityProperties(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { + if (DependencyManager::get()->getEntityPropertiesInternal(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { type = IntersectionType::LOCAL_ENTITY; } } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 2c32be5d79a..63a17b217cd 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -1,6 +1,7 @@ // // Created by Sam Gondelman 7/17/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -232,14 +233,13 @@ void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::ve } } -std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap) { - // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers +std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap, const QList &entityProperties) { QUuid startID; - if (propMap["start"].isValid()) { - QVariantMap startMap = propMap["start"].toMap(); - if (startMap["type"].isValid()) { - startMap.remove("visible"); - startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + if (propMap["startPropertyIndex"].isValid()) { + int startPropertyIndex = propMap["startPropertyIndex"].toInt(); + if (startPropertyIndex >= 0 && startPropertyIndex < entityProperties.length()) { + //startMap.remove("visible"); + startID = DependencyManager::get()->addEntityInternal(entityProperties[startPropertyIndex], entity::HostType::LOCAL); } } @@ -249,6 +249,7 @@ std::shared_ptr ParabolaPointer::buildRenderState(const QVa bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; bool drawInFront = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT; bool enabled = false; + if (propMap["path"].isValid()) { enabled = true; QVariantMap pathMap = propMap["path"].toMap(); @@ -274,11 +275,11 @@ std::shared_ptr ParabolaPointer::buildRenderState(const QVa } QUuid endID; - if (propMap["end"].isValid()) { - QVariantMap endMap = propMap["end"].toMap(); - if (endMap["type"].isValid()) { - endMap.remove("visible"); - endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + if (propMap["endPropertyIndex"].isValid()) { + int endPropertyIndex = propMap["endPropertyIndex"].toInt(); + if (endPropertyIndex >= 0 && endPropertyIndex < entityProperties.length()) { + //endMap.remove("visible"); + endID = DependencyManager::get()->addEntityInternal(entityProperties[endPropertyIndex], entity::HostType::LOCAL); } } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 1701e4dcfa1..59168be5edb 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -10,6 +10,8 @@ #include "PathPointer.h" +#include + #include class ParabolaPointer : public PathPointer { @@ -105,7 +107,7 @@ class ParabolaPointer : public PathPointer { QVariantMap toVariantMap() const override; - static std::shared_ptr buildRenderState(const QVariantMap& propMap); + static std::shared_ptr buildRenderState(const QVariantMap& propMap, const QList &entityProperties); protected: virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 8a1675cfe18..b24c5630c48 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gondelman 7/17/2018 // Copyright 2018 High Fidelity, Inc. +// copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PathPointer.h" @@ -12,6 +14,7 @@ #include #include +#include #include "PickScriptingInterface.h" #include "RayPick.h" @@ -141,11 +144,13 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { auto renderState = _renderStates.find(_currentRenderState); auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); float parentScale = 1.0f; + //if (_scaleWithParent) { if (_enabled && _scaleWithParent) { glm::vec3 dimensions = DependencyManager::get()->getParentTransform(_pickUID).getScale(); parentScale = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z); } + //if (!_currentRenderState.empty() && renderState != _renderStates.end() && if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() && (type != IntersectionType::NONE || _pathLength > 0.0f)) { glm::vec3 origin = getPickOrigin(pickResult); @@ -157,6 +162,7 @@ void PathPointer::updateVisuals(const PickResultPointer& pickResult) { defaultRenderState->second.second->disable(); } } else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { + //} else if (!_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { if (renderState != _renderStates.end() && renderState->second->isEnabled()) { renderState->second->disable(); } @@ -251,7 +257,7 @@ StartEndRenderState::StartEndRenderState(const QUuid& startID, const QUuid& endI EntityPropertyFlags desiredProperties; desiredProperties += PROP_DIMENSIONS; desiredProperties += PROP_IGNORE_PICK_INTERSECTION; - auto properties = entityScriptingInterface->getEntityProperties(_startID, desiredProperties); + auto properties = entityScriptingInterface->getEntityPropertiesInternal(_startID, desiredProperties); _startDim = properties.getDimensions(); _startIgnorePicks = properties.getIgnorePickIntersection(); } @@ -260,7 +266,7 @@ StartEndRenderState::StartEndRenderState(const QUuid& startID, const QUuid& endI desiredProperties += PROP_DIMENSIONS; desiredProperties += PROP_ROTATION; desiredProperties += PROP_IGNORE_PICK_INTERSECTION; - auto properties = entityScriptingInterface->getEntityProperties(_endID, desiredProperties); + auto properties = entityScriptingInterface->getEntityPropertiesInternal(_endID, desiredProperties); _endDim = properties.getDimensions(); _endRot = properties.getRotation(); _endIgnorePicks = properties.getIgnorePickIntersection(); diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index e7777efe458..71a13b096ab 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gondelman 10/20/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PickScriptingInterface.h" @@ -29,6 +31,26 @@ #include "EntityTransformNode.h" #include +#include +#include +#include + +STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + PickScriptingInterface::registerMetaTypes(scriptEngine); +}); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + auto pickScriptingInterface = DependencyManager::get(); + if (pickScriptingInterface) { + pickScriptingInterface->registerProperties(scriptEngine); + } else { + qWarning(scriptengine) << "Cannot register PickScriptingInterface properties with script engine, PickScriptingInterface instance not available"; + } +}); 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); @@ -67,7 +89,7 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, PickFilter getPickFilter(unsigned int filter) { // FIXME: Picks always intersect visible and collidable things right now - filter = filter | (PickScriptingInterface::PICK_INCLUDE_VISIBLE() | PickScriptingInterface::PICK_INCLUDE_COLLIDABLE()); + filter = filter | (PickScriptingInterface::getPickIncludeVisible() | PickScriptingInterface::getPickIncludeCollidable()); return PickFilter(filter); } @@ -425,11 +447,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 +467,26 @@ 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(ScriptEngine* engine) { + scriptRegisterMetaType(engine); } -void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) { - QScriptValue pickTypes = engine->newObject(); +void PickScriptingInterface::registerProperties(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); } unsigned int PickScriptingInterface::getPerFrameTimeBudget() const { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 72470b42ee1..e6af97e6626 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman 10/20/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PickScriptingInterface_h #define hifi_PickScriptingInterface_h @@ -15,6 +17,9 @@ #include #include +class ScriptEngine; +class ScriptValue; + /*@jsdoc * The Picks API lets you create and manage objects for repeatedly calculating intersections. * @@ -71,39 +76,40 @@ class PickScriptingInterface : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT) - Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT) + Q_PROPERTY(unsigned int PICK_ENTITIES READ getPickEntities CONSTANT) + Q_PROPERTY(unsigned int PICK_OVERLAYS READ getPickOverlays CONSTANT) - Q_PROPERTY(unsigned int PICK_DOMAIN_ENTITIES READ PICK_DOMAIN_ENTITIES CONSTANT) - Q_PROPERTY(unsigned int PICK_AVATAR_ENTITIES READ PICK_AVATAR_ENTITIES CONSTANT) - Q_PROPERTY(unsigned int PICK_LOCAL_ENTITIES READ PICK_LOCAL_ENTITIES CONSTANT) - Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT) - Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT) + Q_PROPERTY(unsigned int PICK_DOMAIN_ENTITIES READ getPickDomainEntities CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATAR_ENTITIES READ getPickAvatarEntities CONSTANT) + Q_PROPERTY(unsigned int PICK_LOCAL_ENTITIES READ getPickLocalEntities CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATARS READ getPickAvatars CONSTANT) + Q_PROPERTY(unsigned int PICK_HUD READ getPickHud CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_VISIBLE READ PICK_INCLUDE_VISIBLE CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_VISIBLE READ getPickIncludeVisible CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ getPickIncludeInvisible CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_COLLIDABLE READ PICK_INCLUDE_COLLIDABLE CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_COLLIDABLE READ getPickIncludeCollidable CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ getPickIncludeNoncollidable CONSTANT) - Q_PROPERTY(unsigned int PICK_PRECISE READ PICK_PRECISE CONSTANT) - Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT) + Q_PROPERTY(unsigned int PICK_PRECISE READ getPickPrecise CONSTANT) + Q_PROPERTY(unsigned int PICK_COARSE READ getPickCoarse CONSTANT) - Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) + Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ getPickAllIntersections CONSTANT) - Q_PROPERTY(unsigned int PICK_BYPASS_IGNORE READ PICK_BYPASS_IGNORE CONSTANT) + Q_PROPERTY(unsigned int PICK_BYPASS_IGNORE READ getPickBypassIgnore CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ INTERSECTED_LOCAL_ENTITY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ getIntersectedNone CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ getIntersectedEntity CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ getIntersectedLocalEntity CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ getIntersectedOverlay CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ getIntersectedAvatar CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_HUD READ getIntersectedHud CONSTANT) Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget) SINGLETON_DEPENDENCY public: - void registerMetaTypes(QScriptEngine* engine); + static void registerMetaTypes(ScriptEngine* engine); + void registerProperties(ScriptEngine* engine); /*@jsdoc * Creates a new pick. Different {@link PickType}s use different properties, and within one PickType the properties you @@ -245,7 +251,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 +261,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 @@ -289,17 +295,17 @@ class PickScriptingInterface : public QObject, public Dependency { unsigned int getPerFrameTimeBudget() const; void setPerFrameTimeBudget(unsigned int numUsecs); - static constexpr unsigned int PICK_BYPASS_IGNORE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); } - public slots: + static constexpr unsigned int getPickBypassIgnore() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); } + /*@jsdoc * @function Picks.PICK_ENTITIES * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_DOMAIN_ENTITIES | * Picks.PICK_AVATAR_ENTITIES properties expression instead. * @returns {number} */ - static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } + static constexpr unsigned int getPickEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } /*@jsdoc * @function Picks.PICK_OVERLAYS @@ -307,7 +313,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } + static constexpr unsigned int getPickOverlays() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } /*@jsdoc @@ -316,7 +322,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_DOMAIN_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES); } + static constexpr unsigned int getPickDomainEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES); } /*@jsdoc * @function Picks.PICK_AVATAR_ENTITIES @@ -324,7 +330,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_AVATAR_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } + static constexpr unsigned int getPickAvatarEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } /*@jsdoc * @function Picks.PICK_LOCAL_ENTITIES @@ -332,7 +338,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_LOCAL_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } + static constexpr unsigned int getPickLocalEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } /*@jsdoc * @function Picks.PICK_AVATARS @@ -340,14 +346,14 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATARS); } + static constexpr unsigned int getPickAvatars() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATARS); } /*@jsdoc * @function Picks.PICK_HUD * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_HUD property instead. * @returns {number} */ - static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::HUD); } + static constexpr unsigned int getPickHud() { return PickFilter::getBitMask(PickFilter::FlagBit::HUD); } /*@jsdoc @@ -356,7 +362,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_INCLUDE_VISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } + static constexpr unsigned int getPickIncludeVisible() { return PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } /*@jsdoc * @function Picks.PICK_INCLUDE_INVISIBLE @@ -364,7 +370,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::INVISIBLE); } + static constexpr unsigned int getPickIncludeInvisible() { return PickFilter::getBitMask(PickFilter::FlagBit::INVISIBLE); } /*@jsdoc @@ -373,7 +379,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_INCLUDE_COLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } + static constexpr unsigned int getPickIncludeCollidable() { return PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } /*@jsdoc * @function Picks.PICK_INCLUDE_NONCOLLIDABLE @@ -381,7 +387,7 @@ public slots: * property instead. * @returns {number} */ - static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::NONCOLLIDABLE); } + static constexpr unsigned int getPickIncludeNoncollidable() { return PickFilter::getBitMask(PickFilter::FlagBit::NONCOLLIDABLE); } /*@jsdoc @@ -389,14 +395,14 @@ public slots: * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_PRECISE property instead. * @returns {number} */ - static constexpr unsigned int PICK_PRECISE() { return PickFilter::getBitMask(PickFilter::FlagBit::PRECISE); } + static constexpr unsigned int getPickPrecise() { return PickFilter::getBitMask(PickFilter::FlagBit::PRECISE); } /*@jsdoc * @function Picks.PICK_COARSE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_COARSE property instead. * @returns {number} */ - static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } + static constexpr unsigned int getPickCoarse() { return PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } /*@jsdoc @@ -405,7 +411,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int PICK_ALL_INTERSECTIONS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); } + static constexpr unsigned int getPickAllIntersections() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); } /*@jsdoc * @function Picks.INTERSECTED_NONE @@ -413,7 +419,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; } + static constexpr unsigned int getIntersectedNone() { return IntersectionType::NONE; } /*@jsdoc * @function Picks.INTERSECTED_ENTITY @@ -421,7 +427,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; } + static constexpr unsigned int getIntersectedEntity() { return IntersectionType::ENTITY; } /*@jsdoc * @function Picks.INTERSECTED_LOCAL_ENTITY @@ -429,7 +435,7 @@ public slots: * property instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_LOCAL_ENTITY() { return IntersectionType::LOCAL_ENTITY; } + static constexpr unsigned int getIntersectedLocalEntity() { return IntersectionType::LOCAL_ENTITY; } /*@jsdoc * @function Picks.INTERSECTED_OVERLAY @@ -437,7 +443,7 @@ public slots: * property instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_OVERLAY() { return INTERSECTED_LOCAL_ENTITY(); } + static constexpr unsigned int getIntersectedOverlay() { return getIntersectedLocalEntity(); } /*@jsdoc * @function Picks.INTERSECTED_AVATAR @@ -445,7 +451,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; } + static constexpr unsigned int getIntersectedAvatar() { return IntersectionType::AVATAR; } /*@jsdoc * @function Picks.INTERSECTED_HUD @@ -453,7 +459,7 @@ public slots: * instead. * @returns {number} */ - static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + static constexpr unsigned int getIntersectedHud() { return IntersectionType::HUD; } protected: static std::shared_ptr buildRayPick(const QVariantMap& properties); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 0b4c399d3bd..6ea32725725 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gondelman 10/20/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PointerScriptingInterface.h" @@ -12,45 +14,56 @@ #include #include +#include + #include "Application.h" #include "PickManager.h" #include "LaserPointer.h" #include "StylusPointer.h" #include "ParabolaPointer.h" #include "StylusPick.h" +#include + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + qDebug() << "STATIC_SCRIPT_TYPES_INITIALIZER PointerScriptingInterface"; + auto scriptEngine = manager->engine().get(); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); +})); 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)); } -unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) { +unsigned int PointerScriptingInterface::createPointerInternal(const PickQuery::PickType& type, const PointerProperties& properties) { // Interaction with managers should always happen on the main thread if (QThread::currentThread() != qApp->thread()) { unsigned int result; - BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties)); + BLOCKING_INVOKE_METHOD(this, "createPointerInternal", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(PointerProperties, properties)); return result; } - QVariantMap propertyMap = properties.toMap(); + QVariantMap propertyMap = properties.properties; std::shared_ptr pointer; switch (type) { case PickQuery::PickType::Ray: - pointer = buildLaserPointer(propertyMap); + pointer = buildLaserPointer(properties); break; case PickQuery::PickType::Stylus: - pointer = buildStylus(propertyMap); + pointer = buildStylus(properties); break; case PickQuery::PickType::Parabola: - pointer = buildParabolaPointer(propertyMap); + pointer = buildParabolaPointer(properties); break; default: break; @@ -67,6 +80,19 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& return DependencyManager::get()->addPointer(pointer); } +unsigned int PointerScriptingInterface::createRayPointer(RayPointerProperties properties) { + return createPointerInternal(PickQuery::PickType::Ray, properties); +} + +unsigned int PointerScriptingInterface::createStylusPointer(StylusPointerProperties properties) { + return createPointerInternal(PickQuery::PickType::Stylus, properties); +} + +unsigned int PointerScriptingInterface::createParabolaPointer(ParabolaPointerProperties properties) { + return createPointerInternal(PickQuery::PickType::Parabola, properties); +} + + bool PointerScriptingInterface::isPointerEnabled(unsigned int uid) const { return DependencyManager::get()->isPointerEnabled(uid); } @@ -105,8 +131,8 @@ QVariantMap PointerScriptingInterface::getPointerScriptParameters(unsigned int u * offset. * @property {Quat} [rotationOffset] - The rotation offset of the model from the hand, to override the default rotation offset. */ -std::shared_ptr PointerScriptingInterface::buildStylus(const QVariant& properties) { - QVariantMap propertyMap = properties.toMap(); +std::shared_ptr PointerScriptingInterface::buildStylus(const PointerProperties& properties) { + QVariantMap propertyMap = properties.properties; bool hover = false; if (propertyMap["hover"].isValid()) { @@ -138,7 +164,7 @@ std::shared_ptr PointerScriptingInterface::buildStylus(const QVariant& } } - return std::make_shared(properties, StylusPointer::buildStylus(propertyMap), hover, enabled, modelPositionOffset, modelRotationOffset, modelDimensions); + return std::make_shared(properties.properties, StylusPointer::buildStylus(propertyMap), hover, enabled, modelPositionOffset, modelRotationOffset, modelDimensions); } /*@jsdoc @@ -217,8 +243,8 @@ std::shared_ptr PointerScriptingInterface::buildStylus(const QVariant& * {@link Pointers.getPointerProperties}. * @see {@link Picks.RayPickProperties} for additional properties from the underlying ray pick. */ -std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVariant& properties) { - QVariantMap propertyMap = properties.toMap(); +std::shared_ptr PointerScriptingInterface::buildLaserPointer(const PointerProperties& properties) { + QVariantMap propertyMap = properties.properties; #if defined (Q_OS_ANDROID) QString jointName { "" }; @@ -280,7 +306,7 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVar QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); - renderStates[name] = LaserPointer::buildRenderState(renderStateMap); + renderStates[name] = LaserPointer::buildRenderState(renderStateMap, properties.entityProperties); } } } @@ -295,7 +321,8 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVar if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, + LaserPointer::buildRenderState(renderStateMap, properties.entityProperties)); } } } @@ -324,7 +351,7 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVar } } - return std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, + return std::make_shared(properties.properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithParent, enabled); } @@ -412,8 +439,8 @@ std::shared_ptr PointerScriptingInterface::buildLaserPointer(const QVar * {@link Pointers.getPointerProperties}. * @see {@link Picks.ParabolaPickProperties} for additional properties from the underlying parabola pick. */ -std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const QVariant& properties) { - QVariantMap propertyMap = properties.toMap(); +std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const PointerProperties& properties) { + QVariantMap propertyMap = properties.properties; bool faceAvatar = false; if (propertyMap["faceAvatar"].isValid()) { @@ -464,7 +491,7 @@ std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const Q QVariantMap renderStateMap = renderStateVariant.toMap(); if (renderStateMap["name"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); - renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap); + renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap, properties.entityProperties); } } } @@ -479,7 +506,8 @@ std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const Q if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair>(distance, ParabolaPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, + ParabolaPointer::buildRenderState(renderStateMap, properties.entityProperties)); } } } @@ -508,12 +536,13 @@ std::shared_ptr PointerScriptingInterface::buildParabolaPointer(const Q } } - return std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, + return std::make_shared(properties.properties, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, scaleWithParent, enabled); } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { + //V8TODO: this needs testing QVariantMap propMap = properties.toMap(); QVariant startProps; @@ -542,3 +571,166 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const } return result; } + +ScriptValue rayPointerPropertiesToScriptValue(ScriptEngine* engine, const RayPointerProperties& in) { + return engine->newVariant(QVariant(in.properties)); +} + +bool rayPointerPropertiesFromScriptValue(const ScriptValue& value, RayPointerProperties& out) { + // This copies properties from script value, but also converts entity properties of entities used in render states + // from JS objects into EntityItemProperties + out.properties = value.engine()->fromScriptValue(value); + QList renderStatesNames; + renderStatesNames.append("renderStates"); + renderStatesNames.append("defaultRenderStates"); + for (auto renderStatesName = renderStatesNames.cbegin(); renderStatesName!=renderStatesNames.cend(); renderStatesName++) { + if (out.properties[*renderStatesName].canConvert()) { + QVariantList renderStates = out.properties[*renderStatesName].value(); + for (int i = 0; i < renderStates.length(); i++) { + if (renderStates[i].canConvert()) { + QVariantMap stateMap = renderStates[i].value(); + if (stateMap["name"].canConvert()) { + stateMap["name"].value(); + } + if (stateMap["start"].isValid()) { + ScriptValue start = value.property(*renderStatesName).property(i).property("start"); + EntityItemProperties startProperties; + startProperties.copyFromScriptValue(start, false); + stateMap.insert("startPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(startProperties); + } + + if (stateMap["path"].isValid()) { + ScriptValue path = value.property(*renderStatesName).property(i).property("path"); + EntityItemProperties pathProperties; + pathProperties.copyFromScriptValue(path, false); + stateMap.insert("pathPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(pathProperties); + } + + if (stateMap["end"].isValid()) { + ScriptValue end = value.property(*renderStatesName).property(i).property("end"); + EntityItemProperties endProperties; + endProperties.copyFromScriptValue(end, false); + stateMap.insert("endPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(endProperties); + } + // V8TODO: Check if path is a polyline and if values are valid + renderStates[i].setValue(stateMap); + } + } + out.properties[*renderStatesName].setValue(renderStates); + } + } + qDebug() << "rayPointerPropertiesFromScriptValue" << out.properties; + return true; +} + +ScriptValue stylusPointerPropertiesToScriptValue(ScriptEngine* engine, const StylusPointerProperties& in) { + return engine->newVariant(QVariant(in.properties)); +} + +bool stylusPointerPropertiesFromScriptValue(const ScriptValue& value, StylusPointerProperties& out) { + // This copies properties from script value, but also converts entity properties of entities used in render states + // from JS objects into EntityItemProperties + out.properties = value.engine()->fromScriptValue(value); + QList renderStatesNames; + renderStatesNames.append("renderStates"); + renderStatesNames.append("defaultRenderStates"); + for (auto renderStatesName = renderStatesNames.cbegin(); renderStatesName!=renderStatesNames.cend(); renderStatesName++) { + if (out.properties[*renderStatesName].canConvert()) { + QVariantList renderStates = out.properties[*renderStatesName].value(); + for (int i = 0; i < renderStates.length(); i++) { + if (renderStates[i].canConvert()) { + QVariantMap stateMap = renderStates[i].value(); + if (stateMap["name"].canConvert()) { + stateMap["name"].value(); + } + if (stateMap["start"].isValid()) { + ScriptValue start = value.property(*renderStatesName).property(i).property("start"); + EntityItemProperties startProperties; + startProperties.copyFromScriptValue(start, false); + stateMap.insert("startPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(startProperties); + } + + if (stateMap["path"].isValid()) { + ScriptValue path = value.property(*renderStatesName).property(i).property("path"); + EntityItemProperties pathProperties; + pathProperties.copyFromScriptValue(path, false); + stateMap.insert("pathPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(pathProperties); + } + + if (stateMap["end"].isValid()) { + ScriptValue end = value.property(*renderStatesName).property(i).property("end"); + EntityItemProperties endProperties; + endProperties.copyFromScriptValue(end, false); + stateMap.insert("endPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(endProperties); + } + renderStates[i].setValue(stateMap); + } + } + out.properties[*renderStatesName].setValue(renderStates); + } + } + qDebug() << "stylusPointerPropertiesFromScriptValue" << out.properties; + return true; +} + +ScriptValue parabolaPointerPropertiesToScriptValue(ScriptEngine* engine, const ParabolaPointerProperties& in) { + return engine->newVariant(QVariant(in.properties)); +} + +//V8TODO: adapt render states to what parabola expects +bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, ParabolaPointerProperties& out) { + // This copies properties from script value, but also converts entity properties of entities used in render states + // from JS objects into EntityItemProperties + out.properties = value.engine()->fromScriptValue(value); + QList renderStatesNames; + renderStatesNames.append("renderStates"); + renderStatesNames.append("defaultRenderStates"); + for (auto renderStatesName = renderStatesNames.cbegin(); renderStatesName!=renderStatesNames.cend(); renderStatesName++) { + if (out.properties[*renderStatesName].canConvert()) { + QVariantList renderStates = out.properties[*renderStatesName].value(); + for (int i = 0; i < renderStates.length(); i++) { + if (renderStates[i].canConvert()) { + QVariantMap stateMap = renderStates[i].value(); + if (stateMap["name"].canConvert()) { + stateMap["name"].value(); + } + if (stateMap["start"].isValid()) { + ScriptValue start = value.property(*renderStatesName).property(i).property("start"); + EntityItemProperties startProperties; + startProperties.copyFromScriptValue(start, false); + stateMap.insert("startPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(startProperties); + } + + if (stateMap["path"].isValid()) { + ScriptValue path = value.property(*renderStatesName).property(i).property("path"); + EntityItemProperties pathProperties; + pathProperties.copyFromScriptValue(path, false); + stateMap.insert("pathPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(pathProperties); + qDebug() << "parabolaPointerPropertiesFromScriptValue : added path entity"; + } + + if (stateMap["end"].isValid()) { + ScriptValue end = value.property(*renderStatesName).property(i).property("end"); + EntityItemProperties endProperties; + endProperties.copyFromScriptValue(end, false); + stateMap.insert("endPropertyIndex", QVariant(out.entityProperties.length())); + out.entityProperties.append(endProperties); + qDebug() << "parabolaPointerPropertiesFromScriptValue : added end entity"; + } + renderStates[i].setValue(stateMap); + } + } + out.properties[*renderStatesName].setValue(renderStates); + } + } + qDebug() << "parabolaPointerPropertiesFromScriptValue" << out.properties; + return true; +} diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 58439f221d4..033ef274886 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman 10/20/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PointerScriptingInterface_h #define hifi_PointerScriptingInterface_h @@ -12,12 +14,15 @@ #include "DependencyManager.h" #include "RegisteredMetaTypes.h" +#include #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 + * 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 * overlays intersected. * * @namespace Pointers @@ -27,12 +32,36 @@ * @hifi-avatar */ +class PointerProperties { +public: + QVariantMap properties; + QList entityProperties; +}; + +class RayPointerProperties : public PointerProperties { +}; + +class ParabolaPointerProperties : public PointerProperties { +}; + +class StylusPointerProperties : public PointerProperties { +}; + +Q_DECLARE_METATYPE(RayPointerProperties); +Q_DECLARE_METATYPE(StylusPointerProperties); +Q_DECLARE_METATYPE(ParabolaPointerProperties); +Q_DECLARE_METATYPE(PointerProperties); + class PointerScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: + + // The purpose of registering different classes is to let the script engine know what data structure it has to + // expect in JS object that will be converted to PointerProperties + /*@jsdoc * Specifies that a {@link Controller} action or function should trigger events on the entity or overlay currently * intersected by a {@link Pointers.RayPointerProperties|Ray} or {@link Pointers.ParabolaPointerProperties|Parabola} @@ -129,7 +158,11 @@ class PointerScriptingInterface : public QObject, public Dependency { * }); */ // TODO: expand Pointers to be able to be fully configurable with PickFilters - Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties); + + // V8TODO: add documentation + Q_INVOKABLE unsigned int createRayPointer(RayPointerProperties properties); + Q_INVOKABLE unsigned int createStylusPointer(StylusPointerProperties properties); + Q_INVOKABLE unsigned int createParabolaPointer(ParabolaPointerProperties properties); /*@jsdoc * Enables and shows a pointer. Enabled pointers update their pick results and generate events. @@ -365,7 +398,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 +408,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 @@ -475,9 +508,19 @@ class PointerScriptingInterface : public QObject, public Dependency { Q_INVOKABLE QVariantMap getPointerProperties(unsigned int uid) const; protected: - static std::shared_ptr buildLaserPointer(const QVariant& properties); - static std::shared_ptr buildStylus(const QVariant& properties); - static std::shared_ptr buildParabolaPointer(const QVariant& properties); + static std::shared_ptr buildLaserPointer(const PointerProperties& properties); + static std::shared_ptr buildStylus(const PointerProperties& properties); + static std::shared_ptr buildParabolaPointer(const PointerProperties& properties); +private: + Q_INVOKABLE unsigned int createPointerInternal(const PickQuery::PickType& type, const PointerProperties& properties); }; +ScriptValue rayPointerPropertiesToScriptValue(ScriptEngine* engine, const RayPointerProperties& in); +ScriptValue stylusPointerPropertiesToScriptValue(ScriptEngine* engine, const StylusPointerProperties& in); +ScriptValue parabolaPointerPropertiesToScriptValue(ScriptEngine* engine, const ParabolaPointerProperties& in); + +bool rayPointerPropertiesFromScriptValue(const ScriptValue& value, RayPointerProperties& out); +bool stylusPointerPropertiesFromScriptValue(const ScriptValue& value, StylusPointerProperties& out); +bool parabolaPointerPropertiesFromScriptValue(const ScriptValue& value, ParabolaPointerProperties& out); + #endif // hifi_PointerScriptingInterface_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 8157c32a93b..17326baa1aa 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -40,7 +40,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { if (getFilter().doesPickLocalEntities()) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_ENTITY_HOST_TYPE; - if (DependencyManager::get()->getEntityProperties(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { + if (DependencyManager::get()->getEntityPropertiesInternal(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { type = IntersectionType::LOCAL_ENTITY; } } @@ -123,6 +123,6 @@ glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::ve desiredProperties += PROP_ROTATION; desiredProperties += PROP_DIMENSIONS; desiredProperties += PROP_REGISTRATION_POINT; - auto props = DependencyManager::get()->getEntityProperties(entityID, desiredProperties); + auto props = DependencyManager::get()->getEntityPropertiesInternal(entityID, desiredProperties); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized); } diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index a837121e6ad..54b4d7b5065 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -4,9 +4,11 @@ // // Created by Sam Gondelman 8/15/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RayPickScriptingInterface.h" @@ -15,6 +17,7 @@ #include "GLMHelpers.h" #include +#include unsigned int RayPickScriptingInterface::createRayPick(const QVariant& properties) { return DependencyManager::get()->createPick(PickQuery::PickType::Ray, properties); @@ -45,11 +48,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..f88d7785b5c 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Sam Gondelman 8/15/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RayPickScriptingInterface_h #define hifi_RayPickScriptingInterface_h @@ -18,6 +20,8 @@ #include "PickScriptingInterface.h" +class ScriptValue; + /*@jsdoc * The RayPick API is a subset of the {@link Picks} API, as used for ray picks. * @@ -47,20 +51,20 @@ */ class RayPickScriptingInterface : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT) - Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT) - Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT) - Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT) - Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT) - Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT) - Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ INTERSECTED_LOCAL_ENTITY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) - Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) + Q_PROPERTY(unsigned int PICK_ENTITIES READ getPickEntities CONSTANT) + Q_PROPERTY(unsigned int PICK_OVERLAYS READ getPickOverlays CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATARS READ getPickAvatars CONSTANT) + Q_PROPERTY(unsigned int PICK_HUD READ getPickHud CONSTANT) + Q_PROPERTY(unsigned int PICK_COARSE READ getPickCoarse CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ getPickIncludeInvisible CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ getPickIncludeNoncollidable CONSTANT) + Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ getPickAllIntersections CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ getIntersectedNone CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ getIntersectedEntity CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ getIntersectedLocalEntity CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ getIntersectedOverlay CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ getIntersectedAvatar CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_HUD READ getIntersectedHud CONSTANT) SINGLETON_DEPENDENCY public: @@ -121,7 +125,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 +134,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 @@ -169,35 +173,35 @@ public slots: * @deprecated This function is deprecated and will be removed. Use the Raypick.PICK_ENTITIES property instead. * @returns {number} */ - static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); } + static unsigned int getPickEntities() { return PickScriptingInterface::getPickEntities(); } /*@jsdoc * @function RayPick.PICK_OVERLAYS * @deprecated This function is deprecated and will be removed. Use the RayPick.PICK_OVERLAYS property instead. * @returns {number} */ - static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); } + static unsigned int getPickOverlays() { return PickScriptingInterface::getPickOverlays(); } /*@jsdoc * @function RayPick.PICK_AVATARS * @deprecated This function is deprecated and will be removed. Use the RayPick.PICK_AVATARS property instead. * @returns {number} */ - static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); } + static unsigned int getPickAvatars() { return PickScriptingInterface::getPickAvatars(); } /*@jsdoc * @function RayPick.PICK_HUD * @deprecated This function is deprecated and will be removed. Use the RayPick.PICK_HUD property instead. * @returns {number} */ - static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); } + static unsigned int getPickHud() { return PickScriptingInterface::getPickHud(); } /*@jsdoc * @function RayPick.PICK_COARSE * @deprecated This function is deprecated and will be removed. Use the RayPick.PICK_COARSE property instead. * @returns {number} */ - static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); } + static unsigned int getPickCoarse() { return PickScriptingInterface::getPickCoarse(); } /*@jsdoc * @function RayPick.PICK_INCLUDE_INVISIBLE @@ -205,7 +209,7 @@ public slots: * property instead. * @returns {number} */ - static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); } + static unsigned int getPickIncludeInvisible() { return PickScriptingInterface::getPickIncludeInvisible(); } /*@jsdoc * @function RayPick.PICK_INCLUDE_NONCOLLIDABLE @@ -213,7 +217,7 @@ public slots: * property instead. * @returns {number} */ - static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); } + static unsigned int getPickIncludeNoncollidable() { return PickScriptingInterface::getPickIncludeNoncollidable(); } /*@jsdoc * @function RayPick.PICK_ALL_INTERSECTIONS @@ -221,7 +225,7 @@ public slots: * property instead. * @returns {number} */ - static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); } + static unsigned int getPickAllIntersections() { return PickScriptingInterface::getPickAllIntersections(); } /*@jsdoc * @function RayPick.INTERSECTED_NONE @@ -229,7 +233,7 @@ public slots: * instead. * @returns {number} */ - static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); } + static unsigned int getIntersectedNone() { return PickScriptingInterface::getIntersectedNone(); } /*@jsdoc * @function RayPick.INTERSECTED_ENTITY @@ -237,7 +241,7 @@ public slots: * instead. * @returns {number} */ - static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); } + static unsigned int getIntersectedEntity() { return PickScriptingInterface::getIntersectedEntity(); } /*@jsdoc * @function RayPick.INTERSECTED_OVERLAY @@ -245,7 +249,7 @@ public slots: * property instead. * @returns {number} */ - static unsigned int INTERSECTED_LOCAL_ENTITY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); } + static unsigned int getIntersectedLocalEntity() { return PickScriptingInterface::getIntersectedLocalEntity(); } /*@jsdoc * @function RayPick.INTERSECTED_OVERLAY @@ -253,7 +257,7 @@ public slots: * instead. * @returns {number} */ - static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); } + static unsigned int getIntersectedOverlay() { return PickScriptingInterface::getIntersectedOverlay(); } /*@jsdoc * @function RayPick.INTERSECTED_AVATAR @@ -261,7 +265,7 @@ public slots: * instead. * @returns {number} */ - static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); } + static unsigned int getIntersectedAvatar() { return PickScriptingInterface::getIntersectedAvatar(); } /*@jsdoc * @function RayPick.INTERSECTED_HUD @@ -269,7 +273,7 @@ public slots: * instead. * @returns {number} */ - static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); } + static unsigned int getIntersectedHud() { return PickScriptingInterface::getIntersectedHud(); } }; #endif // hifi_RayPickScriptingInterface_h diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 48b61e9a2ae..8a6097816ea 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -49,7 +49,7 @@ PickQuery::PickType StylusPointer::getType() const { QUuid StylusPointer::buildStylus(const QVariantMap& properties) { // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers - QVariantMap propertiesMap; + /*QVariantMap propertiesMap; QString modelUrl = DEFAULT_STYLUS_MODEL_URL; @@ -69,7 +69,26 @@ QUuid StylusPointer::buildStylus(const QVariantMap& properties) { propertiesMap["ignorePickIntersection"] = true; propertiesMap["drawInFront"] = false; - return qApp->getOverlays().addOverlay("model", propertiesMap); + return qApp->getOverlays().addOverlay("model", propertiesMap);*/ + + EntityItemProperties entityProperties; + QString modelURL = DEFAULT_STYLUS_MODEL_URL; + + if (properties["model"].isValid()) { + QVariantMap modelData = properties["model"].toMap(); + + if (modelData["url"].isValid()) { + modelURL = modelData["url"].toString(); + } + } + // TODO: make these configurable per pointer + entityProperties.setType(EntityTypes::Model); + entityProperties.setName("stylus"); + entityProperties.setModelURL(modelURL); + entityProperties.setPrimitiveMode(PrimitiveMode::SOLID); + entityProperties.setVisible(true); + entityProperties.setIgnorePickIntersection(true); + return DependencyManager::get()->addEntityInternal(entityProperties, entity::HostType::LOCAL); } void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index 87aacad631b..35e9f3b36d0 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -4,18 +4,28 @@ // // Created by Thijs Wenker on 9/10/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AccountServicesScriptingInterface.h" +#include + #include "AccountManager.h" #include "Application.h" #include "DiscoverabilityManager.h" #include "ResourceCache.h" +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); +})); + AccountServicesScriptingInterface::AccountServicesScriptingInterface() { auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, &AccountServicesScriptingInterface::onUsernameChanged); @@ -123,10 +133,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 +146,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 +154,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 5c7d85fdf0a..a716f012d9c 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -4,23 +4,25 @@ // // Created by Thijs Wenker on 9/10/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AccountServicesScriptingInterface_h #define hifi_AccountServicesScriptingInterface_h #include -#include -#include -#include #include #include #include #include +#include + +class ScriptEngine; class DownloadInfoResult { public: @@ -31,8 +33,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 44ff4b2d101..bb9710e9bfc 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -4,14 +4,15 @@ // // Created by Ryan Huffman on 2016-03-09. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AssetMappingsScriptingInterface.h" -#include #include #include diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index b27a72fbd0f..bc624b0dd08 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 2016-03-09. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -15,14 +17,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/Audio.cpp b/interface/src/scripting/Audio.cpp index 4a96e369392..57c160b7e05 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -508,9 +508,9 @@ void Audio::setReverb(bool enable) { }); } -void Audio::setReverbOptions(const AudioEffectOptions* options) { +void Audio::setReverbOptions(const AudioEffectOptions options) { withWriteLock([&] { - DependencyManager::get()->setReverbOptions(options); + DependencyManager::get()->setReverbOptions(&options); }); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index bb8a92aa73e..5c1cb68b505 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -215,7 +215,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @function Audio.setReverbOptions * @param {AudioEffectOptions} options - The reverberation options. */ - Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + Q_INVOKABLE void setReverbOptions(const AudioEffectOptions options); /*@jsdoc * Sets the gain (relative volume) that avatars' voices are played at. This gain is used at the server. diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index cec701c911f..ffa63ea670e 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -7,6 +7,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "DesktopScriptingInterface.h" diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 28d5f8d4446..0f30b140fcc 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -4,16 +4,17 @@ // // Created by David Rowe on 25 Aug 2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_DesktopScriptingInterface_h #define hifi_DesktopScriptingInterface_h #include -#include #include @@ -46,6 +47,7 @@ * {@link InteractiveWindow}: none, top left, top right, bottom right, or bottom left of the Interface window. * Read-only. */ + class DesktopScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 79c0452a452..a91025aeab5 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -4,21 +4,24 @@ // // Created by Thijs Wenker on 1/12/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "HMDScriptingInterface.h" -#include - #include #include #include #include #include #include +#include +#include +#include #include #include "Application.h" @@ -151,23 +154,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..81516ae441b 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Thijs Wenker on 1/12/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_HMDScriptingInterface_h @@ -14,15 +16,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 +444,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..9f3534b3e82 100644 --- a/interface/src/scripting/PerformanceScriptingInterface.cpp +++ b/interface/src/scripting/PerformanceScriptingInterface.cpp @@ -1,16 +1,34 @@ // // Created by Bradley Austin Davis on 2019/05/14 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PerformanceScriptingInterface.h" +#include + #include "../Application.h" +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "PerformancePreset"); + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "RefreshRateProfile"); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptEngine->registerEnum("Performance.PerformancePreset",QMetaEnum::fromType()); + scriptEngine->registerEnum("Performance.RefreshRateProfile",QMetaEnum::fromType()); +}); + std::once_flag PerformanceScriptingInterface::registry_flag; PerformanceScriptingInterface::PerformanceScriptingInterface() { diff --git a/interface/src/scripting/PerformanceScriptingInterface.h b/interface/src/scripting/PerformanceScriptingInterface.h index 76e58f29b6b..249706a5eaa 100644 --- a/interface/src/scripting/PerformanceScriptingInterface.h +++ b/interface/src/scripting/PerformanceScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2019/05/14 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -55,7 +57,7 @@ class PerformanceScriptingInterface : public QObject { * @typedef {number} Performance.PerformancePreset */ // PerformanceManager PerformancePreset tri state level enums - enum PerformancePreset { + enum class PerformancePreset { UNKNOWN = PerformanceManager::PerformancePreset::UNKNOWN, LOW_POWER = PerformanceManager::PerformancePreset::LOW_POWER, LOW = PerformanceManager::PerformancePreset::LOW, @@ -82,7 +84,7 @@ class PerformanceScriptingInterface : public QObject { * @typedef {number} Performance.RefreshRateProfile */ // Must match RefreshRateManager enums - enum RefreshRateProfile { + enum class RefreshRateProfile { ECO = RefreshRateManager::RefreshRateProfile::ECO, INTERACTIVE = RefreshRateManager::RefreshRateProfile::INTERACTIVE, REALTIME = RefreshRateManager::RefreshRateProfile::REALTIME, diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index c1f325237e4..b87175bb745 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Nissim Hadar on 2018/12/28 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PlatformInfoScriptingInterface.h" #include "Application.h" @@ -12,6 +14,7 @@ #include #include +#include #ifdef Q_OS_WIN #include @@ -19,6 +22,18 @@ #include #endif +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "PlatformTier"); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptEngine->registerEnum("PlatformInfo.PlatformTier",QMetaEnum::fromType()); +}); + PlatformInfoScriptingInterface* PlatformInfoScriptingInterface::getInstance() { static PlatformInfoScriptingInterface sharedInstance; return &sharedInstance; diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 71b54f95fd5..c558eeeedc8 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Nissim Hadar on 2018/12/28 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PlatformInfoScriptingInterface_h @@ -12,8 +14,6 @@ #include #include -class QScriptValue; - /*@jsdoc * The PlatformInfo API provides information about the hardware platform being used. * @@ -26,8 +26,12 @@ class QScriptValue; class PlatformInfoScriptingInterface : public QObject { Q_OBJECT +private: + static PlatformInfoScriptingInterface* getInstance(); + friend class Application; public: + PlatformInfoScriptingInterface(); virtual ~PlatformInfoScriptingInterface(); @@ -47,7 +51,7 @@ class PlatformInfoScriptingInterface : public QObject { * @typedef {number} PlatformInfo.PlatformTier */ // Platform tier enum type - enum PlatformTier { + enum class PlatformTier { UNKNOWN = platform::Profiler::Tier::UNKNOWN, LOW = platform::Profiler::Tier::LOW, MID = platform::Profiler::Tier::MID, @@ -56,11 +60,10 @@ class PlatformInfoScriptingInterface : public QObject { Q_ENUM(PlatformTier); public slots: - /*@jsdoc + /* * @function PlatformInfo.getInstance * @deprecated This function is deprecated and will be removed. */ - static PlatformInfoScriptingInterface* getInstance(); /*@jsdoc * Gets the operating system type. diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index b4b1b9864ed..e67d5b6e2ae 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -1,16 +1,33 @@ // // Created by Sam Gondelman on 5/16/19 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RenderScriptingInterface.h" +#include + #include "LightingModel.h" #include #include "ScreenName.h" +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "RenderMethod"); + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "Mode"); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptEngine->registerEnum("Render.RenderMethod",QMetaEnum::fromType()); + scriptEngine->registerEnum("AntialiasingMode",QMetaEnum::fromType()); +}); RenderScriptingInterface* RenderScriptingInterface::getInstance() { static RenderScriptingInterface sharedInstance; diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 27088daf972..cb7bd8fcfad 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 5/16/19 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RenderScriptingInterface_h @@ -148,14 +150,14 @@ public slots: /*@jsdoc * Gets the active anti-aliasing mode. * @function Render.getAntialiasingMode - * @returns {integer} the active anti-aliasing mode. + * @returns {AntialiasingMode} The active anti-aliasing mode. */ AntialiasingConfig::Mode getAntialiasingMode() const; /*@jsdoc * Sets the active anti-aliasing mode. * @function Render.setAntialiasingMode - * @param {integer} the active anti-aliasing mode. + * @param {AntialiasingMode} The active anti-aliasing mode. */ void setAntialiasingMode(AntialiasingConfig::Mode mode); diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index bc4e0b29308..b7ef172f19a 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -4,10 +4,11 @@ // // Created by Brad Hefta-Gaub on 2/25/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "SettingsScriptingInterface.h" @@ -48,7 +49,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..bc3f59e9174 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2016/12/12 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TestScriptingInterface.h" @@ -16,6 +18,7 @@ #include #include #include +#include #include #include @@ -153,7 +156,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..f7b0b5d74dc 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2016/12/12 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -12,8 +14,7 @@ #include #include - -class QScriptValue; +#include class TestScriptingInterface : public QObject { Q_OBJECT @@ -127,7 +128,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/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 0194c62b822..c27b3180e46 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -34,7 +34,7 @@ void WalletScriptingInterface::setWalletStatus(const uint& status) { void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityID) { QSharedPointer contextOverlayInterface = DependencyManager::get(); - EntityItemProperties entityProperties = DependencyManager::get()->getEntityProperties(entityID, + EntityItemProperties entityProperties = DependencyManager::get()->getEntityPropertiesInternal(entityID, contextOverlayInterface->getEntityPropertyFlags()); if (entityProperties.getEntityHostType() == entity::HostType::AVATAR) { if (!entityID.isNull() && entityProperties.getCertificateID().length() > 0) { diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 6c3b7adb06d..98fbc624fab 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "WindowScriptingInterface.h" @@ -14,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -76,8 +78,9 @@ WindowScriptingInterface::~WindowScriptingInterface() { _messageBoxes.clear(); } -QScriptValue WindowScriptingInterface::hasFocus() { - return qApp->hasFocus(); +ScriptValue WindowScriptingInterface::hasFocus() { + Q_ASSERT(engine); + return engine()->newValue(qApp->hasFocus()); } void WindowScriptingInterface::setFocus() { @@ -96,28 +99,31 @@ 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) { + Q_ASSERT(engine); + 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; + Q_ASSERT(engine); + 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 +223,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 +237,8 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + Q_ASSERT(engine); + 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 +268,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 +282,8 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + Q_ASSERT(engine); + 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 +316,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 +330,8 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString& if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + Q_ASSERT(engine); + 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 +364,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 +381,8 @@ QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const if (!result.isEmpty()) { setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath()); } - return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); + Q_ASSERT(engine); + 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 @@ -603,6 +613,7 @@ void WindowScriptingInterface::closeMessageBox(int id) { void WindowScriptingInterface::onMessageBoxSelected(int button) { auto messageBox = qobject_cast(sender()); + Q_ASSERT(messageBox != nullptr); auto keys = _messageBoxes.keys(messageBox); if (keys.length() > 0) { auto id = keys[0]; // Should be just one message box. diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 5d9a8a71ae6..88327de2b98 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_WindowScriptingInterface_h @@ -17,10 +19,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 +50,7 @@ * @property {location} location - Provides facilities for working with your current directory services 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 +73,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 +108,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 +125,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 +155,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 +187,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 +223,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 +258,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..258ba6784b4 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -4,9 +4,11 @@ // // Created by Thijs Wenker on 2018-06-25 // Copyright 2018 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "InteractiveWindow.h" @@ -24,6 +26,8 @@ #include #include #include +#include +#include #include "OffscreenUi.h" #include "shared/QtHelpers.h" @@ -33,6 +37,12 @@ #include #endif +STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + registerInteractiveWindowMetaType(scriptEngine); +}); + static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml"); static const char* const ADDITIONAL_FLAGS_PROPERTY = "additionalFlags"; @@ -91,18 +101,20 @@ static void dockWidgetDeleter(DockWidget* dockWidget) { dockWidget->deleteLater(); } -void registerInteractiveWindowMetaType(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue); +void registerInteractiveWindowMetaType(ScriptEngine* engine) { + scriptRegisterMetaType(engine); } -QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in) { - return engine->newQObject(in, QScriptEngine::ScriptOwnership); +ScriptValue interactiveWindowPointerToScriptValue(ScriptEngine* engine, const InteractiveWindowPointer& in) { + // V8TODO: is ScriptOwnership safe here? + 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..3d69185ecfc 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -4,9 +4,11 @@ // // Created by Thijs Wenker on 2018-06-25 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -16,12 +18,14 @@ #include #include -#include #include #include #include #include +#include + +class ScriptEngine; class QmlWindowProxy : public QmlWrapper { Q_OBJECT @@ -408,10 +412,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..9a3b4bb53a5 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 05/12/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "JSConsole.h" @@ -18,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -133,7 +137,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 +185,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 +309,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 +351,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]() -> QVariant { + QVariant result; + auto scriptManager = weakScriptManager.lock(); + if (scriptManager) { + BLOCKING_INVOKE_METHOD(scriptManager.get(), [&scriptManager, &consoleFileName, &command, &result]() -> void { + result = scriptManager->evaluate(command, consoleFileName).toVariant(); + }); } return result; }); @@ -364,7 +367,7 @@ void JSConsole::executeCommand(const QString& command) { } void JSConsole::commandFinished() { - QScriptValue result = _executeWatcher.result(); + QVariant result = _executeWatcher.result(); _ui->promptTextEdit->setDisabled(false); @@ -373,9 +376,12 @@ void JSConsole::commandFinished() { _ui->promptTextEdit->setFocus(); } - bool error = (_scriptEngine->hasUncaughtException() || result.isError()); - QString gutter = error ? GUTTER_ERROR : GUTTER_PREVIOUS_COMMAND; - QString resultColor = error ? RESULT_ERROR_STYLE : RESULT_SUCCESS_STYLE; + // V8TODO: + //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 gutter = GUTTER_PREVIOUS_COMMAND; + QString resultColor = RESULT_SUCCESS_STYLE; QString resultStr = "" + result.toString().toHtmlEscaped() + ""; appendMessage(gutter, resultStr); diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index 202eb6ed6a8..3e8cb092c7e 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -4,21 +4,29 @@ // // Created by Ryan Huffman on 05/12/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +36,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 +74,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/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 6262210620a..80ba33d0c77 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -118,7 +118,7 @@ std::pair calculateKeyboardPositionAndOrientation() { EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; desiredProperties += PROP_ROTATION; - auto properties = DependencyManager::get()->getEntityProperties(tabletID, desiredProperties); + auto properties = DependencyManager::get()->getEntityPropertiesInternal(tabletID, desiredProperties); auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); bool landscapeMode = tablet->getLandscape(); @@ -146,7 +146,7 @@ void Key::saveDimensionsAndLocalPosition() { EntityPropertyFlags desiredProperties; desiredProperties += PROP_LOCAL_POSITION; desiredProperties += PROP_DIMENSIONS; - auto properties = DependencyManager::get()->getEntityProperties(_keyID, desiredProperties); + auto properties = DependencyManager::get()->getEntityPropertiesInternal(_keyID, desiredProperties); _originalLocalPosition = properties.getLocalPosition(); _originalDimensions = properties.getDimensions(); @@ -271,14 +271,14 @@ void Keyboard::createKeyboard() { QVariantMap leftStylusProperties { { "hand", LEFT_HAND_CONTROLLER_INDEX }, - { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, + { "filter", PickScriptingInterface::getPickLocalEntities() }, { "model", modelProperties }, { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } }; QVariantMap rightStylusProperties { { "hand", RIGHT_HAND_CONTROLLER_INDEX }, - { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, + { "filter", PickScriptingInterface::getPickLocalEntities() }, { "model", modelProperties }, { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } }; @@ -469,7 +469,7 @@ void Keyboard::switchToLayer(int layerIndex) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; desiredProperties += PROP_ROTATION; - auto oldProperties = entityScriptingInterface->getEntityProperties(_anchor.entityID, desiredProperties); + auto oldProperties = entityScriptingInterface->getEntityPropertiesInternal(_anchor.entityID, desiredProperties); glm::vec3 currentPosition = oldProperties.getPosition(); glm::quat currentOrientation = oldProperties.getRotation(); @@ -530,7 +530,7 @@ void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_POSITION; - glm::vec3 keyWorldPosition = DependencyManager::get()->getEntityProperties(id, desiredProperties).getPosition(); + glm::vec3 keyWorldPosition = DependencyManager::get()->getEntityPropertiesInternal(id, desiredProperties).getPosition(); AudioInjectorOptions audioOptions; audioOptions.localOnly = true; @@ -662,7 +662,7 @@ void Keyboard::handleTriggerContinue(const QUuid& id, const PointerEvent& event) auto entityScriptingInterface = DependencyManager::get(); EntityPropertyFlags desiredProperties; desiredProperties += PROP_ROTATION; - glm::quat orientation = entityScriptingInterface->getEntityProperties(id, desiredProperties).getRotation(); + glm::quat orientation = entityScriptingInterface->getEntityPropertiesInternal(id, desiredProperties).getRotation(); glm::vec3 yAxis = orientation * Z_AXIS; glm::vec3 yOffset = yAxis * Z_OFFSET; glm::vec3 localPosition = key.getCurrentLocalPosition() - yOffset; diff --git a/interface/src/ui/TestingDialog.cpp b/interface/src/ui/TestingDialog.cpp index 5f0b20ca7e0..12ddb159ea8 100644 --- a/interface/src/ui/TestingDialog.cpp +++ b/interface/src/ui/TestingDialog.cpp @@ -4,15 +4,18 @@ // // Created by Ryan Jones on 12/3/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TestingDialog.h" #include "Application.h" #include "ScriptEngines.h" +#include TestingDialog::TestingDialog(QWidget* parent) : QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), @@ -23,12 +26,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..2a27c9a5bc0 100644 --- a/interface/src/ui/TestingDialog.h +++ b/interface/src/ui/TestingDialog.h @@ -4,18 +4,24 @@ // // Created by Ryan Jones on 12/3/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +35,7 @@ class TestingDialog : public QDialog { private: std::unique_ptr _console; - ScriptEnginePointer _engine; + ScriptManagerPointer _scriptManager; }; #endif diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 807eed89ba4..d8e8bbbfb91 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -141,7 +141,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& qCDebug(context_overlay) << "Creating Context Overlay on top of entity with ID: " << entityItemID; // Add all necessary variables to the stack - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityPropertiesInternal(entityItemID, _entityPropertyFlags); glm::vec3 cameraPosition = qApp->getCamera().getPosition(); glm::vec3 entityDimensions = entityProperties.getDimensions(); glm::vec3 entityPosition = entityProperties.getPosition(); @@ -231,7 +231,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& } bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityPropertiesInternal(entityItemID, _entityPropertyFlags); return (entityProperties.getCertificateID().length() != 0); } @@ -294,7 +294,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID setLastInspectedEntity(entityID); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityPropertiesInternal(entityID, _entityPropertyFlags); auto nodeList = DependencyManager::get(); @@ -399,7 +399,7 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) { auto ledger = DependencyManager::get(); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityPropertiesInternal(entityItemID, _entityPropertyFlags); connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { qCDebug(entities) << "Ownership challenge timed out for" << entityItemID; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index d9fd9fdaa16..e245acfd409 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -3,17 +3,17 @@ // interface/src/ui/overlays // // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Overlays.h" #include -#include - #include #include #include @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include "VariantMapToScriptValue.h" @@ -42,6 +44,13 @@ Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") std::unordered_map Overlays::_entityToOverlayTypes; std::unordered_map Overlays::_overlayToEntityTypes; +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); +})); + Overlays::Overlays() { ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); @@ -186,590 +195,8 @@ QString Overlays::entityToOverlayType(const QString& type) { return "unknown"; } -QString Overlays::overlayToEntityType(const QString& type) { - auto iter = _overlayToEntityTypes.find(type); - if (iter != _overlayToEntityTypes.end()) { - return iter->second; - } - return "Unknown"; -} - -#define SET_OVERLAY_PROP_DEFAULT(o, d) \ - { \ - if (add && !overlayProps.contains(#o)) { \ - overlayProps[#o] = d; \ - } \ - } - -#define RENAME_PROP(o, e) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end() && \ - !overlayProps.contains(#e)) { \ - overlayProps[#e] = iter.value(); \ - } \ - } - -#define RENAME_PROP_CONVERT(o, e, C) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ - overlayProps[#e] = C(iter.value()); \ - } \ - } - -#define OVERLAY_TO_GROUP_ENTITY_PROP(o, g, e) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ - if (!overlayProps.contains(#g)) { \ - overlayProps[#g] = QVariantMap(); \ - } \ - auto map = overlayProps[#g].toMap(); \ - map[#e] = iter.value(); \ - overlayProps[#g] = map; \ - } \ - } - -#define OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(o, g, e, d) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ - if (!overlayProps.contains(#g)) { \ - overlayProps[#g] = QVariantMap(); \ - } \ - auto map = overlayProps[#g].toMap(); \ - map[#e] = iter.value(); \ - overlayProps[#g] = map; \ - } else if (add) { \ - if (!overlayProps.contains(#g)) { \ - overlayProps[#g] = QVariantMap(); \ - } \ - auto map = overlayProps[#g].toMap(); \ - map[#e] = d; \ - overlayProps[#g] = map; \ - } \ - } - -#define OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(o, e, d, C) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ - overlayProps[#e] = C(iter.value()); \ - } else if (add) { \ - overlayProps[#e] = C(d); \ - } \ - } - -#define OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(o, g, e, C) \ - { \ - auto iter = overlayProps.find(#o); \ - if (iter != overlayProps.end()) { \ - if (!overlayProps.contains(#g)) { \ - overlayProps[#g] = QVariantMap(); \ - } \ - auto map = overlayProps[#g].toMap(); \ - map[#e] = C(iter.value()); \ - overlayProps[#g] = map; \ - } \ - } - -#define GROUP_ENTITY_TO_OVERLAY_PROP(g, e, o) \ - { \ - auto iter = overlayProps.find(#g); \ - if (iter != overlayProps.end()) { \ - auto map = iter.value().toMap(); \ - auto iter2 = map.find(#e); \ - if (iter2 != map.end()) { \ - overlayProps[#o] = iter2.value(); \ - } \ - } \ - } - -#define GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(g, e, o, C) \ - { \ - auto iter = overlayProps.find(#g); \ - if (iter != overlayProps.end()) { \ - auto map = iter.value().toMap(); \ - auto iter2 = map.find(#e); \ - if (iter2 != map.end()) { \ - overlayProps[#o] = C(iter2.value()); \ - } \ - } \ - } - static QHash> savedRotations = QHash>(); -EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id) { - std::pair rotation; - return convertOverlayToEntityProperties(overlayProps, rotation, type, add, id); -} - -EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id) { - overlayProps["type"] = type; - - SET_OVERLAY_PROP_DEFAULT(alpha, 0.7); - if (type != "PolyLine") { - RENAME_PROP(p1, position); - RENAME_PROP(start, position); - } - RENAME_PROP(point, position); - if (type != "Model") { - RENAME_PROP(scale, dimensions); - } else { - RENAME_PROP(scale, modelScale); - } - RENAME_PROP(size, dimensions); - RENAME_PROP(orientation, rotation); - RENAME_PROP(localOrientation, localRotation); - RENAME_PROP(ignoreRayIntersection, ignorePickIntersection); - - RENAME_PROP_CONVERT(drawInFront, renderLayer, [](const QVariant& v) { return v.toBool() ? "front" : "world"; }); - RENAME_PROP_CONVERT(drawHUDLayer, renderLayer, [=](const QVariant& v) { - bool f = v.toBool(); - if (f) { - return QVariant("hud"); - } else if (overlayProps.contains("renderLayer")) { - return overlayProps["renderLayer"]; - } - return QVariant("world"); - }); - - OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(grabbable, grab, grabbable, false); - - OVERLAY_TO_GROUP_ENTITY_PROP(pulseMin, pulse, min); - OVERLAY_TO_GROUP_ENTITY_PROP(pulseMax, pulse, max); - OVERLAY_TO_GROUP_ENTITY_PROP(pulsePeriod, pulse, period); - OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(colorPulse, pulse, colorMode, [](const QVariant& v) { - float f = v.toFloat(); - if (f > 0.0f) { - return "in"; - } else if (f < 0.0f) { - return "out"; - } - return "none"; - }); - OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(alphaPulse, pulse, alphaMode, [](const QVariant& v) { - float f = v.toFloat(); - if (f > 0.0f) { - return "in"; - } else if (f < 0.0f) { - return "out"; - } - return "none"; - }); - - RENAME_PROP_CONVERT(textures, textures, [](const QVariant& v) { - auto map = v.toMap(); - if (!map.isEmpty()) { - auto json = QJsonDocument::fromVariant(map); - if (!json.isNull()) { - return QVariant(QString(json.toJson())); - } - } - return v; - }); - - if (type == "Shape" || type == "Box" || type == "Sphere" || type == "Gizmo") { - RENAME_PROP(solid, isSolid); - RENAME_PROP(isFilled, isSolid); - RENAME_PROP(filled, isSolid); - OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(isSolid, primitiveMode, false, [](const QVariant& v) { return v.toBool() ? "solid" : "lines"; }); - - RENAME_PROP(wire, isWire); - RENAME_PROP_CONVERT(isWire, primitiveMode, [](const QVariant& v) { return v.toBool() ? "lines" : "solid"; }); - } - - if (type == "Shape") { - SET_OVERLAY_PROP_DEFAULT(shape, "Hexagon"); - } else if (type == "Model") { - RENAME_PROP(url, modelURL); - RENAME_PROP(animationSettings, animation); - } else if (type == "Image") { - RENAME_PROP(url, imageURL); - } else if (type == "Text") { - RENAME_PROP(color, textColor); - } else if (type == "Web") { - RENAME_PROP(url, sourceUrl); - RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "Mouse" ? "mouse" : "touch"; }); - } else if (type == "Gizmo") { - RENAME_PROP(radius, outerRadius); - if (add || overlayProps.contains("outerRadius")) { - float ratio = 2.0f; - { - auto iter = overlayProps.find("outerRadius"); - if (iter != overlayProps.end()) { - ratio = iter.value().toFloat() / 0.5f; - } - } - glm::vec3 dimensions = glm::vec3(1.0f); - { - auto iter = overlayProps.find("dimensions"); - if (iter != overlayProps.end()) { - dimensions = vec3FromVariant(iter.value()); - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_DIMENSIONS; - dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); - } - } - overlayProps["dimensions"] = vec3toVariant(ratio * dimensions); - } - - if (add && !overlayProps.contains("rotation") && !overlayProps.contains("localRotation")) { - overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT)); - } else if (overlayProps.contains("rotation")) { - glm::quat rotation = quatFromVariant(overlayProps["rotation"]); - overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); - } else if (overlayProps.contains("localRotation")) { - glm::quat rotation = quatFromVariant(overlayProps["localRotation"]); - overlayProps["localRotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); - } - - { - RENAME_PROP(color, innerStartColor); - RENAME_PROP(color, innerEndColor); - RENAME_PROP(color, outerStartColor); - RENAME_PROP(color, outerEndColor); - - RENAME_PROP(startColor, innerStartColor); - RENAME_PROP(startColor, outerStartColor); - - RENAME_PROP(endColor, innerEndColor); - RENAME_PROP(endColor, outerEndColor); - - RENAME_PROP(innerColor, innerStartColor); - RENAME_PROP(innerColor, innerEndColor); - - RENAME_PROP(outerColor, outerStartColor); - RENAME_PROP(outerColor, outerEndColor); - } - - { - RENAME_PROP(alpha, innerStartAlpha); - RENAME_PROP(alpha, innerEndAlpha); - RENAME_PROP(alpha, outerStartAlpha); - RENAME_PROP(alpha, outerEndAlpha); - - RENAME_PROP(startAlpha, innerStartAlpha); - RENAME_PROP(startAlpha, outerStartAlpha); - - RENAME_PROP(endAlpha, innerEndAlpha); - RENAME_PROP(endAlpha, outerEndAlpha); - - RENAME_PROP(innerAlpha, innerStartAlpha); - RENAME_PROP(innerAlpha, innerEndAlpha); - - RENAME_PROP(outerAlpha, outerStartAlpha); - RENAME_PROP(outerAlpha, outerEndAlpha); - } - - OVERLAY_TO_GROUP_ENTITY_PROP(startAt, ring, startAngle); - OVERLAY_TO_GROUP_ENTITY_PROP(endAt, ring, endAngle); - OVERLAY_TO_GROUP_ENTITY_PROP(innerRadius, ring, innerRadius); - - OVERLAY_TO_GROUP_ENTITY_PROP(innerStartColor, ring, innerStartColor); - OVERLAY_TO_GROUP_ENTITY_PROP(innerEndColor, ring, innerEndColor); - OVERLAY_TO_GROUP_ENTITY_PROP(outerStartColor, ring, outerStartColor); - OVERLAY_TO_GROUP_ENTITY_PROP(outerEndColor, ring, outerEndColor); - OVERLAY_TO_GROUP_ENTITY_PROP(innerStartAlpha, ring, innerStartAlpha); - OVERLAY_TO_GROUP_ENTITY_PROP(innerEndAlpha, ring, innerEndAlpha); - OVERLAY_TO_GROUP_ENTITY_PROP(outerStartAlpha, ring, outerStartAlpha); - OVERLAY_TO_GROUP_ENTITY_PROP(outerEndAlpha, ring, outerEndAlpha); - - OVERLAY_TO_GROUP_ENTITY_PROP(hasTickMarks, ring, hasTickMarks); - OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksAngle, ring, majorTickMarksAngle); - OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksAngle, ring, minorTickMarksAngle); - OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksLength, ring, majorTickMarksLength); - OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksLength, ring, minorTickMarksLength); - OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksColor, ring, majorTickMarksColor); - OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksColor, ring, minorTickMarksColor); - } else if (type == "PolyLine") { - RENAME_PROP(startPoint, p1); - RENAME_PROP(start, p1); - RENAME_PROP(endPoint, p2); - RENAME_PROP(end, p2); - - RENAME_PROP(p1, position); - RENAME_PROP_CONVERT(p1, p1, [](const QVariant& v) { return vec3toVariant(glm::vec3(0.0f)); }); - RENAME_PROP_CONVERT(p2, p2, [=](const QVariant& v) { - glm::vec3 position; - bool hasPosition = false; - glm::quat rotation; - bool hasRotation = false; - - auto iter2 = overlayProps.find("position"); - if (iter2 != overlayProps.end()) { - position = vec3FromVariant(iter2.value()); - hasPosition = true; - } - iter2 = overlayProps.find("rotation"); - if (iter2 != overlayProps.end()) { - rotation = quatFromVariant(iter2.value()); - hasRotation = true; - } - - if (!add && !(hasPosition && hasRotation)) { - auto entity = DependencyManager::get()->getEntity(id); - if (entity) { - if (!hasPosition) { - position = entity->getWorldPosition(); - } - if (!hasRotation) { - rotation = entity->getWorldOrientation(); - } - } - } - - return vec3toVariant(glm::inverse(rotation) * (vec3FromVariant(v) - position)); - }); - - RENAME_PROP(localStart, p1); - RENAME_PROP(localEnd, p2); - - { - QVariantList points; - { - auto iter = overlayProps.find("p1"); - if (iter != overlayProps.end()) { - points.push_back(iter.value()); - } - } - { - auto iter = overlayProps.find("p2"); - if (iter != overlayProps.end()) { - points.push_back(iter.value()); - } - } - overlayProps["linePoints"] = points; - } - { - auto iter = overlayProps.find("lineWidth"); - if (iter != overlayProps.end()) { - QVariantList widths; - QVariant width = iter.value(); - widths.append(width); - widths.append(width); - overlayProps["strokeWidths"] = widths; - } - } - - RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toFloat() > 0.0f ? true : false; }); - SET_OVERLAY_PROP_DEFAULT(faceCamera, true); - { - QVariantList normals; - normals.append(vec3toVariant(Vectors::UP)); - normals.append(vec3toVariant(Vectors::UP)); - SET_OVERLAY_PROP_DEFAULT(normals, normals); - } - - SET_OVERLAY_PROP_DEFAULT(textures, PathUtils::resourcesUrl() + "images/whitePixel.png"); - } - - if (type == "Text" || type == "Image" || type == "Grid" || type == "Web") { - glm::quat originalRotation = ENTITY_ITEM_DEFAULT_ROTATION; - bool local = false; - { - auto iter = overlayProps.find("rotation"); - if (iter != overlayProps.end()) { - originalRotation = quatFromVariant(iter.value()); - local = false; - } else { - iter = overlayProps.find("localRotation"); - if (iter != overlayProps.end()) { - originalRotation = quatFromVariant(iter.value()); - local = true; - } else if (!add) { - auto iter2 = savedRotations.find(id); - if (iter2 != savedRotations.end()) { - originalRotation = iter2.value().first; - local = iter2.value().second; - } - } - } - } - - if (!add) { - savedRotations[id] = { originalRotation, local }; - } else { - rotationToSave = { originalRotation, local }; - } - - glm::vec3 dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; - { - auto iter = overlayProps.find("dimensions"); - if (iter != overlayProps.end()) { - bool valid = false; - dimensions = vec3FromVariant(iter.value(), valid); - if (!valid) { - dimensions = glm::vec3(vec2FromVariant(iter.value()), 0.0f); - } - } else if (!add) { - EntityPropertyFlags desiredProperties; - desiredProperties += PROP_DIMENSIONS; - dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); - } - } - - bool rotateX = dimensions.y < 0.0f; - bool rotateY = dimensions.x < 0.0f; - - { - glm::quat rotation = originalRotation; - if (rotateX) { - rotation = glm::angleAxis((float)M_PI, rotation * Vectors::RIGHT) * rotation; - } - if (rotateY) { - rotation = glm::angleAxis((float)M_PI, rotation * Vectors::UP) * rotation; - } - - if (local) { - overlayProps["localRotation"] = quatToVariant(rotation); - } else { - overlayProps["rotation"] = quatToVariant(rotation); - } - overlayProps["dimensions"] = vec3toVariant(glm::abs(dimensions)); - } - } - - QScriptEngine scriptEngine; - QScriptValue 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(); - - QString type = overlayProps["type"].toString(); - overlayProps["type"] = entityToOverlayType(type); - - if (type != "PolyLine") { - RENAME_PROP(position, p1); - RENAME_PROP(position, start); - } - RENAME_PROP(position, point); - if (type != "Model") { - RENAME_PROP(dimensions, scale); - } else { - RENAME_PROP(modelScale, scale); - } - RENAME_PROP(dimensions, size); - RENAME_PROP(ignorePickIntersection, ignoreRayIntersection); - - { - RENAME_PROP_CONVERT(primitiveMode, isSolid, [](const QVariant& v) { return v.toString() == "solid" ? true : false; }); - RENAME_PROP(isSolid, solid); - RENAME_PROP(isSolid, isFilled); - RENAME_PROP(isSolid, filled); - - RENAME_PROP_CONVERT(primitiveMode, isWire, [](const QVariant& v) { return v.toString() == "lines" ? true : false; }); - RENAME_PROP(isWire, wire); - } - - RENAME_PROP_CONVERT(renderLayer, drawInFront, [](const QVariant& v) { return v.toString() == "front" ? true : false; }); - RENAME_PROP_CONVERT(renderLayer, drawHUDLayer, [](const QVariant& v) { return v.toString() == "hud" ? true : false; }); - - GROUP_ENTITY_TO_OVERLAY_PROP(grab, grabbable, grabbable); - - GROUP_ENTITY_TO_OVERLAY_PROP(pulse, min, pulseMin); - GROUP_ENTITY_TO_OVERLAY_PROP(pulse, max, pulseMax); - GROUP_ENTITY_TO_OVERLAY_PROP(pulse, period, pulsePeriod); - GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, colorMode, colorPulse, [](const QVariant& v) { - QString f = v.toString(); - if (f == "in") { - return 1.0f; - } else if (f == "out") { - return -1.0f; - } - return 0.0f; - }); - GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, alphaMode, alphaPulse, [](const QVariant& v) { - QString f = v.toString(); - if (f == "in") { - return 1.0f; - } else if (f == "out") { - return -1.0f; - } - return 0.0f; - }); - - if (type == "Model") { - RENAME_PROP(modelURL, url); - RENAME_PROP(animation, animationSettings); - } else if (type == "Image") { - RENAME_PROP(imageURL, url); - } else if (type == "Text") { - RENAME_PROP(textColor, color); - } else if (type == "Web") { - RENAME_PROP(sourceUrl, url); - RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "mouse" ? "Mouse" : "Touch"; }); - } else if (type == "Gizmo") { - RENAME_PROP_CONVERT(dimensions, outerRadius, [](const QVariant& v) { return 2.0f * vec3FromVariant(v).x; }); - RENAME_PROP(outerRadius, radius); - - RENAME_PROP_CONVERT(rotation, rotation, [](const QVariant& v) { - glm::quat rot = quatFromVariant(v); - return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); - }); - RENAME_PROP_CONVERT(localRotation, localRotation, [](const QVariant& v) { - glm::quat rot = quatFromVariant(v); - return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); - }); - - GROUP_ENTITY_TO_OVERLAY_PROP(ring, startAngle, startAt); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, endAngle, endAt); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerRadius, innerRadius); - - GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartColor, innerStartColor); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndColor, innerEndColor); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartColor, outerStartColor); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndColor, outerEndColor); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartAlpha, innerStartAlpha); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndAlpha, innerEndAlpha); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartAlpha, outerStartAlpha); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndAlpha, outerEndAlpha); - - GROUP_ENTITY_TO_OVERLAY_PROP(ring, hasTickMarks, hasTickMarks); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksAngle, majorTickMarksAngle); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksAngle, minorTickMarksAngle); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksLength, majorTickMarksLength); - GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksLength, minorTickMarksLength); - 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"])); - glm::vec3 position = vec3FromVariant(overlayProps["position"]); - if (points.length() > 1) { - overlayProps["p1"] = vec3toVariant(points[0] + position); - overlayProps["p2"] = vec3toVariant(points[1] + position); - - overlayProps["localStart"] = vec3toVariant(points[0]); - overlayProps["localEnd"] = vec3toVariant(points[1]); - } - - RENAME_PROP(p1, startPoint); - RENAME_PROP(p1, start); - RENAME_PROP(p2, endPoint); - RENAME_PROP(p2, end); - - QVector widths = qVectorFloatFromScriptValue(scriptEngine.newVariant(overlayProps["strokeWidths"])); - if (widths.length() > 0) { - overlayProps["lineWidth"] = widths[0]; - } - - RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toBool() ? 1.0f : 0.0f; }); - } - - // Do at the end, in case this type was rotated above - RENAME_PROP(rotation, orientation); - RENAME_PROP(localRotation, localOrientation); - - return overlayProps; -} - QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (_shuttingDown) { return UNKNOWN_ENTITY_ID; @@ -800,21 +227,7 @@ QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { return QUuid(); } - QString entityType = overlayToEntityType(type); - if (entityType == "Unknown") { - return UNKNOWN_ENTITY_ID; - } - - QVariantMap propertyMap = properties.toMap(); - if (type == "rectangle3d") { - propertyMap["shape"] = "Quad"; - } - std::pair rotationToSave; - QUuid id = DependencyManager::get()->addEntityInternal(convertOverlayToEntityProperties(propertyMap, rotationToSave, entityType, true), entity::HostType::LOCAL); - if (entityType == "Text" || entityType == "Image" || entityType == "Grid" || entityType == "Web") { - savedRotations[id] = rotationToSave; - } - return id; + return UNKNOWN_ENTITY_ID; } QUuid Overlays::add2DOverlay(const Overlay::Pointer& overlay) { @@ -872,10 +285,7 @@ bool Overlays::editOverlay(const QUuid& id, const QVariant& properties) { return true; } - auto entityScriptingInterface = DependencyManager::get(); - auto propertyMap = properties.toMap(); - EntityItemProperties entityProperties = convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id); - return !entityScriptingInterface->editEntity(id, entityProperties).isNull(); + return false; } bool Overlays::editOverlays(const QVariant& propertiesById) { @@ -887,7 +297,6 @@ bool Overlays::editOverlays(const QVariant& propertiesById) { QVariantMap deferred; const QVariantMap map = propertiesById.toMap(); - auto entityScriptingInterface = DependencyManager::get(); for (const auto& key : map.keys()) { QUuid id = QUuid(key); const QVariant& properties = map[key]; @@ -900,8 +309,7 @@ bool Overlays::editOverlays(const QVariant& propertiesById) { } overlay->setProperties(properties.toMap()); } else { - auto propertyMap = properties.toMap(); - entityScriptingInterface->editEntity(id, convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id)); + qDebug() << "Overlays::editOverlays doesn't support editing entities anymore"; } } @@ -998,51 +406,9 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { return bestID; } -QVariant Overlays::getProperty(const QUuid& id, const QString& property) { - Overlay::Pointer overlay = get2DOverlay(id); - if (overlay) { - // We don't support getting properties from QML Overlays right now - return QVariant(); - } - - QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); - auto propIter = overlayProperties.find(property); - if (propIter != overlayProperties.end()) { - return propIter.value(); - } - return QVariant(); -} - -QVariantMap Overlays::getProperties(const QUuid& id, const QStringList& properties) { - Overlay::Pointer overlay = get2DOverlay(id); - QVariantMap result; - if (overlay) { - // We don't support getting properties from QML Overlays right now - return QVariantMap(); - } - - QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); - for (const auto& property : properties) { - auto propIter = overlayProperties.find(property); - if (propIter != overlayProperties.end()) { - result.insert(property, propIter.value()); - } - } - return result; -} - -QVariantMap Overlays::getOverlaysProperties(const QVariant& propertiesById) { - QVariantMap map = propertiesById.toMap(); - QVariantMap result; - for (const auto& key : map.keys()) { - result[key] = getProperties(QUuid(key), map[key].toStringList()); - } - return result; -} - 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 +476,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) { @@ -1347,53 +714,10 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { * {@link Overlays.OverlayProperties-Text|OverlayProperties-Text} * "cube"3D *

A cube. A "shape" overlay can also be used to create a cube.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube} - * "sphere"3D - *

A sphere. A "shape" overlay can also be used to create a sphere.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere} - * "shape"3D - *

A geometric shape, such as a cube, sphere, or cylinder.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape} - * "model"3D - *

A model.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Model|OverlayProperties-Model} - * "image3d"3D - *

An image. Synonym: "billboard".

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D} - * "rectangle3d"3D - *

A rectangle.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D} - * "text3d"3D - *

Some text.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D} - * "web3d"3D - *

Web content.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Web3D|OverlayProperties-Web3D} - * "line3d"3D - *

A line.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Line3D|OverlayProperties-Line3D} - * "grid"3D - *

A grid of lines in a plane.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid} - * "circle3d"3D - *

A circle.

- *

Deprecated.

- * {@link Overlays.OverlayProperties-Circle3D|OverlayProperties-Circle3D} * * *

2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are * rendered at a position and orientation in-world.

- *

3D overlays are deprecated. Use local {@link Entities} instead.

* @typedef {string} Overlays.OverlayType */ @@ -1409,17 +733,6 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { * @see {@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle} * @see {@link Overlays.OverlayProperties-Image|OverlayProperties-Image} * @see {@link Overlays.OverlayProperties-Text|OverlayProperties-Text} - * @see {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube} — Deprecated. - * @see {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere} — Deprecated. - * @see {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape} — Deprecated. - * @see {@link Overlays.OverlayProperties-Model|OverlayProperties-Model} — Deprecated. - * @see {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D} — Deprecated. - * @see {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D} — Deprecated. - * @see {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D} — Deprecated. - * @see {@link Overlays.OverlayProperties-Web3D|OverlayProperties-Web3D} — Deprecated. - * @see {@link Overlays.OverlayProperties-Line3D|OverlayProperties-Line3D} — Deprecated. - * @see {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid} — Deprecated. - * @see {@link Overlays.OverlayProperties-Circle3D|OverlayProperties-Circle3D} — Deprecated. */ /*@jsdoc @@ -1486,613 +799,3 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { * @property {number} borderAlpha=1.0 - The opacity of the border, 0.01.0. * Write-only. */ - -/*@jsdoc - * The "cube" {@link Overlays.OverlayType|OverlayType} is for 3D cubes. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Box|Box} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Cube - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} isSolid=false - true if the overlay is rendered as a solid, false if it is - * rendered as a wire frame. - * Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - */ - -/*@jsdoc - * The "sphere" {@link Overlays.OverlayType|OverlayType} is for 3D spheres. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Sphere|Sphere} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Sphere - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} isSolid=false - true if the overlay is rendered as a solid, false if it is - * rendered as a wire frame. - * Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - */ - -/*@jsdoc - * The "rectangle3D" {@link Overlays.OverlayType|OverlayType} is for 3D rectangles. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Shape|Shape} entity, with the shape - * property value being "Quad". - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Rectangle3D - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} isSolid=false - true if the overlay is rendered as a solid, false if it is - * rendered as a wire frame. - * Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - */ - -/*@jsdoc - *

A "shape" {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical - * shapes:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
- * @typedef {string} Overlays.Shape - */ - -/*@jsdoc - * The "shape" {@link Overlays.OverlayType|OverlayType} is for 3D shapes. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Shape|Shape} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Shape - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} isSolid=false - true if the overlay is rendered as a solid, false if it is - * rendered as a wire frame. - * Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. - */ - -/*@jsdoc - * The "model" {@link Overlays.OverlayType|OverlayType} is for 3D models. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Model|Model} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Model - * @property {string} name - The name of the overlay. - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: size. - * @property {Vec3} scale - The scale factor applied to the model's dimensions. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {string} url - The URL of the glTF, FBX, or OBJ model used for the overlay. glTF models may be in JSON or binary - * format (".gltf" or ".glb" URLs respectively). Baked models' URLs have ".baked" before the file type. Model files may - * also be compressed in GZ format, in which case the URL ends in ".gz". - * @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load - * first. Currently not used. - * @property {Object.|string} textures - Texture name, URL pairs used when rendering the model in place of the - * model's original textures, per the {@link Entities.EntityProperties-Model} property of the same name. - *

The value can be an object or a JSON string when setting the value; it is a JSON string when getting the value.

- * @property {Entities.AnimationProperties} animationSettings - An animation to play on the model. - */ - -/*@jsdoc - * The "text3D" {@link Overlays.OverlayType|OverlayType} is for 3D text. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Text|Text} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Text3D - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay text. Synonym: textColor. - * @property {number} alpha=0.7 - The opacity of the overlay text, 0.01.0. - *

Currently not used; use textAlpha instead.

- * @comment CURRENTLY BROKEN - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - */ - -/*@jsdoc - * The "image3D" {@link Overlays.OverlayType|OverlayType} is for 3D images. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Image|Image} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Image3D - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {string} url - The URL of the image to display. - */ - -/*@jsdoc - * The "web3d" {@link Overlays.OverlayType|OverlayType} is for 3D web surfaces. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Web|Web} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Web3D - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {string} url - The URL of the web page to display. - */ - -/*@jsdoc - * The "line3d" {@link Overlays.OverlayType|OverlayType} is for 3D lines. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-PolyLine|PolyLine} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Line3D - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to. - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - * @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is - * attached to if parentID is an avatar skeleton. A value of 65535 means "no joint". - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - - * @property {Vec3} start - The start point of the line. Synonyms: startPoint and p1. - *

Note: If parentID is set, use localStart to set the local position of the - * start point.

- * @property {Vec3} end - The end point of the line. Synonyms: endPoint and p2. - *

Note: If parentID is set, use localEnd to set the local position of the - * end point.

- - * @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as start. - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - * @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a - * endParentID set, otherwise the same value as end. - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - - * @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end - * points. - *

Currently doesn't work.

- * @comment CURRENTLY BROKEN - - * @property {number} glow=0 - If glow > 0, the line is rendered with a glow. - * @property {number} lineWidth=0.02 - Width of the line, in meters. - *

You can set this property's value but currently cannot retrieve its value. Use the strokeWidths - * property to retrieve its value instead.

- */ - -/*@jsdoc - * The "grid" {@link Overlays.OverlayType|OverlayType} is for 3D grids. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Grid|Grid} entity. - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Grid - * @property {string} name - The name of the overlay. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.01.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - */ - -/*@jsdoc - * The "circle3d" {@link Overlays.OverlayType|OverlayType} is for 3D circles. - * It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}. - * It additionally has properties per the {@link Entities.EntityProperties-Gizmo|Gizmo} entity, with the - * gizmoType property value being "ring". - *

Deprecated: Use local {@link Entities} instead.

- * @typedef {object} Overlays.OverlayProperties-Circle3D - * @property {string} name - The name of the overlay. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * Read-only. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. Synonym: localOrientation. - * @property {boolean} isSolid=false - true if the overlay is rendered as a solid, false if it is - * rendered as a wire frame. - * Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} ignorePickIntersection=false - true if {@link Picks} ignore the overlay, false - * if they don't. - * Synonym: ignoreRayIntersection. - * @property {boolean} drawInFront=false - true if the overlay is rendered on top of the world layer but behind - * the HUD surface. - * @property {boolean} drawHUDLayer=false - true if the overlay is rendered in front of everything, including the - * HUD surface. - * @property {boolean} grabbable=false - true if the overlay can be grabbed, false if it can't be. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is - * parented to if parentID is set. Use 65535 or -1 to parent to the parent's position and orientation rather - * than a joint. - * - * @property {number} startAt=0 - The counter-clockwise angle from the overlay's x-axis that drawing starts at, in degrees. - * @property {number} endAt=360 - The counter-clockwise angle from the overlay's x-axis that drawing ends at, in degrees. - * @property {number} outerRadius=1 - The outer radius of the overlay in meters. Synonym: radius. - * @property {number} innerRadius=0 - The inner radius of the overlay in meters. - * @property {Color} color - Sets the color of the overlay. Setting this value sets the values of innerStartColor, - * innerEndColor, outerStartColor, and outerEndColor. - * Write-only. - * @property {Color} startColor - Sets the values of innerStartColor and outerStartColor. - * Write-only. - * @property {Color} endColor - Sets the values of innerEndColor and outerEndColor. - * Write-only. - * @property {Color} innerColor - Sets the values of innerStartColor and innerEndColor. - * Write-only. - * @property {Color} outerColor - Sets the values of outerStartColor and outerEndColor. - * Write-only. - * @property {Color} innerStartcolor=255,255,255 - The color at the inner start point of the overlay. - * @property {Color} innerEndColor=255,255,255 - The color at the inner end point of the overlay. - * @property {Color} outerStartColor=255,255,255 - The color at the outer start point of the overlay. - * @property {Color} outerEndColor=255,255,255 - The color at the outer end point of the overlay. - * @property {number} alpha - Sets the opacity of the overlay, 0.01.0. Setting this value - * sets the values of innerStartAlpha, innerEndAlpha, outerStartAlpha, and - * outerEndAlpha. Synonym: Alpha. - * @property {number} startAlpha - Sets the values of innerStartAlpha and outerStartAlpha. - * Write-only. - * @property {number} endAlpha - Sets the values of innerEndAlpha and outerEndAlpha. - * Write-only. - * @property {number} innerAlpha - Sets the values of innerStartAlpha and innerEndAlpha. - * Write-only. - * @property {number} outerAlpha - Sets the values of outerStartAlpha and outerEndAlpha. - * Write-only. - * @property {number} innerStartAlpha=0.7 - The opacity at the inner start point of the overlay, 0.0 – - * 1.0. - * @property {number} innerEndAlpha=0.7 - The opacity at the inner end point of the overlay, 0.0 – - * 1.0. - * @property {number} outerStartAlpha=0.7 - The opacity at the outer start point of the overlay, 0.0 – - * 1.0. - * @property {number} outerEndAlpha=0.7 - The opacity at the outer end point of the overlay, 0.0 – - * 1.0. - * - * @property {boolean} hasTickMarks=false - true if tick marks are drawn, false if they aren't. - * @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees. - * @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees. - * @property {number} majorTickMarksLength=0 - The length of the major tick marks, in meters. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {number} minorTickMarksLength=0 - The length of the minor tick marks, in meters. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {Color} majorTickMarksColor=0,0,0 - The color of the major tick marks. - * @property {Color} minorTickMarksColor=0,0,0 - The color of the minor tick marks. - */ diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index e59880c6d98..b3d9992bd08 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -4,9 +4,11 @@ // // Modified by Zander Otavka on 7/15/15 // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // Exposes methods to scripts for managing `Overlay`s and `OverlayPanel`s. // @@ -20,15 +22,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 +55,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: @@ -74,8 +77,6 @@ class ParabolaToOverlayIntersectionResult { *

Note: 3D overlays are local {@link Entities}, internally, so many of the methods also work with * entities.

* - *

3D overlays are deprecated: Use local {@link Entities} for these instead.

- * * @namespace Overlays * * @hifi-interface @@ -120,8 +121,6 @@ class Overlays : public QObject { void cleanupAllOverlays(); - mutable QScriptEngine _scriptEngine; - public slots: /*@jsdoc * Adds an overlay to the scene. @@ -311,74 +310,6 @@ public slots: */ QUuid getOverlayAtPoint(const glm::vec2& point); - /*@jsdoc - * Gets a specified property value of a 3D overlay (or entity). - *

Note: 2D overlays' property values cannot be retrieved.

- * @function Overlays.getProperty - * @param {Uuid} id - The ID of the 3D overlay (or entity). - * @param {string} property - The name of the property to get the value of. - * @returns {object} The value of the property if the 3D overlay and property can be found, otherwise - * undefined. - * @example Create an overlay in front of your avatar then report its alpha property value. - * var overlay = Overlays.addOverlay("cube", { - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * var alpha = Overlays.getProperty(overlay, "alpha"); - * print("Overlay alpha: " + alpha); - */ - QVariant getProperty(const QUuid& id, const QString& property); - - /*@jsdoc - * Gets specified property values of a 3D overlay (or entity). - *

Note: 2D overlays' property values cannot be retrieved.

- * @function Overlays.getProperties - * @param {Uuid} id - The ID of the overlay (or entity). - * @param {Array.} properties - The names of the properties to get the values of. - * @returns {Overlays.OverlayProperties} The values of valid properties if the overlay can be found, otherwise an empty - * object. - * @example Create an overlay in front of your avatar then report some of its properties. - * var overlay = Overlays.addOverlay("cube", { - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * var properties = Overlays.getProperties(overlay, ["color", "alpha", "grabbable"]); - * print("Overlay properties: " + JSON.stringify(properties)); - */ - QVariantMap getProperties(const QUuid& id, const QStringList& properties); - - /*@jsdoc - * Gets the values of multiple overlays' (or entities') properties. - * @function Overlays.getOverlaysProperties - * @param propertiesById {object.>} - An object with overlay (or entity) IDs as keys and arrays of the - * names of properties to get for each as values. - * @returns {object.} An object with overlay (or entity) IDs as keys and - * {@link Overlays.OverlayProperties|OverlayProperties} as values. - * @example Create two cube overlays in front of your avatar then get some of their properties. - * var overlayA = Overlays.addOverlay("cube", { - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * var overlayB = Overlays.addOverlay("cube", { - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.3, y: 0, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * var propertiesToGet = {}; - * propertiesToGet[overlayA] = ["color", "alpha"]; - * propertiesToGet[overlayB] = ["dimensions"]; - * var properties = Overlays.getOverlaysProperties(propertiesToGet); - * print("Overlays properties: " + JSON.stringify(properties)); - */ - QVariantMap getOverlaysProperties(const QVariant& overlaysProperties); - /*@jsdoc * Finds the closest 3D overlay (or local entity) intersected by a {@link PickRay}. * @function Overlays.findRayIntersection @@ -411,8 +342,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); @@ -759,14 +690,9 @@ public slots: QMouseEvent* event, PointerEvent::EventType eventType); static QString entityToOverlayType(const QString& type); - static QString overlayToEntityType(const QString& type); static std::unordered_map _entityToOverlayTypes; static std::unordered_map _overlayToEntityTypes; - QVariantMap convertEntityToOverlayProperties(const EntityItemProperties& entityProps); - EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id); - EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); - private slots: void mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event); void mousePressOffPointerEvent(); diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 2e811969eca..7500237a526 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2014-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..872051bcf0f 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -4,27 +4,29 @@ // // Created by Howard Stearns on 10/15/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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,54 +76,55 @@ 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); return; } + // This was here before, but marking as V8TODO // POTENTIAL OPTIMIZATION: cache the types we've seen. I.e, keep a dictionary mapping property names to an enumeration of types. // 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(); - Q_ASSERT(false); + 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..ae7d8b4afc5 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -3,9 +3,11 @@ // // Created by Anthony J. Thibault on 9/2/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AnimVariant_h @@ -17,10 +19,12 @@ #include #include #include -#include #include #include #include "AnimationLogging.h" +#include + +class ScriptEngine; class AnimVariant { public: @@ -229,9 +233,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 +278,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..f879a7eea62 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -4,9 +4,11 @@ // // Created by Andrzej Kapolka on 4/14/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright Overte e.V. 2023 // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AnimationCache_h @@ -14,8 +16,6 @@ #include #include -#include -#include #include #include diff --git a/libraries/animation/src/AnimationObject.cpp b/libraries/animation/src/AnimationObject.cpp index bcbf4971995..61efd936290 100644 --- a/libraries/animation/src/AnimationObject.cpp +++ b/libraries/animation/src/AnimationObject.cpp @@ -4,34 +4,49 @@ // // Created by Andrzej Kapolka on 4/17/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AnimationObject.h" -#include +#include +#include +#include +#include #include "AnimationCache.h" +STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager) { + registerAnimationTypes(manager->engine().get()); +}); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + registerAnimationPrototypes(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); +} + +void registerAnimationPrototypes(ScriptEngine* 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..d381cdc872a 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -4,20 +4,22 @@ // // Created by Andrzej Kapolka on 4/17/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AnimationObject_h #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 +37,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 +74,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 +88,7 @@ class AnimationFrameObject : public QObject, protected QScriptable { Q_INVOKABLE QVector getRotations() const; }; -void registerAnimationTypes(QScriptEngine* engine); +void registerAnimationTypes(ScriptEngine* engine); +void registerAnimationPrototypes(ScriptEngine* engine); #endif // hifi_AnimationObject_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 32e44a11818..85d008d4dac 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -4,16 +4,17 @@ // // Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Rig.h" #include #include -#include #include #include @@ -21,7 +22,10 @@ #include #include #include +#include +#include #include +#include #include #include "AnimationLogging.h" @@ -1584,7 +1588,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())) { @@ -1594,19 +1598,19 @@ QScriptValue Rig::addAnimationStateHandler(QScriptValue handler, QScriptValue pr _nextStateHandlerId++; } StateHandler& data = _stateHandlers[_nextStateHandlerId]; - data.function = handler; + data.function = std::make_shared(handler); data.useNames = propertiesList.isArray(); 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 +1620,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 +1639,9 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh // call out: int identifier = data.key(); StateHandler& value = data.value(); - QScriptValue& function = value.function; + std::shared_ptr 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 +1654,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"; + } //V8TODO: std::shared_ptr function should probably be reset here if it's a local copy? + }, + 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. @@ -1670,7 +1695,9 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh // Gather results in (likely from an earlier update). // Note: the behavior is undefined if a handler (re-)sets a trigger. Scripts should not be doing that. - _animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registgered wins. (_map preserves order). + + // V8TODO: This causes a deadlock right now, and in any case will cause stutters. Probably should be done on script thread instead + _animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registered wins. (_map preserves order). } } @@ -1685,6 +1712,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons ++_evaluationCount; + // V8TODO: this causes a deadlock right now updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); if (_networkNode) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e3899f3c3da..69748ad0bad 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -6,9 +6,11 @@ // // Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef __hifi__Rig__ @@ -16,10 +18,10 @@ #include #include -#include #include #include #include +#include #include "AnimNode.h" #include "AnimNodeLoader.h" @@ -40,7 +42,7 @@ class Rig : public QObject { struct StateHandler { AnimVariantMap results; QStringList propertyNames; - QScriptValue function; + std::shared_ptr function; bool useNames; }; @@ -205,9 +207,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..2f35b227fda 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + set(TARGET_NAME audio-client) if (ANDROID) set(PLATFORM_QT_COMPONENTS AndroidExtras) @@ -6,6 +10,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..1cfaf42aa2d 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright 2013-2016, High Fidelity, Inc. +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + set(TARGET_NAME audio) setup_hifi_library(Network) @@ -5,4 +9,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..fd5e3ca81bf 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -3,13 +3,44 @@ // libraries/audio/src // // Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AudioEffectOptions.h" +#include +#include +#include +#include +#include + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + ScriptValue audioEffectOptionsConstructorValue = scriptEngine->newFunction(AudioEffectOptions::constructor); + scriptEngine->globalObject().setProperty("AudioEffectOptions", audioEffectOptionsConstructorValue); +}); + +ScriptValue audioEffectOptionsToScriptValue(ScriptEngine* scriptEngine, const AudioEffectOptions& audioEffectOptions) { + qCritical() << "Conversion of AudioEffectOptions to ScriptValue should never happen."; + return ScriptValue(); +} + +bool audioEffectOptionsFromScriptValue(const ScriptValue& scriptValue, AudioEffectOptions& audioEffectOptions) { + audioEffectOptions = AudioEffectOptions(scriptValue); + return true; +} + static const QString BANDWIDTH_HANDLE = "bandwidth"; static const QString PRE_DELAY_HANDLE = "preDelay"; static const QString LATE_DELAY_HANDLE = "lateDelay"; @@ -54,7 +85,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 +114,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 +168,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..5b85dbc1c1f 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -3,17 +3,21 @@ // libraries/audio/src // // Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AudioEffectOptions_h #define hifi_AudioEffectOptions_h #include -#include -#include +#include + +class ScriptContext; +class ScriptEngine; /*@jsdoc * Audio effect options used by the {@link Audio} API. @@ -78,11 +82,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; } @@ -180,4 +184,10 @@ class AudioEffectOptions : public QObject { float _wetDryMix; // [0, 100] percent }; +Q_DECLARE_METATYPE(AudioEffectOptions); + +ScriptValue audioEffectOptionsToScriptValue(ScriptEngine* scriptEngine, const AudioEffectOptions& audioEffectOptions); + +bool audioEffectOptionsFromScriptValue(const ScriptValue& scriptValue, AudioEffectOptions& audioEffectOptions); + #endif // hifi_AudioEffectOptions_h diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 39b807fd769..39fdfb0a619 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -4,16 +4,18 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AudioInjectorOptions.h" -#include - -#include +#include +#include +#include #include "AudioLogging.h" @@ -32,8 +34,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 +68,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 +79,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..9a6b11ddb04 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -4,18 +4,21 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AudioInjectorOptions_h #define hifi_AudioInjectorOptions_h -#include - #include #include +#include + +class ScriptEngine; class AudioInjectorOptions { public: @@ -35,7 +38,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 80% rename from libraries/script-engine/src/AudioScriptingInterface.cpp rename to libraries/audio/src/AudioScriptingInterface.cpp index a55cac292f8..cc90c909ff4 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AudioScriptingInterface.h" @@ -16,11 +18,26 @@ #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_TYPES_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + registerAudioMetaTypes(scriptEngine); +}); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptEngine->registerGlobalObject("Audio", DependencyManager::get().data()); +}); + +void registerAudioMetaTypes(ScriptEngine* engine) { + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); } 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 6ebe42d67f2..9b276ff6cf7 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,12 +17,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 +294,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 55% rename from libraries/script-engine/src/ScriptAudioInjector.cpp rename to libraries/audio/src/ScriptAudioInjector.cpp index 0e42ec31e70..a4b4b89cb20 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/audio/src/ScriptAudioInjector.cpp @@ -1,29 +1,41 @@ // // ScriptAudioInjector.cpp -// libraries/script-engine/src +// libraries/audio/src // // Created by Stephen Birarda on 2015-02-11. // Copyright 2015 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptAudioInjector.h" -#include "ScriptEngineLogging.h" +#include +#include +#include +#include +#include -QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in) { +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); +})); + +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) { - out = qobject_cast(object.toQObject()); +bool injectorFromScriptValue(const ScriptValue& object, ScriptAudioInjector*& out) { + return (out = qobject_cast(object.toQObject())) != nullptr; } ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/audio/src/ScriptAudioInjector.h similarity index 92% rename from libraries/script-engine/src/ScriptAudioInjector.h rename to libraries/audio/src/ScriptAudioInjector.h index de2c5de107f..3b160211168 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/audio/src/ScriptAudioInjector.h @@ -1,12 +1,14 @@ // // ScriptAudioInjector.h -// libraries/script-engine/src +// libraries/audio/src // // Created by Stephen Birarda on 2015-02-11. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,8 +18,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 +148,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 385ebf66729..dcb67014984 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Sound.h" @@ -26,6 +28,8 @@ #include #include #include +#include +#include #include "AudioRingBuffer.h" #include "AudioLogging.h" @@ -422,14 +426,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..878343e722b 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 1/2/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_Sound_h @@ -16,13 +18,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 +173,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/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index 280fbc0328b..e5ccca550b8 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -4,9 +4,11 @@ // // Created by Leonardo Murillo on 6/1/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AutoUpdater.h" @@ -58,6 +60,7 @@ void AutoUpdater::getLatestVersionData() { void AutoUpdater::parseLatestVersionData() { QNetworkReply* sender = qobject_cast(QObject::sender()); + Q_ASSERT(sender != nullptr); QXmlStreamReader xml(sender); diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 0175d1113ae..aaafda1ce3a 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,11 +1,14 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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 +17,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..c96d8118c0a 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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 2076a88d324..c71da50b1ab 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 4/9/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -35,6 +37,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -63,6 +70,20 @@ static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water #define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + registerAvatarTypes(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + registerAvatarPrototypes(scriptEngine); +}); + size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients) { return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float); } @@ -2535,69 +2556,78 @@ 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; + Q_ASSERT(engine); 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; + Q_ASSERT(engine); 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; + Q_ASSERT(engine); 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; + Q_ASSERT(engine); 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; + Q_ASSERT(engine); 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; + Q_ASSERT(engine); thisObject() = engine()->toScriptValue(data); } bool AttachmentDataObject::getIsSoft() const { - return qscriptvalue_cast(thisObject()).isSoft; + return scriptvalue_cast(thisObject()).isSoft; +} + +void registerAvatarTypes(ScriptEngine* engine) { + scriptRegisterSequenceMetaType >(engine); } -void registerAvatarTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); +void registerAvatarPrototypes(ScriptEngine* engine) { engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( - new AttachmentDataObject(), QScriptEngine::ScriptOwnership)); + new AttachmentDataObject(), ScriptEngine::ScriptOwnership)); } void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { @@ -3146,40 +3176,43 @@ 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); + Q_ASSERT(value.face < 7); 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()); + Q_ASSERT(value.face < 7); - 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 @@ -3192,8 +3225,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); OVERTE_IGNORE_DEPRECATED_BEGIN @@ -3205,7 +3238,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); @@ -3213,13 +3246,13 @@ 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); OVERTE_IGNORE_DEPRECATED_BEGIN @@ -3227,6 +3260,7 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& OVERTE_IGNORE_DEPRECATED_END 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 6a41437e053..0b2a925de01 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -5,10 +5,11 @@ // Created by Stephen Birarda on 4/9/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AvatarData_h @@ -33,8 +34,6 @@ #include #include #include -#include -#include #include #include @@ -51,12 +50,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; @@ -775,16 +778,6 @@ class AvatarData : public QObject, public SpatiallyNestable { */ Q_INVOKABLE char getHandState() const { return _handState; } - const QVector& getRawJointData() const { return _jointData; } - - /*@jsdoc - * Sets joint translations and rotations from raw joint data. - * @function Avatar.setRawJointData - * @param {JointData[]} data - The raw joint data. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE void setRawJointData(QVector data); - /*@jsdoc * Sets a specific joint's rotation and position relative to its parent, in model coordinates. *

Warning: These coordinates are not necessarily in meters.

@@ -1703,6 +1696,9 @@ public slots: QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID); QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID); + const QVector& getRawJointData() const { return _jointData; } + void setRawJointData(QVector data); + void unpackSkeletonModelURL(const QByteArray& data); void unpackSkeletonData(const QByteArray& data); @@ -1891,6 +1887,9 @@ public slots: Q_DISABLE_COPY(AvatarData) friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); + // Required for setRawJointData. Making setRawJointData public would expose it to scripting interface. + friend class SkeletonModel; + friend class MyAvatar; static QUrl _defaultFullAvatarModelUrl; }; Q_DECLARE_METATYPE(AvatarData*) @@ -1925,7 +1924,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) @@ -1955,22 +1954,23 @@ class AttachmentDataObject : public QObject, protected QScriptable { Q_INVOKABLE bool getIsSoft() const; }; -void registerAvatarTypes(QScriptEngine* engine); +void registerAvatarTypes(ScriptEngine* engine); +void registerAvatarPrototypes(ScriptEngine* engine); class RayToAvatarIntersectionResult { public: bool intersects { false }; QUuid avatarID; float distance { FLT_MAX }; - BoxFace face; - glm::vec3 intersection; - glm::vec3 surfaceNormal; + BoxFace face { UNKNOWN_FACE }; + glm::vec3 intersection { glm::vec3(0.0f, 0.0f, 0.0f) }; + glm::vec3 surfaceNormal { glm::vec3(0.0f, 1.0f, 0.0f) }; int jointIndex { -1 }; 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 { @@ -1987,8 +1987,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..a07c4025551 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -4,13 +4,35 @@ // // Created by Zach Fox on 2017-04-10. // Copyright 2017 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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_TYPES_INITIALIZER((+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine, "ScriptAvatarData*"); +})); + ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { diff --git a/libraries/baking/CMakeLists.txt b/libraries/baking/CMakeLists.txt index 12fb1928771..4439a7fc960 100644 --- a/libraries/baking/CMakeLists.txt +++ b/libraries/baking/CMakeLists.txt @@ -1,7 +1,11 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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/JSBaker.cpp b/libraries/baking/src/JSBaker.cpp index bd93ac313de..708208303f3 100644 --- a/libraries/baking/src/JSBaker.cpp +++ b/libraries/baking/src/JSBaker.cpp @@ -4,9 +4,11 @@ // // Created by Utkarsh Gautam on 9/18/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "JSBaker.h" @@ -77,6 +79,7 @@ void JSBaker::loadScript() { void JSBaker::handleScriptNetworkReply() { auto requestReply = qobject_cast(sender()); + Q_ASSERT(requestReply != nullptr); if (requestReply->error() == QNetworkReply::NoError) { qCDebug(js_baking) << "Downloaded script" << _jsURL; diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index 540a2ee358b..48718cd6c31 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -4,9 +4,11 @@ // // Created by Sam Gondelman on 2/26/2019 // Copyright 2019 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MaterialBaker.h" @@ -20,6 +22,8 @@ #include #include +#include +#include #include @@ -32,7 +36,8 @@ MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QStr _isURL(isURL), _destinationPath(destinationPath), _bakedOutputDir(bakedOutputDir), - _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)) + _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)), + _scriptEngine(newScriptEngine()) { } @@ -209,13 +214,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..129b36aa8fd 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -4,9 +4,11 @@ // // Created by Sam Gondelman on 2/26/2019 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_MaterialBaker_h @@ -20,6 +22,7 @@ #include "baking/TextureFileNamer.h" #include +#include static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; @@ -69,7 +72,7 @@ private slots: QString _textureOutputDir; QString _bakedMaterialData; - QScriptEngine _scriptEngine; + ScriptEnginePointer _scriptEngine; static std::function _getNextOvenWorkerThreadOperator; TextureFileNamer _textureFileNamer; diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 936208ca42a..017aecdfe96 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -4,9 +4,11 @@ // // Created by Utkarsh Gautam on 9/29/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ModelBaker.h" @@ -177,6 +179,7 @@ void ModelBaker::saveSourceModel() { void ModelBaker::handleModelNetworkReply() { auto requestReply = qobject_cast(sender()); + Q_ASSERT(requestReply != nullptr); if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded" << _modelURL; diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 6f18a5da40a..9fe73da4aa5 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 4/5/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TextureBaker.h" @@ -114,6 +116,7 @@ void TextureBaker::loadTexture() { void TextureBaker::handleTextureNetworkReply() { auto requestReply = qobject_cast(sender()); + Q_ASSERT(requestReply != nullptr); if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded texture" << _textureURL; diff --git a/libraries/controllers/CMakeLists.txt b/libraries/controllers/CMakeLists.txt index 9c6bbf4aaeb..43ae9297281 100644 --- a/libraries/controllers/CMakeLists.txt +++ b/libraries/controllers/CMakeLists.txt @@ -1,10 +1,14 @@ +# Copyright 2013-2017, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..4d5703f622c 100644 --- a/libraries/controllers/src/controllers/Pose.cpp +++ b/libraries/controllers/src/controllers/Pose.cpp @@ -1,17 +1,20 @@ // // Created by Bradley Austin Davis on 2015/10/18 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Pose.h" -#include -#include +#include +#include #include +#include namespace controller { @@ -39,8 +42,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 +52,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 +69,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..7187dfa3f83 100644 --- a/libraries/controllers/src/controllers/Pose.h +++ b/libraries/controllers/src/controllers/Pose.h @@ -2,17 +2,19 @@ // Created by Bradley Austin Davis on 2015/10/18 // (based on UserInputMapper inner class created by Sam Gateau on 4/27/15) // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once #ifndef hifi_controllers_Pose_h #define hifi_controllers_Pose_h +#include -class QScriptEngine; -class QScriptValue; +class ScriptEngine; #include @@ -44,8 +46,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..0c8869c8956 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptingInterface.h" @@ -25,6 +27,28 @@ #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) { + return (out = qobject_cast(object.toQObject())) != nullptr; +} + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); + manager->connect(manager, &ScriptManager::scriptEnding, [manager]() { + ; + }); +})); static QRegularExpression SANITIZE_NAME_EXPRESSION{ "[\\(\\)\\.\\s]" }; @@ -101,19 +125,11 @@ namespace controller { auto userInputMapper = DependencyManager::get(); return userInputMapper->getPose(Input((uint32_t)source)); } - - QVector ScriptingInterface::getAllActions() { - return DependencyManager::get()->getAllActions(); - } QString ScriptingInterface::getDeviceName(unsigned int device) { return DependencyManager::get()->getDeviceName((unsigned short)device); } - QVector ScriptingInterface::getAvailableInputs(unsigned int device) { - return DependencyManager::get()->getAvailableInputs((unsigned short)device); - } - int ScriptingInterface::findDevice(QString name) { return DependencyManager::get()->findDevice(name); } @@ -167,7 +183,7 @@ namespace controller { InputRecorder* inputRecorder = InputRecorder::getInstance(); inputRecorder->saveRecording(); } - + void ScriptingInterface::loadInputRecording(const QString& file) { InputRecorder* inputRecorder = InputRecorder::getInstance(); inputRecorder->loadRecording(file); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index ac109b41cef..96680dd28c2 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/17/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -29,7 +31,6 @@ #include #include -#include #include #include @@ -72,25 +73,6 @@ namespace controller { ScriptingInterface(); virtual ~ScriptingInterface() {}; - /*@jsdoc - * Gets a list of all available actions. - * @function Controller.getAllActions - * @returns {Action[]} All available actions. - * @deprecated This function is deprecated and will be removed. It no longer works. - */ - // FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13921 - Q_INVOKABLE QVector getAllActions(); - - /*@jsdoc - * Gets a list of all available inputs for a hardware device. - * @function Controller.getAvailableInputs - * @param {number} deviceID - Integer ID of the hardware device. - * @returns {NamedPair[]} All available inputs for the device. - * @deprecated This function is deprecated and will be removed. It no longer works. - */ - // FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13922 - Q_INVOKABLE QVector getAvailableInputs(unsigned int device); - /*@jsdoc * Finds the name of a particular controller from its device ID. * @function Controller.getDeviceName diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index dc37798fc5d..0a23f6e386c 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "UserInputMapper.h" @@ -26,6 +28,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" @@ -264,6 +270,9 @@ void UserInputMapper::update(float deltaTime) { channel = Pose(); } + // Remove callbacks to script engines that are being destroyed + runScriptEndpointCleanup(); + // Run the mappings code runMappings(); @@ -388,17 +397,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 +415,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); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); - qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); + scriptRegisterMetaType(engine); } Input UserInputMapper::makeStandardInput(controller::StandardButtonChannel button) { @@ -658,7 +671,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 +685,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(); @@ -845,6 +858,12 @@ void UserInputMapper::unloadMapping(const QString& jsonFile) { } } +void UserInputMapper::scheduleScriptEndpointCleanup(ScriptEngine* engine) { + _lock.lock(); + scriptEnginesRequestingCleanup.enqueue(engine); + _lock.unlock(); +} + static const QString JSON_NAME = QStringLiteral("name"); static const QString JSON_CHANNELS = QStringLiteral("channels"); static const QString JSON_CHANNEL_FROM = QStringLiteral("from"); @@ -883,7 +902,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; @@ -1239,6 +1258,50 @@ void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) { } } +void UserInputMapper::runScriptEndpointCleanup() { + _lock.lock(); + QList routesToRemove; + while (!scriptEnginesRequestingCleanup.empty()){ + auto engine = scriptEnginesRequestingCleanup.dequeue(); + QList routeLists = {&_deviceRoutes, &_standardRoutes}; + auto iterator = _mappingsByName.begin(); + while (iterator != _mappingsByName.end()) { + if (iterator->second) { + routeLists.append(&iterator->second->routes); + } + iterator++; + } + for (auto routeList: routeLists) { + for (auto route: *routeList) { + auto source = std::dynamic_pointer_cast(route->source); + if (source && source->getEngine() == engine) { + qDebug() << "UserInputMapper::runScriptEndpointCleanup source"; + routesToRemove.append(route); + } + auto destination = std::dynamic_pointer_cast(route->destination); + if (destination && destination->getEngine() == engine) { + qDebug() << "UserInputMapper::runScriptEndpointCleanup destination"; + routesToRemove.append(route); + } + } + } + } + while (!routesToRemove.empty()) { + qDebug() << "UserInputMapper::runScriptEndpointCleanup routesToRemove"; + auto route = routesToRemove.first(); + _deviceRoutes.remove(route); + _standardRoutes.remove(route); + auto iterator = _mappingsByName.begin(); + while (iterator != _mappingsByName.end()) { + iterator->second->routes.remove(route); + iterator++; + } + + routesToRemove.removeAll(route); + } + _lock.unlock(); +} + void UserInputMapper::setActionState(Action action, float value, bool valid) { Locker locker(_lock); _actionStates[toInt(action)] = value; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index ee8b34193fb..401f77a74c7 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -1,9 +1,11 @@ // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -17,8 +19,8 @@ #include #include +#include #include -#include #include #include @@ -32,6 +34,9 @@ #include "Actions.h" #include "StateController.h" +class ScriptEngine; +class ScriptValue; + namespace controller { class RouteBuilderProxy; @@ -64,7 +69,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); @@ -122,6 +127,17 @@ namespace controller { void unloadMappings(const QStringList& jsonFiles); void unloadMapping(const QString& jsonFile); + /** + * @brief Request cleaning up endpoints on script engine shutdown + * + * Script endpoints need to be removed before script engine they belong to gets deleted, because otherwise + * script callback will cause a crash. Script engine invokes this function during shutdown and then waits + * for confirmation before being shut down. + * + * @param engine Pointer to the script engine that will be shut down + */ + void scheduleScriptEndpointCleanup(ScriptEngine* engine); + AxisValue getValue(const Input& input) const; Pose getPose(const Input& input) const; @@ -163,11 +179,21 @@ namespace controller { static bool applyRoute(const RoutePointer& route, bool force = false); void enableMapping(const MappingPointer& mapping); void disableMapping(const MappingPointer& mapping); + + /** + * @brief Clean up endpoints on script engine shutdown + * + * Script endpoints need to be removed before script engine they belong to gets deleted, because otherwise + * script callback will cause a crash. This function is called from UserInputMapper::runMappings. + * + */ + void runScriptEndpointCleanup(); + 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); @@ -196,6 +222,9 @@ namespace controller { InputCalibrationData inputCalibrationData; + // Contains pointers to script engines that are requesting callback cleanup during their shutdown process + QQueue scriptEnginesRequestingCleanup; + mutable std::recursive_mutex _lock; }; diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index 692e427e165..44a8c254ff0 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -20,8 +22,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..84ca5c554da 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -1,15 +1,16 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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..17c2ebed7f7 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MappingBuilderProxy.h" @@ -17,6 +19,7 @@ #include "RouteBuilderProxy.h" #include "../ScriptingInterface.h" #include "../Logging.h" +#include using namespace controller; @@ -26,7 +29,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 +52,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 4c1a6faa512..99004f9f412 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once #ifndef hifi_Controllers_Impl_MappingBuilderProxy_h @@ -16,8 +18,8 @@ #include "Endpoint.h" class QJSValue; -class QScriptValue; class QJsonValue; +class ScriptValue; namespace controller { @@ -165,7 +167,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 +189,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..c9468013f66 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RouteBuilderProxy.h" @@ -17,6 +19,7 @@ #include "MappingBuilderProxy.h" #include "../ScriptingInterface.h" #include "../Logging.h" +#include #include "filters/ClampFilter.h" #include "filters/ConstrainToIntegerFilter.h" @@ -43,7 +46,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 +68,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 88f20d218a7..da3e3ec26be 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -1,10 +1,13 @@ // // Created by Bradley Austin Davis 2015/10/09 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // + #pragma once #ifndef hifi_Controllers_Impl_RouteBuilderProxy_h #define hifi_Controllers_Impl_RouteBuilderProxy_h @@ -18,8 +21,8 @@ #include "../UserInputMapper.h" class QJSValue; -class QScriptValue; class QJsonValue; +class ScriptValue; namespace controller { @@ -115,7 +118,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 +196,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..bfe56f002f1 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/20 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -11,8 +13,7 @@ #define hifi_Controllers_ScriptConditional_h #include - -#include +#include #include "../Conditional.h" @@ -21,12 +22,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..954fc175c6f 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis 2015/10/23 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptEndpoint.h" @@ -11,11 +13,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 +49,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 +77,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 +96,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 +119,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..240da447166 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -1,16 +1,18 @@ // // Created by Bradley Austin Davis 2015/10/23 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once #ifndef hifi_Controllers_ScriptEndpoint_h #define hifi_Controllers_ScriptEndpoint_h -#include +#include #include "../Endpoint.h" @@ -20,7 +22,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) { } @@ -31,6 +33,7 @@ class ScriptEndpoint : public Endpoint { virtual void apply(const Pose& newValue, const Pointer& source) override; virtual bool isPose() const override { return _returnPose; } + virtual const ScriptEngine* getEngine() const { return _callable.engine().get(); } protected: Q_INVOKABLE void updateValue(); @@ -39,7 +42,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..22851d8aa5f 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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 +14,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 949ad85945b..a6d8646af66 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityTreeRenderer.h" @@ -15,7 +17,6 @@ #include #include -#include #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -158,45 +160,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); @@ -205,51 +217,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); } } } @@ -297,15 +309,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) { @@ -343,16 +355,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); } } @@ -376,11 +388,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; @@ -696,14 +708,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"); } } @@ -715,7 +727,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"); } } @@ -733,8 +745,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); @@ -754,7 +766,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"); } @@ -1053,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"); @@ -1103,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()) { @@ -1224,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); } @@ -1236,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 8aae867851f..cdb2253a8d9 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -4,14 +4,18 @@ // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityTreeRenderer_h #define hifi_EntityTreeRenderer_h +#include + #include #include #include @@ -33,6 +37,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; @@ -176,7 +184,7 @@ public slots: void resetPersistentEntitiesScriptEngine(); void resetNonPersistentEntitiesScriptEngine(); - void setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine); + void setupEntityScriptEngineSignals(const ScriptManagerPointer& scriptManager); void findBestZoneAndMaybeContainingEntities(QSet& entitiesContainingAvatar); @@ -188,7 +196,7 @@ public slots: EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; - QScriptValueList createEntityArgs(const EntityItemID& entityID); + ScriptValueList createEntityArgs(const EntityItemID& entityID); void checkEnterLeaveEntities(); void leaveDomainAndNonOwnedEntities(); void leaveAllEntities(); @@ -199,8 +207,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 82ad8081b1c..20638cac296 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ b/libraries/entities-renderer/src/ModelScriptingInterface.cpp @@ -1,29 +1,40 @@ // // ModelScriptingInterface.cpp -// libraries/script-engine/src +// libraries/entities-renderer/src // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ModelScriptingInterface.h" -#include -#include -#include #include -#include "ScriptEngine.h" -#include "ScriptEngineLogging.h" -#include "OBJWriter.h" +#include +#include +#include +#include +#include +#include -ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { - _modelScriptEngine = qobject_cast(parent); +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterSequenceMetaType>(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue>(scriptEngine); +})); + +STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager) { + auto scriptEngine = manager->engine(); - qScriptRegisterSequenceMetaType>(_modelScriptEngine); - qScriptRegisterMetaType(_modelScriptEngine, meshFaceToScriptValue, meshFaceFromScriptValue); - qScriptRegisterMetaType(_modelScriptEngine, qVectorMeshFaceToScriptValue, qVectorMeshFaceFromScriptValue); + scriptEngine->registerGlobalObject("Model", new ModelScriptingInterface(manager)); +}); + +ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) { } QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) { @@ -35,7 +46,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 +151,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 +169,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 +258,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 82% rename from libraries/script-engine/src/ModelScriptingInterface.h rename to libraries/entities-renderer/src/ModelScriptingInterface.h index abf8720a3ee..cf276e130e3 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.h +++ b/libraries/entities-renderer/src/ModelScriptingInterface.h @@ -1,12 +1,14 @@ // // ModelScriptingInterface.h -// libraries/script-engine/src +// libraries/entities-renderer/src // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,10 +17,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 +63,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 +72,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 +82,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 +92,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 +101,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/RenderableGridEntityItem.h b/libraries/entities-renderer/src/RenderableGridEntityItem.h index fd5b3974243..aa4745feb80 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.h +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 11/29/18 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RenderableGridEntityItem_h @@ -37,9 +39,9 @@ class GridEntityRenderer : public TypedEntityRenderer { float _alpha { NAN }; PulsePropertyGroup _pulseProperties; - bool _followCamera; + bool _followCamera { false }; uint32_t _majorGridEvery; - float _minorGridEvery; + float _minorGridEvery { false }; glm::vec3 _dimensions; diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h index cc923daf4b1..add317caf3a 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.h +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 11/29/18 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RenderableImageEntityItem_h @@ -42,8 +44,8 @@ class ImageEntityRenderer : public TypedEntityRenderer { NetworkTexturePointer _texture; bool _textureIsLoaded { false }; - bool _emissive; - bool _keepAspectRatio; + bool _emissive { false }; + bool _keepAspectRatio { false }; QRect _subImage; std::shared_ptr _material { std::make_shared() }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index e5f6cafb82c..8331e016fdc 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -4,10 +4,11 @@ // // Created by Seth Alves on 5/19/15. // Copyright 2015 High Fidelity, Inc. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -22,7 +23,7 @@ #include #include -#include +#include "ModelScriptingInterface.h" #include #include #include diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 0dea2607631..8a18554dea3 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RenderableTextEntityItem_h @@ -57,7 +59,7 @@ class TextEntityRenderer : public TypedEntityRenderer { float _lineHeight; glm::vec3 _textColor; float _textAlpha; - bool _unlit; + bool _unlit { false }; std::shared_ptr _material { std::make_shared() }; glm::vec3 _backgroundColor { NAN }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 14bf5b105d3..81165d140f8 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -2,9 +2,11 @@ // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RenderableWebEntityItem_h @@ -98,9 +100,9 @@ class WebEntityRenderer : public TypedEntityRenderer { uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; - bool _useBackground; + bool _useBackground { false }; QString _userAgent; - WebInputMode _inputMode; + WebInputMode _inputMode { WebInputMode::TOUCH }; glm::vec3 _contextPosition; diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index b6ed62c15a7..e04d9f9fa82 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,5 +1,9 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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 +12,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..829d8ecdf6b 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.cpp +++ b/libraries/entities/src/AmbientLightPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Nissim Hadar on 2017/12/24. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AmbientLightPropertyGroup.h" @@ -19,14 +21,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, const QSet &namesSet, 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..52451a6f8b3 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Nissim Hadar on 2017/12/24. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -17,7 +19,6 @@ #include -#include #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -26,6 +27,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * Ambient light is defined by the following properties: @@ -38,10 +41,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const AmbientLightPropertyGroup& other); diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 0666c7317fd..d15ee3d4cf0 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AnimationPropertyGroup.h" @@ -65,7 +67,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 +80,7 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire } -void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void AnimationPropertyGroup::copyFromScriptValue(const ScriptValue& object, const QSet &namesSet, 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..c980119f8e1 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 2015/9/30. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -17,8 +19,6 @@ #include -#include - #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -27,16 +27,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const AnimationPropertyGroup& other); diff --git a/libraries/entities/src/BloomPropertyGroup.cpp b/libraries/entities/src/BloomPropertyGroup.cpp index 2c4d46ab35f..f785dc74653 100644 --- a/libraries/entities/src/BloomPropertyGroup.cpp +++ b/libraries/entities/src/BloomPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Sam Gondelman on 8/7/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "BloomPropertyGroup.h" @@ -16,13 +18,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, const QSet &namesSet, 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..44b2d18a393 100644 --- a/libraries/entities/src/BloomPropertyGroup.h +++ b/libraries/entities/src/BloomPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Sam Gondelman on 8/7/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_BloomPropertyGroup_h @@ -15,8 +17,6 @@ #include #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -25,6 +25,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 +42,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const BloomPropertyGroup& other); diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 16dace0fc8f..1c5744fddcc 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -4,9 +4,11 @@ // // Created by David Kelly on 2/7/2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityEditFilters.h" @@ -15,6 +17,9 @@ #include #include +#include +#include +#include QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { QList zones; @@ -25,7 +30,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) - EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); if (!zone) { // TODO: maybe remove later? @@ -34,7 +39,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { zones.append(id); } } else { - // the null id is the global filter we put in the domain server's + // the null id is the global filter we put in the domain server's // advanced entity server settings zones.append(id); } @@ -44,7 +49,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, const EntityItemPointer& existingEntity) { - + // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); @@ -52,12 +57,12 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (!itemID.isInvalidID() && id == itemID) { continue; } - - // get the filter pair, etc... + + // get the filter pair, etc... _lock.lockForRead(); FilterData filterData = _filterDataMap.value(id); _lock.unlock(); - + if (filterData.valid()) { if (filterData.rejectAll) { return false; @@ -76,19 +81,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 +103,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 +127,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; @@ -154,45 +159,41 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // otherwise, assume it wants to pass all properties propertiesOut = propertiesIn; wasChanged = false; - + } else { return false; } } } - // if we made it here, + // if we made it here, return true; } void EntityEditFilters::removeFilter(EntityItemID entityID) { QWriteLocker writeLock(&_lock); - FilterData filterData = _filterDataMap.value(entityID); - if (filterData.valid()) { - delete filterData.engine; - } _filterDataMap.remove(entityID); } void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { QUrl scriptURL(filterURL); - - // setting it to an empty string is same as removing + + // setting it to an empty string is same as removing if (filterURL.size() == 0) { removeFilter(entityID); return; } - + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == HIFI_URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; } - + // first remove any existing info for this entity removeFilter(entityID); - + // reject all edits until we load the script FilterData filterData; filterData.rejectAll = true; @@ -200,7 +201,7 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); _lock.unlock(); - + auto scriptRequest = DependencyManager::get()->createResourceRequest( this, scriptURL, true, -1, "EntityEditFilters::addFilter"); if (!scriptRequest) { @@ -216,33 +217,23 @@ 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(); - const auto line = QString::number(engine.uncaughtExceptionLineNumber()); + qCritical() << engine.uncaughtException(); engine.clearExceptions(); - - static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; - auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); - if (!backtrace.empty()) { - static const auto lineSeparator = "\n "; - message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); - } - qCritical() << qPrintable(message); return true; } return false; @@ -255,25 +246,26 @@ 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...) FilterData filterData; filterData.engine = engine; 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 +279,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 +321,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 @@ -375,11 +367,11 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { _lock.unlock(); qDebug() << "script request filter processed for entity id " << entityID; - + emit filterAdded(entityID, true); return; } - } + } } else if (scriptRequest) { const QString urlString = scriptRequest->getUrl().toString(); qCritical() << "Failed to download script"; diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 69fd9209981..1d5eaef388a 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -4,30 +4,34 @@ // // Created by David Kelly on 2/7/2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityEditFilters_h #define hifi_EntityEditFilters_h #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 +45,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 +72,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 72543a99d20..1c96c727756 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -5,10 +5,11 @@ // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityItemProperties.h" @@ -31,6 +32,7 @@ #include #include #include +#include #include "EntitiesLogging.h" #include "EntityItem.h" @@ -1575,40 +1577,41 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.RingGizmo} ring - The ring gizmo properties. */ -QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, - bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const { +ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, + bool strictSemantics, + EntityPseudoPropertyFlags pseudoPropertyFlags) 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); - // Fix to skip the default return all mechanism, when psuedoPropertyFlagsActive - const bool psuedoPropertyFlagsButDesiredEmpty = psuedoPropertyFlagsActive && _desiredProperties.isEmpty(); + const bool pseudoPropertyFlagsActive = pseudoPropertyFlags.test(EntityPseudoPropertyFlag::FlagsActive); + // Fix to skip the default return all mechanism, when pseudoPropertyFlagsActive + const bool pseudoPropertyFlagsButDesiredEmpty = pseudoPropertyFlagsActive && _desiredProperties.isEmpty(); if (_created == UNKNOWN_CREATED_TIME && !allowUnknownCreateTime) { // No entity properties can have been set so return without setting any default, zero property values. return properties; } - if (_idSet && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::ID))) { + if (_idSet && (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::ID))) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(id, _id.toString()); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Type)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::Type)) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(type, EntityTypes::getEntityTypeName(_type)); } if ((!skipDefaults || _lifetime != defaultEntityProperties._lifetime) && !strictSemantics) { - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Age)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::Age)) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::AgeAsText)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::AgeAsText)) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable } } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::LastEdited)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::LastEdited)) { properties.setProperty("lastEdited", convertScriptValue(engine, _lastEdited)); } if (!skipDefaults) { @@ -1766,7 +1769,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot); - if (!psuedoPropertyFlagsButDesiredEmpty) { + if (!pseudoPropertyFlagsButDesiredEmpty) { _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } } @@ -1823,7 +1826,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); - if (!psuedoPropertyFlagsButDesiredEmpty) { + if (!pseudoPropertyFlagsButDesiredEmpty) { _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); @@ -1924,9 +1927,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SUB_IMAGE, subImage); // Handle conversions to old 'textures' property from "imageURL" - if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(PROP_IMAGE_URL)) && + if (((!pseudoPropertyFlagsButDesiredEmpty && _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); } @@ -1958,14 +1961,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool * @property {Vec3} dimensions - The dimensions of the AA box. */ if (!skipDefaults && !strictSemantics && - (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::BoundingBox))) { + (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::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); @@ -1974,15 +1977,15 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool } QString textureNamesStr = QJsonDocument::fromVariant(_textureNames).toJson(); - if (!skipDefaults && !strictSemantics && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::OriginalTextures))) { + if (!skipDefaults && !strictSemantics && (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::OriginalTextures))) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable } // Rendering info if (!skipDefaults && !strictSemantics && - (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::RenderInfo))) { + (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::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 @@ -2007,28 +2010,36 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::ClientOnly)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::ClientOnly)) { properties.setProperty("clientOnly", convertScriptValue(engine, getEntityHostType() == entity::HostType::AVATAR)); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::AvatarEntity)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::AvatarEntity)) { properties.setProperty("avatarEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::AVATAR)); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::LocalEntity)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::LocalEntity)) { properties.setProperty("localEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::LOCAL)); } - if (_type != EntityTypes::PolyLine && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FaceCamera))) { + if (_type != EntityTypes::PolyLine && (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::FaceCamera))) { properties.setProperty("faceCamera", convertScriptValue(engine, getBillboardMode() == BillboardMode::YAW)); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::IsFacingAvatar)) { + if (!pseudoPropertyFlagsActive || pseudoPropertyFlags.test(EntityPseudoPropertyFlag::IsFacingAvatar)) { properties.setProperty("isFacingAvatar", convertScriptValue(engine, getBillboardMode() == BillboardMode::FULL)); } return properties; } -void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool honorReadOnly) { - QScriptValue typeScriptValue = object.property("type"); +void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool honorReadOnly) { + //qDebug() << "EntityItemProperties::copyFromScriptValue: properties: " << object.getPropertyNames(); + QList namesList = object.getPropertyNames(); + + QSet namesSet; + for (auto name = namesList.cbegin(); name != namesList.cend(); name++) { + namesSet.insert(*name); + } + + ScriptValue typeScriptValue = object.property("type"); if (typeScriptValue.isValid()) { setType(typeScriptValue.toVariant().toString()); } @@ -2065,7 +2076,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection); COPY_PROPERTY_FROM_QSCRIPTVALUE(renderWithZones, qVectorQUuid, setRenderWithZones); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); - _grab.copyFromScriptValue(object, _defaultSettings); + _grab.copyFromScriptValue(object, namesSet, _defaultSettings); // Physics COPY_PROPERTY_FROM_QSCRIPTVALUE(density, float, setDensity); @@ -2128,7 +2139,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(color, u8vec3Color, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); - _pulse.copyFromScriptValue(object, _defaultSettings); + _pulse.copyFromScriptValue(object, namesSet, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); // Particles @@ -2175,7 +2186,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled); COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients); COPY_PROPERTY_FROM_QSCRIPTVALUE(useOriginalPivot, bool, setUseOriginalPivot); - _animation.copyFromScriptValue(object, _defaultSettings); + _animation.copyFromScriptValue(object, namesSet, _defaultSettings); // Light COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); @@ -2203,11 +2214,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(alignment, Alignment); // Zone - _keyLight.copyFromScriptValue(object, _defaultSettings); - _ambientLight.copyFromScriptValue(object, _defaultSettings); - _skybox.copyFromScriptValue(object, _defaultSettings); - _haze.copyFromScriptValue(object, _defaultSettings); - _bloom.copyFromScriptValue(object, _defaultSettings); + _keyLight.copyFromScriptValue(object, namesSet, _defaultSettings); + _ambientLight.copyFromScriptValue(object, namesSet, _defaultSettings); + _skybox.copyFromScriptValue(object, namesSet, _defaultSettings); + _haze.copyFromScriptValue(object, namesSet, _defaultSettings); + _bloom.copyFromScriptValue(object, namesSet, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); @@ -2279,11 +2290,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // Gizmo COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(gizmoType, GizmoType); - _ring.copyFromScriptValue(object, _defaultSettings); + _ring.copyFromScriptValue(object, namesSet, _defaultSettings); // Handle conversions from old 'textures' property to "imageURL" - { - QScriptValue V = object.property("textures"); + if (namesSet.contains("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); @@ -2301,8 +2312,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } // Handle old "faceCamera" and "isFacingAvatar" props - if (_type != EntityTypes::PolyLine) { - QScriptValue P = object.property("faceCamera"); + if (_type != EntityTypes::PolyLine && namesSet.contains("textures")) { + ScriptValue P = object.property("faceCamera"); if (P.isValid() && !object.property("billboardMode").isValid()) { bool newValue = P.toVariant().toBool(); bool oldValue = getBillboardMode() == BillboardMode::YAW; @@ -2311,8 +2322,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } } } - { - QScriptValue P = object.property("isFacingAvatar"); + if (namesSet.contains("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; @@ -2325,13 +2336,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); } @@ -2579,37 +2590,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)) { @@ -2626,6 +2639,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue } } } + return true; } static QHash _propertyInfos; @@ -2634,7 +2648,7 @@ static QHash _enumsToPropertyStrings; bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPropertyInfo& propertyInfo) { static std::once_flag initMap; - + // V8TODO: Probably needs mutex before call_once std::call_once(initMap, []() { // Core ADD_PROPERTY_TO_MAP(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); @@ -3018,18 +3032,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 @@ -5192,7 +5207,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 OVERTE_IGNORE_DEPRECATED_BEGIN @@ -5204,17 +5219,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..faf93ccbc27 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -5,9 +5,11 @@ // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityItemProperties_h @@ -21,7 +23,6 @@ #include #include -#include #include #include #include @@ -34,13 +35,14 @@ #include #include #include "FontFamilies.h" +#include -#include "EntityItemID.h" +#include #include "EntityItemPropertiesDefaults.h" #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" #include "EntityPropertyFlags.h" -#include "EntityPsuedoPropertyFlags.h" +#include "EntityPseudoPropertyFlags.h" #include "SimulationOwner.h" #include "TextEntityItem.h" @@ -69,6 +71,8 @@ #include "TextEffect.h" #include "TextAlignment.h" +class ScriptEngine; + const quint64 UNKNOWN_CREATED_TIME = 0; using vec3Color = glm::vec3; @@ -96,7 +100,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 +123,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 +137,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, - bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const; - virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly); - void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString); + virtual ScriptValue copyToScriptValue(ScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, + bool strictSemantics = false, EntityPseudoPropertyFlags pseudoPropertyFlags = EntityPseudoPropertyFlags()) const; + 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 +543,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..d6ee64ba9a0 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -4,17 +4,22 @@ // // Created by Brad Hefta-Gaub on 9/10/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +104,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 +152,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,26 +164,26 @@ 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); \ } #define COPY_PROPERTY_TO_QSCRIPTVALUE(p,P) \ - if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ + if (((!pseudoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ (!skipDefaults || defaultEntityProperties._##P != _##P)) { \ - QScriptValue V = convertScriptValue(engine, _##P); \ - properties.setProperty(#P, V); \ + 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); \ } @@ -186,30 +191,30 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu properties.setProperty(#P, G); #define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, G) \ - if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ + if (((!pseudoPropertyFlagsButDesiredEmpty && _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); \ } // same as COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER but uses #X instead of #P in the setProperty() step #define COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, X, G) \ - if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \ + if (((!pseudoPropertyFlagsButDesiredEmpty && _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 +223,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 +328,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); @@ -339,47 +344,10 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) -#define COPY_PROPERTY_FROM_QSCRIPTVALUE(P, T, S) \ - { \ - QScriptValue V = object.property(#P); \ - if (V.isValid()) { \ - bool isValid = false; \ - T newValue = T##_convertFromScriptValue(V, isValid); \ - if (isValid && (_defaultSettings || newValue != _##P)) { \ - S(newValue); \ - } \ - } \ - } - -#define COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(P, T, S, G) \ -{ \ - QScriptValue V = object.property(#P); \ - if (V.isValid()) { \ - bool isValid = false; \ - T newValue = T##_convertFromScriptValue(V, isValid); \ - if (isValid && (_defaultSettings || newValue != G())) { \ - S(newValue); \ - } \ - } \ -} - -#define COPY_PROPERTY_FROM_QSCRIPTVALUE_NOCHECK(P, T, S) \ -{ \ - QScriptValue V = object.property(#P); \ - if (V.isValid()) { \ - bool isValid = false; \ - T newValue = T##_convertFromScriptValue(V, isValid); \ - if (isValid && (_defaultSettings)) { \ - S(newValue); \ - } \ - } \ -} - -#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(G, P, T, S) \ +#define COPY_PROPERTY_FROM_QSCRIPTVALUE(P, T, S) \ { \ - QScriptValue G = object.property(#G); \ - if (G.isValid()) { \ - QScriptValue V = G.property(#P); \ + if (namesSet.contains(#P)) { \ + ScriptValue V = object.property(#P); \ if (V.isValid()) { \ bool isValid = false; \ T newValue = T##_convertFromScriptValue(V, isValid); \ @@ -390,22 +358,55 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) } \ } -#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ - { \ - QScriptValue P = object.property(#P); \ - if (P.isValid()) { \ - QString newValue = P.toVariant().toString(); \ - if (_defaultSettings || newValue != get##S##AsString()) { \ - set##S##FromString(newValue); \ - } \ - } \ +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(P, T, S, G) \ +{ \ + if (namesSet.contains(#P)) { \ + ScriptValue V = object.property(#P); \ + if (V.isValid()) { \ + bool isValid = false; \ + T newValue = T##_convertFromScriptValue(V, isValid); \ + if (isValid && (_defaultSettings || newValue != G())) { \ + S(newValue); \ + } \ + } \ + } \ +} + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_NOCHECK(P, T, S) \ +{ \ + if (namesSet.contains(#P)) { \ + ScriptValue V = object.property(#P); \ + if (V.isValid()) { \ + bool isValid = false; \ + T newValue = T##_convertFromScriptValue(V, isValid); \ + if (isValid && (_defaultSettings)) { \ + S(newValue); \ + } \ + } \ + } \ +} + +#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(G, P, T, S) \ + { \ + if (namesSet.contains(#G)) { \ + ScriptValue G = object.property(#G); \ + if (G.isValid()) { \ + ScriptValue V = G.property(#P); \ + if (V.isValid()) { \ + bool isValid = false; \ + T newValue = T##_convertFromScriptValue(V, isValid); \ + if (isValid && (_defaultSettings || newValue != _##P)) { \ + S(newValue); \ + } \ + } \ + } \ + } \ } -#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(G, P, S) \ +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ { \ - QScriptValue G = object.property(#G); \ - if (G.isValid()) { \ - QScriptValue P = G.property(#P); \ + if (namesSet.contains(#P)) { \ + ScriptValue P = object.property(#P); \ if (P.isValid()) { \ QString newValue = P.toVariant().toString(); \ if (_defaultSettings || newValue != get##S##AsString()) { \ @@ -415,6 +416,22 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) } \ } +#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(G, P, S) \ + { \ + if (namesSet.contains(#G)) { \ + ScriptValue G = object.property(#G); \ + if (G.isValid()) { \ + ScriptValue P = G.property(#P); \ + if (P.isValid()) { \ + QString newValue = P.toVariant().toString(); \ + if (_defaultSettings || newValue != get##S##AsString()) { \ + set##S##FromString(newValue); \ + } \ + } \ + } \ + } \ + } + #define DEFINE_PROPERTY_GROUP(N, n, T) \ public: \ const T& get##N() const { return _##n; } \ diff --git a/libraries/entities/src/EntityPsuedoPropertyFlags.h b/libraries/entities/src/EntityPseudoPropertyFlags.h similarity index 70% rename from libraries/entities/src/EntityPsuedoPropertyFlags.h rename to libraries/entities/src/EntityPseudoPropertyFlags.h index d6326ee1f5d..2535f5607b3 100644 --- a/libraries/entities/src/EntityPsuedoPropertyFlags.h +++ b/libraries/entities/src/EntityPseudoPropertyFlags.h @@ -1,5 +1,5 @@ // -// EntityPsuedoPropertyFlags.h +// EntityPseudoPropertyFlags.h // libraries/entities/src // // Created by Thijs Wenker on 9/18/18. @@ -11,13 +11,13 @@ #pragma once -#ifndef hifi_EntityPsuedoPropertyFlag_h -#define hifi_EntityPsuedoPropertyFlag_h +#ifndef hifi_EntityPseudoPropertyFlag_h +#define hifi_EntityPseudoPropertyFlag_h #include #include -namespace EntityPsuedoPropertyFlag { +namespace EntityPseudoPropertyFlag { enum { None = 0, FlagsActive, @@ -38,6 +38,6 @@ namespace EntityPsuedoPropertyFlag { NumFlags }; } -typedef std::bitset EntityPsuedoPropertyFlags; +typedef std::bitset EntityPseudoPropertyFlags; -#endif // hifi_EntityPsuedoPropertyFlag_h +#endif // hifi_EntityPseudoPropertyFlag_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 90c521215e9..2cca95b16ff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityScriptingInterface.h" @@ -19,6 +21,7 @@ #include #include #include +#include #include #include @@ -41,10 +44,48 @@ #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 staticEntityScriptTypesInitializer(ScriptManager* manager) { + auto scriptEngine = manager->engine().get(); + + registerMetaTypes(scriptEngine); + + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); +} +STATIC_SCRIPT_TYPES_INITIALIZER(staticEntityScriptTypesInitializer); + +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 + + 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 +105,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); @@ -599,7 +782,7 @@ QUuid EntityScriptingInterface::cloneEntity(const QUuid& entityIDToClone) { } else if (cloneAvatarEntity) { return addEntityInternal(properties, entity::HostType::AVATAR); } else { - // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties + // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties // from the server-created entity, don't change this unless you know what you are doing properties.setLastEdited(0); bool success = addLocalEntityCopy(properties, newEntityID, true); @@ -614,10 +797,38 @@ QUuid EntityScriptingInterface::cloneEntity(const QUuid& entityIDToClone) { EntityItemProperties EntityScriptingInterface::getEntityProperties(const QUuid& entityID) { const EntityPropertyFlags noSpecificProperties; - return getEntityProperties(entityID, noSpecificProperties); + return getEntityPropertiesInternal(entityID, noSpecificProperties); } -EntityItemProperties EntityScriptingInterface::getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties) { +ScriptValue EntityScriptingInterface::getEntityProperties(const QUuid& entityID, const ScriptValue& extendedDesiredProperties) { + EntityPropertyFlags desiredProperties; + EntityItemProperties::entityPropertyFlagsFromScriptValue(extendedDesiredProperties, desiredProperties); + EntityPseudoPropertyFlags desiredPseudoPropertyFlags; + + if (extendedDesiredProperties.isString()) { + readExtendedPropertyStringValue(extendedDesiredProperties, desiredPseudoPropertyFlags); + desiredPseudoPropertyFlags.set(EntityPseudoPropertyFlag::FlagsActive); + } else if (extendedDesiredProperties.isArray()) { + const quint32 length = extendedDesiredProperties.property("length").toInt32(); + for (quint32 i = 0; i < length; i++) { + readExtendedPropertyStringValue(extendedDesiredProperties.property(i), desiredPseudoPropertyFlags); + } + desiredPseudoPropertyFlags.set(EntityPseudoPropertyFlag::FlagsActive); + } + + if (_entityTree) { + if (desiredPseudoPropertyFlags.none() && desiredProperties.isEmpty()) { + desiredPseudoPropertyFlags.set(); + } + } + + EntityItemProperties properties = getEntityPropertiesInternal(entityID, desiredProperties); + + return properties.copyToScriptValue(extendedDesiredProperties.engine().get(), false, false, false, desiredPseudoPropertyFlags); +} + +EntityItemProperties EntityScriptingInterface::getEntityPropertiesInternal(const QUuid& entityID, EntityPropertyFlags desiredProperties) { + PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; @@ -672,62 +883,63 @@ 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) { +void EntityScriptingInterface::readExtendedPropertyStringValue(const ScriptValue& extendedProperty, EntityPseudoPropertyFlags& pseudoPropertyFlags) { + const auto extendedPropertyString = extendedProperty.toString(); + if (extendedPropertyString == "id") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::ID); + } else if (extendedPropertyString == "type") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::Type); + } else if (extendedPropertyString == "age") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::Age); + } else if (extendedPropertyString == "ageAsText") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::AgeAsText); + } else if (extendedPropertyString == "lastEdited") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::LastEdited); + } else if (extendedPropertyString == "boundingBox") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::BoundingBox); + } else if (extendedPropertyString == "originalTextures") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::OriginalTextures); + } else if (extendedPropertyString == "renderInfo") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::RenderInfo); + } else if (extendedPropertyString == "clientOnly") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::ClientOnly); + } else if (extendedPropertyString == "avatarEntity") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::AvatarEntity); + } else if (extendedPropertyString == "localEntity") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::LocalEntity); + } else if (extendedPropertyString == "faceCamera") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::FaceCamera); + } else if (extendedPropertyString == "isFacingAvatar") { + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::IsFacingAvatar); + } +} + +ScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector entityIDs, const ScriptValue& extendedDesiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); - EntityPsuedoPropertyFlags psuedoPropertyFlags; - const auto readExtendedPropertyStringValue = [&](QScriptValue extendedProperty) { - const auto extendedPropertyString = extendedProperty.toString(); - if (extendedPropertyString == "id") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID); - } else if (extendedPropertyString == "type") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Type); - } else if (extendedPropertyString == "age") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Age); - } else if (extendedPropertyString == "ageAsText") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AgeAsText); - } else if (extendedPropertyString == "lastEdited") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LastEdited); - } else if (extendedPropertyString == "boundingBox") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::BoundingBox); - } else if (extendedPropertyString == "originalTextures") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OriginalTextures); - } else if (extendedPropertyString == "renderInfo") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::RenderInfo); - } else if (extendedPropertyString == "clientOnly") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly); - } else if (extendedPropertyString == "avatarEntity") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AvatarEntity); - } else if (extendedPropertyString == "localEntity") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LocalEntity); - } else if (extendedPropertyString == "faceCamera") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FaceCamera); - } else if (extendedPropertyString == "isFacingAvatar") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::IsFacingAvatar); - } - }; + EntityPseudoPropertyFlags pseudoPropertyFlags; if (extendedDesiredProperties.isString()) { - readExtendedPropertyStringValue(extendedDesiredProperties); - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); + readExtendedPropertyStringValue(extendedDesiredProperties, pseudoPropertyFlags); + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::FlagsActive); } else if (extendedDesiredProperties.isArray()) { const quint32 length = extendedDesiredProperties.property("length").toInt32(); for (quint32 i = 0; i < length; i++) { - readExtendedPropertyStringValue(extendedDesiredProperties.property(i)); + readExtendedPropertyStringValue(extendedDesiredProperties.property(i), pseudoPropertyFlags); } - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive); + pseudoPropertyFlags.set(EntityPseudoPropertyFlag::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) || @@ -752,7 +964,7 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri const auto& entityID = entityIDs.at(i); const EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - if (psuedoPropertyFlags.none() && desiredProperties.isEmpty()) { + if (pseudoPropertyFlags.none() && desiredProperties.isEmpty()) { // these are left out of EntityItem::getEntityProperties so that localPosition and localRotation // don't end up in json saves, etc. We still want them here, though. EncodeBitstreamParams params; // unknown @@ -762,7 +974,7 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri desiredProperties.setHasProperty(PROP_LOCAL_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_ANGULAR_VELOCITY); desiredProperties.setHasProperty(PROP_LOCAL_DIMENSIONS); - psuedoPropertyFlags.set(); + pseudoPropertyFlags.set(); needsScriptSemantics = true; } @@ -774,18 +986,18 @@ 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"); foreach(const auto& result, resultProperties) { finalResult.setProperty(i++, convertPropertiesToScriptSemantics(result.properties, result.scalesWithParent) - .copyToScriptValue(engine, false, false, false, psuedoPropertyFlags)); + .copyToScriptValue(engine, false, false, false, pseudoPropertyFlags)); } } else { PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Skip Script Semantics"); foreach(const auto& result, resultProperties) { - finalResult.setProperty(i++, result.properties.copyToScriptValue(engine, false, false, false, psuedoPropertyFlags)); + finalResult.setProperty(i++, result.properties.copyToScriptValue(engine, false, false, false, pseudoPropertyFlags)); } } return finalResult; @@ -1060,23 +1272,23 @@ 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) { PROFILE_RANGE(script_entities, __FUNCTION__); - + 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 +1336,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 +1470,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 +1546,48 @@ 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(); + // It was originally "InternalError", but there's no such error data type in V8 + callScopedHandlerObject(handler, engine->makeError(engine->newValue("Entities Scripting Provider unavailable"), "Error"), 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 +1597,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 +1605,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 +1616,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("callback is not a function", "TypeError"); return false; } @@ -1433,27 +1651,45 @@ 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->maybeEmitUncaughtException(__FUNCTION__); + engine->raiseException("metadata for property " + name + " is not yet queryable"); return false; } } -bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, QScriptValue callback) { +//bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, const ScriptValue& callback) { +bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, 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(); + Q_ASSERT(QThread::currentThread() == engine->thread()); + Q_ASSERT(QThread::currentThread() == engine->manager()->thread()); + if (!manager) { + engine->raiseException("This script does not belong to a ScriptManager"); + return false; + } + + connect(request, &GetScriptStatusRequest::finished, manager, [callback](GetScriptStatusRequest* request) mutable { + auto engine = callback.engine(); + // V8TODO: it seems to sometimes be called on a wrong thread, reading to script engine crashes. I added an assert for now. + // V8TODO: somehow the asserts are not happening here, but destructor still runs on main thread sometimes and deadlocks. + // V8TODO: I think this was fixed? Should be tested + Q_ASSERT(QThread::currentThread() == engine->thread()); + Q_ASSERT(QThread::currentThread() == engine->manager()->thread()); + QString statusString = EntityScriptStatus_::valueToKey(request->getStatus()); + ScriptValueList args { engine->newValue(request->getResponseReceived()), engine->newValue(request->getIsRunning()), engine->newValue(statusString.toLower()), engine->newValue(request->getErrorInfo()) }; + callback.call(ScriptValue(), args); request->deleteLater(); + // This causes ScriptValueProxy to be released, and thus its destructor is called on script engine thread and not main thread + callback = ScriptValue(); }); request->start(); return true; @@ -1483,40 +1719,43 @@ 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)); + Q_ASSERT(value.face < 7); - 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()); + Q_ASSERT(value.face < 7); - 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) { @@ -1805,17 +2044,17 @@ EntityItemPointer EntityScriptingInterface::checkForTreeEntityAndTypeMatch(const if (!_entityTree) { return EntityItemPointer(); } - + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::checkForTreeEntityAndTypeMatch - no entity with ID" << entityID; return entity; } - + if (entityType != EntityTypes::Unknown && entity->getType() != entityType) { return EntityItemPointer(); } - + return entity; } @@ -2237,14 +2476,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 +2492,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..8e50675da59 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -5,9 +5,11 @@ // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // TODO: How will we handle collision callbacks with Entities // @@ -15,18 +17,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 +44,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 +58,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 @@ -81,13 +88,13 @@ class RayToEntityIntersectionResult { QUuid entityID; float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; - glm::vec3 intersection; - glm::vec3 surfaceNormal; + glm::vec3 intersection { glm::vec3(0.0f, 0.0f, 0.0f) }; + glm::vec3 surfaceNormal { glm::vec3(0.0f, 1.0f, 0.0f) }; 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 +189,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 +216,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); @@ -406,7 +413,9 @@ public slots: * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID); - Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); + Q_INVOKABLE ScriptValue getEntityProperties(const QUuid& entityID, const ScriptValue &desiredProperties); + Q_INVOKABLE EntityItemProperties getEntityPropertiesInternal(const QUuid& entityID, EntityPropertyFlags desiwredProperties); + //Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); /*@jsdoc * Edits an entity, changing one or more of its property values. @@ -835,7 +844,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 +873,8 @@ 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); + Q_INVOKABLE bool getServerScriptStatus(const QUuid& entityID, ScriptValue callback); /*@jsdoc * Gets metadata for certain entity properties such as script and serverScripts. @@ -894,8 +904,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 +1918,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 +2539,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); @@ -2548,6 +2564,8 @@ private slots: bool setPoints(QUuid entityID, std::function actor); void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); bool addLocalEntityCopy(EntityItemProperties& propertiesWithSimID, EntityItemID& id, bool isClone = false); + static void readExtendedPropertyStringValue(const ScriptValue& extendedProperty, + EntityPseudoPropertyFlags &pseudoPropertyFlags); EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID, EntityTypes::EntityType entityType = EntityTypes::Unknown); @@ -2564,8 +2582,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..b4767910cf9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -5,9 +5,11 @@ // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EntityTree.h" @@ -23,8 +25,6 @@ #include #include -#include - #include #include #include @@ -2944,8 +2944,9 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer } entityDescription["DataVersion"] = _persistDataVersion; entityDescription["Id"] = _persistID; - QScriptEngine scriptEngine; - RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, + // V8TODO: Creating new script engine each time is very inefficient + ScriptEnginePointer engine = newScriptEngine(); + RecurseOctreeToMapOperator theOperator(entityDescription, element, engine.get(), skipDefaultValues, skipThoseWithBadParents, _myAvatar); withReadLock([&] { recurseTreeWithOperator(&theOperator); @@ -3091,10 +3092,11 @@ 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; + // V8TODO: Creating new script engine each time is very inefficient + ScriptEnginePointer scriptEngine = newScriptEngine(); if (entitiesQList.length() == 0) { // Empty map or invalidly formed file. @@ -3105,7 +3107,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 +3120,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 +3271,9 @@ bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) { } bool EntityTree::writeToJSON(QString& jsonString, const OctreeElementPointer& element) { - QScriptEngine scriptEngine; - RecurseOctreeToJSONOperator theOperator(element, &scriptEngine, jsonString); + // V8TODO: Creating new script engine each time is very inefficient + ScriptEnginePointer engine = newScriptEngine(); + RecurseOctreeToJSONOperator theOperator(element, engine.get(), jsonString); withReadLock([&] { recurseTreeWithOperator(&theOperator); }); diff --git a/libraries/entities/src/GizmoEntityItem.cpp b/libraries/entities/src/GizmoEntityItem.cpp index 44073cb5375..c53f2293fb2 100644 --- a/libraries/entities/src/GizmoEntityItem.cpp +++ b/libraries/entities/src/GizmoEntityItem.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 1/22/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "GizmoEntityItem.h" @@ -21,6 +23,7 @@ EntityItemPointer GizmoEntityItem::factory(const EntityItemID& entityID, const E // our non-pure virtual subclass for now... GizmoEntityItem::GizmoEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Gizmo; + _gizmoType = GizmoType::UNSET_GIZMO_TYPE; } void GizmoEntityItem::setUnscaledDimensions(const glm::vec3& value) { diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index 7a9ba147d9b..f0026d89040 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Seth Alves on 2018-8-8. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "GrabPropertyGroup.h" @@ -16,8 +18,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 +45,7 @@ void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp } -void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { +void GrabPropertyGroup::copyFromScriptValue(const ScriptValue& object, const QSet &namesSet, 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..368867a6d62 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Seth Alves on 2018-8-8. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_GrabPropertyGroup_h @@ -16,8 +18,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -25,6 +25,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 +73,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const GrabPropertyGroup& other); diff --git a/libraries/entities/src/HazePropertyGroup.cpp b/libraries/entities/src/HazePropertyGroup.cpp index 632f73ced6e..7cd41e8d599 100644 --- a/libraries/entities/src/HazePropertyGroup.cpp +++ b/libraries/entities/src/HazePropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Nissim hadar on 9/21/17. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "HazePropertyGroup.h" @@ -16,7 +18,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 +36,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, const QSet &namesSet, 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..2b899871fa1 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Nissim hadar on 9/21/17. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_HazePropertyGroup_h @@ -16,8 +18,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" @@ -26,6 +26,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 +78,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const HazePropertyGroup& other); diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index b70e94504df..74ea8dc520b 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Sam Gateau on 2015/10/23. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "KeyLightPropertyGroup.h" @@ -25,8 +27,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 +38,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, const QSet &namesSet, 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..7d92813a549 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Sam Gateau on 2015/10/23. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -17,7 +19,6 @@ #include -#include #include "EntityItemPropertiesMacros.h" #include "PropertyGroup.h" @@ -26,6 +27,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 +48,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const KeyLightPropertyGroup& other); diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 23f6e36e732..4f3810b9006 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -4,9 +4,11 @@ // // Created by Eric Levin on 8/3/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PolyLineEntityItem_h @@ -103,9 +105,9 @@ class PolyLineEntityItem : public EntityItem { QVector _colors; QVector _widths; QString _textures; - bool _isUVModeStretch; - bool _glow; - bool _faceCamera; + bool _isUVModeStretch { false }; + bool _glow { false }; + bool _faceCamera { false }; bool _pointsChanged { false }; bool _normalsChanged { false }; diff --git a/libraries/entities/src/PropertyGroup.h b/libraries/entities/src/PropertyGroup.h index 8a9d7e9158c..fab78c5ef67 100644 --- a/libraries/entities/src/PropertyGroup.h +++ b/libraries/entities/src/PropertyGroup.h @@ -4,16 +4,16 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PropertyGroup_h #define hifi_PropertyGroup_h -#include - #include #include "EntityPropertyFlags.h" @@ -24,6 +24,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr; @@ -32,8 +34,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, const QSet &namesSet, 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..b760ac3c29b 100644 --- a/libraries/entities/src/PulsePropertyGroup.cpp +++ b/libraries/entities/src/PulsePropertyGroup.cpp @@ -3,9 +3,11 @@ // // Created by Sam Gondelman on 1/15/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "PulsePropertyGroup.h" @@ -57,8 +59,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 +69,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, const QSet &namesSet, 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..e874f114e4e 100644 --- a/libraries/entities/src/PulsePropertyGroup.h +++ b/libraries/entities/src/PulsePropertyGroup.h @@ -3,9 +3,11 @@ // // Created by Sam Gondelman on 1/15/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_PulsePropertyGroup_h @@ -13,8 +15,6 @@ #include -#include - #include #include "PropertyGroup.h" @@ -24,6 +24,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 +42,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const PulsePropertyGroup& other); diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp index b64a700abcc..28288da3fa9 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -4,15 +4,18 @@ // // Created by Simon Walton on Oct 11, 2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +37,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..0bb32bfd452 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.h +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -4,16 +4,22 @@ // // Created by Simon Walton on Oct 11, 2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +29,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..8fd7846c4c9 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -4,18 +4,21 @@ // // Created by Seth Alves on 3/16/15. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +59,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..6d522185925 100644 --- a/libraries/entities/src/RecurseOctreeToMapOperator.h +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -4,23 +4,27 @@ // // Created by Seth Alves on 3/16/15. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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..f8e106c7224 100644 --- a/libraries/entities/src/RingGizmoPropertyGroup.cpp +++ b/libraries/entities/src/RingGizmoPropertyGroup.cpp @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 1/22/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RingGizmoPropertyGroup.h" @@ -20,8 +22,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 +48,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, const QSet &namesSet, 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..7779fea3c12 100644 --- a/libraries/entities/src/RingGizmoPropertyGroup.h +++ b/libraries/entities/src/RingGizmoPropertyGroup.h @@ -1,9 +1,11 @@ // // Created by Sam Gondelman on 1/22/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RingGizmoPropertyGroup_h @@ -11,8 +13,6 @@ #include -#include - #include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" #include "EntityItemPropertiesDefaults.h" @@ -21,6 +21,8 @@ class EntityItemProperties; class EncodeBitstreamParams; class OctreePacketData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; using u8vec3Color = glm::u8vec3; @@ -56,10 +58,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const RingGizmoPropertyGroup& other); diff --git a/libraries/entities/src/SkyboxPropertyGroup.cpp b/libraries/entities/src/SkyboxPropertyGroup.cpp index 89ffa95dbe9..12be5fe4dfb 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.cpp +++ b/libraries/entities/src/SkyboxPropertyGroup.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "SkyboxPropertyGroup.h" @@ -18,12 +20,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, const QSet &namesSet, 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..0cb7d8568b7 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.h +++ b/libraries/entities/src/SkyboxPropertyGroup.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_SkyboxPropertyGroup_h @@ -16,8 +18,6 @@ #include -#include - #include #include "PropertyGroup.h" @@ -28,6 +28,8 @@ class EncodeBitstreamParams; class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +class ScriptEngine; +class ScriptValue; /*@jsdoc * A skybox is defined by the following properties: @@ -39,10 +41,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, const QSet &namesSet, bool& _defaultSettings) override; void merge(const SkyboxPropertyGroup& other); diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index fff7f57bfb8..e39625060a0 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -116,7 +116,7 @@ class TextEntityItem : public EntityItem { float _rightMargin; float _topMargin; float _bottomMargin; - bool _unlit; + bool _unlit { false }; QString _font; TextAlignment _alignment; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index cc689c8998f..d607bbc99d9 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -2,9 +2,11 @@ // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_WebEntityItem_h @@ -92,8 +94,8 @@ class WebEntityItem : public EntityItem { QString _scriptURL; uint8_t _maxFPS; WebInputMode _inputMode; - bool _showKeyboardFocusHighlight; - bool _useBackground; + bool _showKeyboardFocusHighlight { false }; + bool _useBackground { false }; QString _userAgent; bool _localSafeContext { false }; }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 8804b63de9c..847937ba4f0 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -1,3 +1,13 @@ +// +// Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2019-2021 Vircadia contributors +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + #pragma once #include @@ -22,7 +32,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..0dd5b955323 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -3,9 +3,11 @@ // libraries/graphics-scripting/src // // Copyright 2017 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "GraphicsScriptingInterface.h" @@ -17,15 +19,24 @@ #include "ScriptableMeshPart.h" #include #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include -GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), QScriptable() { +STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + GraphicsScriptingInterface::registerMetaTypes(scriptEngine); +}); + + +GraphicsScriptingInterface::GraphicsScriptingInterface(QObject* parent) : QObject(parent), Scriptable() { } void GraphicsScriptingInterface::jsThrowError(const QString& error) { @@ -314,24 +325,26 @@ namespace { } namespace scriptable { - template int registerQPointerMetaType(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType>>(engine); - return qScriptRegisterMetaType>( + template int registerQPointerMetaType(ScriptEngine* engine) { + scriptRegisterSequenceMetaType>>(engine); + return scriptRegisterMetaTypeWithLambdas>( engine, - [](QScriptEngine* engine, const QPointer& object) -> QScriptValue { + [](ScriptEngine* engine, const void* p) -> ScriptValue { + Q_ASSERT(p != NULL); + const QPointer& object = *(reinterpret_cast* >(p)); 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, QVariant &dest) -> 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; + dest.setValue(QPointer(tmp)); + return true; } #if 0 if (auto tmp = static_cast(obj)) { @@ -339,20 +352,21 @@ 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 +483,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 +638,58 @@ 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 scriptRegisterMetaTypeWithLambdas( engine, - [](QScriptEngine* engine, const T& topology) -> QScriptValue { - return instance.value(topology); + [](ScriptEngine* engine, const void* p) -> ScriptValue { + Q_ASSERT(p != NULL); + // V8TODO: I'm not sure if this is safe + const T& topology = *(reinterpret_cast(p)); + return engine->newValue(instance.value(topology)); }, - [](const QScriptValue& value, T& topology) { - topology = instance.key(value.toString()); + [](const ScriptValue& value, QVariant &dest) -> bool { + //Q_ASSERT(p != NULL); + //T& topology = *(reinterpret_cast(p)); + dest.setValue(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 +700,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); + scriptRegisterMetaType(engine); + scriptRegisterMetaType, scriptable::qVectorScriptableMaterialLayerToScriptValue, scriptable::qVectorScriptableMaterialLayerFromScriptValue>(engine); + scriptRegisterMetaType(engine); 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..ba835588bcf 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -3,9 +3,11 @@ // libraries/graphics-scripting/src // // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_GraphicsScriptingInterface_h @@ -14,13 +16,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 +36,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 +151,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..632303ebc7b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -1,17 +1,20 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "GraphicsScriptingUtil.h" -#include - #include #include #include +#include +#include +#include using buffer_helpers::glmVecToVariant; @@ -77,7 +80,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 +88,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 +114,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..5bdcf5b5ded 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.h @@ -1,7 +1,14 @@ +// +// Copyright 2018-2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + #pragma once -#include -#include #include #include #include @@ -9,6 +16,9 @@ #include #include #include +#include + +class ScriptEngine; class Extents; class AABox; @@ -24,15 +34,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..0387b66da42 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.cpp @@ -1,23 +1,25 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptableMesh.h" -#include - #include #include #include -#include #include #include #include #include +#include +#include +#include #include "Forward.h" #include "ScriptableMeshPart.h" @@ -27,7 +29,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 +266,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,12 +280,17 @@ 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()); + // TODO: I think the idea here is that toScriptValue or toValue might throw an exception + // in the script engine, which we're then propagating to the running script. This got broken + // by the scripting engine changes, needs fixing. + + qCCritical(graphics_scripting) << "Uncaught exception, handling broken, FIX ME: " << js->uncaughtException(); + //js->currentContext()->throwValue(js->uncaughtException()); return false; } numProcessed++; @@ -293,16 +300,16 @@ glm::uint32 scriptable::ScriptableMesh::forEachVertex(QScriptValue _callback) { } /*@jsdoc - * Called for each vertex when {@link GraphicsMesh.updateVertexAttributes} is called. The value returned by the script function + * Called for each vertex when {@link GraphicsMesh.updateVertexAttributes} is called. The value returned by the script function * should be the modified attributes to update the vertex with, or false to not update the particular vertex. * @callback GraphicsMesh~updateVertexAttributesCallback * @param {Object} attributes - The attributes of the vertex. * @param {number} index - The vertex index. * @param {object} properties - The properties of the mesh, per {@link GraphicsMesh}. - * @returns {Object|boolean} The attribute values to update the vertex with, or + * @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,14 +323,15 @@ 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()); + qCCritical(graphics_scripting) << "Uncaught exception, handling broken, FIX ME: " << js->uncaughtException(); + //js->currentContext()->throwValue(js->uncaughtException()); return false; } if (result.isBool() && !result.toBool()) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index a6ebbe85363..922e7b79738 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -1,8 +1,10 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -20,12 +22,13 @@ #include #include #include -#include -#include #include "GraphicsScriptingUtil.h" #include +#include + +class ScriptValue; namespace scriptable { /*@jsdoc @@ -65,7 +68,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 +86,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 +227,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 +236,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 +244,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..3410b04f6e0 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.cpp @@ -1,8 +1,10 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptableMeshPart.h" @@ -11,8 +13,7 @@ #include #include -#include -#include +#include #include #include #include @@ -78,12 +79,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..cb06b397107 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -1,13 +1,18 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once #include "ScriptableMesh.h" +#include + +class ScriptValue; namespace scriptable { /*@jsdoc @@ -55,7 +60,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 +83,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 +278,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 +287,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 +295,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..21454dfda07 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -3,14 +3,16 @@ // libraries/graphics-scripting // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptableModel.h" -#include +#include #include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" @@ -262,7 +264,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..0b5eeec9298 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h @@ -1,8 +1,10 @@ // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -10,8 +12,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..8f899fe294c 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -1,5 +1,10 @@ +# Copyright 2013-2018, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..861e2b6b0a7 100644 --- a/libraries/midi/CMakeLists.txt +++ b/libraries/midi/CMakeLists.txt @@ -1,3 +1,12 @@ +# Copyright 2013-2017, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..05940324014 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -5,9 +5,11 @@ // Created by Burt Sloane // Modified by Bruce Brown // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Midi.h" @@ -15,11 +17,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 +135,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..e125109ff78 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -4,18 +4,16 @@ // // Created by Seth Alves on 2017-1-27. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_SimpleMeshProxy_h #define hifi_SimpleMeshProxy_h -#include -#include -#include - #include class SimpleMeshProxy : public MeshProxy { diff --git a/libraries/model-serializers/src/GLTFSerializer.h b/libraries/model-serializers/src/GLTFSerializer.h index 3ce38ead5e4..da5284d0b32 100644 --- a/libraries/model-serializers/src/GLTFSerializer.h +++ b/libraries/model-serializers/src/GLTFSerializer.h @@ -4,9 +4,11 @@ // // Created by Luis Cuenca on 8/30/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_GLTFSerializer_h @@ -422,9 +424,9 @@ struct GLTFMaterial { int emissiveTexture; int normalTexture; int occlusionTexture; - graphics::MaterialKey::OpacityMapMode alphaMode; + graphics::MaterialKey::OpacityMapMode alphaMode { graphics::MaterialKey::OPACITY_MAP_OPAQUE }; double alphaCutoff; - bool doubleSided; + bool doubleSided { false }; GLTFpbrMetallicRoughness pbrMetallicRoughness; QMap defined; void dump() { diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index a8dbc782c8e..bbf78157bfc 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -5,9 +5,11 @@ // Created by Stephen Birarda on 2014-09-10. // Copyright 2014 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AddressManager_h @@ -214,6 +216,7 @@ class AddressManager : public QObject, public Dependency { Suggestions, VisitUserFromPAL }; + Q_ENUM(LookupTrigger) bool isConnected(); QString getProtocol() const; @@ -546,4 +549,6 @@ private slots: QUrl _previousAPILookup; }; +Q_DECLARE_METATYPE(AddressManager::LookupTrigger) + #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 9c0bb846f76..5f87c31b865 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 2015/07/21 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AssetClient.h" @@ -16,7 +18,6 @@ #include #include #include -#include #include #include @@ -79,8 +80,10 @@ void AssetClient::initCaching() { << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; } else { auto cache = qobject_cast(networkAccessManager.cache()); - qInfo() << "ResourceManager disk cache already setup at" << cache->cacheDirectory() - << "(size:" << cache->maximumCacheSize() / BYTES_PER_GIGABYTES << "GB)"; + if (cache) { + qInfo() << "ResourceManager disk cache already setup at" << cache->cacheDirectory() + << "(size:" << cache->maximumCacheSize() / BYTES_PER_GIGABYTES << "GB)"; + } } } @@ -295,7 +298,6 @@ void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) { return; } - if (auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache())) { QMetaObject::invokeMethod(reciever, slot.toStdString().data(), Qt::QueuedConnection, Q_ARG(QString, cache->cacheDirectory()), diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 1dcc5ea2ef1..9096d744bbe 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -3,13 +3,15 @@ // libraries/networking/src // // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // 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..73ee5b6e40e 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -4,9 +4,11 @@ // // Created by Andrzej Kapolka on 2/27/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ResourceCache_h @@ -28,8 +30,6 @@ #include #include -#include - #include #include "ResourceManager.h" @@ -234,7 +234,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 +252,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..3237e712f98 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,3 +1,7 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + set(TARGET_NAME physics) setup_hifi_library() link_hifi_libraries(shared workload entities shaders) @@ -14,5 +18,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..f048c3fd341 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -4,9 +4,11 @@ // // Created by Seth Alves 2015-6-2 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // http://bulletphysics.org/Bullet/BulletFull/classbtDynamicInterface.html @@ -23,7 +25,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..4370cf0d8ba 100644 --- a/libraries/pointers/CMakeLists.txt +++ b/libraries/pointers/CMakeLists.txt @@ -1,5 +1,9 @@ +# Copyright 2013-2017, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..c4fe2b517f0 100644 --- a/libraries/recording/CMakeLists.txt +++ b/libraries/recording/CMakeLists.txt @@ -1,9 +1,13 @@ +# Copyright 2013-2015, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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 82% rename from libraries/script-engine/src/RecordingScriptingInterface.cpp rename to libraries/recording/src/recording/RecordingScriptingInterface.cpp index cbcf94662e2..05cfa8b851e 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/recording/src/recording/RecordingScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015/11/13 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RecordingScriptingInterface.h" @@ -11,7 +13,6 @@ #include #include #include -#include #include #include @@ -21,13 +22,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 +58,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 +87,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 +108,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 +261,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 +271,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 +280,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 +298,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 3189fdbabb6..42dc665706f 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/recording/src/recording/RecordingScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015/11/13 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -18,14 +20,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 +73,7 @@ public slots: * }); * } */ - void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); + void loadRecording(const QString& url, const ScriptValue& callback = ScriptValue()); /*@jsdoc @@ -337,7 +337,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 +370,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..9986f17816a 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -1,12 +1,17 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index ddb243f0649..540810bbc3e 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -4,9 +4,11 @@ // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_AntialiasingEffect_h @@ -109,6 +111,20 @@ class AntialiasingConfig : public render::Job::Config { public: AntialiasingConfig() : render::Job::Config(true) {} + /*@jsdoc + *Antialiasing modes. + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0NONEAntialiasing is disabled.
1TAATemporal Antialiasing.
2FXAAFXAA.
3MODE_COUNTInducates number of antialiasing modes
+ * @typedef {number} AntialiasingMode + */ enum Mode { NONE = 0, TAA, diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index af477a2f09c..17b7df9b233 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -4,9 +4,11 @@ // // Created by Andrzej Kapolka on 10/18/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_Model_h @@ -45,7 +47,6 @@ #define SKIN_DQ class AbstractViewStateInterface; -class QScriptEngine; class ViewFrustum; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index fb213c53b2c..1a31e80d7d3 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -1,3 +1,13 @@ +// +// Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2020-2021 Vircadia contributors +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + #include "Font.h" #include @@ -124,6 +134,7 @@ Font::Pointer Font::load(const QString& family) { void Font::handleFontNetworkReply() { auto requestReply = qobject_cast(sender()); + Q_ASSERT(requestReply != nullptr); if (requestReply->error() == QNetworkReply::NoError) { setLoaded(true); diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 6e0783f0006..8f61af1f958 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -4,9 +4,11 @@ // // Created by Niraj Venkat on 6/29/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_render_DrawStatus_h @@ -57,9 +59,9 @@ namespace render { const gpu::TexturePointer getStatusIconMap() const; protected: - bool _showDisplay; // initialized by Config - bool _showNetwork; // initialized by Config - bool _showFade; // initialized by Config + bool _showDisplay { false }; // initialized by Config + bool _showNetwork { false }; // initialized by Config + bool _showFade { false }; // initialized by Config gpu::Stream::FormatPointer _drawItemFormat; gpu::PipelinePointer _drawItemBoundsPipeline; diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 6def6c185fa..cf404578471 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,18 +1,17 @@ +# Copyright 2013-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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() +target_v8() 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..207d109e794 100644 --- a/libraries/script-engine/src/AbstractScriptingServicesInterface.h +++ b/libraries/script-engine/src/AbstractScriptingServicesInterface.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/16/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,13 +17,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(ScriptManagerPointer& scriptEngine) = 0; }; diff --git a/libraries/script-engine/src/ArrayBufferClass.h b/libraries/script-engine/src/ArrayBufferClass.h deleted file mode 100644 index 6438272f5b7..00000000000 --- a/libraries/script-engine/src/ArrayBufferClass.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ArrayBufferClass.h -// -// -// Created by Clement on 7/3/14. -// Copyright 2014 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 -/// @{ - -#ifndef hifi_ArrayBufferClass_h -#define hifi_ArrayBufferClass_h - -#include -#include -#include -#include -#include -#include -#include -#include - -class ScriptEngine; - -/// Implements the ArrayBuffer scripting class -class ArrayBufferClass : public QObject, public QScriptClass { - Q_OBJECT -public: - ArrayBufferClass(ScriptEngine* scriptEngine); - QScriptValue newInstance(qint32 size); - QScriptValue newInstance(const QByteArray& ba); - - QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) override; - QScriptValue property(const QScriptValue& object, - const QScriptString& name, uint id) override; - QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) override; - - QString name() const override; - QScriptValue prototype() const override; - - -private: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); - - static QScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba); - static void fromScriptValue(const QScriptValue& obj, QByteArray& ba); - - QScriptValue _proto; - QScriptValue _ctor; - - // JS Object attributes - QScriptString _name; - QScriptString _byteLength; - -}; - -#endif // hifi_ArrayBufferClass_h - -/// @} diff --git a/libraries/script-engine/src/ArrayBufferViewClass.h b/libraries/script-engine/src/ArrayBufferViewClass.h deleted file mode 100644 index 173f6073342..00000000000 --- a/libraries/script-engine/src/ArrayBufferViewClass.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// ArrayBufferViewClass.h -// -// -// Created by Clement on 7/8/14. -// Copyright 2014 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 -/// @{ - -#ifndef hifi_ArrayBufferViewClass_h -#define hifi_ArrayBufferViewClass_h - -#include -#include -#include -#include -#include -#include -#include - -#include "ScriptEngine.h" - -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 -class ArrayBufferViewClass : public QObject, public QScriptClass { - Q_OBJECT -public: - ArrayBufferViewClass(ScriptEngine* scriptEngine); - - ScriptEngine* getScriptEngine() { return _scriptEngine; } - - virtual QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) 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; -protected: - // JS Object attributes - QScriptString _bufferName; - QScriptString _byteOffsetName; - QScriptString _byteLengthName; - - ScriptEngine* _scriptEngine; -}; - -#endif // hifi_ArrayBufferViewClass_h - -/// @} diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 8f97f1ce336..dd28cf74eed 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -4,27 +4,31 @@ // // Created by Stephen Birarda on 2016-03-08. // Copyright 2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "AssetScriptingInterface.h" #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 @@ -32,10 +36,15 @@ #include using Promise = MiniPromise::Promise; +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); -AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { + scriptRegisterMetaType, promiseToScriptValue, promiseFromScriptValue>(scriptEngine); +})); + + +AssetScriptingInterface::AssetScriptingInterface(ScriptManager* parent) : BaseAssetScriptingInterface(parent), _scriptManager(parent) { qCDebug(scriptengine) << "AssetScriptingInterface::AssetScriptingInterface" << parent; - MiniPromise::registerMetaTypes(parent); } #define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } } @@ -59,16 +68,18 @@ 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__); + Q_ASSERT(engine); + 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 +94,14 @@ 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__); + Q_ASSERT(engine); + 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 +120,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 +136,11 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb auto assetRequest = assetClient->createRequest(hash); Promise deferred = makePromise(__FUNCTION__); + Q_ASSERT(engine); + 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 +164,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 +194,16 @@ 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); + Q_ASSERT(engine); + 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 +219,51 @@ 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); + Q_ASSERT(engine); + 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; + Q_ASSERT(engine); + //V8TODO: which kind of script context guard needs to be used here? + ScriptContextGuard scriptContextGuard(_scriptManager->engine()->currentContext()); + 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 +293,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 +359,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 +386,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 +427,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 +456,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 +512,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 +527,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 +546,15 @@ void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue s } bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) { - auto scriptEngine = qobject_cast(engine()); - if (!scriptEngine) { + Q_ASSERT(engine); + 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 +570,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 49a619306f3..6e8e00536b4 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2016-03-08. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -19,14 +21,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 @@ -44,12 +46,15 @@ * @hifi-server-entity * @hifi-assignment-client */ + +class ScriptManager; + /// Provides the Assets scripting API -class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { +class AssetScriptingInterface : public BaseAssetScriptingInterface, Scriptable { Q_OBJECT public: using Parent = BaseAssetScriptingInterface; - AssetScriptingInterface(QObject* parent = nullptr); + AssetScriptingInterface(ScriptManager* parent); /*@jsdoc * Called when an {@link Assets.uploadData} call is complete. @@ -76,7 +81,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 +117,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 +131,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 +155,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 +171,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 +227,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 +264,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 +281,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 +315,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 +336,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 +358,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 +403,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 +443,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 +483,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 +522,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,14 +542,15 @@ 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); + ScriptManager *_scriptManager; }; #endif // hifi_AssetScriptingInterface_h diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp index 60de04aa9ea..2d9d5c7ad61 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.cpp +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -4,6 +4,7 @@ // // Created by NeetBhagat on 6/1/17. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // ConsoleScriptingInterface is responsible for following functionality // Printing logs with various tags and grouping on debug Window and Logs/log file. @@ -13,13 +14,17 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ConsoleScriptingInterface.h" #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 +32,73 @@ 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); + Q_ASSERT(engine); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->scriptPrintedMessage(message); } } void ConsoleScriptingInterface::timeEnd(QString labelName) { - if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + Q_ASSERT(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 +108,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 +130,48 @@ 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 + Q_ASSERT(engine); + 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(); + Q_ASSERT(engine); + 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 +182,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 39ccea192be..bfa6d42ecbf 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.h +++ b/libraries/script-engine/src/ConsoleScriptingInterface.h @@ -4,6 +4,7 @@ // // Created by NeetBhagat on 6/1/17. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // ConsoleScriptingInterface is responsible for following functionality // Printing logs with various tags and grouping on debug Window and Logs/log file. @@ -13,6 +14,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -27,7 +29,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 +48,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 +58,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 +76,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 +84,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 +92,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 +100,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 +109,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 +132,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 +160,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 +169,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 +225,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/script-engine/src/DataViewClass.h b/libraries/script-engine/src/DataViewClass.h deleted file mode 100644 index 179aad87d17..00000000000 --- a/libraries/script-engine/src/DataViewClass.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// DataViewClass.h -// -// -// Created by Clement on 7/8/14. -// Copyright 2014 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 -/// @{ - -#ifndef hifi_DataViewClass_h -#define hifi_DataViewClass_h - -#include "ArrayBufferViewClass.h" - -/// Implements the DataView scripting class -class DataViewClass : public ArrayBufferViewClass { - Q_OBJECT -public: - DataViewClass(ScriptEngine* scriptEngine); - QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLength); - - QString name() const override; - QScriptValue prototype() const override; - -private: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); - - QScriptValue _proto; - QScriptValue _ctor; - - QScriptString _name; -}; - - -#endif // hifi_DataViewClass_h - -/// @} diff --git a/libraries/entities/src/EntitiesScriptEngineProvider.h b/libraries/script-engine/src/EntitiesScriptEngineProvider.h similarity index 89% rename from libraries/entities/src/EntitiesScriptEngineProvider.h rename to libraries/script-engine/src/EntitiesScriptEngineProvider.h index 100c17df5f3..7ff92ab4433 100644 --- a/libraries/entities/src/EntitiesScriptEngineProvider.h +++ b/libraries/script-engine/src/EntitiesScriptEngineProvider.h @@ -1,12 +1,14 @@ // // EntitiesScriptEngineProvider.h -// libraries/entities/src +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on Sept. 18, 2015 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // TODO: How will we handle collision callbacks with Entities // diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/script-engine/src/EntityScriptClient.cpp similarity index 95% rename from libraries/networking/src/EntityScriptClient.cpp rename to libraries/script-engine/src/EntityScriptClient.cpp index fb98e8042bb..65b53823fb4 100644 --- a/libraries/networking/src/EntityScriptClient.cpp +++ b/libraries/script-engine/src/EntityScriptClient.cpp @@ -1,6 +1,15 @@ +// +// Copyright 2017-2018 High Fidelity, Inc. +// Copyright 2020-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + #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 91% rename from libraries/networking/src/EntityScriptClient.h rename to libraries/script-engine/src/EntityScriptClient.h index 1fddc6b9767..46b03c6ee26 100644 --- a/libraries/networking/src/EntityScriptClient.h +++ b/libraries/script-engine/src/EntityScriptClient.h @@ -1,12 +1,14 @@ // // EntityScriptClient.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 2017/01/13 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityScriptClient_h @@ -14,10 +16,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 89% rename from libraries/networking/src/EntityScriptUtils.h rename to libraries/script-engine/src/EntityScriptUtils.h index 15b056f0d24..16dfe4ab9e9 100644 --- a/libraries/networking/src/EntityScriptUtils.h +++ b/libraries/script-engine/src/EntityScriptUtils.h @@ -1,12 +1,14 @@ // // EntityScriptUtils.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 2017/01/13 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityScriptUtils_h diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 94c074d44e1..70d0950ebe7 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -4,25 +4,34 @@ // // Created by Brad Hefta-Gaub on 1/28/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "EventTypes.h" #include "KeyEvent.h" #include "MouseEvent.h" -#include "SpatialEvent.h" #include "PointerEvent.h" +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptManager.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); +//STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ +// auto scriptEngine = manager->engine().get(); + +void registerEventTypes(ScriptEngine* scriptEngine){ + scriptRegisterMetaType(scriptEngine, "KeyEvent"); + scriptRegisterMetaType(scriptEngine, "MouseEvent"); + scriptRegisterMetaType(scriptEngine, "PointerEvent"); + scriptRegisterMetaType(scriptEngine, "TouchEvent"); + scriptRegisterMetaType(scriptEngine, "WheelEvent"); + scriptRegisterMetaType(scriptEngine, "SpatialEvent"); } +//})); diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index 052d736d489..d73938674d9 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 1/28/14. // Copyright 2014 High Fidelity, Inc. +// Cyopright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,9 +17,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..d770c9d09f5 100644 --- a/libraries/script-engine/src/KeyEvent.cpp +++ b/libraries/script-engine/src/KeyEvent.cpp @@ -4,17 +4,20 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "KeyEvent.h" #include -#include #include "ScriptEngineLogging.h" +#include "ScriptEngine.h" +#include "ScriptValue.h" KeyEvent::KeyEvent() : key(0), @@ -173,8 +176,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 +189,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 +198,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 +283,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 { @@ -309,5 +313,6 @@ void KeyEvent::fromScriptValue(const QScriptValue& object, KeyEvent& event) { << " event.isKeypad=" << event.isKeypad << " event.isAutoRepeat=" << event.isAutoRepeat; } + return event.isValid; } diff --git a/libraries/script-engine/src/KeyEvent.h b/libraries/script-engine/src/KeyEvent.h index e322aa4a659..8a4b0635972 100644 --- a/libraries/script-engine/src/KeyEvent.h +++ b/libraries/script-engine/src/KeyEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,7 +18,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 +31,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 66% rename from libraries/networking/src/LocationScriptingInterface.cpp rename to libraries/script-engine/src/LocationScriptingInterface.cpp index 39845558a8a..7c68a35c073 100644 --- a/libraries/networking/src/LocationScriptingInterface.cpp +++ b/libraries/script-engine/src/LocationScriptingInterface.cpp @@ -1,33 +1,38 @@ // // LocationScriptingInterface.cpp -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 62% rename from libraries/networking/src/LocationScriptingInterface.h rename to libraries/script-engine/src/LocationScriptingInterface.h index 987c4ccd0d3..d2302b89632 100644 --- a/libraries/networking/src/LocationScriptingInterface.h +++ b/libraries/script-engine/src/LocationScriptingInterface.h @@ -1,26 +1,31 @@ // // LocationScriptingInterface.h -// libraries/networking/src +// libraries/script-engine/src // // Created by Ryan Huffman on 4/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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..559a3485d0b 100644 --- a/libraries/script-engine/src/MIDIEvent.cpp +++ b/libraries/script-engine/src/MIDIEvent.cpp @@ -4,24 +4,35 @@ // // Created by Stephen Birarda on 2014-06-30. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MIDIEvent.h" -void registerMIDIMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, midiEventToScriptValue, midiEventFromScriptValue); -} +#include + +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValue.h" +#include "ScriptManager.h" + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine, "MIDIEvent"); +})); const QString MIDI_DELTA_TIME_PROP_NAME = "deltaTime"; 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 +40,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(); -} \ No newline at end of file + return true; +} diff --git a/libraries/script-engine/src/MIDIEvent.h b/libraries/script-engine/src/MIDIEvent.h index fd8f83d9cba..b2b9aa0e657 100644 --- a/libraries/script-engine/src/MIDIEvent.h +++ b/libraries/script-engine/src/MIDIEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-06-30. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,7 +17,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 +32,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..fefcf6a0f68 100644 --- a/libraries/script-engine/src/Mat4.cpp +++ b/libraries/script-engine/src/Mat4.cpp @@ -4,9 +4,11 @@ // // Created by Anthony Thibault on 3/7/16. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Mat4.h" @@ -20,6 +22,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 +90,8 @@ 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); + Q_ASSERT(engine); + 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 6ee83060674..8c56f513ab8 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -4,11 +4,13 @@ // // Created by Anthony Thibault on 3/7/16. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Scriptable 4x4 Matrix class library. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -19,10 +21,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 +41,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..442c540dd40 100644 --- a/libraries/script-engine/src/MenuItemProperties.cpp +++ b/libraries/script-engine/src/MenuItemProperties.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 1/28/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MenuItemProperties.h" @@ -14,6 +16,15 @@ #include #include +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValue.h" +#include "ScriptManager.h" + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + scriptRegisterMetaType(scriptEngine, "MenuItemProperties"); +})); MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& menuItemName, const QString& shortcutKey, bool checkable, bool checked, bool separator) : @@ -40,12 +51,8 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m { } -void registerMenuItemProperties(QScriptEngine* engine) { - qScriptRegisterMetaType(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 +77,13 @@ 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) { + if (!object.hasProperty("menuName")) { + return false; + } + if (!object.hasProperty("menuItemName")) { + return false; + } properties.menuName = object.property("menuName").toVariant().toString(); properties.menuItemName = object.property("menuItemName").toVariant().toString(); properties.isCheckable = object.property("isCheckable").toVariant().toBool(); @@ -78,12 +91,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 +109,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 c1845ccae9e..0e25500d601 100644 --- a/libraries/script-engine/src/MenuItemProperties.h +++ b/libraries/script-engine/src/MenuItemProperties.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 1/28/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,10 +17,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 +56,8 @@ 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); diff --git a/libraries/script-engine/src/MouseEvent.cpp b/libraries/script-engine/src/MouseEvent.cpp index 6adb39a29bd..20f525e61b6 100644 --- a/libraries/script-engine/src/MouseEvent.cpp +++ b/libraries/script-engine/src/MouseEvent.cpp @@ -4,15 +4,17 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "MouseEvent.h" -#include -#include +#include "ScriptEngine.h" +#include "ScriptValue.h" MouseEvent::MouseEvent() : x(0.0f), @@ -86,8 +88,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 +104,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 05f96812f92..de79be2923a 100644 --- a/libraries/script-engine/src/MouseEvent.h +++ b/libraries/script-engine/src/MouseEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,9 +18,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 +29,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..bc891385995 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/script-engine/src/PointerEvent.cpp @@ -4,17 +4,19 @@ // // Created by Anthony Thibault on 2016-8-11. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +127,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 +148,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 +209,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 +228,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 +236,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 +265,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 85% rename from libraries/shared/src/PointerEvent.h rename to libraries/script-engine/src/PointerEvent.h index 23f435a67c7..0eb9d1c2c8d 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/script-engine/src/PointerEvent.h @@ -4,11 +4,16 @@ // // Created by Anthony Thibault on 2016-8-11. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // +/// @addtogroup ScriptEngine +/// @{ + #ifndef hifi_PointerEvent_h #define hifi_PointerEvent_h @@ -16,8 +21,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 +53,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 +100,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..492534f0215 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 1/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Quat.h" @@ -18,6 +20,7 @@ #include "ScriptEngineLogging.h" #include "ScriptEngine.h" +#include "ScriptManager.h" quat Quat::normalize(const glm::quat& q) { return glm::normalize(q); @@ -123,8 +126,9 @@ 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); + Q_ASSERT(engine); + 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 606a17cf712..75caf0e8af7 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -4,11 +4,13 @@ // // Created by Brad Hefta-Gaub on 1/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Scriptable Quaternion class library. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -21,10 +23,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 +58,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/ResourceScriptingInterface.cpp b/libraries/script-engine/src/ResourceScriptingInterface.cpp new file mode 100644 index 00000000000..c0dac787f99 --- /dev/null +++ b/libraries/script-engine/src/ResourceScriptingInterface.cpp @@ -0,0 +1,25 @@ +// +// ResourceScriptingInterface.cpp +// libraries/script-engine +// +// Created by dr Karol Suprynowicz on 2022/08/18. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include + +#include "ScriptEngineCast.h" +#include "ScriptManager.h" + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "State"); +})); diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 639e413177f..26a0f130a1e 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -4,9 +4,11 @@ // // Created by Sam Gateau on 2/24/15. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,7 +17,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..003e1422785 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 2015-03-30 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptCache.h" @@ -22,6 +24,7 @@ #include #include +#include #include #include "ScriptEngines.h" @@ -133,6 +136,7 @@ void ScriptCache::scriptContentAvailable(int maxRetries) { qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif ResourceRequest* req = qobject_cast(sender()); + Q_ASSERT(req != nullptr); QUrl url = req->getUrl(); QString scriptContent; diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h index 03d815e4ada..2fb9f91046d 100644 --- a/libraries/script-engine/src/ScriptCache.h +++ b/libraries/script-engine/src/ScriptCache.h @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 2015-03-30 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,7 +18,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..8ecca5f120b --- /dev/null +++ b/libraries/script-engine/src/ScriptContext.cpp @@ -0,0 +1,25 @@ +// +// ScriptContext.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 12/5/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#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..7bc70e1080a --- /dev/null +++ b/libraries/script-engine/src/ScriptContext.h @@ -0,0 +1,83 @@ +// +// ScriptContext.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/1/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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 5ac80d45162..dc3ee905c63 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -4,2841 +4,66 @@ // // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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()); -} +#include "ScriptValue.h" +#include "v8/ScriptEngineV8.h" -void ScriptEngine::scriptPrintedMessage(const QString& message) { - qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); - emit printedMessage(message, getFilename()); +ScriptEnginePointer newScriptEngine(ScriptManager* manager) { + return std::make_shared(manager); } -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; - })); -} - -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)); - } -} - -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(). + auto handler = engine->newObject(); + handler.setProperty("scope", scope); + handler.setProperty("callback", callback); + return handler; } -// 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); +ScriptValue callScopedHandlerObject(const ScriptValue& handler, const ScriptValue& err, const ScriptValue& result) { + return handler.property("callback").call(handler.property("scope"), ScriptValueList({ err, result })); } -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; -} - -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 = QVariant::fromValue(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_OVERTE + "://" + 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 45c71b7e90a..886e0b66b18 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -5,9 +5,11 @@ // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,1006 +18,464 @@ #ifndef hifi_ScriptEngine_h #define hifi_ScriptEngine_h -#include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#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" - -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 ScriptEngines; - -Q_DECLARE_METATYPE(ScriptEnginePointer) - -class CallbackData { -public: - QScriptValue function; - EntityItemID definingEntityIdentifier; - QUrl definingSandboxURL; -}; -class DeferredLoadEntity { -public: - EntityItemID entityID; - QString entityScript; - //bool forceRedownload; -}; +#include "ScriptValue.h" +#include "ScriptException.h" -struct EntityScriptContentAvailable { - EntityItemID entityID; - QString scriptOrURL; - QString contents; - bool isURL; - bool success; - QString status; -}; +// These are used for debugging memory leaks caused by persistent handles +//#define OVERTE_V8_MEMORY_DEBUG -typedef std::unordered_map EntityScriptContentAvailableMap; +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; -typedef QList CallbackList; -typedef QHash RegisteredEventHandlers; +Q_DECLARE_METATYPE(ScriptEnginePointer); -class EntityScriptDetails { -public: - EntityScriptStatus status { EntityScriptStatus::PENDING }; +template +inline ScriptValue + +scriptValueFromValue(ScriptEngine* engine, const T& t); - // If status indicates an error, this contains a human-readable string giving more information about the error. - QString errorInfo { "" }; +template +inline T scriptvalue_cast(const ScriptValue& value); - QString scriptText { "" }; - QScriptValue scriptObject { QScriptValue() }; - int64_t lastModified { 0 }; - QUrl definingSandboxURL { QUrl("about:EntityScript") }; +class ScriptEngineMemoryStatistics { +public: + size_t totalHeapSize; + size_t usedHeapSize; + size_t totalAvailableSize; + size_t totalGlobalHandlesSize; + size_t usedGlobalHandlesSize; +#ifdef OVERTE_V8_MEMORY_DEBUG + size_t scriptValueCount; + size_t scriptValueProxyCount; + size_t qObjectCount; + //size_t qVariantProxyCount; +#endif }; -/*@jsdoc - * The Script API provides facilities for working with scripts. +/** + * @brief Provides an engine-independent interface for a scripting engine + * + * Each script engine is strictly single threaded. + * + * This class only provides an interface to the underlying scripting engine, and doesn't + * provide the full environment needed to execute scripts. + * + * To execute scripts that have access to the API, use ScriptManager. * - * @namespace Script + * Exception handling + * ================== * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - * @hifi-server-entity - * @hifi-assignment-client + * Exceptions are handled in two directions: exceptions thrown by the code executing in the scripting + * engine, but not captured by the running code are caught by this object and can be inspected. * - * @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. + * If an exception in the running code occurs, then the exception() signal is emitted. Also, + * hasUncaughtException() returns true, and uncaughException() returns the ScriptException with the + * details. Both the signal and uncaughtException() return the same information, and either can + * be used depending on what best fits the program. + * + * To inject an exception into the running script, use raiseException(). This may result in the script + * not capturing it and an uncaughtException happening as a result. */ -/// The main class managing a scripting engine. Also provides the Script scripting interface -class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { +class ScriptEngine : public QObject { Q_OBJECT - Q_PROPERTY(QString context READ getContext) - Q_PROPERTY(QString type READ getTypeAsString) - Q_PROPERTY(QString fileName MEMBER _fileNameString CONSTANT) public: - enum Context { - CLIENT_SCRIPT, - ENTITY_CLIENT_SCRIPT, - ENTITY_SERVER_SCRIPT, - AGENT_SCRIPT - }; + ScriptEngine(ScriptManager *manager = nullptr) : _manager(manager) { - enum Type { - CLIENT, - ENTITY_CLIENT, - ENTITY_SERVER, - AGENT, - AVATAR - }; - 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); - - /*@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; } + typedef ScriptValue (*FunctionSignature)(ScriptContext*, ScriptEngine*); + typedef ScriptValue (*MarshalFunction)(ScriptEngine*, const void*); + typedef bool (*DemarshalFunction)(const ScriptValue&, QVariant &dest); - /*@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. + /** + * @brief Who owns a given object + * */ - Q_INVOKABLE bool isDebugMode() const; + enum ValueOwnership { + /** + * @brief Object is managed by Qt + * + */ + QtOwnership = 0, + + /** + * @brief Object is managed by the script + * + */ + ScriptOwnership = 1, + + /** + * @brief Ownership is determined automatically. + * If the object has a parent, it's deemed QtOwnership. + * If the object has no parent, it's deemed ScriptOwnership. + * + */ + AutoOwnership = 2, + }; - /*@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. + /** + * @brief Which part of an object is exposed to the script + * */ - Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } + enum QObjectWrapOption { + + /** + * @brief The script object will not expose child objects as properties. + * + */ + //ExcludeChildObjects = 0x0001, + + /** + * @brief The script object will not expose signals and slots inherited from the superclass. + * + */ + ExcludeSuperClassMethods = 0x0002, + + /** + * @brief The script object will not expose properties inherited from the superclass. + * + */ + ExcludeSuperClassProperties = 0x0004, + + /** + * @brief The script object will not expose the QObject::deleteLater() slot. + * + */ + ExcludeSuperClassContents = ExcludeSuperClassMethods | ExcludeSuperClassProperties, + + //ExcludeDeleteLater = 0x0010, + + /** + * @brief The script object will not expose the QObject's slots. + * + */ + ExcludeSlots = 0x0020, + + /** + * @brief 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. + * + */ + AutoCreateDynamicProperties = 0x0100, + + /** + * @brief If a wrapper object with the requested configuration already exists, return that object. + * + */ + PreferExistingWrapperObject = 0x0200, + + /** + * @brief Don't include methods (signals and slots) when enumerating the object's properties. + * + */ + SkipMethodsInEnumeration = 0x0008, + }; + Q_DECLARE_FLAGS(QObjectWrapOptions, QObjectWrapOption); - /*@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. +public: + /** + * @brief Stops the currently running script + * */ - Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } + virtual void abortEvaluation() = 0; - /*@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.overte.org/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. + /** + * @brief Clears uncaughtException and related + * */ - Q_INVOKABLE QScriptValue require(const QString& moduleId); + virtual void clearExceptions() = 0; - /*@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); + /** + * @brief Context of the currently running script + * + * This allows getting a backtrace, the local variables of the currently running function, etc. + * + * @return ScriptContext* */ - Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + virtual ScriptContext* currentContext() const = 0; - /*@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")); + /** + * @brief Runs a script + * + * This may be called several times during the lifetime of a scripting engine, with the + * side effects accumulating. + * + * @param program Code to run + * @param fileName Name of the script, for informational purposes + * @return ScriptValue Return value of the script when it finishes running. */ - Q_INVOKABLE QUrl resolvePath(const QString& path) const; + virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) = 0; - /*@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. + /** + * @brief Evaluates a pre-compiled program + * + * @param program Program to evaluaate + * @return ScriptValue */ - Q_INVOKABLE QUrl resourcesPath() const; + virtual ScriptValue evaluate(const ScriptProgramPointer &program) = 0; - /*@jsdoc - * Starts timing a section of code in order to send usage data about it to Overte. 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 Overte. Shouldn't be used outside of - * the standard scripts. - * @function Script.endProfileRange - * @param {string} label - A name that identifies the section of code. + /** + * @brief Evaluate a script in a separate environment + * + * Used for evaluating included scripts + * + * @param locals Local variables available to the script + * @param program Code to run + * @return ScriptValue */ - Q_INVOKABLE void endProfileRange(const QString& label) const; + virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) = 0; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // 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. + /** + * @brief Global object which holds all the functions and variables available everywhere + * + * This is a JavaScript concept, https://javascript.info/global-object + * + * @note This may not belong in the base class. + * @return ScriptValue Global Object */ - Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { - QReadLocker locker { &_entityScriptsLock }; - auto it = _entityScripts.constFind(entityID); - return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; + virtual ScriptValue globalObject() { + Q_ASSERT(false); + return ScriptValue(); } - 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. + /** + * @brief Whether the script has an uncaught exception + * + * @return true There is an uncaught exception + * @return false There's no exception */ - Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method + virtual bool hasUncaughtException() const = 0; - /*@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. + /** + * @brief Whether a script is currently being evaluated + * + * @return true A script is currently being evaluated + * @return false No script is being evaluated */ - Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + virtual bool isEvaluating() const = 0; + //virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) = 0; - /*@jsdoc - * Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer - * reachable. - * @function Script.requestGarbageCollection + /** + * @brief Check a program for syntax errors + * + * Returns an object with at least the following properties: + * * fileName + * * lineNumber + * * stack + * * formatted + * + * @param program Program to check + * @return ScriptValue Result + * + * @note It could be a good improvement to redo this to return a struct instead. */ - Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0; - /*@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 Overte 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. + /** + * @brief Pointer to the ScriptManager that controls this scripting engine + * + * @return ScriptManager* ScriptManager */ - void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + ScriptManager* manager() const { return _manager; } - /*@jsdoc - * @function Script.updateMemoryCost - * @param {number} deltaSize - Delta size. - * @deprecated This function is deprecated and will be removed. + virtual ScriptValue newArray(uint length = 0) = 0; + virtual ScriptValue newArrayBuffer(const QByteArray& message) = 0; + virtual ScriptValue newFunction(FunctionSignature fun, int length = 0) { + Q_ASSERT(false); + return ScriptValue(); + } + 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; + + + /** + * @brief Make a ScriptValue that contains an error + * + * This is used to throw an error inside the running script + * + * @param other + * @param type + * @return ScriptValue ScriptValue containing error */ - void updateMemoryCost(const qint64&); + virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") = 0; -signals: - /*@jsdoc - * @function Script.scriptLoaded - * @param {string} filename - File name. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. + /** + * @brief Causes an exception to be raised in the currently executing script + * + * @param exception Exception to be thrown in the script + * @param reason Explanatory text about why the exception happened, for logging + * @return true Exception was successfully thrown + * @return false Exception couldn't be thrown because no script is running */ - void scriptLoaded(const QString& scriptFilename); + virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) = 0; - /*@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. + /** + * @brief Causes an exception to be raised in the currently executing script + * + * @param error Exception to be thrown in the script + * @param reason Explanatory text about why the exception happened, for logging + * @return true Exception was successfully thrown + * @return false Exception couldn't be thrown because no script is running + */ + virtual bool raiseException(const QString& error, const QString &reason = QString()) = 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, ScriptEngine::ValueOwnership = ScriptEngine::QtOwnership) = 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; + //Q_INVOKABLE virtual void enterIsolateOnThisThread() = 0; + virtual ScriptValue undefinedValue() = 0; + + /** + * @brief Last uncaught exception, if any. + * + * The returned shared pointer is newly allocated by the function, + * and modifying it has no effect on the internal state of the ScriptEngine. + * + * @return std::shared_ptr Uncaught exception from the script */ - void finished(const QString& fileNameString, ScriptEnginePointer); + virtual std::shared_ptr uncaughtException() const = 0; - /*@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); + virtual void updateMemoryCost(const qint64& deltaSize) = 0; + virtual void requestCollectGarbage() = 0; - /*@jsdoc - * Triggered when the running state of the script changes, e.g., from running to stopping. - * @function Script.runningStateChanged - * @returns {Signal} + /** + * @brief Test the underlying scripting engine + * + * This compiles, executes and verifies the execution of a trivial test program + * to make sure the underlying scripting engine actually works. + * + * @deprecated Test function, not part of the API, can be removed */ - void runningStateChanged(); + virtual void compileTest() = 0; + virtual QString scriptValueDebugDetails(const ScriptValue &value) = 0; + virtual QString scriptValueDebugListMembers(const ScriptValue &value) = 0; - /*@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. + /** + * @brief Log the current backtrace + * + * Logs the current backtrace for debugging + * + * @param title Informative title for the backtrace */ - void reloadScript(const QString& scriptName, bool isUserLoaded); + virtual void logBacktrace(const QString &title) = 0; - /*@jsdoc - * Triggered when the script has stopped. - * @function Script.doneRunning - * @returns {Signal} + /** + * @brief Return memory usage statistics data. + * + * Returns memory usage statistics data for debugging. + * + * @return ScriptEngineMemoryStatistics Object containing memory usage statistics data. */ - void doneRunning(); + virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() = 0; - /*@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. - * }); + /** + * @brief Start collecting object statistics that can later be reported with dumpHeapObjectStatistics(). */ - // Emitted when an entity script has finished running preload - void entityScriptPreloadFinished(const EntityItemID& entityID); + virtual void startCollectingObjectStatistics() = 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. + /** + * @brief Prints heap statistics to a file. Collecting needs to first be started with dumpHeapObjectStatistics(). */ - 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; + virtual void dumpHeapObjectStatistics() = 0; - 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; +public: + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + bool IS_THREADSAFE_INVOCATION(const QString& method); - std::function _emitScriptUpdates{ []() { return true; } }; +public: + template + inline T fromScriptValue(const ScriptValue& value) { + return scriptvalue_cast(value); + } - std::recursive_mutex _lock; + template + inline ScriptValue toScriptValue(const T& value) { + return scriptValueFromValue(this, value); + } - std::chrono::microseconds _totalTimerExecution { 0 }; +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; + virtual QStringList getCurrentScriptURLs() const = 0; + virtual void perManagerLoopIterationCleanup() = 0; - static const QString _SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT; - static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; +signals: + /** + * @brief The script being run threw an exception + * + * @param exception Exception that was thrown + */ + void exception(std::shared_ptr exception); - Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; +protected: + virtual ~ScriptEngine() {} // prevent explicit deletion of base class - QWeakPointer _scriptEngines; + ScriptManager *_manager; }; +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..29746b84b78 --- /dev/null +++ b/libraries/script-engine/src/ScriptEngineCast.h @@ -0,0 +1,185 @@ +// +// ScriptEngineCast.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/9/2021. +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptEngineCast_h +#define hifi_ScriptEngineCast_h + +// Object conversion helpers (copied from QScriptEngine) + +#include +#include +#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()); +} + +// V8TODO: run through debugger for AnimationPointer/AnimationObject +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 +ScriptValue toScriptValueWrapper(ScriptEngine* engine, const void *p) { + Q_ASSERT(p != NULL); + auto &src = *(reinterpret_cast(p)); + return f(engine, src); +} + +template +bool fromScriptValueWrapper(const ScriptValue& val, QVariant &destV) { + //auto &dest = *(reinterpret_cast(p)); + T dest; + bool result = f(val, dest); + destV.setValue(dest); + return result; +} + +template +int scriptRegisterMetaType(ScriptEngine* eng, const char* name = "", + T* = 0) +{ + int id; + if (strlen(name) > 0) { // make sure it's registered + id = qRegisterMetaType(name); + } else { + id = qRegisterMetaType(); + } + eng->registerCustomType(id, toScriptValueWrapper, + fromScriptValueWrapper); + return id; +} + +// This can be safely removed and replaced with scriptRegisterMetaType once we use C++20 everywhere +template +int scriptRegisterMetaTypeWithLambdas(ScriptEngine* eng, + ScriptValue (*toScriptValue)(ScriptEngine*, const void *), + bool (*fromScriptValue)(const ScriptValue&, QVariant &dest), const char* name = "", + T* = 0) +{ + int id; + if (strlen(name) > 0) { // make sure it's registered + id = qRegisterMetaType(name); + } else { + id = qRegisterMetaType(); + } + eng->registerCustomType(id, toScriptValue, + 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 +ScriptValue scriptValueFromEnumClass(ScriptEngine* eng, const T& enumValue) { + ScriptValue a = eng->newValue(static_cast(enumValue)); + return a; +} + +template +bool scriptValueToEnumClass(const ScriptValue& value, T& enumValue) { + if(!value.isNumber()){ + qCDebug(scriptengine) << "ScriptValue \"" << value.toQObject()->metaObject()->className() << "\" is not a number"; + return false; + } + QMetaEnum metaEnum = QMetaEnum::fromType(); + if (!metaEnum.isValid()) { + qCDebug(scriptengine) << "Invalid QMetaEnum"; + return false; + } + bool isValid = false; + int enumInteger = static_cast(value.toInteger()); + for(int i = 0; i < metaEnum.keyCount(); i++){ + if (metaEnum.value(i) == enumInteger) { + isValid = true; + break; + } + } + if (isValid) { + enumValue = static_cast(enumInteger); + return true; + } else { + qCDebug(scriptengine) << "ScriptValue has invalid value " << value.toInteger() << " for enum" << metaEnum.name(); + return false; + } +} + +template +int scriptRegisterSequenceMetaType(ScriptEngine* engine, + T* = 0) { + return scriptRegisterMetaType(engine); +} + +#endif // hifi_ScriptEngineCast_h + +/// @} diff --git a/libraries/script-engine/src/ScriptEngineLogging.cpp b/libraries/script-engine/src/ScriptEngineLogging.cpp index b51d7c3780f..c9484dcf7a8 100644 --- a/libraries/script-engine/src/ScriptEngineLogging.cpp +++ b/libraries/script-engine/src/ScriptEngineLogging.cpp @@ -11,6 +11,7 @@ #include "ScriptEngineLogging.h" -Q_LOGGING_CATEGORY(scriptengine, "hifi.scriptengine") -Q_LOGGING_CATEGORY(scriptengine_module, "hifi.scriptengine.module") -Q_LOGGING_CATEGORY(scriptengine_script, "hifi.scriptengine.script") +Q_LOGGING_CATEGORY(scriptengine, "overte.scriptengine") +Q_LOGGING_CATEGORY(scriptengine_module, "overte.scriptengine.module") +Q_LOGGING_CATEGORY(scriptengine_script, "overte.scriptengine.script") + diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 0e62297905f..400a2fc4159 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2016/01/08 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V: // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptEngines.h" @@ -12,6 +14,7 @@ #include #include +#include #include #include @@ -19,6 +22,7 @@ #include #include +#include "ScriptCache.h" #include "ScriptEngine.h" #include "ScriptEngineLogging.h" @@ -65,7 +69,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 +142,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 @@ -356,7 +360,7 @@ void ScriptEngines::loadScripts() { void ScriptEngines::saveScripts() { // Do not save anything if we are in the process of shutting down if (qApp->closingDown()) { - qWarning() << "Trying to save scripts during shutdown."; + qCWarning(scriptengine) << "Trying to save scripts during shutdown."; return; } @@ -372,8 +376,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 +394,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 +404,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 +450,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 +484,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 +512,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 +556,16 @@ 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(); + Q_ASSERT(scriptEngine != nullptr); 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 +577,43 @@ 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); + manager->waitTillDoneRunning(); + 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 e8366a80170..9fb0c782821 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2016/01/08 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -24,8 +26,8 @@ #include #include #include +#include "ScriptManager.h" -#include "ScriptEngine.h" #include "ScriptsModel.h" #include "ScriptsModelFilter.h" #include "ScriptGatekeeper.h" @@ -54,7 +56,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 +64,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 +77,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 +113,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 +182,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 +328,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/ScriptException.h b/libraries/script-engine/src/ScriptException.h new file mode 100644 index 00000000000..f533d7a4f0e --- /dev/null +++ b/libraries/script-engine/src/ScriptException.h @@ -0,0 +1,186 @@ +// +// ScriptException.h +// libraries/script-engine/src +// +// Created by Dale Glass on 27/02/2023. +// Copyright 2023 Overte e.V. +// +// 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 + +#include "ScriptValue.h" + +#pragma once + +/** + * @brief Scripting exception + * + * Emitted from the scripting engine when an exception happens inside it. + * This is the base class. + * + */ +class ScriptException { + public: + + ScriptException(QString message = "", QString info = "", int line = 0, int column = 0, QStringList backtraceList = QStringList()) : + errorMessage(message), additionalInfo(info), errorLine(line), errorColumn(column), backtrace(backtraceList) { + + } + + /** + * @brief Error message + * + */ + QString errorMessage; + + /** + * @brief Additional information about the exception + * + * This is additional information added at the place where the exception happened. + * It may contain information about what the system was doing when the exception happened. + * + */ + QString additionalInfo; + + + /** + * @brief Error line + * + */ + int errorLine; + + /** + * @brief Error column + * + */ + int errorColumn; + + /** + * @brief Backtrace + * + */ + QStringList backtrace; + + bool isEmpty() const { return errorMessage.isEmpty(); } + + /** + * @brief Clones this object. + * + * This is used in the scripting engine to ensure that while it can return different + * exception objects depending on what happened, the returned object is a copy that + * doesn't allow the caller to accidentally break the ScriptEngine's internal state. + * + * @return std::shared_ptr + */ + virtual std::shared_ptr clone() const { + return std::make_shared(*this); + } +}; + + +/** + * @brief Exception that ocurred inside the scripting engine on the c++ side + * + * This is something that went wrong inside the ScriptEngine or ScriptManager + * infrastructure. + * + */ +class ScriptEngineException : public ScriptException { + public: + + ScriptEngineException(QString message = "", QString info = "", int line = 0, int column = 0, QStringList backtraceList = QStringList()) : + ScriptException(message, info, line, column, backtraceList) { + + } + + + /** + * @brief Clones this object. + * + * This is used in the scripting engine to ensure that while it can return different + * exception objects depending on what happened, the returned object is a copy that + * doesn't allow the caller to accidentally break the ScriptEngine's internal state. + * + * @return std::shared_ptr + */ + virtual std::shared_ptr clone() const override { + return std::make_shared(*this); + } +}; + + + +/** + * @brief Exception that ocurred inside the running script + * + * This is something that went wrong inside the running script. + * + */ +class ScriptRuntimeException : public ScriptException { + public: + + ScriptRuntimeException(QString message = "", QString info = "", int line = 0, int column = 0, QStringList backtraceList = QStringList()) : + ScriptException(message, info, line, column, backtraceList) { + + } + + + /** + * @brief The actual value that was thrown by the script. + * + * The content is completely arbitrary. + * + */ + ScriptValue thrownValue; + + /** + * @brief Clones this object. + * + * This is used in the scripting engine to ensure that while it can return different + * exception objects depending on what happened, the returned object is a copy that + * doesn't allow the caller to accidentally break the ScriptEngine's internal state. + * + * @return std::shared_ptr + */ + virtual std::shared_ptr clone() const override { + return std::make_shared(*this); + } +}; + +inline QDebug operator<<(QDebug debug, const ScriptException& e) { + debug << "Exception:" + << e.errorMessage + << (e.additionalInfo.isEmpty() ? QString("") : "[" + e.additionalInfo + "]") + << " at line " << e.errorLine << ", column " << e.errorColumn; + + if (e.backtrace.length()) { + debug << "Backtrace:"; + debug << e.backtrace; + } + + return debug; +} + +// Is this a bad practice? +inline QDebug operator<<(QDebug debug, std::shared_ptr e) { + if (!e) { + debug << "[Null ScriptException]"; + return debug; + } + + debug << *e.get(); + return debug; +} + +Q_DECLARE_METATYPE(ScriptException) +Q_DECLARE_METATYPE(ScriptEngineException) +Q_DECLARE_METATYPE(ScriptRuntimeException) + +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) + diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp new file mode 100644 index 00000000000..3ce1877de60 --- /dev/null +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -0,0 +1,2648 @@ +// +// ScriptManager.cpp +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#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 "ScriptProgram.h" +#include "ScriptValueIterator.h" +#include "ScriptValueUtils.h" +#include "ScriptManagerScriptingInterface.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; +static ScriptManager::StaticTypesInitializerNode* rootTypesInitializer = nullptr; + + +using ScriptableResourceRawPtr = ScriptableResource*; +ScriptValue externalResourceBucketToScriptValue(ScriptEngine* engine, ExternalResource::Bucket const& in); +bool externalResourceBucketFromScriptValue(const ScriptValue& object, ExternalResource::Bucket& out); +static ScriptValue scriptableResourceToScriptValue(ScriptEngine* engine, + const ScriptableResourceRawPtr& resource); +static bool scriptableResourceFromScriptValue(const ScriptValue& value, ScriptableResourceRawPtr& resource); +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + registerEventTypes(scriptEngine); + scriptRegisterMetaType(scriptEngine); + + scriptRegisterMetaType(scriptEngine); +})); + +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; + } +} +void ScriptManager::registerNewStaticTypesInitializer(StaticTypesInitializerNode* 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 = rootTypesInitializer; + rootTypesInitializer = dest; +} +static void runStaticTypesInitializers(ScriptManager* manager) { + ScriptManager::StaticTypesInitializerNode* here = rootTypesInitializer; + 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; +} + +std::shared_ptr ScriptManager::getUncaughtException() const { + return _engine->uncaughtException(); +} + +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(); }); + return manager; +} + +int ScriptManager::processLevelMaxRetries { ScriptRequest::MAX_RETRIES }; + +ScriptManager::ScriptManager(Context context, const QString& scriptContents, const QString& fileNameString) : + QObject(), + _context(context), + _engine(newScriptEngine(this)), + _scriptContents(scriptContents), + _timerFunctionMap(), + _fileNameString(fileNameString), + _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; + case Context::NETWORKLESS_TEST_SCRIPT: + _type = Type::NETWORKLESS_TEST; + break; + } + + qRegisterMetaType(); + qRegisterMetaType>(); + + _scriptingInterface = std::make_shared(this); + + if (isEntityServerScript()) { + qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1"; + processLevelMaxRetries = 1; + } + + +#if 0 + // V8TODO: Is this actually needed? Exceptions will already be logged on the + // script engine side. + + // this is where all unhandled exceptions end up getting logged + connect(this, &ScriptManager::unhandledException, this, [this](const ScriptValue& err) { + qCCritical(scriptengine) << "Caught exception"; + + auto output = err.engine() == _engine ? err : _engine->makeError(err); + if (!output.property("detail").isValid()) { + output.setProperty("detail", "UnhandledException"); + } + + // Unhandled exception kills the running script + if (_abortOnUncaughtException) { + stop(false); + logException(output); + } + }); +#endif + + // Forward exceptions from the scripting engine + connect(_engine.get(), &ScriptEngine::exception, this, &ScriptManager::unhandledException); + + 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); + } + } + }); + } + + if (!_areMetaTypesInitialized) { + initMetaTypes(); + } +} + +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"; + case NETWORKLESS_TEST_SCRIPT: + return "networkless_test"; + 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); + _assetScriptingInterface->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; + // V8TODO: temporarily increased script timeout. Maybe use different timeouts for release and unoptimized debug? + 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 (evaluation), aborting:" << getFilename(); + _engine->abortEvaluation(); + } else { + auto context = _engine->currentContext(); + if (context) { + qCWarning(scriptengine) << "Script Engine has been running too long (event loop), 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)) { + Q_ASSERT(false); + 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(); + + auto state = engine->newObject(); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + state.setProperty(metaEnum.key(i), metaEnum.value(i)); + } + + prototype.setProperty("State", state); + + 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::initMetaTypes() { + if (_areMetaTypesInitialized) { + return; + } + _areMetaTypesInitialized = true; + runStaticTypesInitializers(this); +} + +void ScriptManager::init() { + if (_isInitialized) { + return; // only initialize once + } + + _isInitialized = true; + + if (_context != NETWORKLESS_TEST_SCRIPT) { + // This initializes a bunch of systems that want network access. We + // want to avoid it in test script mode. + runStaticInitializers(this); + } + + auto scriptEngine = _engine.get(); + + if (_context != NETWORKLESS_TEST_SCRIPT) { + // For test scripts we want to minimize the amount of functionality available, for the least + // amount of dependencies and faster test system startup. + + 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)); + + // 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", _scriptingInterface.get()); + + { + // 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(); + } + + scriptEngine->registerEnum("Script.ExternalPaths", QMetaEnum::fromType()); + + scriptEngine->registerGlobalObject("Quat", &_quatLibrary); + scriptEngine->registerGlobalObject("Vec3", &_vec3Library); + scriptEngine->registerGlobalObject("Mat4", &_mat4Library); + scriptEngine->registerGlobalObject("Uuid", &_uuidLibrary); + + if (_context != NETWORKLESS_TEST_SCRIPT) { + // This requires networking, we want to avoid the need for it in test scripts + 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); + + // constants + scriptEngine->globalObject().setProperty("TREE_SCALE", scriptEngine->newValue(TREE_SCALE)); + + if (_context != NETWORKLESS_TEST_SCRIPT) { + // Scriptable cache access + auto resourcePrototype = createScriptableResourcePrototype(shared_from_this()); + scriptEngine->globalObject().setProperty("Resource", resourcePrototype); + scriptEngine->setDefaultPrototype(qMetaTypeId(), resourcePrototype); + + scriptEngine->registerGlobalObject("Assets", _assetScriptingInterface); + scriptEngine->registerGlobalObject("Resources", DependencyManager::get().data()); + + scriptEngine->registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); + + scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); + } + +#if DEV_BUILD || PR_BUILD + scriptEngine->registerGlobalObject("StackTest", new StackTestScriptingInterface(this)); +#endif + + qCDebug(scriptengine) << "Engine initialized"; +} + +// 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 { + if (_context == NETWORKLESS_TEST_SCRIPT) { + return false; + } + + 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); + } + + if (QThread::currentThread() != _assetScriptingInterface->thread()) { + _assetScriptingInterface->moveToThread(QThread::currentThread()); + } + + auto filenameParts = _fileNameString.split("/"); + auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; + PROFILE_SET_THREAD_NAME("Script: " + name); + + if (isStopped()) { + qCCritical(scriptengine) << "ScriptManager is stopped or ScriptEngines is not available, refusing to run script"; + 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); + _returnValue = _engine->evaluate(_scriptContents, _fileNameString); + + if (_engine->hasUncaughtException() && _abortOnUncaughtException) { + + qCWarning(scriptengine) << "Engine has uncaught exception, stopping"; + stop(); + // V8TODO: Is clearing needed here? + //_engine->clearExceptions(); + } + } +#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); + + qCDebug(scriptengine) << "Waiting for finish"; + + // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptManager + while (!_isFinished) { + //qCDebug(scriptengine) << "In script event loop"; + + 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; + _engine->perManagerLoopIterationCleanup(); + } + + 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(); + emit unhandledException(_engine->uncaughtException()); + _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 + } + +#ifdef SCRIPT_TIMER_PERFORMANCE_STATISTICS + _timerCallCounter++; + if (_timerCallCounter % 100 == 0) { + qCDebug(scriptengine) << "Script engine: " << _engine->manager()->getFilename() + << "timer call count: " << _timerCallCounter << " total time: " << _totalTimeInTimerEvents_s; + } + QElapsedTimer callTimer; + callTimer.start(); +#endif + + 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(); + } + +#ifdef SCRIPT_TIMER_PERFORMANCE_STATISTICS + _totalTimeInTimerEvents_s += callTimer.elapsed() / 1000.0; +#endif +} + +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 { + //qCDebug(scriptengine) << "ScriptManager::resolvePath: getCurrentScriptURLs: " << _engine->getCurrentScriptURLs(); + 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()); + //qCDebug(scriptengine) << "ScriptManager::resolvePath: URL get: " << parentURL << " backtrace: " << context->backtrace() << " " << _engine->getCurrentScriptURLs(); + 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 QString& error, const QString &details = QString()) -> QString { + _engine->raiseException(error, "require.resolve: " + details); + 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(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(message.arg(msg)); + } + } + return throwResolveError(message.arg("system module not found")); + } + } + + if (url.isRelative()) { + return throwResolveError(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(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(message.arg("path does not exist: " + url.toLocalFile())); + } + if (!file.isFile()) { + return throwResolveError(message.arg("path is not a file: " + url.toLocalFile())); + } + } + + 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); + //qCDebug(scriptengine) << "Module object contents" << _engine->scriptValueDebugListMembers(module); + 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); + //_engine->scriptValueDebugDetails(module); + result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath )); + } + + 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, "module error"); + } + 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 + 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); + 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 << ")"; + + //qCDebug(scriptengine_module) << "Exports: " << _engine->scriptValueDebugDetails(module.property("exports")); + 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)) { + //V8TODO this probably needs to be done per context, otherwise file cannot be included again in a module + 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()) { + auto ex = _engine->uncaughtException(); + ex->additionalInfo += "; evaluateInClosure"; + emit unhandledException(ex); + _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 = QVariant::fromValue(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 + qCDebug(scriptengine) << "cloneEntityScriptDetails" << entityID << QThread::currentThread(); +#endif + EntityScriptDetails scriptDetails; + if (getEntityScriptDetails(entityID, scriptDetails)) { +#ifdef DEBUG_ENTITY_STATES + qCDebug(scriptengine) << "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 + qCDebug(scriptengine) << "!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); +} + +// 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); + auto program = _engine->newProgram( contents, fileName ); + auto syntaxCheck = program->checkSyntax(); + if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { + auto message = syntaxCheck->errorMessage(); + //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()); + //V8TODO + //emit unhandledException(syntaxError); + + return; + } + if (!program) { + setError("Bad program (isNull)", EntityScriptStatus::ERROR_RUNNING_SCRIPT); + std::shared_ptr ex = std::make_shared("Program is Null", "Bad program in entityScriptContentAvailable"); + emit unhandledException(ex); + + return; // done processing script + } + + if (isURL) { + setParentURL(scriptOrURL); + } + + // SANITY/PERFORMANCE CHECK USING SANDBOX + // V8TODO: can be skipped for now should we implement it before merging V8 branch? + + /*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_OVERTE + "://" + 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. + + // qCDebug(scriptengine) << "(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 + }*/ + + // 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_OVERTE + "://" + 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. + + ScriptValue exception; + 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")); + } + + if (exception.isError()) { + setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); + auto scriptRuntimeException = std::make_shared(); + ScriptValue message = exception.property("stack"); //This contains more details along with the error message + scriptRuntimeException->errorMessage = message.toString(); + scriptRuntimeException->thrownValue = exception; + emit unhandledException(scriptRuntimeException); + return; + } + // ENTITY SCRIPT WHITELIST ENDS HERE, uncomment below for original full disabling. + + // qCDebug(scriptengine) << "(disabled entity script)" << entityID.toString() << scriptOrURL; + // exception = makeError("UNSAFE_ENTITY_SCRIPTS == 0"); + + // (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); + //V8TODO: check if entityScriptConstructor is a function or not + //Throw V8 exception if it's not? + entityScriptObject = entityScriptConstructor.construct(); + + if (_engine->hasUncaughtException()) { + // V8TODO: Why were we copying the uncaught exception here? Does anything + // actually make use of that? + // entityScriptObject = _engine->cloneUncaughtException("(construct " + entityID.toString() + ")"); + entityScriptObject = _engine->nullValue(); + _engine->clearExceptions(); + } + }; + + doWithEnvironment(entityID, sandboxURL, initialization); + + if (entityScriptObject.isError()) { + // auto exception = entityScriptObject; + // setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT); + // emit unhandledException(exception); + // V8TODO: Is this needed? Wouldn't the ScriptManager have already emitted the 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 + 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) { + qCDebug(scriptengine) << "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(); +} + +void ScriptManager::logBacktrace(const QString &title) { + _engine->logBacktrace(title); +} diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h new file mode 100644 index 00000000000..73fbb77e7de --- /dev/null +++ b/libraries/script-engine/src/ScriptManager.h @@ -0,0 +1,1609 @@ +// +// ScriptManager.h +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#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 "ScriptException.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; +class ScriptManagerScriptingInterface; + +using ScriptEnginePointer = std::shared_ptr; +using ScriptManagerPointer = std::shared_ptr; +using ScriptManagerScriptingInterfacePointer = std::shared_ptr; +using ScriptValueList = QList; + +Q_DECLARE_METATYPE(ScriptManagerPointer) + +const int QTREGISTER_QTimerStar = qRegisterMetaType(); + + +/** + * @brief Callback data for addEventHandler + * + */ +class CallbackData { +public: + /** + * @brief Function to call + * + */ + ScriptValue function; + + /** + * @brief Entity ID + * + */ + EntityItemID definingEntityIdentifier; + + /** + * @brief Sandbox URL for the script + * + */ + QUrl definingSandboxURL; +}; + + +/** + * @brief DeferredLoadEntity + * @deprecated This appars unused + * + */ +class DeferredLoadEntity { +public: + EntityItemID entityID; + QString entityScript; + //bool forceRedownload; +}; + +/** + * @brief Entity with available script contents + * + */ +struct EntityScriptContentAvailable { + /** + * @brief Entity ID + * + */ + EntityItemID entityID; + + /** + * @brief URL to the script, or the actual script if embedded in the URL field + * + */ + QString scriptOrURL; + + /** + * @brief Contents of the script + * + */ + QString contents; + + /** + * @brief Whether scriptOrURL contains an URL + * + */ + bool isURL; + + /** + * @brief Whether the request has been successful + * + */ + bool success; + + /** + * @brief Status as text + * + */ + QString status; +}; + +typedef std::unordered_map EntityScriptContentAvailableMap; + +typedef QList CallbackList; +typedef QHash RegisteredEventHandlers; + + +/** + * @brief Details about an entity script + * + */ +class EntityScriptDetails { +public: + + /** + * @brief Current status + * + */ + EntityScriptStatus status { EntityScriptStatus::PENDING }; + + /** + * @brief Error information + * + * If status indicates an error, this contains a human-readable string giving more information about the error. + * + */ + QString errorInfo { "" }; + + + /** + * @brief The source code of the script + * + */ + QString scriptText { "" }; + + /** + * @brief The return value of the script + * + */ + ScriptValue scriptObject{ ScriptValue() }; + + /** + * @brief Last modified time of the underlying script file + * + * This is used to determine if the script needs reloading when it changes on disk. + */ + int64_t lastModified { 0 }; + + /** + * @brief URL under which the script is allowed to have access + * + * The script is allowed access below this URL (eg, sub-directories), but + * not to the parent context. + */ + QUrl definingSandboxURL { QUrl("about:EntityScript") }; +}; + +// declare a static script initializers +#define STATIC_SCRIPT_TYPES_INITIALIZER(init) \ + static ScriptManager::StaticTypesInitializerNode static_script_types_initializer_(init); + +#define STATIC_SCRIPT_INITIALIZER(init) \ + static ScriptManager::StaticInitializerNode static_script_initializer_(init); + + + + +/** + * @brief Manages a single scripting engine + * + * This class manages and sets up a single scripting engine to make it execute scripts. + * + * It passes the objects needed to expose the public API, provides console access and error + * reporting and event management. + * + * This manipulates a single underlying instance of ScriptEngine. + * + * Part of this class' functionality exists only to provide helper functions to the scripts that are + * run by the scripting engine, and shouldn't be considered part of the C++ API. Those are the functions + * in the "Script support methods", "Module support methods", "Entity Script methods", and "Scripting signals" sections. + * + * The script-facing interface is in ScriptManagerScriptingInterface and documented in JSDoc + * as the Script class. + * + * The ScriptManager provides the following functionality to scripts: + * + * * A math library: Quat, Vec3, Mat4 + * * UUID generation: Uuid + * * Filesystem access: File + * * Console access: console, print + * * Resource access: Resource, Asset, Resources, ExternalPaths + * * Scripting system management: Script + * * Module loading: require + * * Web access: XMLHttpRequest, WebSocket + * * Other miscellaneous functionality. + * + * Example: + * + * @code {.cpp} + * #include "ScriptManager.h" + * + * // Scripts only stop running when Script.stop() is called. + * // In the normal environment this isn't needed, but for things like unit tests we need + * // to use it to make the ScriptManager return from run(). + * + * QString scriptSource = "print(\"Hello, world!\"); Script.stop(true);"; + * QString scriptFilename = "test.js"; + * + * ScriptManagerPointer sm = newScriptManager(ScriptManager::NETWORKLESS_TEST_SCRIPT, scriptSource, scriptFilename); + * connect(sm.get(), &ScriptManager::printedMessage, [](const QString& message, const QString& engineName){ + * qCDebug(scriptengine) << "Printed message from engine" << engineName << ": " << message; + * }); + * + * qInfo() << "Running script!"; + * sm->run(); + * qInfo() << "Done!" + * @endcode + * + * @note + * Technically, the ScriptManager isn't generic enough -- it implements things that imitate + * Node.js for examine in the module loading code, which makes it JS specific. This code + * should probably be moved into the JS ScriptEngine class instead. + * + * The EntityScript functionality might also benefit from being split off into a separate + * class, for better organization. + * + * Some more functionality can be shifted to ScriptManagerScriptingInterface, since + * it only provides services to scripts and isn't called from C++. + */ +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; + + /** + * @brief Context of the script + * + */ + enum Context { + /** + * @brief Client script. + * Allowed to access local HTML files on UI created from C++ calls. + * + */ + CLIENT_SCRIPT, + + /** + * @brief Entity client script + * + */ + ENTITY_CLIENT_SCRIPT, + + /** + * @brief Entity server script + * + */ + ENTITY_SERVER_SCRIPT, + + /** + * @brief Agent script + * + */ + AGENT_SCRIPT, + + /** + * @brief Network-less test system context. + * This is used for the QTest self-tests, and minimizes the API that is made available to + * the running script. It removes the need for network access, which makes for much faster + * test execution. + * + * + * @warning This is a development-targeted bit of functionality. + * + * @warning This is going to break functionality like loadURL and require + */ + NETWORKLESS_TEST_SCRIPT + }; + + /** + * @brief Type of the script + * + */ + enum Type { + /** + * @brief Client + * + */ + CLIENT, + + /** + * @brief Entity client + * Receives the update event. + */ + ENTITY_CLIENT, + + /** + * @brief Entity server + * Receives the update event + * + */ + ENTITY_SERVER, + + /** + * @brief Agent script + * + */ + AGENT, + + /** + * @brief Avatar script + * + */ + AVATAR, + + /** + * @brief Test system script + * + * This is used for the QTest self-tests, and minimizes the API that is made available to + * the running script. It removes the need for network access, which makes for much faster + * test execution. + * + * @warning This is a development-targeted bit of functionality. + */ + NETWORKLESS_TEST + }; + 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); + + class StaticTypesInitializerNode { + public: + ScriptManagerInitializer init; + StaticTypesInitializerNode* prev; + inline StaticTypesInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticTypesInitializer(this); } + }; + static void registerNewStaticTypesInitializer(StaticTypesInitializerNode* dest); + + /** + * @brief Run the script in a dedicated thread + * + * This will have the side effect of evaluating the current script contents and calling run(). + * Callers will likely want to register the script with external services before calling this. + * + * This function will return immediately, and work will continue on the newly created thread. + * + * @note Can't be called twice. + * @note The underlying thread is not accessible. + */ + void runInThread(); + + /** + * @brief Run the script in the caller's thread, exit when Script.stop() is called. + * + * Most scripts never stop running, so this function will never return for them. + */ + void run(); + + + /** + * @brief Get the filename of the running script, without the path. + * + * @return QString Filename + */ + QString getFilename() const; + + /** + * @brief Underlying scripting engine + * + * @return ScriptEnginePointer Scripting engine + */ + inline ScriptEnginePointer engine() { return _engine; } + + QList getListOfEntityScriptIDs(); + + bool isStopped() const; + + + /** + * @name Script support functions + * + * These functions exist to support the scripting API + */ + + ///@{ + + /** + * @brief Stops and unloads the current script. + * + * @note This is part of the public scripting API for Agent scripts and local scripts, but not for EntityScripts + * @param marshal Deprecated + */ + Q_INVOKABLE void stop(bool marshal = false); + + ///@} + + /** + * @brief Stop any evaluating scripts and wait for the scripting thread to finish. + * + * @param shutdown True if we are currently shutting down. Setting this to true will allow + * processing events emitted during the script's shutdown, such as scripts saving settings. + * + * @note This function has an internal timeout, and will forcefully abort the script if it + * takes too long. + */ + void waitTillDoneRunning(bool shutdown = false); + + /** + * @brief Load a script from a given URL + * + * If the script engine is not already running, this will download the URL and start the process of seting it up + * to run. + * + * + * @param scriptURL URL where to load the script from. Can be http, https, atp, or file protocol. The file extension + * has to pass hasValidScriptSuffix(). + * @param reload Load the script again even if it's in the cache. + * + * @note For file:// URLs, only URLs under the default scripts location are allowed. + * @see PathUtils::defaultScriptsLocation + */ + void loadURL(const QUrl& scriptURL, bool reload); + + /** + * @brief Determines whether a script filename has the right suffix + * + * + * @param scriptFileName + * @return true When the script has the right file extension (eg, .js) + * @return false Otherwise + */ + bool hasValidScriptSuffix(const QString& scriptFileName); + + + /** + * @name Script support methods + * + * These functions exist to support the scripting API + */ + + ///@{ + + /** + * @brief Gets the context that the script is running in: Interface/avatar, client entity, server entity, or assignment client. + * + * @note This is part of the public JS API + * @return QString + */ + Q_INVOKABLE QString getContext() const; + + /** + * @brief Checks whether the script is running as an Interface or avatar script. + * @note This is part of the public JS API + * @return bool + */ + Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } + + /** + * @brief Checks whether the application was compiled as a debug build. + * @note This is part of the public JS API + * @return bool + */ + Q_INVOKABLE bool isDebugMode() const; + + /** + * @brief Checks whether the script is running as a client entity script. + * + * @return bool + */ + Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } + + + /** + * @brief Checks whether the script is running as a server entity script. + * + * @return bool + */ + Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } + + /** + * @brief Checks whether the script is running as an assignment client script. + * + * @return bool + */ + Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } + + /** + * @brief Registers a global object by name. + * + * @param valueName + * @param value + */ + Q_INVOKABLE void registerValue(const QString& valueName, ScriptValue value); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - these are intended to be public interfaces available to scripts + + /** + * @brief Format an exception and return it as a string + * + * @param exception Exception object, containing the exception information. + * @param includeExtendedDetails Include additional troubleshooting information from the "detail" property, if there's one + * @return QString A multi-line string containing the formatted exception + */ + Q_INVOKABLE QString formatException(const ScriptValue& exception, bool includeExtendedDetails); + + + + /** + * @brief Adds a function to the list of functions called when a particular event occurs on a particular entity. + * + * @param entityID Entity ID + * @param eventName Name of the event + * @param handler Event handler + * + * @note The same handler can be added multiple times. + */ + Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler); + + /** + * @brief Removes a function from the list of functions called when an entity event occurs on a particular entity. + * + * @param entityID Entity ID + * @param eventName Name if the event + * @param handler Event handler + */ + Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler); + + + /** + * @brief Starts running another script in Interface, if it isn't already running. The script is not automatically loaded next + * time Interface starts. + * + * The script is loaded as a stand-alone script. + * + * @param loadfile File to load + * @warning In practice this seems equivalent to calling loadScript or reloadScript. It reacts to _isReloading in an odd-looking manner. + * Is this function superfluous? + */ + Q_INVOKABLE void load(const QString& loadfile); + + /** + * @brief Includes JavaScript from other files in the current script. + * + * 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. + * + * @param includeFiles List of files to include + * @param callback Callback to call when the files have finished loading. + */ + Q_INVOKABLE void include(const QStringList& includeFiles, const ScriptValue& callback = ScriptValue()); + + /** + * @brief Includes JavaScript from another file in the current script. + * + * 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. + * + * @param includeFile + * @param callback + */ + Q_INVOKABLE void include(const QString& includeFile, const ScriptValue& callback = ScriptValue()); + + ///@} + + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + * @name Module support methods + * + */ + ///@{ + + /** + * @brief Provides access to methods or objects provided in an external JavaScript or JSON file. + * + * Implements CommonJS/Node.js like require/module support + * + * @param moduleId Module to load + * @return ScriptValue + */ + Q_INVOKABLE ScriptValue require(const QString& moduleId); + + /** + * @brief Resets the module cache + * + * @deprecated + * @param deleteScriptCache + */ + Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); + + /** + * @brief The current parent module from the running JS script + * + * + * @return ScriptValue Module. May be null or empty. + */ + ScriptValue currentModule(); + + /** + * @brief Replaces or adds "module" to "parent.children[]" array + * + * This is an internal use function used as a part of the 'require' implementation. + * + * @param module Module to register + * @param parent Parent + * @return true Registration successful + * @return false Registration failed, if the parent isn't a valid module + */ + bool registerModuleWithParent(const ScriptValue& module, const ScriptValue& parent); + + /** + * @brief creates a new JS "module" Object with default metadata properties + * + * This imitates what is provided by https://nodejs.org/api/modules.html + * + * @param modulePath File path to the module + * @param parent Parent module + * @return ScriptValue Created module object + */ + ScriptValue newModule(const QString& modulePath, const ScriptValue& parent = ScriptValue()); + + /** + * @brief Synchronously fetch a module's source code + * + * The return value is a map containing the following fields: + * + * * "status" -- A string indicating the status of the operation + * * "success" -- A true or false value indicating success or failure + * * "url" -- The URL of the source. May not be present. + * * "contents" -- The contents of the source. May not be present. + * + * @param modulePath Path to the module's source code + * @param forceDownload Force a redownload even if the source is already in the cache + * @return QVariantMap The result of the operation + */ + QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); + + /** + * @brief Evaluate a pending module object using the fetched source code + * + * @param module Module object + * @param sourceCode Source code to evaluate + * @return ScriptValue The result of evaluating the source code + */ + ScriptValue instantiateModule(const ScriptValue& module, const QString& sourceCode); + + /** + * @brief Evaluate a program in the underlying scripting engine + * + * This simply calls to ScriptEngine::evaluate() + * + * @param program Source of the program + * @param fileName Filename it was obtained from + * @return ScriptValue Result of the evaluation + */ + ScriptValue evaluate(const QString& program, const QString& fileName = QString()); + + /** + * @brief Calls a function repeatedly, at a set interval. + * + * @note This is a JS API service. + * + * @param function Function to call + * @param intervalMS Interval at which to call the function, in ms + * @return QTimer* A pointer to the timer + */ + Q_INVOKABLE QTimer* setInterval(const ScriptValue& function, int intervalMS); + + + /** + * @brief Calls a function once, after a delay. + * + * @note This is a JS API service. + * + * @param function Function to call + * @param timeoutMS How long to wait before calling the function, in ms + * @return QTimer* A pointer to the timer + */ + Q_INVOKABLE QTimer* setTimeout(const ScriptValue& function, int timeoutMS); + + /** + * @brief Stops an interval timer + * + * @param timer Timer to stop + */ + Q_INVOKABLE void clearInterval(QTimer* timer) { stopTimer(timer); } + + /** + * @brief Stops an interval timer + * + * Overloaded version is needed in case the timer has expired + * + * @param timer Timer to stop + */ + Q_INVOKABLE void clearInterval(QVariantMap timer) { ; } + + /** + * @brief Stops a timeout timer + * + * @param timer Timer to stop + */ + Q_INVOKABLE void clearTimeout(QTimer* timer) { stopTimer(timer); } + + /** + * @brief Stops a timeout timer + * Overloaded version is needed in case the timer has expired + * + * @param timer Timer to stop + */ + + Q_INVOKABLE void clearTimeout(QVariantMap timer) { ; } + + + /** + * @brief Prints a message to the program log + * + * @param message + */ + Q_INVOKABLE void print(const QString& message); + + /** + * @brief Resolves a relative path to an absolute path. The relative path is relative to the script's location. + * + * @param path + * @return QUrl + */ + Q_INVOKABLE QUrl resolvePath(const QString& path) const; + + /** + * @brief Gets the path to the resources directory for QML files. + * + * @return QUrl + */ + Q_INVOKABLE QUrl resourcesPath() const; + + /** + * @brief Starts timing a section of code in order to send usage data about it to Overte. Shouldn't be used outside of the + * standard scripts. + * @param label + */ + Q_INVOKABLE void beginProfileRange(const QString& label) const; + + /** + * @brief Finishes timing a section of code in order to send usage data about it to Overte. Shouldn't be used outside of + * the standard scripts + * @param label + */ + Q_INVOKABLE void endProfileRange(const QString& label) const; + + + ///@} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Entity Script Related methods + + + /** + * @name Entity Script methods + * + */ + + ///@{ + + /** + * @brief Checks whether an entity has an entity script running. + * + * @param entityID + * @return bool + */ + Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; + } + + /** + * @brief Clone the details of an entity script + * + * @param entityID Entity ID + * @return QVariant Copy of the details + */ + QVariant cloneEntityScriptDetails(const EntityItemID& entityID); + + + /** + * @brief Get the details of a local entity script + * + * Same as cloneEntityScriptDetails, only as a QFuture. + * + * @param entityID Entity ID + * @return QFuture + */ + QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; + + + /** + * @brief Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer reachable + * + */ + Q_INVOKABLE void requestGarbageCollection(); + + /** + * @brief Prints out current backtrace to the log. + * + * @param title + */ + Q_INVOKABLE void logBacktrace(const QString &title); + + /** + * @brief Load an entity script + * + * @param entityID + * @param entityScript + * @param forceRedownload + */ + Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); + + /** + * @brief Unload an entity script + * + * @param entityID + * @param shouldRemoveFromMap + */ + Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method + + + /** + * @brief Unload all entity scripts + * + * @param blockingCall + */ + Q_INVOKABLE void unloadAllEntityScripts(bool blockingCall = false); + + /** + * @brief Call a method on an entity script + * + * @param entityID + * @param methodName + * @param params + * @param remoteCallerID + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, + const QStringList& params = QStringList(), + const QUuid& remoteCallerID = QUuid()) override; + + /** + * @brief Call a method on an entity script + * + * @param entityID + * @param methodName + * @param event + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); + + + /** + * @brief Call a method on an entity script + * + * @param entityID + * @param methodName + * @param otherID + * @param collision + */ + Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + + + /** + * @brief Set the script type + * + * @param type Type of this script + */ + void setType(Type type) { _type = type; }; + + /** + * @brief Returns the script type + * + * @return Type of this script + */ + Type getType() { return _type; }; + + /** + * @brief Returns the type of the script as a string + * + * @return QString A string describing the script's type + */ + QString getTypeAsString() const; + + /** + * @brief Whether the script has finished running + * + * The finished status is set by stop() + * @return true The script has finished + * @return false The script is running + */ + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget + + /** + * @brief Whether the script is running + * + * @return true The script is running + * @return false The script is not running + */ + bool isRunning() const { return _isRunning; } // used by ScriptWidget + + // this is used by code in ScriptEngines.cpp during the "reload all" operation + /** + * @brief Whether this ScriptManager is stopping. Once this is true, it stays true. + * + * @return true We are stopping + * @return false We are not stopping + */ + bool isStopping() const { return _isStopping; } + + /** + * @brief Disconnect all signals, except essential ones + * + * This disconnects all signals, except the destroyed() and finished() handlers that + * are needed for cleanup. + */ + void disconnectNonEssentialSignals(); + + + ///@} + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * @brief Set whether this script was user-loaded + * + * This is used by Application to track if a script is user loaded or not. + * @note Consider finding a solution inside of Application so that the ScriptManager class is not polluted by this notion + * + * @param isUserLoaded Script is user-loaded. + */ + void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } + + /** + * @brief Whether the script was user-loaded. + * + * This is used by Application to track if a script is user loaded or not. + * @note Consider finding a solution inside of Application so that the ScriptManager class is not polluted by this notion + * + * @return true + * @return false + */ + bool isUserLoaded() const { return _isUserLoaded; } + + /** + * @brief Set whether to quit when finished + * + * This is used by ScriptEngines + * + * @param quitWhenFinished + */ + void setQuitWhenFinished(const bool quitWhenFinished) { _quitWhenFinished = quitWhenFinished; } + + /** + * @brief Whether to quit when finished + * + * This is read by ScriptEngines + * + * @return true + * @return false + */ + bool isQuitWhenFinished() const { return _quitWhenFinished; } + + /** + * @brief Set a function that determines whether to emit update events + * + * Function func will be called from run() to determine whether update() will be issued. + * The update() event will be called if func() returns true. + * + * @param func Function that determines whether update() events will be issued. + */ + void setEmitScriptUpdatesFunction(std::function func) { _emitScriptUpdates = func; } + + /** + * @brief Logs a script error message and emits an errorMessage event + * + * Emits errorMessage() + * + * @param message Message to send to the log + */ + void scriptErrorMessage(const QString& message); + + /** + * @brief Logs a script warning message and emits an warningMessage event + * + * Emits warningMessage() + * + * @param message Message to send to the log + */ + void scriptWarningMessage(const QString& message); + + /** + * @brief Logs a script info message and emits an infoMessage event + * + * Emits infoMessage() + * + * @param message Message to send to the log + */ + void scriptInfoMessage(const QString& message); + + /** + * @brief Logs a script printed message and emits an printedMessage event + * + * These are messages scripts provide by calling the print function. + * Emits printedMessage() + * + * @param message Message to send to the log + */ + + void scriptPrintedMessage(const QString& message); + + /** + * @brief Clears the debug log window + * + * This only emits clearDebugWindow() + * + */ + void clearDebugLogWindow(); + + /** + * @brief Get the number of running entity scripts + * + * @return int Number of scripts with the status EntityScriptStatus::RUNNING + */ + int getNumRunningEntityScripts() const; + + /** + * @brief Retrieves the details about an entity script + * + * @param entityID Entity ID + * @param details Returned details + * @return true If the entity ID was found + * @return false If the entity ID wasn't found. details will be unmodified. + */ + bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + + /** + * @brief Whether there are script details for a given entity ID + * + * @param entityID Entity ID + * @return true There is an entity script for this entity + * @return false There's no entity script + */ + bool hasEntityScriptDetails(const EntityItemID& entityID) const; + + /** + * @brief Set a shared pointer to the ScriptEngines class + * + * This is used to ask ScriptEngines whether the system is being stopped. + * Setting this is optional. + * + * isStopped() is implemented by asking ScriptEngines. + * + * @param scriptEngines ScriptEngines class + */ + void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } + + + /** + * @brief Call all the registered event handlers on an entity for the specified name. + * + * Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args + * + * @param entityID + * @param eventName + * @param eventHanderArgs + */ + void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, const ScriptValueList& eventHanderArgs); + + /** + * @brief Remove all event handlers for the specified entityID (i.e. the entity is being removed) + * + * @param entityID Entity ID + */ + void removeAllEventHandlers(const EntityItemID& entityID); + + + /** + * @brief Return value of the script that finished running + * + * This should only be used after the script terminates. + * + * @return ScriptValue + */ + ScriptValue getReturnValue() const { return _returnValue; } + + + /** + * @brief Gets the URL for an asset in an external resource bucket. + * + * @param bucket + * @param path + * @return Q_INVOKABLE + */ + Q_INVOKABLE QString getExternalPath(ExternalResource::Bucket bucket, const QString& path); + + + /** + * @brief Get the uncaught exception from the underlying script engine + * + * @return std::shared_ptr Exception + */ + std::shared_ptr getUncaughtException() const; + + /** + * @brief Whether this engine will abort on an uncaught exception + * + * @warning This probably should be refactored into a more comprehensive per-script flags system + * @return true + * @return false + */ + bool getAbortOnUncaughtException() const { return _abortOnUncaughtException; } + + /** + * @brief Whether to abort on an uncaught exception + * + * @warning This probably should be refactored into a more comprehensive per-script flags system + * @param value + */ + void setAbortOnUncaughtException(bool value) { _abortOnUncaughtException = value; } + +public slots: + + /** + * @brief Script.updateMemoryCost + * + * Sends a memory cost update to the underlying scripting engine + * + * @param deltaSize Difference in memory usage + * @deprecated Deprecated + */ + void updateMemoryCost(const qint64 &deltaSize); + +signals: + + + /** + * @name Scripting events + * + */ + ///@{ + + /** + * @brief Script.scriptLoaded + * @deprecated + * @param scriptFilename + */ + void scriptLoaded(const QString& scriptFilename); + + + /** + * @brief Script.errorLoadingScript + * @deprecated + * @param scriptFilename + */ + void errorLoadingScript(const QString& scriptFilename); + + + /** + * @brief Triggered frequently at a system-determined interval. + * + * @param deltaTime + */ + void update(float deltaTime); + + + + /** + * @brief Triggered when the script is stopping. + * + */ + void scriptEnding(); + + + + /** + * @brief Script.finished + * + * @param fileNameString + */ + void finished(const QString& fileNameString, ScriptManagerPointer); + + /** + * @brief Triggered when the script prints a message to the program log + * + * @param message + * @param scriptName + */ + void printedMessage(const QString& message, const QString& scriptName); + + + /** + * @brief Triggered when the script generates an error + * + * @param message + * @param scriptName + */ + void errorMessage(const QString& message, const QString& scriptName); + + + + /** + * @brief Triggered when the script generates a warning + * + * @param message + * @param scriptName + */ + void warningMessage(const QString& message, const QString& scriptName); + + + /** + * @brief Triggered when the script generates an information message + * + * @param message + * @param scriptName + */ + void infoMessage(const QString& message, const QString& scriptName); + + + /** + * @brief Triggered when the running state of the script changes, e.g., from running to stopping. + * + */ + void runningStateChanged(); + + + /** + * @brief Script.clearDebugWindow + * @deprecated + */ + void clearDebugWindow(); + + + /** + * @brief Script.loadScript + * @deprecated + * @param scriptName + * @param isUserLoaded + */ + void loadScript(const QString& scriptName, bool isUserLoaded); + + + /** + * @brief Script.reloadScript + * + * @param scriptName + * @param isUserLoaded + */ + void reloadScript(const QString& scriptName, bool isUserLoaded); + + /** + * @brief Triggered when the script has stopped. + * + */ + void doneRunning(); + + /** + * @brief 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(); + + /** + * @brief Emitted when an entity script has finished running preload + * + * @param entityID + */ + void entityScriptPreloadFinished(const EntityItemID& entityID); + + + /** + * @brief Triggered when a script generates an unhandled exception. + * + * @param exception + */ + void unhandledException(std::shared_ptr exception); + + ///@} + + /** + * @brief Triggered once before the first call to Script.addEventHandler happens on this ScriptManager + * connections assumed to use Qt::DirectConnection + * + * @note not for use by scripts + */ + void attachDefaultEventHandlers(); + + /** + * @brief Triggered repeatedly in the scripting loop to ensure entity edit messages get processed properly + * connections assumed to use Qt::DirectConnection + * + * @note Not for use by scripts + * @param wait + */ + void releaseEntityPacketSenderMessages(bool wait); + +protected: + // Is called by the constructor, bceause all types need to be registered before method discovery with ScriptObjectV8Proxy::investigate() + void initMetaTypes(); + + /** + * @brief Initializes the underlying scripting engine + * + * This sets up the scripting engine with the default APIs + */ + void init(); + + /** + * @brief executeOnScriptThread + * + * @deprecated + * @param function + * @param type + */ + Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); + + /** + * @brief Script._requireResolve + * + * @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;" + * + * @deprecated + * @param moduleId + * @param relativeTo + * @return QString + */ + Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString()); + + /** + * @brief Log an exception + * + * This both sends an exception to the log as an error message, and returns the formatted + * text as a string. + * + * @param exception Exception + * @return QString Exception formatted as a string + */ + QString logException(const ScriptValue& exception); + void timerFired(); + void stopAllTimers(); + void stopAllTimersForEntityScript(const EntityItemID& entityID); + void refreshFileScript(const EntityItemID& entityID); + + /** + * @brief Updates the status of an entity script + * + * Emits entityScriptDetailsUpdated() + * + * @param entityID Entity ID + * @param status Status + * @param errorInfo Description of the error, if any + */ + void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString()); + + + /** + * @brief Set the details for an entity script + * + * @param entityID Entity ID + * @param details Details + */ + void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details); + + /** + * @brief Set the parent URL, used to resolve relative paths + * + * Relative paths are resolved respect of this URL + * + * @param parentURL Parent URL + */ + void setParentURL(const QString& parentURL) { _parentURL = parentURL; } + + /** + * @brief Creates a timer with the specified interval + * + * @param function Function to call when the interval elapses + * @param intervalMS Interval in milliseconds + * @param isSingleShot Whether the timer happens continuously or a single time + * @return QTimer* + */ + QTimer* setupTimerWithInterval(const ScriptValue& function, int intervalMS, bool isSingleShot); + + /** + * @brief Stops a timer + * + * @param timer Timer to stop + */ + void stopTimer(QTimer* timer); + + QHash _registeredHandlers; + + + /** + * @brief Script.entityScriptContentAvailable + * + * @param entityID + * @param scriptOrURL + * @param contents + * @param isURL + * @param success + * @param status + */ + 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. + + /** + * @brief 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). + * + * @param entityID Entity ID, may be null + * @param sandboxURL Sandbox URL + * @param operation Operation to call + */ + void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); + + /** + * @brief Execute operation in the appropriate context for (the possibly empty) entityID. + * + * This is equivalent to doWithEnvironment(), only with separate arguments for the function, this object and arguments. + * + * This is a convenience function, which performs: + * + * @code {.cpp} + * auto operation = [&]() { + * function.call(thisObject, args); + * }; + * doWithEnvironment(entityID, sandboxURL, operation); + * @endcode + * + * @param entityID Entity ID, may be null + * @param sandboxURL Sandbox URL + * @param function Function to call + * @param thisObject "this" object to use for the call + * @param args Arguments + */ + 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 _areMetaTypesInitialized { false }; + bool _isInitialized { false }; + QHash _timerFunctionMap; + QSet _includedURLs; + mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; + QHash _entityScripts; + EntityScriptContentAvailableMap _contentAvailableQueue; + ScriptValue _returnValue; + + 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; + + + + // For debugging performance issues + int _timerCallCounter{ 0 }; + double _totalTimeInTimerEvents_s{ 0.0 }; + + ScriptManagerScriptingInterfacePointer _scriptingInterface; + + bool _abortOnUncaughtException{ false }; + + friend ScriptManagerPointer newScriptManager(Context context, const QString& scriptContents, const QString& fileNameString); + friend class ScriptManagerScriptingInterface; + +}; + +/** + * @brief Creates a new ScriptManager + * + * @param context Context in which scripts will run + * @param scriptContents Contents of the script to run + * @param fileNameString Filename for the script + * @return ScriptManagerPointer + */ +ScriptManagerPointer newScriptManager(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString); + +/** + * @brief Creates a new ScriptManager and adds it to ScriptEngines + * + * Same as newScriptManager, but it additionally registers the new + * ScriptManager with ScriptEngines. + * + * @param context Context in which scripts will run + * @param scriptContents Contents of the script + * @param fileNameString Filename of the script + * @return ScriptManagerPointer + */ +ScriptManagerPointer scriptManagerFactory(ScriptManager::Context context, + const QString& scriptContents, + const QString& fileNameString); + +#endif // hifi_ScriptManager_h diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp new file mode 100644 index 00000000000..144b0e413a6 --- /dev/null +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -0,0 +1,83 @@ +// +// ScriptManagerScriptingInterface.h +// libraries/script-engine/src +// +// Created by Dale Glass on 24/02/2023. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptManager.h" +#include "ScriptManagerScriptingInterface.h" +#include "ScriptEngine.h" +#include + + + + ScriptManagerScriptingInterface::ScriptManagerScriptingInterface(ScriptManager *parent): QObject(parent), _manager(parent) { + + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); + + + connect(_manager, &ScriptManager::scriptLoaded, this, &ScriptManagerScriptingInterface::scriptLoaded); + connect(_manager, &ScriptManager::errorLoadingScript, this, &ScriptManagerScriptingInterface::errorLoadingScript); + connect(_manager, &ScriptManager::update, this, &ScriptManagerScriptingInterface::update); + connect(_manager, &ScriptManager::scriptEnding, this, &ScriptManagerScriptingInterface::scriptEnding); + connect(_manager, &ScriptManager::finished, this, &ScriptManagerScriptingInterface::finished); + connect(_manager, &ScriptManager::printedMessage, this, &ScriptManagerScriptingInterface::printedMessage); + connect(_manager, &ScriptManager::errorMessage, this, &ScriptManagerScriptingInterface::errorMessage); + connect(_manager, &ScriptManager::warningMessage, this, &ScriptManagerScriptingInterface::warningMessage); + connect(_manager, &ScriptManager::infoMessage, this, &ScriptManagerScriptingInterface::infoMessage); + connect(_manager, &ScriptManager::runningStateChanged, this, &ScriptManagerScriptingInterface::runningStateChanged); + connect(_manager, &ScriptManager::clearDebugWindow, this, &ScriptManagerScriptingInterface::clearDebugWindow); + connect(_manager, &ScriptManager::loadScript, this, &ScriptManagerScriptingInterface::loadScript); + connect(_manager, &ScriptManager::doneRunning, this, &ScriptManagerScriptingInterface::doneRunning); + connect(_manager, &ScriptManager::entityScriptDetailsUpdated, this, &ScriptManagerScriptingInterface::entityScriptDetailsUpdated); + connect(_manager, &ScriptManager::entityScriptPreloadFinished, this, &ScriptManagerScriptingInterface::entityScriptPreloadFinished); + connect(_manager, &ScriptManager::unhandledException, this, &ScriptManagerScriptingInterface::scriptManagerException); +} + +void ScriptManagerScriptingInterface::scriptManagerException(std::shared_ptr exception) { + // V8TODO: What should we actually handle here? + + // emit unhandledException(exception.thrownValue); +} + +QVariantMap ScriptManagerScriptingInterface::getMemoryUsageStatistics() { + auto statistics = _manager->engine()->getMemoryUsageStatistics(); + QVariantMap map; + map.insert("totalHeapSize", QVariant((qulonglong)(statistics.totalHeapSize))); + map.insert("usedHeapSize", QVariant((qulonglong)(statistics.usedHeapSize))); + map.insert("totalAvailableSize", QVariant((qulonglong)(statistics.totalAvailableSize))); + map.insert("totalGlobalHandlesSize", QVariant((qulonglong)(statistics.totalGlobalHandlesSize))); + map.insert("usedGlobalHandlesSize", QVariant((qulonglong)(statistics.usedGlobalHandlesSize))); +#ifdef OVERTE_V8_MEMORY_DEBUG + map.insert("scriptValueCount", QVariant((qulonglong)(statistics.scriptValueCount))); + map.insert("scriptValueProxyCount", QVariant((qulonglong)(statistics.scriptValueProxyCount))); + map.insert("qObjectCount", QVariant((qulonglong)(statistics.qObjectCount))); +#endif + return map; +} + +void ScriptManagerScriptingInterface::startCollectingObjectStatistics() { + _manager->engine()->startCollectingObjectStatistics(); +} + +void ScriptManagerScriptingInterface::dumpHeapObjectStatistics() { + _manager->engine()->dumpHeapObjectStatistics(); +} + +ScriptValue ScriptManagerScriptingInterface::createGarbageCollectorDebuggingObject() { + //auto value = _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); + return _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); + //return _manager->engine()->newValue(1); +} diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h new file mode 100644 index 00000000000..2b9aeb49e8d --- /dev/null +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -0,0 +1,762 @@ +// +// ScriptManagerScriptingInterface.h +// libraries/script-engine/src +// +// Created by Dale Glass on 24/02/2023. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include "ScriptManager.h" + + +/**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. + */ + +// V8TODO: this should be moved to somewhere test-related +class TestQObject : public QObject { + Q_OBJECT +public: + //Q_INVOKABLE virtual void testMethod() { qDebug() << "TestQObject::testMethod"; }; +}; + +class ScriptManagerScriptingInterface : public QObject { + Q_OBJECT +public: + ScriptManagerScriptingInterface(ScriptManager *parent); + + virtual ~ScriptManagerScriptingInterface() { + + } + /**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) { _manager->stop(marshal); } + + /**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 { return _manager->getContext(); } + + + /**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 _manager->isClientScript(); } + + /**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 { return _manager->isDebugMode(); } + + /**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 _manager->isEntityClientScript(); } + + /**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 _manager->isEntityServerScript(); } + + /**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 _manager->isAgentScript(); } + + /*@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) { _manager->registerValue(valueName, 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) { return _manager->formatException(exception, 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) { _manager->addEventHandler(entityID, eventName, 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) { _manager->removeEventHandler(entityID, eventName, 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) { _manager->load(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()) { _manager->include(includeFiles, callback);}; + + /**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()) { _manager->include( includeFile, callback); }; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MODULE related methods + + /**jsdoc + * Provides access to methods or objects provided in an external JavaScript or JSON file. + * See {@link https://docs.overte.org/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) { return _manager->require(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) { _manager->resetModuleCache(deleteScriptCache);} + + /**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) { return _manager->setInterval(function, 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) { return _manager->setTimeout(function, 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) { _manager->clearInterval(timer); } + + // Overloaded version is needed in case the timer has expired + Q_INVOKABLE void clearInterval(QVariantMap 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) { _manager->clearTimeout(timer); } + + // Overloaded version is needed in case the timer has expired + Q_INVOKABLE void clearTimeout(QVariantMap 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) { _manager->print(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 { return _manager->resolvePath(path);} + + /**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 { return _manager->resourcesPath(); } + + /**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 { _manager->beginProfileRange(label); } + + /**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 { _manager->endProfileRange(label); } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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) { return _manager->isEntityScriptRunning(entityID); } + + /**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() { _manager->requestGarbageCollection(); } + + /**jsdoc + * Prints out current backtrace to the log. + * @function Script.logBacktrace + * @param {string} title - Title added to the printed out backtrace. + */ + Q_INVOKABLE void logBacktrace(const QString &title) { _manager->logBacktrace(title); } + + /*@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) { _manager->loadEntityScript(entityID, entityScript, 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) { _manager->unloadEntityScript(entityID, shouldRemoveFromMap); } + + /**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) { _manager->unloadAllEntityScripts(blockingCall); } + + /**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()) { _manager->callEntityScriptMethod(entityID, methodName, params, remoteCallerID); } + + /**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) { _manager->callEntityScriptMethod(entityID, methodName, 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) { _manager->callEntityScriptMethod(entityID, methodName, otherID, 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(); } + + /**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) { return _manager->getExternalPath(bucket, path); } + + /**jsdoc + *

Object containing memory usage statistics data.

+ * + * + * + * + * + * + * Amount of heap memory that is currently in use. + * + * + * + * + *
NameTypeDescription
totalHeapSize{number}Total heap size allocated by scripting engine.
usedHeapSize{number}
totalAvailableSize{number}Amount of remaining available heap memory
totalGlobalHandlesSize{number}V8-specific property
usedGlobalHandlesSize{number}V8-specific property
+ * @typedef {object} Script.MemoryUsageData + */ + + /**jsdoc + * Returns memory usage statistics data. + * @function Script.getMemoryUsageStatistics + * @Returns {Script.MemoryUsageData} Object containing statistics about memory usage. + */ + Q_INVOKABLE QVariantMap getMemoryUsageStatistics(); + + /**jsdoc + * Start collecting object statistics that can later be reported with Script.dumpHeapObjectStatistics(). + * @function Script.dumpHeapObjectStatistics + */ + Q_INVOKABLE void startCollectingObjectStatistics(); + + /**jsdoc + * Prints heap statistics to a file. Collecting needs to first be started with Script.dumpHeapObjectStatistics(). + * @function Script.dumpHeapObjectStatistics + */ + Q_INVOKABLE void dumpHeapObjectStatistics(); + + /**jsdoc + * Create test object for garbage collector debugging. + * @function Script.createGarbageCollectorDebuggingObject() + * @Returns Test object. + */ + Q_INVOKABLE ScriptValue createGarbageCollectorDebuggingObject(); + +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); + + +protected: + /**jsdoc + * @function Script.executeOnScriptThread + * @param {function} function - Function. + * @param {ConnectionType} [type=2] - Connection type. + * @deprecated This function is deprecated and will be removed. + */ + // V8TODO: Nothing seems to be using this, and it generates: + // hifi.scriptengine: Parameter 1 in method ScriptManagerScriptingInterface :: "executeOnScriptThread" is of type QMetaType::UnknownType + // so removing for now. + // + // Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ) { _manager->executeOnScriptThread(function, type);} + + /**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()) { return _manager->_requireResolve(moduleId, relativeTo); } + + /**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) + { _manager->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success, status); } + +private slots: + void scriptManagerException(std::shared_ptr exception); + + +private: + + ScriptManager *_manager; +}; \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptProgram.h b/libraries/script-engine/src/ScriptProgram.h new file mode 100644 index 00000000000..2e7e41620fc --- /dev/null +++ b/libraries/script-engine/src/ScriptProgram.h @@ -0,0 +1,122 @@ +// +// ScriptProgram.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/2/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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; + +/** + * @brief Engine-independent representation of a script program + * + * This is an analog of QScriptProgram from Qt5. + * + * It's used to pre-compile scripts, and to check their syntax. + * + */ +class ScriptProgram { +public: + virtual ScriptSyntaxCheckResultPointer checkSyntax() = 0; //It cannot be const anymore because V8 doesn't have separate syntax checking function + + /** + * @brief Returns the filename associated with this program. + * + * @return QString + */ + virtual QString fileName() const = 0; + + /** + * @brief Returns the source code of this program. + * + * @return QString + */ + virtual QString sourceCode() const = 0; + +protected: + ~ScriptProgram() {} // prevent explicit deletion of base class +}; + + +/** + * @brief Engine-independent representation of a script syntax check + * + * This is an analog of QScriptSyntaxCheckResult from Qt5. + * + * + */ +class ScriptSyntaxCheckResult { +public: + + /** + * @brief State of the syntax check + * + */ + enum State + { + Error = 0, /** The program contains a syntax error. */ + Intermediate = 1, /** The program is incomplete. */ + Valid = 2 /** The program is a syntactically correct program. */ + }; + +public: + + /** + * @brief Returns the error column number of this ScriptSyntaxCheckResult, or -1 if there is no error. + * + * @return int + */ + virtual int errorColumnNumber() const = 0; + + /** + * @brief Returns the error line number of this ScriptSyntaxCheckResult, or -1 if there is no error. + * + * @return int + */ + virtual int errorLineNumber() const = 0; + + /** + * @brief Returns the error message of this ScriptSyntaxCheckResult, or an empty string if there is no error. + * + * @return QString + */ + virtual QString errorMessage() const = 0; + + /** + * @brief + * + * @return QString + */ + virtual QString errorBacktrace() const = 0; + + /** + * @brief Returns the state of this ScriptSyntaxCheckResult. + * + * @return State + */ + 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..a6c054d2f63 100644 --- a/libraries/script-engine/src/ScriptUUID.cpp +++ b/libraries/script-engine/src/ScriptUUID.cpp @@ -4,11 +4,13 @@ // // Created by Andrew Meadows on 2014-04-07 // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ScriptUUID.h" @@ -17,6 +19,7 @@ #include "ScriptEngineLogging.h" #include "ScriptEngine.h" +#include "ScriptManager.h" QUuid ScriptUUID::fromString(const QString& s) { return QUuid(s); @@ -42,7 +45,8 @@ 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); + Q_ASSERT(engine); + 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 abcd34ccece..921c61428bd 100644 --- a/libraries/script-engine/src/ScriptUUID.h +++ b/libraries/script-engine/src/ScriptUUID.h @@ -4,11 +4,13 @@ // // Created by Andrew Meadows on 2014-04-07 // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -19,7 +21,8 @@ #include #include -#include + +#include "Scriptable.h" /*@jsdoc * The Uuid API provides facilities for working with UUIDs. @@ -36,7 +39,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..2c8f9e25811 --- /dev/null +++ b/libraries/script-engine/src/ScriptValue.cpp @@ -0,0 +1,256 @@ +// +// ScriptValue.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 9/2/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptValue.h" + +#include "ScriptEngineLogging.h" + +//V8TODO name is misleading, it's actually undefined +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 ScriptValue prototype() const override; + virtual void setData(const ScriptValue& val) override; + virtual bool hasProperty(const QString &name) const 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 QList getPropertyNames() 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(); +} + +ScriptValue ScriptValueProxyNull::prototype() const { + return ScriptValue(); +} + +void ScriptValueProxyNull::setData(const ScriptValue& val) { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::setData called on empty value"); +} + +bool ScriptValueProxyNull::hasProperty(const QString& name) const { + Q_ASSERT(false); + qCWarning(scriptengine_script, "ScriptValue::hasProperty called on empty value"); + return false; +} + + +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(); +} + +QList ScriptValueProxyNull::getPropertyNames() const { + return QList(); +} + +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..1560c99a3ec --- /dev/null +++ b/libraries/script-engine/src/ScriptValue.h @@ -0,0 +1,411 @@ +// +// ScriptValue.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 4/25/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValue_h +#define hifi_ScriptValue_h + +#include + +#include +#include +#include +#include +#include +#include + +#include "ScriptEngineLogging.h" + +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, + QObjectMember = 0x00000020, + 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 ScriptValue prototype() const; + inline void setData(const ScriptValue& val); + inline bool hasProperty(const QString &name) const; + 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 QList getPropertyNames() 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 ScriptValue prototype() const = 0; + virtual void setData(const ScriptValue& val) = 0; + virtual bool hasProperty(const QString &name) const = 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 QList getPropertyNames() 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: + virtual ~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); + ScriptEnginePointer scriptEngine = _proxy->engine(); + if (scriptEngine == nullptr) { + qCDebug(scriptengine) << "Call to deleted or non-existing script engine"; + return ScriptValue(); + } + 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); +} + +ScriptValue ScriptValue::prototype() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->prototype(); +} + +void ScriptValue::setData(const ScriptValue& val) { + Q_ASSERT(_proxy != nullptr); + return _proxy->setData(val); +} + + +bool ScriptValue::hasProperty(const QString& name) const { + Q_ASSERT(_proxy != nullptr); + return _proxy->hasProperty(name); +} + +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); +} + +inline QList ScriptValue::getPropertyNames() const { + Q_ASSERT(_proxy != nullptr); + return _proxy->getPropertyNames(); +}; + +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..6d568fc99f3 --- /dev/null +++ b/libraries/script-engine/src/ScriptValueIterator.h @@ -0,0 +1,44 @@ +// +// ScriptValueIterator.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/2/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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..f888c4767dc --- /dev/null +++ b/libraries/script-engine/src/ScriptValueUtils.cpp @@ -0,0 +1,1031 @@ +// +// ScriptValueUtils.cpp +// libraries/shared/src +// +// Created by Anthony Thibault on 4/15/16. +// Copyright 2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. +// +// 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 +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptValueUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ScriptEngine.h" +#include "ScriptEngineCast.h" +#include "ScriptValueIterator.h" +#include "ScriptEngineLogging.h" +#include "v8/FastScriptValueUtils.h" +#include "AddressManager.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); + scriptRegisterMetaType(engine); + + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + + scriptRegisterMetaType, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue>(engine); + scriptRegisterMetaType, qVectorQuatToScriptValue, qVectorQuatFromScriptValue>(engine); + scriptRegisterMetaType, qVectorBoolToScriptValue, qVectorBoolFromScriptValue>(engine); + scriptRegisterMetaType, qVectorFloatToScriptValue, qVectorFloatFromScriptValue>(engine); + scriptRegisterMetaType, qVectorIntToScriptValue, qVectorIntFromScriptValue>(engine); + scriptRegisterMetaType, qVectorQUuidToScriptValue, qVectorQUuidFromScriptValue>(engine); + + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine, "QTimer*"); + + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + + scriptRegisterMetaType(engine); + + scriptRegisterMetaType(engine); + + scriptRegisterMetaType(engine); + scriptRegisterMetaType(engine); + + scriptRegisterSequenceMetaType >(engine); + scriptRegisterSequenceMetaType>(engine); + scriptRegisterSequenceMetaType>(engine); + + scriptRegisterSequenceMetaType>(engine); + scriptRegisterSequenceMetaType>(engine); + scriptRegisterSequenceMetaType>(engine); + + scriptRegisterMetaType, scriptValueToEnumClass>(engine, "LookupTrigger"); +} + +ScriptValue qVector2DToScriptValue(ScriptEngine* engine, const QVector2D& qVector2D) { + glm::vec2 vec2(qVector2D.x(), qVector2D.y()); + return vec2ToScriptValue(engine, vec2); +} + +bool qVector2DFromScriptValue(const ScriptValue& object, QVector2D& qVector2D) { + glm::vec2 vec2; + if (vec2FromScriptValue(object, vec2)) { + qVector2D.setX(vec2.x); + qVector2D.setY(vec2.y); + return true; + } else { + return false; + } +} + +ScriptValue qVector3DToScriptValue(ScriptEngine* engine, const QVector3D& qVector3D) { + glm::vec3 vec3(qVector3D.x(), qVector3D.y(), qVector3D.z()); + return vec3ToScriptValue(engine, vec3); +} + +bool qVector3DFromScriptValue(const ScriptValue& object, QVector3D& qVector3D) { + glm::vec3 vec3; + if (vec3FromScriptValue(object, vec3)) { + qVector3D.setX(vec3.x); + qVector3D.setY(vec3.y); + qVector3D.setZ(vec3.z); + return true; + } else { + return false; + } +} + +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; +} + +#ifndef CONVERSIONS_OPTIMIZED_FOR_V8 +ScriptValue vec3ToScriptValue(ScriptEngine* engine, const glm::vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_vec3__"); + if (!prototype.hasProperty("defined") || !prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "globalThis.__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; +} +#endif + +ScriptValue vec3ColorToScriptValue(ScriptEngine* engine, const glm::vec3& vec3) { + auto prototype = engine->globalObject().property("__hifi_vec3_color__"); + if (!prototype.property("defined").toBool()) { + prototype = engine->evaluate( + "globalThis.__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; +} + +// V8TODO: add similar checks to rest of the conversions +#ifndef CONVERSIONS_OPTIMIZED_FOR_V8 +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 { + return false; + } + } 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 { + return false; + } + } else if (object.isObject()) { + 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"); + } + + if (!x.isValid() || !y.isValid() || !z.isValid()) { + // V8TODO: This breaks the sit script because in our API valid Vec3 can be created from just 2 values + //return false; + } + + vec3.x = x.toVariant().toFloat(); + vec3.y = y.toVariant().toFloat(); + vec3.z = z.toVariant().toFloat(); + } else { + return false; + } + return true; +} +#endif + +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; +} + +// V8TODO: add similar check to other conversions +bool quatFromScriptValue(const ScriptValue& object, glm::quat& quat) { + if (!object.isValid()) { + return false; + } + if (!object.isObject()) { + return false; + } + QVariant x = object.property("x").toVariant(); + QVariant y = object.property("y").toVariant(); + QVariant z = object.property("z").toVariant(); + QVariant w = object.property("w").toVariant(); + if (!x.isValid() || !y.isValid() || !z.isValid() || !w.isValid()) { + return false; + } + 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; +} + +ScriptValue qTimerToScriptValue(ScriptEngine* engine, QTimer* const &in) { + return engine->newQObject(in, ScriptEngine::QtOwnership); +} + +bool qTimerFromScriptValue(const ScriptValue& object, QTimer* &out) { + return (out = qobject_cast(object.toQObject())) != nullptr; +} + +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) { + return (out = qobject_cast(value.toQObject())) != nullptr; +} + +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()); + + qCDebug(scriptengine) << "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 { + qCDebug(scriptengine) << "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..49319667cb2 --- /dev/null +++ b/libraries/script-engine/src/ScriptValueUtils.h @@ -0,0 +1,287 @@ +// +// ScriptValueUtils.h +// libraries/shared/src +// +// Created by Anthony Thibault on 4/15/16. +// Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// 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 +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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 QVector2D; +ScriptValue qVector2DToScriptValue(ScriptEngine* engine, const QVector2D& qVector2D); +bool qVector2DFromScriptValue(const ScriptValue& object, QVector2D& qVector2D); + +class QVector3D; +ScriptValue qVector3DToScriptValue(ScriptEngine* engine, const QVector3D& qVector3D); +bool qVector3DFromScriptValue(const ScriptValue& object, QVector3D& qVector3D); + +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); + +//QTimer +class QTimer; +ScriptValue qTimerToScriptValue(ScriptEngine* engine, QTimer* const &in); +bool qTimerFromScriptValue(const ScriptValue& object, QTimer* &out); + +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..e8be77b6abc --- /dev/null +++ b/libraries/script-engine/src/Scriptable.cpp @@ -0,0 +1,24 @@ +// +// Scriptable.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#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..489b2ac330b --- /dev/null +++ b/libraries/script-engine/src/Scriptable.h @@ -0,0 +1,62 @@ +// +// Scriptable.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/1/21. +// Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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 7773033083e..ae16bbdbdf9 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 05/12/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -144,10 +146,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..1e675b5dcfe 100644 --- a/libraries/script-engine/src/SpatialEvent.cpp +++ b/libraries/script-engine/src/SpatialEvent.cpp @@ -4,14 +4,19 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "SpatialEvent.h" #include +#include "ScriptEngine.h" +#include "ScriptValueUtils.h" +#include "ScriptValue.h" SpatialEvent::SpatialEvent() : locTranslation(0.0f), @@ -30,17 +35,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..b21575ee110 100644 --- a/libraries/script-engine/src/SpatialEvent.h +++ b/libraries/script-engine/src/SpatialEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -18,7 +20,9 @@ #include #include -#include +#include "ScriptValue.h" + +class ScriptEngine; /// [unused] Represents a spatial event to the scripting engine class SpatialEvent { @@ -26,8 +30,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..f6b61a49f29 100644 --- a/libraries/script-engine/src/TouchEvent.cpp +++ b/libraries/script-engine/src/TouchEvent.cpp @@ -4,19 +4,21 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +206,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 +220,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 +234,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 +247,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 5783418d54c..f17f263a529 100644 --- a/libraries/script-engine/src/TouchEvent.h +++ b/libraries/script-engine/src/TouchEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -20,8 +22,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 +33,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/script-engine/src/TypedArrayPrototype.h b/libraries/script-engine/src/TypedArrayPrototype.h deleted file mode 100644 index adcc9f3abf9..00000000000 --- a/libraries/script-engine/src/TypedArrayPrototype.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// TypedArrayPrototype.h -// -// -// Created by Clement on 7/14/14. -// Copyright 2014 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 -/// @{ - -#ifndef hifi_TypedArrayPrototype_h -#define hifi_TypedArrayPrototype_h - -#include "ArrayBufferViewClass.h" - -/// The javascript functions associated with a TypedArray instance prototype -class TypedArrayPrototype : public QObject, public QScriptable { - Q_OBJECT -public: - TypedArrayPrototype(QObject* parent = NULL); - -public slots: - void set(QScriptValue array, qint32 offset = 0); - QScriptValue subarray(qint32 begin); - QScriptValue subarray(qint32 begin, qint32 end); - - QScriptValue get(quint32 index); - void set(quint32 index, QScriptValue& value); -private: - QByteArray* thisArrayBuffer() const; -}; - -#endif // hifi_TypedArrayPrototype_h - -/// @} diff --git a/libraries/script-engine/src/TypedArrays.h b/libraries/script-engine/src/TypedArrays.h deleted file mode 100644 index 948b9677f14..00000000000 --- a/libraries/script-engine/src/TypedArrays.h +++ /dev/null @@ -1,154 +0,0 @@ -// -// TypedArrays.h -// -// -// Created by Clement on 7/9/14. -// Copyright 2014 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 -/// @{ - -#ifndef hifi_TypedArrays_h -#define hifi_TypedArrays_h - -#include "ArrayBufferViewClass.h" - -static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT"; -static const QString LENGTH_PROPERTY_NAME = "length"; - -static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array"; -static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array"; -static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray"; -static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array"; -static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array"; -static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array"; -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 -class TypedArray : public ArrayBufferViewClass { - Q_OBJECT -public: - TypedArray(ScriptEngine* scriptEngine, QString name); - virtual QScriptValue newInstance(quint32 length); - virtual QScriptValue newInstance(QScriptValue array); - virtual QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length); - - virtual QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) override; - virtual QScriptValue property(const QScriptValue& object, - const QScriptString& name, uint id) override; - virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override = 0; - virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) override; - - QString name() const override; - QScriptValue prototype() const override; - -protected: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); - - void setBytesPerElement(quint32 bytesPerElement); - - QScriptValue _proto; - QScriptValue _ctor; - - QScriptString _name; - QScriptString _bytesPerElementName; - QScriptString _lengthName; - - quint32 _bytesPerElement; - - friend class TypedArrayPrototype; -}; - -class Int8ArrayClass : public TypedArray { - Q_OBJECT -public: - Int8ArrayClass(ScriptEngine* 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; -}; - -class Uint8ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint8ArrayClass(ScriptEngine* 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; -}; - -class Uint8ClampedArrayClass : public TypedArray { - Q_OBJECT -public: - Uint8ClampedArrayClass(ScriptEngine* 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; -}; - -class Int16ArrayClass : public TypedArray { - Q_OBJECT -public: - Int16ArrayClass(ScriptEngine* 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; -}; - -class Uint16ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint16ArrayClass(ScriptEngine* 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; -}; - -class Int32ArrayClass : public TypedArray { - Q_OBJECT -public: - Int32ArrayClass(ScriptEngine* 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; -}; - -class Uint32ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint32ArrayClass(ScriptEngine* 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; -}; - -class Float32ArrayClass : public TypedArray { - Q_OBJECT -public: - Float32ArrayClass(ScriptEngine* 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; -}; - -class Float64ArrayClass : public TypedArray { - Q_OBJECT -public: - Float64ArrayClass(ScriptEngine* 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; -}; - -#endif // hifi_TypedArrays_h - -/// @} diff --git a/libraries/shared/src/VariantMapToScriptValue.cpp b/libraries/script-engine/src/VariantMapToScriptValue.cpp similarity index 67% rename from libraries/shared/src/VariantMapToScriptValue.cpp rename to libraries/script-engine/src/VariantMapToScriptValue.cpp index 156a438bd74..b40f9fd735c 100644 --- a/libraries/shared/src/VariantMapToScriptValue.cpp +++ b/libraries/script-engine/src/VariantMapToScriptValue.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "VariantMapToScriptValue.h" @@ -15,20 +17,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 +44,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 +67,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..a64d43ac332 --- /dev/null +++ b/libraries/script-engine/src/VariantMapToScriptValue.h @@ -0,0 +1,25 @@ +// +// VariantMapToScriptValue.h +// libraries/shared/src/ +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @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..6b689d4c4c7 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -4,9 +4,11 @@ // // Created by Brad Hefta-Gaub on 1/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Vec3.h" @@ -21,7 +23,12 @@ #include "NumericalConstants.h" #include "ScriptEngine.h" #include "ScriptEngineLogging.h" +#include "ScriptManager.h" +Vec3::~Vec3() { + qCDebug(scriptengine) << "ScriptMethodV8Proxy destroyed"; + printf("ScriptMethodV8Proxy destroyed"); +} float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3)); @@ -32,8 +39,9 @@ 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); + Q_ASSERT(engine); + if (ScriptManager* scriptManager = engine()->manager()) { + scriptManager->print(message); } } @@ -47,14 +55,14 @@ glm::vec3 Vec3::toPolar(const glm::vec3& v) { if (glm::abs(radius) < EPSILON) { return glm::vec3(0.0f, 0.0f, 0.0f); } - + glm::vec3 u = v / radius; - + float elevation, azimuth; - + elevation = glm::asin(-u.y); azimuth = atan2(v.x, v.z); - + // Round off small decimal values if (glm::abs(elevation) < EPSILON) { elevation = 0.0f; diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 61fcd824759..e63a7cd7ca7 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -4,11 +4,13 @@ // // Created by Brad Hefta-Gaub on 1/29/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Scriptable Vec3 class library. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -20,9 +22,9 @@ #include #include -#include #include "GLMHelpers.h" +#include "Scriptable.h" /*@jsdoc * The Vec3 API provides facilities for generating and manipulating 3-dimensional vectors. Overte uses a @@ -75,7 +77,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) @@ -418,6 +420,9 @@ public slots: const glm::vec3& RIGHT() { return Vectors::RIGHT; } const glm::vec3& UP() { return Vectors::UP; } const glm::vec3& FRONT() { return Vectors::FRONT; } + +public: + virtual ~Vec3(); }; #endif // hifi_Vec3_h diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index a001e2b2c27..dada3029510 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -4,21 +4,34 @@ // // Created by Thijs Wenker on 8/4/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // This class is an implementation of the WebSocket object for scripting use. It provides a near-complete implementation // of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "WebSocketClass.h" +#include "ScriptContext.h" #include "ScriptEngine.h" - +#include "ScriptEngineCast.h" #include "ScriptEngineLogging.h" +#include "ScriptValue.h" +#include "ScriptManager.h" + +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); + scriptRegisterMetaType(scriptEngine); +})); -WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : +WebSocketClass::WebSocketClass(ScriptEngine* engine, QString url) : _webSocket(new QWebSocket()), _engine(engine) { @@ -26,7 +39,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 +56,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 +104,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 +183,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 +215,28 @@ 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) { - out = qobject_cast(object.toQObject()); +bool webSocketFromScriptValue(const ScriptValue &object, WebSocketClass* &out) { + return (out = qobject_cast(object.toQObject())) != nullptr; } -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 70c02a94b44..da9eec73ff3 100644 --- a/libraries/script-engine/src/WebSocketClass.h +++ b/libraries/script-engine/src/WebSocketClass.h @@ -4,9 +4,11 @@ // // Created by Thijs Wenker on 8/4/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,9 +18,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 +94,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 +109,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 +174,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 +193,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 +231,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 +254,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..b6a73125221 100644 --- a/libraries/script-engine/src/WebSocketServerClass.cpp +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -4,18 +4,22 @@ // // Created by Thijs Wenker on 8/10/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Making WebSocketServer accessible through scripting. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +28,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 0fa01f7813a..9f372aa96b5 100644 --- a/libraries/script-engine/src/WebSocketServerClass.h +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -4,9 +4,11 @@ // // Created by Thijs Wenker on 8/10/15. // Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,10 +18,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 +90,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 +109,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 4d7a2bc4799..066f79bbf03 100644 --- a/libraries/script-engine/src/WheelEvent.cpp +++ b/libraries/script-engine/src/WheelEvent.cpp @@ -4,15 +4,17 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "WheelEvent.h" -#include -#include +#include "ScriptEngine.h" +#include "ScriptValue.h" WheelEvent::WheelEvent() : x(0.0f), @@ -80,8 +82,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); @@ -96,6 +98,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 b14b521fe9f..cd55e261e01 100644 --- a/libraries/script-engine/src/WheelEvent.h +++ b/libraries/script-engine/src/WheelEvent.h @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 2014-10-27. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -18,8 +20,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 +30,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 c848e2c0c55..caca759096e 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -4,12 +4,14 @@ // // Created by Ryan Huffman on 5/2/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // This class is an implementation of the XMLHttpRequest object for scripting use. It provides a near-complete implementation // of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "XMLHttpRequestClass.h" @@ -23,12 +25,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 +46,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 +94,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 +104,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 +124,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 +160,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 +201,7 @@ void XMLHttpRequestClass::doSend() { */ void XMLHttpRequestClass::requestTimeout() { if (_onTimeout.isFunction()) { - _onTimeout.call(QScriptValue::NullValue); + _onTimeout.call(_engine->nullValue()); } abortRequest(); _errorCode = QNetworkReply::TimeoutError; @@ -214,16 +221,16 @@ void XMLHttpRequestClass::requestFinished() { _rawResponseData.append(_reply->readAll()); if (_responseType == "json") { + // V8TODO: V8 JSON parser needs to be used instead _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 06568710c56..02dfcc3dd62 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -4,9 +4,11 @@ // // Created by Ryan Huffman on 5/2/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -19,11 +21,13 @@ #include #include #include -#include -#include -#include #include +#include "ScriptEngine.h" +#include "ScriptValue.h" + +class ScriptContext; + /* XMlHttpRequest object XMlHttpRequest.objectName string @@ -54,7 +58,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 +159,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 +175,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 +216,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 +271,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 +286,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 +304,7 @@ public slots: void disconnectFromReply(QNetworkReply* reply); void abortRequest(); - QScriptEngine* _engine { nullptr }; + ScriptEngine* _engine { nullptr }; bool _async { true }; QUrl _url; QString _method; @@ -309,9 +313,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/v8/ArrayBufferClass.cpp similarity index 70% rename from libraries/script-engine/src/ArrayBufferClass.cpp rename to libraries/script-engine/src/v8/ArrayBufferClass.cpp index 4844428eb4c..1846625776a 100644 --- a/libraries/script-engine/src/ArrayBufferClass.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferClass.cpp @@ -4,9 +4,11 @@ // // Created by Clement on 7/3/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ArrayBufferClass.h" @@ -15,24 +17,22 @@ #include "ArrayBufferPrototype.h" #include "DataViewClass.h" -#include "ScriptEngine.h" +#include "ScriptEngineV8.h" #include "TypedArrays.h" -static const QString CLASS_NAME = "ArrayBuffer"; +// V8TODO Do not remove yet, this will be useful in later PRs +/*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(ScriptEngineV8* scriptEngine) : QObject(scriptEngine), QScriptClass(scriptEngine) { qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); - QScriptValue global = engine()->globalObject(); + V8ScriptValue global = engine()->globalObject(); // Save string handles for quick lookup _name = engine()->toStringHandle(CLASS_NAME.toLatin1()); @@ -66,15 +66,15 @@ QScriptClass(scriptEngine) { new Float64ArrayClass(scriptEngine); } -QScriptValue ArrayBufferClass::newInstance(qint32 size) { +V8ScriptValue ArrayBufferClass::newInstance(qint32 size) { const qint32 MAX_LENGTH = 100000000; if (size < 0) { engine()->evaluate("throw \"ArgumentError: negative length\""); - return QScriptValue(); + return V8ScriptValue(); } if (size > MAX_LENGTH) { engine()->evaluate("throw \"ArgumentError: absurd length\""); - return QScriptValue(); + return V8ScriptValue(); } // 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) @@ -82,28 +82,29 @@ QScriptValue ArrayBufferClass::newInstance(qint32 size) { #endif QScriptEngine* eng = engine(); QVariant variant = QVariant::fromValue(QByteArray(size, 0)); - QScriptValue data = eng->newVariant(variant); + V8ScriptValue data = eng->newVariant(variant); return engine()->newObject(this, data); } -QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { - QScriptValue data = engine()->newVariant(QVariant::fromValue(ba)); - return engine()->newObject(this, data); +V8ScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { + QScriptEngine* eng = engine(); + V8ScriptValue data = eng->newVariant(QVariant::fromValue(ba)); + return eng->newObject(this, data); } -QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue ArrayBufferClass::construct(V8ScriptContext* context, QScriptEngine* engine) { ArrayBufferClass* cls = qscriptvalue_cast(context->callee().data()); if (!cls) { // return if callee (function called) is not of type ArrayBuffer - return QScriptValue(); + return V8ScriptValue(); } - QScriptValue arg = context->argument(0); + V8ScriptValue arg = context->argument(0); if (!arg.isValid() || !arg.isNumber()) { - return QScriptValue(); + return V8ScriptValue(); } quint32 size = arg.toInt32(); - QScriptValue newObject = cls->newInstance(size); + V8ScriptValue newObject = cls->newInstance(size); if (context->isCalledAsConstructor()) { // if called with keyword new, replace this object. @@ -114,59 +115,59 @@ QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* return newObject; } -QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object, - const QScriptString& name, +ScriptObjectV8Proxy::QueryFlags ArrayBufferClass::queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, QueryFlags flags, uint* id) { QByteArray* ba = qscriptvalue_cast(object.data()); if (ba && name == _byteLength) { // if the property queried is byteLength, only handle read access return flags &= HandlesReadAccess; } - return QScriptClass::QueryFlags(); // No access + return ScriptObjectV8Proxy::QueryFlags(); // No access } -QScriptValue ArrayBufferClass::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue ArrayBufferClass::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { QByteArray* ba = qscriptvalue_cast(object.data()); if (ba && name == _byteLength) { return ba->length(); } - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; +V8ScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; } QString ArrayBufferClass::name() const { return _name.toString(); } -QScriptValue ArrayBufferClass::prototype() const { +V8ScriptValue ArrayBufferClass::prototype() const { return _proto; } -QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { - QScriptValue ctor = engine->globalObject().property(CLASS_NAME); +V8ScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { + V8ScriptValue ctor = engine->globalObject().property(CLASS_NAME); ArrayBufferClass* cls = qscriptvalue_cast(ctor.data()); if (!cls) { if (engine->currentContext()) { engine->currentContext()->throwError("arrayBufferClass::toScriptValue -- could not get " + CLASS_NAME + " class constructor"); } - return QScriptValue::NullValue; + return V8ScriptValue::NullValue; } return cls->newInstance(ba); } -void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& byteArray) { +void ArrayBufferClass::fromScriptValue(const V8ScriptValue& object, QByteArray& byteArray) { if (object.isString()) { // UTF-8 encoded String byteArray = object.toString().toUtf8(); } else if (object.isArray()) { // Array of uint8s eg: [ 128, 3, 25, 234 ] auto Uint8Array = object.engine()->globalObject().property("Uint8Array"); - auto typedArray = Uint8Array.construct(QScriptValueList{object}); + auto typedArray = Uint8Array.construct(V8ScriptValueList{object}); if (QByteArray* buffer = qscriptvalue_cast(typedArray.property("buffer"))) { byteArray = *buffer; } @@ -177,4 +178,4 @@ void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& b } } } - +*/ diff --git a/libraries/script-engine/src/v8/ArrayBufferClass.h b/libraries/script-engine/src/v8/ArrayBufferClass.h new file mode 100644 index 00000000000..ddc0b323b4b --- /dev/null +++ b/libraries/script-engine/src/v8/ArrayBufferClass.h @@ -0,0 +1,68 @@ +// +// ArrayBufferClass.h +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ArrayBufferClass_h +#define hifi_ArrayBufferClass_h + +#include +#include "libplatform/libplatform.h" +#include "v8.h" +#include + +// V8TODO Do not remove yet, this will be useful in later PRs +//#include "V8Types.h" +/* +class ScriptEngineV8; + +/// [V8] Implements the ArrayBuffer scripting class +class ArrayBufferClass : public QObject, public ScriptClass { + Q_OBJECT +public: + ArrayBufferClass(ScriptEngineV8* scriptEngine); + V8ScriptValue newInstance(qint32 size); + V8ScriptValue newInstance(const QByteArray& ba); + + QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + + QString name() const override; + V8ScriptValue prototype() const override; + + +private: + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); + + static V8ScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba); + static void fromScriptValue(const V8ScriptValue& obj, QByteArray& ba); + + V8ScriptValue _proto; + V8ScriptValue _ctor; + + // JS Object attributes + V8ScriptString _name; + V8ScriptString _byteLength; + +}; + +*/ +#endif // hifi_ArrayBufferClass_h + +/// @} diff --git a/libraries/script-engine/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/v8/ArrayBufferPrototype.cpp similarity index 90% rename from libraries/script-engine/src/ArrayBufferPrototype.cpp rename to libraries/script-engine/src/v8/ArrayBufferPrototype.cpp index d75482aa2e6..62d89eff8cf 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferPrototype.cpp @@ -4,24 +4,27 @@ // // Created by Clement on 7/3/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ArrayBufferPrototype.h" #include -#include -#include - -#include "ArrayBufferClass.h" +#include +#include +#include "libplatform/libplatform.h" +#include "v8.h" static const int QCOMPRESS_HEADER_POSITION = 0; static const int QCOMPRESS_HEADER_SIZE = 4; -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO Do not remove yet, this will be useful in later PRs +/*Q_DECLARE_METATYPE(QByteArray*) ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) { } @@ -87,4 +90,4 @@ QByteArray ArrayBufferPrototype::recodeImage(const QString& sourceFormat, const QByteArray* ArrayBufferPrototype::thisArrayBuffer() const { return qscriptvalue_cast(thisObject().data()); -} +}*/ diff --git a/libraries/script-engine/src/ArrayBufferPrototype.h b/libraries/script-engine/src/v8/ArrayBufferPrototype.h similarity index 63% rename from libraries/script-engine/src/ArrayBufferPrototype.h rename to libraries/script-engine/src/v8/ArrayBufferPrototype.h index 324f3176609..4a39e7e9090 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.h +++ b/libraries/script-engine/src/v8/ArrayBufferPrototype.h @@ -4,9 +4,11 @@ // // Created by Clement on 7/3/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -15,11 +17,13 @@ #ifndef hifi_ArrayBufferPrototype_h #define hifi_ArrayBufferPrototype_h -#include -#include +// V8TODO Do not remove yet, this will be useful in later PRs +/*#include -/// The javascript functions associated with an ArrayBuffer instance prototype -class ArrayBufferPrototype : public QObject, public QScriptable { +#include "../Scriptable.h" + +/// [V8] The javascript functions associated with an ArrayBuffer instance prototype +class ArrayBufferPrototype : public QObject, public Scriptable { Q_OBJECT public: ArrayBufferPrototype(QObject* parent = NULL); @@ -33,7 +37,7 @@ public slots: private: QByteArray* thisArrayBuffer() const; }; - +*/ #endif // hifi_ArrayBufferPrototype_h /// @} diff --git a/libraries/script-engine/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/v8/ArrayBufferViewClass.cpp similarity index 65% rename from libraries/script-engine/src/ArrayBufferViewClass.cpp rename to libraries/script-engine/src/v8/ArrayBufferViewClass.cpp index 64cbcc53523..436e67e6a25 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferViewClass.cpp @@ -4,27 +4,32 @@ // // Created by Clement on 7/8/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ArrayBufferViewClass.h" +#include "ScriptEngineV8.h" Q_DECLARE_METATYPE(QByteArray*) -ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) : -QObject(scriptEngine), -QScriptClass(scriptEngine), -_scriptEngine(scriptEngine) { +// V8TODO Do not remove yet, this will be useful in later PRs +/*ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngineV8* 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()); _byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); } -QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object, - const QScriptString& name, +QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, QueryFlags flags, uint* id) { if (name == _bufferName || name == _byteOffsetName || name == _byteLengthName) { return flags &= HandlesReadAccess; // Only keep read access flags @@ -32,8 +37,8 @@ QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& return QScriptClass::QueryFlags(); // No access } -QScriptValue ArrayBufferViewClass::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue ArrayBufferViewClass::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { if (name == _bufferName) { return object.data().property(_bufferName); } @@ -43,10 +48,10 @@ QScriptValue ArrayBufferViewClass::property(const QScriptValue& object, if (name == _byteLengthName) { return object.data().property(_byteLengthName); } - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; -} +V8ScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; +}*/ diff --git a/libraries/script-engine/src/v8/ArrayBufferViewClass.h b/libraries/script-engine/src/v8/ArrayBufferViewClass.h new file mode 100644 index 00000000000..a345ce630f3 --- /dev/null +++ b/libraries/script-engine/src/v8/ArrayBufferViewClass.h @@ -0,0 +1,56 @@ +// +// ArrayBufferViewClass.h +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ArrayBufferViewClass_h +#define hifi_ArrayBufferViewClass_h + +#include +#include "V8Types.h" + +// V8TODO Do not remove yet, this will be useful in later PRs +/*class ScriptEngineV8; + +static const QString BUFFER_PROPERTY_NAME = "buffer"; +static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset"; +static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; + +/// [V8] The base class containing common code for ArrayBuffer views +class ArrayBufferViewClass : public QObject, public QScriptClass { + Q_OBJECT +public: + ArrayBufferViewClass(ScriptEngineV8* scriptEngine); + + ScriptEngineV8* getScriptEngine() { return _scriptEngine; } + + virtual QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + virtual V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + virtual V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; +protected: + // JS Object attributes + V8ScriptString _bufferName; + V8ScriptString _byteOffsetName; + V8ScriptString _byteLengthName; + + ScriptEngineV8* _scriptEngine; +}; +*/ +#endif // hifi_ArrayBufferViewClass_h + +/// @} diff --git a/libraries/script-engine/src/DataViewClass.cpp b/libraries/script-engine/src/v8/DataViewClass.cpp similarity index 70% rename from libraries/script-engine/src/DataViewClass.cpp rename to libraries/script-engine/src/v8/DataViewClass.cpp index 3cc5443973a..8d91fdab9fc 100644 --- a/libraries/script-engine/src/DataViewClass.cpp +++ b/libraries/script-engine/src/v8/DataViewClass.cpp @@ -4,21 +4,24 @@ // // Created by Clement on 7/8/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "DataViewClass.h" -#include "DataViewPrototype.h" +// V8TODO Do not remove yet, this will be useful in later PRs +/*#include "DataViewPrototype.h" Q_DECLARE_METATYPE(QByteArray*) static const QString DATA_VIEW_NAME = "DataView"; -DataViewClass::DataViewClass(ScriptEngine* scriptEngine) : ArrayBufferViewClass(scriptEngine) { - QScriptValue global = engine()->globalObject(); +DataViewClass::DataViewClass(ScriptEngineV8* scriptEngine) : ArrayBufferViewClass(scriptEngine) { + V8ScriptValue global = engine()->globalObject(); // Save string handles for quick lookup _name = engine()->toStringHandle(DATA_VIEW_NAME.toLatin1()); @@ -37,8 +40,8 @@ DataViewClass::DataViewClass(ScriptEngine* scriptEngine) : ArrayBufferViewClass( engine()->globalObject().setProperty(name(), _ctor); } -QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) { - QScriptValue data = engine()->newObject(); +V8ScriptValue DataViewClass::newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) { + V8ScriptValue data = engine()->newObject(); data.setProperty(_bufferName, buffer); data.setProperty(_byteOffsetName, byteOffset); data.setProperty(_byteLengthName, byteLentgh); @@ -46,36 +49,36 @@ QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, return engine()->newObject(this, data); } -QScriptValue DataViewClass::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue DataViewClass::construct(V8ScriptContext* context, QScriptEngine* engine) { DataViewClass* cls = qscriptvalue_cast(context->callee().data()); if (!cls || context->argumentCount() < 1) { - return QScriptValue(); + return V8ScriptValue(); } - QScriptValue bufferArg = context->argument(0); - QScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : QScriptValue(); - QScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : QScriptValue(); + V8ScriptValue bufferArg = context->argument(0); + V8ScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : V8ScriptValue(); + V8ScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : V8ScriptValue(); QByteArray* arrayBuffer = (bufferArg.isValid()) ? qscriptvalue_cast(bufferArg.data()) :NULL; if (!arrayBuffer) { engine->evaluate("throw \"TypeError: 1st argument not a ArrayBuffer\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.isNumber() && (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size())) { engine->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteLengthArg.isNumber() && (byteLengthArg.toInt32() < 0 || byteOffsetArg.toInt32() + byteLengthArg.toInt32() > arrayBuffer->size())) { engine->evaluate("throw \"RangeError: byteLength out of range\""); - return QScriptValue(); + return V8ScriptValue(); } quint32 byteOffset = (byteOffsetArg.isNumber()) ? byteOffsetArg.toInt32() : 0; quint32 byteLength = (byteLengthArg.isNumber()) ? byteLengthArg.toInt32() : arrayBuffer->size() - byteOffset; - QScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength); + V8ScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength); if (context->isCalledAsConstructor()) { context->setThisObject(newObject); @@ -89,6 +92,7 @@ QString DataViewClass::name() const { return _name.toString(); } -QScriptValue DataViewClass::prototype() const { +V8ScriptValue DataViewClass::prototype() const { return _proto; } +*/ diff --git a/libraries/script-engine/src/v8/DataViewClass.h b/libraries/script-engine/src/v8/DataViewClass.h new file mode 100644 index 00000000000..c8ed7efdea2 --- /dev/null +++ b/libraries/script-engine/src/v8/DataViewClass.h @@ -0,0 +1,46 @@ +// +// DataViewClass.h +// +// +// Created by Clement on 7/8/14. +// Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_DataViewClass_h +#define hifi_DataViewClass_h + +// V8TODO Do not remove yet, this will be useful in later PRs +/* +#include "ArrayBufferViewClass.h" + +/// [V8] Implements the DataView scripting class +class DataViewClass : public ArrayBufferViewClass { + Q_OBJECT +public: + DataViewClass(ScriptEngineV8* scriptEngine); + V8ScriptValue newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 byteLength); + + QString name() const override; + V8ScriptValue prototype() const override; + +private: + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); + + V8ScriptValue _proto; + V8ScriptValue _ctor; + + V8ScriptString _name; +}; +*/ + +#endif // hifi_DataViewClass_h + +/// @} diff --git a/libraries/script-engine/src/DataViewPrototype.cpp b/libraries/script-engine/src/v8/DataViewPrototype.cpp similarity index 92% rename from libraries/script-engine/src/DataViewPrototype.cpp rename to libraries/script-engine/src/v8/DataViewPrototype.cpp index ef757a5cb4d..d54110557dd 100644 --- a/libraries/script-engine/src/DataViewPrototype.cpp +++ b/libraries/script-engine/src/v8/DataViewPrototype.cpp @@ -12,18 +12,24 @@ #include "DataViewPrototype.h" #include +#include +#include "libplatform/libplatform.h" +#include "v8.h" #include -#include "DataViewClass.h" +#include -Q_DECLARE_METATYPE(QByteArray*) +#include "ArrayBufferViewClass.h" + +// V8TODO Do not remove yet, this will be useful in later PRs +/*Q_DECLARE_METATYPE(QByteArray*) DataViewPrototype::DataViewPrototype(QObject* parent) : QObject(parent) { } QByteArray* DataViewPrototype::thisArrayBuffer() const { - QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + V8ScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); return qscriptvalue_cast(bufferObject.data()); } @@ -119,7 +125,7 @@ quint32 DataViewPrototype::getUint32(qint32 byteOffset, bool littleEndian) { return 0; } -QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) { +V8ScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) { if (realOffset(byteOffset, sizeof(float))) { QDataStream stream(*thisArrayBuffer()); stream.skipRawData(byteOffset); @@ -129,16 +135,16 @@ QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) float result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } - return QScriptValue(result); + return V8ScriptValue(result); } thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) { +V8ScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) { if (realOffset(byteOffset, sizeof(double))) { QDataStream stream(*thisArrayBuffer()); stream.skipRawData(byteOffset); @@ -148,13 +154,13 @@ QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) double result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } return result; } thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } void DataViewPrototype::setInt8(qint32 byteOffset, qint32 value) { @@ -252,5 +258,5 @@ void DataViewPrototype::setFloat64(qint32 byteOffset, double value, bool littleE thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); } } - +*/ diff --git a/libraries/script-engine/src/DataViewPrototype.h b/libraries/script-engine/src/v8/DataViewPrototype.h similarity index 81% rename from libraries/script-engine/src/DataViewPrototype.h rename to libraries/script-engine/src/v8/DataViewPrototype.h index b4d9462e8f6..c880551e16e 100644 --- a/libraries/script-engine/src/DataViewPrototype.h +++ b/libraries/script-engine/src/v8/DataViewPrototype.h @@ -4,9 +4,11 @@ // // Created by Clement on 7/8/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine @@ -16,10 +18,13 @@ #define hifi_DataViewPrototype_h #include -#include -/// The javascript functions associated with a DataView instance prototype -class DataViewPrototype : public QObject, public QScriptable { +#include "V8Types.h" +#include "../Scriptable.h" + +// V8TODO Do not remove yet, this will be useful in later PRs +/*/// [V8] The javascript functions associated with a DataView instance prototype +class DataViewPrototype : public QObject, public Scriptable { Q_OBJECT public: DataViewPrototype(QObject* parent = NULL); @@ -41,8 +46,8 @@ public slots: quint32 getUint16(qint32 byteOffset, bool littleEndian = false); qint32 getInt32(qint32 byteOffset, bool littleEndian = false); quint32 getUint32(qint32 byteOffset, bool littleEndian = false); - QScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false); - QScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false); + V8ScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false); + V8ScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false); // Stores a value of the given type at the specified byte offset // from the start of the view. There is no alignment constraint; @@ -68,7 +73,7 @@ public slots: QByteArray* thisArrayBuffer() const; bool realOffset(qint32& offset, size_t size) const; }; - +*/ #endif // hifi_DataViewPrototype_h /// @} diff --git a/libraries/script-engine/src/v8/FastScriptValueUtils.cpp b/libraries/script-engine/src/v8/FastScriptValueUtils.cpp new file mode 100644 index 00000000000..66c5fce2e40 --- /dev/null +++ b/libraries/script-engine/src/v8/FastScriptValueUtils.cpp @@ -0,0 +1,211 @@ +// +// FastScriptValueUtils.cpp +// libraries/script-engine/src/v8/FastScriptValueUtils.cpp +// +// Created by dr Karol Suprynowicz on 2023/03/30. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "FastScriptValueUtils.h" + +#include + +#include "../ScriptEngine.h" +#include "V8Types.h" +#include "ScriptValueV8Wrapper.h" + +#ifdef CONVERSIONS_OPTIMIZED_FOR_V8 + +ScriptValue vec3ToScriptValue(ScriptEngine* engine, const glm::vec3& vec3) { + ScriptValue value = engine->newObject(); + + ScriptValueV8Wrapper *proxy = ScriptValueV8Wrapper::unwrap(value); + + auto engineV8 = proxy->getV8Engine(); + + auto isolate = engineV8->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = engineV8->getContext(); + v8::Context::Scope contextScope(engineV8->getContext()); + V8ScriptValue v8ScriptValue = proxy->toV8Value(); + v8::Local v8Object = v8::Local::Cast(v8ScriptValue.get()); + + v8::Local prototype; + bool hasPrototype = false; + + if (context->Global()->Get(context, v8::String::NewFromUtf8(isolate, "__hifi_vec3__").ToLocalChecked()).ToLocal(&prototype)) { + if (!prototype->IsNullOrUndefined() && prototype->IsObject()) { + v8::Local prototypeObject = v8::Local::Cast(prototype); + v8::Local isDefined; + if (prototypeObject->Get(context, v8::String::NewFromUtf8(isolate, "defined").ToLocalChecked()).ToLocal(&isDefined)) { + if ((!isDefined->IsNullOrUndefined()) && isDefined->BooleanValue(isolate)) { + hasPrototype = true; + } + } + } + } + + if (!hasPrototype) { + QString sourceCode("globalThis.__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; } }" + "})"); + v8::TryCatch tryCatch(isolate); + v8::ScriptOrigin scriptOrigin(isolate, v8::String::NewFromUtf8(isolate, "Vec3prototype").ToLocalChecked()); + v8::Local script; + if (!v8::Script::Compile(context, v8::String::NewFromUtf8(isolate, sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + Q_ASSERT(false); + } + v8::Local result; + if (!script->Run(context).ToLocal(&result)) { + Q_ASSERT(false); + } + if (!context->Global()->Get(context, v8::String::NewFromUtf8(isolate, "__hifi_vec3__").ToLocalChecked()).ToLocal(&prototype)) { + Q_ASSERT(false); + } + Q_ASSERT(!tryCatch.HasCaught()); + qDebug() <<"vec3ToScriptValue: creating prototype"; + } + + if (!v8Object->Set(context, v8::String::NewFromUtf8(isolate, "x").ToLocalChecked(), v8::Number::New(isolate, vec3.x)).FromMaybe(false)) { + Q_ASSERT(false); + } + if (!v8Object->Set(context, v8::String::NewFromUtf8(isolate, "y").ToLocalChecked(), v8::Number::New(isolate, vec3.y)).FromMaybe(false)) { + Q_ASSERT(false); + } + if (!v8Object->Set(context, v8::String::NewFromUtf8(isolate, "z").ToLocalChecked(), v8::Number::New(isolate, vec3.z)).FromMaybe(false)) { + Q_ASSERT(false); + } + + if (!v8Object->SetPrototype(context, prototype).FromMaybe(false)) { + Q_ASSERT(false); + } + return value; +} + +bool vec3FromScriptValue(const ScriptValue& object, glm::vec3& vec3) { + ScriptValueV8Wrapper *proxy = ScriptValueV8Wrapper::unwrap(object); + + auto engineV8 = proxy->getV8Engine(); + + auto isolate = engineV8->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = engineV8->getContext(); + v8::Context::Scope contextScope(engineV8->getContext()); + V8ScriptValue v8ScriptValue = proxy->toV8Value(); + + v8::Local v8Value = v8ScriptValue.get(); + + + if (v8Value->IsNumber()) { + vec3 = glm::vec3(v8Value->NumberValue(context).ToChecked()); + } else if (v8Value->IsString()) { + QColor qColor(QString(*v8::String::Utf8Value(isolate, v8::Local::Cast(v8Value)))); + if (qColor.isValid()) { + vec3.x = qColor.red(); + vec3.y = qColor.green(); + vec3.z = qColor.blue(); + } else { + return false; + } + } else if (v8Value->IsArray()) { + auto array = v8::Local::Cast(v8Value); + if (array->Length() == 3) { + v8::Local xValue,yValue,zValue; + if (!array->Get(context, 0).ToLocal(&xValue)) { + return false; + } + if (!array->Get(context, 1).ToLocal(&yValue)) { + return false; + } + if (!array->Get(context, 2).ToLocal(&zValue)) { + return false; + } + if (xValue->IsNullOrUndefined() || yValue->IsNullOrUndefined() || zValue->IsNullOrUndefined()) { + return false; + } + double x,y,z; + if (!xValue->NumberValue(context).To(&x) + || !yValue->NumberValue(context).To(&y) + || !zValue->NumberValue(context).To(&z)) { + return false; + } + + vec3.x = xValue->NumberValue(context).FromMaybe(0.0); + vec3.y = yValue->NumberValue(context).FromMaybe(0.0); + vec3.z = zValue->NumberValue(context).FromMaybe(0.0); + } else { + return false; + } + } else if (v8Value->IsObject()) { + v8::Local v8Object = v8::Local::Cast(v8Value); + v8::Local xValue; + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "x").ToLocalChecked()).ToLocal(&xValue)) { + Q_ASSERT(false); + } + if (xValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "r").ToLocalChecked()).ToLocal(&xValue)) { + Q_ASSERT(false); + } + } + if (xValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "red").ToLocalChecked()).ToLocal(&xValue)) { + Q_ASSERT(false); + } + } + + v8::Local yValue; + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "y").ToLocalChecked()).ToLocal(&yValue)) { + Q_ASSERT(false); + } + if (yValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "g").ToLocalChecked()).ToLocal(&yValue)) { + Q_ASSERT(false); + } + } + if (yValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "green").ToLocalChecked()).ToLocal(&yValue)) { + Q_ASSERT(false); + } + } + + v8::Local zValue; + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "z").ToLocalChecked()).ToLocal(&zValue)) { + Q_ASSERT(false); + } + if (zValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "b").ToLocalChecked()).ToLocal(&zValue)) { + Q_ASSERT(false); + } + } + if (zValue->IsNullOrUndefined()) { + if (!v8Object->Get(context, v8::String::NewFromUtf8(isolate, "blue").ToLocalChecked()).ToLocal(&zValue)) { + Q_ASSERT(false); + } + } + + vec3.x = xValue->NumberValue(context).FromMaybe(0.0); + vec3.y = yValue->NumberValue(context).FromMaybe(0.0); + vec3.z = zValue->NumberValue(context).FromMaybe(0.0); + } else { + return false; + } + return true; +} + +#endif \ No newline at end of file diff --git a/libraries/script-engine/src/v8/FastScriptValueUtils.h b/libraries/script-engine/src/v8/FastScriptValueUtils.h new file mode 100644 index 00000000000..80d97023a54 --- /dev/null +++ b/libraries/script-engine/src/v8/FastScriptValueUtils.h @@ -0,0 +1,32 @@ +// +// FastScriptValueUtils.cpp +// libraries/script-engine/src/v8/FastScriptValueUtils.cpp +// +// Created by dr Karol Suprynowicz on 2023/03/30. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Contains V8-specific implementations of the function converting Overte datatypes to and from script values. +// These are used instead of generic implementations if CONVERSIONS_OPTIMIZED_FOR_V8 is defined. +// V8-specific implementations can make script engine several times faster. + +#ifndef overte_FastScriptValueUtils_h +#define overte_FastScriptValueUtils_h + +#include +#include + +#include "../ScriptValue.h" + +#define CONVERSIONS_OPTIMIZED_FOR_V8 + +#ifdef CONVERSIONS_OPTIMIZED_FOR_V8 +ScriptValue vec3ToScriptValue(ScriptEngine* engine, const glm::vec3& vec3); + +bool vec3FromScriptValue(const ScriptValue& object, glm::vec3& vec3); +#endif + +#endif // overte_FastScriptValueUtils_h diff --git a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp new file mode 100644 index 00000000000..2b92a9ae8a5 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp @@ -0,0 +1,251 @@ +// +// ScriptContextV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/22/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptContextV8Wrapper.h" + +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" + +ScriptContextV8Wrapper::ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context, ScriptContextPointer parent) : + _functionCallbackInfo(nullptr), _propertyCallbackInfo(nullptr), _engine(engine), + _context(engine->getIsolate(), context), _parentContext(parent) { +} + +ScriptContextV8Wrapper::ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::FunctionCallbackInfo *functionCallbackInfo, + const v8::Local context, ScriptContextPointer parent) : + _functionCallbackInfo(functionCallbackInfo), _propertyCallbackInfo(nullptr), _engine(engine), + _context(engine->getIsolate(), context), _parentContext(parent) { +} + +ScriptContextV8Wrapper::ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::PropertyCallbackInfo *propertyCallbackInfo, + const v8::Local context, ScriptContextPointer parent) : + _functionCallbackInfo(nullptr), _propertyCallbackInfo(propertyCallbackInfo), _engine(engine), + _context(engine->getIsolate(), context), _parentContext(parent) { +} + +ScriptContextV8Wrapper::~ScriptContextV8Wrapper() noexcept { + //V8TODO: what if destructor happens during shutdown and V8 isolate is already disposed of? + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + _context.Reset(); +} + +ScriptContextV8Wrapper* ScriptContextV8Wrapper::unwrap(ScriptContext* val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val); +} + +v8::Local ScriptContextV8Wrapper::toV8Value() const { + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_context.Get(_engine->getIsolate())); +} + +int ScriptContextV8Wrapper::argumentCount() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + if (_functionCallbackInfo) { + return _functionCallbackInfo->Length(); + } else if (_propertyCallbackInfo) { + return 0; + } else { + return Q_METAMETHOD_INVOKE_MAX_ARGS; + } +} + +ScriptValue ScriptContextV8Wrapper::argument(int index) const { + if (_functionCallbackInfo) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local result = (*_functionCallbackInfo)[index]; + if (index < _functionCallbackInfo->kArgsLength) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); + } else { + return _engine->undefinedValue(); + } + } else if (_propertyCallbackInfo) { + return _engine->undefinedValue(); + } else { + return _engine->undefinedValue(); + } +} + +QStringList ScriptContextV8Wrapper::backtrace() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 40); + QStringList backTrace; + for (int i = 0; i < stackTrace->GetFrameCount(); i++) { + v8::Local stackFrame = stackTrace->GetFrame(isolate, i); + backTrace.append(QString(*v8::String::Utf8Value(isolate, stackFrame->GetScriptNameOrSourceURL())) + + QString(" ") + + QString(*v8::String::Utf8Value(isolate, stackFrame->GetFunctionName())) + + QString(":") + + QStringLiteral("%1").arg(stackFrame->GetLineNumber()) + ); + } + return backTrace; +} + +ScriptValue ScriptContextV8Wrapper::callee() const { + Q_ASSERT(false); + //V8TODO + //Can this be done with CurrentStackTrace? + //V8ScriptValue result = _context->callee(); + return _engine->undefinedValue(); +} + +ScriptEnginePointer ScriptContextV8Wrapper::engine() const { + return _engine->shared_from_this(); +} + +ScriptFunctionContextPointer ScriptContextV8Wrapper::functionContext() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + auto scriptFunctionContextPointer = std::make_shared(_engine, _context.Get(_engine->getIsolate())); + return scriptFunctionContextPointer; +} + +ScriptContextPointer ScriptContextV8Wrapper::parentContext() const { + return _parentContext; +} + +ScriptValue ScriptContextV8Wrapper::thisObject() const { + if (_functionCallbackInfo) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local result = _functionCallbackInfo->This(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); + } else if (_propertyCallbackInfo) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local result = _propertyCallbackInfo->This(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); + } else { + return _engine->undefinedValue(); + } +} + +ScriptValue ScriptContextV8Wrapper::throwError(const QString& text) { + auto isolate = _engine->getIsolate(); + // V8TODO: I have no idea how to do this yet when it happens on another thread + if(isolate->IsCurrent()) { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + V8ScriptValue result(_engine, + _engine->getIsolate()->ThrowError( + v8::String::NewFromUtf8(_engine->getIsolate(), text.toStdString().c_str()).ToLocalChecked())); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } else { + qCWarning(scriptengine_v8) << "throwError on a different thread not implemented yet, error value: " << text; + return ScriptValue(); + } +} + +ScriptValue ScriptContextV8Wrapper::throwValue(const ScriptValue& value) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(value); + if (!unwrapped) { + return _engine->undefinedValue(); + } + V8ScriptValue result(_engine, _engine->getIsolate()->ThrowException(unwrapped->toV8Value().constGet())); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + +ScriptFunctionContextV8Wrapper::ScriptFunctionContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context) : _engine(engine) { + v8::Locker locker(engine->getIsolate()); + v8::Isolate::Scope isolateScope(engine->getIsolate()); + v8::HandleScope handleScope(engine->getIsolate()); + _context.Reset(engine->getIsolate(), context); +} + +ScriptFunctionContextV8Wrapper::~ScriptFunctionContextV8Wrapper() { + //V8TODO: what if destructor happens during shutdown and V8 isolate is already disposed of? + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + _context.Reset(); +} + +QString ScriptFunctionContextV8Wrapper::fileName() const { + // It's not exactly like in QtScript, because there's no such context object in V8, let's return the current one for now + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local name = v8::StackTrace::CurrentScriptNameOrSourceURL(_engine->getIsolate()); + v8::String::Utf8Value nameUTF(_engine->getIsolate(), name); + return QString(*nameUTF); +} + +QString ScriptFunctionContextV8Wrapper::functionName() const { + // It's not exactly like in QtScript, because there's no such context object in V8, let's return the current one for now + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(_engine->getIsolate(), 1); + v8::Local stackFrame = stackTrace->GetFrame(_engine->getIsolate(), 0); + v8::Local name = stackFrame->GetFunctionName(); + v8::String::Utf8Value nameUTF(_engine->getIsolate(), name); + return QString(*nameUTF); +} + +ScriptFunctionContext::FunctionType ScriptFunctionContextV8Wrapper::functionType() const { + //V8TODO: This is only used in debugPrint. Should we remove it? + //return static_cast(_value.functionType()); + return ScriptFunctionContext::FunctionType::ScriptFunction; +} + +int ScriptFunctionContextV8Wrapper::lineNumber() const { + // It's not exactly like in QtScript, because there's no such context object in V8, let's return the current one for now + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_context.Get(isolate)); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(_engine->getIsolate(), 1); + v8::Local stackFrame = stackTrace->GetFrame(_engine->getIsolate(), 0); + return stackFrame->GetLineNumber(); +} diff --git a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h new file mode 100644 index 00000000000..c410587c458 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h @@ -0,0 +1,87 @@ +// +// ScriptContextV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/22/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptContextV8Wrapper_h +#define hifi_ScriptContextV8Wrapper_h + +#include + +#include "../ScriptContext.h" +#include "../ScriptValue.h" + +#include +#include + +//class V8ScriptContext; +class ScriptEngineV8; + +/// [V8] Implements ScriptContext for V8 and translates calls for V8ScriptContextInfo +class ScriptContextV8Wrapper final : public ScriptContext { +public: // construction + ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context, ScriptContextPointer parent); + ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::FunctionCallbackInfo *functionCallbackInfo, + const v8::Local context, ScriptContextPointer parent); + ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::PropertyCallbackInfo *propertyCallbackInfo, + const v8::Local context, ScriptContextPointer parent); + virtual ~ScriptContextV8Wrapper(); + + static ScriptContextV8Wrapper* unwrap(ScriptContext* val); + +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; + +public: // For use by V8-related functions + v8::Local toV8Value() const; + +private: // storage + const v8::FunctionCallbackInfo *_functionCallbackInfo; + const v8::PropertyCallbackInfo *_propertyCallbackInfo; + ScriptEngineV8* _engine; + v8::Persistent _context; + ScriptContextPointer _parentContext; + Q_DISABLE_COPY(ScriptContextV8Wrapper) +}; + +class ScriptFunctionContextV8Wrapper final : public ScriptFunctionContext { +public: // construction + ScriptFunctionContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context); + virtual ~ScriptFunctionContextV8Wrapper(); + +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 + ScriptEngineV8* _engine; + v8::Persistent _context; + Q_DISABLE_COPY(ScriptFunctionContextV8Wrapper) +}; + +#endif // hifi_ScriptContextV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptEngineLoggingV8.cpp b/libraries/script-engine/src/v8/ScriptEngineLoggingV8.cpp new file mode 100644 index 00000000000..728db5f22ab --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineLoggingV8.cpp @@ -0,0 +1,17 @@ +// +// ScriptEngineLoggingV8.cpp +// libraries/script-engine/src/v8 +// +// Created by Dale Glass on 06/03/2023 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngineLoggingV8.h" + +Q_LOGGING_CATEGORY(scriptengine_v8, "overte.scriptengine.v8") +Q_LOGGING_CATEGORY(scriptengine_module_v8, "overte.scriptengine.module.v8") +Q_LOGGING_CATEGORY(scriptengine_script_v8, "overte.scriptengine.script.v8") + diff --git a/libraries/script-engine/src/v8/ScriptEngineLoggingV8.h b/libraries/script-engine/src/v8/ScriptEngineLoggingV8.h new file mode 100644 index 00000000000..5035e538731 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineLoggingV8.h @@ -0,0 +1,25 @@ +// +// ScriptEngineLoggingV8.h +// libraries/script-engine/src/v8 +// +// Created by Dale Glass on 06/03/2023 +// Copyright 2023 Overte e.V. +// +// 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 +/// @{ + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(scriptengine_v8) +Q_DECLARE_LOGGING_CATEGORY(scriptengine_module_v8) +Q_DECLARE_LOGGING_CATEGORY(scriptengine_script_v8) + + + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp new file mode 100644 index 00000000000..3c7e3fb49fb --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -0,0 +1,1451 @@ +// +// ScriptEngineV8.cpp +// libraries/script-engine/src/v8 +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2013 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptEngineV8.h" + +#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 "../ScriptEngineCast.h" +#include "../ScriptValue.h" +#include "../ScriptManagerScriptingInterface.h" + +#include "ScriptContextV8Wrapper.h" +#include "ScriptObjectV8Proxy.h" +#include "ScriptProgramV8Wrapper.h" +#include "ScriptValueV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" +#include "ScriptValueIteratorV8Wrapper.h" + +static const int MAX_DEBUG_VALUE_LENGTH { 80 }; + +std::once_flag ScriptEngineV8::_v8InitOnceFlag; +QMutex ScriptEngineV8::_v8InitMutex; + +bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { + const QThread* currentThread = QThread::currentThread(); + if (currentThread == thread) { + return true; + } + qCCritical(scriptengine_v8) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") + .arg(method) + .arg(thread ? thread->objectName() : "(!thread)") + .arg(QThread::currentThread()->objectName()); + qCDebug(scriptengine_v8) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; + Q_ASSERT(false); + return false; +} + +ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + + 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().get() != 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(ScriptValueList({ other.property("message") })); + + // transfer over any existing properties + auto it = other.newIterator(); + 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 +ScriptValue ScriptEngineV8::checkScriptSyntax(ScriptProgramPointer program) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + ScriptSyntaxCheckResultPointer syntaxCheck = program->checkSyntax(); + if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { + auto err = globalObject().property("SyntaxError").construct(ScriptValueList({ newValue(syntaxCheck->errorMessage()) })); + err.setProperty("fileName", program->fileName()); + err.setProperty("lineNumber", syntaxCheck->errorLineNumber()); + err.setProperty("expressionBeginOffset", syntaxCheck->errorColumnNumber()); + err.setProperty("stack", syntaxCheck->errorBacktrace()); + { + 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, program->fileName(), line, column); + err.setProperty("formatted", message); + } + return err; + } + return undefinedValue(); +} + +#ifdef DEBUG_JS +void ScriptEngineV8::_debugDump(const QString& header, const V8ScriptValue& object, const QString& footer) { + // V8TODO + /*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; + } + V8ScriptValueIterator it(object); + while (it.hasNext()) { + it.next(); + qCDebug(shared) << it.name() << ":" << it.value().toString(); + } + if (!footer.isEmpty()) { + qCDebug(shared) << footer; + }*/ +} +#endif + +v8::Platform* ScriptEngineV8::getV8Platform() { + static std::unique_ptr platform = v8::platform::NewDefaultPlatform(); + return platform.get(); +} + +ScriptEngineV8::ScriptEngineV8(ScriptManager *manager) : ScriptEngine(manager), _evaluatingCounter(0) + //V8TODO _arrayBufferClass(new ArrayBufferClass(this)) +{ + _v8InitMutex.lock(); + std::call_once ( _v8InitOnceFlag, [ ]{ + v8::V8::InitializeExternalStartupData(""); + + // Experimentally determined that the maximum size that works on Linux with a stack size of 8192K is 8182. + // That would seem to be the overhead of our code and V8. + // + // Windows stacks are 1MB. + // + // Based on that, going with 256K for stacks for now. That seems like a reasonable value. + // We'll probably need a more complex system on the longer term, with configurable limits. + // Flags to try: + // --single-threaded is to check if it fixes random crashes + // --jitless - might improve debugging performance due to no JIT? + // --assert-types + + v8::V8::InitializeICU(); +#ifdef OVERTE_V8_MEMORY_DEBUG + v8::V8::SetFlagsFromString("--stack-size=256 --track_gc_object_stats --assert-types"); +#else + v8::V8::SetFlagsFromString("--stack-size=256"); +#endif + v8::Platform* platform = getV8Platform(); + v8::V8::InitializePlatform(platform); + v8::V8::Initialize(); qCDebug(scriptengine_v8) << "V8 platform initialized"; + } ); + _v8InitMutex.unlock(); + qCDebug(scriptengine_v8) << "Creating new script engine"; + { + v8::Isolate::CreateParams isolateParams; + isolateParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + _v8Isolate = v8::Isolate::New(isolateParams); + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = v8::Context::New(_v8Isolate); + Q_ASSERT(!context.IsEmpty()); + v8::Context::Scope contextScope(context); + _contexts.append(std::make_shared(this,context, ScriptContextPointer())); + + V8ScriptValue nullScriptValue(this, v8::Null(_v8Isolate)); + _nullValue = ScriptValue(new ScriptValueV8Wrapper(this, nullScriptValue)); + + V8ScriptValue undefined(this, v8::Undefined(_v8Isolate)); + _undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, undefined)); + + registerSystemTypes(); + + // V8TODO: dispose of isolate on ScriptEngineV8 destruction + // V8TODO: + //QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); + } + + //if (_scriptManager) { + // V8TODO: port to V8 + /*connect(this, &QScriptEngine::signalHandlerException, this, [this](const V8ScriptValue& 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 + V8ScriptValue thrown = makeError(exception); + emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); + } + }, Qt::DirectConnection);*/ + //} +} + +ScriptEngineV8::~ScriptEngineV8() { + deleteUnusedValueWrappers(); +} + +void ScriptEngineV8::perManagerLoopIterationCleanup() { + deleteUnusedValueWrappers(); +} + +void ScriptEngineV8::deleteUnusedValueWrappers() { + while (!_scriptValueWrappersToDelete.empty()) { + auto wrapper = _scriptValueWrappersToDelete.dequeue(); + wrapper->release(); + } +} + +void ScriptEngineV8::registerEnum(const QString& enumName, QMetaEnum newEnum) { + if (!newEnum.isValid()) { + qCCritical(scriptengine_v8) << "registerEnum called on invalid enum with name " << enumName; + return; + } + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + for (int i = 0; i < newEnum.keyCount(); i++) { + const char* keyName = newEnum.key(i); + QString fullName = enumName + "." + keyName; + registerValue(fullName, V8ScriptValue(this, v8::Integer::New(_v8Isolate, newEnum.keyToValue(keyName)))); + } +} + +void ScriptEngineV8::registerValue(const QString& valueName, V8ScriptValue value) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + QMetaObject::invokeMethod(this, "registerValue", + Q_ARG(const QString&, valueName), + Q_ARG(V8ScriptValue, value)); + return; + } + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = getContext(); + v8::Context::Scope contextScope(getContext()); + QStringList pathToValue = valueName.split("."); + int partsToGo = pathToValue.length(); + v8::Local partObject = context->Global(); + + for (const auto& pathPart : pathToValue) { + partsToGo--; + v8::Local pathPartV8 = v8::String::NewFromUtf8(_v8Isolate, pathPart.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + v8::Local currentPath; + bool createProperty = false; + if (!partObject->Get(context, pathPartV8).ToLocal(¤tPath)) { + createProperty = true; + } + if (currentPath->IsUndefined()) { + createProperty = true; + } + if (createProperty) { + if (partsToGo > 0) { + v8::Local partValue = v8::Object::New(_v8Isolate); + if (!partObject->Set(context, pathPartV8, partValue).FromMaybe(false)) { + Q_ASSERT(false); + } + } else { + if (!partObject->Set(context, pathPartV8, value.constGet()).FromMaybe(false)) { + Q_ASSERT(false); + } + } + } + + v8::Local child; + if (!partObject->Get(context, pathPartV8).ToLocal(&child)) { + Q_ASSERT(false); + } + if (partsToGo > 0) { + if (!child->IsObject()) { + QString details = *v8::String::Utf8Value(_v8Isolate, child->ToDetailString(context).ToLocalChecked()); + qCDebug(scriptengine_v8) << "ScriptEngineV8::registerValue: Part of path is not an object: " << pathPart << " details: " << details; + Q_ASSERT(false); + } + partObject = v8::Local::Cast(child); + } + } +} + +void ScriptEngineV8::registerGlobalObject(const QString& name, QObject* object, ScriptEngine::ValueOwnership) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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_v8) << "ScriptEngineV8::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + Q_ASSERT(_v8Isolate->IsCurrent()); + v8::Local context = getContext(); + v8::Context::Scope contextScope(context); + v8::Local v8GlobalObject = getContext()->Global(); + v8::Local v8Name = v8::String::NewFromUtf8(_v8Isolate, name.toStdString().c_str()).ToLocalChecked(); + + if (!v8GlobalObject->Get(getContext(), v8Name).IsEmpty()) { + if (object) { + V8ScriptValue value = ScriptObjectV8Proxy::newQObject(this, object, ScriptEngine::QtOwnership); + if(!v8GlobalObject->Set(getContext(), v8Name, value.get()).FromMaybe(false)) { + Q_ASSERT(false); + } + } else { + if(!v8GlobalObject->Set(getContext(), v8Name, v8::Null(_v8Isolate)).FromMaybe(false)) { + Q_ASSERT(false); + } + } + } +} + +void ScriptEngineV8::registerFunction(const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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_v8) << "ScriptEngineV8::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + auto scriptFun = newFunction(functionSignature, numArguments); + + globalObject().setProperty(name, scriptFun); +} + +void ScriptEngineV8::registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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_v8) << "ScriptEngineV8::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; +#endif + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + ScriptValue object = globalObject().property(parent); + if (object.isValid()) { + ScriptValue scriptFun = newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } +} + +void ScriptEngineV8::registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, + ScriptEngine::FunctionSignature setter, const QString& parent) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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_v8) << "ScriptEngineV8::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; +#endif + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + ScriptValue setterFunction = newFunction(setter, 1); + ScriptValue getterFunction = newFunction(getter); + V8ScriptValue unwrappedGetter = ScriptValueV8Wrapper::fullUnwrap(this, getterFunction); + V8ScriptValue unwrappedSetter = ScriptValueV8Wrapper::fullUnwrap(this, setterFunction); + v8::PropertyDescriptor propertyDescriptor(unwrappedGetter.get(), unwrappedSetter.get()); + + if (!parent.isNull() && !parent.isEmpty()) { + ScriptValue object = globalObject().property(parent); + if (object.isValid()) { + V8ScriptValue v8parent = ScriptValueV8Wrapper::fullUnwrap(this, object); + Q_ASSERT(v8parent.get()->IsObject()); + v8::Local v8ParentObject = v8::Local::Cast(v8parent.get()); + v8::Local v8propertyName = + v8::String::NewFromUtf8(_v8Isolate, name.toStdString().c_str()).ToLocalChecked(); + v8::Local v8ObjectToSetProperty; + ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(V8ScriptValue(this, v8ParentObject)); + // If object is ScriptObjectV8Proxy, then setting property needs to be handled differently + if (proxy) { + v8ObjectToSetProperty = v8ParentObject->GetInternalField(2).As(); + } else { + v8ObjectToSetProperty = v8ParentObject; + } + if (!v8ObjectToSetProperty->DefineProperty(getContext(), v8propertyName, propertyDescriptor).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "DefineProperty failed for registerGetterSetter \"" << name << "\" for parent: \"" + << parent << "\""; + } + } else { + qCDebug(scriptengine_v8) << "Parent object \"" << parent << "\" for registerGetterSetter \"" << name + << "\" is not valid: "; + } + } else { + v8::Local v8propertyName = + v8::String::NewFromUtf8(_v8Isolate, name.toStdString().c_str()).ToLocalChecked(); + if (!getContext()->Global()->DefineProperty(getContext(), v8propertyName, propertyDescriptor).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "DefineProperty failed for registerGetterSetter \"" << name << "\" for global object"; + } + } +} + +v8::Local ScriptEngineV8::getContext() { + v8::EscapableHandleScope handleScope(_v8Isolate); + Q_ASSERT(!_contexts.isEmpty()); + return handleScope.Escape(_contexts.last().get()->toV8Value()); +} + +const v8::Local ScriptEngineV8::getConstContext() const { + v8::EscapableHandleScope handleScope(_v8Isolate); + Q_ASSERT(!_contexts.isEmpty()); + return handleScope.Escape(_contexts.last().get()->toV8Value()); +} + +// Stored objects are used to create global objects for evaluateInClosure +void ScriptEngineV8::storeGlobalObjectContents() { + if (areGlobalObjectContentsStored) { + return; + } + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + auto context = getContext(); + v8::Context::Scope contextScope(context); + v8::Local globalMemberObjects = v8::Object::New(_v8Isolate); + + auto globalMemberNames = context->Global()->GetPropertyNames(context).ToLocalChecked(); + for (size_t i = 0; i < globalMemberNames->Length(); i++) { + auto name = globalMemberNames->Get(context, i).ToLocalChecked(); + if(!globalMemberObjects->Set(context, name, context->Global()->Get(context, name).ToLocalChecked()).FromMaybe(false)) { + Q_ASSERT(false); + } + } + + _globalObjectContents.Reset(_v8Isolate, globalMemberObjects); + qCDebug(scriptengine_v8) << "ScriptEngineV8::storeGlobalObjectContents: " << globalMemberNames->Length() << " objects stored"; + areGlobalObjectContentsStored = true; +} + +ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, + const ScriptProgramPointer& _program) { + PROFILE_RANGE(script, "evaluateInClosure"); + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + _evaluatingCounter++; + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + storeGlobalObjectContents(); + + v8::Local closureObject; + v8::Local closureGlobal; + ScriptValueV8Wrapper* unwrappedClosure; + ScriptProgramV8Wrapper* unwrappedProgram; + + { + v8::Context::Scope contextScope(getContext()); + unwrappedProgram = ScriptProgramV8Wrapper::unwrap(_program); + if (unwrappedProgram == nullptr) { + _evaluatingCounter--; + qCDebug(scriptengine_v8) << "Cannot unwrap program for closure"; + Q_ASSERT(false); + return nullValue(); + } + + const auto fileName = unwrappedProgram->fileName(); + const auto shortName = QUrl(fileName).fileName(); + + unwrappedClosure = ScriptValueV8Wrapper::unwrap(_closure); + if (unwrappedClosure == nullptr) { + _evaluatingCounter--; + qCDebug(scriptengine_v8) << "Cannot unwrap closure"; + Q_ASSERT(false); + return nullValue(); + } + + const V8ScriptValue& closure = unwrappedClosure->toV8Value(); + if (!closure.constGet()->IsObject()) { + _evaluatingCounter--; + qCDebug(scriptengine_v8) << "Unwrapped closure is not an object"; + Q_ASSERT(false); + return nullValue(); + } + Q_ASSERT(closure.constGet()->IsObject()); + closureObject = v8::Local::Cast(closure.constGet()); + qCDebug(scriptengine_v8) << "Closure object members:" << scriptValueDebugListMembersV8(closure); + v8::Local testObject = v8::Object::New(_v8Isolate); + if(!testObject->Set(getContext(), v8::String::NewFromUtf8(_v8Isolate, "test_value").ToLocalChecked(), closureObject).FromMaybe(false)) { + Q_ASSERT(false); + } + qCDebug(scriptengine_v8) << "Test object members:" << scriptValueDebugListMembersV8(V8ScriptValue(this, testObject)); + + if (!closureObject->Get(closure.constGetContext(), v8::String::NewFromUtf8(_v8Isolate, "global").ToLocalChecked()) + .ToLocal(&closureGlobal)) { + _evaluatingCounter--; + qCDebug(scriptengine_v8) << "Cannot get global from unwrapped closure"; + Q_ASSERT(false); + return nullValue(); + } + } + v8::Local closureContext; + + closureContext = v8::Context::New(_v8Isolate); + pushContext(closureContext); + + ScriptValue result; + // V8TODO: a lot of functions rely on _v8Context, which was not updated here + // It might cause trouble + { + v8::Context::Scope contextScope(closureContext); + //const V8ScriptValue& closure = unwrappedClosure->toV8Value(); + if (!unwrappedProgram->compile()) { + qCDebug(scriptengine_v8) << "Can't compile script for evaluating in closure"; + Q_ASSERT(false); + popContext(); + return nullValue(); + } + const V8ScriptProgram& program = unwrappedProgram->toV8Value(); + + v8::Local thiz; +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + { + v8::TryCatch tryCatch(getIsolate()); + // Since V8 cannot use arbitrary object as global object, objects from main global need to be copied to closure's global object + auto globalObjectContents = _globalObjectContents.Get(_v8Isolate); + auto globalMemberNames = globalObjectContents->GetPropertyNames(globalObjectContents->CreationContext()).ToLocalChecked(); + for (size_t i = 0; i < globalMemberNames->Length(); i++) { + auto name = globalMemberNames->Get(closureContext, i).ToLocalChecked(); + if(!closureContext->Global()->Set(closureContext, name, globalObjectContents->Get(globalObjectContents->CreationContext(), name).ToLocalChecked()).FromMaybe(false)) { + Q_ASSERT(false); + } + } + qCDebug(scriptengine_v8) << "ScriptEngineV8::evaluateInClosure: " << globalMemberNames->Length() << " objects added to global"; + + // Objects from closure need to be copied to global object too + // V8TODO: I'm not sure which context to use with Get + auto closureMemberNames = closureObject->GetPropertyNames(closureContext).ToLocalChecked(); + for (size_t i = 0; i < closureMemberNames->Length(); i++) { + auto name = closureMemberNames->Get(closureContext, i).ToLocalChecked(); + if(!closureContext->Global()->Set(closureContext, name, closureObject->Get(closureContext, name).ToLocalChecked()).FromMaybe(false)) { + Q_ASSERT(false); + } + } + // "Script" API is context-dependent, so it needs to be recreated for each new context + registerGlobalObject("Script", new ScriptManagerScriptingInterface(_manager), ScriptEngine::ScriptOwnership); + + auto maybeResult = program.constGet()->GetUnboundScript()->BindToCurrentContext()->Run(closureContext); + v8::Local v8Result; + if (!maybeResult.ToLocal(&v8Result)) { + v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); + QString errorMessage = QString(*utf8Value); + qCWarning(scriptengine_v8) << __FUNCTION__ << "---------- hasCaught:" << errorMessage; + qCWarning(scriptengine_v8) << __FUNCTION__ << "---------- tryCatch details:" << formatErrorMessageFromTryCatch(tryCatch); + } + + if (hasUncaughtException()) { +#ifdef DEBUG_JS_EXCEPTIONS + qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); + err.setProperty("_result", result); +#endif + result = nullValue(); + } else { + result = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, v8Result))); + } + } +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + popContext(); + } + + _evaluatingCounter--; + return result; +} + +ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& fileName) { + + if (QThread::currentThread() != thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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; + } + // Compile and check syntax + Q_ASSERT(!_v8Isolate->IsDead()); + _evaluatingCounter++; + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked()); + v8::Local script; + { + v8::TryCatch tryCatch(getIsolate()); + if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + setUncaughtException(tryCatch, "Error while compiling script"); + _evaluatingCounter--; + return nullValue(); + } + } + + v8::Local result; + v8::TryCatch tryCatchRun(getIsolate()); + if (!script->Run(getContext()).ToLocal(&result)) { + Q_ASSERT(tryCatchRun.HasCaught()); + auto runError = tryCatchRun.Message(); + ScriptValue errorValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get()))); + qCDebug(scriptengine_v8) << "Running script: \"" << fileName << "\" " << formatErrorMessageFromTryCatch(tryCatchRun); + setUncaughtException(tryCatchRun, "script evaluation"); + + _evaluatingCounter--; + return errorValue; + } + V8ScriptValue resultValue(this, result); + _evaluatingCounter--; + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue))); +} + + +void ScriptEngineV8::setUncaughtEngineException(const QString &reason, const QString& info) { + auto ex = std::make_shared(reason, info); + setUncaughtException(ex); +} + +void ScriptEngineV8::setUncaughtException(const v8::TryCatch &tryCatch, const QString& info) { + if (!tryCatch.HasCaught()) { + qCWarning(scriptengine_v8) << "setUncaughtException called without exception"; + clearExceptions(); + return; + } + + auto ex = std::make_shared(); + ex->additionalInfo = info; + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + QString result(""); + + QString errorMessage = ""; + QString errorBacktrace = ""; + v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get()); + + ex->errorMessage = QString(*utf8Value); + + auto exceptionValue = tryCatch.Exception(); + ex->thrownValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, exceptionValue))); + + + v8::Local exceptionMessage = tryCatch.Message(); + if (!exceptionMessage.IsEmpty()) { + ex->errorLine = exceptionMessage->GetLineNumber(getContext()).FromJust(); + ex->errorColumn = exceptionMessage->GetStartColumn(getContext()).FromJust(); + v8::Local backtraceV8String; + if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) { + if (backtraceV8String->IsString()) { + if (v8::Local::Cast(backtraceV8String)->Length() > 0) { + v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); + QString errorBacktrace = *backtraceUtf8Value; + ex->backtrace = errorBacktrace.split("\n"); + + } + } + } + } + + setUncaughtException(ex); +} + +void ScriptEngineV8::setUncaughtException(std::shared_ptr uncaughtException) { + qCDebug(scriptengine_v8) << "Emitting exception:" << uncaughtException; + _uncaughtException = uncaughtException; + + auto copy = uncaughtException->clone(); + emit exception(copy); +} + + +QString ScriptEngineV8::formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + QString result(""); + int errorColumnNumber = 0; + int errorLineNumber = 0; + QString errorMessage = ""; + QString errorBacktrace = ""; + v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Message()->Get()); + errorMessage = QString(*utf8Value); + v8::Local exceptionMessage = tryCatch.Message(); + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(getContext()).FromJust(); + errorColumnNumber = exceptionMessage->GetStartColumn(getContext()).FromJust(); + v8::Local backtraceV8String; + if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String)) { + if (backtraceV8String->IsString()) { + if (v8::Local::Cast(backtraceV8String)->Length() > 0) { + v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); + errorBacktrace = *backtraceUtf8Value; + } + } + } + QTextStream resultStream(&result); + resultStream << "failed on line " << errorLineNumber << " column " << errorColumnNumber << " with message: \"" << errorMessage <<"\" backtrace: " << errorBacktrace; + } + return result; +} + +v8::Local ScriptEngineV8::getObjectProxyTemplate() { + v8::EscapableHandleScope handleScope(_v8Isolate); + if (_objectProxyTemplate.IsEmpty()) { + auto objectTemplate = v8::ObjectTemplate::New(_v8Isolate); + objectTemplate->SetInternalFieldCount(3); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(ScriptObjectV8Proxy::v8Get, ScriptObjectV8Proxy::v8Set, nullptr, nullptr, ScriptObjectV8Proxy::v8GetPropertyNames)); + _objectProxyTemplate.Reset(_v8Isolate, objectTemplate); + } + + return handleScope.Escape(_objectProxyTemplate.Get(_v8Isolate)); +} + +v8::Local ScriptEngineV8::getMethodDataTemplate() { + v8::EscapableHandleScope handleScope(_v8Isolate); + if (_methodDataTemplate.IsEmpty()) { + auto methodDataTemplate = v8::ObjectTemplate::New(_v8Isolate); + methodDataTemplate->SetInternalFieldCount(2); + _methodDataTemplate.Reset(_v8Isolate, methodDataTemplate); + } + + return handleScope.Escape(_methodDataTemplate.Get(_v8Isolate)); +} + +v8::Local ScriptEngineV8::getFunctionDataTemplate() { + v8::EscapableHandleScope handleScope(_v8Isolate); + if (_functionDataTemplate.IsEmpty()) { + auto functionDataTemplate = v8::ObjectTemplate::New(_v8Isolate); + functionDataTemplate->SetInternalFieldCount(2); + _functionDataTemplate.Reset(_v8Isolate, functionDataTemplate); + } + + return handleScope.Escape(_functionDataTemplate.Get(_v8Isolate)); +} + +v8::Local ScriptEngineV8::getVariantDataTemplate() { + v8::EscapableHandleScope handleScope(_v8Isolate); + if (_variantDataTemplate.IsEmpty()) { + auto variantDataTemplate = v8::ObjectTemplate::New(_v8Isolate); + variantDataTemplate->SetInternalFieldCount(2); + _variantDataTemplate.Reset(_v8Isolate, variantDataTemplate); + } + + return handleScope.Escape(_variantDataTemplate.Get(_v8Isolate)); +} + +v8::Local ScriptEngineV8::getVariantProxyTemplate() { + v8::EscapableHandleScope handleScope(_v8Isolate); + if (_variantProxyTemplate.IsEmpty()) { + auto variantProxyTemplate = v8::ObjectTemplate::New(_v8Isolate); + variantProxyTemplate->SetInternalFieldCount(2); + variantProxyTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(ScriptVariantV8Proxy::v8Get, ScriptVariantV8Proxy::v8Set, nullptr, nullptr, ScriptVariantV8Proxy::v8GetPropertyNames)); + _variantProxyTemplate.Reset(_v8Isolate, variantProxyTemplate); + } + + return handleScope.Escape(_variantProxyTemplate.Get(_v8Isolate)); +} + + +ScriptContextV8Pointer ScriptEngineV8::pushContext(v8::Local context) { + v8::HandleScope handleScope(_v8Isolate); + Q_ASSERT(!_contexts.isEmpty()); + ScriptContextPointer parent = _contexts.last(); + _contexts.append(std::make_shared(this, context, parent)); + v8::Context::Scope contextScope(context); + /*static volatile int debug_context_id = 1; + if (!context->Global()->Set(context, v8::String::NewFromUtf8(_v8Isolate, "debug_context_id").ToLocalChecked(), v8::Integer::New(_v8Isolate, debug_context_id)).FromMaybe(false)) { + Q_ASSERT(false); + } + debug_context_id++;*/ + return _contexts.last(); +} + +void ScriptEngineV8::popContext() { + Q_ASSERT(!_contexts.isEmpty()); + _contexts.pop_back(); +} + +Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& program) { + + if (QThread::currentThread() != thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine_v8) << "*** WARNING *** ScriptEngineV8::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; + } + _evaluatingCounter++; + ScriptValue errorValue; + ScriptValue resultValue; + bool hasFailed = false; + { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program); + if (!unwrapped) { + setUncaughtEngineException("Could not unwrap program", "Compile error"); + hasFailed = true; + } + + if(!hasFailed) { + ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax(); + if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) { + setUncaughtEngineException(syntaxCheck->errorMessage(), "Compile error"); + hasFailed = true; + } + } + + v8::Local result; + if(!hasFailed) { + const V8ScriptProgram& v8Program = unwrapped->toV8Value(); + + v8::TryCatch tryCatchRun(getIsolate()); + if (!v8Program.constGet()->Run(getContext()).ToLocal(&result)) { + Q_ASSERT(tryCatchRun.HasCaught()); + auto runError = tryCatchRun.Message(); + errorValue = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(this, runError->Get()))); + raiseException(errorValue, "evaluation error"); + hasFailed = true; + } else { + // V8TODO this is just to check if run will always return false for uncaught exception + Q_ASSERT(!tryCatchRun.HasCaught()); + } + } + if(!hasFailed) { + V8ScriptValue resultValueV8(this, result); + resultValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValueV8))); + } + } + _evaluatingCounter--; + if (hasFailed) { + return errorValue; + } else { + return resultValue; + } +} + + +void ScriptEngineV8::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) + // V8TODO: it seems to be broken in V8 branch on Windows for some reason + //reportAdditionalMemoryCost(deltaSize); +#endif + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ScriptEngine implementation + +ScriptValue ScriptEngineV8::globalObject() { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getConstContext()); + V8ScriptValue global(this, getConstContext()->Global());// = QScriptEngine::globalObject(); // can't cache the value as it may change + return ScriptValue(new ScriptValueV8Wrapper(const_cast(this), std::move(global))); +} + +ScriptValue ScriptEngineV8::newArray(uint length) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(this, v8::Array::New(_v8Isolate, static_cast(length))); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newArrayBuffer(const QByteArray& message) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + std::shared_ptr backingStore(v8::ArrayBuffer::NewBackingStore(_v8Isolate, message.size())); + std::memcpy(backingStore.get()->Data(), message.constData(), message.size()); + auto arrayBuffer = v8::ArrayBuffer::New(_v8Isolate, backingStore); + //V8TODO: this needs to be finished and tested + /*V8ScriptValue data = QScriptEngine::newVariant(QVariant::fromValue(message)); + V8ScriptValue ctor = QScriptEngine::globalObject().property("ArrayBuffer"); + auto array = qscriptvalue_cast(ctor.data()); + if (!array) { + return undefinedValue(); + }*/ + V8ScriptValue result(this, arrayBuffer);//QScriptEngine::newObject(array, data); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newObject() { + ScriptValue result; + { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue resultV8 = V8ScriptValue(this, v8::Object::New(_v8Isolate)); + result = ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultV8))); + } + return result; +} + +ScriptValue ScriptEngineV8::newMethod(QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(ScriptMethodV8Proxy::newMethod(this, object, lifetime, metas, numMaxParams)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptProgramPointer ScriptEngineV8::newProgram(const QString& sourceCode, const QString& fileName) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + return std::make_shared(this, sourceCode, fileName); +} + +ScriptValue ScriptEngineV8::newQObject(QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result = ScriptObjectV8Proxy::newQObject(this, object, ownership, options); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(bool value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(this, v8::Boolean::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(int value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(this, v8::Integer::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(uint value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(this, v8::Uint32::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(double value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result(this, v8::Number::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const QString& value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value.toStdString().c_str(), v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(this, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const QLatin1String& value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value.latin1(), v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(this, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const char* value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value, v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(this, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newVariant(const QVariant& value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + V8ScriptValue result = castVariantToValue(value); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::nullValue() { + return _nullValue; +} + +ScriptValue ScriptEngineV8::undefinedValue() { + return _undefinedValue; +} + +void ScriptEngineV8::abortEvaluation() { + //V8TODO + //QScriptEngine::abortEvaluation(); +} + +void ScriptEngineV8::clearExceptions() { + _uncaughtException.reset(); +} + +ScriptContext* ScriptEngineV8::currentContext() const { + // V8TODO: add FunctionCallbackInfo or PropertyCallbackInfo when necessary + // Is it needed? + return _contexts.last().get(); +} + +bool ScriptEngineV8::hasUncaughtException() const { + return _uncaughtException != nullptr; +} + +bool ScriptEngineV8::isEvaluating() const { + //return QScriptEngine::isEvaluating(); + return _evaluatingCounter > 0; + return false; +} + +ScriptValue ScriptEngineV8::newFunction(ScriptEngine::FunctionSignature fun, int length) { + //V8TODO is callee() used for anything? + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + auto v8FunctionCallback = [](const v8::FunctionCallbackInfo& info) { + //V8TODO: is using GetCurrentContext ok, or context wrapper needs to be added? + v8::HandleScope handleScope(info.GetIsolate()); + auto context = info.GetIsolate()->GetCurrentContext(); + v8::Context::Scope contextScope(context); + Q_ASSERT(info.Data()->IsObject()); + auto object = v8::Local::Cast(info.Data()); + Q_ASSERT(object->InternalFieldCount() == 2); + auto function = reinterpret_cast + (object->GetAlignedPointerFromInternalField(0)); + ScriptEngineV8 *scriptEngine = reinterpret_cast + (object->GetAlignedPointerFromInternalField(1)); + ContextScopeV8 contextScopeV8(scriptEngine); + ScriptContextV8Wrapper scriptContext(scriptEngine, &info, scriptEngine->getContext(), scriptEngine->currentContext()->parentContext()); + ScriptContextGuard scriptContextGuard(&scriptContext); + ScriptValue result = function(&scriptContext, scriptEngine); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(result); + if (unwrapped) { + info.GetReturnValue().Set(unwrapped->toV8Value().constGet()); + } + }; + auto functionDataTemplate = getFunctionDataTemplate(); + auto functionData = functionDataTemplate->NewInstance(getContext()).ToLocalChecked(); + functionData->SetAlignedPointerInInternalField(0, reinterpret_cast(fun)); + functionData->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); + auto v8Function = v8::Function::New(getContext(), v8FunctionCallback, functionData, length).ToLocalChecked(); + V8ScriptValue result(this, v8Function); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +//V8TODO +void ScriptEngineV8::setObjectName(const QString& name) { + QObject::setObjectName(name); +} + +bool ScriptEngineV8::setProperty(const char* name, const QVariant& value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::Local global = getContext()->Global(); + auto v8Name = v8::String::NewFromUtf8(getIsolate(), name).ToLocalChecked(); + V8ScriptValue v8Value = castVariantToValue(value); + return global->Set(getContext(), v8Name, v8Value.get()).FromMaybe(false); +} + +void ScriptEngineV8::setProcessEventsInterval(int interval) { + //V8TODO + //QScriptEngine::setProcessEventsInterval(interval); +} + +QThread* ScriptEngineV8::thread() const { + return QObject::thread(); +} + +void ScriptEngineV8::setThread(QThread* thread) { + if (_v8Isolate->IsCurrent()) { + _v8Isolate->Exit(); + qCDebug(scriptengine_v8) << "Script engine " << objectName() << " exited isolate"; + } + Q_ASSERT(QObject::thread() == QThread::currentThread()); + moveToThread(thread); + qCDebug(scriptengine_v8) << "Moved script engine " << objectName() << " to different thread"; +} + +std::shared_ptr ScriptEngineV8::uncaughtException() const { + if (_uncaughtException) { + return _uncaughtException->clone(); + } else { + return std::shared_ptr(); + } +} + +bool ScriptEngineV8::raiseException(const QString& error, const QString &reason) { + return raiseException(newValue(error), reason); +} + +bool ScriptEngineV8::raiseException(const ScriptValue& exception, const QString &reason) { + //V8TODO Im not sure how to finish these + qCCritical(scriptengine_v8) << "Script exception occurred: " << exception.toString(); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(exception); + ScriptValue qException = unwrapped ? exception : newVariant(exception.toVariant()); + + return raiseException(ScriptValueV8Wrapper::fullUnwrap(this, exception)); +} + +bool ScriptEngineV8::raiseException(const V8ScriptValue& exception) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + auto trace = v8::StackTrace::CurrentStackTrace(_v8Isolate, 2); + if (trace->GetFrameCount() > 0) { + // we have an active context / JS stack frame so throw the exception per usual + //currentContext()->throwValue(makeError(exception)); + auto thrown = makeError(ScriptValue(new ScriptValueV8Wrapper(this, exception))); + auto thrownV8 = ScriptValueV8Wrapper::fullUnwrap(this, thrown); + _v8Isolate->ThrowException(thrownV8.get()); + return true; + } else if (_manager) { + // 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 + ScriptValue thrown = makeError(ScriptValue(new ScriptValueV8Wrapper(this, exception))); + auto scriptRuntimeException = std::make_shared(); + ScriptValue message = thrown.property("stack"); //This contains more details along with the error message + scriptRuntimeException->errorMessage = message.toString(); + scriptRuntimeException->thrownValue = thrown; + emit _manager->unhandledException(scriptRuntimeException); + } + + return false; +} + + +ScriptValue ScriptEngineV8::create(int type, const void* ptr) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + QVariant variant(type, ptr); + V8ScriptValue scriptValue = castVariantToValue(variant); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(scriptValue))); +} + +QVariant ScriptEngineV8::convert(const ScriptValue& value, int typeId) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(value); + if (unwrapped == nullptr) { + return QVariant(); + } + + QVariant var; + if (!castValueToVariant(unwrapped->toV8Value(), 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; + return QVariant(); +} + +void ScriptEngineV8::compileTest() { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + v8::Local script; + v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(),"test").ToLocalChecked()); + if (v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), "print(\"hello world\");").ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + qCDebug(scriptengine_v8) << "Compile test successful"; + } else { + qCDebug(scriptengine_v8) << "Compile test failed"; + Q_ASSERT(false); + } +} + +QString ScriptEngineV8::scriptValueDebugDetails(const ScriptValue &value) { + V8ScriptValue v8Value = ScriptValueV8Wrapper::fullUnwrap(this, value); + return scriptValueDebugDetailsV8(v8Value); +} + +QString ScriptEngineV8::scriptValueDebugListMembers(const ScriptValue &value) { + V8ScriptValue v8Value = ScriptValueV8Wrapper::fullUnwrap(this, value); + return scriptValueDebugDetailsV8(v8Value); +} + +QString ScriptEngineV8::scriptValueDebugListMembersV8(const V8ScriptValue &v8Value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + QString membersString(""); + if (v8Value.constGet()->IsObject()) { + v8::Local membersStringV8; + v8::Local object = v8::Local::Cast(v8Value.constGet()); + auto names = object->GetPropertyNames(getContext()).ToLocalChecked(); + if (v8::JSON::Stringify(getContext(), names).ToLocal(&membersStringV8)) { + membersString = QString(*v8::String::Utf8Value(_v8Isolate, membersStringV8)); + } + membersString = QString(*v8::String::Utf8Value(_v8Isolate, membersStringV8)); + } else { + membersString = QString(" Is not an object"); + } + return membersString; +} + +QString ScriptEngineV8::scriptValueDebugDetailsV8(const V8ScriptValue &v8Value) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Context::Scope contextScope(getContext()); + + QString parentValueQString(""); + v8::Local parentValueString; + if (v8Value.constGet()->ToDetailString(getContext()).ToLocal(&parentValueString)) { + parentValueQString = QString(*v8::String::Utf8Value(_v8Isolate, parentValueString)); + } + QString JSONQString; + v8::Local JSONString; + if (v8::JSON::Stringify(getContext(), v8Value.constGet()).ToLocal(&JSONString)) { + JSONQString = QString(*v8::String::Utf8Value(_v8Isolate, JSONString)); + } + return parentValueQString + QString(" JSON: ") + JSONQString; +} + +void ScriptEngineV8::logBacktrace(const QString &title) { + QStringList backtrace = currentContext()->backtrace(); + qCDebug(scriptengine_v8) << title; + for (int n = 0; n < backtrace.length(); n++) { + qCDebug(scriptengine_v8) << backtrace[n]; + } +} + +QStringList ScriptEngineV8::getCurrentScriptURLs() const { + auto isolate = _v8Isolate; + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_v8Isolate->GetCurrentContext()); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 100); + QStringList scriptURLs; + //V8TODO nicer formatting + for (int i = 0; i < stackTrace->GetFrameCount(); i++) { + v8::Local stackFrame = stackTrace->GetFrame(isolate, i); + scriptURLs.append(QString(*v8::String::Utf8Value(isolate, stackFrame->GetScriptNameOrSourceURL()))); + } + return scriptURLs; +} + +ScriptEngineMemoryStatistics ScriptEngineV8::getMemoryUsageStatistics() { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + ScriptEngineMemoryStatistics statistics; + v8::HeapStatistics heapStatistics; + _v8Isolate->GetHeapStatistics(&heapStatistics); + statistics.totalHeapSize = heapStatistics.total_available_size(); + statistics.usedHeapSize = heapStatistics.used_heap_size(); + statistics.totalAvailableSize = heapStatistics.total_available_size(); + statistics.totalGlobalHandlesSize = heapStatistics.total_global_handles_size(); + statistics.usedGlobalHandlesSize = heapStatistics.used_global_handles_size(); +#ifdef OVERTE_V8_MEMORY_DEBUG + statistics.scriptValueCount = scriptValueCount; + statistics.scriptValueProxyCount = scriptValueProxyCount; + statistics.qObjectCount = _qobjectWrapperMapV8.size(); +#endif + return statistics; +} + +void ScriptEngineV8::startCollectingObjectStatistics() { + auto heapProfiler = _v8Isolate->GetHeapProfiler(); + heapProfiler->StartTrackingHeapObjects(); +} + +void ScriptEngineV8::dumpHeapObjectStatistics() { + // V8TODO: this is not very elegant, but very convenient + QFile dumpFile("/tmp/heap_objectStatistics_dump.csv"); + if (!dumpFile.open(QFile::WriteOnly | QFile::Truncate)) { + return; + } + QTextStream dump(&dumpFile); + size_t objectTypeCount = _v8Isolate->NumberOfTrackedHeapObjectTypes(); + for (size_t i = 0; i < objectTypeCount; i++) { + v8::HeapObjectStatistics statistics; + if (_v8Isolate->GetHeapObjectStatisticsAtLastGC(&statistics, i)) { + dump << statistics.object_type() << " " << statistics.object_sub_type() << " " << statistics.object_count() << " " + << statistics.object_size() << "\n"; + } + } +} + +ContextScopeV8::ContextScopeV8(ScriptEngineV8 *engine) : + _engine(engine) { + Q_ASSERT(engine); + _isContextChangeNeeded = engine->getContext() != engine->getIsolate()->GetCurrentContext(); + if (_isContextChangeNeeded) { + engine->pushContext(engine->getIsolate()->GetCurrentContext()); + } +} + +ContextScopeV8::~ContextScopeV8() { + if (_isContextChangeNeeded) { + _engine->popContext(); + } +} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h new file mode 100644 index 00000000000..68907b14a83 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -0,0 +1,293 @@ +// +// ScriptEngineV8.h +// libraries/script-engine/src/qtscript +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptEngineV8_h +#define hifi_ScriptEngineV8_h + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libplatform/libplatform.h" +#include "v8.h" + +#include "../ScriptEngine.h" +#include "../ScriptManager.h" +#include "../ScriptException.h" +//#include "V8Types.h" + +#include "ArrayBufferClass.h" + +class ScriptContextV8Wrapper; +class ScriptEngineV8; +class ScriptManager; +class ScriptObjectV8Proxy; +class ScriptMethodV8Proxy; +class ScriptValueV8Wrapper; + +template class V8ScriptValueTemplate; +typedef V8ScriptValueTemplate V8ScriptValue; +typedef V8ScriptValueTemplate V8ScriptProgram; + +using ScriptContextV8Pointer = std::shared_ptr; + +const double GARBAGE_COLLECTION_TIME_LIMIT_S = 1.0; + +Q_DECLARE_METATYPE(ScriptEngine::FunctionSignature) + +/// [V8] Implements ScriptEngine for V8 and translates calls for QScriptEngine +class ScriptEngineV8 final : public ScriptEngine, + public std::enable_shared_from_this { + Q_OBJECT + +public: // construction + ScriptEngineV8(ScriptManager *manager = nullptr); + virtual ~ScriptEngineV8(); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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 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() override; + virtual bool hasUncaughtException() const override; + virtual bool isEvaluating() const override; + virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) 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 ScriptValue newMethod(QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams); + 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 ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override; + + virtual bool raiseException(const QString& exception, const QString &reason = QString()) override; + virtual bool raiseException(const ScriptValue& exception, const QString &reason = QString()) 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, ScriptEngine::ValueOwnership = ScriptEngine::QtOwnership) 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 std::shared_ptr uncaughtException() const override; + virtual void updateMemoryCost(const qint64& deltaSize) override; + virtual void requestCollectGarbage() override { while(!_v8Isolate->IdleNotificationDeadline(getV8Platform()->MonotonicallyIncreasingTime() + GARBAGE_COLLECTION_TIME_LIMIT_S)) {}; } + virtual void compileTest() override; + virtual QString scriptValueDebugDetails(const ScriptValue &value) override; + QString scriptValueDebugDetailsV8(const V8ScriptValue &value); + virtual QString scriptValueDebugListMembers(const ScriptValue &value) override; + QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value); + virtual void logBacktrace(const QString &title = QString("")) override; + virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() override; + virtual void startCollectingObjectStatistics() override; + virtual void dumpHeapObjectStatistics() override; + void scheduleValueWrapperForDeletion(ScriptValueV8Wrapper* wrapper) {_scriptValueWrappersToDelete.enqueue(wrapper);} + void deleteUnusedValueWrappers(); + virtual void perManagerLoopIterationCleanup() override; + + // 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 + + + // 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 V8ScriptValue& 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 object by name + Q_INVOKABLE void registerValue(const QString& valueName, V8ScriptValue value); + + // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety + // V8TODO + //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; + int computeCastPenalty(const V8ScriptValue& val, int destTypeId); + bool castValueToVariant(const V8ScriptValue& val, QVariant& dest, int destTypeId); + + // Converts JS objects created in V8 to variants. Iterates over all properties and converts them to variants. + bool convertJSArrayToVariant(v8::Local array, QVariant &dest); + bool convertJSObjectToVariant(v8::Local object, QVariant &dest); + V8ScriptValue castVariantToValue(const QVariant& val); + QString valueType(const V8ScriptValue& val); + v8::Isolate* getIsolate() { + Q_ASSERT(_v8Isolate != nullptr); + return _v8Isolate;} + v8::Local getContext(); + const v8::Local getConstContext() const; + QString formatErrorMessageFromTryCatch(v8::TryCatch &tryCatch); + // Useful for debugging + virtual QStringList getCurrentScriptURLs() const override; + + using ObjectWrapperMap = QMap>; + mutable QMutex _qobjectWrapperMapProtect; + ObjectWrapperMap _qobjectWrapperMap; + // Second map, from which wrappers are removed by script engine upon deletion + QMap> _qobjectWrapperMapV8; + // V8TODO: maybe just a single map can be used instead to increase performance? + + // Sometimes ScriptValueV8Wrapper::release() is called from inside ScriptValueV8Wrapper. + // Then wrapper needs to be deleted in the event loop + QQueue _scriptValueWrappersToDelete; + + // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones + v8::Local getObjectProxyTemplate(); + v8::Local getMethodDataTemplate(); + v8::Local getFunctionDataTemplate(); + v8::Local getVariantDataTemplate(); + v8::Local getVariantProxyTemplate(); + + ScriptContextV8Pointer pushContext(v8::Local context); + void popContext(); + void storeGlobalObjectContents(); +#ifdef OVERTE_V8_MEMORY_DEBUG + void incrementScriptValueCounter() { scriptValueCount++; }; + void decrementScriptValueCounter() { scriptValueCount--; }; + void incrementScriptValueProxyCounter() { scriptValueProxyCount++; }; + void decrementScriptValueProxyCounter() { scriptValueProxyCount--; }; +#endif + +protected: + + void registerSystemTypes(); + +protected: + static QMutex _v8InitMutex; + static std::once_flag _v8InitOnceFlag; + static v8::Platform* getV8Platform(); + + void setUncaughtEngineException(const QString &message, const QString& info = QString()); + void setUncaughtException(const v8::TryCatch &tryCatch, const QString& info = QString()); + void setUncaughtException(std::shared_ptr exception); + + friend class ScriptSignalV8Proxy; + + std::shared_ptr _uncaughtException; + + + // V8TODO: clean up isolate when script engine is destroyed? + v8::Isolate* _v8Isolate; + + struct CustomMarshal { + ScriptEngine::MarshalFunction marshalFunc; + ScriptEngine::DemarshalFunction demarshalFunc; + }; + using CustomMarshalMap = QHash; + using CustomPrototypeMap = QHash; + + mutable QReadWriteLock _customTypeProtect { QReadWriteLock::Recursive }; + CustomMarshalMap _customTypes; + CustomPrototypeMap _customPrototypes; + ScriptValue _nullValue; + ScriptValue _undefinedValue; + // Current context stack. Main context is first on the list and current one is last. + QList _contexts; + // V8TODO: release in destructor + v8::Persistent _globalObjectContents; + bool areGlobalObjectContentsStored {false}; + + // Used by ScriptObjectV8Proxy to create JS objects referencing C++ ones + // V8TODO: release in destructor + v8::Persistent _objectProxyTemplate; + v8::Persistent _methodDataTemplate; + v8::Persistent _functionDataTemplate; + v8::Persistent _variantDataTemplate; + v8::Persistent _variantProxyTemplate; + +public: + volatile int _memoryCorruptionIndicator = 12345678; +private: + //V8TODO + //ArrayBufferClass* _arrayBufferClass; + // Counts how many nested evaluate calls are there at a given point + int _evaluatingCounter; +#ifdef OVERTE_V8_MEMORY_DEBUG + std::atomic scriptValueCount{0}; + std::atomic scriptValueProxyCount{0}; +#endif +}; + +// This class is used to automatically add context to script engine's context list that is used by C++ calls +// An instance of it needs to be created in every V8 callback + +class ContextScopeV8 { +public: + ContextScopeV8(ScriptEngineV8 *engine); + ~ContextScopeV8(); +private: + bool _isContextChangeNeeded; + ScriptEngineV8* _engine; +}; + +#include "V8Types.h" + +#endif // hifi_ScriptEngineV8_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp new file mode 100644 index 00000000000..8dead23ddb7 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -0,0 +1,761 @@ +// +// ScriptEngineV8_cast.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson 12/9/2021 +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptEngineV8.h" + +#include +#include +#include +#include "libplatform/libplatform.h" +#include "v8.h" + +#include "../ScriptEngineCast.h" +#include "../ScriptValueIterator.h" + +#include "ScriptObjectV8Proxy.h" +#include "ScriptValueV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" + +void ScriptEngineV8::setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) { + ScriptValueV8Wrapper* unwrappedPrototype = ScriptValueV8Wrapper::unwrap(prototype); + if (unwrappedPrototype) { + const V8ScriptValue& scriptPrototype = unwrappedPrototype->toV8Value(); + _customTypeProtect.lockForWrite(); + _customPrototypes.insert(metaTypeId, scriptPrototype); + _customTypeProtect.unlock(); + } +} + +void ScriptEngineV8::registerCustomType(int type, + ScriptEngine::MarshalFunction marshalFunc, + ScriptEngine::DemarshalFunction demarshalFunc) +{ + _customTypeProtect.lockForWrite(); + + // storing it in a map for our own benefit + CustomMarshal& customType = _customTypes.insert(type, CustomMarshal()).value(); + customType.demarshalFunc = demarshalFunc; + customType.marshalFunc = marshalFunc; + _customTypeProtect.unlock(); +} + +Q_DECLARE_METATYPE(ScriptValue); +Q_DECLARE_METATYPE(QVariantMap); + +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 ScriptEngineV8::registerSystemTypes() { + scriptRegisterMetaType(static_cast(this)); + scriptRegisterMetaType(this); + scriptRegisterMetaType(this); + scriptRegisterMetaType(this); + scriptRegisterMetaType(this); + scriptRegisterMetaType(this); + scriptRegisterMetaType(this); +} + +int ScriptEngineV8::computeCastPenalty(const V8ScriptValue& v8Val, int destTypeId) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = getContext(); + v8::Context::Scope contextScope(context); + + const v8::Local val = v8Val.constGet(); + if (val->IsNumber()) { + switch (destTypeId){ + case QMetaType::Bool: + // Conversion to bool is acceptable, but numbers are preferred + return 5; + break; + case QMetaType::UInt: + case QMetaType::ULong: + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::ULongLong: + case QMetaType::LongLong: + case QMetaType::UShort: + // Perfect case. JS doesn't have separate integer and floating point type + return 0; + break; + case QMetaType::QString: + case QMetaType::QByteArray: + case QMetaType::QDateTime: + case QMetaType::QDate: + // Conversion to string should be avoided, it's probably not what we want + return 100; + break; + default: + // Other, not predicted cases + return 5; + } + } else if (val->IsString() || val->IsDate() || val->IsRegExp()) { + switch (destTypeId){ + case QMetaType::Bool: + // Conversion from to bool should be avoided if possible, it's probably not what we want + return 100; + case QMetaType::UInt: + case QMetaType::ULong: + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::ULongLong: + case QMetaType::LongLong: + case QMetaType::UShort: + // Conversion from to number should be avoided if possible, it's probably not what we want + return 100; + case QMetaType::QString: + // Perfect case + return 0; + case QMetaType::QByteArray: + case QMetaType::QDateTime: + case QMetaType::QDate: + // String to string should be slightly preferred + return 5; + default: + return 5; + } + } else if (val->IsBoolean()) { + switch (destTypeId){ + case QMetaType::Bool: + // Perfect case + return 0; + break; + case QMetaType::UInt: + case QMetaType::ULong: + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::ULongLong: + case QMetaType::LongLong: + case QMetaType::UShort: + // Using function with bool parameter should be preferred over converted bool to nimber + return 5; + break; + case QMetaType::QString: + case QMetaType::QByteArray: + case QMetaType::QDateTime: + case QMetaType::QDate: + // Bool probably shouldn't be converted to string if there are better alternatives + return 50; + break; + default: + return 5; + } + } + return 0; +} + +bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& dest, int destTypeId) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = getContext(); + v8::Context::Scope contextScope(context); + const v8::Local val = v8Val.constGet(); + + // 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 = ScriptObjectV8Proxy::unwrap(v8Val); + 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 ScriptValueV8Wrapper(this, v8Val))); + return true; + } + + QString errorMessage(""); + // do we have a registered handler for this type? + ScriptEngine::DemarshalFunction demarshalFunc = nullptr; + { + _customTypeProtect.lockForRead(); + CustomMarshalMap::const_iterator lookup = _customTypes.find(destTypeId); + if (lookup != _customTypes.cend()) { + demarshalFunc = lookup.value().demarshalFunc; + } + _customTypeProtect.unlock(); + } + if (demarshalFunc) { + dest = QVariant(); + ScriptValue wrappedVal(new ScriptValueV8Wrapper(this, v8Val)); + bool success = demarshalFunc(wrappedVal, dest); + 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->IsBoolean()) { + dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value()); + break; + } + if (val->IsString()) { + v8::String::Utf8Value string(_v8Isolate, val); + Q_ASSERT(*string != nullptr); + dest = QVariant::fromValue(QString(*string)); + break; + } + if (val->IsNumber()) { + dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value()); + break; + } + { + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); + if (obj) { + dest = QVariant::fromValue(obj); + break; + } + } + { + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); + if (var.isValid()) { + dest = var; + break; + } + } + if (val->IsArray()) { + if (convertJSArrayToVariant(v8::Local::Cast(val), dest)) { + return true; + } + } + // This is for generic JS objects + if (val->IsObject()) { + if (convertJSObjectToVariant(v8::Local::Cast(val), dest)) { + break; + } + } + errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) + + "to variant. Destination type: " + QMetaType::typeName(destTypeId) +" details: "+ scriptValueDebugDetailsV8(v8Val); + qCDebug(scriptengine_v8) << errorMessage; + + // V8TODO: this doesn't seem to be necessary anymore but I'm keeping it until all the API is tested + //dest = val->ToVariant(); + break; + case QMetaType::Bool: + dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value()); + break; + case QMetaType::QDateTime: + case QMetaType::QDate: + if (val->IsDate()){ + double timeMs = v8::Date::Cast(*val)->NumberValue(context).ToChecked(); + dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(timeMs)); + } else if (val->IsNumber()) { + dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(val->ToNumber(context).ToLocalChecked()->Value())); + } else { + return false; + } + break; + case QMetaType::UInt: + case QMetaType::ULong: + if ( val->IsArray() || val->IsObject() ){ + return false; + } + dest = QVariant::fromValue(val->ToUint32(context).ToLocalChecked()->Value()); + break; + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + if ( val->IsArray() || val->IsObject() ){ + return false; + } + dest = QVariant::fromValue(val->ToInt32(context).ToLocalChecked()->Value()); + break; + case QMetaType::Double: + case QMetaType::Float: + case QMetaType::ULongLong: + case QMetaType::LongLong: + if ( val->IsArray() || val->IsObject() ){ + return false; + } + dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value()); + break; + case QMetaType::QString: + case QMetaType::QByteArray: + { + v8::String::Utf8Value string(_v8Isolate, val); + Q_ASSERT(*string != nullptr); + dest = QVariant::fromValue(QString(*string)); + } + break; + case QMetaType::UShort: + if ( val->IsArray() || val->IsObject() ){ + return false; + } + dest = QVariant::fromValue(static_cast(val->ToUint32(context).ToLocalChecked()->Value())); + break; + case QMetaType::QObjectStar: + { + // V8TODO: This is to diagnose a really weird segfault where it looks like only half of the QPointer is set + // to null upon object deletion, it doesn't seem to happen anymore, but I'm keeping it for now + uint64_t ptrVal = (uint64_t)(ScriptObjectV8Proxy::unwrap(v8Val)); + if ((uint32_t)(ptrVal) == 0 && ptrVal != 0) { + qCDebug(scriptengine_v8) << "ScriptEngineV8::castValueToVariant pointer bug happened"; + //Q_ASSERT(false); + return false; + } + } + dest = QVariant::fromValue(ScriptObjectV8Proxy::unwrap(v8Val)); + break; + case QMetaType::QVariant: + if (val->IsUndefined()) { + dest = QVariant(); + return true; + } + if (val->IsNull()) { + dest = QVariant::fromValue(nullptr); + return true; + } + if (val->IsBoolean()) { + dest = QVariant::fromValue(val->BooleanValue(_v8Isolate)); + return true; + } + if (val->IsString()) { + v8::String::Utf8Value string(_v8Isolate, val); + Q_ASSERT(*string != nullptr); + dest = QVariant::fromValue(QString(*string)); + return true; + } + if (val->IsNumber()) { + dest = QVariant::fromValue(val->ToNumber(context).ToLocalChecked()->Value()); + return true; + } + { + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); + if (obj) { + dest = QVariant::fromValue(obj); + return true; + } + } + { + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); + if (var.isValid()) { + dest = var; + return true; + } + } + if (val->IsArray()) { + if (convertJSArrayToVariant(v8::Local::Cast(val), dest)) { + return true; + } + } + if (val->IsObject()) { + if (convertJSObjectToVariant(v8::Local::Cast(val), dest)) { + return true; + } + } + errorMessage = QString() + "Conversion to variant failed: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) + + " Destination type: " + QMetaType::typeName(destTypeId) + " Value details: " + scriptValueDebugDetailsV8(v8Val); + qCDebug(scriptengine_v8) << errorMessage; + return false; + 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 = ScriptObjectV8Proxy::unwrap(v8Val); + 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 if it's a pointer to QVariant + { + QVariant var = *ScriptVariantV8Proxy::unwrapQVariantPointer(_v8Isolate, v8Val.constGet()); + if (var.isValid()) { + dest = var; + break; + } + } + // check to see if we have a registered prototype + { + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); + if (var.isValid()) { + dest = var; + break; + } + } + // last chance, just convert it to a variant (impossible on V8) + // V8TODO + errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) + + "to variant. Destination type: " + QMetaType::typeName(destTypeId); + qCDebug(scriptengine_v8) << errorMessage; + if(destTypeId == QMetaType::QVariant) { + Q_ASSERT(false); + } + //dest = val->ToVariant(); + break; + } + } + + return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(destTypeId); +} + +bool ScriptEngineV8::convertJSArrayToVariant(v8::Local array, QVariant &dest) { + v8::HandleScope handleScope(_v8Isolate); + auto context = getContext(); + v8::Context::Scope contextScope(context); + int length = array->Length(); + QList properties; + for (int i = 0; i < length; i++) { + v8::Local v8Property; + if (!array->Get(context, i).ToLocal(&v8Property)) { + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could not get property: " + QString(i); + continue; + } + QVariant property; + // Maybe QMetaType::QVariant? + if (castValueToVariant(V8ScriptValue(this, v8Property), property, QMetaType::UnknownType)) { + properties.append(property); + } else { + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could cast property to variant: " + QString(i); + ; + } + } + dest = QVariant(properties); + return true; +} + +bool ScriptEngineV8::convertJSObjectToVariant(v8::Local object, QVariant &dest) { + v8::HandleScope handleScope(_v8Isolate); + auto context = getContext(); + v8::Context::Scope contextScope(context); + v8::Local names; + if(!object->GetPropertyNames(context).ToLocal(&names)) { + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSObjectToVariant could not get property names"; + return false; + } + int length = names->Length(); + QMap properties; + for (int i = 0; i < length; i++) { + v8::Local v8Property; + QString name = *v8::String::Utf8Value(_v8Isolate, names->Get(context, i).ToLocalChecked()); + if (!object->Get(context, names->Get(context, i).ToLocalChecked()).ToLocal(&v8Property)) { + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSObjectToVariant could not get property: " + name; + continue; + } + QVariant property; + // Maybe QMetaType::QVariant? + if (castValueToVariant(V8ScriptValue(this, v8Property), property, QMetaType::UnknownType)) { + properties.insert( name, property); + } else { + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSObjectToVariant could cast property to variant: " + name; + ; + } + } + dest = QVariant(properties); + return true; +} + +QString ScriptEngineV8::valueType(const V8ScriptValue& v8Val) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = getContext(); + v8::Context::Scope contextScope(context); + + const v8::Local val = v8Val.constGet(); + + if (val->IsUndefined()) { + return "undefined"; + } + if (val->IsNull()) { + return "null"; + } + if (val->IsBoolean()) { + return "boolean"; + } + if (val->IsString()) { + return "string"; + } + if (val->IsNumber()) { + return "number"; + } + { + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); + if (obj) { + QString objectName = obj->objectName(); + if (!objectName.isEmpty()) return objectName; + return obj->metaObject()->className(); + } + } + { + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); + if (var.isValid()) { + return var.typeName(); + } + } + QVariant dest; + if (castValueToVariant(v8Val, dest, QMetaType::QVariant)) { + return dest.typeName(); + } + qCDebug(scriptengine_v8) << "Cast to variant failed"; + return "undefined"; +} + +V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + v8::HandleScope handleScope(_v8Isolate); + v8::Local context = getContext(); + v8::Context::Scope contextScope(context); + + 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 ScriptValueV8Wrapper::fullUnwrap(this, innerVal); + } + + // do we have a registered handler for this type? + ScriptEngine::MarshalFunction marshalFunc = nullptr; + { + _customTypeProtect.lockForRead(); + CustomMarshalMap::const_iterator lookup = _customTypes.find(valTypeId); + if (lookup != _customTypes.cend()) { + marshalFunc = lookup.value().marshalFunc; + } + _customTypeProtect.unlock(); + } + if (marshalFunc) { + Q_ASSERT(val.constData() != nullptr); + ScriptValue wrappedVal = marshalFunc(this, val.constData()); + return ScriptValueV8Wrapper::fullUnwrap(this, wrappedVal); + } + + switch (valTypeId) { + case QMetaType::UnknownType: + case QMetaType::Void: + return V8ScriptValue(this, v8::Undefined(_v8Isolate)); + case QMetaType::Nullptr: + return V8ScriptValue(this, v8::Null(_v8Isolate)); + case QMetaType::Bool: + return V8ScriptValue(this, v8::Boolean::New(_v8Isolate, val.toBool())); + case QMetaType::Int: + case QMetaType::Long: + case QMetaType::Short: + return V8ScriptValue(this, v8::Integer::New(_v8Isolate, val.toInt())); + case QMetaType::UInt: + case QMetaType::UShort: + case QMetaType::ULong: + return V8ScriptValue(this, v8::Uint32::New(_v8Isolate, val.toUInt())); + case QMetaType::ULongLong: + return V8ScriptValue(this, v8::Number::New(_v8Isolate, val.toULongLong())); + case QMetaType::LongLong: + return V8ScriptValue(this, v8::Number::New(_v8Isolate, val.toLongLong())); + case QMetaType::Float: + case QMetaType::Double: + return V8ScriptValue(this, v8::Number::New(_v8Isolate, val.toDouble())); + case QMetaType::QString: + case QMetaType::QByteArray: + return V8ScriptValue(this, v8::String::NewFromUtf8(_v8Isolate, val.toString().toStdString().c_str()).ToLocalChecked()); + case QMetaType::QVariant: + return castVariantToValue(val.value()); + case QMetaType::QObjectStar: { + QObject* obj = val.value(); + if (obj == nullptr) return V8ScriptValue(this, v8::Null(_v8Isolate)); + //V8TODO: what should be the ownership in this case? + return ScriptObjectV8Proxy::newQObject(this, obj); + } + case QMetaType::QDateTime: + { + double timeMs = val.value().currentMSecsSinceEpoch(); + return V8ScriptValue(this, v8::Date::New(getContext(), timeMs).ToLocalChecked()); + } + case QMetaType::QDate: + { + double timeMs = val.value().startOfDay().currentMSecsSinceEpoch(); + return V8ScriptValue(this, v8::Date::New(getContext(), timeMs).ToLocalChecked()); + } + default: + // check to see if this is a pointer to a QObject-derived object + // WeakPointerToQObject and SharedPointerToQObject were causing trouble here because some values are handled by custom prototypes instead + if (QMetaType::typeFlags(valTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { + QObject* obj = val.value(); + if (obj == nullptr) return V8ScriptValue(this, v8::Null(_v8Isolate)); + //V8TODO: what should be the ownership in this case? + return ScriptObjectV8Proxy::newQObject(this, obj); + } + // have we set a prototyped variant? + { + _customTypeProtect.lockForRead(); + CustomPrototypeMap::const_iterator lookup = _customPrototypes.find(valTypeId); + if (lookup != _customPrototypes.cend()) { + return ScriptVariantV8Proxy::newVariant(this, val, lookup.value()); + } + _customTypeProtect.unlock(); + } + // just do a generic variant + //V8TODO + qCDebug(scriptengine_v8) << "ScriptEngineV8::castVariantToValue failed for " << QMetaType::typeName(valTypeId); + logBacktrace("ScriptEngineV8::castVariantToValue failed"); + //Q_ASSERT(false); + return V8ScriptValue(this, v8::Undefined(_v8Isolate)); + //return QScriptEngine::newVariant(val); + } +} diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp new file mode 100644 index 00000000000..e2fd9dfaa68 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -0,0 +1,1518 @@ +// +// ScriptObjectV8Proxy.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 12/5/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptObjectV8Proxy.h" + +#include +#include +#include + +#include "../ScriptEngineLogging.h" + +#include "ScriptContextV8Wrapper.h" +#include "ScriptValueV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" + +Q_DECLARE_METATYPE(ScriptValue) + +Q_DECLARE_METATYPE(QSharedPointer) +Q_DECLARE_METATYPE(QSharedPointer) + + +// These values are put into internal fields of V8 objects, to signalize what kind of data is the pointer in another +// internal field pointing to. Proxy unwrapping functions recognize proxies by checking for these values in internal field 0 +// of V8 object. + +// Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1 +static const void *internalPointsToQObjectProxy = (void *)0x13370000; +// Internal field value of object pointing to ScriptObjectV8Proxy is changed to this value upon proxy's deletion +static const void *internalPointsToDeletedQObjectProxy = (void *)0x13370010; +static const void *internalPointsToQVariantProxy = (void *)0x13371000; +//static const void *internalPointsToSignalProxy = (void *)0x13372000; +static const void *internalPointsToMethodProxy = (void *)0x13373000; +// This is used to pass object in ScriptVariantV8Proxy to methods of prototype object, for example passing AnimationPointer to AnimationObject +// Object is then converted using scriptvalue_cast for use inside the prototype +static const void *internalPointsToQVariantInProxy = (void *)0x13374000; + +// 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 ScriptPropertyContextV8Wrapper final : public ScriptContext { +public: // construction + inline ScriptPropertyContextV8Wrapper(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; +}; + +ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : + _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object) { + Q_ASSERT(_engine != nullptr); + investigate(); +} + +V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + // do we already have a valid wrapper for this QObject? + { + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); + ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + if (proxy) { + return V8ScriptValue(engine, proxy.get()->toV8Value()); + } + } + } + + bool ownsObject; + switch (ownership) { + case ScriptEngine::QtOwnership: + ownsObject = false; + break; + case ScriptEngine::ScriptOwnership: + ownsObject = true; + break; + case ScriptEngine::AutoOwnership: + ownsObject = !object->parent(); + break; + default: + ownsObject = false; + qCCritical(scriptengine_v8) << "Wrong ScriptEngine::ValueOwnership value: " << ownership; + 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 + ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + if (proxy) { + return V8ScriptValue(engine, proxy.get()->toV8Value()); + } + } + // register the wrapper with the engine and make sure it cleans itself up + engine->_qobjectWrapperMap.insert(object, proxy); + engine->_qobjectWrapperMapV8.insert(object, proxy); + QPointer enginePtr = engine; + object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { + if (!enginePtr) return; + QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); + ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); + if (lookup != enginePtr->_qobjectWrapperMap.end()) { + enginePtr->_qobjectWrapperMap.erase(lookup); + } + auto lookupV8 = enginePtr->_qobjectWrapperMapV8.find(object); + if (lookupV8 != enginePtr->_qobjectWrapperMapV8.end()) { + enginePtr->_qobjectWrapperMapV8.erase(lookupV8); + } + }); + } + + return V8ScriptValue(engine, proxy.get()->toV8Value()); +} + +ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { + auto isolate = val.getEngine()->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Local context = val.constGetContext(); + v8::Context::Scope contextScope(context); + auto v8Value = val.constGet(); + Q_ASSERT(!v8Value.IsEmpty()); + + if (v8Value->IsNullOrUndefined()) { + return nullptr; + } + if (!v8Value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8::Local::Cast(v8Value); + if (v8Object->InternalFieldCount() != 3) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { + qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local &value) { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + + if (value->IsNullOrUndefined()) { + return nullptr; + } + if (!value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8::Local::Cast(value); + if (v8Object->InternalFieldCount() != 3) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQObjectProxy) { + qCDebug(scriptengine_v8) << "Cannot unwrap proxy - internal fields don't point to object proxy"; + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { + ScriptObjectV8Proxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQObject() : nullptr; +} + +ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { + if (_ownsObject) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + _v8Object.Reset(); + QObject* qobject = _object; + if(qobject) qobject->deleteLater(); + } else { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + if (_object) + qCDebug(scriptengine_v8) << "Deleting object proxy: " << name(); + Q_ASSERT(!_v8Object.Get(isolate)->IsNullOrUndefined()); + // This prevents unwrap function from unwrapping proxy that was deleted + _v8Object.Get(isolate)->SetAlignedPointerInInternalField(0, const_cast(internalPointsToDeletedQObjectProxy)); + _v8Object.Reset(); + } +} + +void ScriptObjectV8Proxy::investigate() { + QObject* qobject = _object; + if (!qobject) { + QStringList backtrace = _engine->currentContext()->backtrace(); + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::investigate: Object pointer is NULL, " << backtrace; + } + if (!qobject) return; + + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope contextScope(_engine->getContext()); + + const QMetaObject* metaObject = qobject->metaObject(); + + auto objectTemplate = _engine->getObjectProxyTemplate(); + + if (QString("ConsoleScriptingInterface") == metaObject->className()) { + printf("ConsoleScriptingInterface"); + } + // 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(prop.name(), idx)).value(); + _propNameMap.insert(prop.name(), &propDef); + propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter | + ScriptValue::QObjectMember; + if (prop.isConstant()) propDef.flags |= ScriptValue::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; + default: + break; + } + + auto nameString = v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked(); + V8ScriptString name(_engine, nameString); + auto nameLookup = methodNames.find(szName); + if (isSignal) { + if (nameLookup == methodNames.end()) { + SignalDef& signalDef = _signals.insert(idx, SignalDef(szName, idx)).value(); + signalDef.name = szName; + signalDef.signal = method; + _signalNameMap.insert(szName, &signalDef); + methodNames.insert(szName, 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(method.returnType() == QMetaType::UnknownType) { + qCCritical(scriptengine_v8) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; + } + for (int i = 0; i < method.parameterCount(); i++) { + if (method.parameterType(i) == QMetaType::UnknownType) { + qCCritical(scriptengine_v8) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; + } + } + if (nameLookup == methodNames.end()) { + MethodDef& methodDef = _methods.insert(idx, MethodDef(szName, idx)).value(); + methodDef.name = szName; + methodDef.numMaxParams = parameterCount; + methodDef.methods.append(method); + _methodNameMap.insert(szName, &methodDef); + methodNames.insert(szName, idx); + } else { + int originalMethodId = nameLookup.value(); + MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); + Q_ASSERT(methodLookup != _methods.end()); + MethodDef& methodDef = methodLookup.value(); + if(methodDef.numMaxParams < parameterCount) methodDef.numMaxParams = parameterCount; + methodDef.methods.append(method); + } + } + } + + v8::Local v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); + + v8Object->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQObjectProxy)); + v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); + + _v8Object.Reset(_engine->getIsolate(), v8Object); + if (_ownsObject) { + _v8Object.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); + } + + // Properties added later will be stored in this object + v8::Local propertiesObject = v8::Object::New(_engine->getIsolate()); + v8Object->SetInternalField(2, propertiesObject); + + // Add all the methods objects as properties - this allows adding properties to a given method later. Is used by Script.request. + for (auto i = _methods.begin(); i != _methods.end(); i++) { + V8ScriptValue method = ScriptMethodV8Proxy::newMethod(_engine, qobject, V8ScriptValue(_engine, v8Object), + i.value().methods, i.value().numMaxParams); + if(!propertiesObject->Set(_engine->getContext(), v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(), method.get()).FromMaybe(false)) { + Q_ASSERT(false); + } + } +} + +void ScriptObjectV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo& info) { + auto proxy = info.GetParameter(); + proxy->_v8Object.Reset(); + info.GetParameter()->_object->deleteLater(); +} + +QString ScriptObjectV8Proxy::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(); +} + +ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Local context = _engine->getContext(); + v8::Context::Scope contextScope(context); + QString nameStr(*v8::String::Utf8Value(isolate, name.constGet())); + + // check for methods + MethodNameMap::const_iterator method = _methodNameMap.find(nameStr); + if (method != _methodNameMap.cend()) { + const MethodDef* methodDef = method.value(); + *id = methodDef->_id | METHOD_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for properties + PropertyNameMap::const_iterator prop = _propNameMap.find(nameStr); + if (prop != _propNameMap.cend()) { + const PropertyDef* propDef = prop.value(); + *id = propDef->_id | PROPERTY_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for signals + // V8TODO: this should use _signalNameMap QHash for faster search + for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { + if (!(trans.value().name == nameStr)) continue; + *id = trans.key() | SIGNAL_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + return QueryFlags(); +} + +ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + QObject* qobject = _object; + if (!qobject) { + return ScriptValue::PropertyFlags(); + } + + switch (id & TYPE_MASK) { + case PROPERTY_TYPE: { + PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); + if (lookup == _props.cend()) return ScriptValue::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 ScriptValue::PropertyFlags(); + return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; + } + case SIGNAL_TYPE: { + SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); + if (lookup == _signals.cend()) return ScriptValue::PropertyFlags(); + return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; + } + } + return ScriptValue::PropertyFlags(); +} + +void ScriptObjectV8Proxy::v8Get(v8::Local name, const v8::PropertyCallbackInfo& info) { + v8::HandleScope handleScope(info.GetIsolate()); + v8::String::Utf8Value utf8Value(info.GetIsolate(), name); + v8::Local objectV8 = info.This(); + ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Value; + return; + } + V8ScriptValue object(proxy->_engine, objectV8); + if (!name->IsString() && !name->IsSymbol()) { + QString notStringMessage("ScriptObjectV8Proxy::v8Get: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); + qCDebug(scriptengine_v8) << notStringMessage; + Q_ASSERT(false); + } + v8::Local v8NameString; + + ContextScopeV8 contextScopeV8(proxy->_engine); + + if (name->IsString()) { + V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); + uint id; + QueryFlags flags = proxy->queryProperty(object, nameString, HandlesReadAccess, &id); + if (flags) { + V8ScriptValue value = proxy->property(object, nameString, id); + info.GetReturnValue().Set(value.get()); + return; + } + } + + v8::Local property; + if(info.This()->GetInternalField(2).As()->Get(proxy->_engine->getContext(), name).ToLocal(&property)) { + info.GetReturnValue().Set(property); + } else { + qCDebug(scriptengine_v8) << "Value not found: " << *utf8Value; + } +} + +void ScriptObjectV8Proxy::v8Set(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::HandleScope handleScope(info.GetIsolate()); + v8::String::Utf8Value utf8Value(info.GetIsolate(), name); + v8::Local objectV8 = info.This(); + ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "Proxy object not found when setting: " << *utf8Value; + return; + } + V8ScriptValue object(proxy->_engine, objectV8); + if (!name->IsString() && !name->IsSymbol()) { + QString notStringMessage("ScriptObjectV8Proxy::v8Set: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); + qCDebug(scriptengine_v8) << notStringMessage; + Q_ASSERT(false); + } + + ContextScopeV8 contextScopeV8(proxy->_engine); + + if (name->IsString()) { + V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); + uint id; + QueryFlags flags = proxy->queryProperty(object, nameString, HandlesWriteAccess, &id); + if (flags) { + proxy->setProperty(object, nameString, id, V8ScriptValue(proxy->_engine, value)); + info.GetReturnValue().Set(value); + return; + } + } + + if (info.This()->GetInternalField(2).As()->Set(proxy->_engine->getContext(), name, value).FromMaybe(false)) { + info.GetReturnValue().Set(value); + } else { + qCDebug(scriptengine_v8) << "Set failed: " << *utf8Value; + } +} + +void ScriptObjectV8Proxy::v8GetPropertyNames(const v8::PropertyCallbackInfo& info) { + v8::HandleScope handleScope(info.GetIsolate()); + auto context = info.GetIsolate()->GetCurrentContext(); + v8::Context::Scope contextScope(context); + v8::Local objectV8 = info.This(); + ScriptObjectV8Proxy *proxy = ScriptObjectV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::v8GetPropertyNames: Proxy object not found when listing"; + return; + } + V8ScriptValue object(proxy->_engine, objectV8); + v8::Local properties = proxy->getPropertyNames(); + v8::Local objectProperties; + uint32_t propertiesLength = properties->Length(); + if (info.This()->GetInternalField(2).As()->GetPropertyNames(context).ToLocal(&objectProperties)) { + for (uint32_t n = 0; n < objectProperties->Length(); n++) { + if(!properties->Set(context, propertiesLength+n, objectProperties->Get(context, n).ToLocalChecked()).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::v8GetPropertyNames: Cannot add member name"; + } + } + } + info.GetReturnValue().Set(properties); +} + +v8::Local ScriptObjectV8Proxy::getPropertyNames() { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(_engine->getContext()); + + //V8TODO: this is really slow. It could be cached if this is called often. + v8::Local properties = v8::Array::New(isolate, _props.size() + _methods.size() + _signals.size()); + uint32_t position = 0; + for (PropertyDefMap::const_iterator i = _props.begin(); i != _props.end(); i++){ + v8::Local name = v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(); + if(!properties->Set(context, position++, name).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; + } + } + for (MethodDefMap::const_iterator i = _methods.begin(); i != _methods.end(); i++){ + v8::Local name = v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(); + if(!properties->Set(context, position++, name).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; + } + } + for (SignalDefMap::const_iterator i = _signals.begin(); i != _signals.end(); i++){ + v8::Local name = v8::String::NewFromUtf8(isolate, i.value().name.toStdString().c_str()).ToLocalChecked(); + if(!properties->Set(context, position++, name).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::getPropertyNames: Cannot add property member name"; + } + } + return handleScope.Escape(properties); +} + + +V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("Referencing deleted native object"); + return V8ScriptValue(_engine, v8::Null(isolate)); + } + + 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 V8ScriptValue(_engine, v8::Null(isolate)); + + QMetaProperty prop = metaObject->property(propId); + ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); + ScriptPropertyContextV8Wrapper 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 V8ScriptValue(_engine, v8::Null(isolate)); + const MethodDef& methodDef = lookup.value(); + for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) { + if((*iter).returnType() == QMetaType::UnknownType) { + qCDebug(scriptengine_v8) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name(); + } + } //V8TODO: is new method created during every call? It needs to be cached instead + v8::Local property; + if(_v8Object.Get(isolate)->GetInternalField(2).As()->Get(_engine->getContext(), name.constGet()).ToLocal(&property)) { + if (!property->IsUndefined()) { + return V8ScriptValue(_engine, property); + } + } + Q_ASSERT(false); + qCDebug(scriptengine_v8) << "(This should not happen) Creating new method object for " << metaObject->className() << " " << name.toQString(); + return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParams); + } + case SIGNAL_TYPE: { + int signalId = id & ~TYPE_MASK; + SignalDefMap::const_iterator defLookup = _signals.find(signalId); + if (defLookup == _signals.cend()) return V8ScriptValue(_engine, v8::Null(isolate)); + + InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); + if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { + instLookup = _signalInstances.insert(signalId, + new ScriptSignalV8Proxy(_engine, qobject, object, defLookup.value().signal)); + Q_ASSERT(instLookup != _signalInstances.cend()); + } + ScriptSignalV8Proxy* proxy = instLookup.value(); + + ScriptEngine::QObjectWrapOptions options = ScriptEngine::ExcludeSuperClassContents | + //V8TODO ScriptEngine::ExcludeDeleteLater | + ScriptEngine::PreferExistingWrapperObject; + // It's not necessarily new, newQObject looks for it first in object wrapper map + // V8TODO: won't ScriptEngine::ScriptOwnership cause trouble here? + return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); + } + } + return V8ScriptValue(_engine, v8::Null(isolate)); +} + +void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + if (!(id & PROPERTY_TYPE)) return; + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("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 & ScriptValue::ReadOnly) return; + + const QMetaObject* metaObject = qobject->metaObject(); + QMetaProperty prop = metaObject->property(propId); + + ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); + ScriptPropertyContextV8Wrapper 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(); + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName).toStdString().c_str()).ToLocalChecked()); + return; + } + prop.write(qobject, varValue); +} + +ScriptVariantV8Proxy::ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto) : + _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { + auto isolate = engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(engine->getContext()); + auto variantDataTemplate = _engine->getVariantDataTemplate(); + auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); + variantData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQVariantInProxy)); + // Internal field doesn't point directly to QVariant, because then alignment would need to be guaranteed in all compilers + variantData->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); + _v8Object.Reset(isolate, v8::Local::Cast(variantData)); + _name = QString::fromLatin1(variant.typeName()); +} + +ScriptVariantV8Proxy::~ScriptVariantV8Proxy() { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + // V8TODO: Add similar deletion handling as for object proxy + _v8Object.Reset(); +} + +V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { + qDebug() << "ScriptVariantV8Proxy::newVariant"; + auto isolate = engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(engine->getContext()); + ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); + if (!protoProxy) { + Q_ASSERT(protoProxy); + //return engine->newVariant(variant); + return V8ScriptValue(engine, v8::Undefined(isolate)); + } + // V8TODO probably needs connection to be deleted + auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); + + auto variantProxyTemplate = engine->getVariantProxyTemplate(); + auto variantProxy = variantProxyTemplate->NewInstance(engine->getContext()).ToLocalChecked(); + variantProxy->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQVariantProxy)); + variantProxy->SetAlignedPointerInInternalField(1, reinterpret_cast(proxy)); + return V8ScriptValue(engine, variantProxy); +} + +ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { + auto isolate = val.getEngine()->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(val.getEngine()->getContext()); + + auto v8Value = val.constGet(); + if (!v8Value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8::Local::Cast(v8Value); + if (v8Object->InternalFieldCount() != 2) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantProxy) { + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(v8::Isolate* isolate, v8::Local &value) { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + + if (!value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8::Local::Cast(value); + if (v8Object->InternalFieldCount() != 2) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantProxy) { + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +QVariant* ScriptVariantV8Proxy::unwrapQVariantPointer(v8::Isolate* isolate, const v8::Local &value) { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + + if (!value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8::Local::Cast(value); + if (v8Object->InternalFieldCount() != 2) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) != internalPointsToQVariantInProxy) { + return nullptr; + } + auto proxy = reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); + return &(proxy->_variant); +} + + +void ScriptVariantV8Proxy::v8Get(v8::Local name, const v8::PropertyCallbackInfo& info) { + v8::HandleScope handleScope(info.GetIsolate()); + v8::String::Utf8Value utf8Name(info.GetIsolate(), name); + v8::Local objectV8 = info.This(); + ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Name; + return; + } + V8ScriptValue object(proxy->_engine, proxy->_v8Object.Get(info.GetIsolate())); + + ContextScopeV8 contextScopeV8(proxy->_engine); + + if (name->IsString()) { + V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); + uint id; + ScriptObjectV8Proxy::QueryFlags flags = proxy->_proto->queryProperty(object, nameString, ScriptObjectV8Proxy::HandlesReadAccess, &id); + if (flags) { + V8ScriptValue value = proxy->property(object, nameString, id); + info.GetReturnValue().Set(value.get()); + return; + } + } + + qCDebug(scriptengine_v8) << "Value not found: " << *utf8Name; +} + +void ScriptVariantV8Proxy::v8Set(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { + v8::HandleScope handleScope(info.GetIsolate()); + v8::String::Utf8Value utf8Name(info.GetIsolate(), name); + v8::Local objectV8 = info.This(); + ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "Proxy object not found when getting: " << *utf8Name; + return; + } + + V8ScriptValue object(proxy->_engine, objectV8); + if (!name->IsString() && !name->IsSymbol()) { + QString notStringMessage("ScriptObjectV8Proxy::v8Set: " + proxy->_engine->scriptValueDebugDetailsV8(V8ScriptValue(proxy->_engine, name))); + qCDebug(scriptengine_v8) << notStringMessage; + Q_ASSERT(false); + } + + ContextScopeV8 contextScopeV8(proxy->_engine); + if (name->IsString()) { + V8ScriptString nameString(proxy->_engine, v8::Local::Cast(name)); + uint id; + ScriptObjectV8Proxy::QueryFlags flags = proxy->_proto->queryProperty(object, nameString, ScriptObjectV8Proxy::HandlesWriteAccess, &id); + if (flags) { + proxy->setProperty(object, nameString, id, V8ScriptValue(proxy->_engine, value)); + info.GetReturnValue().Set(value); + return; + } + } + qCDebug(scriptengine_v8) << "Set failed: " << *utf8Name; +} + +void ScriptVariantV8Proxy::v8GetPropertyNames(const v8::PropertyCallbackInfo& info) { + //V8TODO: Only methods from the prototype should be listed. + v8::HandleScope handleScope(info.GetIsolate()); + auto context = info.GetIsolate()->GetCurrentContext(); + v8::Context::Scope contextScope(context); + v8::Local objectV8 = info.This(); + ScriptVariantV8Proxy *proxy = ScriptVariantV8Proxy::unwrapProxy(info.GetIsolate(), objectV8); + if (!proxy) { + qCDebug(scriptengine_v8) << "ScriptObjectV8Proxy::v8GetPropertyNames: Proxy object not found when listing"; + return; + } + V8ScriptValue object(proxy->_engine, objectV8); + v8::Local properties = proxy->_proto->getPropertyNames(); + v8::Local objectProperties; + info.GetReturnValue().Set(properties); +} + +QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { + ScriptVariantV8Proxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQVariant() : QVariant(); + // V8TODO +} + +ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams) : + _numMaxParams(numMaxParams), _engine(engine), _object(object), /*_objectLifetime(lifetime),*/ _metas(metas) { + auto isolate = engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(engine->getContext()); + _objectLifetime.Reset(isolate, lifetime.get()); + _objectLifetime.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); +} + +ScriptMethodV8Proxy::~ScriptMethodV8Proxy() { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + _objectLifetime.Reset(); +} + +void ScriptMethodV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo& info) { + auto proxy = info.GetParameter(); + proxy->_objectLifetime.Reset(); + info.GetParameter()->deleteLater(); +} + +V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams) { + auto isolate = engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(engine->getContext()); + auto methodDataTemplate = engine->getMethodDataTemplate(); + auto methodData = methodDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); + methodData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToMethodProxy)); + methodData->SetAlignedPointerInInternalField(1, reinterpret_cast(new ScriptMethodV8Proxy(engine, object, lifetime, metas, numMaxParams))); + auto v8Function = v8::Function::New(engine->getContext(), callback, methodData, numMaxParams).ToLocalChecked(); + return V8ScriptValue(engine, v8Function); +} + +QString ScriptMethodV8Proxy::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()); +} + +void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo& arguments) { + v8::Locker locker(arguments.GetIsolate()); + v8::Isolate::Scope isolateScope(arguments.GetIsolate()); + v8::HandleScope handleScope(arguments.GetIsolate()); + v8::Context::Scope contextScope(arguments.GetIsolate()->GetCurrentContext()); + if (!arguments.Data()->IsObject()) { + arguments.GetIsolate()->ThrowError("Method value is not an object"); + return; + } + v8::Local data = v8::Local::Cast(arguments.Data()); + if (data->InternalFieldCount() != 2) { + arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call"); + return; + } + if (data->GetAlignedPointerFromInternalField(0) != internalPointsToMethodProxy) { + arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value"); + return; + } + ScriptMethodV8Proxy *proxy = reinterpret_cast(data->GetAlignedPointerFromInternalField(1)); + + ContextScopeV8 contextScopeV8(proxy->_engine); + proxy->call(arguments); +} + +void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& arguments) { + v8::Isolate *isolate = arguments.GetIsolate(); + Q_ASSERT(isolate == _engine->getIsolate()); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + ContextScopeV8 contextScopeV8(_engine); + v8::Context::Scope contextScope(_engine->getContext()); + QObject* qobject = _object; + if (!qobject) { + isolate->ThrowError("Referencing deleted native object"); + return; + } + + int scriptNumArgs = arguments.Length(); + int numArgs = std::min(scriptNumArgs, _numMaxParams); + + const int scriptValueTypeId = qMetaTypeId(); + + int parameterConversionFailureId = 0; + int parameterConversionFailureCount = 0; + + int num_metas = _metas.size(); + QVector< QList > qScriptArgLists; + QVector< QVector > qGenArgsVectors; + QVector< QList > qVarArgLists; + qScriptArgLists.resize(num_metas); + qGenArgsVectors.resize(num_metas); + qVarArgLists.resize(num_metas); + bool isValidMetaSelected = false; + int bestMeta = 0; + int bestConversionPenaltyScore = 0; + + for (int i = 0; i < num_metas; i++) { + const QMetaMethod& meta = _metas[i]; + int methodNumArgs = meta.parameterCount(); + if (methodNumArgs != numArgs) { + continue; + } + + qGenArgsVectors[i].resize(10); + int conversionPenaltyScore = 0; + int conversionFailures = 0; + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + if (methodArgTypeId == QMetaType::UnknownType) { + QString methodName = fullName(); + qCDebug(scriptengine_v8) << "One of the arguments is QMetaType::UnknownType for method " << methodName; + Q_ASSERT(false); + } + v8::Local argVal = arguments[arg]; + if (methodArgTypeId == scriptValueTypeId) { + qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); + qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); + } else if (methodArgTypeId == QMetaType::QVariant) { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgLists[i].append(varArgVal); + qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); + } + } else { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgLists[i].append(varArgVal); + const QVariant& converted = qVarArgLists[i].back(); + conversionPenaltyScore = _engine->computeCastPenalty(V8ScriptValue(_engine, argVal), methodArgTypeId); + + // 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. + qGenArgsVectors[i][arg] = + QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); + } + } + } + if (conversionFailures) { + if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { + parameterConversionFailureCount = conversionFailures; + parameterConversionFailureId = meta.methodIndex(); + } + continue; + } + + if (!isValidMetaSelected) { + isValidMetaSelected = true; + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + } + + if (isValidMetaSelected) { + // V8TODO: is this the correct wrapper? + ScriptContextV8Wrapper ourContext(_engine, &arguments, _engine->getContext(), + _engine->currentContext()->parentContext()); + ScriptContextGuard guard(&ourContext); + const QMetaMethod& meta = _metas[bestMeta]; + int returnTypeId = meta.returnType(); + QVector &qGenArgs = qGenArgsVectors[bestMeta]; + + // 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! + if (returnTypeId == QMetaType::UnknownType) { + QString methodName = fullName(); + qCDebug(scriptengine_v8) << "returnTypeId == QMetaType::UnknownType for method " << methodName; + _engine->logBacktrace(""); + //Q_ASSERT(false); + } + if (returnTypeId == QMetaType::UnknownType) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } 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) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + } + return; + } 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) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); + arguments.GetReturnValue().Set(v8Result.get()); + return; + } 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) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); + arguments.GetReturnValue().Set(v8Result.get()); + return; + } + } + + // we failed to convert the call to C++, try to create a somewhat sane error message + if (parameterConversionFailureCount == 0) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + + 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); + v8::Local argVal = arguments[arg]; + if (methodArgTypeId != scriptValueTypeId) { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { + QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); + QByteArray argTypeName = _engine->valueType(V8ScriptValue(_engine, argVal)).toLatin1(); + QString errorMessage = QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName); + qCDebug(scriptengine_v8) << errorMessage << "\n Backtrace:" << _engine->currentContext()->backtrace(); + isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); + return; + } + } + } + QString errorMessage = QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()); + qCDebug(scriptengine_v8) << errorMessage; + isolate->ThrowError(v8::String::NewFromUtf8(isolate, errorMessage.toStdString().c_str()).ToLocalChecked()); + Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? + return; +} + +ScriptSignalV8Proxy::ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta) : + _engine(engine), _object(object), _meta(meta), _metaCallId(discoverMetaCallIdx()) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + _objectLifetime.Reset(isolate, lifetime.get()); + _objectLifetime.SetWeak(this, weakHandleCallback, v8::WeakCallbackType::kParameter); + _v8Context.Reset(isolate, _engine->getContext()); +} + +ScriptSignalV8Proxy::~ScriptSignalV8Proxy() { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + _objectLifetime.Reset(); + _v8Context.Reset(); +} + +void ScriptSignalV8Proxy::weakHandleCallback(const v8::WeakCallbackInfo& info) { + auto proxy = info.GetParameter(); + proxy->_objectLifetime.Reset(); + proxy->deleteLater(); +} + +QString ScriptSignalV8Proxy::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 ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { + id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); + if (id != 0 || call != QMetaObject::InvokeMetaMethod) { + return id; + } + +#ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS + _callCounter++; + if (_callCounter % 1000 == 0) { + qCDebug(scriptengine_v8) << "Script engine: " << _engine->manager()->getFilename() << " Signal proxy " << fullName() + << " call count: " << _callCounter << " total time: " << _totalCallTime_s; + } + QElapsedTimer callTimer; + callTimer.start(); +#endif + + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + + QList connections; + withReadLock([&]{ + connections = _connections; + }); + + // V8TODO: this may cause deadlocks on connect/disconnect, so the connect/disconnect procedure needs to be reworked. + // It should probably add events to a separate list that would be processed before and after all the events for the signal. + { + v8::HandleScope handleScope(isolate); + // V8TODO: should we use function creation context, or context in which connect happened? + auto context = _engine->getContext(); + v8::Context::Scope contextScope(context); + v8::Local args[Q_METAMETHOD_INVOKE_MAX_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[arg] = _engine->castVariantToValue(argValue).get(); + } + for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { + Connection& conn = *iter; + { + auto functionContext = context; + + Q_ASSERT(!conn.callback.get().IsEmpty()); + Q_ASSERT(!conn.callback.get()->IsUndefined()); + if (conn.callback.get()->IsNull()) { + qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::qt_metacall: Connection callback is Null"; + _engine->popContext(); + continue; + } + if (!conn.callback.get()->IsFunction()) { + auto stringV8 = conn.callback.get()->ToDetailString(functionContext).ToLocalChecked(); + QString error = *v8::String::Utf8Value(_engine->getIsolate(), stringV8); + qCDebug(scriptengine_v8) << error; + Q_ASSERT(false); + } + v8::Local callback = v8::Local::Cast(conn.callback.get()); + + v8::Local v8This; + if (conn.thisValue.get()->IsObject()) { + v8This = conn.thisValue.get(); + } else { + v8This = functionContext->Global(); + } + + v8::TryCatch tryCatch(isolate); + callback->Call(functionContext, v8This, numArgs, args); + if (tryCatch.HasCaught()) { + qCDebug(scriptengine) << "Signal proxy " << fullName() << " connection call failed: \"" + << _engine->formatErrorMessageFromTryCatch(tryCatch) + << "\nThis provided: " << conn.thisValue.get()->IsObject(); + + _engine->setUncaughtException(tryCatch, "Error in signal proxy"); + } + } + } + } + +#ifdef SCRIPT_EVENT_PERFORMANCE_STATISTICS + _totalCallTime_s += callTimer.elapsed() / 1000.0f; +#endif + return -1; +} + +int ScriptSignalV8Proxy::discoverMetaCallIdx() { + const QMetaObject* ourMeta = metaObject(); + return ourMeta->methodCount(); +} + +ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { + auto iterOut = resultWithReadLock([&]{ + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope contextScope(_engine->getContext()); + ConnectionList::iterator iter; + for (iter = _connections.begin(); iter != _connections.end(); ++iter) { + Connection& conn = *iter; + if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { + break; + } + } + return iter; + }); + return iterOut; +} + +void ScriptSignalV8Proxy::connect(ScriptValue arg0, ScriptValue arg1) { + v8::Isolate *isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + QObject* qobject = _object; + if (!qobject) { + isolate->ThrowError("Referencing deleted native object"); + return; + } + + // untangle the arguments + V8ScriptValue callback(_engine, v8::Null(isolate)); + V8ScriptValue callbackThis(_engine, v8::Null(isolate)); + if (arg1.isFunction()) { + auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); + auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); + if (!unwrappedArg0 || !unwrappedArg1) { + Q_ASSERT(false); + return; + } + callbackThis = unwrappedArg0->toV8Value(); + callback = unwrappedArg1->toV8Value(); + } else { + auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); + if (!unwrappedArg0) { + Q_ASSERT(false); + return; + } + callback = unwrappedArg0->toV8Value(); + } + if (!callback.get()->IsFunction()) { + isolate->ThrowError("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 + Q_ASSERT(!callback.get().IsEmpty()); + Q_ASSERT(!callback.get()->IsUndefined()); + Q_ASSERT(callback.get()->IsFunction()); + v8::Local destFunction = v8::Local::Cast(callback.get()); + v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); + v8::Local destData; + // V8TODO: I'm not sure which context to use here + //auto destFunctionContext = destFunction->CreationContext(); + auto destFunctionContext = _engine->getContext(); + Q_ASSERT(thisObject().isObject()); + V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); + Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); + ScriptSignalV8Proxy* thisProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); + Q_ASSERT(thisProxy); + qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: " << thisProxy->fullName() << " fullName: " << fullName(); + if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { + Q_ASSERT(false); + } + if (destData->IsArray()) { + v8::Local destArray = v8::Local::Cast(destData); + int length = destArray->Length(); + // V8TODO: Maybe copying array is unnecessary? + v8::Local newArray = v8::Array::New(isolate, length + 1); + bool foundIt = false; + for (int idx = 0; idx < length && !foundIt; ++idx) { + v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); + { + qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)); + Q_ASSERT(entry->IsObject()); + V8ScriptValue v8EntryObject(_engine, entry); + Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); + // For debugging + ScriptSignalV8Proxy* entryProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); + Q_ASSERT(thisProxy); + qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::connect: entry proxy: " << entryProxy->fullName(); + } + if (!newArray->Set(destFunctionContext, idx, entry).FromMaybe(false)) { + Q_ASSERT(false); + } + } + if (!newArray->Set(destFunctionContext, length, v8ThisObject.get()).FromMaybe(false)) { + Q_ASSERT(false); + } + if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { + Q_ASSERT(false); + } + } else { + v8::Local newArray = v8::Array::New(isolate, 1); + if (!newArray->Set(destFunctionContext, 0, v8ThisObject.get()).FromMaybe(false)) { + Q_ASSERT(false); + } + if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { + Q_ASSERT(false); + } + } + + // add this to our internal list of connections + Connection newConnection(callbackThis, callback); + + withWriteLock([&]{ + _connections.append(newConnection); + }); + + // 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 ScriptSignalV8Proxy::disconnect(ScriptValue arg0, ScriptValue arg1) { + QObject* qobject = _object; + v8::Isolate *isolate = _engine->getIsolate(); + if (!qobject) { + isolate->ThrowError("Referencing deleted native object"); + return; + } + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + + // untangle the arguments + V8ScriptValue callback(_engine, v8::Null(isolate)); + V8ScriptValue callbackThis(_engine, v8::Null(isolate)); + if (arg1.isFunction()) { + auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); + auto unwrappedArg1 = ScriptValueV8Wrapper::unwrap(arg1); + if (!unwrappedArg0 || !unwrappedArg1) { + Q_ASSERT(false); + return; + } + callbackThis = unwrappedArg0->toV8Value(); + callback = unwrappedArg1->toV8Value(); + } else { + auto unwrappedArg0 = ScriptValueV8Wrapper::unwrap(arg0); + if (!unwrappedArg0) { + Q_ASSERT(false); + return; + } + callback = unwrappedArg0->toV8Value(); + } + if (!callback.get()->IsFunction()) { + isolate->ThrowError("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 + withWriteLock([&]{ + _connections.erase(lookup); + }); + } + + // remove a reference to ourselves from the destination callback + v8::Local destFunction = v8::Local::Cast(callback.get()); + v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); + v8::Local destData; + + //auto destFunctionContext = destFunction->CreationContext(); + auto destFunctionContext = _engine->getContext(); + Q_ASSERT(thisObject().isObject()); + V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); + Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)); + // For debugging + ScriptSignalV8Proxy* thisProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8ThisObject)->toQObject()); + Q_ASSERT(thisProxy); + //qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::disconnect: " << thisProxy->fullName() << " fullName: " << fullName(); + if (!destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData)) { + Q_ASSERT(false); + } + if (destData->IsArray()) { + v8::Local destArray = v8::Local::Cast(destData); + int length = destArray->Length(); + v8::Local newArray = v8::Array::New(isolate, length - 1); + bool foundIt = false; + int newIndex = 0; + for (int idx = 0; idx < length; ++idx) { + v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); + // For debugging: + { + //qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::disconnect: entry details: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, entry)) + // << " Array: " << _engine->scriptValueDebugDetailsV8(V8ScriptValue(_engine, destArray)); + Q_ASSERT(entry->IsObject()); + V8ScriptValue v8EntryObject(_engine, entry); + Q_ASSERT(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)); + // For debugging + //ScriptSignalV8Proxy* entryProxy = dynamic_cast(ScriptObjectV8Proxy::unwrapProxy(v8EntryObject)->toQObject()); + Q_ASSERT(thisProxy); + //qCDebug(scriptengine_v8) << "ScriptSignalV8Proxy::disconnect: entry proxy: " << entryProxy->fullName(); + } + if (entry->StrictEquals(v8ThisObject.get())) { + foundIt = true; + } else { + if (!newArray->Set(destFunctionContext, newIndex, entry).FromMaybe(false)) { + Q_ASSERT(false); + } + newIndex++; + } + } + Q_ASSERT(foundIt); + if (!destFunction->Set(destFunctionContext, destDataName, newArray).FromMaybe(false)) { + Q_ASSERT(false); + } + } else { + Q_ASSERT(false); + } + + // 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/v8/ScriptObjectV8Proxy.h b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h new file mode 100644 index 00000000000..eb1e51ccdcc --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h @@ -0,0 +1,294 @@ +// +// ScriptObjectV8Proxy.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 12/5/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptObjectV8Proxy_h +#define hifi_ScriptObjectV8Proxy_h + +#include +#include +#include +#include + +#include "../ScriptEngine.h" +#include "../Scriptable.h" +#include "ScriptEngineV8.h" +#include "V8Types.h" + +#include + +class ScriptEngineV8; +class ScriptSignalV8Proxy; + +// V8TODO: Current implementation relies on weak handle callbacks for destroying objects on C++ side +// this is fine for avoiding memory leaks while script engine runs, but there's no guarantee that these will be called +// when script engine shuts down, so memory leaks may happen +// To avoid this handle visitor needs to be added (it's a feature of V8) + +/// [V8] (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 ScriptObjectV8Proxy final { +private: // implementation + class PropertyDef { + public: + PropertyDef(QString string, uint id) : name(string), _id(id) {}; + QString name; + ScriptValue::PropertyFlags flags; + uint _id; + }; + class MethodDef { + public: + MethodDef(QString string, uint id) : name(string), _id(id) {}; + QString name; + int numMaxParams; + QList methods; + uint _id; + }; + class SignalDef { + public: + SignalDef(QString string, uint id) : name(string), _id(id) {}; + QString name; + QMetaMethod signal; + uint _id; + }; + using PropertyDefMap = QHash; + using MethodDefMap = QHash; + using SignalDefMap = QHash; + using InstanceMap = QHash >; + using PropertyNameMap = QHash; + using MethodNameMap = QHash; + using SignalNameMap = 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 + ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options); + virtual ~ScriptObjectV8Proxy(); + + static V8ScriptValue newQObject(ScriptEngineV8* engine, + QObject* object, + ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, + const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()); + static ScriptObjectV8Proxy* unwrapProxy(const V8ScriptValue& val); + static ScriptObjectV8Proxy* unwrapProxy(v8::Isolate* isolate, v8::Local& value); + static QObject* unwrap(const V8ScriptValue& val); + inline QObject* toQObject() const { return _object; } + inline v8::Local toV8Value() const { + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_v8Object.Get(_engine->getIsolate())); + } + +public: + enum QueryFlag + { + HandlesReadAccess = 0x00000001, + HandlesWriteAccess = 0x00000002, + }; + Q_DECLARE_FLAGS(QueryFlags, QueryFlag); + + virtual QString name() const; + + virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id); + virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id); + virtual QueryFlags queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id); + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value); + v8::Local getPropertyNames(); + static void v8Get(v8::Local name, const v8::PropertyCallbackInfo& info); + static void v8Set(v8::Local name, v8::Local value_obj, const v8::PropertyCallbackInfo& info); + static void v8GetPropertyNames(const v8::PropertyCallbackInfo& info); + +private: // implementation + void investigate(); + // This gets called when script-owned object is being garbage-collected + static void weakHandleCallback(const v8::WeakCallbackInfo &info); + +private: // storage + ScriptEngineV8* _engine; + const ScriptEngine::QObjectWrapOptions _wrapOptions; + PropertyDefMap _props; + MethodDefMap _methods; + SignalDefMap _signals; + // These are used for property lookups from V8 callbacks + PropertyNameMap _propNameMap; + MethodNameMap _methodNameMap; + SignalNameMap _signalNameMap; + InstanceMap _signalInstances; + const bool _ownsObject; + QPointer _object; + // Handle for its own object + v8::Persistent _v8Object; + + Q_DISABLE_COPY(ScriptObjectV8Proxy) +}; + +/** + * @brief [V8] (re-)implements the translation layer between ScriptValue and QVariant where a prototype is set. + * + * This object depends on a ScriptObjectV8Proxy to provide the prototype's behavior. + * ScriptVariantV8Proxy uses prototype class which provides methods which operate on QVariant. + * Typically it's used for class with larger number of methods which has a simplified JS API. + * For example it's used to provide JS scripting interface to AnimationPointer by using methods of AnimationObject. + * To use this functionality, given type has to be registered with script engine together with its prototype: + * + * engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationObject(), ScriptEngine::ScriptOwnership)); + * + */ + // V8TODO: there may be memory leaks in these, it's worth checking if the proxy actually gets garbage-collected and destroyed +class ScriptVariantV8Proxy final { +public: // construction + ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto); + ~ScriptVariantV8Proxy(); + + static V8ScriptValue newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto); + static ScriptVariantV8Proxy* unwrapProxy(const V8ScriptValue& val); + static ScriptVariantV8Proxy* unwrapProxy(v8::Isolate* isolate, v8::Local &value); + /** + * @brief Used to retrieve QVariant pointer contained inside script value. This is indirectly used by ScriptVariantV8Proxy + * getters and setters through scriptvalue_cast and ScriptEngineV8::castValueToVariant. + */ + static QVariant* unwrapQVariantPointer(v8::Isolate* isolate, const v8::Local &value); + static QVariant unwrap(const V8ScriptValue& val); + inline QVariant toQVariant() const { return _variant; } + inline v8::Local toV8Value() const { + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_v8Object.Get(_engine->getIsolate())); + } + +public: // QScriptClass implementation + virtual QString name() const { return _name; } + + virtual V8ScriptValue prototype() const { return _scriptProto; } + + virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + return _proto->property(object, name, id); + } + virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + return _proto->propertyFlags(object, name, id); + } + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { + return _proto->setProperty(object, name, id, value); + } + static void v8Get(v8::Local name, const v8::PropertyCallbackInfo& info); + static void v8Set(v8::Local name, v8::Local value_obj, const v8::PropertyCallbackInfo& info); + static void v8GetPropertyNames(const v8::PropertyCallbackInfo& info); + +private: + ScriptEngineV8* _engine; + QVariant _variant; + V8ScriptValue _scriptProto; + ScriptObjectV8Proxy* _proto; + QString _name; + v8::UniquePersistent _v8Object; + + Q_DISABLE_COPY(ScriptVariantV8Proxy) +}; + +class ScriptMethodV8Proxy final : public QObject { + Q_OBJECT +public: // construction + ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams); + virtual ~ScriptMethodV8Proxy(); + +public: // QScriptClass implementation + virtual QString name() const { return fullName(); } + static void callback(const v8::FunctionCallbackInfo& arguments); + void call(const v8::FunctionCallbackInfo& arguments); + static V8ScriptValue newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams); + +private: + static void weakHandleCallback(const v8::WeakCallbackInfo &info); + QString fullName() const; + +private: // storage + const int _numMaxParams; + ScriptEngineV8* _engine; + QPointer _object; + v8::Persistent _objectLifetime; + //V8ScriptValue _objectLifetime; + const QList _metas; + + Q_DISABLE_COPY(ScriptMethodV8Proxy) +}; + +// This abstract base class serves solely to declare the Q_INVOKABLE methods for ScriptSignalV8Proxy +// as we're overriding qt_metacall later for the signal callback yet still want to support +// metacalls for the connect/disconnect API +class ScriptSignalV8ProxyBase : public QObject, protected Scriptable { + Q_OBJECT +public: // API + // arg1 was had Null default value, but that needs isolate pointer in V8 + Q_INVOKABLE virtual void connect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) = 0; + Q_INVOKABLE virtual void disconnect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) = 0; +}; + +class ScriptSignalV8Proxy final : public ScriptSignalV8ProxyBase, public ReadWriteLockable { +private: // storage + class Connection { + public: + V8ScriptValue thisValue; + V8ScriptValue callback; + Connection(const V8ScriptValue &v8ThisValue, const V8ScriptValue &v8Callback) : + thisValue(v8ThisValue), callback(v8Callback) {}; + }; + using ConnectionList = QList; + +public: // construction + inline ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta); + + ~ScriptSignalV8Proxy(); + +private: // implementation + virtual int qt_metacall(QMetaObject::Call call, int id, void** arguments) override; + int discoverMetaCallIdx(); + ConnectionList::iterator findConnection(V8ScriptValue thisObject, V8ScriptValue callback); + //QString fullName() const; + static void weakHandleCallback(const v8::WeakCallbackInfo &info); + +public: // API + // arg1 was had Null default value, but that needs isolate pointer to create Null in V8 + virtual void connect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) override; + virtual void disconnect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) override; + //Moved to public temporarily for debugging: + QString fullName() const; + +private: // storage + + ScriptEngineV8* _engine; + QPointer _object; + v8::Persistent _objectLifetime; + + const QMetaMethod _meta; + const int _metaCallId; + ConnectionList _connections; + bool _isConnected{ false }; + // Context in which it was created + v8::UniquePersistent _v8Context; + // Call counter for debugging purposes. It can be used to determine which signals are overwhelming script engine. + int _callCounter{0}; + float _totalCallTime_s{ 0.0 }; + + Q_DISABLE_COPY(ScriptSignalV8Proxy) +}; + +#endif // hifi_ScriptObjectV8Proxy_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp new file mode 100644 index 00000000000..71a08745136 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp @@ -0,0 +1,79 @@ +// +// ScriptProgramV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/24/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptProgramV8Wrapper.h" + +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" + +ScriptProgramV8Wrapper* ScriptProgramV8Wrapper::unwrap(ScriptProgramPointer val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val.get()); +} + +ScriptSyntaxCheckResultPointer ScriptProgramV8Wrapper::checkSyntax() { + if (!_isCompiled) { + compile(); + } + return std::make_shared(_compileResult); +} + +bool ScriptProgramV8Wrapper::compile() { + if (_isCompiled) { + return true; + } + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(context); + int errorColumnNumber = 0; + int errorLineNumber = 0; + QString errorMessage = ""; + QString errorBacktrace = ""; + v8::TryCatch tryCatch(isolate); + v8::ScriptOrigin scriptOrigin(isolate, v8::String::NewFromUtf8(isolate, _url.toStdString().c_str()).ToLocalChecked()); + v8::Local script; + if (v8::Script::Compile(context, v8::String::NewFromUtf8(isolate, _source.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + qCDebug(scriptengine_v8) << "Script compilation successful: " << _url; + _compileResult = ScriptSyntaxCheckResultV8Wrapper(ScriptSyntaxCheckResult::Valid); + _value = V8ScriptProgram(_engine, script); + _isCompiled = true; + return true; + } + qCDebug(scriptengine_v8) << "Script compilation failed: " << _url; + v8::String::Utf8Value utf8Value(isolate, tryCatch.Exception()); + errorMessage = QString(*utf8Value); + v8::Local exceptionMessage = tryCatch.Message(); + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(context).FromJust(); + errorColumnNumber = exceptionMessage->GetStartColumn(context).FromJust(); + v8::Local backtraceV8String; + if (tryCatch.StackTrace(context).ToLocal(&backtraceV8String)) { + if (backtraceV8String->IsString()) { + if (v8::Local::Cast(backtraceV8String)->Length() > 0) { + v8::String::Utf8Value backtraceUtf8Value(isolate, backtraceV8String); + errorBacktrace = *backtraceUtf8Value; + } + } + } + } + _compileResult = ScriptSyntaxCheckResultV8Wrapper(ScriptSyntaxCheckResult::Error, errorColumnNumber, errorLineNumber, errorMessage, errorBacktrace); + return false; +} diff --git a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h new file mode 100644 index 00000000000..f374964b616 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h @@ -0,0 +1,83 @@ +// +// ScriptProgramV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/21/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptProgramV8Wrapper_h +#define hifi_ScriptProgramV8Wrapper_h + +#include + +#include "../ScriptProgram.h" +#include "ScriptEngineV8.h" +#include "V8Types.h" + + +// V8TODO: This class is likely unnecessary, and it'd be enough +// to just use a non-abstract version of ScriptSyntaxCheckResult instead. +class ScriptSyntaxCheckResultV8Wrapper final : public ScriptSyntaxCheckResult { +public: // construction + inline ScriptSyntaxCheckResultV8Wrapper() : _errorColumnNumber(0), _errorLineNumber(0), _errorMessage("Not compiled"), _state(ScriptSyntaxCheckResult::Error) {} + inline ScriptSyntaxCheckResultV8Wrapper(State state, int columnNumber = 0, int lineNumber = 0, const QString &message = QString(""), const QString &errorBacktrace = QString("")) : + _errorColumnNumber(columnNumber), _errorLineNumber(lineNumber), _errorMessage(message), _state(state) {} + +public: // ScriptSyntaxCheckResult implementation + virtual int errorColumnNumber() const override {return _errorColumnNumber;} + virtual int errorLineNumber() const override {return _errorLineNumber;} + virtual QString errorMessage() const override {return _errorMessage;} + virtual QString errorBacktrace() const override {return _errorBacktrace;} + virtual State state() const override {return _state;} + +private: // storage + int _errorColumnNumber; + int _errorLineNumber; + QString _errorMessage; + QString _errorBacktrace; + State _state; +}; + +/// [V8] Implements ScriptProgram for V8 and translates calls for V8ScriptProgram +class ScriptProgramV8Wrapper final : public ScriptProgram { +public: // construction + inline ScriptProgramV8Wrapper(ScriptEngineV8* engine, QString source, QString url) : + _engine(engine), _source(source), _url(url), _value(_engine) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + _value = V8ScriptProgram(engine, v8::Local()); + } + static ScriptProgramV8Wrapper* unwrap(ScriptProgramPointer val); + bool compile(); + inline const V8ScriptProgram& toV8Value() const { return _value; } + +public: // ScriptProgram implementation + virtual ScriptSyntaxCheckResultPointer checkSyntax() override; + virtual QString fileName() const override {return _url;} + virtual QString sourceCode() const override {return _source;} + +private: // storage + ScriptEngineV8 *_engine; + QString _source; + QString _url; + V8ScriptProgram _value; + bool _isCompiled = false; + ScriptSyntaxCheckResultV8Wrapper _compileResult; +}; + +#endif // hifi_ScriptValueV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp new file mode 100644 index 00000000000..7bfff6edd01 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp @@ -0,0 +1,112 @@ +// +// ScriptValueIteratorV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/29/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptValueIteratorV8Wrapper.h" +#include "ScriptEngineLoggingV8.h" + +V8ScriptValueIterator::V8ScriptValueIterator(ScriptEngineV8* engine, v8::Local object) : _engine(engine) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + _context.Reset(isolate, _engine->getContext()); + auto context = _context.Get(isolate); + v8::Context::Scope contextScope(context); + Q_ASSERT(object->IsObject()); + v8::Local v8Object = v8::Local::Cast(object); + _object.Reset(isolate, v8Object); + _propertyNames.Reset(isolate, v8Object->GetOwnPropertyNames(context).ToLocalChecked()); + _length = _propertyNames.Get(isolate)->Length(); + _currentIndex = -1; +} + +V8ScriptValueIterator::~V8ScriptValueIterator() { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + _propertyNames.Reset(); + _object.Reset(); + _context.Reset(); +} + +bool V8ScriptValueIterator::hasNext() const { + return _currentIndex < _length - 1; +} + +QString V8ScriptValueIterator::name() const { + Q_ASSERT(_currentIndex >= 0); + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _context.Get(isolate); + v8::Context::Scope contextScope(context); + v8::Local propertyName; + if (!_propertyNames.Get(isolate)->Get(context, _currentIndex).ToLocal(&propertyName)) { + Q_ASSERT(false); + } + return QString(*v8::String::Utf8Value(isolate, propertyName)); +} + +void V8ScriptValueIterator::next() { + if (_currentIndex < _length - 1) { + _currentIndex++; + } +} + +V8ScriptValue V8ScriptValueIterator::value() { + Q_ASSERT(_currentIndex >= 0); + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _context.Get(isolate); + v8::Context::Scope contextScope(context); + v8::Local v8Value; + v8::Local propertyName; + if (!_propertyNames.Get(isolate)->Get(context, _currentIndex).ToLocal(&propertyName)) { + Q_ASSERT(false); + } + if (!_object.Get(isolate)->Get(context, propertyName->ToString(context).ToLocalChecked()).ToLocal(&v8Value)) { + Q_ASSERT(false); + } + if (v8Value.IsEmpty()) { + qDebug() << "V8ScriptValueIterator::value: value handle is empty for key: " << *v8::String::Utf8Value(isolate, propertyName->ToString(context).ToLocalChecked()); + v8Value = v8::Undefined(isolate); + } + return V8ScriptValue(_engine, v8Value); +} + +ScriptValue::PropertyFlags ScriptValueIteratorV8Wrapper::flags() const { + //V8TODO + return ScriptValue::PropertyFlags(); +} + +bool ScriptValueIteratorV8Wrapper::hasNext() const { + return _value->hasNext(); +} + +QString ScriptValueIteratorV8Wrapper::name() const { + return _value->name(); +} + +void ScriptValueIteratorV8Wrapper::next() { + _value->next(); +} + +ScriptValue ScriptValueIteratorV8Wrapper::value() const { + V8ScriptValue result = _value->value(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} diff --git a/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h new file mode 100644 index 00000000000..99d98a6258b --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h @@ -0,0 +1,67 @@ +// +// ScriptValueIteratorV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/29/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueIteratorV8Wrapper_h +#define hifi_ScriptValueIteratorV8Wrapper_h + +#include + +#include "../ScriptValueIterator.h" +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" + +class V8ScriptValueIterator { +public: + V8ScriptValueIterator(ScriptEngineV8* engine, v8::Local object); + ~V8ScriptValueIterator(); + bool hasNext() const; + QString name() const; + void next(); + V8ScriptValue value(); +private: + v8::UniquePersistent _propertyNames; + v8::UniquePersistent _object; + v8::UniquePersistent _context; + int _length; + int _currentIndex; + ScriptEngineV8 *_engine; + Q_DISABLE_COPY(V8ScriptValueIterator) +}; + +/// [V8] Implements ScriptValueIterator for V8 and translates calls for V8ScriptValueIterator +class ScriptValueIteratorV8Wrapper final : public ScriptValueIterator { +public: // construction + inline ScriptValueIteratorV8Wrapper(ScriptEngineV8* engine, const ScriptValue& object) : + _engine(engine), _value(new V8ScriptValueIterator(engine, ScriptValueV8Wrapper::fullUnwrap(engine, object).get())) {} + inline ScriptValueIteratorV8Wrapper(ScriptEngineV8* engine, const V8ScriptValue& object) : + _engine(engine), _value(new V8ScriptValueIterator(engine, object.constGet())) {} + +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 + ScriptEngineV8 *_engine; + std::shared_ptr _value; +}; + +#endif // hifi_ScriptValueIteratorV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp new file mode 100644 index 00000000000..c4c172d218b --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp @@ -0,0 +1,752 @@ +// +// ScriptValueV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/16/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include "ScriptValueV8Wrapper.h" + +#include "ScriptValueIteratorV8Wrapper.h" + +#include "../ScriptEngineLogging.h" +#include "ScriptEngineLoggingV8.h" + +void ScriptValueV8Wrapper::release() { + // Check if ScriptValueV8Wrapper::release was called from inside ScriptValueV8Wrapper functions, and if so, delete it later + // This prevents access-after-delete crashes when ScriptValueV8Wrapper::release is called from inside JS executed in + // ScriptValueV8Wrapper::call, ScriptValueV8Wrapper::construct and others + if (lock.tryLockForWrite()) { + lock.unlock(); + delete this; + } else { + _engine->scheduleValueWrapperForDeletion(this); + } +} + +ScriptValueProxy* ScriptValueV8Wrapper::copy() const { + //V8TODO: check if the value needs to be copied or just wrapper + v8::Isolate *isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + // V8TODO: I'm not sure if this part is right: + ScriptValueV8Wrapper *copiedWrapper = new ScriptValueV8Wrapper(_engine, _value); + return copiedWrapper; +} + +ScriptValueV8Wrapper* ScriptValueV8Wrapper::unwrap(const ScriptValue& val) { + return dynamic_cast(val.ptr()); +} + +V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(const ScriptValue& value) const { + ScriptValueV8Wrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != _engine) { + return _engine->castVariantToValue(unwrapped->toVariant()); + } else { + return unwrapped->toV8Value(); + } + } + QVariant varValue = value.toVariant(); + return _engine->castVariantToValue(varValue); +} + +V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(ScriptEngineV8* engine, const ScriptValue& value) { + ScriptValueV8Wrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != engine) { + return engine->castVariantToValue(unwrapped->toVariant()); + } else { + return unwrapped->toV8Value(); + } + } + QVariant varValue = value.toVariant(); + return engine->castVariantToValue(varValue); +} + +ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValueList& args) { + Q_ASSERT(_engine == _value.getEngine()); + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(context); + V8ScriptValue v8This = fullUnwrap(thisObject); + Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS); + v8::Local v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS]; + int argIndex = 0; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + v8Args[argIndex++] = fullUnwrap(*iter).get(); + } + Q_ASSERT(_value.get()->IsFunction()); + v8::Local v8Function = v8::Local::Cast(_value.get()); + v8::TryCatch tryCatch(isolate); + v8::Local recv; + if (v8This.get()->IsObject()) { + recv = v8This.get(); + }else{ + recv = _engine->getContext()->Global(); + } + + lock.lockForRead(); + auto maybeResult = v8Function->Call(_engine->getContext(), recv, args.length(), v8Args); + lock.unlock(); + if (tryCatch.HasCaught()) { + qCDebug(scriptengine_v8) << "Function call failed: \"" << _engine->formatErrorMessageFromTryCatch(tryCatch); + } + v8::Local result; + Q_ASSERT(_engine == _value.getEngine()); + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); + } else { + //V8TODO Add more details + qCWarning(scriptengine_v8) << "JS function call failed"; + return _engine->undefinedValue(); + } +} + +ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValue& arguments) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + V8ScriptValue v8This = fullUnwrap(thisObject); + V8ScriptValue v8Args = fullUnwrap(arguments); + // V8TODO should there be a v8 try-catch here? + // IsFunction check should be here probably + // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used + // V8TODO: this seems to never be used? + Q_ASSERT(false); + return _engine->undefinedValue(); + /*v8::Local v8Function = v8::Local::Cast(_value.get()); + auto maybeResult = v8Function->Call(_engine->getContext(), v8This, v8Args); + v8::Local result; + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getContext(), result))); + } else { + //V8TODO Add more details + qCWarning(scriptengine_v8) << "JS function call failed"; + return _engine->undefinedValue(); + }*/ +} + +ScriptValue ScriptValueV8Wrapper::construct(const ScriptValueList& args) { + //V8TODO: there is CallAsContructor in V8 + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS); + v8::Local v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS]; + int argIndex = 0; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + v8Args[argIndex++] = fullUnwrap(*iter).get(); + } + //V8TODO: should there be a v8 try-catch here? + //V8TODO: Can something else than a function be callable in this way in JS? + if (!_value.get()->IsFunction()) { + qCWarning(scriptengine_v8) << "ScriptValueV8Wrapper::construct: value is not a function"; + return _engine->undefinedValue(); + } + + v8::Local v8Function = v8::Local::Cast(_value.get()); + // V8TODO: I'm not sure if this is correct, maybe use CallAsConstructor instead? + // Maybe it's CallAsConstructor for function and NewInstance for class? + lock.lockForRead(); + auto maybeResult = v8Function->NewInstance(_engine->getContext(), args.length(), v8Args); + lock.unlock(); + v8::Local result; + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, result))); + } else { + //V8TODO Add more details + qCWarning(scriptengine_v8) << "JS function call failed"; + return _engine->undefinedValue(); + } +} + +// V8TODO: this seems to never be used? +ScriptValue ScriptValueV8Wrapper::construct(const ScriptValue& arguments) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used + Q_ASSERT(false); + return _engine->undefinedValue(); + //V8ScriptValue unwrapped = fullUnwrap(arguments); + //V8ScriptValue result = _value.construct(unwrapped); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + +// V8TODO: check how data() is used and if it needs fixing +ScriptValue ScriptValueV8Wrapper::data() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + // Private properties are an experimental feature for now on V8, so we are using regular value for now + if (_value.constGet()->IsObject()) { + auto v8Object = v8::Local::Cast(_value.constGet()); + v8::Local data; + //bool createData = false; + if (!v8Object->Get(_engine->getContext(), v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked()).ToLocal(&data)) { + data = v8::Undefined(isolate); + Q_ASSERT(false); + //createData = true; + } + /*else { + if (data->IsUndefined()) { + createData = true; + } + } + if (createData) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object doesn't exist, creating new one"; + // Create data object if it's non-existent or invalid + data = v8::Object::New(isolate); + if( !v8Object->Set(_engine->getContext(), v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked(), data).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object couldn't be created"; + Q_ASSERT(false); + } + }*/ + V8ScriptValue result(_engine, data); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } else { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data() was called on a value that is not an object"; + Q_ASSERT(false); + } + //V8TODO I'm not sure how this would work in V8 + //V8ScriptValue result = _value.data(); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + return _engine->nullValue(); +} + +ScriptEnginePointer ScriptValueV8Wrapper::engine() const { + if (!_engine) { + return ScriptEnginePointer(); + } + return _engine->shared_from_this(); +} + +ScriptValueIteratorPointer ScriptValueV8Wrapper::newIterator() const { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope contextScope(_engine->getContext()); + ScriptValueIteratorPointer iterator = std::make_shared(_engine, _value); + return iterator; +} + +bool ScriptValueV8Wrapper::hasProperty(const QString& name) const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + //V8TODO: does function return true on IsObject too? + if (_value.constGet()->IsObject()) { + //V8TODO: what about flags? + v8::Local resultLocal; + v8::Local key = v8::String::NewFromUtf8(isolate, name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + const v8::Local object = v8::Local::Cast(_value.constGet()); + //V8TODO: Which context? + if (object->Get(_engine->getContext(), key).ToLocal(&resultLocal)) { + return true; + } else { + return false; + } + } + return false; +} + + + +ScriptValue ScriptValueV8Wrapper::property(const QString& name, const ScriptValue::ResolveFlags &mode) const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + if (_value.constGet()->IsNullOrUndefined()) { + return _engine->undefinedValue(); + } + if (_value.constGet()->IsObject()) { + //V8TODO: what about flags? + v8::Local resultLocal; + v8::Local key = v8::String::NewFromUtf8(_engine->getIsolate(), name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + const v8::Local object = v8::Local::Cast(_value.constGet()); + //V8TODO: Which context? + lock.lockForRead(); + if (object->Get(_engine->getContext(), key).ToLocal(&resultLocal)) { + V8ScriptValue result(_engine, resultLocal); + lock.unlock(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } else { + QString parentValueQString(""); + v8::Local parentValueString; + if (_value.constGet()->ToDetailString(_engine->getContext()).ToLocal(&parentValueString)) { + QString(*v8::String::Utf8Value(isolate, parentValueString)); + } + qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << name << ", parent type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate))) << " parent value: " << parentValueQString; + } + } + if (name == QString("x")) { + printf("x"); + } + return _engine->undefinedValue(); +} + +ScriptValue ScriptValueV8Wrapper::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + if (_value.constGet()->IsNullOrUndefined()) { + qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << arrayIndex << " is not a V8 object, reported type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate))); + return _engine->undefinedValue(); + } + if (_value.constGet()->IsObject()) { + //V8TODO: what about flags? + v8::Local resultLocal; + const v8::Local object = v8::Local::Cast(_value.constGet()); + lock.lockForRead(); + if (object->Get(_value.constGetContext(), arrayIndex).ToLocal(&resultLocal)) { + V8ScriptValue result(_engine, resultLocal); + lock.unlock(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } + lock.unlock(); + } + qCDebug(scriptengine_v8) << "Failed to get property, parent of value: " << arrayIndex << " is not a V8 object, reported type: " << QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate))); + return _engine->undefinedValue(); +} + +ScriptValue ScriptValueV8Wrapper::prototype() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + + auto value = _value.constGet(); + if (!value->IsObject()) { + return _engine->undefinedValue(); + } + auto object = v8::Local::Cast(value); + auto prototype = object->GetPrototype(); + + V8ScriptValue result(_engine, prototype); + return ScriptValue(new ScriptValueV8Wrapper(_engine, result)); +} + +void ScriptValueV8Wrapper::setData(const ScriptValue& value) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + V8ScriptValue unwrapped = fullUnwrap(value); + // Private properties are an experimental feature for now on V8, so we are using regular value for now + if (_value.constGet()->IsNullOrUndefined()) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setData() was called on a value that is null or undefined"; + return; + } + if (_value.constGet()->IsObject()) { + auto v8Object = v8::Local::Cast(_value.constGet()); + if( !v8Object->Set(_engine->getContext(), v8::String::NewFromUtf8(isolate, "__data").ToLocalChecked(), unwrapped.constGet()).FromMaybe(false)) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data(): Data object couldn't be created"; + Q_ASSERT(false); + } + } else { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::data() was called on a value that is not an object"; + Q_ASSERT(false); + } +} + +void ScriptValueV8Wrapper::setProperty(const QString& name, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + Q_ASSERT(flags != ScriptValue::PropertyGetter || flags != ScriptValue::PropertySetter); + auto isolate = _engine->getIsolate(); + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + V8ScriptValue unwrapped = fullUnwrap(value); + if (_value.constGet()->IsNullOrUndefined()) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setProperty() was called on a value that is null or undefined"; + return; + } + if(_value.constGet()->IsObject()) { + v8::Local key = v8::String::NewFromUtf8(isolate, name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + Q_ASSERT(_value.get()->IsObject()); + auto object = v8::Local::Cast(_value.get()); + lock.lockForRead(); + v8::Maybe retVal = object->Set(isolate->GetCurrentContext(), key, unwrapped.constGet()); + lock.unlock(); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qCDebug(scriptengine_v8) << "Failed to set property"; + } + } else { + v8::Local details; + QString detailsString(""); + if(_value.get()->ToDetailString(_engine->getContext()).ToLocal(&details)) { + v8::String::Utf8Value utf8Value(isolate,details); + detailsString = *utf8Value; + } + qCDebug(scriptengine_v8) << "Failed to set property:" + name + " - parent is not an object. Parent details: " + " Type: " + QString(*v8::String::Utf8Value(isolate, _value.constGet()->TypeOf(isolate))); + qCDebug(scriptengine_v8) << _engine->currentContext()->backtrace(); + } + //V8TODO: what about flags? + //_value.setProperty(name, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueV8Wrapper::setProperty(quint32 arrayIndex, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + V8ScriptValue unwrapped = fullUnwrap(value); + if (_value.constGet()->IsNullOrUndefined()) { + qCDebug(scriptengine_v8) << "ScriptValueV8Wrapper::setProperty() was called on a value that is null or undefined"; + return; + } + if(_value.constGet()->IsObject()) { + auto object = v8::Local::Cast(_value.get()); + //V8TODO: I don't know which context to use here + lock.lockForRead(); + v8::Maybe retVal(object->Set(_engine->getContext(), arrayIndex, unwrapped.constGet())); + lock.unlock(); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qCDebug(scriptengine_v8) << "Failed to set property"; + } + } else { + qCDebug(scriptengine_v8) << "Failed to set property: " + QString(arrayIndex) + " - parent is not an object"; + } + //V8TODO: what about flags? + //_value.setProperty(arrayIndex, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueV8Wrapper::setPrototype(const ScriptValue& prototype) { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(context); + ScriptValueV8Wrapper* unwrappedPrototype = unwrap(prototype); + if (unwrappedPrototype) { + if(unwrappedPrototype->toV8Value().constGet()->IsNullOrUndefined() && _value.constGet()->IsNullOrUndefined()) { + qCDebug(scriptengine_v8) << "Failed to assign prototype - one of values is null or undefined"; + } + if(unwrappedPrototype->toV8Value().constGet()->IsObject() && _value.constGet()->IsObject()) { + auto object = v8::Local::Cast(_value.get()); + //V8TODO: I don't know which context to use here + lock.lockForRead(); + v8::Maybe retVal = object->SetPrototype(context, unwrappedPrototype->toV8Value().constGet()); + lock.unlock(); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qCDebug(scriptengine_v8) << "Failed to assign prototype"; + } + } else { + qCDebug(scriptengine_v8) << "Failed to assign prototype - one of values is not an object"; + } + } +} + +bool ScriptValueV8Wrapper::strictlyEquals(const ScriptValue& other) const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + ScriptValueV8Wrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.constGet()->StrictEquals(unwrappedOther->toV8Value().constGet()) : false; +} + +inline QList ScriptValueV8Wrapper::getPropertyNames() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(context); + v8::Local value = _value.constGet(); + if (value->IsNullOrUndefined()) { + return QList(); + } + if (!value->IsObject()) { + return QList(); + } + v8::Local object = v8::Local::Cast(value); + v8::Local array; + if (!object->GetPropertyNames(context).ToLocal(&array)) { + return QList(); + } + QList names; + for (uint32_t n = 0; n < array->Length(); n++) { + v8::Local name = array->Get(context, n).ToLocalChecked()->ToString(context).ToLocalChecked(); + names.append(*v8::String::Utf8Value(isolate, name)); + } + return names; +} + +bool ScriptValueV8Wrapper::toBool() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->ToBoolean(_engine->getIsolate())->Value(); +} + +qint32 ScriptValueV8Wrapper::toInt32() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local integer; + if (!_value.constGet()->ToInteger(_engine->getContext()).ToLocal(&integer)) { + Q_ASSERT(false); + } + return static_cast((integer)->Value()); +} + +double ScriptValueV8Wrapper::toInteger() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local integer; + if (!_value.constGet()->ToInteger(_engine->getContext()).ToLocal(&integer)) { + Q_ASSERT(false); + } + return (integer)->Value(); +} + +double ScriptValueV8Wrapper::toNumber() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local number; + if (!_value.constGet()->ToNumber(_engine->getContext()).ToLocal(&number)) { + Q_ASSERT(false); + } + return number->Value(); +} + +QString ScriptValueV8Wrapper::toString() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::String::Utf8Value string(_engine->getIsolate(), _value.constGet()); + Q_ASSERT(*string != nullptr); + return QString(*string); +} + +quint16 ScriptValueV8Wrapper::toUInt16() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local integer; + if (!_value.constGet()->ToUint32(_engine->getContext()).ToLocal(&integer)) { + Q_ASSERT(false); + } + return static_cast(integer->Value()); +} + +quint32 ScriptValueV8Wrapper::toUInt32() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local integer; + if (!_value.constGet()->ToUint32(_engine->getContext()).ToLocal(&integer)) { + Q_ASSERT(false); + } + return integer->Value(); +} + +QVariant ScriptValueV8Wrapper::toVariant() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::UnknownType)) { + return dest; + } else { + Q_ASSERT(false); + return QVariant(); + } +} + +QObject* ScriptValueV8Wrapper::toQObject() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::QObjectStar)) { + if (dest.canConvert()) { + return dest.value(); + } else { + //Q_ASSERT(false); + return nullptr; + } + } else { + //Q_ASSERT(false); + return nullptr; + } +} + +bool ScriptValueV8Wrapper::equals(const ScriptValue& other) const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + ScriptValueV8Wrapper* unwrappedOther = unwrap(other); + Q_ASSERT(_engine->getIsolate() == unwrappedOther->_engine->getIsolate()); + if (!unwrappedOther) { + return false; + }else{ + if (_value.constGet()->Equals(_engine->getContext(), unwrappedOther->toV8Value().constGet()).IsNothing()) { + return false; + } else { + return _value.constGet()->Equals(_engine->getContext(), unwrappedOther->toV8Value().constGet()).FromJust(); + } + } +} + +bool ScriptValueV8Wrapper::isArray() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsArray(); +} + +bool ScriptValueV8Wrapper::isBool() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsBoolean(); +} + +bool ScriptValueV8Wrapper::isError() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + auto context = _engine->getContext(); + v8::Context::Scope contextScope(_engine->getContext()); + v8::Local error; + if (!context->Global()->Get(context, v8::String::NewFromUtf8(isolate, "Error").ToLocalChecked()).ToLocal(&error)) { + Q_ASSERT(false); + } + if (!error->IsObject()) { + Q_ASSERT(false); + } + auto errorObj = v8::Local::Cast(error); + if (_value.constGet()->InstanceOf(context, errorObj).FromMaybe(false)) { + return true; + } + return false; +} + +bool ScriptValueV8Wrapper::isFunction() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsFunction(); +} + +bool ScriptValueV8Wrapper::isNumber() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsNumber(); +} + +bool ScriptValueV8Wrapper::isNull() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsNull(); +} + +bool ScriptValueV8Wrapper::isObject() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsObject(); +} + +bool ScriptValueV8Wrapper::isString() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsString(); +} + +bool ScriptValueV8Wrapper::isUndefined() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + return _value.constGet()->IsUndefined(); +} + +bool ScriptValueV8Wrapper::isValid() const { + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + if (_value.constGet()->IsNullOrUndefined()) { + return false; + } + return true; +} + +bool ScriptValueV8Wrapper::isVariant() const { + //V8TODO: check if it's variant proxy? I'm not sure though. + auto isolate = _engine->getIsolate(); + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(_engine->getContext()); + Q_ASSERT(false); + return false; + //return _value.isVariant(); +} diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.h b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.h new file mode 100644 index 00000000000..14340dc2f41 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.h @@ -0,0 +1,145 @@ +// +// ScriptValueV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/16/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueV8Wrapper_h +#define hifi_ScriptValueV8Wrapper_h + +#include + +#include + +#include "../ScriptValue.h" +#include "ScriptEngineV8.h" +#include "V8Types.h" + +//#define OVERTE_V8_SCRIPT_VALUE_WRAPPER_DELETE_GUARD + +/// [V8] Implements ScriptValue for V8 and translates calls for V8ScriptValue +class ScriptValueV8Wrapper final : public ScriptValueProxy { +public: // construction + ScriptValueV8Wrapper() = delete; + //ScriptValueV8Wrapper(ScriptValueV8Wrapper &) = delete; + inline ScriptValueV8Wrapper(ScriptEngineV8* engine, const V8ScriptValue& value) : + _engine(engine), _value(value) { +#ifdef OVERTE_V8_MEMORY_DEBUG + engine->incrementScriptValueProxyCounter(); +#endif + } + inline ScriptValueV8Wrapper(ScriptEngineV8* engine, V8ScriptValue&& value) : + _engine(engine), _value(std::move(value)) { +#ifdef OVERTE_V8_MEMORY_DEBUG + engine->incrementScriptValueProxyCounter(); +#endif + } + static ScriptValueV8Wrapper* unwrap(const ScriptValue& val); + inline const V8ScriptValue& toV8Value() const { return _value; } + static V8ScriptValue fullUnwrap(ScriptEngineV8* engine, const ScriptValue& value); + ScriptEngineV8* getV8Engine() {return _engine;} + +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 ScriptValue prototype() const override; + virtual void setData(const ScriptValue& val) override; + + virtual bool hasProperty(const QString &name) const 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 QList getPropertyNames() 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; + +#ifdef OVERTE_V8_SCRIPT_VALUE_WRAPPER_DELETE_GUARD + // These can be used for debugging crashes caused access after delete + // If delete guard is enabled, deleting wrapper will cause a crash and thus trigger debugger and reveal location where object was deleted. + void enableDeleteGuard() { deleteGuard = true;} + void disableDeleteGuard() { deleteGuard = false;} +#endif + +protected: + virtual ~ScriptValueV8Wrapper() { +#ifdef OVERTE_V8_MEMORY_DEBUG + _engine->decrementScriptValueProxyCounter(); +#endif +#ifdef OVERTE_V8_SCRIPT_VALUE_WRAPPER_DELETE_GUARD + if (deleteGuard) { + uint32_t* crashTrigger = nullptr; + *crashTrigger = 0x12345678; + } +#endif + }; + +private: // helper functions + V8ScriptValue fullUnwrap(const ScriptValue& value) const; + +private: // storage + ScriptEngineV8 *_engine; + V8ScriptValue _value; + +#ifdef OVERTE_V8_SCRIPT_VALUE_WRAPPER_DELETE_GUARD + bool deleteGuard{false}; +#endif + // This is to prevent proxy being deleted when it is in use for example during callbacks from inside it + mutable QReadWriteLock lock; + + Q_DISABLE_COPY(ScriptValueV8Wrapper) +}; + +#endif // hifi_ScriptValueV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/v8/TypedArrayPrototype.cpp similarity index 79% rename from libraries/script-engine/src/TypedArrayPrototype.cpp rename to libraries/script-engine/src/v8/TypedArrayPrototype.cpp index a1f3ff87e8d..08eaf059f2a 100644 --- a/libraries/script-engine/src/TypedArrayPrototype.cpp +++ b/libraries/script-engine/src/v8/TypedArrayPrototype.cpp @@ -4,26 +4,31 @@ // // Created by Clement on 7/14/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TypedArrayPrototype.h" +#include + #include "TypedArrays.h" -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO Do not remove yet, this will be useful in later PRs +/*Q_DECLARE_METATYPE(QByteArray*) TypedArrayPrototype::TypedArrayPrototype(QObject* parent) : QObject(parent) { } QByteArray* TypedArrayPrototype::thisArrayBuffer() const { - QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + V8ScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); return qscriptvalue_cast(bufferObject.data()); } -void TypedArrayPrototype::set(QScriptValue array, qint32 offset) { +void TypedArrayPrototype::set(V8ScriptValue array, qint32 offset) { TypedArray* typedArray = static_cast(parent()); if (array.isArray() || typedArray) { if (offset < 0) { @@ -42,9 +47,9 @@ void TypedArrayPrototype::set(QScriptValue array, qint32 offset) { } } -QScriptValue TypedArrayPrototype::subarray(qint32 begin) { +V8ScriptValue TypedArrayPrototype::subarray(qint32 begin) { TypedArray* typedArray = static_cast(parent()); - QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + V8ScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); qint32 bytesPerElement = typedArray->_bytesPerElement; @@ -59,9 +64,9 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin) { return typedArray->newInstance(arrayBuffer, byteOffset, length - begin); } -QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { +V8ScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { TypedArray* typedArray = static_cast(parent()); - QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + V8ScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); qint32 bytesPerElement = typedArray->_bytesPerElement; @@ -81,9 +86,9 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { return typedArray->newInstance(arrayBuffer, byteOffset, length); } -QScriptValue TypedArrayPrototype::get(quint32 index) { +V8ScriptValue TypedArrayPrototype::get(quint32 index) { TypedArray* typedArray = static_cast(parent()); - QScriptString name = engine()->toStringHandle(QString::number(index)); + V8ScriptString name = engine()->toStringHandle(QString::number(index)); uint id; QScriptClass::QueryFlags flags = typedArray->queryProperty(thisObject(), name, @@ -91,13 +96,13 @@ QScriptValue TypedArrayPrototype::get(quint32 index) { if (QScriptClass::HandlesReadAccess & flags) { return typedArray->property(thisObject(), name, id); } - return QScriptValue(); + return V8ScriptValue(); } -void TypedArrayPrototype::set(quint32 index, QScriptValue& value) { +void TypedArrayPrototype::set(quint32 index, V8ScriptValue& value) { TypedArray* typedArray = static_cast(parent()); - QScriptValue object = thisObject(); - QScriptString name = engine()->toStringHandle(QString::number(index)); + V8ScriptValue object = thisObject(); + V8ScriptString name = engine()->toStringHandle(QString::number(index)); uint id; QScriptClass::QueryFlags flags = typedArray->queryProperty(object, name, @@ -106,3 +111,4 @@ void TypedArrayPrototype::set(quint32 index, QScriptValue& value) { typedArray->setProperty(object, name, id, value); } } +*/ diff --git a/libraries/script-engine/src/v8/TypedArrayPrototype.h b/libraries/script-engine/src/v8/TypedArrayPrototype.h new file mode 100644 index 00000000000..3c19935f0c9 --- /dev/null +++ b/libraries/script-engine/src/v8/TypedArrayPrototype.h @@ -0,0 +1,45 @@ +// +// TypedArrayPrototype.h +// +// +// Created by Clement on 7/14/14. +// Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_TypedArrayPrototype_h +#define hifi_TypedArrayPrototype_h + +#include + +#include "V8Types.h" +#include "../Scriptable.h" + +// V8TODO Do not remove yet, this will be useful in later PRs +/// [V8] The javascript functions associated with a TypedArray instance prototype +/*class TypedArrayPrototype : public QObject, public Scriptable { + Q_OBJECT +public: + TypedArrayPrototype(QObject* parent = NULL); + +public slots: + void set(V8ScriptValue array, qint32 offset = 0); + V8ScriptValue subarray(qint32 begin); + V8ScriptValue subarray(qint32 begin, qint32 end); + + V8ScriptValue get(quint32 index); + void set(quint32 index, V8ScriptValue& value); +private: + QByteArray* thisArrayBuffer() const; +}; +*/ +#endif // hifi_TypedArrayPrototype_h + +/// @} diff --git a/libraries/script-engine/src/TypedArrays.cpp b/libraries/script-engine/src/v8/TypedArrays.cpp similarity index 65% rename from libraries/script-engine/src/TypedArrays.cpp rename to libraries/script-engine/src/v8/TypedArrays.cpp index f2c3d3fd3db..ba7e0fe8ccb 100644 --- a/libraries/script-engine/src/TypedArrays.cpp +++ b/libraries/script-engine/src/v8/TypedArrays.cpp @@ -4,26 +4,34 @@ // // Created by Clement on 7/9/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TypedArrays.h" #include -#include "ScriptEngine.h" +#include + +#include + +#include "ArrayBufferClass.h" +#include "ScriptEngineV8.h" #include "TypedArrayPrototype.h" -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO Do not remove yet, this will be useful in later PRs +/*Q_DECLARE_METATYPE(QByteArray*) -TypedArray::TypedArray(ScriptEngine* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { +TypedArray::TypedArray(ScriptEngineV8* 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()); - QScriptValue global = engine()->globalObject(); + V8ScriptValue global = engine()->globalObject(); // build prototype _proto = engine()->newQObject(new TypedArrayPrototype(this), @@ -39,30 +47,30 @@ TypedArray::TypedArray(ScriptEngine* scriptEngine, QString name) : ArrayBufferVi engine()->globalObject().setProperty(_name, _ctor); } -QScriptValue TypedArray::newInstance(quint32 length) { +V8ScriptValue TypedArray::newInstance(quint32 length) { ArrayBufferClass* array = getScriptEngine()->getArrayBufferClass(); - QScriptValue buffer = array->newInstance(length * _bytesPerElement); + V8ScriptValue buffer = array->newInstance(length * _bytesPerElement); return newInstance(buffer, 0, length); } -QScriptValue TypedArray::newInstance(QScriptValue array) { +V8ScriptValue TypedArray::newInstance(V8ScriptValue array) { const QString ARRAY_LENGTH_HANDLE = "length"; if (array.property(ARRAY_LENGTH_HANDLE).isValid()) { quint32 length = array.property(ARRAY_LENGTH_HANDLE).toInt32(); - QScriptValue newArray = newInstance(length); + V8ScriptValue newArray = newInstance(length); for (quint32 i = 0; i < length; ++i) { - QScriptValue value = array.property(QString::number(i)); + V8ScriptValue value = array.property(QString::number(i)); setProperty(newArray, engine()->toStringHandle(QString::number(i)), - i * _bytesPerElement, (value.isNumber()) ? value : QScriptValue(0)); + i * _bytesPerElement, (value.isNumber()) ? value : V8ScriptValue(0)); } return newArray; } engine()->evaluate("throw \"ArgumentError: not an array\""); - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length) { - QScriptValue data = engine()->newObject(); +V8ScriptValue TypedArray::newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 length) { + V8ScriptValue data = engine()->newObject(); data.setProperty(_bufferName, buffer); data.setProperty(_byteOffsetName, byteOffset); data.setProperty(_byteLengthName, length * _bytesPerElement); @@ -71,17 +79,17 @@ QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, qu return engine()->newObject(this, data); } -QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue TypedArray::construct(V8ScriptContext* context, QScriptEngine* engine) { TypedArray* cls = qscriptvalue_cast(context->callee().data()); if (!cls) { - return QScriptValue(); + return V8ScriptValue(); } if (context->argumentCount() == 0) { return cls->newInstance(0); } - QScriptValue newObject; - QScriptValue bufferArg = context->argument(0); + V8ScriptValue newObject; + V8ScriptValue bufferArg = context->argument(0); QByteArray* arrayBuffer = qscriptvalue_cast(bufferArg.data()); // parse arguments @@ -94,14 +102,14 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin quint32 length = arrayBuffer->size() / cls->_bytesPerElement; newObject = cls->newInstance(bufferArg, 0, length); } else { - QScriptValue byteOffsetArg = context->argument(1); + V8ScriptValue byteOffsetArg = context->argument(1); if (!byteOffsetArg.isNumber()) { engine->evaluate("throw \"ArgumentError: 2nd arg is not a number\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size()) { engine->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.toInt32() % cls->_bytesPerElement != 0) { engine->evaluate("throw \"RangeError: byteOffset not a multiple of BYTES_PER_ELEMENT\""); @@ -117,15 +125,15 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin newObject = cls->newInstance(bufferArg, byteOffset, length); } else { - QScriptValue lengthArg = (context->argumentCount() > 2) ? context->argument(2) : QScriptValue(); + V8ScriptValueee lengthArg = (context->argumentCount() > 2) ? context->argument(2) : V8ScriptValue(); if (!lengthArg.isNumber()) { engine->evaluate("throw \"ArgumentError: 3nd arg is not a number\""); - return QScriptValue(); + return V8ScriptValue(); } if (lengthArg.toInt32() < 0 || byteOffsetArg.toInt32() + lengthArg.toInt32() * (qint32)(cls->_bytesPerElement) > arrayBuffer->size()) { engine->evaluate("throw \"RangeError: byteLength out of range\""); - return QScriptValue(); + return V8ScriptValue(); } quint32 length = lengthArg.toInt32(); @@ -149,8 +157,8 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin return newObject; } -QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object, - const QScriptString& name, +QScriptClass::QueryFlags TypedArray::queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, QueryFlags flags, uint* id) { if (name == _bytesPerElementName || name == _lengthName) { return flags &= HandlesReadAccess; // Only keep read access flags @@ -170,10 +178,10 @@ QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object, return ArrayBufferViewClass::queryProperty(object, name, flags, id); } -QScriptValue TypedArray::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue TypedArray::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { if (name == _bytesPerElementName) { - return QScriptValue(_bytesPerElement); + return V8ScriptValue(_bytesPerElement); } if (name == _lengthName) { return object.data().property(_lengthName); @@ -181,16 +189,16 @@ QScriptValue TypedArray::property(const QScriptValue& object, return ArrayBufferViewClass::property(object, name, id); } -QScriptValue::PropertyFlags TypedArray::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; +V8ScriptValue::PropertyFlags TypedArray::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; } QString TypedArray::name() const { return _name.toString(); } -QScriptValue TypedArray::prototype() const { +V8ScriptValue TypedArray::prototype() const { return _proto; } @@ -202,7 +210,7 @@ void TypedArray::setBytesPerElement(quint32 bytesPerElement) { // templated helper functions // don't work for floats as they require single precision settings template -QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& name, uint id) { +V8ScriptValue propertyHelper(const QByteArray* arrayBuffer, const V8ScriptString& name, uint id) { bool ok = false; name.toArrayIndex(&ok); @@ -215,11 +223,11 @@ QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& stream >> result; return result; } - return QScriptValue(); + return V8ScriptValue(); } template -void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint id, const QScriptValue& value) { +void setPropertyHelper(QByteArray* arrayBuffer, const V8ScriptString& name, uint id, const V8ScriptValue& value) { if (arrayBuffer && value.isNumber()) { QDataStream stream(arrayBuffer, QIODevice::ReadWrite); stream.skipRawData(id); @@ -229,50 +237,50 @@ void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint } } -Int8ArrayClass::Int8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { +Int8ArrayClass::Int8ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint8)); } -QScriptValue Int8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int8ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int8ArrayClass::setProperty(QScriptValue &object, const QScriptString &name, - uint id, const QScriptValue& value) { +void Int8ArrayClass::setProperty(V8ScriptValue &object, const V8ScriptString &name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint8ArrayClass::Uint8ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { +Uint8ArrayClass::Uint8ArrayClass(ScriptEngineV8ptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } -QScriptValue Uint8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint8ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint8ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint8ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { +Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } -QScriptValue Uint8ClampedArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint8ClampedArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint8ClampedArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -287,76 +295,76 @@ void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptStri } } -Int16ArrayClass::Int16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { +Int16ArrayClass::Int16ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint16)); } -QScriptValue Int16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int16ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Int16ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint16ArrayClass::Uint16ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { +Uint16ArrayClass::Uint16ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint16)); } -QScriptValue Uint16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint16ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint16ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Int32ArrayClass::Int32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { +Int32ArrayClass::Int32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint32)); } -QScriptValue Int32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int32ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Int32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint32ArrayClass::Uint32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { +Uint32ArrayClass::Uint32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint32)); } -QScriptValue Uint32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint32ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Float32ArrayClass::Float32ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { +Float32ArrayClass::Float32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(float)); } -QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Float32ArrayClass::property(const V8ScriptValuee& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; name.toArrayIndex(&ok); @@ -369,15 +377,15 @@ QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScri float result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValuee(); } return result; } return TypedArray::property(object, name, id); } -void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Float32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -389,11 +397,11 @@ void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& n } } -Float64ArrayClass::Float64ArrayClass(ScriptEngine* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { +Float64ArrayClass::Float64ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(double)); } -QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Float64ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; name.toArrayIndex(&ok); @@ -406,15 +414,15 @@ QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScri double result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } return result; } return TypedArray::property(object, name, id); } -void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Float64ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -424,5 +432,5 @@ void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& n stream << (double)value.toNumber(); } -} +}*/ diff --git a/libraries/script-engine/src/v8/TypedArrays.h b/libraries/script-engine/src/v8/TypedArrays.h new file mode 100644 index 00000000000..4350eefe583 --- /dev/null +++ b/libraries/script-engine/src/v8/TypedArrays.h @@ -0,0 +1,157 @@ +// +// TypedArrays.h +// +// +// Created by Clement on 7/9/14. +// Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_TypedArrays_h +#define hifi_TypedArrays_h + +// V8TODO Do not remove yet, this will be useful in later PRs +/*#include "ArrayBufferViewClass.h" + +static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT"; +static const QString LENGTH_PROPERTY_NAME = "length"; + +static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array"; +static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array"; +static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray"; +static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array"; +static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array"; +static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array"; +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"; + +/// [QtScript] Implements the TypedArray scripting class +class TypedArray : public ArrayBufferViewClass { + Q_OBJECT +public: + TypedArray(ScriptEngineV8* scriptEngine, QString name); + virtual V8ScriptValue newInstance(quint32 length); + virtual V8ScriptValue newInstance(V8ScriptValue array); + virtual V8ScriptValue newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 length); + + virtual QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + virtual V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override = 0; + virtual V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + + QString name() const override; + V8ScriptValue prototype() const override; + +protected: + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); + + void setBytesPerElement(quint32 bytesPerElement); + + V8ScriptValue _proto; + V8ScriptValue _ctor; + + V8ScriptString _name; + V8ScriptString _bytesPerElementName; + V8ScriptString _lengthName; + + quint32 _bytesPerElement; + + friend class TypedArrayPrototype; +}; + +class Int8ArrayClass : public TypedArray { + Q_OBJECT +public: + Int8ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint8ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint8ClampedArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ClampedArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Int16ArrayClass : public TypedArray { + Q_OBJECT +public: + Int16ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint16ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint16ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Int32ArrayClass : public TypedArray { + Q_OBJECT +public: + Int32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint32ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Float32ArrayClass : public TypedArray { + Q_OBJECT +public: + Float32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Float64ArrayClass : public TypedArray { + Q_OBJECT +public: + Float64ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; +*/ +#endif // hifi_TypedArrays_h + +/// @} diff --git a/libraries/script-engine/src/v8/V8Types.h b/libraries/script-engine/src/v8/V8Types.h new file mode 100644 index 00000000000..499fc6ee21a --- /dev/null +++ b/libraries/script-engine/src/v8/V8Types.h @@ -0,0 +1,146 @@ +// +// V8Types.h +// libraries/script-engine/src/v8 +// +// Created by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2022-2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef hifi_V8Types_h +#define hifi_V8Types_h + +#include + +#include +#include + +#include "ScriptEngineV8.h" + +template +class V8ScriptValueTemplate { +public: + V8ScriptValueTemplate() = delete; + + V8ScriptValueTemplate(ScriptEngineV8 *engine, const v8::Local value/*, V8ScriptValueTemplate::Ownership ownership*/) : _engine(engine) { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope(_engine->getContext()); +#ifdef OVERTE_V8_MEMORY_DEBUG + _engine->incrementScriptValueCounter(); +#endif + _value.reset(new v8::UniquePersistent(_engine->getIsolate(), value)); + }; + + V8ScriptValueTemplate& operator= (const V8ScriptValueTemplate &source) { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope(_engine->getContext()); + _engine = source.getEngine(); + _value.reset(new v8::UniquePersistent(_engine->getIsolate(), source.constGet())); + return *this; + }; + + V8ScriptValueTemplate(ScriptEngineV8 *engine) : _engine(engine) { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope(_engine->getContext()); +#ifdef OVERTE_V8_MEMORY_DEBUG + _engine->incrementScriptValueCounter(); +#endif + _value.reset(new v8::UniquePersistent(_engine->getIsolate(), v8::Local())); + }; + + V8ScriptValueTemplate(const V8ScriptValueTemplate &copied) : _engine(copied.getEngine()) { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + v8::Context::Scope(_engine->getContext()); +#ifdef OVERTE_V8_MEMORY_DEBUG + _engine->incrementScriptValueCounter(); +#endif + _value.reset(new v8::UniquePersistent(_engine->getIsolate(), copied.constGet())); + } + + v8::Local get() { + Q_ASSERT(_engine->getIsolate()->IsCurrent()); + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_value.get()->Get(_engine->getIsolate())); + }; + + const v8::Local constGet() const { + Q_ASSERT(_engine->getIsolate()->IsCurrent()); + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_value.get()->Get(_engine->getIsolate())); + }; + + const v8::Local constGetContext() const { + Q_ASSERT(_engine->getIsolate()->IsCurrent()); + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_engine->getIsolate()->GetCurrentContext()); + }; + + const v8::Isolate* constGetIsolate() const { return _engine->getIsolate(); }; + v8::Isolate* getIsolate() { return _engine->getIsolate();}; + + ScriptEngineV8* getEngine() const { return _engine; }; + + v8::Local getContext() { + Q_ASSERT(_engine->getIsolate()->IsCurrent()); + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_engine->getIsolate()->GetCurrentContext()); + }; + + void reset(v8::Isolate *isolate, v8::Local) { + Q_ASSERT(false); + }; + // V8TODO: add thread safe destructors to all objects that have persistent handles + ~V8ScriptValueTemplate() { + v8::Locker locker(_engine->getIsolate()); + v8::Isolate::Scope isolateScope(_engine->getIsolate()); + v8::HandleScope handleScope(_engine->getIsolate()); + //v8::Context::Scope(_engine->getContext()); +#ifdef OVERTE_V8_MEMORY_DEBUG + _engine->decrementScriptValueCounter(); +#endif + _value->Reset(); + } + +private: + std::shared_ptr> _value; + ScriptEngineV8 *_engine; +}; + +class V8ScriptString : public V8ScriptValueTemplate { +public: + V8ScriptString() = delete; + V8ScriptString(ScriptEngineV8 *engine, const v8::Local value) : V8ScriptValueTemplate(engine, value) {}; + const QString toQString() const { + v8::Locker locker(getEngine()->getIsolate()); + v8::Isolate::Scope isolateScope(getEngine()->getIsolate()); + v8::HandleScope handleScope(getEngine()->getIsolate()); + v8::Context::Scope contextScope(getEngine()->getContext()); + Q_ASSERT(constGet()->IsString()); + return QString(*v8::String::Utf8Value(const_cast(constGetIsolate()), constGet())); + }; + bool operator==(const V8ScriptString& string) const { + v8::Locker locker(getEngine()->getIsolate()); + v8::Isolate::Scope isolateScope(getEngine()->getIsolate()); + v8::HandleScope handleScope(getEngine()->getIsolate()); + v8::Context::Scope contextScope(getEngine()->getContext()); + Q_ASSERT(constGet()->IsString()); + return constGet()->StringEquals(string.constGet()); + } +}; + +inline uint qHash(const V8ScriptString &key, uint seed = 0) { + return qHash(key.toQString(), seed); +}; + +#endif diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index c03366ecda8..3c08c9a1bce 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -1,9 +1,13 @@ +# Copyright 2013-2020, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..c2afe1cca60 --- /dev/null +++ b/libraries/shared/src/EntityItemID.cpp @@ -0,0 +1,44 @@ +// +// EntityItemID.cpp +// libraries/shared/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#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..6e26e8a28ce 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/shared/src/EntityItemID.h @@ -1,12 +1,14 @@ // // EntityItemID.h -// libraries/entities/src +// libraries/shared/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_EntityItemID_h @@ -17,7 +19,7 @@ #include #include #include -#include +#include #include const QUuid UNKNOWN_ENTITY_ID; // null uuid @@ -29,7 +31,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 +42,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/Profile.cpp b/libraries/shared/src/Profile.cpp index 420b4ee54af..859d0ae1403 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -1,9 +1,11 @@ // // Created by Ryan Huffman on 2016-12-14 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "Profile.h" @@ -48,15 +50,15 @@ static bool tracingEnabled() { return (tracer && tracer->isEnabled()); } -DurationBase::DurationBase(const QLoggingCategory& category, const QString& name) : _name(name), _category(category) { +ProfileDurationBase::ProfileDurationBase(const QLoggingCategory& category, const QString& name) : _name(name), _category(category) { } -Duration::Duration(const QLoggingCategory& category, +ProfileDuration::ProfileDuration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : - DurationBase(category, name) { + ProfileDurationBase(category, name) { if (tracingEnabled() && category.isDebugEnabled()) { QVariantMap args = baseArgs; args["nv_payload"] = QVariant::fromValue(payload); @@ -78,7 +80,7 @@ Duration::Duration(const QLoggingCategory& category, } } -Duration::~Duration() { +ProfileDuration::~ProfileDuration() { if (tracingEnabled() && _category.isDebugEnabled()) { tracing::traceEvent(_category, _name, tracing::DurationEnd); #ifdef NSIGHT_TRACING @@ -88,7 +90,7 @@ Duration::~Duration() { } // FIXME -uint64_t Duration::beginRange(const QLoggingCategory& category, const char* name, uint32_t argbColor) { +uint64_t ProfileDuration::beginRange(const QLoggingCategory& category, const char* name, uint32_t argbColor) { #ifdef NSIGHT_TRACING if (tracingEnabled() && category.isDebugEnabled()) { nvtxEventAttributes_t eventAttrib = { 0 }; @@ -105,7 +107,7 @@ uint64_t Duration::beginRange(const QLoggingCategory& category, const char* name } // FIXME -void Duration::endRange(const QLoggingCategory& category, uint64_t rangeId) { +void ProfileDuration::endRange(const QLoggingCategory& category, uint64_t rangeId) { #ifdef NSIGHT_TRACING if (tracingEnabled() && category.isDebugEnabled()) { nvtxRangeEnd(rangeId); @@ -113,11 +115,11 @@ void Duration::endRange(const QLoggingCategory& category, uint64_t rangeId) { #endif } -ConditionalDuration::ConditionalDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime) : - DurationBase(category, name), _startTime(tracing::Tracer::now()), _minTime(minTime * USECS_PER_MSEC) { +ConditionalProfileDuration::ConditionalProfileDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime) : + ProfileDurationBase(category, name), _startTime(tracing::Tracer::now()), _minTime(minTime * USECS_PER_MSEC) { } -ConditionalDuration::~ConditionalDuration() { +ConditionalProfileDuration::~ConditionalProfileDuration() { if (tracingEnabled() && _category.isDebugEnabled()) { auto endTime = tracing::Tracer::now(); auto duration = endTime - _startTime; diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index e7084b4f79b..2b1b3c4efe5 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -1,9 +1,11 @@ // // Created by Ryan Huffman on 2016-12-14 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -37,27 +39,27 @@ Q_DECLARE_LOGGING_CATEGORY(trace_startup) Q_DECLARE_LOGGING_CATEGORY(trace_workload) Q_DECLARE_LOGGING_CATEGORY(trace_baker) -class DurationBase { +class ProfileDurationBase { protected: - DurationBase(const QLoggingCategory& category, const QString& name); + ProfileDurationBase(const QLoggingCategory& category, const QString& name); const QString _name; const QLoggingCategory& _category; }; -class Duration : public DurationBase { +class ProfileDuration : public ProfileDurationBase { public: - Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor = 0xff0000ff, uint64_t payload = 0, const QVariantMap& args = QVariantMap()); - ~Duration(); + ProfileDuration(const QLoggingCategory& category, const QString& name, uint32_t argbColor = 0xff0000ff, uint64_t payload = 0, const QVariantMap& args = QVariantMap()); + ~ProfileDuration(); static uint64_t beginRange(const QLoggingCategory& category, const char* name, uint32_t argbColor); static void endRange(const QLoggingCategory& category, uint64_t rangeId); }; -class ConditionalDuration : public DurationBase { +class ConditionalProfileDuration : public ProfileDurationBase { public: - ConditionalDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime); - ~ConditionalDuration(); + ConditionalProfileDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime); + ~ConditionalProfileDuration(); private: const int64_t _startTime; @@ -108,11 +110,11 @@ inline void metadata(const QString& metadataType, const QVariantMap& args) { tracing::traceEvent(trace_metadata(), metadataType, tracing::Metadata, "", args); } -#define PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name); -#define PROFILE_RANGE_IF_LONGER(category, name, ms) ConditionalDuration profileRangeThis(trace_##category(), name, ms); -#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); -#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor) -#define PROFILE_RANGE_END(category, rangeId) Duration::endRange(trace_##category(), rangeId) +#define PROFILE_RANGE(category, name) ProfileDuration profileRangeThis(trace_##category(), name); +#define PROFILE_RANGE_IF_LONGER(category, name, ms) ConditionalProfileDuration profileRangeThis(trace_##category(), name, ms); +#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) ProfileDuration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); +#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = ProfileDuration::beginRange(trace_##category(), name, argbColor) +#define PROFILE_RANGE_END(category, rangeId) ProfileDuration::endRange(trace_##category(), rangeId) #define PROFILE_SYNC_BEGIN(category, name, id, ...) syncBegin(trace_##category(), name, id, ##__VA_ARGS__); #define PROFILE_SYNC_END(category, name, id, ...) syncEnd(trace_##category(), name, id, ##__VA_ARGS__); #define PROFILE_ASYNC_BEGIN(category, name, id, ...) asyncBegin(trace_##category(), name, id, ##__VA_ARGS__); @@ -130,8 +132,8 @@ inline void metadata(const QString& metadataType, const QVariantMap& args) { // uncomment WANT_DETAILED_PROFILING definition to enable profiling in high-frequency contexts //#define WANT_DETAILED_PROFILING #ifdef WANT_DETAILED_PROFILING -#define DETAILED_PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name); -#define DETAILED_PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); +#define DETAILED_PROFILE_RANGE(category, name) ProfileDuration profileRangeThis(trace_##category(), name); +#define DETAILED_PROFILE_RANGE_EX(category, name, argbColor, payload, ...) ProfileDuration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); #else // WANT_DETAILED_PROFILING #define DETAILED_PROFILE_RANGE(category, name) ; // no-op #define DETAILED_PROFILE_RANGE_EX(category, name, argbColor, payload, ...) ; // no-op diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index c8b03694eba..f84333d3603 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 10/3/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "RegisteredMetaTypes.h" @@ -22,11 +24,10 @@ #include #include #include -#include -#include #include int uint32MetaTypeId = qRegisterMetaType("uint32"); +int uint16MetaTypeId = qRegisterMetaType(); int glmUint32MetaTypeId = qRegisterMetaType("glm::uint32"); int vec2MetaTypeId = qRegisterMetaType(); int u8vec3MetaTypeId = qRegisterMetaType(); @@ -46,81 +47,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 +96,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 +266,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 +316,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 +391,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 +448,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 +479,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 +510,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 +520,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 +538,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 +572,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..96f88abf420 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -4,15 +4,16 @@ // // Created by Stephen Birarda on 10/3/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_RegisteredMetaTypes_h #define hifi_RegisteredMetaTypes_h -#include #include #include @@ -30,6 +31,7 @@ class QColor; class QUrl; +Q_DECLARE_METATYPE(uint16_t) Q_DECLARE_METATYPE(glm::vec2) Q_DECLARE_METATYPE(glm::u8vec3) Q_DECLARE_METATYPE(glm::vec3) @@ -43,8 +45,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 +67,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 +85,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 +109,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 +151,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 +165,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 +182,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 +228,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 +578,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 +599,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 +649,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 +662,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/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index 5695472fae9..b4caa2f7bf4 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -4,10 +4,11 @@ // // Created by Clement on 9/13/16. // Copyright 2016 High Fidelity, Inc. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "SettingHelpers.h" @@ -133,6 +134,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { } switch (variantType) { + // QML has problems with QVariant::Hash case QVariant::Hash: { qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant; Q_ASSERT(false); diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h index 90bfc1aaa64..c0c0a7ed94c 100644 --- a/libraries/shared/src/Transform.h +++ b/libraries/shared/src/Transform.h @@ -4,10 +4,13 @@ // // Created by Sam Gateau on 11/4/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // + #ifndef hifi_gpu_Transform_h #define hifi_gpu_Transform_h @@ -26,7 +29,11 @@ class QJsonValue; inline bool isValidScale(glm::vec3 scale) { bool result = scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f; - assert(result); + if(!result){ + qWarning() << "Scale is equal to 0"; + } + // V8TODO: commented out for now + // assert(result); return result; } 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..99ad0e7c79b 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -1,26 +1,13 @@ // // Created by Timothy Dedischew on 2017/12/21 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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..c6f1d3695a0 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -1,9 +1,11 @@ // // Created by Timothy Dedischew on 2017/12/21 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -42,7 +44,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..1804fa41a11 100644 --- a/libraries/shared/src/shared/QtHelpers.cpp +++ b/libraries/shared/src/shared/QtHelpers.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015/11/09 // Copyright 2013-2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "QtHelpers.h" @@ -40,6 +42,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, @@ -71,6 +83,8 @@ bool blockingInvokeMethod( } PROFILE_RANGE(app, function); + // V8TODO: this causes a deadlock when main thread calls blocking invoke method on entity script thread, + // for example when clearing cache. Some sort of mutex is needed to prevent this. return QMetaObject::invokeMethod(obj, member, Qt::BlockingQueuedConnection, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); } @@ -92,10 +106,10 @@ bool blockingInvokeMethod( } // Inspecting of the qt event queue -// requres access to private Qt datastructures +// requires access to private Qt datastructures // Querying the event queue should be done with // care as it could lock the threadData->postEventList.mutex -// The code uses a tryLock to avoid the possability of a +// The code uses a tryLock to avoid the possibility of a // deadlock during a call to this code, although that is unlikely // #ifdef DEBUG_EVENT_QUEUE diff --git a/libraries/shared/src/shared/QtHelpers.h b/libraries/shared/src/shared/QtHelpers.h index 9a9d33a3ceb..d4a7efec5ed 100644 --- a/libraries/shared/src/shared/QtHelpers.h +++ b/libraries/shared/src/shared/QtHelpers.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2017/06/29 // Copyright 2013-2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once @@ -11,14 +13,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 +59,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..f0f3201dc48 100644 --- a/libraries/shared/src/shared/ScriptInitializerMixin.h +++ b/libraries/shared/src/shared/ScriptInitializerMixin.h @@ -3,8 +3,11 @@ // libraries/shared/src/shared // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. +// SPDX-License-Identifier: Apache-2.0 +// #pragma once @@ -12,7 +15,6 @@ #include #include "../DependencyManager.h" -class QScriptEngine; class ScriptEngine; template class ScriptInitializerMixin { @@ -35,11 +37,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/shared/src/shared/WebRTC.h b/libraries/shared/src/shared/WebRTC.h index 3f4c9e156f9..ef194e971ac 100644 --- a/libraries/shared/src/shared/WebRTC.h +++ b/libraries/shared/src/shared/WebRTC.h @@ -4,9 +4,11 @@ // // Copyright 2019 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_WebRTC_h diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index e79542dc407..e51261e83b2 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -4,9 +4,11 @@ // // Created by Zach Pomerantz on 1/6/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_task_Task_h @@ -559,7 +561,7 @@ class Engine : public Task { class className : public PerformanceTimer { \ public: \ className(const std::string& label) : PerformanceTimer(label.c_str()), profileRange(category(), label.c_str()) {} \ - Duration profileRange; \ + ProfileDuration profileRange; \ }; #endif // hifi_task_Task_h diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 6dde4cc1a20..1af3ca0ba86 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2013-2018, High Fidelity, Inc. +# Copyright 2020-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..e3f31557023 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -1,26 +1,30 @@ // // Created by Gabriel Calero & Cristian Duarte on Aug 25, 2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +37,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 +49,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..ea42125a9a1 100644 --- a/libraries/ui/src/QmlFragmentClass.h +++ b/libraries/ui/src/QmlFragmentClass.h @@ -1,27 +1,33 @@ // // Created by Gabriel Calero & Cristian Duarte on Aug 25, 2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ui_QmlFragmentClass_h #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 +52,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..6542bc750f0 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -1,17 +1,20 @@ // // Created by Bradley Austin Davis on 2015-12-15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "QmlWebWindowClass.h" #include -#include -#include +#include +#include +#include #include @@ -19,7 +22,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 +32,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..2a81ca5f49c 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -1,16 +1,25 @@ // // Created by Bradley Austin Davis on 2015-12-15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #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 +151,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..85cc976fd4b 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015-12-15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "QmlWindowClass.h" @@ -11,8 +13,6 @@ #include #include -#include -#include #include #include @@ -27,6 +27,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 +41,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 +74,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 +84,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..76a5be0c2d0 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015-12-15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ui_QmlWindowClass_h @@ -11,13 +13,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 +55,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 +347,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..6b0971bb8af 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2015-04-04 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #pragma once #ifndef hifi_OffscreenQmlSurface_h @@ -13,7 +15,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..4992d411b6b 100644 --- a/libraries/ui/src/ui/QmlWrapper.h +++ b/libraries/ui/src/ui/QmlWrapper.h @@ -1,9 +1,11 @@ // // Created by Anthony J. Thibault on 2016-12-12 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_QmlWrapper_h @@ -11,8 +13,11 @@ #include #include -#include -#include + +#include +#include + +class ScriptEngine; class QmlWrapper : public QObject { Q_OBJECT @@ -29,16 +34,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..42af68f820f 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -1,9 +1,11 @@ // // Created by Anthony J. Thibault on 2016-12-12 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "TabletScriptingInterface.h" @@ -17,6 +19,8 @@ #include #include #include +#include +#include #include "../QmlWindowClass.h" #include "../OffscreenUi.h" @@ -28,6 +32,14 @@ #include "SettingHandle.h" +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, wrapperFromScriptValue >(scriptEngine); + scriptRegisterMetaType, wrapperFromScriptValue >(scriptEngine); + scriptRegisterMetaType, scriptValueToEnumClass>(scriptEngine); +})); + // FIXME move to global app properties const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; @@ -159,6 +171,8 @@ bool TabletButtonsProxyModel::filterAcceptsRow(int sourceRow, TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); + qRegisterMetaType("TabletScriptingInterface::TabletAudioEvents"); + qRegisterMetaType("TabletScriptingInterface::TabletConstants"); qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletButtonsProxyModel"); } @@ -309,6 +323,7 @@ void TabletScriptingInterface::processTabletEvents(QObject* object, const QKeyEv void TabletScriptingInterface::processEvent(const QKeyEvent* event) { Q_ASSERT(QThread::currentThread() == qApp->thread()); TabletProxy* tablet = qobject_cast(getTablet(SYSTEM_TABLET)); + Q_ASSERT(tablet != nullptr); QObject* qmlTablet = tablet->getQmlTablet(); QObject* qmlMenu = tablet->getQmlMenu(); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 041fcc52a34..d3119cb7ca7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Anthony J. Thibault on 2016-12-12 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_TabletScriptingInterface_h @@ -19,10 +21,6 @@ #include #include -#include -#include -#include - #include #include @@ -689,5 +687,6 @@ class TabletButtonProxy : public QObject { }; Q_DECLARE_METATYPE(TabletButtonProxy*); +Q_DECLARE_METATYPE(TabletScriptingInterface::TabletAudioEvents) #endif // hifi_TabletScriptingInterface_h diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp index d01b538004a..4934ba4e351 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp @@ -1,41 +1,54 @@ // // Created by Bradley Austin Davis on 2016-06-16 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ToolbarScriptingInterface.h" #include #include -#include -#include +#include +#include +#include +#include #include #include "../OffscreenUi.h" -QScriptValue toolbarToScriptValue(QScriptEngine* engine, ToolbarProxy* const &in) { +STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ + auto scriptEngine = manager->engine().get(); + + scriptRegisterMetaType, wrapperFromScriptValue >(scriptEngine); + scriptRegisterMetaType, wrapperFromScriptValue >(scriptEngine); +})); + +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()); + Q_ASSERT(out != nullptr); } -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()); + Q_ASSERT(out != nullptr); } diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index 5cc847d4428..8f9a1d92f01 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -1,9 +1,11 @@ // // Created by Bradley Austin Davis on 2016-06-16 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_ToolbarScriptingInterface_h @@ -12,7 +14,6 @@ #include #include -#include #include #include "QmlWrapper.h" diff --git a/plugins/JSAPIExample/CMakeLists.txt b/plugins/JSAPIExample/CMakeLists.txt index a8fa0a1fd6a..8864573be3e 100644 --- a/plugins/JSAPIExample/CMakeLists.txt +++ b/plugins/JSAPIExample/CMakeLists.txt @@ -1,3 +1,8 @@ +# Copyright 2023 Overte contributors +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..ad23838e04f 100644 --- a/plugins/JSAPIExample/src/JSAPIExample.cpp +++ b/plugins/JSAPIExample/src/JSAPIExample.cpp @@ -4,9 +4,12 @@ // // Copyright (c) 2019-2020 humbletim (humbletim@gmail.com) // Copyright (c) 2019 Kalila L. (somnilibertas@gmail.com) +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// // Example of prototyping new JS APIs by leveraging the existing plugin system. @@ -17,14 +20,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 +41,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 +54,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 +67,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 +77,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 +153,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 +164,23 @@ 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) { + Q_ASSERT(engine); + 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..4f25997b285 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -1,16 +1,19 @@ # # Created by Anthony Thibault on 2015/12/18 # Copyright 2015 High Fidelity, Inc. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # 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..4d9b583ca8b 100644 --- a/plugins/hifiOsc/CMakeLists.txt +++ b/plugins/hifiOsc/CMakeLists.txt @@ -1,14 +1,17 @@ # # Created by Anthony Thibault on 2019/8/24 # Copyright 2019 High Fidelity, Inc. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # 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..1cb56a01f20 100644 --- a/plugins/hifiSdl2/CMakeLists.txt +++ b/plugins/hifiSdl2/CMakeLists.txt @@ -1,9 +1,11 @@ # # Created by Bradley Austin Davis on 2015/11/18 # Copyright 2015 High Fidelity, Inc. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # if (NOT APPLE) @@ -16,6 +18,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 04d36840faf..941109c205c 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -1,9 +1,11 @@ # # Created by Bradley Austin Davis on 2015/10/25 # Copyright 2015 High Fidelity, Inc. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # if (WIN32 AND (NOT USE_GLES) AND (MSVC_VERSION LESS 1930) ) @@ -22,6 +24,7 @@ if (WIN32 AND (NOT USE_GLES) AND (MSVC_VERSION LESS 1930) ) ${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..bc7869a3578 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -2,18 +2,21 @@ # Created by Bradley Austin Davis on 2015/11/18 # Copyright 2015 High Fidelity, Inc. # Copyright 2020 Vircadia contributors. +# Copyright 2023 Overte e.V. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# SPDX-License-Identifier: Apache-2.0 # 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/script-archive/libraries/overlayManager.js b/script-archive/libraries/overlayManager.js index cca9be8f751..17293f3b564 100644 --- a/script-archive/libraries/overlayManager.js +++ b/script-archive/libraries/overlayManager.js @@ -65,6 +65,7 @@ properties.forEach(function(prop) { Object.defineProperty(that.prototype, prop, { get: function() { + //V8TODO: return Overlays.getProperty(this._id, prop); }, set: function(newValue) { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index dec95dd9362..8c44910581c 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -36,11 +36,11 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/audioMuteOverlay.js", "system/inspect.js", "system/keyboardShortcuts/keyboardShortcuts.js", - "system/checkForUpdates.js", "system/onEscape.js", "system/onFirstRun.js", "system/appreciate/appreciate_app.js", - "system/places/places.js" + "system/places/places.js", + "developer/debugging/scriptMemoryReport.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 049ccbc03de..dc61cb5e7af 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -3,8 +3,9 @@ // // EZrecord.js // -// Created by David Rowe on 24 Jun 2017. +// Created by David Rowe on June 24th, 2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -48,21 +49,23 @@ if (HMD.active) { // 3D overlay attached to avatar. - hmdOverlay = Overlays.addOverlay("text3d", { - text: recordingText, - dimensions: { x: 3 * HMD_FONT_SIZE, y: HMD_FONT_SIZE }, - parentID: MyAvatar.sessionUUID, - parentJointIndex: CAMERA_JOINT_INDEX, - localPosition: { x: 0.95, y: 0.95, z: -2.0 }, - color: { red: 255, green: 0, blue: 0 }, - alpha: 0.9, - lineHeight: HMD_FONT_SIZE, - backgroundAlpha: 0, - ignoreRayIntersection: true, - isFacingAvatar: true, - drawInFront: true, - visible: true - }); + hmdOverlay = Entities.addEntity({ + "type": "Text", + "text": recordingText, + "dimensions": { "x": 3 * HMD_FONT_SIZE, "y": HMD_FONT_SIZE, "z": 0.01 }, + "parentID": MyAvatar.sessionUUID, + "parentJointIndex": CAMERA_JOINT_INDEX, + "localPosition": { "x": 0.95, "y": 0.95, "z": -2.0 }, + "color": { "red": 255, "green": 0, "blue": 0 }, + "alpha": 0.9, + "unlit": true, + "lineHeight": HMD_FONT_SIZE, + "backgroundAlpha": 0, + "ignorePickIntersection": true, + "billboardMode": "full", + "renderLayer": "front", + "visible": true + },"local"); } else { // 2D overlay on desktop. desktopOverlay = Overlays.addOverlay("text", { @@ -85,7 +88,7 @@ Overlays.deleteOverlay(desktopOverlay); } if (hmdOverlay) { - Overlays.deleteOverlay(hmdOverlay); + Entities.deleteEntity(hmdOverlay); } } diff --git a/scripts/developer/debugging/QObjectWrapperGCTest.js b/scripts/developer/debugging/QObjectWrapperGCTest.js new file mode 100644 index 00000000000..895211fcb1a --- /dev/null +++ b/scripts/developer/debugging/QObjectWrapperGCTest.js @@ -0,0 +1,38 @@ +// +// scriptMemoryReport.js +// scripts/developer/debugging +// +// Created by dr Karol Suprynowicz on 2023/04/23 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +// A simple script that prints memory usage statistics for a given script engine every 5 seconds +// It can be included for example as a part of default scripts or controller scripts + +var memoryStatisticsIntervalHandle = Script.setInterval(function () { + statistics = Script.getMemoryUsageStatistics(); + if (statistics.scriptValueCount != null) { + print("GC test script memory usage: Total heap size: " + statistics.totalHeapSize + + " usedHeapSize: " + statistics.usedHeapSize + + " totalAvailableSize: " + statistics.totalAvailableSize + + " totalGlobalHandlesSize: " + statistics.totalGlobalHandlesSize + + " usedGlobalHandlesSize: " + statistics.usedGlobalHandlesSize + + " scriptValueCount: " + statistics.scriptValueCount + + " scriptValueProxyCount: " + statistics.scriptValueProxyCount + + " qObjectCount: " + statistics.qObjectCount); + } else { + print("GC test script memory usage: Total heap size: " + statistics.totalHeapSize + + " usedHeapSize: " + statistics.usedHeapSize + + " totalAvailableSize: " + statistics.totalAvailableSize + + " totalGlobalHandlesSize: " + statistics.totalGlobalHandlesSize + + " usedGlobalHandlesSize: " + statistics.usedGlobalHandlesSize); + } +}, 5000); +Script.setInterval(function () { + for (let i = 0; i < 50000; i++) { + let dbgobj = Script.createGarbageCollectorDebuggingObject(); + }}, 10); diff --git a/scripts/developer/debugging/debugAvatarMixer.js b/scripts/developer/debugging/debugAvatarMixer.js index fad4283f7cd..1eca8423523 100644 --- a/scripts/developer/debugging/debugAvatarMixer.js +++ b/scripts/developer/debugging/debugAvatarMixer.js @@ -4,13 +4,14 @@ // debugAvatarMixer.js // scripts/developer/debugging // -// Created by Brad Hefta-Gaub on 01/09/2017 +// Created by Brad Hefta-Gaub on January 9th, 2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ +/* global Toolbars, Script, Users, Entities, AvatarList, Controller, Camera, getControllerWorldLocation */ (function() { // BEGIN LOCAL_SCOPE @@ -19,11 +20,12 @@ Script.include("/~/system/libraries/controllers.js"); var isShowingOverlays = true; var debugOverlays = {}; -var textSizeOverlay = Overlays.addOverlay("text3d", { - position: MyAvatar.position, - lineHeight: 0.1, - visible: false -}); +var textSizeOverlay = Entities.addEntity({ + "type": "Text", + "position": MyAvatar.position, + "lineHeight": 0.1, + "visible": false +}, "local"); function removeOverlays() { // enumerate the overlays and remove them @@ -32,11 +34,11 @@ function removeOverlays() { for (var i = 0; i < overlayKeys.length; ++i) { var avatarID = overlayKeys[i]; for (var j = 0; j < debugOverlays[avatarID].length; ++j) { - Overlays.deleteOverlay(debugOverlays[avatarID][j]); + Entities.deleteEntity(debugOverlays[avatarID][j]); } } - Overlays.deleteOverlay(textSizeOverlay); + Entities.deleteEntity(textSizeOverlay); debugOverlays = {}; } @@ -90,27 +92,28 @@ function updateOverlays() { //+" SM: " + AvatarManager.getAvatarSimulationRate(avatarID,"skeletonModel").toFixed(2) + "hz \n" +" JD: " + AvatarManager.getAvatarSimulationRate(avatarID,"jointData").toFixed(2) + "hz \n" - var dimensions = Overlays.textSize(textSizeOverlay, text); + var dimensions = Entities.textSize(textSizeOverlay, text); if (avatarID in debugOverlays) { // keep the overlay above the current position of this avatar - Overlays.editOverlay(debugOverlays[avatarID][0], { - dimensions: { x: 1.1 * dimensions.width, y: 0.6 * dimensions.height }, - position: overlayPosition, - text: text + Entities.editEntity(debugOverlays[avatarID][0], { + "dimensions": { "x": 1.1 * dimensions.width, "y": 0.6 * dimensions.height }, + "position": overlayPosition, + "text": text }); } else { // add the overlay above this avatar - var newOverlay = Overlays.addOverlay("text3d", { - position: overlayPosition, - dimensions: { x: 1.1 * dimensions.width, y: 0.6 * dimensions.height }, - lineHeight: 0.1, - text: text, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - solid: true, - isFacingAvatar: true, - drawInFront: true - }); + var newOverlay = Entities.addEntity({ + "type": "Text", + "position": overlayPosition, + "dimensions": { "x": 1.1 * dimensions.width, "y": 0.6 * dimensions.height }, + "lineHeight": 0.1, + "text": text, + "color": { "red": 255, "green": 255, "blue": 255}, + "alpha": 1, + "primitiveMode": "solid", + "billboardMode": "full", + "renderLayer": "front" + }, "local"); debugOverlays[avatarID]=[newOverlay]; } @@ -126,7 +129,7 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ // first remove the rendered overlays for (var j = 0; j < debugOverlays[avatarID].length; ++j) { - Overlays.deleteOverlay(debugOverlays[avatarID][j]); + Entities.deleteEntity(debugOverlays[avatarID][j]); } // delete the saved ID of the overlay from our mod overlays object diff --git a/scripts/developer/debugging/debugWorkloadWithMouseHover.js b/scripts/developer/debugging/debugWorkloadWithMouseHover.js index eaff6073598..7150182a54a 100644 --- a/scripts/developer/debugging/debugWorkloadWithMouseHover.js +++ b/scripts/developer/debugging/debugWorkloadWithMouseHover.js @@ -30,7 +30,7 @@ color: COLOR2, ignorePickIntersection: true } - var laser = Pointers.createPointer(PickType.Ray, { + var laser = Pointers.createRayPointer({ joint: "Mouse", filter: Picks.PICK_ENTITIES | Picks.PICK_BYPASS_IGNORE | Picks.PICK_INCLUDE_COLLIDABLE | Picks.PICK_INCLUDE_NONCOLLIDABLE, renderStates: [{name: "one", end: end1}], @@ -40,7 +40,7 @@ Pointers.setRenderState(laser, "one"); var hoveredObject = undefined; - var SelectionListName = "DebugWorkloadSelection"; // sekret undocumented selection list (hard coded in C++) + var SelectionListName = "DebugWorkloadSelection"; // secret undocumented selection list (hard coded in C++) var selectionStyle = { isOutlineSmooth: true, outlineWidth: 5, diff --git a/scripts/developer/debugging/queryAACubeInspector.js b/scripts/developer/debugging/queryAACubeInspector.js index d6adcf02b73..86b3899e61a 100644 --- a/scripts/developer/debugging/queryAACubeInspector.js +++ b/scripts/developer/debugging/queryAACubeInspector.js @@ -2,8 +2,9 @@ // grabInspector.js // examples/debugging/ // -// Created by Seth Alves on 2015-12-19. +// Created by Seth Alves on December 19th, 2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -26,23 +27,27 @@ function updateOverlay(entityID, queryAACube) { if (entityID in overlays) { var overlay = overlays[entityID]; - Overlays.editOverlay(overlay, { - position: cubeCenter, - size: queryAACube.scale + Entities.editEntity(overlay, { + "position": cubeCenter, + "size": queryAACube.scale }); } else { - overlays[entityID] = Overlays.addOverlay("cube", { - position: cubeCenter, - size: queryAACube.scale, - color: { - red: 0, - green: 0, - blue: 255 + overlays[entityID] = Entities.addEntity({ + "type": "Shape", + "shape": "Cube", + "position": cubeCenter, + "size": queryAACube.scale, + "color": { + "red": 0, + "green": 0, + "blue": 255 }, - alpha: 1, - solid: false, - grabbable: false - }); + "alpha": 1, + "primitiveMode": "lines", + "grab": { + "grabbable": false + } + }, "local"); } } @@ -57,7 +62,7 @@ Script.setInterval(function() { function cleanup() { for (var entityID in overlays) { - Overlays.deleteOverlay(overlays[entityID]); + Entities.deleteEntity((overlays[entityID]); } } diff --git a/scripts/developer/debugging/scriptMemoryReport.js b/scripts/developer/debugging/scriptMemoryReport.js new file mode 100644 index 00000000000..0566fb27247 --- /dev/null +++ b/scripts/developer/debugging/scriptMemoryReport.js @@ -0,0 +1,38 @@ +// +// scriptMemoryReport.js +// scripts/developer/debugging +// +// Created by dr Karol Suprynowicz on 2023/04/23 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +// A simple script that prints memory usage statistics for a given script engine every 5 seconds +// It can be included for example as a part of default scripts or controller scripts + +var memoryStatisticsIntervalHandle = Script.setInterval(function () { + statistics = Script.getMemoryUsageStatistics(); + if (statistics.scriptValueCount != null) { + print("Script memory usage: Total heap size: " + statistics.totalHeapSize + + " usedHeapSize: " + statistics.usedHeapSize + + " totalAvailableSize: " + statistics.totalAvailableSize + + " totalGlobalHandlesSize: " + statistics.totalGlobalHandlesSize + + " usedGlobalHandlesSize: " + statistics.usedGlobalHandlesSize + + " scriptValueCount: " + statistics.scriptValueCount + + " scriptValueProxyCount: " + statistics.scriptValueProxyCount + + " qObjectCount: " + statistics.qObjectCount); + } else { + print("Script memory usage: Total heap size: " + statistics.totalHeapSize + + " usedHeapSize: " + statistics.usedHeapSize + + " totalAvailableSize: " + statistics.totalAvailableSize + + " totalGlobalHandlesSize: " + statistics.totalGlobalHandlesSize + + " usedGlobalHandlesSize: " + statistics.usedGlobalHandlesSize); + } +}, 5000); + +Script.scriptEnding.connect(function () { + Script.clearInterval(memoryStatisticsIntervalHandle); +}); diff --git a/scripts/developer/tests/raypickTester.js b/scripts/developer/tests/raypickTester.js index cebee4f29af..83b2f3be72f 100644 --- a/scripts/developer/tests/raypickTester.js +++ b/scripts/developer/tests/raypickTester.js @@ -1,5 +1,10 @@ +// // raypickTester.js // +// Created by Humbletim on June 18th, 2018. +// Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// // display intersection details (including material) when hovering over entities/avatars/overlays // @@ -11,16 +16,18 @@ var JOINT_NAME = HMD.active ? HAND_JOINT : 'Mouse'; var UPDATE_MS = 1000/30; // create tect3d overlay to display hover results -var overlayID = Overlays.addOverlay('text3d', { - text: 'hover', - visible: false, - backgroundAlpha: 0, - isFacingAvatar: true, - lineHeight: 0.05, - dimensions: Vec3.HALF, -}); +var overlayID = Entities.addEntity({ + "type": "Text", + "text": "hover", + "visible": false, + "backgroundAlpha": 0, + "billboardMode": "full", + "lineHeight": 0.05, + "dimensions": Vec3.HALF, +}, "local"); + Script.scriptEnding.connect(function() { - Overlays.deleteOverlay(overlayID); + Entities.deleteEntity(overlayID); }); // create raycast picker @@ -59,10 +66,10 @@ function updateOverlay(overlayID, result) { ['submesh: ' + extraInfo.subMeshIndex, 'part: '+extraInfo.partIndex, 'shape: '+extraInfo.shapeID].join(' | '), ].filter(Boolean).join('\n'); - Overlays.editOverlay(overlayID, { - text: text, - position: position, - visible: result.intersects, + Entities.editEntity(overlayID, { + "text": text, + "position": position, + "visible": result.intersects }); } diff --git a/scripts/developer/tests/sphereLODTest.js b/scripts/developer/tests/sphereLODTest.js index d0cb35eaa1e..2504042f482 100644 --- a/scripts/developer/tests/sphereLODTest.js +++ b/scripts/developer/tests/sphereLODTest.js @@ -2,9 +2,10 @@ // sphereLodTest.js // examples/tests // -// Created by Eric Levin on 1/21/16. +// Created by Eric Levin on January 21st, 2016. // Copyright 2016 High Fidelity, Inc. - +// Copyright 2023 Overte e.V. +// // A test script for testing LODing of sphere entities and sphere overlays // // Distributed under the Apache License, Version 2.0. @@ -46,21 +47,21 @@ var entitySphere = Entities.addEntity({ }); var overlaySpherePosition = Vec3.sum(tablePosition, {x: sphereDimensions.x, y: tableDimensions.y/2 + sphereDimensions.y/2, z: 0}); -var overlaySphere = Overlays.addOverlay("sphere", { - position: overlaySpherePosition, - size: 0.01, - color: { red: 20, green: 200, blue: 0}, - alpha: 1.0, - solid: true, -}); +var overlaySphere = Entities.addEntity({ + "type": "Sphere", + "position": overlaySpherePosition, + "dimensions": sphereDimensions, + "color": { "red": 20, "green": 200, "blue": 0}, + "alpha": 1.0 +}, "local"); function cleanup() { Entities.deleteEntity(table); Entities.deleteEntity(entitySphere); - Overlays.deleteOverlay(overlaySphere); + Entities.deleteEntity(overlaySphere); } -Script.scriptEnding.connect(cleanup); \ No newline at end of file +Script.scriptEnding.connect(cleanup); diff --git a/scripts/developer/tests/webOverlayTool.js b/scripts/developer/tests/webOverlayTool.js index 1a3aa352051..8249453708a 100644 --- a/scripts/developer/tests/webOverlayTool.js +++ b/scripts/developer/tests/webOverlayTool.js @@ -1,4 +1,9 @@ -// webSpawnTool.js +// +// webOverlayTool.js +// +// Created by Bradley Austin Davis on March 16th, 2018. +// Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Stress tests the rendering of web surfaces over time // @@ -23,24 +28,25 @@ SPAWNER = function (properties) { } function makeObject(properties) { - - var overlay = Overlays.addOverlay("web3d", { - name: "Web", - url: "https://www.reddit.com/r/random", - localPosition: randomPositionXZ( { x: 0, y: 0, z: -1 }, 1), - localRotation: Quat.angleAxis(180, Vec3.Y_AXIS), - dimensions: {x: .8, y: .45, z: 0.1}, - color: { red: 255, green: 255, blue: 255 }, - alpha: 1.0, - showKeyboardFocusHighlight: false, - visible: true - }); + + var overlay = Entities.addEntity({ + "type": "Web", + "name": "Web", + "sourceUrl": "https://www.reddit.com/r/random", + "localPosition": randomPositionXZ( { "x": 0, "y": 0, "z": -1 }, 1), + "localRotation": Quat.angleAxis(180, Vec3.Y_AXIS), + "dimensions": {"x": 0.8, "y": 0.45, "z": 0.1}, + "color": { "red": 255, "green": 255, "blue": 255 }, + "alpha": 1.0, + "showKeyboardFocusHighlight": false, + "visible": true + }, "local"); var now = Date.now(); return { destroy: function () { - Overlays.deleteOverlay(overlay) + Entities.deleteEntity(overlay); }, getAge: function () { return (Date.now() - now) / 1000.0; diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index e8a83f0fc5d..7ce9bad459c 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -94,7 +94,8 @@ color: COLOR2, ignorePickIntersection: true } - var laser = Pointers.createPointer(PickType.Ray, { + //V8TODO + var laser = Pointers.createRayPointer({ joint: "Mouse", filter: Picks.PICK_ENTITIES | Picks.PICK_OVERLAYS | Picks.PICK_AVATARS, renderStates: [{name: "one", end: end1}], @@ -124,7 +125,7 @@ Pointers.disablePointer(laser) function fromQml(message) { - tokens = message.split(' ') + var tokens = message.split(' ') print("Received message from QML") if (tokens[0]=="highlight") { currentSelectionName = tokens[1]; diff --git a/scripts/developer/utilities/render/debugTransition.js b/scripts/developer/utilities/render/debugTransition.js index 491905a2ef7..e87c2330c34 100644 --- a/scripts/developer/utilities/render/debugTransition.js +++ b/scripts/developer/utilities/render/debugTransition.js @@ -78,18 +78,18 @@ type: "sphere", dimensions: END_DIMENSIONS, color: COLOR1, - ignoreRayIntersection: true + ignorePickIntersection: true } var end2 = { - type: "sphere", + type: "Sphere", dimensions: END_DIMENSIONS, color: COLOR2, - ignoreRayIntersection: true + ignorePickIntersection: true } var laser function enablePointer() { - laser = Pointers.createPointer(PickType.Ray, { + laser = Pointers.createRayPointer({ joint: "Mouse", filter: Picks.PICK_ENTITIES, renderStates: [{name: "one", end: end1}], diff --git a/scripts/developer/utilities/tools/currentAPI.js b/scripts/developer/utilities/tools/currentAPI.js index 6cab6a5710f..fc4bca160b9 100644 --- a/scripts/developer/utilities/tools/currentAPI.js +++ b/scripts/developer/utilities/tools/currentAPI.js @@ -4,9 +4,11 @@ // // Created by Clément Brisset on 5/30/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // @@ -90,6 +92,7 @@ var keys = Object.keys(object); for (var i = 0; i < keys.length; ++i) { + print("key: " + keys[i]); if (string === "") { listKeys(keys[i], object[keys[i]]); } else if (keys[i] !== "parent") { diff --git a/scripts/simplifiedUI/ui/simplifiedNametag/resources/modules/nameTagListManager.js b/scripts/simplifiedUI/ui/simplifiedNametag/resources/modules/nameTagListManager.js index c073a065892..9349f6e90a7 100644 --- a/scripts/simplifiedUI/ui/simplifiedNametag/resources/modules/nameTagListManager.js +++ b/scripts/simplifiedUI/ui/simplifiedNametag/resources/modules/nameTagListManager.js @@ -3,9 +3,11 @@ // nameTagListManager.js // Created by Milad Nazeri on 2019-03-09 // Copyright 2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // // Helps manage the list of avatars added to the nametag list // @@ -148,8 +150,9 @@ function getAvatarData(uuid) { var avatarInfo = avatar.avatarInfo; var newAvatarInfo = AvatarManager.getAvatar(uuid); + // Save the username so it doesn't get overwritten when grabbing new avatarData - var combinedAvatarInfo = Object.assign({}, newAvatarInfo, { + var combinedAvatarInfo = Object.assign({}, {avatarData: newAvatarInfo}, { username: avatarInfo === null ? null : avatarInfo.username }); @@ -168,7 +171,7 @@ function getDistance(uuid, checkAvatar, shouldSave) { var avatar = _this.avatars[uuid]; var avatarInfo = avatar.avatarInfo; - var target = avatarInfo.position; + var target = avatarInfo.avatarData.position; var currentDistance = Vec3.distance(target, eye); @@ -304,7 +307,7 @@ function getCorrectName(uuid) { var avatar = _this.avatars[uuid]; var avatarInfo = avatar.avatarInfo; - var displayNameToUse = avatarInfo.displayName.trim(); + var displayNameToUse = avatarInfo.avatarData.displayName.trim(); if (displayNameToUse === "") { displayNameToUse = "anonymous"; @@ -744,4 +747,4 @@ nameTagListManager.prototype = { }; -module.exports = nameTagListManager; \ No newline at end of file +module.exports = nameTagListManager; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 9acc5ab123e..8c72a70d721 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -1,19 +1,19 @@ +"use strict"; // // audioMuteOverlay.js // -// client script that creates an overlay to provide mute feedback -// -// Created by Triplelexx on 17/03/09 -// Reworked by Seth Alves on 2019-2-17 +// Created by Triplelexx on March 9th, 2017 +// Reworked by Seth Alves on February 17th, 2019 // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// client script that creates an overlay to provide mute feedback // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -"use strict"; - -/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ +/* global Audio, Script, Entities, Quat, MyAvatar, HMD */ (function() { // BEGIN LOCAL_SCOPE @@ -41,23 +41,27 @@ } if (HMD.active) { - warningOverlayID = Overlays.addOverlay("text3d", { - name: "Muted-Warning", - localPosition: { x: 0.0, y: -0.45, z: -1.0 }, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), - text: warningText, - textAlpha: 1, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - lineHeight: 0.042, - dimensions: { x: 0.11, y: 0.05 }, - visible: true, - ignoreRayIntersection: true, - drawInFront: true, - grabbable: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") - }); + warningOverlayID = Entities.addEntity({ + "type": "Text", + "name": "Muted-Warning", + "localPosition": { "x": 0.0, "y": -0.45, "z": -1.0 }, + "localRotation": Quat.fromVec3Degrees({ "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }), + "text": warningText, + "unlit": true, + "textAlpha": 1.0, + "textColor": { "red": 245, "green": 44, "blue": 74 }, + "backgroundAlpha": 0.0, + "lineHeight": 0.042, + "dimensions": {"x": 0.11, "y": 0.05, "z": 0.01 }, + "visible": true, + "ignorePickIntersection": true, + "renderLayer": "front", + "grab": { + "grabbable": false + }, + "parentID": MyAvatar.SELF_ID, + "parentJointIndex": MyAvatar.getJointIndex("_CAMERA_MATRIX") + }, "local"); } } @@ -65,7 +69,7 @@ if (!warningOverlayID) { return; } - Overlays.deleteOverlay(warningOverlayID); + Entities.deleteEntity(warningOverlayID); warningOverlayID = null; } diff --git a/scripts/system/avatarFinderBeacon.js b/scripts/system/avatarFinderBeacon.js index 7375d4bf4fa..64e2cbf7afb 100644 --- a/scripts/system/avatarFinderBeacon.js +++ b/scripts/system/avatarFinderBeacon.js @@ -1,7 +1,8 @@ // avatarFinderBeacon.js // -// Created by Thijs Wenker on 12/7/16 +// Created by Thijs Wenker on December 7th, 2016 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Shows 2km long red beams for each avatar outside of the 20 meter radius of your avatar, tries to ignore AC Agents. // @@ -9,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html var MIN_DISPLAY_DISTANCE = 20.0; // meters -var BEAM_COLOR = {red: 255, green: 0, blue: 0}; +var BEAM_COLOR = {"red": 255, "green": 0, "blue": 0}; var SHOW_THROUGH_WALLS = false; var BEACON_LENGTH = 2000.0; // meters var TRY_TO_IGNORE_AC_AGENTS = true; @@ -27,28 +28,38 @@ var POSSIBLE_AC_AVATARS = [ AvatarFinderBeacon = function(avatar) { var visible = false; var avatarSessionUUID = avatar.sessionUUID; - this.overlay = Overlays.addOverlay('line3d', { - color: BEAM_COLOR, - dashed: false, - start: Vec3.sum(avatar.position, {x: 0, y: -HALF_BEACON_LENGTH, z: 0}), - end: Vec3.sum(avatar.position, {x: 0, y: HALF_BEACON_LENGTH, z: 0}), - rotation: {x: 0, y: 0, z: 0, w: 1}, - visible: visible, - drawInFront: SHOW_THROUGH_WALLS, - ignoreRayIntersection: true, - parentID: avatarSessionUUID, - parentJointIndex: -2 - }); + var renderLayer = "world"; + if (SHOW_THROUGH_WALLS) { + renderLayer = "front"; + } + this.overlay = Entities.addEntity({ + "type": "PolyLine", + "color": BEAM_COLOR, + "linePoints": [ + Vec3.sum(avatar.position, {"x": 0, "y": -HALF_BEACON_LENGTH, "z": 0}), + Vec3.sum(avatar.position, {"x": 0, "y": HALF_BEACON_LENGTH, "z": 0}) + ], + "strokeWidths": [ + 0.02, + 0.02 + ], + "rotation": {"x": 0, "y": 0, "z": 0, "w": 1}, + "visible": visible, + "renderLayer": renderLayer, + "ignorePickIntersection": true, + "parentID": avatarSessionUUID, + "parentJointIndex": -2 + }, "local"); this.cleanup = function() { - Overlays.deleteOverlay(this.overlay); + Entities.deleteEntity(this.overlay); }; this.shouldShow = function() { return Vec3.distance(MyAvatar.position, avatar.position) >= MIN_DISPLAY_DISTANCE; }; this.update = function() { avatar = AvatarList.getAvatar(avatarSessionUUID); - Overlays.editOverlay(this.overlay, { - visible: this.shouldShow() + Entities.editEntity(this.overlay, { + "visible": this.shouldShow() }); }; }; diff --git a/scripts/system/away.js b/scripts/system/away.js index 7268dc32332..87273b27272 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -1,11 +1,11 @@ "use strict"; - // // away.js // -// Created by Howard Stearns 11/3/15 +// Created by Howard Stearns November, 3rd, 2015 // Copyright 2015 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -21,31 +21,31 @@ var BASIC_TIMER_INTERVAL = 50; // 50ms = 20hz var OVERLAY_WIDTH = 1920; var OVERLAY_HEIGHT = 1080; var OVERLAY_DATA = { - width: OVERLAY_WIDTH, - height: OVERLAY_HEIGHT, - imageURL: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), - emissive: true, - drawInFront: true, - alpha: 1 + "width": OVERLAY_WIDTH, + "height": OVERLAY_HEIGHT, + "imageURL": Script.resolvePath("assets/images/Overlay-Viz-blank.png"), + "emissive": true, + "drawInFront": true, + "alpha": 1.0 }; var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away var CAMERA_MATRIX = -7; var OVERLAY_DATA_HMD = { - localPosition: {x: 0, y: 0, z: -1 * MyAvatar.sensorToWorldScale}, - localRotation: {x: 0, y: 0, z: 0, w: 1}, - width: OVERLAY_WIDTH, - height: OVERLAY_HEIGHT, - url: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), - color: {red: 255, green: 255, blue: 255}, - alpha: 1, - scale: 2 * MyAvatar.sensorToWorldScale, - emissive: true, - drawInFront: true, - parentID: MyAvatar.SELF_ID, - parentJointIndex: CAMERA_MATRIX, - ignorePickIntersection: true + "type": "Image", + "localPosition": {"x": 0, "y": 0, "z": -1 * MyAvatar.sensorToWorldScale}, + "localRotation": {"x": 0, "y": 0, "z": 0, "w": 1}, + "keepAspectRatio": true, + "imageURL": Script.resolvePath("assets/images/Overlay-Viz-blank.png"), + "color": {"red": 255, "green": 255, "blue": 255}, + "alpha": 1.0, + "dimensions": Vec3.multiply({"x": 2, "y": 2, "z": 2}, MyAvatar.sensorToWorldScale), + "emissive": true, + "renderLayer": "front", + "parentID": MyAvatar.SELF_ID, + "parentJointIndex": CAMERA_MATRIX, + "ignorePickIntersection": true }; var AWAY_INTRO = { @@ -89,32 +89,32 @@ function stopAwayAnimation() { // OVERLAY var overlay = Overlays.addOverlay("image", OVERLAY_DATA); -var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD); +var overlayHMD = Entities.addEntity(OVERLAY_DATA_HMD, "local"); function showOverlay() { if (HMD.active) { // make sure desktop version is hidden - Overlays.editOverlay(overlay, { visible: false }); - Overlays.editOverlay(overlayHMD, { visible: true }); + Overlays.editOverlay(overlay, { "visible": false }); + Entities.editEntity(overlayHMD, { "visible": true }); } else { // make sure HMD is hidden - Overlays.editOverlay(overlayHMD, { visible: false }); + Entities.editEntity(overlayHMD, { "visible": false }); // Update for current screen size, keeping overlay proportions constant. var screen = Controller.getViewportDimensions(); // keep the overlay it's natural size and always center it... Overlays.editOverlay(overlay, { - visible: true, - x: ((screen.x - OVERLAY_WIDTH) / 2), - y: ((screen.y - OVERLAY_HEIGHT) / 2) + "visible": true, + "x": ((screen.x - OVERLAY_WIDTH) / 2), + "y": ((screen.y - OVERLAY_HEIGHT) / 2) }); } } function hideOverlay() { - Overlays.editOverlay(overlay, {visible: false}); - Overlays.editOverlay(overlayHMD, {visible: false}); + Overlays.editOverlay(overlay, {"visible": false}); + Entities.editEntity(overlayHMD, {"visible": false}); } hideOverlay(); @@ -131,10 +131,10 @@ function maybeMoveOverlay() { var sensorScaleFactor = MyAvatar.sensorToWorldScale; var localPosition = {x: 0, y: 0, z: -1 * sensorScaleFactor}; - Overlays.editOverlay(overlayHMD, { visible: true, localPosition: localPosition, scale: 2 * sensorScaleFactor }); + Entities.editEntity(overlayHMD, { "visible": true, "localPosition": localPosition, "dimensions": Vec3.multiply({"x": 2, "y": 2, "z": 2}, MyAvatar.sensorToWorldScale )}); // make sure desktop version is hidden - Overlays.editOverlay(overlay, { visible: false }); + Overlays.editOverlay(overlay, { "visible": false }); // also remember avatar position avatarPosition = MyAvatar.position; diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 88dee88d625..6f187dba7d4 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -1,16 +1,16 @@ "use strict"; - // // bubble.js // scripts/system/ // -// Created by Brad Hefta-Gaub on 11/18/2016 +// Created by Brad Hefta-Gaub on November 18th, 2016 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ +/* global Script, Users, Entities, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ (function () { // BEGIN LOCAL_SCOPE var button; @@ -21,15 +21,19 @@ // Affects bubble height var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself - var bubbleOverlay = Overlays.addOverlay("model", { - url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) - dimensions: { x: MyAvatar.sensorToWorldScale, y: 0.75 * MyAvatar.sensorToWorldScale, z: MyAvatar.sensorToWorldScale }, - position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, z: MyAvatar.position.z }, - rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), - scale: { x: 2 , y: MyAvatar.scale * 0.5 + 0.5, z: 2 }, - visible: false, - ignoreRayIntersection: true - }); + var bubbleOverlay = Entities.addEntity({ + "type": "Model", + "modelURL": Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) + "dimensions": { + "x": MyAvatar.sensorToWorldScale * 2, + "y": (0.75 * MyAvatar.sensorToWorldScale) * ((MyAvatar.scale * 0.5) + 0.5), + "z": MyAvatar.sensorToWorldScale * 2 + }, + "position": { "x": MyAvatar.position.x, "y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z }, + "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})), + "visible": false, + "ignorePickIntersection": true + }, "local"); // The bubble activation sound var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); // Is the update() function connected? @@ -41,8 +45,8 @@ // Hides the bubble model overlay function hideOverlays() { - Overlays.editOverlay(bubbleOverlay, { - visible: false + Entities.editEntity(bubbleOverlay, { + "visible": false }); } @@ -84,24 +88,19 @@ Script.update.disconnect(update); } - Overlays.editOverlay(bubbleOverlay, { - dimensions: { - x: MyAvatar.sensorToWorldScale, - y: 0.75 * MyAvatar.sensorToWorldScale, - z: MyAvatar.sensorToWorldScale - }, - position: { - x: MyAvatar.position.x, - y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, - z: MyAvatar.position.z + Entities.editEntity(bubbleOverlay, { + "dimensions": { + "x": MyAvatar.sensorToWorldScale * 2, + "y": (0.75 * MyAvatar.sensorToWorldScale) * ((MyAvatar.scale * 0.5) + 0.5), + "z": MyAvatar.sensorToWorldScale * 2 }, - rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), - scale: { - x: 2 , - y: MyAvatar.scale * 0.5 + 0.5 , - z: 2 + "position": { + "x": MyAvatar.position.x, + "y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + "z": MyAvatar.position.z }, - visible: true + "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})), + "visible": true }); bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); @@ -126,44 +125,34 @@ var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); if (overlayAlpha > 0) { if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { - Overlays.editOverlay(bubbleOverlay, { - dimensions: { - x: MyAvatar.sensorToWorldScale, - y: 0.75 * MyAvatar.sensorToWorldScale, - z: MyAvatar.sensorToWorldScale + Entities.editEntity(bubbleOverlay, { + "dimensions": { + "x": MyAvatar.sensorToWorldScale * 2, + "y": (0.75 * MyAvatar.sensorToWorldScale) * ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5), + "z": MyAvatar.sensorToWorldScale * 2 }, // Quickly raise the bubble from the ground up - position: { - x: MyAvatar.position.x, - y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, - z: MyAvatar.position.z + "position": { + "x": MyAvatar.position.x, + "y": (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + "z": MyAvatar.position.z }, - rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), - scale: { - x: 2 , - y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5), - z: 2 - } + "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})) }); } else { // Keep the bubble in place for a couple seconds - Overlays.editOverlay(bubbleOverlay, { - dimensions: { - x: MyAvatar.sensorToWorldScale, - y: 0.75 * MyAvatar.sensorToWorldScale, - z: MyAvatar.sensorToWorldScale + Entities.editEntity(bubbleOverlay, { + "dimensions": { + "x": MyAvatar.sensorToWorldScale * 2, + "y": (0.75 * MyAvatar.sensorToWorldScale) * ((MyAvatar.scale * 0.5) + 0.5), + "z": MyAvatar.sensorToWorldScale * 2 }, - position: { - x: MyAvatar.position.x, - y: MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, - z: MyAvatar.position.z + "position": { + "x": MyAvatar.position.x, + "y": MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + "z": MyAvatar.position.z }, - rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), - scale: { - x: 2, - y: MyAvatar.scale * 0.5 + 0.5 , - z: 2 - } + "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})) }); } } else { @@ -221,7 +210,7 @@ } Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); - Overlays.deleteOverlay(bubbleOverlay); + Entities.deleteEntity(bubbleOverlay); if (updateConnected === true) { Script.update.disconnect(update); } diff --git a/scripts/system/chat.js b/scripts/system/chat.js index 749665f3d8d..94a26dc6320 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -1,7 +1,11 @@ "use strict"; - +// // Chat.js -// By Don Hopkins (dhopkins@donhopkins.com) +// +// By Don Hopkins (dhopkins@donhopkins.com) on May 5th, 2017 +// Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -45,16 +49,19 @@ var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters - var textSizeOverlay = Overlays.addOverlay("text3d", { - position: MyAvatar.position, - lineHeight: speechBubbleLineHeight, - leftMargin: 0, - topMargin: 0, - rightMargin: 0, - bottomMargin: 0, - ignoreRayIntersection: true, - visible: false - }); + var textSizeOverlay = Entities.addEntity({ + "type": "Text", + "position": MyAvatar.position, + "lineHeight": speechBubbleLineHeight, + "leftMargin": 0, + "topMargin": 0, + "rightMargin": 0, + "bottomMargin": 0, + "unlit": true, + "alignment": "center", + "ignorePickIntersection": true, + "visible": false + }, "local"); // Load the persistent variables from the Settings, with defaults. function loadSettings() { @@ -74,8 +81,8 @@ speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); - Overlays.editOverlay(textSizeOverlay, { - lineHeight: speechBubbleLineHeight + Entities.editEntity(textSizeOverlay, { + "lineHeight": speechBubbleLineHeight }); saveSettings(); @@ -290,20 +297,23 @@ } var identifierParams = { - parentID: myAvatarID, - parentJointIndex: myJointIndex, - lifetime: identifyAvatarDuration, - start: myJointPosition, - endParentID: yourAvatarID, - endParentJointIndex: yourJointIndex, - end: yourJointPosition, - color: identifyAvatarLineColor, - alpha: 1 + "type": "PolyLine", + "parentID": myAvatarID, + "parentJointIndex": myJointIndex, + "lifetime": identifyAvatarDuration, + "linePoints": [ + myJointPosition, + yourJointPosition + ], + "strokeWidths": [ 0.02, 0.02], + //endParentID: yourAvatarID, //Currently doesn't work. Never implemented. + //endParentJointIndex: yourJointIndex, //Currently doesn't work. Never implemented. + "color": identifyAvatarLineColor }; avatarIdentifiers[yourAvatarID] = identifierParams; - identifierParams.lineID = Overlays.addOverlay("line3d", identifierParams); + identifierParams.lineID = Entities.addEntity(identifierParams, "local"); //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); @@ -329,7 +339,7 @@ } if (identifierParams.lineID) { - Overlays.deleteOverlay(identifierParams.lineID); + Entities.deleteEntity(identifierParams.lineID); } delete avatarIdentifiers[yourAvatarID]; @@ -623,37 +633,36 @@ var jointIndex = MyAvatar.getJointIndex(speechBubbleJointName); var dimensions = { - x: 100.0, - y: 100.0, - z: 0.1 + "x": 100.0, + "y": 100.0, + "z": 0.1 }; speechBubbleParams = { - type: "Text", - lifetime: speechBubbleDuration, - parentID: MyAvatar.sessionUUID, - jointIndex: jointIndex, - dimensions: dimensions, - lineHeight: speechBubbleLineHeight, - leftMargin: 0, - topMargin: 0, - rightMargin: 0, - bottomMargin: 0, - faceCamera: true, - drawInFront: true, - ignoreRayIntersection: true, - text: speechBubbleMessage, - textColor: speechBubbleTextColor, - color: speechBubbleTextColor, - backgroundColor: speechBubbleBackgroundColor + "type": "Text", + "lifetime": speechBubbleDuration, + "parentID": MyAvatar.sessionUUID, + "jointIndex": jointIndex, + "dimensions": dimensions, + "lineHeight": speechBubbleLineHeight, + "leftMargin": 0, + "topMargin": 0, + "rightMargin": 0, + "bottomMargin": 0, + "billboardMode": "full", + "renderLayer": "front", + "ignorePickIntersection": true, + "text": speechBubbleMessage, + "textColor": speechBubbleTextColor, + "backgroundColor": speechBubbleBackgroundColor }; // Only overlay text3d has a way to measure the text, not entities. // So we make a temporary one just for measuring text, then delete it. - var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams); - var textSize = Overlays.textSize(textSizeOverlay, speechBubbleMessage); + var speechBubbleTextOverlayID = Entities.addEntity(speechBubbleParams, "local"); + var textSize = Entities.textSize(textSizeOverlay, speechBubbleMessage); try { - Overlays.deleteOverlay(speechBubbleTextOverlayID); + Entities.deleteEntity(speechBubbleTextOverlayID); } catch (e) {} //print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height); @@ -985,7 +994,7 @@ unidentifyAvatars(); disconnectWebHandler(); - Overlays.deleteOverlay(textSizeOverlay); + Entities.deleteEntity(textSizeOverlay); if (onChatPage) { tablet.gotoHomeScreen(); diff --git a/scripts/system/checkForUpdates.js b/scripts/system/checkForUpdates.js deleted file mode 100644 index 6982024db11..00000000000 --- a/scripts/system/checkForUpdates.js +++ /dev/null @@ -1,54 +0,0 @@ -// checkForUpdates.js -// -// Created by Kalila L. on 8/27/20 -// 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 - -Script.include("/~/system/libraries/utils.js"); -var networkingConstants = Script.require("./libraries/networkingConstants.js"); - -// We'll want a way to specify this later, auto detection can also be a thing, but strictly optional. -var currentBuildType = "latest"; -var currentVersion = About.buildVersion; - -// Notification Variables -var NOTIFICATION_CHANNEL = "System-Notifications"; -var NOTIFICATION_TYPE = "Update-Notification"; -var NOTIFICATION_CATEGORY = "Interface"; -var notificationText = "An update is available: "; -var notificationColor = { red: 137, green: 63, blue: 255 }; - -function retrieveMetadata() { - var requireMetadata = Script.require(networkingConstants.INTERFACE_METADATA_SOURCE + '?' + Date.now()); - - if (requireMetadata) { - return requireMetadata; - } else { - return false; - } -} - -function checkForUpdates() { - var vircadiaMetadata = retrieveMetadata(); - - // Don't check for updates on a dev build. - if (vircadiaMetadata && currentVersion !== "dev") { - var checkVersion = semanticVersionCompare(currentVersion, vircadiaMetadata[currentBuildType].version); - - // An update is available! - if (checkVersion === -1) { - Messages.sendMessage(NOTIFICATION_CHANNEL, JSON.stringify({ - type: NOTIFICATION_TYPE, - category: NOTIFICATION_CATEGORY, - channel: "Local", - position: MyAvatar.position, - colour: notificationColor, - message: notificationText + vircadiaMetadata[currentBuildType].version, - })); - } - } -} - -checkForUpdates(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 5af86d3bbd0..64ee5b5a363 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -2,8 +2,14 @@ // controllerDispatcher.js // +// Created by Seth Alves, July 27th, 2017. +// Copyright 2017 High Fidelity, Inc. +// Copyright 2023, Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* jslint bitwise: true */ @@ -15,8 +21,8 @@ PointerManager, print, Keyboard */ -controllerDispatcherPlugins = {}; -controllerDispatcherPluginsNeedSort = false; +var controllerDispatcherPlugins = {}; +var controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); @@ -256,9 +262,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.dataGatherers.rightControllerLocation() ]; - // find 3d overlays near each hand + // find 3d overlays/Local Entities near each hand var nearbyOverlayIDs = []; var h; +//V8TODO: Overlays.findOverlays might not work here for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { var nearbyOverlays = @@ -282,9 +289,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } nearbyOverlays.sort(function (a, b) { - var aPosition = Overlays.getProperty(a, "position"); + var aPosition = Entities.getEntityProperties(a, ["position"]).position; var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); - var bPosition = Overlays.getProperty(b, "position"); + var bPosition = Entities.getEntityProperties(b, ["position"]).position; var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); @@ -305,20 +312,31 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (SHOW_GRAB_SPHERE) { if (this.grabSphereOverlays[h]) { - Overlays.editOverlay(this.grabSphereOverlays[h], { position: controllerLocations[h].position }); + Entities.editEntity(this.grabSphereOverlays[h], { "position": controllerLocations[h].position }); } else { var grabSphereSize = findRadius * 2; - this.grabSphereOverlays[h] = Overlays.addOverlay("sphere", { - position: controllerLocations[h].position, - dimensions: { x: grabSphereSize, y: grabSphereSize, z: grabSphereSize }, - color: { red: 30, green: 30, blue: 255 }, - alpha: 0.3, - solid: true, - visible: true, - // lineWidth: 2.0, - drawInFront: false, - grabbable: false - }); + this.grabSphereOverlays[h] = Entities.addEntity({ + "type": "Shape", + "shape": "Sphere", + "position": controllerLocations[h].position, + "dimensions": { + "x": grabSphereSize, + "y": grabSphereSize, + "z": grabSphereSize + }, + "color": { + "red": 30, + "green": 30, + "blue": 255 + }, + "alpha": 0.3, + "primitiveMode": "solid", + "visible": true, + "renderLayer": "front", + "grab": { + "grabbable": false + } + }, "local"); } } @@ -364,28 +382,30 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // XXX find a way to extract searchRay from samuel's stuff - rayPicks[h].searchRay = { - origin: controllerLocations[h].position, - direction: Quat.getUp(controllerLocations[h].orientation), - length: 1000 - }; - - if (rayPicks[h].type === Picks.INTERSECTED_ENTITY) { - // XXX check to make sure this one isn't already in nearbyEntityProperties? - if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS * sensorScaleFactor) { - var nearEntityID = rayPicks[h].objectID; - var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); - nearbyProps.id = nearEntityID; - nearbyProps.distance = rayPicks[h].distance; - nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; - nearbyEntityProperties[h].push(nearbyProps); + if (controllerLocations[h].valid) { + rayPicks[h].searchRay = { + origin: controllerLocations[h].position, + direction: Quat.getUp(controllerLocations[h].orientation), + length: 1000 + }; + + if (rayPicks[h].type === Picks.INTERSECTED_ENTITY) { + // XXX check to make sure this one isn't already in nearbyEntityProperties? + if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS * sensorScaleFactor) { + var nearEntityID = rayPicks[h].objectID; + var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); + nearbyProps.id = nearEntityID; + nearbyProps.distance = rayPicks[h].distance; + nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; + nearbyEntityProperties[h].push(nearbyProps); + } } - } - // sort by distance from each hand - nearbyEntityProperties[h].sort(function (a, b) { - return a.distance - b.distance; - }); + // sort by distance from each hand + nearbyEntityProperties[h].sort(function (a, b) { + return a.distance - b.distance; + }); + } } // sometimes, during a HMD snap-turn, an equipped or held item wont be near @@ -598,7 +618,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); hand: RIGHT_HAND }); - this.mouseRayPointer = Pointers.createPointer(PickType.Ray, { + this.mouseRayPointer = Pointers.createRayPointer({ joint: "Mouse", filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, enabled: true @@ -648,8 +668,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); Pointers.removePointer(this.mouseRayPointer); - Overlays.mouseReleaseOnOverlay.disconnect(mouseReleaseOnOverlay); - Overlays.mousePressOnOverlay.disconnect(mousePress); + Entities.mouseReleaseOnEntity.disconnect(mouseReleaseOn); Entities.mousePressOnEntity.disconnect(mousePress); Messages.messageReceived.disconnect(controllerDispatcher.handleMessage); if (_this.debugPanelID) { @@ -660,30 +679,30 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (DEBUG) { this.debugPanelID = Entities.addEntity({ - name: "controllerDispatcher debug panel", - type: "Text", - dimensions: { x: 1.0, y: 0.3, z: 0.01 }, - parentID: MyAvatar.sessionUUID, + "name": "controllerDispatcher debug panel", + "type": "Text", + "dimensions": { "x": 1.0, "y": 0.3, "z": 0.01 }, + "parentID": MyAvatar.sessionUUID, // parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), - parentJointIndex: -1, - localPosition: { x: -0.25, y: 0.8, z: -1.2 }, - textColor: { red: 255, green: 255, blue: 255}, - backgroundColor: { red: 0, green: 0, blue: 0}, - text: "", - lineHeight: 0.03, - leftMargin: 0.015, - topMargin: 0.01, - backgroundAlpha: 0.7, - textAlpha: 1.0, - unlit: true, - ignorePickIntersection: true + "parentJointIndex": -1, + "localPosition": { "x": -0.25, "y": 0.8, "z": -1.2 }, + "textColor": { "red": 255, "green": 255, "blue": 255}, + "backgroundColor": { "red": 0, "green": 0, "blue": 0}, + "text": "", + "lineHeight": 0.03, + "leftMargin": 0.015, + "topMargin": 0.01, + "backgroundAlpha": 0.7, + "textAlpha": 1.0, + "unlit": true, + "ignorePickIntersection": true }, "local"); } } - function mouseReleaseOnOverlay(overlayID, event) { - if (HMD.homeButtonID && overlayID === HMD.homeButtonID && event.button === "Primary") { - Messages.sendLocalMessage("home", overlayID); + function mouseReleaseOn(id, event) { + if (HMD.homeButtonID && id === HMD.homeButtonID && event.button === "Primary") { + Messages.sendLocalMessage("home", id); } } @@ -700,8 +719,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } - Overlays.mouseReleaseOnOverlay.connect(mouseReleaseOnOverlay); - Overlays.mousePressOnOverlay.connect(mousePress); + Entities.mouseReleaseOnEntity.connect(mouseReleaseOn); Entities.mousePressOnEntity.connect(mousePress); var controllerDispatcher = new ControllerDispatcher(); diff --git a/scripts/system/controllers/controllerDisplay.js b/scripts/system/controllers/controllerDisplay.js index e40b7613072..c2fe71f5003 100644 --- a/scripts/system/controllers/controllerDisplay.js +++ b/scripts/system/controllers/controllerDisplay.js @@ -3,11 +3,13 @@ // // Created by Anthony J. Thibault on 10/20/16 // Originally created by Ryan Huffman on 9/21/2016 +// Copyright 2016 High Fidelity, Inc. +// Copyright 2023, Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* globals createControllerDisplay:true, deleteControllerDisplay:true, Controller, Overlays, Vec3, MyAvatar, Quat */ +/* globals createControllerDisplay:true, deleteControllerDisplay:true, Controller, Entities, Vec3, MyAvatar, Quat */ function clamp(value, min, max) { if (value < min) { @@ -51,8 +53,8 @@ createControllerDisplay = function(config) { setVisible: function(visible) { for (var i = 0; i < this.overlays.length; ++i) { - Overlays.editOverlay(this.overlays[i], { - visible: visible + Entities.editEntity(this.overlays[i], { + "visible": visible }); } }, @@ -62,8 +64,8 @@ createControllerDisplay = function(config) { /* if (partName in this.partOverlays) { for (var i = 0; i < this.partOverlays[partName].length; ++i) { - Overlays.editOverlay(this.partOverlays[partName][i], { - //visible: visible + Entities.editEntity(this.partOverlays[partName][i], { + //"visible": visible }); } } @@ -80,8 +82,8 @@ createControllerDisplay = function(config) { textures[part.textureName] = layer.defaultTextureURL; } for (var i = 0; i < this.partOverlays[partName].length; ++i) { - Overlays.editOverlay(this.partOverlays[partName][i], { - textures: textures + Entities.editEntity(this.partOverlays[partName][i], { + "textures": textures }); } } @@ -93,14 +95,14 @@ createControllerDisplay = function(config) { var controller = config.controllers[0]; var position = controller.position; - // first overlay is main body. + // first overlay/Local Entity is main body. var overlayID = this.overlays[0]; var localPosition = Vec3.multiply(sensorScaleFactor, Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position)); var dimensions = Vec3.multiply(sensorScaleFactor, controller.dimensions); - Overlays.editOverlay(overlayID, { - dimensions: dimensions, - localPosition: localPosition + Entities.editEntity(overlayID, { + "dimensions": dimensions, + "localPosition": localPosition }); if (controller.parts) { @@ -143,15 +145,15 @@ createControllerDisplay = function(config) { } } if (localRotation !== undefined) { - Overlays.editOverlay(overlayID, { - dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions), - localPosition: Vec3.multiply(sensorScaleFactor, localPosition), - localRotation: localRotation + Entities.editEntity(overlayID, { + "dimensions": Vec3.multiply(sensorScaleFactor, part.naturalDimensions), + "localPosition": Vec3.multiply(sensorScaleFactor, localPosition), + "localRotation": localRotation }); } else { - Overlays.editOverlay(overlayID, { - dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions), - localPosition: Vec3.multiply(sensorScaleFactor, localPosition) + Entities.editEntity(overlayID, { + "dimensions": Vec3.multiply(sensorScaleFactor, part.naturalDimensions), + "localPosition": Vec3.multiply(sensorScaleFactor, localPosition) }); } } @@ -172,15 +174,16 @@ createControllerDisplay = function(config) { controller.naturalPosition = { x: 0, y: 0, z: 0 }; } - var baseOverlayID = Overlays.addOverlay("model", { - url: controller.modelURL, - dimensions: Vec3.multiply(sensorScaleFactor, controller.dimensions), - localRotation: controller.rotation, - localPosition: Vec3.multiply(sensorScaleFactor, position), - parentID: MyAvatar.SELF_ID, - parentJointIndex: controller.jointIndex, - ignoreRayIntersection: true - }); + var baseOverlayID = Entities.addEntity({ + "type": "Model", + "modelURL": controller.modelURL, + "dimensions": Vec3.multiply(sensorScaleFactor, controller.dimensions), + "localRotation": controller.rotation, + "localPosition": Vec3.multiply(sensorScaleFactor, position), + "parentID": MyAvatar.SELF_ID, + "parentJointIndex": controller.jointIndex, + "ignorePickIntersection": true + }, "local"); controllerDisplay.overlays.push(baseOverlayID); @@ -193,11 +196,12 @@ createControllerDisplay = function(config) { controllerDisplay.parts[partName] = controller.parts[partName]; var properties = { - url: part.modelURL, - localPosition: localPosition, - localRotation: localRotation, - parentID: baseOverlayID, - ignoreRayIntersection: true + "type": "Model", + "modelURL": part.modelURL, + "localPosition": localPosition, + "localRotation": localRotation, + "parentID": baseOverlayID, + "ignorePickIntersection": true }; if (part.defaultTextureLayer) { @@ -206,7 +210,7 @@ createControllerDisplay = function(config) { properties.textures = textures; } - var overlayID = Overlays.addOverlay("model", properties); + var overlayID = Entities.addEntity(properties, "local"); if (part.type === "rotational") { var input = resolveHardware(part.input); @@ -286,7 +290,7 @@ createControllerDisplay = function(config) { deleteControllerDisplay = function(controllerDisplay) { for (var i = 0; i < controllerDisplay.overlays.length; ++i) { - Overlays.deleteOverlay(controllerDisplay.overlays[i]); + Entities.deleteEntity(controllerDisplay.overlays[i]); } Controller.disableMapping(controllerDisplay.mappingName); }; diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index fee38f2cdd5..e5d712d448c 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -2,6 +2,10 @@ // equipEntity.js // +// Created by Seth Alves, August 14th, 2017. +// Copyright 2017 High Fidelity, Inc. +// Copyright 2023, Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -74,19 +78,20 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { } // override default sphere with a user specified model, if it exists. - overlayInfoSet.overlays.push(Overlays.addOverlay("model", { - name: "hotspot overlay", - url: hotspot.indicatorURL ? hotspot.indicatorURL : DEFAULT_SPHERE_MODEL_URL, - position: hotspot.worldPosition, - rotation: { - x: 0, - y: 0, - z: 0, - w: 1 + overlayInfoSet.overlays.push(Entities.addEntity({ + "type": "Model", + "name": "hotspot overlay", + "modelURL": hotspot.indicatorURL ? hotspot.indicatorURL : DEFAULT_SPHERE_MODEL_URL, + "position": hotspot.worldPosition, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 }, - dimensions: dimensions, - ignoreRayIntersection: true - })); + "dimensions": dimensions, + "ignorePickIntersection": true + }, "local")); overlayInfoSet.type = "model"; this.map[hotspot.key] = overlayInfoSet; } else { @@ -131,7 +136,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (overlayInfoSet.timestamp !== timestamp && overlayInfoSet.currentSize <= 0.05) { // this is an old overlay, that has finished fading out, delete it! - overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); + overlayInfoSet.overlays.forEach(Entities.deleteEntity); delete this.map[keys[i]]; } else { // update overlay position, rotation to follow the object it's attached to. @@ -153,14 +158,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } overlayInfoSet.overlays.forEach(function(overlay) { - Overlays.editOverlay(overlay, { - position: position, - rotation: props.rotation, - dimensions: dimensions + Entities.editEntity(overlay, { + "position": position, + "rotation": props.rotation, + "dimensions": dimensions }); }); } else { - overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); + overlayInfoSet.overlays.forEach(Entities.deleteEntity); delete this.map[keys[i]]; } } diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index c486d46c333..fa6e7b5b15d 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -567,6 +567,8 @@ Script.include("/~/system/libraries/controllers.js"); if (this.distanceHolding) { var targetProps = Entities.getEntityProperties(this.targetObject.entityID, ["position", "rotation", "registrationPoint", "dimensions"]); + if (controllerData.rayPicks[this.hand].intersection == null) + return undefined; return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 8453a7d8d39..d553bf57144 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -1,4 +1,4 @@ -"use strict"; +"no use strict"; // inEditMode.js // @@ -72,14 +72,17 @@ Script.include("/~/system/libraries/utils.js"); if (controllerData.triggerClicks[this.hand]) { var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand; if (!this.triggerClicked) { + print("inEditMode click"); this.selectedTarget = controllerData.rayPicks[this.hand]; if (!this.selectedTarget.intersects) { + print("inEditMode no intersect"); Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "clearSelection", hand: hand })); } else { if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) { + print("inEditMode select entity"); Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "selectEntity", entityID: this.selectedTarget.objectID, @@ -88,6 +91,7 @@ Script.include("/~/system/libraries/utils.js"); intersection: this.selectedTarget.intersection })); } else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) { + print("inEditMode select overlay"); Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({ method: "selectOverlay", overlayID: this.selectedTarget.objectID, @@ -106,6 +110,10 @@ Script.include("/~/system/libraries/utils.js"); this.sendPointingAtData = function(controllerData) { var rayPick = controllerData.rayPicks[this.hand]; var hudRayPick = controllerData.hudRayPicks[this.hand]; + // V8TODO: this needs to be checked if it works correctly + if (!hudRayPick.intersects) { + return; + } var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); var desktopWindow = Window.isPointOnDesktopWindow(point2d); var tablet = this.pointingAtTablet(rayPick.objectID); @@ -195,12 +203,13 @@ Script.include("/~/system/libraries/utils.js"); if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)) { var stopRunning = false; - controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) { + // V8TODO: check if this doesn't break anything + /*controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) { var overlayName = Overlays.getProperty(overlayID, "name"); if (overlayName === "KeyboardAnchor") { stopRunning = true; } - }); + });*/ if (stopRunning) { return this.exitModule(); diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 104e37d76c6..1483a19feb7 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -1,12 +1,15 @@ -"use strict"; +"no use strict"; // inVREditMode.js // // Created by David Rowe on 16 Sep 2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* global Script, HMD, Messages, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName, makeLaserParams diff --git a/scripts/system/controllers/controllerModules/nearGrabEntity.js b/scripts/system/controllers/controllerModules/nearGrabEntity.js index 45d518bb398..1a1b55c0209 100644 --- a/scripts/system/controllers/controllerModules/nearGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabEntity.js @@ -102,7 +102,9 @@ Script.include("/~/system/libraries/controllers.js"); for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position. - var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props); + // TODO: this function gives incorrect result now and needs to be fixed later + //var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props); + var dist = 0; var distance = Vec3.distance(grabPosition, props.position); if ((dist > nearGrabDistance) || (distance > nearGrabRadius)) { // Only smallish entities can be near grabbed. diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 5dcfee23cb9..a8f1487105e 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -47,7 +47,7 @@ Script.include("/~/system/libraries/utils.js"); }; this.isGrabbedThingVisible = function() { - return Overlays.getProperty(this.grabbedThingID, "visible"); + return Entities.getEntityProperties(this.grabbedThingID, ["visible"]).visible; }; this.thisHandIsParent = function(props) { @@ -78,10 +78,10 @@ Script.include("/~/system/libraries/utils.js"); this.getGrabbedProperties = function() { return { - position: Overlays.getProperty(this.grabbedThingID, "position"), - rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), - parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), - parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + position: Entities.getEntityProperties(this.grabbedThingID, ["position"]).position, + rotation: Entities.getEntityProperties(this.grabbedThingID, ["rotation"]).rotation, + parentID: Entities.getEntityProperties(this.grabbedThingID, ["parentID"]).parentID, + parentJointIndex: Entities.getEntityProperties(this.grabbedThingID, ["parentJointIndex"]).parentJointIndex, dynamic: false, shapeType: "none" }; @@ -164,7 +164,7 @@ Script.include("/~/system/libraries/utils.js"); this.getTargetID = function(overlays, controllerData) { var sensorScaleFactor = MyAvatar.sensorToWorldScale; for (var i = 0; i < overlays.length; i++) { - var overlayPosition = Overlays.getProperty(overlays[i], "position"); + var overlayPosition = Entities.getEntityProperties(overlays[i], ["position"]).position; var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(overlayPosition, handPosition); if (distance <= NEAR_GRAB_RADIUS * sensorScaleFactor) { @@ -202,7 +202,8 @@ Script.include("/~/system/libraries/utils.js"); var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; var grabbableOverlays = candidateOverlays.filter(function(overlayID) { - return Overlays.getProperty(overlayID, "grabbable"); + // V8TODO: check if this works + return Entities.getEntityProperties(overlayID, ["grab"]).grab.grabbable; }); var targetID = this.getTargetID(grabbableOverlays, controllerData); diff --git a/scripts/system/controllers/controllerModules/stylusInput.js b/scripts/system/controllers/controllerModules/stylusInput.js index cbef45050ef..15048bca006 100644 --- a/scripts/system/controllers/controllerModules/stylusInput.js +++ b/scripts/system/controllers/controllerModules/stylusInput.js @@ -1,9 +1,14 @@ -"use strict"; +"no use strict"; // stylusInput.js // +// Copyright 2017-2020 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* global Script, MyAvatar, Controller, Uuid, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Vec3, makeDispatcherModuleParameters, Overlays, HMD, Settings, getEnabledModuleByName, Pointers, @@ -26,7 +31,7 @@ Script.include("/~/system/libraries/controllers.js"); } function getOverlayDistance(controllerPosition, overlayID) { - var position = Overlays.getProperty(overlayID, "position"); + var position = Entities.getEntityProperties(overlayID, ["position"]).position; return { id: overlayID, distance: Vec3.distance(position, controllerPosition) @@ -42,7 +47,8 @@ Script.include("/~/system/libraries/controllers.js"); [], 100); - this.pointer = Pointers.createPointer(PickType.Stylus, { + //V8TODO + this.pointer = Pointers.createStylusPointer({ hand: this.hand, filter: Picks.PICK_OVERLAYS, hover: true, @@ -93,7 +99,7 @@ Script.include("/~/system/libraries/controllers.js"); for (i = 0; i < candidateOverlays.length; i++) { if (!(HMD.tabletID && candidateOverlays[i] === HMD.tabletID) && - Overlays.getProperty(candidateOverlays[i], "visible")) { + Entities.getEntityProperties(candidateOverlays[i], ["visible"]).visible) { stylusTarget = getOverlayDistance(controllerPosition, candidateOverlays[i]); if (stylusTarget) { stylusTargets.push(stylusTarget); @@ -103,7 +109,7 @@ Script.include("/~/system/libraries/controllers.js"); // add the tabletScreen, if it is valid if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL && - Overlays.getProperty(HMD.tabletScreenID, "visible")) { + Entities.getEntityProperties(HMD.tabletScreenID, ["visible"]).visible) { stylusTarget = getOverlayDistance(controllerPosition, HMD.tabletScreenID); if (stylusTarget) { stylusTargets.push(stylusTarget); @@ -112,7 +118,7 @@ Script.include("/~/system/libraries/controllers.js"); // add the tablet home button. if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL && - Overlays.getProperty(HMD.homeButtonID, "visible")) { + Entities.getEntityProperties(HMD.homeButtonID, ["visible"]).visible) { stylusTarget = getOverlayDistance(controllerPosition, HMD.homeButtonID); if (stylusTarget) { stylusTargets.push(stylusTarget); @@ -120,7 +126,7 @@ Script.include("/~/system/libraries/controllers.js"); } // Add the mini tablet. - if (HMD.miniTabletScreenID && Overlays.getProperty(HMD.miniTabletScreenID, "visible") && + if (HMD.miniTabletScreenID && Entities.getEntityProperties(HMD.miniTabletScreenID, ["visible"]).visible && this.hand != HMD.miniTabletHand) { stylusTarget = getOverlayDistance(controllerPosition, HMD.miniTabletScreenID); if (stylusTarget) { diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 98c3e70fdf3..63106b62416 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -73,23 +73,24 @@ Script.include("/~/system/libraries/controllers.js"); width: 0.025, drawInFront: true }; - + + //V8TODO: check render states var teleportEnd = { - type: "model", + type: "Model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; var seatEnd = { - type: "model", + type: "Model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, ignorePickIntersection: true }; var collisionEnd = { - type: "shape", + type: "Shape", shape: "box", dimensions: { x: 1.0, y: 0.001, z: 1.0 }, alpha: 0.0, @@ -215,16 +216,22 @@ Script.include("/~/system/libraries/controllers.js"); var avatarSensorPosition = Mat4.transformPoint(worldToSensorMatrix, MyAvatar.position); avatarSensorPosition.y = 0; - var targetRotation = Overlays.getProperty(_this.targetOverlayID, "rotation"); - var relativePlayAreaCenterOffset = - Vec3.sum(_this.playAreaCenterOffset, { x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0 }); - var localPosition = Vec3.multiplyQbyV(Quat.inverse(targetRotation), - Vec3.multiplyQbyV(sensorToWorldRotation, - Vec3.multiply(avatarScale, Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))); - localPosition.y = _this.teleportScaleFactor * localPosition.y; + var targetRotation = Entities.getEntityProperties(_this.targetOverlayID, ["rotation"]).rotation; + if (targetRotation != null) { + var relativePlayAreaCenterOffset = + Vec3.sum(_this.playAreaCenterOffset, {x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0}); + var localPosition = Vec3.multiplyQbyV(Quat.inverse(targetRotation), + Vec3.multiplyQbyV(sensorToWorldRotation, + Vec3.multiply(avatarScale, Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))); + localPosition.y = _this.teleportScaleFactor * localPosition.y; + } else { + print(""); + } playAreaOverlayProperties.parentID = _this.targetOverlayID; - playAreaOverlayProperties.localPosition = localPosition; + if (targetRotation != null) { + playAreaOverlayProperties.localPosition = localPosition; + } } Overlays.editOverlay(_this.playAreaOverlay, playAreaOverlayProperties); @@ -295,7 +302,8 @@ Script.include("/~/system/libraries/controllers.js"); _this.cleanup(); } - _this.teleportParabolaHandVisuals = Pointers.createPointer(PickType.Parabola, { + //V8TODO + _this.teleportParabolaHandVisuals = Pointers.createParabolaPointer({ joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", dirOffset: { x: 0, y: 1, z: 0.1 }, posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, @@ -311,7 +319,8 @@ Script.include("/~/system/libraries/controllers.js"); maxDistance: 8.0 }); - _this.teleportParabolaHandCollisions = Pointers.createPointer(PickType.Parabola, { + //V8TODO + _this.teleportParabolaHandCollisions = Pointers.createParabolaPointer({ joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", dirOffset: { x: 0, y: 1, z: 0.1 }, posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, @@ -326,7 +335,8 @@ Script.include("/~/system/libraries/controllers.js"); maxDistance: 8.0 }); - _this.teleportParabolaHeadVisuals = Pointers.createPointer(PickType.Parabola, { + //V8TODO + _this.teleportParabolaHeadVisuals = Pointers.createParabolaPointer({ joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, @@ -340,7 +350,8 @@ Script.include("/~/system/libraries/controllers.js"); maxDistance: 8.0 }); - _this.teleportParabolaHeadCollisions = Pointers.createPointer(PickType.Parabola, { + //V8TODO + _this.teleportParabolaHeadCollisions = Pointers.createParabolaPointer({ joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, @@ -399,6 +410,7 @@ Script.include("/~/system/libraries/controllers.js"); }); + //V8TODO: this won't work anymore _this.playAreaOverlay = Overlays.addOverlay("model", { url: _this.PLAY_AREA_OVERLAY_MODEL, drawInFront: false, @@ -424,6 +436,7 @@ Script.include("/~/system/libraries/controllers.js"); for (var i = 0; i < _this.playAreaSensorPositions.length; i++) { if (i > _this.playAreaSensorPositionOverlays.length - 1) { + //V8TODO: replace with local entity var overlay = Overlays.addOverlay("model", { url: _this.PLAY_AREA_SENSOR_OVERLAY_MODEL, dimensions: _this.PLAY_AREA_SENSOR_OVERLAY_DIMENSIONS, @@ -507,18 +520,21 @@ Script.include("/~/system/libraries/controllers.js"); }); } else { // Set play area position and rotation in local coordinates with parenting. - var targetRotation = Overlays.getProperty(_this.targetOverlayID, "rotation"); - var sensorToTargetRotation = Quat.multiply(Quat.inverse(targetRotation), sensorToWorldRotation); - var relativePlayAreaCenterOffset = - Vec3.sum(_this.playAreaCenterOffset, { x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0 }); - Overlays.editOverlay(_this.playAreaOverlay, { - parentID: _this.targetOverlayID, - localPosition: Vec3.multiplyQbyV(Quat.inverse(targetRotation), - Vec3.multiplyQbyV(sensorToWorldRotation, - Vec3.multiply(MyAvatar.sensorToWorldScale, - Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))), - localRotation: sensorToTargetRotation - }); + var targetRotation = Entities.getEntityProperties(_this.targetOverlayID, ["rotation"]).rotation; + // TODO: Why is targetRotation undefined sometimes? + if (targetRotation) { + var sensorToTargetRotation = Quat.multiply(Quat.inverse(targetRotation), sensorToWorldRotation); + var relativePlayAreaCenterOffset = + Vec3.sum(_this.playAreaCenterOffset, {x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0}); + Overlays.editOverlay(_this.playAreaOverlay, { + parentID: _this.targetOverlayID, + localPosition: Vec3.multiplyQbyV(Quat.inverse(targetRotation), + Vec3.multiplyQbyV(sensorToWorldRotation, + Vec3.multiply(MyAvatar.sensorToWorldScale, + Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))), + localRotation: sensorToTargetRotation + }); + } } }; diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index cf700a8ad9f..27388ed153a 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -58,7 +58,8 @@ Script.include("/~/system/libraries/controllers.js"); if (nearGrabModule) { var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; var grabbableOverlays = candidateOverlays.filter(function(overlayID) { - return Overlays.getProperty(overlayID, "grabbable"); + //V8TODO: this needs to be checked if it works + return Entities.getEntityProperties(overlayID, ["grab"]).grab.grabbable; }); var target = nearGrabModule.getTargetID(grabbableOverlays, controllerData); if (target) { @@ -105,7 +106,7 @@ Script.include("/~/system/libraries/controllers.js"); if (intersection.type === Picks.INTERSECTED_OVERLAY) { var overlayIndex = this.ignoredObjects.indexOf(objectID); - var overlayName = Overlays.getProperty(objectID, "name"); + var overlayName = Entities.getEntityProperties(objectID, ["name"]).name; if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" && overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") { var data = { diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index fb422ebdf7c..6eabce512ac 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -11,13 +11,15 @@ /* global Script, Menu */ +Script.include("controllerDispatcher.js"); + var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", "grab.js", //"toggleAdvancedMovementForHandControllers.js", "handTouch.js", - "controllerDispatcher.js", + //"controllerDispatcher.js", "controllerModules/nearParentGrabOverlay.js", "controllerModules/stylusInput.js", "controllerModules/equipEntity.js", @@ -39,11 +41,15 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/trackedHandTablet.js" ]; +Script.include("../../developer/debugging/scriptMemoryReport.js"); +//Script.include("developer/debugging/scriptMemoryReport.js"); + var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; function runDefaultsTogether() { for (var j in CONTOLLER_SCRIPTS) { if (CONTOLLER_SCRIPTS.hasOwnProperty(j)) { + print("including " + CONTOLLER_SCRIPTS[j]); Script.include(CONTOLLER_SCRIPTS[j]); } } @@ -52,6 +58,7 @@ function runDefaultsTogether() { function runDefaultsSeparately() { for (var i in CONTOLLER_SCRIPTS) { if (CONTOLLER_SCRIPTS.hasOwnProperty(i)) { + print("loading " + CONTOLLER_SCRIPTS[j]); Script.load(CONTOLLER_SCRIPTS[i]); } } diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index cd18c383024..459fad34256 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -5,6 +5,7 @@ // // Created by Eric Levin on May 1, 2015 // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Grab's physically moveable entities with the mouse, by applying a spring force. // @@ -12,6 +13,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /* global MyAvatar, Entities, Script, HMD, Camera, Vec3, Reticle, Overlays, Messages, Quat, Controller, @@ -146,7 +148,7 @@ Mouse.prototype.restoreRotateCursor = function() { var mouse = new Mouse(); var beacon = { - type: "cube", + type: "Box", dimensions: { x: 0.01, y: 0, @@ -158,8 +160,7 @@ var beacon = { blue: 200 }, alpha: 1, - solid: true, - ignoreRayIntersection: true, + ignorePickIntersection: true, visible: true }; @@ -211,7 +212,7 @@ function Grabber() { Picks.setIncludeItems(this.mouseRayOverlays, tabletItems); } var renderStates = [{name: "grabbed", end: beacon}]; - this.mouseRayEntities = Pointers.createPointer(PickType.Ray, { + this.mouseRayEntities = Pointers.createRayPointer({ joint: "Mouse", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, faceAvatar: true, @@ -344,7 +345,7 @@ Grabber.prototype.pressEvent = function(event) { this.computeNewGrabPlane(); this.moveEvent(event); - var args = "mouse"; + var args = ["mouse"]; Entities.callEntityMethod(this.entityID, "startDistanceGrab", args); Messages.sendLocalMessage('Hifi-Object-Manipulation', JSON.stringify({ @@ -378,7 +379,7 @@ Grabber.prototype.releaseEvent = function(event) { Pointers.setRenderState(this.mouseRayEntities, ""); Pointers.setLockEndUUID(this.mouseRayEntities, null, false); - var args = "mouse"; + var args = ["mouse"]; Entities.callEntityMethod(this.entityID, "releaseGrab", args); Messages.sendLocalMessage('Hifi-Object-Manipulation', JSON.stringify({ diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index e9ad02c3946..d0865169509 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -4,3264 +4,3287 @@ // Persist toolbar by HRS 6/11/15. // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // +"use strict"; /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EditTools, EditVoxels, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, - progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, - keyUpEventFromUIWindow:true */ + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */ (function() { // BEGIN LOCAL_SCOPE + //var CreateApp = function() { // BEGIN LOCAL_SCOPE + var createApp = {}; + + var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + + var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + + Script.include([ + "../libraries/stringHelpers.js", + "../libraries/dataViewHelpers.js", + "../libraries/progressDialog.js", + "../libraries/ToolTip.js", + "../libraries/entityCameraTool.js", + "../libraries/utils.js", + "../libraries/entityIconOverlayManager.js", + "../libraries/gridTool.js", + "entityList/entityList.js", + "entitySelectionTool/entitySelectionTool.js", + "audioFeedback/audioFeedback.js", + "modules/brokenURLReport.js", + "editModes/editModes.js", + "editModes/editVoxels.js" + ]); + + var CreateWindow = Script.require('./modules/createWindow.js'); + + var TITLE_OFFSET = 60; + var CREATE_TOOLS_WIDTH = 750; + var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; + + var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); + var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); + + var createToolsWindow = new CreateWindow( + Script.resolvePath("qml/EditTools.qml"), + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; + } + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false + ); + + /** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ + var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); + }; -"use strict"; -var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; - -var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; - -Script.include([ - "../libraries/stringHelpers.js", - "../libraries/dataViewHelpers.js", - "../libraries/progressDialog.js", - "../libraries/ToolTip.js", - "../libraries/entityCameraTool.js", - "../libraries/utils.js", - "../libraries/entityIconOverlayManager.js", - "../libraries/gridTool.js", - "entityList/entityList.js", - "entitySelectionTool/entitySelectionTool.js", - "audioFeedback/audioFeedback.js", - "modules/brokenURLReport.js", - "editModes/editModes.js", - "editModes/editVoxels.js" -]); - -var CreateWindow = Script.require('./modules/createWindow.js'); - -var TITLE_OFFSET = 60; -var CREATE_TOOLS_WIDTH = 750; -var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; -var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; - -var DEFAULT_IMAGE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_image.jpg"); -var DEFAULT_PARTICLE = Script.getExternalPath(Script.ExternalPaths.Assets, "Bazaar/Assets/Textures/Defaults/Interface/default_particle.png"); - -var createToolsWindow = new CreateWindow( - Script.resolvePath("qml/EditTools.qml"), - 'Create Tools', - 'com.highfidelity.create.createToolsWindow', - function () { - var windowHeight = Window.innerHeight - TITLE_OFFSET; - if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { - windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; - } - return { - size: { - x: CREATE_TOOLS_WIDTH, - y: windowHeight - }, - position: { - x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, - y: Window.y + TITLE_OFFSET - } - } - }, - false -); - -/** - * @description Returns true in case we should use the tablet version of the CreateApp - * @returns boolean - */ -var shouldUseEditTabletApp = function() { - return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); -}; - - -var selectionDisplay = SelectionDisplay; -var selectionManager = SelectionManager; - -var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); -var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); -var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); -var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); -var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); - -var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { - var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); - if (properties.type === "Light") { - return { - imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL - }; - } else if (properties.type === "Zone") { - return { imageURL: ZONE_URL }; - } else if (properties.type === "Material") { - if (properties.parentID !== Uuid.NULL && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) { - return { imageURL: MATERIAL_URL }; + var selectionDisplay = SelectionDisplay; + selectionDisplay.createApp = createApp; + var selectionManager = SelectionManager; + selectionManager.createApp = createApp; + + var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); + var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); + var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); + var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); + var MATERIAL_URL = Script.resolvePath("assets/images/icon-material.svg"); + + var entityIconOverlayManager = new EntityIconOverlayManager(["Light", "ParticleEffect", "Zone", "Material"], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ["type", "isSpotlight", "parentID", "name"]); + if (properties.type === "Light") { + return { + imageURL: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL + }; + } else if (properties.type === "Zone") { + return { imageURL: ZONE_URL }; + } else if (properties.type === "Material") { + if (properties.parentID !== Uuid.NULL && properties.name !== "MATERIAL_" + entityShapeVisualizerSessionName) { + return { imageURL: MATERIAL_URL }; + } else { + return { imageURL: "" }; + } } else { - return { imageURL: "" }; + return { imageURL: PARTICLE_SYSTEM_URL }; } - } else { - return { imageURL: PARTICLE_SYSTEM_URL }; - } -}); - -var hmdMultiSelectMode = false; -var expectingRotateAsClickedSurface = false; -var keepSelectedOnNextClick = false; - -var copiedPosition; -var copiedRotation; - -var cameraManager = new CameraManager(); - -var grid = new Grid(); -var gridTool = new GridTool({ - horizontalGrid: grid, - createToolsWindow: createToolsWindow, - shouldUseEditTabletApp: shouldUseEditTabletApp -}); -gridTool.setVisible(false); - -var editTools = new EditTools({ - createToolsWindow: createToolsWindow, -}); - -var editVoxels = new EditVoxels(); -editVoxels.editTools = editTools; - -editTools.addListener(editVoxels.updateEditSettings); -editTools.addListener(selectionManager.updateEditSettings); - -var entityShapeVisualizerSessionName = "SHAPE_VISUALIZER_" + Uuid.generate(); - -var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); -var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"], entityShapeVisualizerSessionName); - -var entityListTool = new EntityListTool(shouldUseEditTabletApp); - -selectionManager.addEventListener(function () { - selectionDisplay.updateHandles(); - entityIconOverlayManager.updatePositions(); - entityShapeVisualizer.setEntities(selectionManager.selections); -}); - -var DEGREES_TO_RADIANS = Math.PI / 180.0; -var RADIANS_TO_DEGREES = 180.0 / Math.PI; - -var MIN_ANGULAR_SIZE = 2; -var MAX_ANGULAR_SIZE = 45; -var allowLargeModels = true; -var allowSmallModels = true; - -var DEFAULT_DIMENSION = 0.20; - -var DEFAULT_DIMENSIONS = { - x: DEFAULT_DIMENSION, - y: DEFAULT_DIMENSION, - z: DEFAULT_DIMENSION -}; - -var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); - -var MENU_IMPORT_FROM_FILE = "Import Entities (.json) From a File"; -var MENU_IMPORT_FROM_URL = "Import Entities (.json) From a URL"; -var MENU_CREATE_SEPARATOR = "Create Application"; -var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences"; -var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; -var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; -var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode"; -var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; -var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; -var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; -var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; -var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius"; - -var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; -var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; -var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; -var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; -var SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup"; -var SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius"; - -var SETTING_EDIT_PREFIX = "Edit/"; - - -var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; -var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; - -// marketplace info, etc. not quite ready yet. -var SHOULD_SHOW_PROPERTY_MENU = false; -var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; -var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; - -var isActive = false; -var createButton = null; - -var IMPORTING_SVO_OVERLAY_WIDTH = 144; -var IMPORTING_SVO_OVERLAY_HEIGHT = 30; -var IMPORTING_SVO_OVERLAY_MARGIN = 5; -var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; -var importingSVOImageOverlay = Overlays.addOverlay("image", { - imageURL: Script.resolvePath("assets/images/hourglass.svg"), - width: 20, - height: 20, - alpha: 1.0, - x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, - y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, - visible: false -}); -var importingSVOTextOverlay = Overlays.addOverlay("text", { - font: { - size: 14 - }, - text: "Importing SVO...", - leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, - x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, - y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, - width: IMPORTING_SVO_OVERLAY_WIDTH, - height: IMPORTING_SVO_OVERLAY_HEIGHT, - backgroundColor: { - red: 80, - green: 80, - blue: 80 - }, - backgroundAlpha: 0.7, - visible: false -}); - -var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; -var marketplaceWindow = new OverlayWebWindow({ - title: 'Marketplace', - source: "about:blank", - width: 900, - height: 700, - visible: false -}); - -function showMarketplace(marketplaceID) { - var url = MARKETPLACE_URL; - if (marketplaceID) { - url = url + "/items/" + marketplaceID; - } - marketplaceWindow.setURL(url); - marketplaceWindow.setVisible(true); - marketplaceWindow.raise(); - - UserActivityLogger.logAction("opened_marketplace"); -} - -function hideMarketplace() { - marketplaceWindow.setVisible(false); - marketplaceWindow.setURL("about:blank"); -} - -// function toggleMarketplace() { -// if (marketplaceWindow.visible) { -// hideMarketplace(); -// } else { -// showMarketplace(); -// } -// } - -function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original - // position in the given direction. - var CORNERS = [ - { x: 0, y: 0, z: 0 }, - { x: 0, y: 0, z: 1 }, - { x: 0, y: 1, z: 0 }, - { x: 0, y: 1, z: 1 }, - { x: 1, y: 0, z: 0 }, - { x: 1, y: 0, z: 1 }, - { x: 1, y: 1, z: 0 }, - { x: 1, y: 1, z: 1 }, - ]; - - // Go through all corners and find least (most negative) distance in front of position. - var distance = 0; - for (var i = 0, length = CORNERS.length; i < length; i++) { - var cornerVector = - Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); - var cornerDistance = Vec3.dot(cornerVector, direction); - distance = Math.min(cornerDistance, distance); - } - position = Vec3.sum(Vec3.multiply(distance, direction), position); - return position; -} - -// Handles any edit mode updates required when domains have switched -function checkEditPermissionsAndUpdate() { - if ((createButton === null) || (createButton === undefined)) { - //--EARLY EXIT--( nothing to safely update ) - return; - } - - var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); - createButton.editProperties({ - icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), - captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), }); - if (!hasRezPermissions && isActive) { - that.setActive(false); - tablet.gotoHomeScreen(); - } -} - -// Copies the properties in `b` into `a`. `a` will be modified. -function copyProperties(a, b) { - for (var key in b) { - a[key] = b[key]; - } - return a; -} - -const DEFAULT_DYNAMIC_PROPERTIES = { - dynamic: true, - damping: 0.39347, - angularDamping: 0.39347, - gravity: { x: 0, y: -9.8, z: 0 }, -}; - -const DEFAULT_NON_DYNAMIC_PROPERTIES = { - dynamic: false, - damping: 0, - angularDamping: 0, - gravity: { x: 0, y: 0, z: 0 }, -}; - -const DEFAULT_ENTITY_PROPERTIES = { - All: { - description: "", - rotation: { x: 0, y: 0, z: 0, w: 1 }, - collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", - collisionSoundURL: "", - cloneable: false, - ignoreIK: true, - canCastShadow: true, - href: "", - script: "", - serverScripts:"", - velocity: { - x: 0, - y: 0, - z: 0 + createApp.hmdMultiSelectMode = false; + createApp.expectingRotateAsClickedSurface = false; + var keepSelectedOnNextClick = false; + + var copiedPosition; + var copiedRotation; + + var cameraManager = new CameraManager(); + + var grid = new Grid(); + selectionDisplay.grid = grid; + var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp + }); + gridTool.selectionDisplay = selectionDisplay; + gridTool.createApp = createApp; + gridTool.setVisible(false); + + var editTools = new EditTools({ + createToolsWindow: createToolsWindow, + }); + + var editVoxels = new EditVoxels(); + editVoxels.editTools = editTools; + + editTools.addListener(editVoxels.updateEditSettings); + editTools.addListener(selectionManager.updateEditSettings); + + var entityShapeVisualizerSessionName = "SHAPE_VISUALIZER_" + Uuid.generate(); + + var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); + var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"], entityShapeVisualizerSessionName); + + var entityListTool = new EntityListTool(shouldUseEditTabletApp, selectionManager); + entityListTool.createApp = createApp; + entityListTool.cameraManager = cameraManager; + entityListTool.selectionDisplay = selectionDisplay; + + selectionManager.addEventListener(function () { + selectionDisplay.updateHandles(); + entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); + }); + + var DEGREES_TO_RADIANS = Math.PI / 180.0; + var RADIANS_TO_DEGREES = 180.0 / Math.PI; + + var MIN_ANGULAR_SIZE = 2; + var MAX_ANGULAR_SIZE = 45; + var allowLargeModels = true; + var allowSmallModels = true; + + var DEFAULT_DIMENSION = 0.20; + + var DEFAULT_DIMENSIONS = { + x: DEFAULT_DIMENSION, + y: DEFAULT_DIMENSION, + z: DEFAULT_DIMENSION + }; + + var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + + var MENU_IMPORT_FROM_FILE = "Import Entities (.json) From a File"; + var MENU_IMPORT_FROM_URL = "Import Entities (.json) From a URL"; + var MENU_CREATE_SEPARATOR = "Create Application"; + var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Preferences"; + var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; + var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; + createApp.MENU_EASE_ON_FOCUS = MENU_EASE_ON_FOCUS; + var MENU_SHOW_ICONS_IN_CREATE_MODE = "Show Icons in Create Mode"; + var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; + var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; + var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; + var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius"; + + var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; + var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; + var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; + createApp.SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + createApp.SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup"; + createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius"; + + var SETTING_EDIT_PREFIX = "Edit/"; + + + var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; + var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; + + // marketplace info, etc. not quite ready yet. + var SHOULD_SHOW_PROPERTY_MENU = false; + var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; + var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; + + var isActive = false; + var createButton = null; + + var IMPORTING_SVO_OVERLAY_WIDTH = 144; + var IMPORTING_SVO_OVERLAY_HEIGHT = 30; + var IMPORTING_SVO_OVERLAY_MARGIN = 5; + var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; + var importingSVOImageOverlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("assets/images/hourglass.svg"), + width: 20, + height: 20, + alpha: 1.0, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, + visible: false + }); + var importingSVOTextOverlay = Overlays.addOverlay("text", { + font: { + size: 14 }, - angularVelocity: { - x: 0, - y: 0, - z: 0 + text: "Importing SVO...", + leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { + red: 80, + green: 80, + blue: 80 }, - restitution: 0.5, - friction: 0.5, - density: 1000, + backgroundAlpha: 0.7, + visible: false + }); + + var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; + var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false + }); + + function showMarketplace(marketplaceID) { + var url = MARKETPLACE_URL; + if (marketplaceID) { + url = url + "/items/" + marketplaceID; + } + marketplaceWindow.setURL(url); + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); + } + + function hideMarketplace() { + marketplaceWindow.setVisible(false); + marketplaceWindow.setURL("about:blank"); + } + + // function toggleMarketplace() { + // if (marketplaceWindow.visible) { + // hideMarketplace(); + // } else { + // showMarketplace(); + // } + // } + + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; + } + + // Handles any edit mode updates required when domains have switched + function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } + } + + // Copies the properties in `b` into `a`. `a` will be modified. + function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; + } + + const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, + }; + + const DEFAULT_NON_DYNAMIC_PROPERTIES = { dynamic: false, - }, - Shape: { - shape: "Box", - dimensions: { x: 0.2, y: 0.2, z: 0.2 }, - color: { red: 0, green: 180, blue: 239 }, - }, - Text: { - text: "Text", - dimensions: { - x: 0.65, - y: 0.3, - z: 0.01 + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, + }; + + const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + restitution: 0.5, + friction: 0.5, + density: 1000, + dynamic: false, }, - textColor: { red: 255, green: 255, blue: 255 }, - backgroundColor: { red: 0, green: 0, blue: 0 }, - lineHeight: 0.06, - faceCamera: false, - }, - Zone: { - dimensions: { - x: 10, - y: 10, - z: 10 + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, }, - flyingAllowed: true, - ghostingAllowed: true, - filter: "", - keyLightMode: "inherit", - keyLightColor: { red: 255, green: 255, blue: 255 }, - keyLight: { - intensity: 1.0, - direction: { - x: 0.0, - y: -0.707106769084930, // 45 degrees - z: 0.7071067690849304 + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 }, - castShadows: true + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, }, - ambientLightMode: "inherit", - ambientLight: { - ambientIntensity: 0.5, - ambientURL: "" - }, - hazeMode: "inherit", - haze: { - hazeRange: 1000, - hazeAltitudeEffect: false, - hazeBaseRef: 0, - hazeColor: { - red: 128, - green: 154, - blue: 179 + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 }, - hazeBackgroundBlend: 0, - hazeEnableGlare: false, - hazeGlareColor: { - red: 255, - green: 229, - blue: 179 + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + shapeType: "box", + bloomMode: "inherit", + avatarPriority: "inherit", + screenshare: "inherit", }, - shapeType: "box", - bloomMode: "inherit", - avatarPriority: "inherit", - screenshare: "inherit", - }, - Model: { - collisionShape: "none", - compoundShapeURL: "", - animation: { - url: "", - running: false, - allowTranslation: false, - loop: true, - hold: false, - currentFrame: 0, - firstFrame: 0, - lastFrame: 100000, - fps: 30.0, - } - }, - Image: { - dimensions: { - x: 0.5385, - y: 0.2819, - z: 0.0092 - }, - shapeType: "box", - collisionless: true, - keepAspectRatio: false, - imageURL: DEFAULT_IMAGE - }, - Web: { - dimensions: { - x: 1.6, - y: 0.9, - z: 0.01 + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } }, - sourceUrl: "https://overte.org/", - dpi: 30, - }, - ParticleEffect: { - lifespan: 1.5, - maxParticles: 10, - textures: DEFAULT_PARTICLE, - emitRate: 5.5, - emitSpeed: 0, - speedSpread: 0, - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, - emitterShouldTrail: true, - particleRadius: 0.25, - radiusStart: 0, - radiusSpread: 0, - particleColor: { - red: 255, - green: 255, - blue: 255 + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE }, - colorSpread: { - red: 0, - green: 0, - blue: 0 + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://overte.org/", + dpi: 30, }, - alpha: 0, - alphaStart: 1, - alphaSpread: 0, - emitAcceleration: { - x: 0, - y: 2.5, - z: 0 + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: DEFAULT_PARTICLE, + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: Math.PI, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI }, - accelerationSpread: { - x: 0, - y: 0, - z: 0 + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, }, - particleSpin: 0, - spinSpread: 0, - rotateWithEntity: false, - polarStart: 0, - polarFinish: Math.PI, - azimuthStart: -Math.PI, - azimuthFinish: Math.PI - }, - Light: { - color: { red: 255, green: 255, blue: 255 }, - intensity: 5.0, - dimensions: DEFAULT_LIGHT_DIMENSIONS, - falloffRadius: 1.0, - isSpotlight: false, - exponent: 1.0, - cutoff: 75.0, - }, -}; - -var toolBar = (function () { - var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts - var that = {}, - toolBar, - activeButton = null, - systemToolbar = null, - dialogWindow = null, - tablet = null; - - function createNewEntity(requestedProperties) { - var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; - var position = getPositionToCreateEntity(); - var entityID = null; - - var properties = {}; - - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); - - var type = requestedProperties.type; - if (type === "Box" || type === "Sphere") { - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); - } else { - copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); - } + }; - // We apply the requested properties first so that they take priority over any default properties. - copyProperties(properties, requestedProperties); + var toolBar = (function () { + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts + var that = {}, + toolBar, + activeButton = null, + systemToolbar = null, + dialogWindow = null, + tablet = null; - if (properties.dynamic) { - copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); - } else { - copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); - } + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = createApp.getPositionToCreateEntity(); + var entityID = null; + var properties = {}; - if (position !== null && position !== undefined) { - var direction; - if (Camera.mode === "entity" || Camera.mode === "independent") { - direction = Camera.orientation; + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = requestedProperties.type; + if (type === "Box" || type === "Sphere") { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); } else { - direction = MyAvatar.orientation; + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); } - direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; - if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // We apply the requested properties first so that they take priority over any default properties. + copyProperties(properties, requestedProperties); - // Adjust position of entity per bounding box prior to creating it. - var registration = properties.registration; - if (registration === undefined) { - var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; - registration = DEFAULT_REGISTRATION; - } + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } - var orientation = properties.orientation; - if (orientation === undefined) { - properties.orientation = MyAvatar.orientation; - var DEFAULT_ORIENTATION = properties.orientation; - orientation = DEFAULT_ORIENTATION; + + if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; } else { - // If the orientation is already defined, we perform the corresponding rotation assuming that - // our start referential is the avatar referential. - properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); - var DEFAULT_ORIENTATION = properties.orientation; - orientation = DEFAULT_ORIENTATION; + direction = MyAvatar.orientation; } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); - } + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { - position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); - properties.position = position; + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } - if (!properties.grab) { - properties.grab = {}; - if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && - !(properties.type === "Zone" || properties.type === "Light" - || properties.type === "ParticleEffect" || properties.type === "Web")) { - properties.grab.grabbable = true; - } else { - properties.grab.grabbable = false; + var orientation = properties.orientation; + if (orientation === undefined) { + properties.orientation = MyAvatar.orientation; + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } else { + // If the orientation is already defined, we perform the corresponding rotation assuming that + // our start referential is the avatar referential. + properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); } - } - if (type === "Model") { - properties.visible = false; - } + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); + properties.position = position; + + if (!properties.grab) { + properties.grab = {}; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" + || properties.type === "ParticleEffect" || properties.type === "Web")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } + } + + if (type === "Model") { + properties.visible = false; + } - entityID = Entities.addEntity(properties); + entityID = Entities.addEntity(properties); + + var dimensionsCheckCallback = function(){ + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + // Make sure the model entity is loaded before we try to figure out + // its dimensions. We need to give ample time to load the entity. + var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds. + var LOADED_CHECK_INTERVAL = 100; + var isLoadedCheckCount = 0; + var entityIsLoadedCheck = function() { + isLoadedCheckCount++; + if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) { + var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions; + + if (isLoadedCheckCount === MAX_LOADED_CHECKS) { + console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions)) + } - var dimensionsCheckCallback = function(){ - // Adjust position of entity per bounding box after it has been created and auto-resized. - var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; - var DIMENSIONS_CHECK_INTERVAL = 200; - var MAX_DIMENSIONS_CHECKS = 10; - var dimensionsCheckCount = 0; - var dimensionsCheckFunction = function () { - dimensionsCheckCount++; - var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); - if (!Vec3.equal(properties.dimensions, initialDimensions)) { - position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, - properties.dimensions, properties.rotation); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), - properties.dimensions); Entities.editEntity(entityID, { - position: position - }); - selectionManager._update(false, this); - } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); - } - }; - Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); - } - // Make sure the model entity is loaded before we try to figure out - // its dimensions. We need to give ample time to load the entity. - var MAX_LOADED_CHECKS = 100; // 100 * 100ms = 10 seconds. - var LOADED_CHECK_INTERVAL = 100; - var isLoadedCheckCount = 0; - var entityIsLoadedCheck = function() { - isLoadedCheckCount++; - if (isLoadedCheckCount === MAX_LOADED_CHECKS || Entities.isLoaded(entityID)) { - var naturalDimensions = Entities.getEntityProperties(entityID, "naturalDimensions").naturalDimensions; - - if (isLoadedCheckCount === MAX_LOADED_CHECKS) { - console.log("Model entity failed to load in time: " + (MAX_LOADED_CHECKS * LOADED_CHECK_INTERVAL) + " ... setting dimensions to: " + JSON.stringify(naturalDimensions)) + visible: true, + dimensions: naturalDimensions + }) + dimensionsCheckCallback(); + // We want to update the selection manager again since the script has moved on without us. + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); + return; } - - Entities.editEntity(entityID, { - visible: true, - dimensions: naturalDimensions - }) - dimensionsCheckCallback(); - // We want to update the selection manager again since the script has moved on without us. - selectionManager.clearSelections(this); - entityListTool.sendUpdate(); - selectionManager.setSelections([entityID], this); - return; + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); } - Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); - } - - var POST_ADJUST_ENTITY_TYPES = ["Model"]; - if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { - Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); - } - SelectionManager.addEntity(entityID, false, this); - SelectionManager.saveProperties(); - pushCommandForSelections([{ - entityID: entityID, - properties: properties - }], [], true); + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + Script.setTimeout(entityIsLoadedCheck, LOADED_CHECK_INTERVAL); + } - } else { - Window.notifyEditError("Can't create " + properties.type + ": " + - properties.type + " would be out of bounds."); - } + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + createApp.pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); - selectionManager.clearSelections(this); - entityListTool.sendUpdate(); - selectionManager.setSelections([entityID], this); + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); + } - Window.setFocus(); + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); - return entityID; - } + Window.setFocus(); - function closeExistingDialogWindow() { - if (dialogWindow) { - dialogWindow.close(); - dialogWindow = null; + return entityID; } - } - function cleanup() { - that.setActive(false); - if (tablet) { - tablet.removeButton(activeButton); - } - if (systemToolbar) { - systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; + } } - } - - var buttonHandlers = {}; // only used to tablet mode - - function addButton(name, handler) { - buttonHandlers[name] = handler; - } - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; - var DYNAMIC_DEFAULT = false; + function cleanup() { + that.setActive(false); + if (tablet) { + tablet.removeButton(activeButton); + } + if (systemToolbar) { + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + } - var MATERIAL_MODE_UV = 0; - var MATERIAL_MODE_PROJECTED = 1; + var buttonHandlers = {}; // only used to tablet mode + + function addButton(name, handler) { + buttonHandlers[name] = handler; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var DYNAMIC_DEFAULT = false; + + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + + function handleNewModelDialogResult(result) { + if (result) { + var url = result.url; + var shapeType; + switch (result.collisionShapeIndex) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; + } - function handleNewModelDialogResult(result) { - if (result) { - var url = result.url; - var shapeType; - switch (result.collisionShapeIndex) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + createNewEntity({ + type: "Model", + modelURL: url, + shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, + dynamic: dynamic, + useOriginalPivot: result.useOriginalPivot + }); + } } + } - var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - createNewEntity({ - type: "Model", - modelURL: url, - shapeType: shapeType, + function handleNewPolyVoxDialogResult(result) { + if (result) { + var initialShape = result.initialShapeIndex; + var volumeSizeX = parseInt(result.volumeSizeX); + var volumeSizeY = parseInt(result.volumeSizeY); + var volumeSizeZ = parseInt(result.volumeSizeZ); + var voxelSurfaceStyle = parseInt(result.surfaceStyleIndex); + var voxelPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: volumeSizeZ * -1.6 })); + + var polyVoxID = createNewEntity({ + type: "PolyVox", + name: "terrain", + dimensions: { + x: volumeSizeX, + y: volumeSizeY, + z: volumeSizeZ + }, + voxelVolumeSize: { + x: volumeSizeX, + y: volumeSizeY, + z: volumeSizeZ + }, + xTextureURL: result.xTextureURL, + yTextureURL: result.yTextureURL, + zTextureURL: result.zTextureURL, + voxelSurfaceStyle: voxelSurfaceStyle, + collisionless: !(result.collisions), grab: { grabbable: result.grabbable }, - dynamic: dynamic, - useOriginalPivot: result.useOriginalPivot }); - } - } - } - - function handleNewPolyVoxDialogResult(result) { - if (result) { - var initialShape = result.initialShapeIndex; - var volumeSizeX = parseInt(result.volumeSizeX); - var volumeSizeY = parseInt(result.volumeSizeY); - var volumeSizeZ = parseInt(result.volumeSizeZ); - var voxelSurfaceStyle = parseInt(result.surfaceStyleIndex); - var voxelPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: volumeSizeZ * -1.6 })); - - var polyVoxID = createNewEntity({ - type: "PolyVox", - name: "terrain", - dimensions: { - x: volumeSizeX, - y: volumeSizeY, - z: volumeSizeZ - }, - voxelVolumeSize: { - x: volumeSizeX, - y: volumeSizeY, - z: volumeSizeZ - }, - xTextureURL: result.xTextureURL, - yTextureURL: result.yTextureURL, - zTextureURL: result.zTextureURL, - voxelSurfaceStyle: voxelSurfaceStyle, - collisionless: !(result.collisions), - grab: { - grabbable: result.grabbable - }, - }); - - Entities.editEntity(polyVoxID, { - position: voxelPosition - }); - - if (polyVoxID){ - switch (initialShape) { - case 0: - Entities.setVoxelsInCuboid(polyVoxID, { - x: Math.round(volumeSizeX / 4), - y: Math.round(volumeSizeY / 4), - z: Math.round(volumeSizeZ / 4) - }, { - x: Math.round(volumeSizeX / 2.0), - y: Math.round(volumeSizeY / 2.0), - z: Math.round(volumeSizeZ / 2.0) - }, 255); - break; - // Plane 1/4 - case 1: - Entities.setVoxelsInCuboid(polyVoxID, { - x: 0, - y: 0, - z: 0 - }, { - x: volumeSizeX, - y: Math.round(volumeSizeY / 4), - z: volumeSizeZ - }, 255); - break; - // Plane 3/4 - case 2: - Entities.setVoxelsInCuboid(polyVoxID, { - x: 0, - y: 0, - z: 0 - }, { - x: volumeSizeX, - y: Math.round(3 * volumeSizeY / 4), - z: volumeSizeZ - }, 255); - break; - // Single voxel at center - case 3: - Entities.setVoxel(polyVoxID, { - x: Math.round(volumeSizeX / 2), - y: Math.round(volumeSizeY / 2), - z: Math.round(volumeSizeZ / 2) - }, 255); - break; - } - } - } - } - function handleNewMaterialDialogResult(result) { - if (result) { - var materialURL = result.textInput; - if (materialURL === "") { - materialURL = "materialData"; - } - //var materialMappingMode; - //switch (result.comboBox) { - // case MATERIAL_MODE_PROJECTED: - // materialMappingMode = "projected"; - // break; - // default: - // shapeType = "uv"; - //} - var materialData = ""; - if (materialURL.startsWith("materialData")) { - materialData = JSON.stringify({ - "materials": {} + Entities.editEntity(polyVoxID, { + position: voxelPosition }); - } - var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; - if (materialURL) { - createNewEntity({ - type: "Material", - materialURL: materialURL, - //materialMappingMode: materialMappingMode, - priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, - materialData: materialData - }); + if (polyVoxID){ + switch (initialShape) { + case 0: + Entities.setVoxelsInCuboid(polyVoxID, { + x: Math.round(volumeSizeX / 4), + y: Math.round(volumeSizeY / 4), + z: Math.round(volumeSizeZ / 4) + }, { + x: Math.round(volumeSizeX / 2.0), + y: Math.round(volumeSizeY / 2.0), + z: Math.round(volumeSizeZ / 2.0) + }, 255); + break; + // Plane 1/4 + case 1: + Entities.setVoxelsInCuboid(polyVoxID, { + x: 0, + y: 0, + z: 0 + }, { + x: volumeSizeX, + y: Math.round(volumeSizeY / 4), + z: volumeSizeZ + }, 255); + break; + // Plane 3/4 + case 2: + Entities.setVoxelsInCuboid(polyVoxID, { + x: 0, + y: 0, + z: 0 + }, { + x: volumeSizeX, + y: Math.round(3 * volumeSizeY / 4), + z: volumeSizeZ + }, 255); + break; + // Single voxel at center + case 3: + Entities.setVoxel(polyVoxID, { + x: Math.round(volumeSizeX / 2), + y: Math.round(volumeSizeY / 2), + z: Math.round(volumeSizeZ / 2) + }, 255); + break; + } + } } } - } - - function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.popFromStack(); - switch (message.method) { - case "newModelDialogAdd": - handleNewModelDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newModelDialogCancel": - closeExistingDialogWindow(); - break; - case "newEntityButtonClicked": - buttonHandlers[message.params.buttonName](); - break; - case "newMaterialDialogAdd": - handleNewMaterialDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newMaterialDialogCancel": - closeExistingDialogWindow(); - break; - case "newPolyVoxDialogAdd": - handleNewPolyVoxDialogResult(message.params); - closeExistingDialogWindow(); - break; - case "newPolyVoxDialogCancel": - closeExistingDialogWindow(); - break; - } - } - var entitiesToDelete = []; - var deletedEntityTimer = null; - var DELETE_ENTITY_TIMER_TIMEOUT = 100; + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + if (materialURL === "") { + materialURL = "materialData"; + } + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }); + } - function checkDeletedEntityAndUpdate(entityID) { - // Allow for multiple entity deletes before updating the entities selected. - entitiesToDelete.push(entityID); - if (deletedEntityTimer !== null) { - Script.clearTimeout(deletedEntityTimer); - } - deletedEntityTimer = Script.setTimeout(function () { - if (entitiesToDelete.length > 0) { - selectionManager.removeEntities(entitiesToDelete, this); + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData + }); + } } - entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); - entitiesToDelete = []; - deletedEntityTimer = null; - }, DELETE_ENTITY_TIMER_TIMEOUT); - } + } - function initialize() { - Script.scriptEnding.connect(cleanup); - Window.domainChanged.connect(function () { - if (isActive) { - tablet.gotoHomeScreen(); + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.popFromStack(); + switch (message.method) { + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; + case "newPolyVoxDialogAdd": + handleNewPolyVoxDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newPolyVoxDialogCancel": + closeExistingDialogWindow(); + break; } - that.setActive(false); - that.clearEntityList(); - checkEditPermissionsAndUpdate(); - }); + } - HMD.displayModeChanged.connect(function() { - if (isActive) { - tablet.gotoHomeScreen(); - } - that.setActive(false); - }); + var entitiesToDelete = []; + var deletedEntityTimer = null; + var DELETE_ENTITY_TIMER_TIMEOUT = 100; - Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { - if (isActive && !canAdjustLocks) { - that.setActive(false); + function checkDeletedEntityAndUpdate(entityID) { + // Allow for multiple entity deletes before updating the entities selected. + entitiesToDelete.push(entityID); + if (deletedEntityTimer !== null) { + Script.clearTimeout(deletedEntityTimer); } - checkEditPermissionsAndUpdate(); - }); - - Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); - Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); - var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); - - Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + deletedEntityTimer = Script.setTimeout(function () { + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete, this); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); + entitiesToDelete = []; + deletedEntityTimer = null; + }, DELETE_ENTITY_TIMER_TIMEOUT); + } - var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - activeButton = tablet.addButton({ - captionColor: hasRezPermissions ? "#ffffff" : "#888888", - icon: createButtonIconRsrc, - activeIcon: "icons/tablet-icons/edit-a.svg", - text: "CREATE", - sortOrder: 10 - }); - createButton = activeButton; - tablet.screenChanged.connect(function (type, url) { - var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && - (url === 'hifi/tablet/TabletHome.qml' || url === '')); - if (isActive && (type !== "QML" || url !== Script.resolvePath("qml/Edit.qml")) && !isGoingToHomescreenOnDesktop) { + function initialize() { + Script.scriptEnding.connect(cleanup); + Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } that.setActive(false); - } - }); - tablet.fromQml.connect(fromQml); - createToolsWindow.fromQml.addListener(fromQml); - - createButton.clicked.connect(function() { - if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { - Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); - return; - } - - that.toggle(); - }); + that.clearEntityList(); + checkEditPermissionsAndUpdate(); + }); - addButton("importEntitiesButton", function() { - importEntitiesFromFile(); - }); + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); - addButton("importEntitiesFromUrlButton", function() { - importEntitiesFromUrl(); - }); + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); + } + checkEditPermissionsAndUpdate(); + }); - addButton("openAssetBrowserButton", function() { - Window.showAssetServer(); - }); - function createNewEntityDialogButtonCallback(entityType) { - return function() { - if (shouldUseEditTabletApp()) { - // tablet version of new-model dialog - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml")); - } else { - closeExistingDialogWindow(); - var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml"); - var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; - if( entityType === "PolyVox" ){ - DIALOG_WINDOW_SIZE.x = 600; - DIALOG_WINDOW_SIZE.y = 500; - } - dialogWindow = Desktop.createWindow(qmlPath, { - title: "New " + entityType + " Entity", - additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, - presentationMode: Desktop.PresentationMode.NATIVE, - size: DIALOG_WINDOW_SIZE, - visible: true - }); - dialogWindow.fromQml.connect(fromQml); + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + + Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + + var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + activeButton = tablet.addButton({ + captionColor: hasRezPermissions ? "#ffffff" : "#888888", + icon: createButtonIconRsrc, + activeIcon: "icons/tablet-icons/edit-a.svg", + text: "CREATE", + sortOrder: 10 + }); + createButton = activeButton; + tablet.screenChanged.connect(function (type, url) { + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== Script.resolvePath("qml/Edit.qml")) && !isGoingToHomescreenOnDesktop) { + that.setActive(false); } - }; - } + }); + tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); - addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } - addButton("newShapeButton", function () { - createNewEntity({ - type: "Shape", - shape: "Cube", + that.toggle(); }); - }); - addButton("newLightButton", function () { - createNewEntity({ - type: "Light", + addButton("importEntitiesButton", function() { + createApp.importEntitiesFromFile(); }); - }); - addButton("newTextButton", function () { - createNewEntity({ - type: "Text", + addButton("importEntitiesFromUrlButton", function() { + createApp.importEntitiesFromUrl(); }); - }); - addButton("newImageButton", function () { - createNewEntity({ - type: "Image", + addButton("openAssetBrowserButton", function() { + Window.showAssetServer(); }); - }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml")); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml"); + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + if( entityType === "PolyVox" ){ + DIALOG_WINDOW_SIZE.x = 600; + DIALOG_WINDOW_SIZE.y = 500; + } + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; + } - addButton("newWebButton", function () { - createNewEntity({ - type: "Web", + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + + addButton("newShapeButton", function () { + createNewEntity({ + type: "Shape", + shape: "Cube", + }); }); - }); - addButton("newZoneButton", function () { - createNewEntity({ - type: "Zone", + addButton("newLightButton", function () { + createNewEntity({ + type: "Light", + }); }); - }); - addButton("newParticleButton", function () { - createNewEntity({ - type: "ParticleEffect", + addButton("newTextButton", function () { + createNewEntity({ + type: "Text", + }); }); - }); - addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + addButton("newImageButton", function () { + createNewEntity({ + type: "Image", + }); + }); - addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); - - var deactivateCreateIfDesktopWindowsHidden = function() { - if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { - that.setActive(false); - } - }; - entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); - createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + addButton("newWebButton", function () { + createNewEntity({ + type: "Web", + }); + }); - that.setActive(false); - } + addButton("newZoneButton", function () { + createNewEntity({ + type: "Zone", + }); + }); - that.clearEntityList = function () { - entityListTool.clearEntityList(); - }; + addButton("newParticleButton", function () { + createNewEntity({ + type: "ParticleEffect", + }); + }); - that.toggle = function () { - that.setActive(!isActive); - if (!isActive) { - tablet.gotoHomeScreen(); - } - }; + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); - that.setActive = function (active) { - ContextOverlay.enabled = !active; - Settings.setValue(EDIT_SETTING, active); - if (active) { - Controller.captureEntityClickEvents(); - } else { - Controller.releaseEntityClickEvents(); + addButton("newPolyVoxButton", createNewEntityDialogButtonCallback("PolyVox")); - closeExistingDialogWindow(); - } - if (active === isActive) { - return; - } - if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { - Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); - return; + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + + that.setActive(false); } - Messages.sendLocalMessage("edit-events", JSON.stringify({ - enabled: active - })); - isActive = active; - activeButton.editProperties({isActive: isActive}); - undoHistory.setEnabled(isActive); - - editVoxels.setActive(active); - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + that.clearEntityList = function () { + entityListTool.clearEntityList(); + }; - if (!isActive) { - entityListTool.setVisible(false); - gridTool.setVisible(false); - grid.setEnabled(false); - propertiesTool.setVisible(false); - selectionManager.clearSelections(this); - cameraManager.disable(); - selectionDisplay.disableTriggerMapping(); - tablet.landscape = false; - Controller.disableMapping(CONTROLLER_MAPPING_NAME); - } else { - if (shouldUseEditTabletApp()) { - tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); - } else { - // make other apps inactive while in desktop mode + that.toggle = function () { + that.setActive(!isActive); + if (!isActive) { tablet.gotoHomeScreen(); } - UserActivityLogger.enabledEdit(); - entityListTool.setVisible(true); - entityListTool.sendUpdate(); - gridTool.setVisible(true); - grid.setEnabled(true); - propertiesTool.setVisible(true); - selectionDisplay.enableTriggerMapping(); - print("starting tablet in landscape mode"); - tablet.landscape = true; - Controller.enableMapping(CONTROLLER_MAPPING_NAME); - // Not sure what the following was meant to accomplish, but it currently causes - // everybody else to think that Interface has lost focus overall. fogbugzid:558 - // Window.setFocus(); - } - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - }; - - initialize(); - return that; -})(); + }; -var selectedEntityID; -var orientation; -var intersection; + that.setActive = function (active) { + ContextOverlay.enabled = !active; + Settings.setValue(EDIT_SETTING, active); + if (active) { + Controller.captureEntityClickEvents(); + } else { + Controller.releaseEntityClickEvents(); + closeExistingDialogWindow(); + } + if (active === isActive) { + return; + } + if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + print("Setting isActive: " + active); + isActive = active; + activeButton.editProperties({isActive: isActive}); + createApp.undoHistory.setEnabled(isActive); + + editVoxels.setActive(active); + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(this); + cameraManager.disable(); + selectionDisplay.disableTriggerMapping(); + tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + } else { + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } + UserActivityLogger.enabledEdit(); + entityListTool.setVisible(true); + entityListTool.sendUpdate(); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.enableTriggerMapping(); + print("starting tablet in landscape mode"); + tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + }; -function rayPlaneIntersection(pickRay, point, normal) { // - // - // This version of the test returns the intersection of a line with a plane - // - var collides = Vec3.dot(pickRay.direction, normal); + initialize(); + return that; + })(); - var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + var selectedEntityID; + var orientation; + var intersection; - return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); -} -function rayPlaneIntersection2(pickRay, point, normal) { - // - // This version of the test returns false if the ray is directed away from the plane - // - var collides = Vec3.dot(pickRay.direction, normal); - var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; - if (t < 0.0) { - return false; - } else { - return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); - } -} + createApp.rayPlaneIntersection = function(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); -function findClickedEntity(event) { - var pickZones = event.isControl; + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; - if (pickZones) { - Entities.setZonesArePickable(true); + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); } - var pickRay = Camera.computePickRay(event.x, event.y); - var tabletIDs = getMainTabletIDs(); - if (tabletIDs.length > 0) { - var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); - if (overlayResult.intersects) { - return null; + createApp.rayPlaneIntersection2 = function(pickRay, point, normal) { + // + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); } } - var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking - var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); - iconResult.accurate = true; + function findClickedEntity(event) { + var pickZones = event.isControl; - if (pickZones) { - Entities.setZonesArePickable(false); - } + if (pickZones) { + Entities.setZonesArePickable(true); + } - var result; - if (expectingRotateAsClickedSurface) { - if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected, or the selection is locked."); - expectingRotateAsClickedSurface = false; - } else { - //Rotate Selection according the Surface Normal - var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1)); - selectionDisplay.rotateSelection(normalRotation); - //Translate Selection according the clicked Surface - var distanceFromSurface; - if (selectionDisplay.getSpaceMode() === "world"){ - distanceFromSurface = SelectionManager.worldDimensions.z / 2; - } else { - distanceFromSurface = SelectionManager.localDimensions.z / 2; + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; } - selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); - selectionManager._update(false, this); - pushCommandForSelections(); - expectingRotateAsClickedSurface = false; - audioFeedback.action(); } - keepSelectedOnNextClick = true; - return null; - } else { - if (iconResult.intersects) { - result = iconResult; - } else if (entityResult.intersects) { - result = entityResult; - } else { - return null; + + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); + iconResult.accurate = true; + + if (pickZones) { + Entities.setZonesArePickable(false); } - if (!result.accurate) { + var result; + if (createApp.expectingRotateAsClickedSurface) { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + createApp.expectingRotateAsClickedSurface = false; + } else { + //Rotate Selection according the Surface Normal + var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(entityResult.surfaceNormal, -1)); + selectionDisplay.rotateSelection(normalRotation); + //Translate Selection according the clicked Surface + var distanceFromSurface; + if (selectionDisplay.getSpaceMode() === "world"){ + distanceFromSurface = SelectionManager.worldDimensions.z / 2; + } else { + distanceFromSurface = SelectionManager.localDimensions.z / 2; + } + selectionDisplay.moveSelection(Vec3.sum(entityResult.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); + selectionManager._update(false, this); + createApp.pushCommandForSelections(); + createApp.expectingRotateAsClickedSurface = false; + audioFeedback.action(); + } + keepSelectedOnNextClick = true; return null; - } + } else { + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } - var foundEntity = result.entityID; - return { - pickRay: pickRay, - entityID: foundEntity, - intersection: result.intersection - }; + if (!result.accurate) { + return null; + } + + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; + } } -} -// Handles selections on overlays while in edit mode by querying entities from -// entityIconOverlayManager. -function handleOverlaySelectionToolUpdates(channel, message, sender) { - var wantDebug = false; - if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') - return; + // Handles selections on overlays while in edit mode by querying entities from + // entityIconOverlayManager. + function handleOverlaySelectionToolUpdates(channel, message, sender) { + var wantDebug = false; + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; - var data = JSON.parse(message); + var data = JSON.parse(message); - if (data.method === "selectOverlay") { - if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { - if (wantDebug) { - print("setting selection to overlay " + data.overlayID); - } - var entity = entityIconOverlayManager.findEntity(data.overlayID); + if (data.method === "selectOverlay") { + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); - if (entity !== null) { - if (hmdMultiSelectMode) { - selectionManager.addEntity(entity, true, this); - } else { - selectionManager.setSelections([entity], this); + if (entity !== null) { + if (createApp.hmdMultiSelectMode) { + selectionManager.addEntity(entity, true, this); + } else { + selectionManager.setSelections([entity], this); + } } } } } -} -function handleMessagesReceived(channel, message, sender) { - switch( channel ){ - case 'entityToolUpdates': { - handleOverlaySelectionToolUpdates( channel, message, sender ); - break; - } - default: { - return; + function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + default: { + return; + } } } -} -Messages.subscribe("entityToolUpdates"); -Messages.messageReceived.connect(handleMessagesReceived); + Messages.subscribe("entityToolUpdates"); + Messages.messageReceived.connect(handleMessagesReceived); -var mouseHasMovedSincePress = false; -var mousePressStartTime = 0; -var mousePressStartPosition = { - x: 0, - y: 0 -}; -var mouseDown = false; - -function mousePressEvent(event) { - mouseDown = true; - mousePressStartPosition = { - x: event.x, - y: event.y + var mouseHasMovedSincePress = false; + var mousePressStartTime = 0; + var mousePressStartPosition = { + x: 0, + y: 0 }; - mousePressStartTime = Date.now(); - mouseHasMovedSincePress = false; - mouseCapturedByTool = false; + var mouseDown = false; - if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { - mouseCapturedByTool = true; - return; - } - if (isActive) { - if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { - // Event handled; do nothing. + function mousePressEvent(event) { + mouseDown = true; + mousePressStartPosition = { + x: event.x, + y: event.y + }; + mousePressStartTime = Date.now(); + mouseHasMovedSincePress = false; + mouseCapturedByTool = false; + + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + mouseCapturedByTool = true; return; } + if (isActive) { + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. + return; + } + } } -} -var mouseCapturedByTool = false; -var lastMousePosition = null; -var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms -var CLICK_MOVE_DISTANCE_THRESHOLD = 20; -var IDLE_MOUSE_TIMEOUT = 200; + var mouseCapturedByTool = false; + var lastMousePosition = null; + var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms + var CLICK_MOVE_DISTANCE_THRESHOLD = 20; + var IDLE_MOUSE_TIMEOUT = 200; -var lastMouseMoveEvent = null; + var lastMouseMoveEvent = null; + + function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; + } -function mouseMoveEventBuffered(event) { - lastMouseMoveEvent = event; -} + function mouseMove(event) { + if (mouseDown && !mouseHasMovedSincePress) { + var timeSincePressMicro = Date.now() - mousePressStartTime; -function mouseMove(event) { - if (mouseDown && !mouseHasMovedSincePress) { - var timeSincePressMicro = Date.now() - mousePressStartTime; + var dX = mousePressStartPosition.x - event.x; + var dY = mousePressStartPosition.y - event.y; + var sqDist = (dX * dX) + (dY * dY); - var dX = mousePressStartPosition.x - event.x; - var dY = mousePressStartPosition.y - event.y; - var sqDist = (dX * dX) + (dY * dY); + // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved + // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move + // yet. The goal is to provide mouse clicks that are more lenient to small movements. + if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + return; + } + mouseHasMovedSincePress = true; + } - // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved - // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move - // yet. The goal is to provide mouse clicks that are more lenient to small movements. - if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + if (!isActive) { return; } - mouseHasMovedSincePress = true; - } - if (!isActive) { - return; - } + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; + } - // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing - if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { - return; + lastMousePosition = { + x: event.x, + y: event.y + }; } - lastMousePosition = { - x: event.x, - y: event.y - }; -} + function mouseReleaseEvent(event) { + print("mouseReleaseEvent"); + mouseDown = false; -function mouseReleaseEvent(event) { - mouseDown = false; + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + if (propertyMenu.mouseReleaseEvent(event)) { + print("propertyMenu.mouseReleaseEvent(event)"); + return true; + } + if (isActive && selectionManager.hasSelection()) { + tooltip.show(false); + } + if (mouseCapturedByTool) { + print("mouseCapturedByTool"); + return; + } - if (lastMouseMoveEvent) { - mouseMove(lastMouseMoveEvent); - lastMouseMoveEvent = null; - } - if (propertyMenu.mouseReleaseEvent(event)) { - return true; - } - if (isActive && selectionManager.hasSelection()) { - tooltip.show(false); + cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + print("!mouseHasMovedSincePress: " + JSON.stringify(event)); + mouseClickEvent(event); + } else { + print("mouseHasMovedSincePress"); + } } - if (mouseCapturedByTool) { - return; + function wasTabletOrEditHandleClicked(event) { + var rayPick = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; + } + } + return false; } - cameraManager.mouseReleaseEvent(event); + function mouseClickEvent(event) { + print("mouseClickEvent isActive: " + isActive); + var wantDebug = false; + var result, properties, tabletClicked; + if (isActive && event.isLeftButton) { + print("mouseClickEvent isActive && event.isLeftButton"); + result = findClickedEntity(event); + var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { + return; + } - if (!mouseHasMovedSincePress) { - mouseClickEvent(event); - } -} + if (result === null || result === undefined) { + if (!event.isShifted) { + if (!keepSelectedOnNextClick) { + selectionManager.clearSelections(this); + } + keepSelectedOnNextClick = false; + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + if (HMD.tabletID && foundEntity === HMD.tabletID) { + return; + } + properties = Entities.getEntityProperties(foundEntity); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; -function wasTabletOrEditHandleClicked(event) { - var rayPick = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(rayPick, true); - if (result.intersects) { - var overlayID = result.overlayID; - var tabletIDs = getMainTabletIDs(); - if (tabletIDs.indexOf(overlayID) >= 0) { - return true; - } else if (selectionDisplay.isEditHandle(overlayID)) { - return true; - } - } - return false; -} + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK && selectionManager.editEnabled) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = createApp.rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity], this); + } else { + selectionManager.addEntity(foundEntity, true, this); + } + selectionManager.saveProperties(); -function mouseClickEvent(event) { - var wantDebug = false; - var result, properties, tabletClicked; - if (isActive && event.isLeftButton) { - result = findClickedEntity(event); - var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); - if (tabletOrEditHandleClicked) { - return; - } + if (wantDebug) { + print("Model selected: " + foundEntity); + } + selectionDisplay.select(selectedEntityID, event); - if (result === null || result === undefined) { - if (!event.isShifted) { - if (!keepSelectedOnNextClick) { - selectionManager.clearSelections(this); + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } - keepSelectedOnNextClick = false; } - return; - } - toolBar.setActive(true); - var pickRay = result.pickRay; - var foundEntity = result.entityID; - if (HMD.tabletID && foundEntity === HMD.tabletID) { - return; + } else if (event.isRightButton) { + result = findClickedEntity(event); + if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } + properties = Entities.getEntityProperties(result.entityID); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); + } else { + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); + } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); + } } - properties = Entities.getEntityProperties(foundEntity); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + } - if (wantDebug) { - print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); - } - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X === A + ((P-A).B)B - // d = |P-X| + Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - var x = Vec3.dot(Vec3.subtract(P, A), B); + // In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already + // exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that + // added it. + var originalLightsArePickable = Entities.getLightsArePickable(); - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * - 180 / Math.PI; + function setupModelMenus() { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Undo", + shortcutKey: 'Ctrl+Z', + position: 0, + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Redo", + shortcutKey: 'Ctrl+Y', + position: 1, + }); - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && - (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_CREATE_SEPARATOR, + isSeparator: true + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_IMPORT_FROM_FILE, + afterItem: MENU_CREATE_SEPARATOR + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_IMPORT_FROM_URL, + afterItem: MENU_IMPORT_FROM_FILE + }); - if (0 < x && sizeOK && selectionManager.editEnabled) { - selectedEntityID = foundEntity; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); - if (!event.isShifted) { - selectionManager.setSelections([foundEntity], this); - } else { - selectionManager.addEntity(foundEntity, true, this); - } - selectionManager.saveProperties(); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + position: 0, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + afterItem: MENU_ALLOW_SELECTION_LIGHTS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, + menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS, + afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE + }); - if (wantDebug) { - print("Model selected: " + foundEntity); - } - selectionDisplay.select(selectedEntityID, event); + Entities.setLightsArePickable(false); + } - if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - } - } - } else if (event.isRightButton) { - result = findClickedEntity(event); - if (result) { - if (SHOULD_SHOW_PROPERTY_MENU !== true) { - return; - } - properties = Entities.getEntityProperties(result.entityID); - if (properties.marketplaceID) { - propertyMenu.marketplaceID = properties.marketplaceID; - propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); - } else { - propertyMenu.marketplaceID = null; - propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); - } - propertyMenu.setPosition(event.x, event.y); - propertyMenu.show(); - } else { - propertyMenu.hide(); - } + setupModelMenus(); // do this when first running our script. + + function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE); + Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS); + Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); + Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_URL); + Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_FILE); + Menu.removeSeparator("Edit", MENU_CREATE_SEPARATOR); } -} -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + Script.scriptEnding.connect(function () { + toolBar.setActive(false); + Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); -// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already -// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that -// added it. -var originalLightsArePickable = Entities.getLightsArePickable(); -function setupModelMenus() { - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "Undo", - shortcutKey: 'Ctrl+Z', - position: 0, - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "Redo", - shortcutKey: 'Ctrl+Y', - position: 1, - }); + progressDialog.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); + Entities.setLightsArePickable(originalLightsArePickable); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_CREATE_SEPARATOR, - isSeparator: true - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_IMPORT_FROM_FILE, - afterItem: MENU_CREATE_SEPARATOR - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: MENU_IMPORT_FROM_URL, - afterItem: MENU_IMPORT_FROM_FILE - }); + Overlays.deleteOverlay(importingSVOImageOverlay); + Overlays.deleteOverlay(importingSVOTextOverlay); - Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, - position: 0, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_LARGE, - afterItem: MENU_CREATE_ENTITIES_GRABBABLE, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_SMALL, - afterItem: MENU_ALLOW_SELECTION_LARGE, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ALLOW_SELECTION_LIGHTS, - afterItem: MENU_ALLOW_SELECTION_SMALL, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_AUTO_FOCUS_ON_SELECT, - afterItem: MENU_ALLOW_SELECTION_LIGHTS, - isCheckable: true, - isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_EASE_ON_FOCUS, - afterItem: MENU_AUTO_FOCUS_ON_SELECT, - isCheckable: true, - isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_SHOW_ICONS_IN_CREATE_MODE, - afterItem: MENU_EASE_ON_FOCUS, - isCheckable: true, - isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" - }); - Menu.addMenuItem({ - menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES, - menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS, - afterItem: MENU_SHOW_ICONS_IN_CREATE_MODE + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleMessagesReceived); + Messages.unsubscribe("entityToolUpdates"); + createButton = null; }); - Entities.setLightsArePickable(false); -} - -setupModelMenus(); // do this when first running our script. - -function cleanupModelMenus() { - Menu.removeMenuItem("Edit", "Undo"); - Menu.removeMenuItem("Edit", "Redo"); - - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ICONS_IN_CREATE_MODE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE); - Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS); - Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES); - Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_URL); - Menu.removeMenuItem("Edit", MENU_IMPORT_FROM_FILE); - Menu.removeSeparator("Edit", MENU_CREATE_SEPARATOR); -} - -Script.scriptEnding.connect(function () { - toolBar.setActive(false); - Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); - Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); - - - progressDialog.cleanup(); - cleanupModelMenus(); - tooltip.cleanup(); - selectionDisplay.cleanup(); - entityShapeVisualizer.cleanup(); - Entities.setLightsArePickable(originalLightsArePickable); - - Overlays.deleteOverlay(importingSVOImageOverlay); - Overlays.deleteOverlay(importingSVOTextOverlay); - - Controller.keyReleaseEvent.disconnect(keyReleaseEvent); - Controller.keyPressEvent.disconnect(keyPressEvent); - - Controller.mousePressEvent.disconnect(mousePressEvent); - Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); - Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); - - Messages.messageReceived.disconnect(handleMessagesReceived); - Messages.unsubscribe("entityToolUpdates"); - createButton = null; -}); - -var lastOrientation = null; -var lastPosition = null; - -// Do some stuff regularly, like check for placement of various overlays -Script.update.connect(function (deltaTime) { - progressDialog.move(); - selectionDisplay.checkControllerMove(); - var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); - var dPosition = Vec3.distance(Camera.position, lastPosition); - if (dOrientation > 0.001 || dPosition > 0.001) { - propertyMenu.hide(); - lastOrientation = Camera.orientation; - lastPosition = Camera.position; - } - if (lastMouseMoveEvent) { - mouseMove(lastMouseMoveEvent); - lastMouseMoveEvent = null; - } -}); - -function insideBox(center, dimensions, point) { - return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && - (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && - (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); -} - -function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { - if (selectionManager.hasSelection()) { - // Get all entities touching the bounding box of the current selection - var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, - Vec3.multiply(selectionManager.worldDimensions, 0.5)); - var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); - - if (!keepIfTouching) { - var isValid; - if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { - isValid = function (position) { - return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); - }; - } else { - isValid = function (position) { - var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), - Vec3.subtract(position, - selectionManager.localPosition)); - return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); - }; - } - for (var i = 0; i < entities.length; ++i) { - var properties = Entities.getEntityProperties(entities[i]); - if (!isValid(properties.position)) { - entities.splice(i, 1); - --i; - } - } + var lastOrientation = Camera.orientation; + var lastPosition = Camera.position; + + // Do some stuff regularly, like check for placement of various overlays + Script.update.connect(function (deltaTime) { + progressDialog.move(); + selectionDisplay.checkControllerMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; } - selectionManager.setSelections(entities, this); + }); + + function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && + (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && + (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); } -} -function sortSelectedEntities(selected) { - var sortedEntities = selected.slice(); - var begin = 0; - while (begin < sortedEntities.length) { - var elementRemoved = false; - var next = begin + 1; - while (next < sortedEntities.length) { - var beginID = sortedEntities[begin]; - var nextID = sortedEntities[next]; + createApp.selectAllEntitiesInCurrentSelectionBox = function(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); - if (Entities.isChildOfParent(beginID, nextID)) { - sortedEntities[begin] = nextID; - sortedEntities[next] = beginID; - sortedEntities.splice(next, 1); - elementRemoved = true; - break; - } else if (Entities.isChildOfParent(nextID, beginID)) { - sortedEntities.splice(next, 1); - elementRemoved = true; - break; + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { + isValid = function (position) { + return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); + }; + } else { + isValid = function (position) { + var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), + Vec3.subtract(position, + selectionManager.localPosition)); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); + }; + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities, this); + } + } + + function sortSelectedEntities(selected) { + var sortedEntities = selected.slice(); + var begin = 0; + while (begin < sortedEntities.length) { + var elementRemoved = false; + var next = begin + 1; + while (next < sortedEntities.length) { + var beginID = sortedEntities[begin]; + var nextID = sortedEntities[next]; + + if (Entities.isChildOfParent(beginID, nextID)) { + sortedEntities[begin] = nextID; + sortedEntities[next] = beginID; + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } else if (Entities.isChildOfParent(nextID, beginID)) { + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } + next++; + } + if (!elementRemoved) { + begin++; } - next++; - } - if (!elementRemoved) { - begin++; } + return sortedEntities; } - return sortedEntities; -} -function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { - var wantDebug = false; - var entitiesLength = entities.length; - var initialPropertySets = Entities.getMultipleEntityProperties(entities); - var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); - for (var i = 0; i < entitiesLength; ++i) { - var entityID = entities[i]; + function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { + var wantDebug = false; + var entitiesLength = entities.length; + var initialPropertySets = Entities.getMultipleEntityProperties(entities); + var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); + for (var i = 0; i < entitiesLength; ++i) { + var entityID = entities[i]; - if (entityHostTypes[i].entityHostType !== entityHostType) { - if (wantDebug) { - console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + - entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; } - continue; - } - var children = Entities.getChildrenIDs(entityID); - var grandchildrenList = []; - recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); - childrenList.push({ - entityID: entityID, - properties: initialPropertySets[i], - children: grandchildrenList - }); - deletedIDs.push(entityID); - Entities.deleteEntity(entityID); + var children = Entities.getChildrenIDs(entityID); + var grandchildrenList = []; + recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); + childrenList.push({ + entityID: entityID, + properties: initialPropertySets[i], + children: grandchildrenList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } } -} -function unparentSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var selectedEntities = selectionManager.selections; - var parentCheck = false; + createApp.unparentSelectedEntities = function() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; - if (selectedEntities.length < 1) { - audioFeedback.rejection(); - Window.notifyEditError("You must have an entity selected in order to unparent it."); - return; - } - selectedEntities.forEach(function (id, index) { - var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; - if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { - parentCheck = true; + if (selectedEntities.length < 1) { + audioFeedback.rejection(); + Window.notifyEditError("You must have an entity selected in order to unparent it."); + return; } - Entities.editEntity(id, {parentID: null}); - return true; - }); - if (parentCheck) { - audioFeedback.confirmation(); - if (selectedEntities.length > 1) { - Window.notify("Entities unparented"); + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + audioFeedback.confirmation(); + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + //Refresh + entityListTool.sendUpdate(); + selectionManager._update(false, this); } else { - Window.notify("Entity unparented"); + audioFeedback.rejection(); + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } } - //Refresh - entityListTool.sendUpdate(); - selectionManager._update(false, this); } else { audioFeedback.rejection(); - if (selectedEntities.length > 1) { - Window.notify("Selected Entities have no parents"); + Window.notifyEditError("You have nothing selected or the selection has locked entities."); + } + } + createApp.parentSelectedEntities = function() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + audioFeedback.rejection(); + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}); + } + }); + + if (parentCheck) { + audioFeedback.confirmation(); + Window.notify("Entities parented"); + //Refresh + entityListTool.sendUpdate(); + selectionManager._update(false, this); } else { - Window.notify("Selected Entity does not have a parent"); + audioFeedback.rejection(); + Window.notify("Entities are already parented to last"); } - } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); - } -} -function parentSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var selectedEntities = selectionManager.selections; - if (selectedEntities.length <= 1) { + } else { audioFeedback.rejection(); - Window.notifyEditError("You must have multiple entities selected in order to parent them"); - return; + Window.notifyEditError("You have nothing selected or the selection has locked entities."); } - var parentCheck = false; - var lastEntityId = selectedEntities[selectedEntities.length - 1]; - selectedEntities.forEach(function (id, index) { - if (lastEntityId !== id) { - var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; - if (parentId !== lastEntityId) { - parentCheck = true; + } + createApp.deleteSelectedEntities = function() { + if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { + var deletedIDs = []; + + SelectionManager.saveProperties(); + var savedProperties = []; + var newSortedSelection = sortSelectedEntities(selectionManager.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); + for (var i = 0; i < newSortedSelection.length; ++i) { + var entityID = newSortedSelection[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + if (initialProperties.locked || + (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + continue; } - Entities.editEntity(id, {parentID: lastEntityId}); + var children = Entities.getChildrenIDs(entityID); + var childList = []; + recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); + savedProperties.push({ + entityID: entityID, + properties: initialProperties, + children: childList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); } - }); - if (parentCheck) { - audioFeedback.confirmation(); - Window.notify("Entities parented"); - //Refresh - entityListTool.sendUpdate(); - selectionManager._update(false, this); + if (savedProperties.length > 0) { + SelectionManager.clearSelections(this); + createApp.pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } } else { audioFeedback.rejection(); - Window.notify("Entities are already parented to last"); + Window.notifyEditError("You have nothing selected or the selection has locked entities."); } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); - } -} -function deleteSelectedEntities() { - if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) { - var deletedIDs = []; - - SelectionManager.saveProperties(); - var savedProperties = []; - var newSortedSelection = sortSelectedEntities(selectionManager.selections); - var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); - for (var i = 0; i < newSortedSelection.length; ++i) { - var entityID = newSortedSelection[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - if (initialProperties.locked || - (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { - continue; + } + + createApp.toggleSelectedEntitiesLocked = function() { + if (SelectionManager.hasSelection()) { + var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + locked: locked + }); } - var children = Entities.getChildrenIDs(entityID); - var childList = []; - recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); - savedProperties.push({ - entityID: entityID, - properties: initialProperties, - children: childList - }); - deletedIDs.push(entityID); - Entities.deleteEntity(entityID); + entityListTool.sendUpdate(); + selectionManager._update(false, this); } + } - if (savedProperties.length > 0) { - SelectionManager.clearSelections(this); - pushCommandForSelections([], savedProperties); - entityListTool.deleteEntities(deletedIDs); + createApp.toggleSelectedEntitiesVisible = function() { + if (SelectionManager.hasSelection()) { + var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + visible: visible + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); } - } else { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected or the selection has locked entities."); } -} -function toggleSelectedEntitiesLocked() { - if (SelectionManager.hasSelection()) { - var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - Entities.editEntity(entityID, { - locked: locked - }); + function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } } - entityListTool.sendUpdate(); - selectionManager._update(false, this); } -} -function toggleSelectedEntitiesVisible() { - if (SelectionManager.hasSelection()) { - var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - Entities.editEntity(entityID, { - visible: visible - }); + function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { + // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + // Ignore. + } + + var importURL = null; + if (filename !== "") { + importURL = filename; + if (!/^(http|https):\/\//.test(filename)) { + importURL = "file:///" + importURL; + } + } + if (importURL) { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(importURL); } - entityListTool.sendUpdate(); - selectionManager._update(false, this); - } -} - -function onFileSaveChanged(filename) { - Window.saveFileChanged.disconnect(onFileSaveChanged); - if (filename !== "") { - var success = Clipboard.exportEntities(filename, selectionManager.selections); - if (!success) { - Window.notifyEditError("Export failed."); - } - } -} - -function onFileOpenChanged(filename) { - // disconnect the event, otherwise the requests will stack up - try { - // Not all calls to onFileOpenChanged() connect an event. - Window.browseChanged.disconnect(onFileOpenChanged); - } catch (e) { - // Ignore. - } - - var importURL = null; - if (filename !== "") { - importURL = filename; - if (!/^(http|https):\/\//.test(filename)) { - importURL = "file:///" + importURL; - } - } - if (importURL) { - if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { - toolBar.toggle(); - } - importSVO(importURL); - } -} - -function onPromptTextChanged(prompt) { - Window.promptTextChanged.disconnect(onPromptTextChanged); - if (prompt !== "") { - if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { - toolBar.toggle(); - } - importSVO(prompt); - } -} - -function onPromptTextChangedDefaultRadiusUserPref(prompt) { - Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref); - if (prompt !== "") { - var radius = parseInt(prompt); - if (radius < 0 || isNaN(radius)){ - radius = 100; - } - Settings.setValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius); - } -} - -function handleMenuEvent(menuItem) { - if (menuItem === MENU_ALLOW_SELECTION_SMALL) { - allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL); - } else if (menuItem === MENU_ALLOW_SELECTION_LARGE) { - allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE); - } else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) { - Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); - } else if (menuItem === "Delete") { - deleteSelectedEntities(); - } else if (menuItem === "Undo") { - undoHistory.undo(); - } else if (menuItem === "Redo") { - undoHistory.redo(); - } else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) { - entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); - } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { - Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); - } else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) { - Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref); - Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100)); - } else if (menuItem === MENU_IMPORT_FROM_FILE) { - importEntitiesFromFile(); - } else if (menuItem === MENU_IMPORT_FROM_URL) { - importEntitiesFromUrl(); - } - tooltip.show(false); -} - -var HALF_TREE_SCALE = 16384; - -function getPositionToCreateEntity(extra) { - var CREATE_DISTANCE = 2; - var position; - var delta = extra !== undefined ? extra : 0; - if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); - } else { - position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); } - if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { - return null; + function onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } } - return position; -} -function importSVO(importURL) { - if (!Entities.canRez() && !Entities.canRezTmp() && - !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { - Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); - return; + function onPromptTextChangedDefaultRadiusUserPref(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref); + if (prompt !== "") { + var radius = parseInt(prompt); + if (radius < 0 || isNaN(radius)){ + radius = 100; + } + Settings.setValue(createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius); + } + } + + function handleMenuEvent(menuItem) { + if (menuItem === MENU_ALLOW_SELECTION_SMALL) { + allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL); + } else if (menuItem === MENU_ALLOW_SELECTION_LARGE) { + allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE); + } else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) { + Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + } else if (menuItem === "Delete") { + createApp.deleteSelectedEntities(); + } else if (menuItem === "Undo") { + createApp.undoHistory.undo(); + } else if (menuItem === "Redo") { + createApp.undoHistory.redo(); + } else if (menuItem === MENU_SHOW_ICONS_IN_CREATE_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_ICONS_IN_CREATE_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) { + Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref); + Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100)); + } else if (menuItem === MENU_IMPORT_FROM_FILE) { + createApp.importEntitiesFromFile(); + } else if (menuItem === MENU_IMPORT_FROM_URL) { + createApp.importEntitiesFromUrl(); + } + tooltip.show(false); } - Overlays.editOverlay(importingSVOTextOverlay, { - visible: true - }); - Overlays.editOverlay(importingSVOImageOverlay, { - visible: true - }); + var HALF_TREE_SCALE = 16384; + + createApp.getPositionToCreateEntity = function(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + } - var success = Clipboard.importEntities(importURL); + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; + } - if (success) { - var VERY_LARGE = 10000; - var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; - var position = Vec3.ZERO; - if (!isLargeImport) { - position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + function importSVO(importURL) { + if (!Entities.canRez() && !Entities.canRezTmp() && + !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); + return; } - if (position !== null && position !== undefined) { - var pastedEntityIDs = Clipboard.pasteEntities(position); + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: true + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: true + }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; if (!isLargeImport) { - // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move - // entities after they're imported so that they're all the correct distance in front of and with geometric mean - // centered on the avatar/camera direction. - var deltaPosition = Vec3.ZERO; - var entityPositions = []; - var entityParentIDs = []; - - var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; - var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; - if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { - var targetDirection; - if (Camera.mode === "entity" || Camera.mode === "independent") { - targetDirection = Camera.orientation; - } else { - targetDirection = MyAvatar.orientation; - } - targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); - - var targetPosition = getPositionToCreateEntity(); - var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. - var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. - for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", - "registrationPoint", "rotation", "parentID"]); - var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, - curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); - var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); - var distance = Vec3.dot(delta, targetDirection); - deltaParallel = Math.min(distance, deltaParallel); - deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), - deltaPerpendicular); - entityPositions[i] = curLoopEntityProps.position; - entityParentIDs[i] = curLoopEntityProps.parentID; + position = createApp.getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = createApp.getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); } - deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); - deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); - } - if (grid.getSnapToGrid()) { - var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", - "registrationPoint"]); - var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); - position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, - firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); - deltaPosition = Vec3.subtract(position, firstEntityProps.position); - } + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } - if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { - if (Uuid.isNull(entityParentIDs[editEntityIndex])) { - Entities.editEntity(pastedEntityIDs[editEntityIndex], { - position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) - }); + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } } } } - } - if (isActive) { - selectionManager.setSelections(pastedEntityIDs, this); + if (isActive) { + selectionManager.setSelections(pastedEntityIDs, this); + } + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); } } else { - Window.notifyEditError("Can't import entities: entities would be out of bounds."); + Window.notifyEditError("There was an error importing the entity file."); } - } else { - Window.notifyEditError("There was an error importing the entity file."); + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: false + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: false + }); } + Window.svoImportRequested.connect(importSVO); - Overlays.editOverlay(importingSVOTextOverlay, { - visible: false - }); - Overlays.editOverlay(importingSVOImageOverlay, { - visible: false - }); -} -Window.svoImportRequested.connect(importSVO); - -Menu.menuItemEvent.connect(handleMenuEvent); - -var keyPressEvent = function (event) { - if (isActive) { - cameraManager.keyPressEvent(event); - } -}; -var keyReleaseEvent = function (event) { - if (isActive) { - cameraManager.keyReleaseEvent(event); - } -}; -Controller.keyReleaseEvent.connect(keyReleaseEvent); -Controller.keyPressEvent.connect(keyPressEvent); - -function deleteKey(value) { - if (value === 0) { // on release - deleteSelectedEntities(); - } -} -function deselectKey(value) { - if (value === 0) { // on release - selectionManager.clearSelections(this); - } -} -function toggleKey(value) { - if (value === 0) { // on release - selectionDisplay.toggleSpaceMode(); - } -} -function focusKey(value) { - if (value === 0) { // on release - setCameraFocusToSelection(); - } -} -function gridKey(value) { - if (value === 0) { // on release - alignGridToSelection(); - } -} -function viewGridKey(value) { - if (value === 0) { // on release - toggleGridVisibility(); - } -} -function snapKey(value) { - if (value === 0) { // on release - entityListTool.toggleSnapToGrid(); - } -} -function gridToAvatarKey(value) { - if (value === 0) { // on release - alignGridToAvatar(); - } -} -function rotateAsNextClickedSurfaceKey(value) { - if (value === 0) { // on release - rotateAsNextClickedSurface(); - } -} -function quickRotate90xKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("X"); - } -} -function quickRotate90yKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("Y"); - } -} -function quickRotate90zKey(value) { - if (value === 0) { // on release - selectionDisplay.rotate90degreeSelection("Z"); - } -} -function recursiveAdd(newParentID, parentData) { - if (parentData.children !== undefined) { - var children = parentData.children; - for (var i = 0; i < children.length; i++) { - var childProperties = children[i].properties; - childProperties.parentID = newParentID; - var newChildID = Entities.addEntity(childProperties); - recursiveAdd(newChildID, children[i]); - } - } -} - -var UndoHistory = function(onUpdate) { - this.history = []; - // The current position is the index of the last executed action in the history array. - // - // -1 0 1 2 3 <- position - // A B C D <- actions in history - // - // If our lastExecutedIndex is 1, the last executed action is B. - // If we undo, we undo B (index 1). If we redo, we redo C (index 2). - this.lastExecutedIndex = -1; - this.enabled = true; - this.onUpdate = onUpdate; -}; - -UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { - if (!this.enabled) { - return; - } - // Delete any history following the last executed action. - this.history.splice(this.lastExecutedIndex + 1); - this.history.push({ - undoFn: undoFn, - undoArgs: undoArgs, - redoFn: redoFn, - redoArgs: redoArgs - }); - this.lastExecutedIndex++; - - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.setEnabled = function(enabled) { - this.enabled = enabled; - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.canUndo = function() { - return this.enabled && this.lastExecutedIndex >= 0; -}; -UndoHistory.prototype.canRedo = function() { - return this.enabled && this.lastExecutedIndex < this.history.length - 1; -}; -UndoHistory.prototype.undo = function() { - if (!this.canUndo()) { - console.warn("Cannot undo action"); - return; - } - - var command = this.history[this.lastExecutedIndex]; - command.undoFn(command.undoArgs); - this.lastExecutedIndex--; - - if (this.onUpdate) { - this.onUpdate(); - } -}; -UndoHistory.prototype.redo = function() { - if (!this.canRedo()) { - console.warn("Cannot redo action"); - return; - } - - var command = this.history[this.lastExecutedIndex + 1]; - command.redoFn(command.redoArgs); - this.lastExecutedIndex++; - - if (this.onUpdate) { - this.onUpdate(); - } -}; - -function updateUndoRedoMenuItems() { - Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); - Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); -} -var undoHistory = new UndoHistory(updateUndoRedoMenuItems); -updateUndoRedoMenuItems(); - -// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently -// possible to create an entity with a specific id, earlier undo commands to the deleted entity -// will fail if there isn't a way to find the new entity id. -var DELETED_ENTITY_MAP = {}; - -function applyEntityProperties(data) { - var editEntities = data.editEntities; - var createEntities = data.createEntities; - var deleteEntities = data.deleteEntities; - var selectedEntityIDs = []; - var selectEdits = createEntities.length === 0 || !data.selectCreated; - var i, entityID, entityProperties; - for (i = 0; i < createEntities.length; i++) { - entityID = createEntities[i].entityID; - entityProperties = createEntities[i].properties; - var newEntityID = Entities.addEntity(entityProperties); - recursiveAdd(newEntityID, createEntities[i]); - DELETED_ENTITY_MAP[entityID] = newEntityID; - if (data.selectCreated) { - selectedEntityIDs.push(newEntityID); - } - } - for (i = 0; i < deleteEntities.length; i++) { - entityID = deleteEntities[i].entityID; - if (DELETED_ENTITY_MAP[entityID] !== undefined) { - entityID = DELETED_ENTITY_MAP[entityID]; - } - Entities.deleteEntity(entityID); - var index = selectedEntityIDs.indexOf(entityID); - if (index >= 0) { - selectedEntityIDs.splice(index, 1); - } - } - for (i = 0; i < editEntities.length; i++) { - entityID = editEntities[i].entityID; - if (DELETED_ENTITY_MAP[entityID] !== undefined) { - entityID = DELETED_ENTITY_MAP[entityID]; - } - entityProperties = editEntities[i].properties; - if (entityProperties !== null) { - Entities.editEntity(entityID, entityProperties); - } - if (selectEdits) { - selectedEntityIDs.push(entityID); - } - } - - // We might be getting an undo while edit.js is disabled. If that is the case, don't set - // our selections, causing the edit widgets to display. - if (isActive) { - selectionManager.setSelections(selectedEntityIDs, this); - selectionManager.saveProperties(); - } -} - -// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the -// redo command, and the saved properties for the undo command. Also, include create and delete entity data. -function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { - doNotSaveEditProperties = false; - var undoData = { - editEntities: [], - createEntities: deletedEntityData || [], - deleteEntities: createdEntityData || [], - selectCreated: true - }; - var redoData = { - editEntities: [], - createEntities: createdEntityData || [], - deleteEntities: deletedEntityData || [], - selectCreated: true + Menu.menuItemEvent.connect(handleMenuEvent); + + var keyPressEvent = function (event) { + if (isActive) { + cameraManager.keyPressEvent(event); + } }; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var currentProperties = null; - if (!initialProperties) { - continue; + var keyReleaseEvent = function (event) { + if (isActive) { + cameraManager.keyReleaseEvent(event); } + }; + Controller.keyReleaseEvent.connect(keyReleaseEvent); + Controller.keyPressEvent.connect(keyPressEvent); - if (doNotSaveEditProperties) { - initialProperties = null; - } else { - currentProperties = Entities.getEntityProperties(entityID); + function deleteKey(value) { + if (value === 0) { // on release + createApp.deleteSelectedEntities(); + } + } + function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(this); + } + } + function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } + } + function focusKey(value) { + if (value === 0) { // on release + createApp.setCameraFocusToSelection(); + } + } + function gridKey(value) { + if (value === 0) { // on release + createApp.alignGridToSelection(); + } + } + function viewGridKey(value) { + if (value === 0) { // on release + createApp.toggleGridVisibility(); + } + } + function snapKey(value) { + if (value === 0) { // on release + entityListTool.toggleSnapToGrid(); + } + } + function gridToAvatarKey(value) { + if (value === 0) { // on release + createApp.alignGridToAvatar(); + } + } + createApp.rotateAsNextClickedSurfaceKey = function(value) { + if (value === 0) { // on release + createApp.rotateAsNextClickedSurface(); + } + } + function quickRotate90xKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("X"); + } + } + function quickRotate90yKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Y"); + } + } + function quickRotate90zKey(value) { + if (value === 0) { // on release + selectionDisplay.rotate90degreeSelection("Z"); + } + } + function recursiveAdd(newParentID, parentData) { + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } } - - undoData.editEntities.push({ - entityID: entityID, - properties: initialProperties - }); - redoData.editEntities.push({ - entityID: entityID, - properties: currentProperties - }); } - undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); -} -var ServerScriptStatusMonitor = function(entityID, statusCallback) { - var self = this; + var UndoHistory = function(onUpdate) { + this.history = []; + // The current position is the index of the last executed action in the history array. + // + // -1 0 1 2 3 <- position + // A B C D <- actions in history + // + // If our lastExecutedIndex is 1, the last executed action is B. + // If we undo, we undo B (index 1). If we redo, we redo C (index 2). + this.lastExecutedIndex = -1; + this.enabled = true; + this.onUpdate = onUpdate; + }; - self.entityID = entityID; - self.active = true; - self.sendRequestTimerID = null; + UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { + if (!this.enabled) { + return; + } + // Delete any history following the last executed action. + this.history.splice(this.lastExecutedIndex + 1); + this.history.push({ + undoFn: undoFn, + undoArgs: undoArgs, + redoFn: redoFn, + redoArgs: redoArgs + }); + this.lastExecutedIndex++; - var onStatusReceived = function(success, isRunning, status, errorInfo) { - if (self.active) { - statusCallback({ - statusRetrieved: success, - isRunning: isRunning, - status: status, - errorInfo: errorInfo - }); - self.sendRequestTimerID = Script.setTimeout(function() { - if (self.active) { - Entities.getServerScriptStatus(entityID, onStatusReceived); - } - }, 1000); + if (this.onUpdate) { + this.onUpdate(); } }; - self.stop = function() { - self.active = false; + UndoHistory.prototype.setEnabled = function(enabled) { + this.enabled = enabled; + if (this.onUpdate) { + this.onUpdate(); + } }; + UndoHistory.prototype.canUndo = function() { + return this.enabled && this.lastExecutedIndex >= 0; + }; + UndoHistory.prototype.canRedo = function() { + return this.enabled && this.lastExecutedIndex < this.history.length - 1; + }; + UndoHistory.prototype.undo = function() { + if (!this.canUndo()) { + console.warn("Cannot undo action"); + return; + } - Entities.getServerScriptStatus(entityID, onStatusReceived); -}; - -var PropertiesTool = function (opts) { - var that = {}; - - var webView = null; - webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - webView.setVisible = function(value) {}; + var command = this.history[this.lastExecutedIndex]; + command.undoFn(command.undoArgs); + this.lastExecutedIndex--; - var visible = false; + if (this.onUpdate) { + this.onUpdate(); + } + }; + UndoHistory.prototype.redo = function() { + if (!this.canRedo()) { + console.warn("Cannot redo action"); + return; + } - // This keeps track of the last entity ID that was selected. If multiple entities - // are selected or if no entity is selected this will be `null`. - var currentSelectedEntityID = null; - var statusMonitor = null; - var blockPropertyUpdates = false; + var command = this.history[this.lastExecutedIndex + 1]; + command.redoFn(command.redoArgs); + this.lastExecutedIndex++; - that.setVisible = function (newVisible) { - visible = newVisible; - webView.setVisible(shouldUseEditTabletApp() && visible); - createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + if (this.onUpdate) { + this.onUpdate(); + } }; - that.setVisible(false); + function updateUndoRedoMenuItems() { + Menu.setMenuEnabled("Edit > Undo", createApp.undoHistory.canUndo()); + Menu.setMenuEnabled("Edit > Redo", createApp.undoHistory.canRedo()); + } + createApp.undoHistory = new UndoHistory(updateUndoRedoMenuItems); + updateUndoRedoMenuItems(); + + // When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently + // possible to create an entity with a specific id, earlier undo commands to the deleted entity + // will fail if there isn't a way to find the new entity id. + var DELETED_ENTITY_MAP = {}; + + function applyEntityProperties(data) { + var editEntities = data.editEntities; + var createEntities = data.createEntities; + var deleteEntities = data.deleteEntities; + var selectedEntityIDs = []; + var selectEdits = createEntities.length === 0 || !data.selectCreated; + var i, entityID, entityProperties; + for (i = 0; i < createEntities.length; i++) { + entityID = createEntities[i].entityID; + entityProperties = createEntities[i].properties; + var newEntityID = Entities.addEntity(entityProperties); + recursiveAdd(newEntityID, createEntities[i]); + DELETED_ENTITY_MAP[entityID] = newEntityID; + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (i = 0; i < deleteEntities.length; i++) { + entityID = deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + Entities.deleteEntity(entityID); + var index = selectedEntityIDs.indexOf(entityID); + if (index >= 0) { + selectedEntityIDs.splice(index, 1); + } + } + for (i = 0; i < editEntities.length; i++) { + entityID = editEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } + } - function emitScriptEvent(data) { - var dataString = JSON.stringify(data); - webView.emitScriptEvent(dataString); - createToolsWindow.emitScriptEvent(dataString); + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs, this); + selectionManager.saveProperties(); + } } - function updateScriptStatus(info) { - info.type = "server_script_status"; - emitScriptEvent(info); - } + // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the + // redo command, and the saved properties for the undo command. Also, include create and delete entity data. + createApp.pushCommandForSelections = function (createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; + var undoData = { + editEntities: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true + }; + var redoData = { + editEntities: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: true + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + var currentProperties = null; + if (!initialProperties) { + continue; + } - function resetScriptStatus() { - updateScriptStatus({ - statusRetrieved: undefined, - isRunning: undefined, - status: "", - errorInfo: "" - }); + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ + entityID: entityID, + properties: initialProperties + }); + redoData.editEntities.push({ + entityID: entityID, + properties: currentProperties + }); + } + createApp.undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); } - that.setSpaceMode = function(spaceMode) { - emitScriptEvent({ - type: 'setSpaceMode', - spaceMode: spaceMode - }) + var ServerScriptStatusMonitor = function(entityID, statusCallback) { + var self = this; + + self.entityID = entityID; + self.active = true; + self.sendRequestTimerID = null; + + var onStatusReceived = function(success, isRunning, status, errorInfo) { + if (self.active) { + statusCallback({ + statusRetrieved: success, + isRunning: isRunning, + status: status, + errorInfo: errorInfo + }); + self.sendRequestTimerID = Script.setTimeout(function() { + if (self.active) { + Entities.getServerScriptStatus(entityID, onStatusReceived); + } + }, 1000); + } + }; + self.stop = function() { + self.active = false; + }; + + Entities.getServerScriptStatus(entityID, onStatusReceived); }; - function updateSelections(selectionUpdated, caller) { - if (HMD.active && visible) { - webView.setLandscape(true); - } else { - if (!visible) { - hmdMultiSelectMode = false; - webView.setLandscape(false); + var PropertiesTool = function (opts) { + var that = {}; + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value) {}; + + var visible = false; + + // This keeps track of the last entity ID that was selected. If multiple entities + // are selected or if no entity is selected this will be `null`. + var currentSelectedEntityID = null; + var statusMonitor = null; + var blockPropertyUpdates = false; + + that.setVisible = function (newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); + } + + function updateScriptStatus(info) { + info.type = "server_script_status"; + emitScriptEvent(info); + } + + function resetScriptStatus() { + updateScriptStatus({ + statusRetrieved: undefined, + isRunning: undefined, + status: "", + errorInfo: "" + }); + } + + that.setSpaceMode = function(spaceMode) { + emitScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }) + }; + + function updateSelections(selectionUpdated, caller) { + if (HMD.active && visible) { + webView.setLandscape(true); + } else { + if (!visible) { + createApp.hmdMultiSelectMode = false; + webView.setLandscape(false); + } + } + + if (blockPropertyUpdates) { + return; } - } - - if (blockPropertyUpdates) { - return; - } - var data = { - type: 'update', - spaceMode: selectionDisplay.getSpaceMode(), - isPropertiesToolUpdate: caller === this, - }; + var data = { + type: 'update', + spaceMode: selectionDisplay.getSpaceMode(), + isPropertiesToolUpdate: caller === this, + }; - if (selectionUpdated) { - resetScriptStatus(); + if (selectionUpdated) { + resetScriptStatus(); - if (selectionManager.selections.length !== 1) { - if (statusMonitor !== null) { - statusMonitor.stop(); - statusMonitor = null; - } - currentSelectedEntityID = null; - } else if (currentSelectedEntityID !== selectionManager.selections[0]) { - if (statusMonitor !== null) { - statusMonitor.stop(); + if (selectionManager.selections.length !== 1) { + if (statusMonitor !== null) { + statusMonitor.stop(); + statusMonitor = null; + } + currentSelectedEntityID = null; + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { + if (statusMonitor !== null) { + statusMonitor.stop(); + } + var entityID = selectionManager.selections[0]; + currentSelectedEntityID = entityID; + statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); } - var entityID = selectionManager.selections[0]; - currentSelectedEntityID = entityID; - statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); } - } - var selections = []; - for (var i = 0; i < selectionManager.selections.length; i++) { - var entity = {}; - entity.id = selectionManager.selections[i]; - entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); - if (entity.properties.rotation !== undefined) { - entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); - } - if (entity.properties.localRotation !== undefined) { - entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); - } - if (entity.properties.emitOrientation !== undefined) { - entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); - } - if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); - entity.properties.keyLight.direction.z = 0.0; + var selections = []; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entity = {}; + entity.id = selectionManager.selections[i]; + entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); + if (entity.properties.rotation !== undefined) { + entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); + } + if (entity.properties.localRotation !== undefined) { + entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); + } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } + if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); + entity.properties.keyLight.direction.z = 0.0; + } + selections.push(entity); } - selections.push(entity); - } - data.selections = selections; - - emitScriptEvent(data); - } - selectionManager.addEventListener(updateSelections, this); + data.selections = selections; - - var onWebEventReceived = function(data) { - try { - data = JSON.parse(data); - } catch(e) { - return; + emitScriptEvent(data); } - var i, properties, dY, diff, newPosition; - if (data.type === "update") { + selectionManager.addEventListener(updateSelections, this); - if (data.properties || data.propertiesMap) { - var propertiesMap = data.propertiesMap; - if (propertiesMap === undefined) { - propertiesMap = [{ - entityIDs: data.ids, - properties: data.properties, - }]; - } - var sendListUpdate = false; - propertiesMap.forEach(function(propertiesObject) { - var properties = propertiesObject.properties; - var updateEntityIDs = propertiesObject.entityIDs; - if (properties.dynamic === false) { - // this object is leaving dynamic, so we zero its velocities - properties.localVelocity = Vec3.ZERO; - properties.localAngularVelocity = Vec3.ZERO; - } - if (properties.rotation !== undefined) { - properties.rotation = Quat.fromVec3Degrees(properties.rotation); - } - if (properties.localRotation !== undefined) { - properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); - } - if (properties.emitOrientation !== undefined) { - properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + return; + } + var i, properties, dY, diff, newPosition; + if (data.type === "update") { + + if (data.properties || data.propertiesMap) { + var propertiesMap = data.propertiesMap; + if (propertiesMap === undefined) { + propertiesMap = [{ + entityIDs: data.ids, + properties: data.properties, + }]; } - if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { - var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); - if (properties.keyLight.direction.x === undefined) { - properties.keyLight.direction.x = currentKeyLightDirection.x; + + var sendListUpdate = false; + propertiesMap.forEach(function(propertiesObject) { + var properties = propertiesObject.properties; + var updateEntityIDs = propertiesObject.entityIDs; + if (properties.dynamic === false) { + // this object is leaving dynamic, so we zero its velocities + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; } - if (properties.keyLight.direction.y === undefined) { - properties.keyLight.direction.y = currentKeyLightDirection.y; + if (properties.rotation !== undefined) { + properties.rotation = Quat.fromVec3Degrees(properties.rotation); + } + if (properties.localRotation !== undefined) { + properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); + } + if (properties.emitOrientation !== undefined) { + properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + } + if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (properties.keyLight.direction.x === undefined) { + properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (properties.keyLight.direction.y === undefined) { + properties.keyLight.direction.y = currentKeyLightDirection.y; + } + properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); } - properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); - } - updateEntityIDs.forEach(function (entityID) { - Entities.editEntity(entityID, properties); - }); + updateEntityIDs.forEach(function (entityID) { + Entities.editEntity(entityID, properties); + }); - if (properties.name !== undefined || properties.modelURL !== undefined || properties.imageURL !== undefined || - properties.materialURL !== undefined || properties.visible !== undefined || properties.locked !== undefined) { + if (properties.name !== undefined || properties.modelURL !== undefined || properties.imageURL !== undefined || + properties.materialURL !== undefined || properties.visible !== undefined || properties.locked !== undefined) { - sendListUpdate = true; + sendListUpdate = true; + } + + }); + if (sendListUpdate) { + entityListTool.sendUpdate(); } + } - }); - if (sendListUpdate) { - entityListTool.sendUpdate(); + if (data.onlyUpdateEntities) { + blockPropertyUpdates = true; + } else { + createApp.pushCommandForSelections(); + SelectionManager.saveProperties(); } - } + selectionManager._update(false, this); + blockPropertyUpdates = false; - if (data.onlyUpdateEntities) { - blockPropertyUpdates = true; - } else { - pushCommandForSelections(); - SelectionManager.saveProperties(); - } - selectionManager._update(false, this); - blockPropertyUpdates = false; - - if (data.snapToGrid !== undefined) { - entityListTool.setListMenuSnapToGrid(data.snapToGrid); - } - } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { - data.ids.forEach(function(entityID) { - Entities.editEntity(entityID, data.properties); - }); - } else if (data.type === "showMarketplace") { - showMarketplace(); - } else if (data.type === "action") { - if (data.action === "moveSelectionToGrid") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); - diff = { - x: 0, - y: dY, - z: 0 - }; - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - newPosition = Vec3.sum(properties.position, diff); - Entities.editEntity(selectionManager.selections[i], { - position: newPosition - }); - } - pushCommandForSelections(); - selectionManager._update(false, this); + if (data.snapToGrid !== undefined) { + entityListTool.setListMenuSnapToGrid(data.snapToGrid); } - } else if (data.action === "moveAllToGrid") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; - dY = grid.getOrigin().y - bottomY; + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { + data.ids.forEach(function(entityID) { + Entities.editEntity(entityID, data.properties); + }); + } else if (data.type === "showMarketplace") { + showMarketplace(); + } else if (data.type === "action") { + if (data.action === "moveSelectionToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); diff = { x: 0, y: dY, z: 0 }; - newPosition = Vec3.sum(properties.position, diff); - Entities.editEntity(selectionManager.selections[i], { - position: newPosition - }); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "resetToNaturalDimensions") { - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - var naturalDimensions = properties.naturalDimensions; - - // If any of the natural dimensions are not 0, resize - if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && - naturalDimensions.z === 0) { - Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + - " is invalid or the model has not yet been loaded."); - } else { + } else if (data.action === "moveAllToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; + dY = grid.getOrigin().y - bottomY; + diff = { + x: 0, + y: dY, + z: 0 + }; + newPosition = Vec3.sum(properties.position, diff); Entities.editEntity(selectionManager.selections[i], { - dimensions: properties.naturalDimensions + position: newPosition }); } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "previewCamera") { - if (selectionManager.hasSelection()) { - Camera.mode = "entity"; - Camera.cameraEntity = selectionManager.selections[0]; - } - } else if (data.action === "rescaleDimensions") { - var multiplier = data.percentage / 100.0; - if (selectionManager.hasSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - properties = selectionManager.savedProperties[selectionManager.selections[i]]; - Entities.editEntity(selectionManager.selections[i], { - dimensions: Vec3.multiply(multiplier, properties.dimensions) - }); + } else if (data.action === "resetToNaturalDimensions") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var naturalDimensions = properties.naturalDimensions; + + // If any of the natural dimensions are not 0, resize + if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && + naturalDimensions.z === 0) { + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + + " is invalid or the model has not yet been loaded."); + } else { + Entities.editEntity(selectionManager.selections[i], { + dimensions: properties.naturalDimensions + }); + } + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } - pushCommandForSelections(); - selectionManager._update(false, this); - } - } else if (data.action === "reloadClientScripts") { - if (selectionManager.hasSelection()) { - var timestamp = Date.now(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - scriptTimestamp: timestamp - }); + } else if (data.action === "previewCamera") { + if (selectionManager.hasSelection()) { + Camera.mode = "entity"; + Camera.cameraEntity = selectionManager.selections[0]; } - } - } else if (data.action === "reloadServerScripts") { - if (selectionManager.hasSelection()) { - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.reloadServerScripts(selectionManager.selections[i]); + } else if (data.action === "rescaleDimensions") { + var multiplier = data.percentage / 100.0; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions) + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } - } - } else if (data.action === "copyPosition") { - if (selectionManager.selections.length === 1) { - selectionManager.saveProperties(); - properties = selectionManager.savedProperties[selectionManager.selections[0]]; - copiedPosition = properties.position; - Window.copyToClipboard(JSON.stringify(copiedPosition)); - } - } else if (data.action === "copyRotation") { - if (selectionManager.selections.length === 1) { - selectionManager.saveProperties(); - properties = selectionManager.savedProperties[selectionManager.selections[0]]; - copiedRotation = properties.rotation; - Window.copyToClipboard(JSON.stringify(copiedRotation)); - } - } else if (data.action === "pastePosition") { - if (copiedPosition !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - position: copiedPosition - }); + } else if (data.action === "reloadClientScripts") { + if (selectionManager.hasSelection()) { + var timestamp = Date.now(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + scriptTimestamp: timestamp + }); + } } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - } - } else if (data.action === "pasteRotation") { - if (copiedRotation !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], { - rotation: copiedRotation - }); + } else if (data.action === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); - } - } else if (data.action === "setRotationToZero") { - if (selectionManager.selections.length === 1 && SelectionManager.hasUnlockedSelection()) { - selectionManager.saveProperties(); - var parentState = getParentState(selectionManager.selections[0]); - if ((parentState === "PARENT_CHILDREN" || parentState === "CHILDREN") && selectionDisplay.getSpaceMode() === "local" ) { - Entities.editEntity(selectionManager.selections[0], { - localRotation: Quat.IDENTITY - }); + } else if (data.action === "copyPosition") { + if (selectionManager.selections.length === 1) { + selectionManager.saveProperties(); + properties = selectionManager.savedProperties[selectionManager.selections[0]]; + copiedPosition = properties.position; + Window.copyToClipboard(JSON.stringify(copiedPosition)); + } + } else if (data.action === "copyRotation") { + if (selectionManager.selections.length === 1) { + selectionManager.saveProperties(); + properties = selectionManager.savedProperties[selectionManager.selections[0]]; + copiedRotation = properties.rotation; + Window.copyToClipboard(JSON.stringify(copiedRotation)); + } + } else if (data.action === "pastePosition") { + if (copiedPosition !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + position: copiedPosition + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); } else { - Entities.editEntity(selectionManager.selections[0], { - rotation: Quat.IDENTITY - }); + audioFeedback.rejection(); + } + } else if (data.action === "pasteRotation") { + if (copiedRotation !== undefined && selectionManager.selections.length > 0 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + rotation: copiedRotation + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); + } + } else if (data.action === "setRotationToZero") { + if (selectionManager.selections.length === 1 && SelectionManager.hasUnlockedSelection()) { + selectionManager.saveProperties(); + var parentState = createApp.getParentState(selectionManager.selections[0]); + if ((parentState === "PARENT_CHILDREN" || parentState === "CHILDREN") && selectionDisplay.getSpaceMode() === "local" ) { + Entities.editEntity(selectionManager.selections[0], { + localRotation: Quat.IDENTITY + }); + } else { + Entities.editEntity(selectionManager.selections[0], { + rotation: Quat.IDENTITY + }); + } + createApp.pushCommandForSelections(); + selectionManager._update(false, this); + } else { + audioFeedback.rejection(); } - pushCommandForSelections(); - selectionManager._update(false, this); - } else { - audioFeedback.rejection(); } - } - } else if (data.type === "propertiesPageReady") { - updateSelections(true); - } else if (data.type === "tooltipsRequest") { - emitScriptEvent({ - type: 'tooltipsReply', - tooltips: Script.require('./assets/data/createAppTooltips.json'), - hmdActive: HMD.active, - }); - } else if (data.type === "propertyRangeRequest") { - var propertyRanges = {}; - data.properties.forEach(function (property) { - propertyRanges[property] = Entities.getPropertyInfo(property); - }); - emitScriptEvent({ - type: 'propertyRangeReply', - propertyRanges: propertyRanges, - }); - } else if (data.type === "materialTargetRequest") { - var parentModelData; - var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); - if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { - var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; - if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { - parentModelData = Graphics.getModel(properties.parentID); - } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { - parentModelData = {}; - parentModelData.numMeshes = 1; - parentModelData.materialNames = []; + } else if (data.type === "propertiesPageReady") { + updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); + } else if (data.type === "materialTargetRequest") { + var parentModelData; + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { + var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; + if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { + parentModelData = Graphics.getModel(properties.parentID); + } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { + parentModelData = {}; + parentModelData.numMeshes = 1; + parentModelData.materialNames = []; + } } + emitScriptEvent({ + type: 'materialTargetReply', + entityID: data.entityID, + materialTargetData: parentModelData, + }); + } else if (data.type === "zoneListRequest") { + emitScriptEvent({ + type: 'zoneListRequest', + zones: getExistingZoneList() + }); } + }; + + HMD.displayModeChanged.connect(function() { emitScriptEvent({ - type: 'materialTargetReply', - entityID: data.entityID, - materialTargetData: parentModelData, - }); - } else if (data.type === "zoneListRequest") { - emitScriptEvent({ - type: 'zoneListRequest', - zones: getExistingZoneList() + type: 'hmdActiveChanged', + hmdActive: HMD.active, }); - } - }; - - HMD.displayModeChanged.connect(function() { - emitScriptEvent({ - type: 'hmdActiveChanged', - hmdActive: HMD.active, }); - }); - - createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); - webView.webEventReceived.connect(this, onWebEventReceived); + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); - return that; -}; + webView.webEventReceived.connect(this, onWebEventReceived); + return that; + }; -var PopupMenu = function () { - var self = this; - var MENU_ITEM_HEIGHT = 21; - var MENU_ITEM_SPACING = 1; - var TEXT_MARGIN = 7; + var PopupMenu = function () { + var self = this; - var overlays = []; - var overlayInfo = {}; + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; - var visible = false; + var overlays = []; + var overlayInfo = {}; - var upColor = { - red: 0, - green: 0, - blue: 0 - }; - var downColor = { - red: 192, - green: 192, - blue: 192 - }; - var overColor = { - red: 128, - green: 128, - blue: 128 - }; + var visible = false; - self.onSelectMenuItem = function () {}; - - self.addMenuItem = function (name) { - var id = Overlays.addOverlay("text", { - text: name, - backgroundAlpha: 1.0, - backgroundColor: upColor, - topMargin: TEXT_MARGIN, - leftMargin: TEXT_MARGIN, - width: 210, - height: MENU_ITEM_HEIGHT, - font: { - size: 12 - }, - visible: false - }); - overlays.push(id); - overlayInfo[id] = { - name: name + var upColor = { + red: 0, + green: 0, + blue: 0 + }; + var downColor = { + red: 192, + green: 192, + blue: 192 + }; + var overColor = { + red: 128, + green: 128, + blue: 128 }; - return id; - }; - self.updateMenuItemText = function (id, newText) { - Overlays.editOverlay(id, { - text: newText - }); - }; + self.onSelectMenuItem = function () {}; + + self.addMenuItem = function (name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { + size: 12 + }, + visible: false + }); + overlays.push(id); + overlayInfo[id] = { + name: name + }; + return id; + }; - self.setPosition = function (x, y) { - for (var key in overlayInfo) { - Overlays.editOverlay(key, { - x: x, - y: y + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText }); - y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; - } - }; + }; - self.onSelected = function () {}; + self.setPosition = function (x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + x: x, + y: y + }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; - var pressingOverlay = null; - var hoveringOverlay = null; + self.onSelected = function () {}; - self.mousePressEvent = function (event) { - if (event.isLeftButton) { - var overlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - if (overlay in overlayInfo) { - pressingOverlay = overlay; - Overlays.editOverlay(pressingOverlay, { - backgroundColor: downColor + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function (event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y }); - } else { - self.hide(); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { + backgroundColor: downColor + }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function (event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (!pressingOverlay) { + if (hoveringOverlay !== null && overlay !== hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { + backgroundColor: upColor + }); + hoveringOverlay = null; + } + if (overlay !== hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { + backgroundColor: overColor + }); + hoveringOverlay = overlay; + } + } } return false; - } - }; - self.mouseMoveEvent = function (event) { - if (visible) { + }; + self.mouseReleaseEvent = function (event) { var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (!pressingOverlay) { - if (hoveringOverlay !== null && overlay !== hoveringOverlay) { - Overlays.editOverlay(hoveringOverlay, { - backgroundColor: upColor - }); - hoveringOverlay = null; + if (pressingOverlay !== null && pressingOverlay !== undefined) { + if (overlay === pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); } - if (overlay !== hoveringOverlay && overlay in overlayInfo) { - Overlays.editOverlay(overlay, { - backgroundColor: overColor + Overlays.editOverlay(pressingOverlay, { + backgroundColor: upColor + }); + pressingOverlay = null; + self.hide(); + } + }; + + self.setVisible = function (newVisible) { + if (newVisible !== visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + visible: newVisible }); - hoveringOverlay = overlay; } } - } - return false; - }; - self.mouseReleaseEvent = function (event) { - var overlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - if (pressingOverlay !== null && pressingOverlay !== undefined) { - if (overlay === pressingOverlay) { - self.onSelectMenuItem(overlayInfo[overlay].name); + }; + self.show = function () { + self.setVisible(true); + }; + self.hide = function () { + self.setVisible(false); + }; + + function cleanup() { + ContextOverlay.enabled = true; + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); } - Overlays.editOverlay(pressingOverlay, { - backgroundColor: upColor - }); - pressingOverlay = null; - self.hide(); + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; }; - self.setVisible = function (newVisible) { - if (newVisible !== visible) { - visible = newVisible; - for (var key in overlayInfo) { - Overlays.editOverlay(key, { - visible: newVisible - }); + function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; + } + + function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); } + }; + } + + var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + + var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); + } else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); + } + mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); + mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); + mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey); + mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey); + mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey); + mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey); + mapping.from([Controller.Hardware.Keyboard["0"]]).to(createApp.rotateAsNextClickedSurfaceKey); + mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey); + mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey); + mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey); + mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); + mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); + mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); + mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.duplicateSelection() })); + + // Bind undo to ctrl-shift-z to maintain backwards-compatibility + mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { createApp.undoHistory.redo() })); + + + mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { createApp.unparentSelectedEntities(); })); + + mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { createApp.parentSelectedEntities(); })); + + createApp.keyUpEventFromUIWindow = function(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + + deleteKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "T") { + toggleKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "F") { + focusKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "J") { + gridKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "G") { + viewGridKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "H") { + snapKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "K") { + gridToAvatarKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "0") { + createApp.rotateAsNextClickedSurfaceKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "7") { + quickRotate90xKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "8") { + quickRotate90yKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "9") { + quickRotate90zKey(pressedValue); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { + selectionManager.duplicateSelection(); + } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + createApp.undoHistory.undo(); // undo is only handled via handleMenuItem on Mac + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + createApp.parentSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + createApp.unparentSelectedEntities(); + } else if (!isOnMacPlatform && + ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { + createApp.undoHistory.redo(); // redo is only handled via handleMenuItem on Mac + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) } }; - self.show = function () { - self.setVisible(true); - }; - self.hide = function () { - self.setVisible(false); - }; - function cleanup() { - ContextOverlay.enabled = true; - for (var i = 0; i < overlays.length; i++) { - Overlays.deleteOverlay(overlays[i]); + var propertyMenu = new PopupMenu(); + + propertyMenu.onSelectMenuItem = function (name) { + + if (propertyMenu.marketplaceID) { + showMarketplace(propertyMenu.marketplaceID); } - Controller.mousePressEvent.disconnect(self.mousePressEvent); - Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); - Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + }; - Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); - Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); - } + var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); - Controller.mousePressEvent.connect(self.mousePressEvent); - Controller.mouseMoveEvent.connect(self.mouseMoveEvent); - Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); - Script.scriptEnding.connect(cleanup); + var propertiesTool = new PropertiesTool(); - return this; -}; + selectionDisplay.onSpaceModeChange = function(spaceMode) { + entityListTool.setSpaceMode(spaceMode); + propertiesTool.setSpaceMode(spaceMode); + }; -function whenPressed(fn) { - return function(value) { - if (value > 0) { - fn(); + function getExistingZoneList() { + var center = { "x": 0, "y": 0, "z": 0 }; + var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_SCAN_RADIUS); + var listExistingZones = []; + var thisZone = {}; + var properties; + for (var k = 0; k < existingZoneIDs.length; k++) { + properties = Entities.getEntityProperties(existingZoneIDs[k], ["name"]); + thisZone = { + "id": existingZoneIDs[k], + "name": properties.name + }; + listExistingZones.push(thisZone); } - }; -} + listExistingZones.sort(zoneSortOrder); + return listExistingZones; + } -function whenReleased(fn) { - return function(value) { - if (value === 0) { - fn(); + function zoneSortOrder(a, b) { + var nameA = a.name.toUpperCase(); + var nameB = b.name.toUpperCase(); + if (nameA > nameB) { + return 1; + } else if (nameA < nameB) { + return -1; } - }; -} - -var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); - -var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); -if (isOnMacPlatform) { - mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); -} else { - mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); -} -mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); -mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); -mapping.from([Controller.Hardware.Keyboard.J]).to(gridKey); -mapping.from([Controller.Hardware.Keyboard.G]).to(viewGridKey); -mapping.from([Controller.Hardware.Keyboard.H]).to(snapKey); -mapping.from([Controller.Hardware.Keyboard.K]).to(gridToAvatarKey); -mapping.from([Controller.Hardware.Keyboard["0"]]).to(rotateAsNextClickedSurfaceKey); -mapping.from([Controller.Hardware.Keyboard["7"]]).to(quickRotate90xKey); -mapping.from([Controller.Hardware.Keyboard["8"]]).to(quickRotate90yKey); -mapping.from([Controller.Hardware.Keyboard["9"]]).to(quickRotate90zKey); -mapping.from([Controller.Hardware.Keyboard.X]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); -mapping.from([Controller.Hardware.Keyboard.C]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.copySelectedEntities() })); -mapping.from([Controller.Hardware.Keyboard.V]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.pasteEntities() })); -mapping.from([Controller.Hardware.Keyboard.D]) - .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { selectionManager.duplicateSelection() })); - -// Bind undo to ctrl-shift-z to maintain backwards-compatibility -mapping.from([Controller.Hardware.Keyboard.Z]) - .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) - .to(whenPressed(function() { undoHistory.redo() })); - - -mapping.from([Controller.Hardware.Keyboard.P]) - .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) - .to(whenReleased(function() { unparentSelectedEntities(); })); - -mapping.from([Controller.Hardware.Keyboard.P]) - .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) - .to(whenReleased(function() { parentSelectedEntities(); })); - -keyUpEventFromUIWindow = function(keyUpEvent) { - var WANT_DEBUG_MISSING_SHORTCUTS = false; - - var pressedValue = 0.0; - - if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") - || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { - - deleteKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "T") { - toggleKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "F") { - focusKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "J") { - gridKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "G") { - viewGridKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "H") { - snapKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "K") { - gridToAvatarKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "0") { - rotateAsNextClickedSurfaceKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "7") { - quickRotate90xKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "8") { - quickRotate90yKey(pressedValue); - } else if (keyUpEvent.keyCodeString === "9") { - quickRotate90zKey(pressedValue); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { - selectionManager.cutSelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { - selectionManager.copySelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { - selectionManager.pasteEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { - selectionManager.duplicateSelection(); - } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { - undoHistory.undo(); // undo is only handled via handleMenuItem on Mac - } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { - parentSelectedEntities(); - } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { - unparentSelectedEntities(); - } else if (!isOnMacPlatform && - ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || - (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { - undoHistory.redo(); // redo is only handled via handleMenuItem on Mac - } else if (WANT_DEBUG_MISSING_SHORTCUTS) { - console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) - } -}; - -var propertyMenu = new PopupMenu(); - -propertyMenu.onSelectMenuItem = function (name) { - - if (propertyMenu.marketplaceID) { - showMarketplace(propertyMenu.marketplaceID); - } -}; - -var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); - -var propertiesTool = new PropertiesTool(); - -selectionDisplay.onSpaceModeChange = function(spaceMode) { - entityListTool.setSpaceMode(spaceMode); - propertiesTool.setSpaceMode(spaceMode); -}; - -function getExistingZoneList() { - var center = { "x": 0, "y": 0, "z": 0 }; - var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_SCAN_RADIUS); - var listExistingZones = []; - var thisZone = {}; - var properties; - for (var k = 0; k < existingZoneIDs.length; k++) { - properties = Entities.getEntityProperties(existingZoneIDs[k], ["name"]); - thisZone = { - "id": existingZoneIDs[k], - "name": properties.name - }; - listExistingZones.push(thisZone); - } - listExistingZones.sort(zoneSortOrder); - return listExistingZones; -} - -function zoneSortOrder(a, b) { - var nameA = a.name.toUpperCase(); - var nameB = b.name.toUpperCase(); - if (nameA > nameB) { - return 1; - } else if (nameA < nameB) { - return -1; - } - if (a.name > b.name) { - return 1; - } else if (a.name < b.name) { - return -1; - } - return 0; -} - -function getParentState(id) { - var state = "NONE"; - var properties = Entities.getEntityProperties(id, ["parentID"]); - var children = getDomainOnlyChildrenIDs(id); - if (properties.parentID !== Uuid.NULL) { - if (children.length > 0) { - state = "PARENT_CHILDREN"; - } else { - state = "CHILDREN"; + if (a.name > b.name) { + return 1; + } else if (a.name < b.name) { + return -1; } - } else { - if (children.length > 0) { - state = "PARENT"; + return 0; + } + + //print("getParentState added"); + //function getParentState(id) { + createApp.getParentState = function(id) { + var state = "NONE"; + var properties = Entities.getEntityProperties(id, ["parentID"]); + var children = createApp.getDomainOnlyChildrenIDs(id); + if (properties.parentID !== Uuid.NULL) { + if (children.length > 0) { + state = "PARENT_CHILDREN"; + } else { + state = "CHILDREN"; + } + } else { + if (children.length > 0) { + state = "PARENT"; + } } + return state; } - return state; -} -function getDomainOnlyChildrenIDs(id) { - var allChildren = Entities.getChildrenIDs(id); - var realChildren = []; - var properties; - for (var i = 0; i < allChildren.length; i++) { - properties = Entities.getEntityProperties(allChildren[i], ["name"]); - if (properties.name !== undefined && properties.name !== entityShapeVisualizerSessionName) { - realChildren.push(allChildren[i]); + //print("Global object after getParentState" + JSON.stringify(globalThis)); + + createApp.getDomainOnlyChildrenIDs = function(id) { + var allChildren = Entities.getChildrenIDs(id); + var realChildren = []; + var properties; + for (var i = 0; i < allChildren.length; i++) { + properties = Entities.getEntityProperties(allChildren[i], ["name"]); + if (properties.name !== undefined && properties.name !== entityShapeVisualizerSessionName) { + realChildren.push(allChildren[i]); + } } + return realChildren; } - return realChildren; -} -function importEntitiesFromFile() { - Window.browseChanged.connect(onFileOpenChanged); - Window.browseAsync("Select .json to Import", "", "*.json"); -} + createApp.importEntitiesFromFile = function() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select .json to Import", "", "*.json"); + } -function importEntitiesFromUrl() { - Window.promptTextChanged.connect(onPromptTextChanged); - Window.promptAsync("URL of a .json to import", ""); -} + createApp.importEntitiesFromUrl = function() { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of a .json to import", ""); + } -function setCameraFocusToSelection() { - cameraManager.enable(); - if (selectionManager.hasSelection()) { - cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + createApp.setCameraFocusToSelection = function() { + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } } -} -function alignGridToSelection() { - if (selectionManager.hasSelection()) { - if (!grid.getVisible()) { - grid.setVisible(true, true); + createApp.alignGridToSelection = function() { + if (selectionManager.hasSelection()) { + if (!grid.getVisible()) { + grid.setVisible(true, true); + } + grid.moveToSelection(); } - grid.moveToSelection(); } -} -function alignGridToAvatar() { - if (!grid.getVisible()) { - grid.setVisible(true, true); + createApp.alignGridToAvatar = function() { + if (!grid.getVisible()) { + grid.setVisible(true, true); + } + grid.moveToAvatar(); } - grid.moveToAvatar(); -} -function toggleGridVisibility() { - if (!grid.getVisible()) { - grid.setVisible(true, true); - } else { - grid.setVisible(false, true); + createApp.toggleGridVisibility = function() { + if (!grid.getVisible()) { + grid.setVisible(true, true); + } else { + grid.setVisible(false, true); + } } -} -function rotateAsNextClickedSurface() { - if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { - audioFeedback.rejection(); - Window.notifyEditError("You have nothing selected, or the selection is locked."); - expectingRotateAsClickedSurface = false; - } else { - expectingRotateAsClickedSurface = true; + createApp.rotateAsNextClickedSurface = function() { + if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { + audioFeedback.rejection(); + Window.notifyEditError("You have nothing selected, or the selection is locked."); + createApp.expectingRotateAsClickedSurface = false; + } else { + createApp.expectingRotateAsClickedSurface = true; + } } -} }()); // END LOCAL_SCOPE +//}(); // END LOCAL_SCOPE diff --git a/scripts/system/create/editModes/editVoxels.js b/scripts/system/create/editModes/editVoxels.js index 53fae0ae7fb..13138e55e17 100644 --- a/scripts/system/create/editModes/editVoxels.js +++ b/scripts/system/create/editModes/editVoxels.js @@ -654,10 +654,10 @@ EditVoxels = function() { } function cleanup() { - Controller.mousePressEvent.disconnect(self.mousePressEvent); - Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); - Controller.keyPressEvent.disconnect(self.keyPressEvent); - Controller.keyReleaseEvent.disconnect(self.keyReleaseEvent); + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); } Controller.mousePressEvent.connect(mousePressEvent); diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index aa4f7eb218c..67810b1f3f2 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -4,14 +4,16 @@ // // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // -/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, - cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible, - keyUpEventFromUIWindow, Script, SelectionDisplay, SelectionManager, Clipboard */ +/* global EntityListTool, Tablet, Entities, Camera, MyAvatar, Vec3, Menu, Messages, + MENU_EASE_ON_FOCUS, + Script, Clipboard */ var PROFILING_ENABLED = false; var profileIndent = ''; @@ -30,8 +32,9 @@ const PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); }; -EntityListTool = function(shouldUseEditTabletApp) { +var EntityListTool = function(shouldUseEditTabletApp, selectionManager) { var that = {}; + that.selectionManager = selectionManager; var CreateWindow = Script.require('../modules/createWindow.js'); @@ -108,8 +111,8 @@ EntityListTool = function(shouldUseEditTabletApp) { } var selectedIDs = []; - for (var i = 0; i < selectionManager.selections.length; i++) { - selectedIDs.push(selectionManager.selections[i]); + for (var i = 0; i < that.selectionManager.selections.length; i++) { + selectedIDs.push(that.selectionManager.selections[i]); } emitJSONScriptEvent({ @@ -217,8 +220,8 @@ EntityListTool = function(shouldUseEditTabletApp) { } else if (properties.type === "Image") { url = properties.imageURL; } - - var parentStatus = getParentState(ids[i]); + //print("Global object before getParentState call: " + JSON.stringify(globalThis)); + var parentStatus = that.createApp.getParentState(ids[i]); var parentState = ""; if (parentStatus === "PARENT") { parentState = "A"; @@ -257,8 +260,8 @@ EntityListTool = function(shouldUseEditTabletApp) { }); var selectedIDs = []; - for (var j = 0; j < selectionManager.selections.length; j++) { - selectedIDs.push(selectionManager.selections[j]); + for (var j = 0; j < that.selectionManager.selections.length; j++) { + selectedIDs.push(that.selectionManager.selections[j]); } emitJSONScriptEvent({ @@ -289,7 +292,7 @@ EntityListTool = function(shouldUseEditTabletApp) { function onFileSaveChanged(filename) { Window.saveFileChanged.disconnect(onFileSaveChanged); if (filename !== "") { - var success = Clipboard.exportEntities(filename, selectionManager.selections); + var success = Clipboard.exportEntities(filename, that.selectionManager.selections); if (!success) { Window.notifyEditError("Export failed."); } @@ -297,6 +300,7 @@ EntityListTool = function(shouldUseEditTabletApp) { } var onWebEventReceived = function(data) { + //print("entityList.js onWebEventReceived: " + data); try { data = JSON.parse(data); } catch(e) { @@ -310,113 +314,113 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs, that); + that.selectionManager.setSelections(entityIDs, that); if (data.focus) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, + that.cameraManager.enable(); + that.cameraManager.focus(that.selectionManager.worldPosition, + that.selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } } else if (data.type === "refresh") { that.sendUpdate(); } else if (data.type === "teleport") { - if (selectionManager.hasSelection()) { - MyAvatar.position = selectionManager.worldPosition; + if (that.selectionManager.hasSelection()) { + MyAvatar.position = that.selectionManager.worldPosition; } } else if (data.type === "export") { - if (!selectionManager.hasSelection()) { + if (!that.selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); } else { Window.saveFileChanged.connect(onFileSaveChanged); Window.saveAsync("Select Where to Save", "", "*.json"); } } else if (data.type === "delete") { - deleteSelectedEntities(); + that.createApp.deleteSelectedEntities(); } else if (data.type === "toggleLocked") { - toggleSelectedEntitiesLocked(); + that.createApp.toggleSelectedEntitiesLocked(); } else if (data.type === "toggleVisible") { - toggleSelectedEntitiesVisible(); + that.createApp.toggleSelectedEntitiesVisible(); } else if (data.type === "filterInView") { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; } else if (data.type === "cut") { - SelectionManager.cutSelectedEntities(); + that.selectionManager.cutSelectedEntities(); } else if (data.type === "copy") { - SelectionManager.copySelectedEntities(); + that.selectionManager.copySelectedEntities(); } else if (data.type === "paste") { - SelectionManager.pasteEntities(); + that.selectionManager.pasteEntities(); } else if (data.type === "duplicate") { - SelectionManager.duplicateSelection(); + that.selectionManager.duplicateSelection(); that.sendUpdate(); } else if (data.type === "rename") { Entities.editEntity(data.entityID, {name: data.name}); // make sure that the name also gets updated in the properties window - SelectionManager._update(); + that.selectionManager._update(); } else if (data.type === "toggleSpaceMode") { SelectionDisplay.toggleSpaceMode(); } else if (data.type === 'keyUpEvent') { - keyUpEventFromUIWindow(data.keyUpEvent); + that.createApp.keyUpEventFromUIWindow(data.keyUpEvent); } else if (data.type === 'undo') { - undoHistory.undo(); + that.createApp.undoHistory.undo(); } else if (data.type === 'redo') { - undoHistory.redo(); + that.createApp.undoHistory.redo(); } else if (data.type === 'parent') { - parentSelectedEntities(); + that.createApp.parentSelectedEntities(); } else if (data.type === 'unparent') { - unparentSelectedEntities(); + that.createApp.unparentSelectedEntities(); } else if (data.type === 'hmdMultiSelectMode') { - hmdMultiSelectMode = data.value; + that.createApp.hmdMultiSelectMode = data.value; } else if (data.type === 'selectAllInBox') { - selectAllEntitiesInCurrentSelectionBox(false); + that.createApp.selectAllEntitiesInCurrentSelectionBox(false); } else if (data.type === 'selectAllTouchingBox') { - selectAllEntitiesInCurrentSelectionBox(true); + that.createApp.selectAllEntitiesInCurrentSelectionBox(true); } else if (data.type === 'selectParent') { - SelectionManager.selectParent(); + that.selectionManager.selectParent(); } else if (data.type === 'selectTopParent') { - SelectionManager.selectTopParent(); + that.selectionManager.selectTopParent(); } else if (data.type === 'addChildrenToSelection') { - SelectionManager.addChildrenToSelection(); + that.selectionManager.addChildrenToSelection(); } else if (data.type === 'selectFamily') { - SelectionManager.selectFamily(); + that.selectionManager.selectFamily(); } else if (data.type === 'selectTopFamily') { - SelectionManager.selectTopFamily(); + that.selectionManager.selectTopFamily(); } else if (data.type === 'teleportToEntity') { - SelectionManager.teleportToEntity(); + that.selectionManager.teleportToEntity(); } else if (data.type === 'rotateAsTheNextClickedSurface') { - rotateAsNextClickedSurface(); + that.createApp.rotateAsNextClickedSurface(); } else if (data.type === 'quickRotate90x') { - selectionDisplay.rotate90degreeSelection("X"); + that.selectionDisplay.rotate90degreeSelection("X"); } else if (data.type === 'quickRotate90y') { - selectionDisplay.rotate90degreeSelection("Y"); + that.selectionDisplay.rotate90degreeSelection("Y"); } else if (data.type === 'quickRotate90z') { - selectionDisplay.rotate90degreeSelection("Z"); + that.selectionDisplay.rotate90degreeSelection("Z"); } else if (data.type === 'moveEntitySelectionToAvatar') { - SelectionManager.moveEntitiesSelectionToAvatar(); + that.selectionManager.moveEntitiesSelectionToAvatar(); } else if (data.type === 'loadConfigSetting') { - var columnsData = Settings.getValue(SETTING_EDITOR_COLUMNS_SETUP, "NO_DATA"); - var defaultRadius = Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100); + var columnsData = Settings.getValue(that.createApp.SETTING_EDITOR_COLUMNS_SETUP, "NO_DATA"); + var defaultRadius = Settings.getValue(that.createApp.SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100); emitJSONScriptEvent({ "type": "loadedConfigSetting", "columnsData": columnsData, "defaultRadius": defaultRadius }); } else if (data.type === 'saveColumnsConfigSetting') { - Settings.setValue(SETTING_EDITOR_COLUMNS_SETUP, data.columnsData); + Settings.setValue(that.createApp.SETTING_EDITOR_COLUMNS_SETUP, data.columnsData); } else if (data.type === 'importFromFile') { - importEntitiesFromFile(); + that.createApp.importEntitiesFromFile(); } else if (data.type === 'importFromUrl') { - importEntitiesFromUrl(); + that.createApp.importEntitiesFromUrl(); } else if (data.type === 'setCameraFocusToSelection') { - setCameraFocusToSelection(); + that.createApp.setCameraFocusToSelection(); } else if (data.type === 'alignGridToSelection') { - alignGridToSelection(); + that.createApp.alignGridToSelection(); } else if (data.type === 'alignGridToAvatar') { - alignGridToAvatar(); + that.createApp.alignGridToAvatar(); } else if (data.type === 'brokenURLReport') { - brokenURLReport(selectionManager.selections); + brokenURLReport(that.selectionManager.selections); } else if (data.type === 'toggleGridVisibility') { - toggleGridVisibility(); + that.createApp.toggleGridVisibility(); } else if (data.type === 'toggleSnapToGrid') { that.toggleSnapToGrid(); } diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index f5469bada76..38a0d684cc1 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -6,12 +6,13 @@ // Modified by David Back on 1/9/2018 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // This script implements a class useful for building tools for editing entities. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // /* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, @@ -83,6 +84,7 @@ SelectionManager = (function() { // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES function handleEntitySelectionToolUpdates(channel, message, sender) { + //print("Channel: " + channel + " Sender: " + sender + " Message: " + JSON.stringify(message)); if (channel !== 'entityToolUpdates') { return; } @@ -107,11 +109,11 @@ SelectionManager = (function() { if (wantDebug) { print("setting selection to " + messageParsed.entityID); } - if (expectingRotateAsClickedSurface) { + if (that.createApp.expectingRotateAsClickedSurface) { if (!SelectionManager.hasSelection() || !SelectionManager.hasUnlockedSelection()) { audioFeedback.rejection(); Window.notifyEditError("You have nothing selected, or the selection is locked."); - expectingRotateAsClickedSurface = false; + that.createApp.expectingRotateAsClickedSurface = false; } else { //Rotate Selection according the Surface Normal var normalRotation = Quat.lookAtSimple(Vec3.ZERO, Vec3.multiply(messageParsed.surfaceNormal, -1)); @@ -125,8 +127,8 @@ SelectionManager = (function() { } selectionDisplay.moveSelection(Vec3.sum(messageParsed.intersection, Vec3.multiplyQbyV( normalRotation, {"x": 0.0, "y":0.0, "z": distanceFromSurface}))); that._update(false, this); - pushCommandForSelections(); - expectingRotateAsClickedSurface = false; + that.createApp.pushCommandForSelections(); + that.createApp.expectingRotateAsClickedSurface = false; audioFeedback.action(); } } else { @@ -135,7 +137,7 @@ SelectionManager = (function() { if (intersectObj.intersects) { return; } - if (hmdMultiSelectMode) { + if (that.createApp.hmdMultiSelectMode) { that.addEntity(messageParsed.entityID, true, that); } else { that.setSelections([messageParsed.entityID], that); @@ -232,6 +234,8 @@ SelectionManager = (function() { }; that.setSelections = function(entityIDs, caller) { + print("setSelections: " + JSON.stringify(entityIDs)); + Script.logBacktrace("setSelections"); that.selections = []; for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; @@ -487,7 +491,7 @@ SelectionManager = (function() { that.cutSelectedEntities = function() { that.copySelectedEntities(); - deleteSelectedEntities(); + that.createApp.deleteSelectedEntities(); }; that.copySelectedEntities = function() { @@ -581,7 +585,7 @@ SelectionManager = (function() { that.pasteEntities = function() { var dimensions = entityClipboard.dimensions; var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); - var pastePosition = getPositionToCreateEntity(maxDimension); + var pastePosition = that.createApp.getPositionToCreateEntity(maxDimension); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); var copiedProperties = []; @@ -616,7 +620,7 @@ SelectionManager = (function() { } redo(copiedProperties); - undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); + that.createApp.undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); }; that._update = function(selectionUpdated, caller) { @@ -723,7 +727,7 @@ SelectionManager = (function() { var newPosition = Vec3.sum(relativePosition, targetPosition); Entities.editEntity(id, { "position": newPosition }); } - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); that._update(false, this); } else { audioFeedback.rejection(); @@ -779,12 +783,12 @@ SelectionManager = (function() { that.addChildrenToSelection = function() { if (that.hasSelection()) { for (var i = 0; i < that.selections.length; i++) { - var childrenIDs = getDomainOnlyChildrenIDs(that.selections[i]); + var childrenIDs = that.createApp.getDomainOnlyChildrenIDs(that.selections[i]); var collectNewChildren; var j; var k = 0; do { - collectNewChildren = getDomainOnlyChildrenIDs(childrenIDs[k]); + collectNewChildren = that.createApp.getDomainOnlyChildrenIDs(childrenIDs[k]); if (collectNewChildren.length > 0) { for (j = 0; j < collectNewChildren.length; j++) { childrenIDs.push(collectNewChildren[j]); @@ -1297,7 +1301,7 @@ SelectionDisplay = (function() { var debugPickPlaneEnabled = false; var debugPickPlane = Entities.addEntity({ - type: "shape", + type: "Shape", shape: "Quad", alpha: 0.25, color: COLOR_DEBUG_PICK_PLANE, @@ -2002,7 +2006,7 @@ SelectionDisplay = (function() { var handleBoundingBoxColor = COLOR_BOUNDING_EDGE; if (SelectionManager.selections.length === 1) { - var parentState = getParentState(SelectionManager.selections[0]); + var parentState = that.createApp.getParentState(SelectionManager.selections[0]); if (parentState === "CHILDREN") { handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN; } else if (parentState === "PARENT") { @@ -2504,7 +2508,7 @@ SelectionDisplay = (function() { } updateSelectionsRotation(axisRotation, SelectionManager.worldPosition); SelectionManager._update(false, this); - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); audioFeedback.action(); } }; @@ -2585,7 +2589,7 @@ SelectionDisplay = (function() { print(" starting elevation: " + startingElevation); } - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); @@ -2598,7 +2602,7 @@ SelectionDisplay = (function() { } }, onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); + that.createApp.pushCommandForSelections(duplicatedEntityIDs); if (isConstrained) { Entities.editEntity(xRailToolEntity, { visible: false, @@ -2617,7 +2621,7 @@ SelectionDisplay = (function() { var wantDebug = false; var pickRay = generalComputePickRay(event.x, event.y); - var newPick = rayPlaneIntersection2(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection2(pickRay, pickPlanePosition, pickPlaneNormal); // If the pick ray doesn't hit the pick plane in this direction, do nothing. // this will happen when someone drags across the horizon from the side they started on. @@ -2634,7 +2638,12 @@ SelectionDisplay = (function() { that.showDebugPickPlaneHit(newPick); } - var vector = Vec3.subtract(newPick, initialPick); + var vector = null; + if (initialPick) { + vector = Vec3.subtract(newPick, initialPick); + } else { + vector = newPick; + } // If the mouse is too close to the horizon of the pick plane, stop moving var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it @@ -2722,7 +2731,7 @@ SelectionDisplay = (function() { var negateAndHalve = -0.5; var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); vector = Vec3.subtract( - grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + that.grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); // editing a parent will cause all the children to automatically follow along, so don't @@ -2801,7 +2810,7 @@ SelectionDisplay = (function() { axisVector = Vec3.multiplyQbyV(rotation, axisVector); pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); pickPlanePosition = SelectionManager.worldPosition; - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); SelectionManager.saveProperties(); that.resetPreviousHandleColor(); @@ -2822,7 +2831,7 @@ SelectionDisplay = (function() { } }, onEnd: function(event, reason) { - pushCommandForSelections(duplicatedEntityIDs); + that.createApp.pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -2832,7 +2841,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -2850,8 +2859,8 @@ SelectionDisplay = (function() { var dotVector = Vec3.dot(vector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector); - var gridOrigin = grid.getOrigin(); - vector = Vec3.subtract(grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); + var gridOrigin = that.grid.getOrigin(); + vector = Vec3.subtract(that.grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); var wantDebug = false; if (wantDebug) { @@ -2962,7 +2971,7 @@ SelectionDisplay = (function() { pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); pickPlanePosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); that.setHandleTranslateVisible(false); that.setHandleRotateVisible(false); @@ -3008,8 +3017,8 @@ SelectionDisplay = (function() { Entities.editEntity(stretchPanel, { visible: false, ignorePickIntersection: true }); } activeStretchCubePanelOffset = null; - - pushCommandForSelections(); + + that.createApp.pushCommandForSelections(); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -3019,7 +3028,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -3029,7 +3038,7 @@ SelectionDisplay = (function() { changeInDimensions = Vec3.multiply(dotVector, axisVector); changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(rotation), changeInDimensions); changeInDimensions = Vec3.multiplyVbyV(mask, changeInDimensions); - changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = that.grid.snapToSpacing(changeInDimensions); changeInDimensions = Vec3.multiply(NEGATE_VECTOR, Vec3.multiplyVbyV(signs, changeInDimensions)); var newDimensions = Vec3.sum(initialDimensions, changeInDimensions); @@ -3088,7 +3097,7 @@ SelectionDisplay = (function() { pickPlanePosition = initialPosition; pickPlaneNormal = Vec3.subtract(pickRay.origin, pickPlanePosition); - initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + initialPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); that.setHandleTranslateVisible(false); that.setHandleRotateVisible(false); @@ -3120,8 +3129,8 @@ SelectionDisplay = (function() { Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); that.replaceCollisionsAfterStretch = false; } - - pushCommandForSelections(); + + that.createApp.pushCommandForSelections(); }, onMove: function(event) { var pickRay = generalComputePickRay(event.x, event.y); @@ -3131,7 +3140,7 @@ SelectionDisplay = (function() { pickRay = previousPickRay; } - var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + var newPick = that.createApp.rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); if (debugPickPlaneEnabled) { that.showDebugPickPlaneHit(newPick); } @@ -3140,7 +3149,7 @@ SelectionDisplay = (function() { var dimensionsMultiple = toCameraDistance * SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE; var changeInDimensions = Vec3.subtract(newPick, initialPick); changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(Camera.orientation), changeInDimensions); - changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = that.grid.snapToSpacing(changeInDimensions); changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); var averageDimensionChange = (changeInDimensions.x + changeInDimensions.y + changeInDimensions.z) / 3; @@ -3309,7 +3318,7 @@ SelectionDisplay = (function() { // editOverlays may not have committed rotation changes. // Compute zero position based on where the toolEntity will be eventually. - var initialPick = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + var initialPick = that.createApp.rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); // In case of a parallel ray, this will be null, which will cause early-out // in the onMove helper. rotationZero = initialPick; @@ -3343,7 +3352,7 @@ SelectionDisplay = (function() { } }); Entities.editEntity(handleRotateCurrentRing, { visible: false, ignorePickIntersection: true }); - pushCommandForSelections(); + that.createApp.pushCommandForSelections(); if (wantDebug) { print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); } @@ -3364,7 +3373,7 @@ SelectionDisplay = (function() { } var pickRay = generalComputePickRay(event.x, event.y); - var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + var result = that.createApp.rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); if (result) { var centerToZero = Vec3.subtract(rotationZero, rotationCenter); var centerToIntersect = Vec3.subtract(result, rotationCenter); diff --git a/scripts/system/create/modules/createWindow.js b/scripts/system/create/modules/createWindow.js index 18cc2d6ebd4..9f79a51b2d7 100644 --- a/scripts/system/create/modules/createWindow.js +++ b/scripts/system/create/modules/createWindow.js @@ -1,13 +1,15 @@ -"use strict"; +"no use strict"; // createWindow.js // // Created by Thijs Wenker on 6/1/18 // // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // var getWindowRect = function(settingsKey, defaultRect) { @@ -71,6 +73,7 @@ var CallableEvent = (function() { module.exports = (function() { function CreateWindow(qmlPath, title, settingsKey, defaultRect, createOnStartup) { this.qmlPath = qmlPath; + print("QML path: " + qmlPath); this.title = title; this.settingsKey = settingsKey; this.defaultRect = defaultRect; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 0c238756a9c..2f573abc321 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -237,8 +237,8 @@ var connectionToDomainFailed = false; function getAnchorLocalYOffset() { - var loadingSpherePosition = Overlays.getProperty(loadingSphereID, "position"); - var loadingSphereOrientation = Overlays.getProperty(loadingSphereID, "rotation"); + var loadingSpherePosition = Entities.getEntityProperties(loadingSphereID, ["position"]).position; + var loadingSphereOrientation = Entities.getEntityProperties(loadingSphereID, ["rotation"]).rotation; var overlayXform = new Xform(loadingSphereOrientation, loadingSpherePosition); var worldToOverlayXform = overlayXform.inv(); var headPosition = MyAvatar.getHeadPosition(); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 94143f5bab4..53d4682c97a 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -1,8 +1,9 @@ // // WebTablet.js // -// Created by Anthony J. Thibault on 8/8/2016 +// Created by Anthony J. Thibault on August 8th, 2016 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023, Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -119,17 +120,16 @@ WebTablet = function (url, width, dpi, hand, location, visible) { var modelURL = LOCAL_TABLET_MODEL_PATH; var tabletProperties = { - name: "WebTablet Tablet", - type: "Model", - modelURL: modelURL, - url: modelURL, // for overlay - grabbable: true, // for overlay - loadPriority: 10.0, // for overlay - grab: { grabbable: true }, - dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth }, - parentID: MyAvatar.SELF_ID, - visible: visible, - isGroupCulled: true + "name": "WebTablet Tablet", + "type": "Model", + "modelURL": modelURL, + "grab": { + "grabbable": true + }, + "dimensions": { "x": tabletWidth, "y": tabletHeight, "z": tabletDepth }, + "parentID": MyAvatar.SELF_ID, + "visible": visible, + "groupCulled": true }; // compute position, rotation & parentJointIndex of the tablet @@ -142,64 +142,87 @@ WebTablet = function (url, width, dpi, hand, location, visible) { this.cleanUpOldTablets(); cleanUpOldMaterialEntities(); - this.tabletEntityID = Overlays.addOverlay("model", tabletProperties); + this.tabletEntityID = Entities.addEntity(tabletProperties, "local"); if (this.webOverlayID) { - Overlays.deleteOverlay(this.webOverlayID); + Entities.deleteEntity(this.webOverlayID); } var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleFactor; var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor; var screenWidth = 0.9367 * tabletWidth; var screenHeight = 0.9000 * tabletHeight; - this.webOverlayID = Overlays.addOverlay("web3d", { - name: "WebTablet Web", - url: url, - localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: Quat.angleAxis(180, Y_AXIS), - dimensions: {x: screenWidth, y: screenHeight, z: 1.0}, - dpi: tabletDpi, - color: { red: 255, green: 255, blue: 255 }, - alpha: 1.0, - parentID: this.tabletEntityID, - parentJointIndex: -1, - showKeyboardFocusHighlight: false, - grabbable: false, - visible: visible - }); + this.webOverlayID = Entities.addEntity({ + "type": "Web", + "name": "WebTablet Web", + "sourceUrl": url, + "localPosition": { "x": 0, "y": WEB_ENTITY_Y_OFFSET, "z": -WEB_ENTITY_Z_OFFSET }, + "localRotation": Quat.angleAxis(180, Y_AXIS), + "dimensions": {"x": screenWidth, "y": screenHeight, "z": 1.0}, + "dpi": tabletDpi, + "color": { "red": 255, "green": 255, "blue": 255 }, + "alpha": 1.0, + "parentID": this.tabletEntityID, + "showKeyboardFocusHighlight": false, + "grab": { + "grabbable": false + }, + "visible": visible + }, "local"); var homeButtonDim = 4.0 * tabletScaleFactor / 1.5; var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleFactor; var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)); var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleFactor; - this.homeButtonID = Overlays.addOverlay("circle3d", { - name: "homeButton", - localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}), - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, - solid: true, - alpha: 0.0, - visible: visible, - drawInFront: false, - parentID: this.tabletEntityID, - parentJointIndex: -1 - }); - - this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { - name: "homeButtonHighlight", - localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, - localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}), - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, - color: {red: 255, green: 255, blue: 255}, - solid: true, - innerRadius: 0.9, - ignorePickIntersection: true, - alpha: 0.0, - visible: visible, - drawInFront: false, - parentID: this.tabletEntityID, - parentJointIndex: -1 - }); + this.homeButtonID = Entities.addEntity({ + "type": "Gizmo", + "gizmoType": "ring", + "name": "homeButton", + "localPosition": { "x": HOME_BUTTON_X_OFFSET, "y": HOME_BUTTON_Y_OFFSET, "z": -HOME_BUTTON_Z_OFFSET }, + "localRotation": Quat.fromVec3Degrees({ "x": 90, "y": 0, "z": 0}), + "dimensions": { "x": homeButtonDim, "y": homeButtonDim, "z": homeButtonDim }, + "primitiveMode": "solid", + "ring": { + "innerStartAlpha": 0.0, + "innerEndAlpha": 0.0, + "outerStartAlpha": 0.0, + "outerEndAlpha": 0.0, + }, + "visible": visible, + "renderLayer": "world", + "grab": { + "grabbable": false + }, + "parentID": this.tabletEntityID + }, "local"); + + this.homeButtonHighlightID = Entities.addEntity({ + "type": "Gizmo", + "gizmoType": "ring", + "name": "homeButtonHighlight", + "localPosition": { "x": -HOME_BUTTON_X_OFFSET, "y": HOME_BUTTON_Y_OFFSET, "z": -HOME_BUTTON_Z_OFFSET }, + "localRotation": Quat.fromVec3Degrees({ "x": 90, "y": 0, "z": 0}), + "dimensions": { "x": homeButtonDim, "y": homeButtonDim, "z": homeButtonDim }, + "primitiveMode": "solid", + "ring": { + "innerStartColor": {"red": 255, "green": 255, "blue": 255}, + "innerEndColor": {"red": 255, "green": 255, "blue": 255}, + "outerStartColor": {"red": 255, "green": 255, "blue": 255}, + "outerEndColor": {"red": 255, "green": 255, "blue": 255}, + "innerStartAlpha": 0.0, + "innerEndAlpha": 0.0, + "outerStartAlpha": 0.0, + "outerEndAlpha": 0.0, + "innerRadius": 0.9, + }, + "ignorePickIntersection": true, + "visible": visible, + "renderLayer": "world", + "grab": { + "grabbable": false + }, + "parentID": this.tabletEntityID + }, "local"); this.receive = function (channel, senderID, senderUUID, localOnly) { if (_this.homeButtonID === senderID) { @@ -285,8 +308,8 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { this.landscape = newLandscapeValue; var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation); var tabletRotation = Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180); - Overlays.editOverlay(this.tabletEntityID, { - rotation: tabletRotation + Entities.editEntity(this.tabletEntityID, { + "rotation": tabletRotation }); var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; @@ -295,18 +318,18 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { var screenWidth = 0.9275 * tabletWidth; var screenHeight = 0.8983 * tabletHeight; var screenRotation = Quat.angleAxis(180, Vec3.UP); - Overlays.editOverlay(this.webOverlayID, { - localRotation: this.landscape ? Quat.multiply(screenRotation, Quat.angleAxis(-90, Vec3.FRONT)) : screenRotation, - dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} + Entities.editEntity(this.webOverlayID, { + "localRotation": this.landscape ? Quat.multiply(screenRotation, Quat.angleAxis(-90, Vec3.FRONT)) : screenRotation, + "dimensions": {"x": this.landscape ? screenHeight : screenWidth, "y": this.landscape ? screenWidth : screenHeight, "z": 0.1} }); }; WebTablet.prototype.getLocation = function() { - var location = Overlays.getProperty(this.tabletEntityID, "localPosition"); - var orientation = Overlays.getProperty(this.tabletEntityID, "localOrientation"); + var location = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; + var orientation = Entities.getEntityProperties(this.tabletEntityID, ["localRotation"]).localRotation; return { - localPosition: location, - localRotation: orientation + "localPosition": location, + "localRotation": orientation }; }; @@ -316,11 +339,11 @@ WebTablet.prototype.setHomeButtonTexture = function() { }; WebTablet.prototype.setURL = function (url) { - Overlays.editOverlay(this.webOverlayID, { url: url }); + Entities.editEntity(this.webOverlayID, { "sourceUrl": url }); }; WebTablet.prototype.setScriptURL = function (scriptURL) { - Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL }); + Entities.editEntity(this.webOverlayID, { "scriptURL": scriptURL }); }; WebTablet.prototype.getOverlayObject = function () { @@ -333,10 +356,10 @@ WebTablet.prototype.setWidth = function (width) { }; WebTablet.prototype.destroy = function () { - Overlays.deleteOverlay(this.webOverlayID); - Overlays.deleteOverlay(this.tabletEntityID); - Overlays.deleteOverlay(this.homeButtonID); - Overlays.deleteOverlay(this.homeButtonHighlightID); + Entities.deleteEntity(this.webOverlayID); + Entities.deleteEntity(this.tabletEntityID); + Entities.deleteEntity(this.homeButtonID); + Entities.deleteEntity(this.homeButtonHighlightID); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -352,7 +375,7 @@ WebTablet.prototype.geometryChanged = function (geometry) { var tabletProperties = {}; // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties); - Overlays.editOverlay(HMD.tabletID, tabletProperties); + Entities.editEntity(HMD.tabletID, tabletProperties); } }; @@ -464,7 +487,7 @@ WebTablet.prototype.onHmdChanged = function () { var tabletProperties = {}; // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties); - Overlays.editOverlay(HMD.tabletID, tabletProperties); + Entities.editEntity(HMD.tabletID, tabletProperties); }; WebTablet.prototype.pickle = function () { @@ -510,7 +533,7 @@ WebTablet.unpickle = function (string) { }; WebTablet.prototype.getPosition = function () { - return Overlays.getProperty(this.webOverlayID, "position"); + return Entities.getEntityProperties(this.webOverlayID, ["position"]).position; }; WebTablet.prototype.mousePressEvent = function (event) { @@ -523,7 +546,7 @@ WebTablet.prototype.mousePressEvent = function (event) { this.dragging = true; var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection); - this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); + this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; } } } @@ -557,7 +580,15 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() { WebTablet.prototype.handleHomeButtonHover = function(x, y) { var pickRay = Camera.computePickRay(x, y); var homePickResult = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID]); - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homePickResult.intersects ? 1.0 : 0.0 }); + var alpha = homePickResult.intersects ? 1.0 : 0.0; + Entities.editEntity(this.homeButtonHighlightID, { + "ring": { + "innerStartAlpha": alpha, + "innerEndAlpha": alpha, + "outerStartAlpha": alpha, + "outerEndAlpha": alpha + } + }); }; WebTablet.prototype.mouseMoveEvent = function (event) { @@ -590,8 +621,8 @@ WebTablet.prototype.mouseMoveProcessor = function () { var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance)); var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint); var localPosition = Vec3.sum(this.initialLocalPosition, localOffset); - Overlays.editOverlay(this.tabletEntityID, { - localPosition: localPosition + Entities.editEntity(this.tabletEntityID, { + "localPosition": localPosition }); } this.scheduleMouseMoveProcessor(); diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index f789e19cd87..bd51d2fbf80 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -2,8 +2,13 @@ // cloneEntity.js // +// Copyright 2017-2019 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* global entityIsCloneable:true, cloneEntity:true, propsAreCloneDynamic:true, Script, propsAreCloneDynamic:true, Entities, Uuid */ @@ -31,14 +36,14 @@ if (typeof Object.assign !== 'function') { }; } -entityIsCloneable = function(props) { +var entityIsCloneable = function(props) { if (props) { return props.cloneable; } return false; }; -propsAreCloneDynamic = function(props) { +var propsAreCloneDynamic = function(props) { var cloneable = entityIsCloneable(props); if (cloneable) { return props.cloneDynamic; @@ -46,7 +51,7 @@ propsAreCloneDynamic = function(props) { return false; }; -cloneEntity = function(props) { +var cloneEntity = function(props) { var entityIDToClone = props.id; if (entityIsCloneable(props) && (Uuid.isNull(props.certificateID) || props.certificateType.indexOf('domainUnlimited') >= 0)) { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 5cfd899da04..f0f15316946 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -2,8 +2,13 @@ // controllerDispatcherUtils.js // +// Copyright 2017-2020 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* global module, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4, Selection, Uuid, Controller, @@ -68,50 +73,50 @@ handsAreTracked: true */ -MSECS_PER_SEC = 1000.0; -INCHES_TO_METERS = 1.0 / 39.3701; +var MSECS_PER_SEC = 1000.0; +var INCHES_TO_METERS = 1.0 / 39.3701; -HAPTIC_PULSE_STRENGTH = 1.0; -HAPTIC_PULSE_DURATION = 13.0; +var HAPTIC_PULSE_STRENGTH = 1.0; +var HAPTIC_PULSE_DURATION = 13.0; -ZERO_VEC = { x: 0, y: 0, z: 0 }; -ONE_VEC = { x: 1, y: 1, z: 1 }; +var ZERO_VEC = { x: 0, y: 0, z: 0 }; +var ONE_VEC = { x: 1, y: 1, z: 1 }; -LEFT_HAND = 0; -RIGHT_HAND = 1; +var LEFT_HAND = 0; +var RIGHT_HAND = 1; -FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; +var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; -HAPTIC_PULSE_STRENGTH = 1.0; -HAPTIC_PULSE_DURATION = 13.0; +var HAPTIC_PULSE_STRENGTH = 1.0; +var HAPTIC_PULSE_DURATION = 13.0; -DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; +var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; -TRIGGER_OFF_VALUE = 0.1; -TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab -BUMPER_ON_VALUE = 0.5; +var TRIGGER_OFF_VALUE = 0.1; +var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab +var BUMPER_ON_VALUE = 0.5; -PICK_MAX_DISTANCE = 500; // max length of pick-ray -DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? -NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. +var PICK_MAX_DISTANCE = 500; // max length of pick-ray +var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? +var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. -COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; -COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; -COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; +var COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; +var COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; +var COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; -NEAR_GRAB_RADIUS = 1.0; +var NEAR_GRAB_RADIUS = 1.0; -TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand -TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away -TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks +var TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand +var TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away +var TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks -TELEPORT_DEADZONE = 0.15; +var TELEPORT_DEADZONE = 0.15; -NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. +var NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. // Smaller than TEAR_AWAY_DISTANCE for hysteresis. -DISPATCHER_HOVERING_LIST = "dispatcherHoveringList"; -DISPATCHER_HOVERING_STYLE = { +var DISPATCHER_HOVERING_LIST = "dispatcherHoveringList"; +var DISPATCHER_HOVERING_STYLE = { isOutlineSmooth: true, outlineWidth: 0, outlineUnoccludedColor: {red: 255, green: 128, blue: 128}, @@ -124,7 +129,7 @@ DISPATCHER_HOVERING_STYLE = { fillOccludedAlpha: 0.0 }; -DISPATCHER_PROPERTIES = [ +var DISPATCHER_PROPERTIES = [ "position", "registrationPoint", "rotation", @@ -169,7 +174,7 @@ DISPATCHER_PROPERTIES = [ // activitySlots -- indicates which "slots" must not yet be in use for this module to start // requiredDataForReady -- which "situation" parts this module looks at to decide if it will start // sleepMSBetweenRuns -- how long to wait between calls to this module's "run" method -makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForReady, sleepMSBetweenRuns, enableLaserForHand) { +var makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForReady, sleepMSBetweenRuns, enableLaserForHand) { if (enableLaserForHand === undefined) { enableLaserForHand = -1; } @@ -183,7 +188,7 @@ makeDispatcherModuleParameters = function (priority, activitySlots, requiredData }; }; -makeLaserLockInfo = function(targetID, isOverlay, hand, offset) { +var makeLaserLockInfo = function(targetID, isOverlay, hand, offset) { return { targetID: targetID, isOverlay: isOverlay, @@ -192,7 +197,7 @@ makeLaserLockInfo = function(targetID, isOverlay, hand, offset) { }; }; -makeLaserParams = function(hand, alwaysOn) { +var makeLaserParams = function(hand, alwaysOn) { if (alwaysOn === undefined) { alwaysOn = false; } @@ -203,7 +208,7 @@ makeLaserParams = function(hand, alwaysOn) { }; }; -makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) { +var makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) { return { active: active, targets: targets, @@ -212,7 +217,7 @@ makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo }; }; -enableDispatcherModule = function (moduleName, module, priority) { +var enableDispatcherModule = function (moduleName, module, priority) { if (!controllerDispatcherPlugins) { controllerDispatcherPlugins = {}; } @@ -220,19 +225,19 @@ enableDispatcherModule = function (moduleName, module, priority) { controllerDispatcherPluginsNeedSort = true; }; -disableDispatcherModule = function (moduleName) { +var disableDispatcherModule = function (moduleName) { delete controllerDispatcherPlugins[moduleName]; controllerDispatcherPluginsNeedSort = true; }; -getEnabledModuleByName = function (moduleName) { +var getEnabledModuleByName = function (moduleName) { if (controllerDispatcherPlugins.hasOwnProperty(moduleName)) { return controllerDispatcherPlugins[moduleName]; } return null; }; -getGrabbableData = function (ggdProps) { +var getGrabbableData = function (ggdProps) { // look in userData for a "grabbable" key, return the value or some defaults var grabbableData = {}; var userDataParsed = null; @@ -297,7 +302,7 @@ getGrabbableData = function (ggdProps) { return grabbableData; }; -isAnothersAvatarEntity = function (iaaeProps) { +var isAnothersAvatarEntity = function (iaaeProps) { if (!iaaeProps.avatarEntity) { return false; } @@ -310,7 +315,7 @@ isAnothersAvatarEntity = function (iaaeProps) { return true; }; -isAnothersChildEntity = function (iaceProps) { +var isAnothersChildEntity = function (iaceProps) { while (iaceProps.parentID && iaceProps.parentID !== Uuid.NULL) { if (Entities.getNestableType(iaceProps.parentID) == "avatar") { if (iaceProps.parentID == MyAvatar.SELF_ID || iaceProps.parentID == MyAvatar.sessionUUID) { @@ -330,7 +335,7 @@ isAnothersChildEntity = function (iaceProps) { }; -entityIsEquippable = function (eieProps) { +var entityIsEquippable = function (eieProps) { var grabbable = getGrabbableData(eieProps).grabbable; if (!grabbable || isAnothersAvatarEntity(eieProps) || @@ -341,7 +346,7 @@ entityIsEquippable = function (eieProps) { return true; }; -entityIsGrabbable = function (eigProps) { +var entityIsGrabbable = function (eigProps) { var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || eigProps.locked || @@ -351,19 +356,19 @@ entityIsGrabbable = function (eigProps) { return true; }; -clearHighlightedEntities = function() { +var clearHighlightedEntities = function() { Selection.clearSelectedItemsList(DISPATCHER_HOVERING_LIST); }; -highlightTargetEntity = function(entityID) { +var highlightTargetEntity = function(entityID) { Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); }; -unhighlightTargetEntity = function(entityID) { +var unhighlightTargetEntity = function(entityID) { Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); }; -entityIsDistanceGrabbable = function(eidgProps) { +var entityIsDistanceGrabbable = function(eidgProps) { if (!entityIsGrabbable(eidgProps)) { return false; } @@ -377,10 +382,10 @@ entityIsDistanceGrabbable = function(eidgProps) { return true; }; -getControllerJointIndexCacheTime = [0, 0]; -getControllerJointIndexCache = [-1, -1]; +var getControllerJointIndexCacheTime = [0, 0]; +var getControllerJointIndexCache = [-1, -1]; -getControllerJointIndex = function (hand) { +var getControllerJointIndex = function (hand) { var GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME = 3000; // msecs var now = Date.now(); @@ -400,7 +405,7 @@ getControllerJointIndex = function (hand) { return -1; }; -propsArePhysical = function (papProps) { +var propsArePhysical = function (papProps) { if (!papProps.dynamic) { return false; } @@ -408,7 +413,7 @@ propsArePhysical = function (papProps) { return isPhysical; }; -projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) { +var projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) { var invRot = Quat.inverse(rotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position)); var invDimensions = { @@ -424,25 +429,25 @@ projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registr }; }; -projectOntoEntityXYPlane = function (entityID, worldPos, popProps) { +var projectOntoEntityXYPlane = function (entityID, worldPos, popProps) { return projectOntoXYPlane(worldPos, popProps.position, popProps.rotation, popProps.dimensions, popProps.registrationPoint); }; -projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { - var position = Overlays.getProperty(overlayID, "position"); - var rotation = Overlays.getProperty(overlayID, "rotation"); - var dimensions = Overlays.getProperty(overlayID, "dimensions"); +var projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { + var position = Entities.getEntityProperties(overlayID, ["position"]).position; + var rotation = Entities.getEntityProperties(overlayID, ["rotation"]).rotation; + var dimensions = Entities.getEntityProperties(overlayID, ["dimensions"]).dimensions; dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); }; -entityHasActions = function (entityID) { +var entityHasActions = function (entityID) { return Entities.getActionIDs(entityID).length > 0; }; -ensureDynamic = function (entityID) { +var ensureDynamic = function (entityID) { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. var edProps = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); @@ -455,7 +460,7 @@ ensureDynamic = function (entityID) { } }; -findGrabbableGroupParent = function (controllerData, targetProps) { +var findGrabbableGroupParent = function (controllerData, targetProps) { while (targetProps.grab.grabDelegateToParent && targetProps.parentID && targetProps.parentID !== Uuid.NULL && @@ -475,7 +480,7 @@ findGrabbableGroupParent = function (controllerData, targetProps) { return targetProps; }; -getEntityParents = function(targetProps) { +var getEntityParents = function(targetProps) { var parentProperties = []; while (targetProps.parentID && targetProps.parentID !== Uuid.NULL && @@ -493,7 +498,7 @@ getEntityParents = function(targetProps) { }; -findHandChildEntities = function(hand) { +var findHandChildEntities = function(hand) { // find children of avatar's hand joint var handJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); @@ -517,7 +522,7 @@ findHandChildEntities = function(hand) { }); }; -findFarGrabJointChildEntities = function(hand) { +var findFarGrabJointChildEntities = function(hand) { // find children of avatar's far-grab joint var farGrabJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "_FARGRAB_RIGHTHAND" : "_FARGRAB_LEFTHAND"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, farGrabJointIndex); @@ -529,7 +534,7 @@ findFarGrabJointChildEntities = function(hand) { }); }; -distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps, jointGrabOffset) { +var distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps, jointGrabOffset) { var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; var rotInv = Quat.inverse(entityProps.localRotation); var localPosition = Vec3.sum(entityProps.localPosition, jointGrabOffset); @@ -553,13 +558,16 @@ distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps, jointGr return Vec3.distance(v, localPoint); }; -distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { +var distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { var entityXform = new Xform(entityProps.rotation, entityProps.position); var localPoint = entityXform.inv().xformPoint(point); var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); - var localMin = Vec3.subtract(entityXform.trans, minOffset); - var localMax = Vec3.sum(entityXform.trans, maxOffset); + var localMin = Vec3.subtract(entityXform.pos, minOffset); + var localMax = Vec3.sum(entityXform.pos, maxOffset); + // TODO: was originally this, but this causes an error on V8 branch and probably never worked on QtScript either + //var localMin = Vec3.subtract(entityXform.trans, minOffset); + //var localMax = Vec3.sum(entityXform.trans, maxOffset); var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; v.x = Math.max(v.x, localMin.x); @@ -572,7 +580,7 @@ distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { return Vec3.distance(v, localPoint); }; -entityIsEquipped = function(entityID) { +var entityIsEquipped = function(entityID) { var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; @@ -580,11 +588,11 @@ entityIsEquipped = function(entityID) { return equippedInRightHand || equippedInLeftHand; }; -worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { +var worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { // get world matrix for intersection point var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos); - // calculate world matrix for registrationPoint addjusted entity + // calculate world matrix for registrationPoint adjusted entity var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, wptrProps.registrationPoint); var regOffset = Vec3.multiplyVbyV(regRatio, wptrProps.dimensions); @@ -602,7 +610,7 @@ worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { return offsetMat; }; -handsAreTracked = function () { +var handsAreTracked = function () { return Controller.getPoseValue(Controller.Standard.LeftHandIndex3).valid || Controller.getPoseValue(Controller.Standard.RightHandIndex3).valid; }; diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js index be7d22e0730..5722458990b 100644 --- a/scripts/system/libraries/controllers.js +++ b/scripts/system/libraries/controllers.js @@ -2,9 +2,13 @@ // // Created by Seth Alves on 2016-9-7 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + /* global MyAvatar, Vec3, HMD, Controller, Camera, Quat, Settings, getGrabPointSphereOffset:true, setGrabCommunications:true, @@ -12,17 +16,17 @@ getControllerWorldLocation:true */ -var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing"; -setGrabCommunications = function setFarGrabCommunications(on) { +const GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing"; +const setGrabCommunications = function setFarGrabCommunications(on) { Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : ""); }; -getGrabCommunications = function getFarGrabCommunications() { +const getGrabCommunications = function getFarGrabCommunications() { return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, ""); }; // this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 -getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) { +const getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) { var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral var offset = GRAB_POINT_SPHERE_OFFSET; if (handController === Controller.Standard.LeftHand) { @@ -40,7 +44,7 @@ getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) { }; // controllerWorldLocation is where the controller would be, in-world, with an added offset -getControllerWorldLocation = function (handController, doOffset) { +const getControllerWorldLocation = function (handController, doOffset) { var orientation; var position; var valid = false; diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 4674e5925d5..fd40a87574c 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -3,12 +3,12 @@ // Created by Ryan Huffman on 6 Nov 2014 // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // -/* global keyUpEventFromUIWindow */ var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html'); @@ -292,8 +292,8 @@ GridTool = function(opts) { var dataString = JSON.stringify(data); webView.emitScriptEvent(dataString); createToolsWindow.emitScriptEvent(dataString); - if (selectionDisplay) { - selectionDisplay.updateHandles(); + if (that.selectionDisplay) { + that.selectionDisplay.updateHandles(); } }); @@ -319,7 +319,7 @@ GridTool = function(opts) { horizontalGrid.moveToSelection(); } } else if (data.type === 'keyUpEvent') { - keyUpEventFromUIWindow(data.keyUpEvent); + that.createApp.keyUpEventFromUIWindow(data.keyUpEvent); } }; diff --git a/scripts/system/libraries/networkingConstants.js b/scripts/system/libraries/networkingConstants.js index 2a9c60f5258..fe2ae0561d8 100644 --- a/scripts/system/libraries/networkingConstants.js +++ b/scripts/system/libraries/networkingConstants.js @@ -2,12 +2,16 @@ // // Created by Kalila L. on 8/27/20 // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// // Interface Metadata Source -var INTERFACE_METADATA_SOURCE = "https://cdn.vircadia.com/dist/launcher/vircadiaMeta.json"; +//var INTERFACE_METADATA_SOURCE = "https://cdn.vircadia.com/dist/launcher/vircadiaMeta.json"; +var INTERFACE_METADATA_SOURCE = ""; // Base CDN URLs var CONTENT_CDN_URL = Script.getExternalPath(Script.ExternalPaths.HF_Content, "/"); diff --git a/scripts/system/libraries/overlayUtils.js b/scripts/system/libraries/overlayUtils.js index 366cdbcc979..19719b24399 100644 --- a/scripts/system/libraries/overlayUtils.js +++ b/scripts/system/libraries/overlayUtils.js @@ -12,6 +12,7 @@ // OverlayGroup provides a way to create composite overlays and control their // position relative to a settable rootPosition and rootRotation. // +//V8TODO: check if it uses 3D overlays OverlayGroup = function(opts) { var that = {}; diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index ec9a08b7eb6..5920911414f 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -2,8 +2,13 @@ // pointerUtils.js // +// Copyright 2017-2018 High Fidelity, Inc. +// Copyright 2022-2023 Overte e.V. +// // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// /* jslint bitwise: true */ @@ -14,62 +19,66 @@ */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Pointer = function(hudLayer, pickType, pointerData) { +var Pointer = function(hudLayer, pickType, pointerData) { this.SEARCH_SPHERE_SIZE = 0.0132; this.dim = {x: this.SEARCH_SPHERE_SIZE, y: this.SEARCH_SPHERE_SIZE, z: this.SEARCH_SPHERE_SIZE}; this.halfPath = { - type: "line3d", + type: "PolyLine", color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, visible: true, alpha: 1, solid: true, - glow: 1.0, - ignoreRayIntersection: true, // always ignore this - drawInFront: !hudLayer, // Even when burried inside of something, show it. + glow: true, + faceCamera: true, + ignorePickIntersection: true, // always ignore this + //V8TODO + drawInFront: !hudLayer, // Even when buried inside of something, show it. drawHUDLayer: hudLayer, }; this.halfEnd = { - type: "sphere", + type: "Sphere", dimensions: this.dim, solid: true, color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, alpha: 0.9, - ignoreRayIntersection: true, - drawInFront: !hudLayer, // Even when burried inside of something, show it. + ignorePickIntersection: true, + drawInFront: !hudLayer, // Even when buried inside of something, show it. drawHUDLayer: hudLayer, visible: true }; this.fullPath = { - type: "line3d", + type: "PolyLine", color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, visible: true, alpha: 1, solid: true, - glow: 1.0, - ignoreRayIntersection: true, // always ignore this - drawInFront: !hudLayer, // Even when burried inside of something, show it. + glow: true, + faceCamera: true, + ignorePickIntersection: true, // always ignore this + drawInFront: !hudLayer, // Even when buried inside of something, show it. drawHUDLayer: hudLayer, }; this.fullEnd = { - type: "sphere", + type: "Sphere", dimensions: this.dim, solid: true, color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, alpha: 0.9, - ignoreRayIntersection: true, - drawInFront: !hudLayer, // Even when burried inside of something, show it. + ignorePickIntersection: true, + drawInFront: !hudLayer, // Even when buried inside of something, show it. drawHUDLayer: hudLayer, visible: true }; this.holdPath = { - type: "line3d", + type: "PolyLine", color: COLORS_GRAB_DISTANCE_HOLD, visible: true, alpha: 1, solid: true, - glow: 1.0, - ignoreRayIntersection: true, // always ignore this - drawInFront: !hudLayer, // Even when burried inside of something, show it. + glow: true, + faceCamera: true, + ignorePickIntersection: true, // always ignore this + drawInFront: !hudLayer, // Even when buried inside of something, show it. drawHUDLayer: hudLayer, }; @@ -94,10 +103,15 @@ Pointer = function(hudLayer, pickType, pointerData) { delete pointerData.hand; function createPointer(pickType, pointerData) { - var pointerID = Pointers.createPointer(pickType, pointerData); - Pointers.setRenderState(pointerID, ""); - Pointers.enablePointer(pointerID); - return pointerID; + //V8TODO + if (pickType == PickType.Ray) { + var pointerID = Pointers.createRayPointer(pointerData); + Pointers.setRenderState(pointerID, ""); + Pointers.enablePointer(pointerID); + return pointerID; + } else { + print("pointerUtils.js createPointer: ray type not supported yet on V8 branch"); + } } this.enable = function() { @@ -158,7 +172,7 @@ Pointer = function(hudLayer, pickType, pointerData) { }; -PointerManager = function() { +var PointerManager = function() { this.pointers = []; this.createPointer = function(hudLayer, pickType, pointerData) { diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js index f0f7ec46fed..bdee5cf5dc5 100644 --- a/scripts/system/libraries/touchEventUtils.js +++ b/scripts/system/libraries/touchEventUtils.js @@ -152,13 +152,13 @@ function composeTouchTargetFromIntersection(intersection) { // will return undefined if overlayID does not exist. function calculateTouchTargetFromOverlay(touchTip, overlayID) { - var overlayPosition = Overlays.getProperty(overlayID, "position"); + var overlayPosition = Entities.getEntityProperties(overlayID, ["position"]).position; if (overlayPosition === undefined) { return; } // project touchTip onto overlay plane. - var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + var overlayRotation = Entities.getEntityProperties(overlayID, ["rotation"]).rotation; if (overlayRotation === undefined) { return; } @@ -170,7 +170,8 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) { var invRot = Quat.inverse(overlayRotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - var dimensions = Overlays.getProperty(overlayID, "dimensions"); + // V8TODO: check if this is correct for entities + var dimensions = Entities.getEntityProperties(overlayID, ["dimensions"]).dimensions; if (dimensions === undefined) { return; } diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index c7c30a58dd2..5d76c538395 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -376,7 +376,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; var sensorScaleOffsetOverride = 1; var SENSOR_TO_ROOM_MATRIX = 65534; - var parentJointIndex = newParentJointIndex || Overlays.getProperty(HMD.tabletID, "parentJointIndex"); + var parentJointIndex = newParentJointIndex || Entities.getEntityProperties(HMD.tabletID, ["parentJointIndex"]).parentJointIndex; if (parentJointIndex === SENSOR_TO_ROOM_MATRIX) { sensorScaleOffsetOverride = 1 / sensorScaleFactor; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 38287e3af30..fcc37cef0db 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -115,7 +115,7 @@ function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { } } else { // if we're hiding the tablet, check to see if it was visible in the first place - tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); + tabletShouldBeVisibleInSecondaryCamera = Entities.getEntityProperties(HMD.tabletID, ["isVisibleInSecondaryCamera"]).isVisibleInSecondaryCamera; } Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera: visibleInSecondaryCam }); diff --git a/scripts/system/menu.js b/scripts/system/menu.js index 277a223e07f..15c8da3e3b0 100644 --- a/scripts/system/menu.js +++ b/scripts/system/menu.js @@ -77,8 +77,8 @@ var HOME_BUTTON_TEXTURE = Script.getExternalPath(Script.ExternalPaths.HF_Content if (button) { button.clicked.disconnect(onClicked); + tablet.removeButton(button); } - tablet.removeButton(button); tablet.screenChanged.disconnect(onScreenChanged); }); }()); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index c9942afd480..6271276584e 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -1,15 +1,16 @@ // // miniTablet.js // -// Created by David Rowe on 9 Aug 2018. +// Created by David Rowe on August 9th, 2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /* global getTabletWidthFromSettings, handsAreTracked, TRIGGER_OFF_VALUE, Controller, Script, Camera, Tablet, MyAvatar, - Quat, SoundCache, HMD, Overlays, Vec3, Uuid, Messages */ + Quat, SoundCache, HMD, Vec3, Uuid, Messages */ (function () { @@ -265,7 +266,7 @@ } function getMiniTabletProperties() { - var properties = Overlays.getProperties(miniOverlay, ["position", "orientation"]); + var properties = Entities.getEntityProperties(miniOverlay, ["position", "orientation"]); return { position: properties.position, orientation: properties.orientation @@ -282,22 +283,24 @@ uiHand = hand; - Overlays.editOverlay(miniOverlay, { - parentID: MyAvatar.SELF_ID, - parentJointIndex: handJointIndex(hand), - localPosition: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_POSITIONS[hand]), - localRotation: MINI_ROTATIONS[hand], - dimensions: Vec3.multiply(initialScale, MINI_DIMENSIONS), - grabbable: true, - visible: true + Entities.editEntity(miniOverlay, { + "parentID": MyAvatar.SELF_ID, + "parentJointIndex": handJointIndex(hand), + "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_POSITIONS[hand]), + "localRotation": MINI_ROTATIONS[hand], + "dimensions": Vec3.multiply(initialScale, MINI_DIMENSIONS), + "grab": { + "grabbable": true + }, + "visible": true }); - Overlays.editOverlay(miniUIOverlay, { - url: handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, - localPosition: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), - localRotation: MINI_UI_LOCAL_ROTATION, - dimensions: Vec3.multiply(initialScale, MINI_UI_DIMENSIONS), - dpi: MINI_UI_DPI / initialScale, - visible: true + Entities.editEntity(miniUIOverlay, { + "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, + "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), + "localRotation": MINI_UI_LOCAL_ROTATION, + "dimensions": Vec3.multiply(initialScale, MINI_UI_DIMENSIONS), + "dpi": MINI_UI_DPI / initialScale, + "visible": true }); updateMiniTabletID(); @@ -306,20 +309,20 @@ // Overlay content is created the first time it is visible to the user. The initial creation displays artefacts. // Delay showing UI overlay until after giving it time for its content to be created. Script.setTimeout(function () { - Overlays.editOverlay(miniUIOverlay, { alpha: 1.0 }); + Entities.editEntity(miniUIOverlay, { "alpha": 1.0 }); }, MINI_UI_OVERLAY_ENABLED_DELAY); } } function size(scaleFactor) { // Scale UI in place. - Overlays.editOverlay(miniOverlay, { - dimensions: Vec3.multiply(scaleFactor, MINI_DIMENSIONS) + Entities.editEntity(miniOverlay, { + "dimensions": Vec3.multiply(scaleFactor, MINI_DIMENSIONS) }); - Overlays.editOverlay(miniUIOverlay, { - localPosition: Vec3.multiply(scaleFactor, MINI_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(scaleFactor, MINI_UI_DIMENSIONS), - dpi: MINI_UI_DPI / scaleFactor + Entities.editEntity(miniUIOverlay, { + "localPosition": Vec3.multiply(scaleFactor, MINI_UI_LOCAL_POSITION), + "dimensions": Vec3.multiply(scaleFactor, MINI_UI_DIMENSIONS), + "dpi": MINI_UI_DPI / scaleFactor }); updateRotation(); } @@ -335,7 +338,7 @@ } // Grab details. - var properties = Overlays.getProperties(miniOverlay, ["localPosition", "localRotation"]); + var properties = Entities.getEntityProperties(miniOverlay, ["localPosition", "localRotation"]); miniExpandHand = hand; miniExpandLocalRotation = properties.localRotation; miniExpandLocalPosition = Vec3.sum(properties.localPosition, @@ -369,19 +372,19 @@ Vec3.multiplyQbyV(miniExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 })); localPosition = Vec3.sum(localPosition, Vec3.multiplyQbyV(localRotation, { x: 0, y: 0.5 * dimensions.y, z: 0 })); - Overlays.editOverlay(miniOverlay, { - localPosition: localPosition, - localRotation: localRotation, - dimensions: dimensions + Entities.editEntity(miniOverlay, { + "localPosition": localPosition, + "localRotation": localRotation, + "dimensions": dimensions }); // FIXME: Temporary code change to try not displaying UI when mini tablet is expanding to become the tablet proper. - Overlays.editOverlay(miniUIOverlay, { + Entities.editEntity(miniUIOverlay, { /* - localPosition: Vec3.multiply(tabletScaleFactor, MINI_UI_LOCAL_POSITION), - dimensions: Vec3.multiply(tabletScaleFactor, MINI_UI_DIMENSIONS), - dpi: MINI_UI_DPI / tabletScaleFactor + "localPosition": Vec3.multiply(tabletScaleFactor, MINI_UI_LOCAL_POSITION), + "dimensions": Vec3.multiply(tabletScaleFactor, MINI_UI_DIMENSIONS), + "dpi": MINI_UI_DPI / tabletScaleFactor */ - visible: false + "visible": false }); } @@ -402,7 +405,7 @@ deltaRotation, localRotation; - if (Overlays.getProperty(miniOverlay, "parentJointIndex") !== handJointIndex(uiHand)) { + if (Entities.getEntityProperties(miniOverlay, ["parentJointIndex"]).parentJointIndex !== handJointIndex(uiHand)) { // Overlay has been grabbed by other hand but this script hasn't received notification yet. return; } @@ -430,24 +433,26 @@ deltaRotation = Quat.angleAxis(deltaAngle, Vec3.multiplyQbyV(defaultLocalRotation, Vec3.UNIT_Z)); localRotation = Quat.multiply(deltaRotation, defaultLocalRotation); } - Overlays.editOverlay(miniOverlay, { - localRotation: localRotation + Entities.editEntity(miniOverlay, { + "localRotation": localRotation }); } function release() { - Overlays.editOverlay(miniOverlay, { - parentID: Uuid.NULL, // Release hold so that hand can grab tablet proper. - grabbable: false + Entities.editEntity(miniOverlay, { + "parentID": Uuid.NULL, // Release hold so that hand can grab tablet proper. + "grab": { + "grabbable": false + } }); } function hide() { - Overlays.editOverlay(miniOverlay, { - visible: false + Entities.editEntity(miniOverlay, { + "visible": false }); - Overlays.editOverlay(miniUIOverlay, { - visible: false + Entities.editEntity(miniUIOverlay, { + "visible": false }); } @@ -458,35 +463,40 @@ return; } - miniOverlayObject = Overlays.getOverlayObject(miniUIOverlay); + miniOverlayObject = Entities.getEntityObject(miniUIOverlay); if (miniOverlayObject) { miniOverlayObject.webEventReceived.connect(onWebEventReceived); } } function create() { - miniOverlay = Overlays.addOverlay("model", { - url: MINI_MODEL, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_DIMENSIONS), - solid: true, - grabbable: true, - showKeyboardFocusHighlight: false, - drawInFront: false, - visible: false - }); - miniUIOverlay = Overlays.addOverlay("web3d", { - url: handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, - parentID: miniOverlay, - localPosition: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), - localRotation: MINI_UI_LOCAL_ROTATION, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_DIMENSIONS), - dpi: MINI_UI_DPI / MyAvatar.sensorToWorldScale, - alpha: 0, // Hide overlay while its content is being created. - grabbable: false, - showKeyboardFocusHighlight: false, - drawInFront: false, - visible: false - }); + miniOverlay = Entities.addEntity({ + "type": "Model", + "modelURL": MINI_MODEL, + "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_DIMENSIONS), + "primitiveMode": "solid", + "grab": { + "grabbable": true + }, + "renderLayer": "world", + "visible": false + }, "local"); + miniUIOverlay = Entities.addEntity({ + "type": "Web", + "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, + "parentID": miniOverlay, + "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), + "localRotation": MINI_UI_LOCAL_ROTATION, + "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_DIMENSIONS), + "dpi": MINI_UI_DPI / MyAvatar.sensorToWorldScale, + "alpha": 0, // Hide overlay while its content is being created. + "grab": { + "grabbable": false + }, + "showKeyboardFocusHighlight": false, + "renderLayer": "world", + "visible": false + }, "local"); miniUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. @@ -496,8 +506,8 @@ function destroy() { if (miniOverlayObject) { miniOverlayObject.webEventReceived.disconnect(onWebEventReceived); - Overlays.deleteOverlay(miniUIOverlay); - Overlays.deleteOverlay(miniOverlay); + Entities.deleteEntity(miniUIOverlay); + Entities.deleteEntity(miniOverlay); miniOverlayObject = null; miniUIOverlay = null; miniOverlay = null; @@ -915,9 +925,9 @@ ui.release(); miniTabletProperties = ui.getMiniTabletProperties(); - Overlays.editOverlay(HMD.tabletID, { - position: miniTabletProperties.position, - orientation: miniTabletProperties.orientation + Entities.editEntity(HMD.tabletID, { + "position": miniTabletProperties.position, + "orientation": miniTabletProperties.orientation }); HMD.openTablet(true); diff --git a/scripts/system/mod.js b/scripts/system/mod.js index a3b4974f8d5..71d2c32bac4 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -1,18 +1,17 @@ "use strict"; - // // mod.js // scripts/system/ // -// Created by Stephen Birarda on 07/11/2016 +// Created by Stephen Birarda on July 11th, 2016 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ - (function() { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/controllers.js"); @@ -48,7 +47,7 @@ function removeOverlays() { for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; for (var j = 0; j < modOverlays[avatarID].length; ++j) { - Overlays.deleteOverlay(modOverlays[avatarID][j]); + Entities.deleteEntity(modOverlays[avatarID][j]); } } @@ -107,44 +106,44 @@ function updateOverlays() { if (avatarID in modOverlays) { // keep the overlay above the current position of this avatar - Overlays.editOverlay(modOverlays[avatarID][0], { - position: kickOverlayPosition, - url: kickOverlayURL() + Entities.editEntity(modOverlays[avatarID][0], { + "position": kickOverlayPosition, + "imageURL": kickOverlayURL() }); if (Users.canKick) { - Overlays.editOverlay(modOverlays[avatarID][1], { - position: muteOverlayPosition, - url: muteOverlayURL() + Entities.editEntity(modOverlays[avatarID][1], { + "position": muteOverlayPosition, + "imageURL": muteOverlayURL() }); } } else { // add the overlay above this avatar - var newKickOverlay = Overlays.addOverlay("image3d", { - url: kickOverlayURL(), - position: kickOverlayPosition, - size: 1, - scale: 0.4, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - solid: true, - isFacingAvatar: true, - drawInFront: true - }); + var newKickOverlay = Entities.addEntity({ + "type": "Image", + "imageURL": kickOverlayURL(), + "position": kickOverlayPosition, + "dimensions": { "x": 0.4, "y": 0.4, "z": 0.4}, + "color": { "red": 255, "green": 255, "blue": 255}, + "alpha": 1, + "primitiveMode": "solid", + "billboardMode": "full", + "renderLayer": "front" + }, "local"); modOverlays[avatarID]=[newKickOverlay]; if (Users.canKick) { - var newMuteOverlay = Overlays.addOverlay("image3d", { - url: muteOverlayURL(), - position: muteOverlayPosition, - size: 1, - scale: 0.4, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - solid: true, - isFacingAvatar: true, - drawInFront: true - }); + var newMuteOverlay = Entities.addEntity({ + "type": "Image", + "imageURL": muteOverlayURL(), + "position": muteOverlayPosition, + "dimensions": { "x": 0.4, "y": 0.4, "z": 0.4}, + "color": { "red": 255, "green": 255, "blue": 255}, + "alpha": 1, + "primitiveMode": "solid", + "billboardMode": "full", + "renderLayer": "front" + }, "local"); // push this overlay to our array of overlays modOverlays[avatarID].push(newMuteOverlay); } @@ -161,7 +160,7 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ // first remove the rendered overlays for (var j = 0; j < modOverlays[avatarID].length; ++j) { - Overlays.deleteOverlay(modOverlays[avatarID][j]); + Entities.deleteEntity(modOverlays[avatarID][j]); } // delete the saved ID of the overlay from our mod overlays object diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 98198c8d3f5..34795ab487a 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -4,18 +4,20 @@ // // Created by Adrian McCarlie on October 8th, 2014 // Copyright 2014 High Fidelity, Inc. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Display notifications to the user for some specific events. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// (function () { Script.include([ "create/audioFeedback/audioFeedback.js" ]); - + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"; var SETTING_ACTIVATION_SNAPSHOT_NOTIFICATIONS = "snapshotNotifications"; var NOTIFICATION_LIFE_DURATION = 10000; //10 seconds (in millisecond) before expiration. @@ -87,7 +89,7 @@ if (remainingTime < FADE_OUT_DURATION) { alpha = NOTIFICATION_ALPHA * (remainingTime/FADE_OUT_DURATION); } - var properties, count, extraline, breaks, height; + var properties, count, extraLine, breaks, height; var breakPoint = MAX_LINE_LENGTH + 1; var level = overlayLocationY; var entityLevel = 0; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 4cf6ef1c34e..e4e76bbb818 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -1,19 +1,21 @@ "use strict"; -/* jslint vars:true, plusplus:true, forin:true */ -/* global Tablet, Settings, Script, AvatarList, Users, Entities, - MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, - UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation -*/ -/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js // -// Created by Howard Stearns on December 9, 2016 -// Copyright 2016 High Fidelity, Inc +// Created by Howard Stearns on December 9th, 2016 +// Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* jslint vars:true, plusplus:true, forin:true */ +/* global Tablet, Settings, Script, AvatarList, Users, Entities, + MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, + UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation +*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// (function () { // BEGIN LOCAL_SCOPE @@ -62,14 +64,15 @@ function angleBetweenVectorsInPlane(from, to, normal) { // var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. -function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. +function ExtendedOverlay(key, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. overlays[key] = this; if (hasModel) { var modelKey = key + "-m"; - this.model = new ExtendedOverlay(modelKey, "model", { - url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), - textures: textures(selected), - ignoreRayIntersection: true + this.model = new ExtendedOverlay(modelKey, { + "type": "Model", + "modelURL": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), + "textures": textures(selected), + "ignorePickIntersection": true }, false, false); } else { this.model = undefined; @@ -77,16 +80,16 @@ function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapp this.key = key; this.selected = selected || false; // not undefined this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... + this.activeOverlay = Entities.addEntity(properties, "local"); // We could use different overlays for (un)selected... } // Instance methods: ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); + Entities.deleteEntity(this.activeOverlay); delete overlays[this.key]; }; ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); + Entities.editEntity(this.activeOverlay, properties); }; function color(selected, hovering, level) { @@ -183,35 +186,37 @@ ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { // function HighlightedEntity(id, entityProperties) { this.id = id; - this.overlay = Overlays.addOverlay('cube', { - position: entityProperties.position, - rotation: entityProperties.rotation, - dimensions: entityProperties.dimensions, - solid: false, - color: { - red: 0xF3, - green: 0x91, - blue: 0x29 + this.overlay = Entities.addEntity({ + "type": "Shape", + "shape": "Cube", + "position": entityProperties.position, + "rotation": entityProperties.rotation, + "dimensions": entityProperties.dimensions, + "primitiveMode": "solid", + "color": { + "red": 0xF3, + "green": 0x91, + "blue": 0x29 }, - ignoreRayIntersection: true, - drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. - }); + "ignorePickIntersection": true, + "renderLayer": "world" // Arguable. For now, let's not distract with mysterious wires around the scene. + }, "local"); HighlightedEntity.overlays.push(this); } HighlightedEntity.overlays = []; HighlightedEntity.clearOverlays = function clearHighlightedEntities() { HighlightedEntity.overlays.forEach(function (highlighted) { - Overlays.deleteOverlay(highlighted.overlay); + Entities.deleteEntity(highlighted.overlay); }); HighlightedEntity.overlays = []; }; HighlightedEntity.updateOverlays = function updateHighlightedEntities() { HighlightedEntity.overlays.forEach(function (highlighted) { var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); - Overlays.editOverlay(highlighted.overlay, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions + Entities.editEntity(highlighted.overlay, { + "position": properties.position, + "rotation": properties.rotation, + "dimensions": properties.dimensions }); }); }; @@ -432,12 +437,14 @@ function getConnectionData(specificUsername, domain) { // Update all the usernam // function addAvatarNode(id) { var selected = ExtendedOverlay.isSelected(id); - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(selected, false, 0.0), - ignoreRayIntersection: false + return new ExtendedOverlay(id, { + "type": "Shape", + "shape": "Sphere", + "renderLayer": "front", + "primitiveMode": "solid", + "alpha": 0.8, + "color": color(selected, false, 0.0), + "ignorePickIntersection": false }, selected, true); } // Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter. diff --git a/scripts/system/places/places.js b/scripts/system/places/places.js index bfb111c4dae..9242acbeeab 100644 --- a/scripts/system/places/places.js +++ b/scripts/system/places/places.js @@ -3,35 +3,37 @@ // places.js // // Created by Alezia Kurdis, January 1st, 2022. -// Copyright 2022 Overte e.V. +// Copyright 2022-2023 Overte e.V. // // Generate an explore app based on the differents source of placename data. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // + (function() { var jsMainFileName = "places.js"; var ROOT = Script.resolvePath('').split(jsMainFileName)[0]; - + var metaverseServers = []; var SETTING_METAVERSE_TO_FETCH = "placesAppMetaverseToFetch"; var SETTING_PINNED_METAVERSE = "placesAppPinnedMetaverse"; var REQUEST_TIMEOUT = 10000; //10 seconds - + var httpRequest = null; var placesData; var portalList = []; var nbrPlacesNoProtocolMatch = 0; var nbrPlaceProtocolKnown = 0; - + var APP_NAME = "PLACES"; var APP_URL = ROOT + "places.html"; var APP_ICON_INACTIVE = ROOT + "icons/appicon_i.png"; var APP_ICON_ACTIVE = ROOT + "icons/appicon_a.png"; var appStatus = false; - var channel = "com.overte.places"; + var channel = "com.overte.places"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -72,6 +74,7 @@ var n = d.getTime(); var messageObj = JSON.parse(message); + print(message); if (messageObj.channel === channel) { if (messageObj.action === "READY_FOR_CONTENT" && (n - timestamp) > INTERCALL_DELAY) { d = new Date(); @@ -79,7 +82,7 @@ transmitPortalList(); sendCurrentLocationToUI(); - + } else if (messageObj.action === "TELEPORT" && (n - timestamp) > INTERCALL_DELAY) { d = new Date(); timestamp = d.getTime(); @@ -210,6 +213,7 @@ warning = "WARNING: " + percentProtocolRejected + "% of the places are not listed because they are running under a different protocol. Maybe consider to upgrade."; } + print(JSON.stringify(portalList)); var message = { "channel": channel, "action": "PLACE_DATA", @@ -504,13 +508,13 @@ } //####### seed random library ################ - Math.seed = 75; + var seed = 75; - Math.seededRandom = function(max, min) { + var seededRandom = function(max, min) { max = max || 1; min = min || 0; - Math.seed = (Math.seed * 9301 + 49297) % 233280; - var rnd = Math.seed / 233280; + seed = (seed * 9301 + 49297) % 233280; + var rnd = seed / 233280; return min + rnd * (max - min); } @@ -527,8 +531,8 @@ var d = new Date(); var n = d.getTime(); var currentSeed = Math.floor(n / PERSISTENCE_ORDERING_CYCLE); - Math.seed = score * currentSeed; - return zeroPad(Math.floor(Math.seededRandom() * 100000),5); + seed = score * currentSeed; + return zeroPad(Math.floor(seededRandom() * 100000),5); } //####### END of seed random library ################ diff --git a/scripts/system/redirectOverlays.js b/scripts/system/redirectOverlays.js index bb537bee0e7..5983dfe139e 100644 --- a/scripts/system/redirectOverlays.js +++ b/scripts/system/redirectOverlays.js @@ -1,4 +1,16 @@ "use strict"; +// +// redirectOverlays.js +// +// Created by Wayne Chen on September 25th, 2018 +// Copyright 2018 High Fidelity, Inc. +// Copyright 2023 Overte e.V. +// +// Overlays +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + (function() { var ERROR_MESSAGE_MAP = [ @@ -38,119 +50,140 @@ var oopsDimensions = {x: 4.2, y: 0.8}; - var redirectOopsText = Overlays.addOverlay("text3d", { - name: "oopsText", - position: {x: 0, y: 1.6763916015625, z: 1.45927095413208}, - rotation: {x: -4.57763671875e-05, y: 0.4957197904586792, z: -7.62939453125e-05, w: 0.8684672117233276}, - text: getOopsText(), - textAlpha: 1, - backgroundColor: {x: 0, y: 0, z:0}, - backgroundAlpha: 0, - lineHeight: 0.10, - leftMargin: 0.538373570564886, - visible: false, - emissive: true, - ignoreRayIntersection: true, - dimensions: oopsDimensions, - grabbable: false, - }); + var redirectOopsText = Entities.addEntity({ + "type": "Text", + "name": "oopsText", + "position": {"x": 0, "y": 1.6763916015625, "z": 1.45927095413208}, + "rotation": {"x": -4.57763671875e-05, "y": 0.4957197904586792, "z": -7.62939453125e-05, "w": 0.8684672117233276}, + "text": getOopsText(), + "textAlpha": 1, + "backgroundColor": {"x": 0, "y": 0, "z":0}, + "backgroundAlpha": 0, + "lineHeight": 0.10, + "leftMargin": 0.538373570564886, + "visible": false, + "unlit": true, + "ignorePickIntersection": true, + "dimensions": oopsDimensions, + "grab": { + "grabbable": false + } + }, "local"); - var tryAgainImageNeutral = Overlays.addOverlay("image3d", { - name: "tryAgainImage", - localPosition: {x: -0.6, y: -0.6, z: 0.0}, - url: Script.resourcesPath() + "images/interstitialPage/button.png", - alpha: 1, - visible: false, - emissive: true, - ignoreRayIntersection: false, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var tryAgainImageNeutral = Entities.addEntity({ + "type": "Image", + "name": "tryAgainImage", + "localPosition": {"x": -0.6, "y": -0.6, "z": 0.0}, + "imageURL": Script.resourcesPath() + "images/interstitialPage/button.png", + "alpha": 1, + "visible": false, + "emissive": true, + "ignorePickIntersection": false, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); - var tryAgainImageHover = Overlays.addOverlay("image3d", { - name: "tryAgainImageHover", - localPosition: {x: -0.6, y: -0.6, z: 0.0}, - url: Script.resourcesPath() + "images/interstitialPage/button_hover.png", - alpha: 1, - visible: false, - emissive: true, - ignoreRayIntersection: false, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var tryAgainImageHover = Entities.addEntity({ + "type": "Image", + "name": "tryAgainImageHover", + "localPosition": {"x": -0.6, "y": -0.6, "z": 0.0}, + "imageURL": Script.resourcesPath() + "images/interstitialPage/button_hover.png", + "alpha": 1, + "visible": false, + "emissive": true, + "ignorePickIntersection": false, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); - var tryAgainText = Overlays.addOverlay("text3d", { - name: "tryAgainText", - localPosition: {x: -0.6, y: -0.962, z: 0.0}, - text: "Try Again", - textAlpha: 1, - backgroundAlpha: 0.00393, - lineHeight: 0.08, - visible: false, - emissive: true, - ignoreRayIntersection: true, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var tryAgainText = Entities.addEntity({ + "type": "Text", + "name": "tryAgainText", + "localPosition": {"x": -0.6, "y": -0.962, "z": 0.0}, + "text": "Try Again", + "textAlpha": 1, + "backgroundAlpha": 0.00393, + "lineHeight": 0.08, + "visible": false, + "unlit": true, + "ignorePickIntersection": true, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); - var backImageNeutral = Overlays.addOverlay("image3d", { - name: "backImage", - localPosition: {x: 0.6, y: -0.6, z: 0.0}, - url: Script.resourcesPath() + "images/interstitialPage/button.png", - alpha: 1, - visible: false, - emissive: true, - ignoreRayIntersection: false, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var backImageNeutral = Entities.addEntity({ + "type": "Image", + "name": "backImage", + "localPosition": {"x": 0.6, "y": -0.6, "z": 0.0}, + "imageURL": Script.resourcesPath() + "images/interstitialPage/button.png", + "alpha": 1, + "visible": false, + "emissive": true, + "ignorePickIntersection": false, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); - var backImageHover = Overlays.addOverlay("image3d", { - name: "backImageHover", - localPosition: {x: 0.6, y: -0.6, z: 0.0}, - url: Script.resourcesPath() + "images/interstitialPage/button_hover.png", - alpha: 1, - visible: false, - emissive: true, - ignoreRayIntersection: false, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var backImageHover = Entities.addEntity({ + "type": "Image", + "name": "backImageHover", + "localPosition": {"x": 0.6, "y": -0.6, "z": 0.0}, + "imageURL": Script.resourcesPath() + "images/interstitialPage/button_hover.png", + "alpha": 1, + "visible": false, + "emissive": true, + "ignorePickIntersection": false, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); - var backText = Overlays.addOverlay("text3d", { - name: "backText", - localPosition: {x: 0.6, y: -0.962, z: 0.0}, - text: "Back", - textAlpha: 1, - backgroundAlpha: 0.00393, - lineHeight: 0.08, - visible: false, - emissive: true, - ignoreRayIntersection: true, - grabbable: false, - orientation: Overlays.getProperty(redirectOopsText, "orientation"), - parentID: redirectOopsText - }); + var backText = Entities.addEntity({ + "type": "Text", + "name": "backText", + "localPosition": {"x": 0.6, "y": -0.962, "z": 0.0}, + "text": "Back", + "textAlpha": 1, + "backgroundAlpha": 0.00393, + "lineHeight": 0.08, + "visible": false, + "unlit": true, + "ignorePickIntersection": true, + "grab": { + "grabbable": false + }, + "rotation": Entities.getEntityProperties(redirectOopsText, ["rotation"]).rotation, + "parentID": redirectOopsText + }, "local"); function toggleOverlays(isInErrorState) { isErrorState = isInErrorState; if (!isInErrorState) { var properties = { - visible: false + "visible": false }; - Overlays.editOverlay(redirectOopsText, properties); - Overlays.editOverlay(tryAgainImageNeutral, properties); - Overlays.editOverlay(tryAgainImageHover, properties); - Overlays.editOverlay(backImageNeutral, properties); - Overlays.editOverlay(backImageHover, properties); - Overlays.editOverlay(tryAgainText, properties); - Overlays.editOverlay(backText, properties); + Entities.editEntity(redirectOopsText, properties); + Entities.editEntity(tryAgainImageNeutral, properties); + Entities.editEntity(tryAgainImageHover, properties); + Entities.editEntity(backImageNeutral, properties); + Entities.editEntity(backImageHover, properties); + Entities.editEntity(tryAgainText, properties); + Entities.editEntity(backText, properties); return; } var oopsText = getOopsText(); @@ -159,44 +192,44 @@ // for catching init or if error state were to be different. isErrorState = overlaysVisible; var properties = { - visible: overlaysVisible + "visible": overlaysVisible }; - var textWidth = Overlays.textSize(redirectOopsText, oopsText).width; + var textWidth = Entities.textSize(redirectOopsText, oopsText).width; var textOverlayWidth = oopsDimensions.x; var oopsTextProperties = { - visible: overlaysVisible, - text: oopsText, - textAlpha: overlaysVisible, + "visible": overlaysVisible, + "text": oopsText, + "textAlpha": overlaysVisible, // either visible or invisible. 0 doesn't work in Mac. - backgroundAlpha: overlaysVisible * 0.00393, - leftMargin: (textOverlayWidth - textWidth) / 2 + "backgroundAlpha": overlaysVisible * 0.00393, + "leftMargin": (textOverlayWidth - textWidth) / 2 }; - var tryAgainTextWidth = Overlays.textSize(tryAgainText, "Try Again").width; - var tryAgainImageWidth = Overlays.getProperty(tryAgainImageNeutral, "dimensions").x; + var tryAgainTextWidth = Entities.textSize(tryAgainText, "Try Again").width; + var tryAgainImageWidth = Entities.getEntityProperties(tryAgainImageNeutral, ["dimensions"]).dimensions.x; var tryAgainTextProperties = { - visible: overlaysVisible, - leftMargin: (tryAgainImageWidth - tryAgainTextWidth) / 2 + "visible": overlaysVisible, + "leftMargin": (tryAgainImageWidth - tryAgainTextWidth) / 2 }; - var backTextWidth = Overlays.textSize(backText, "Back").width; - var backImageWidth = Overlays.getProperty(backImageNeutral, "dimensions").x; + var backTextWidth = Entities.textSize(backText, "Back").width; + var backImageWidth = Entities.getEntityProperties(backImageNeutral, ["dimensions"]).dimensions.x; var backTextProperties = { - visible: overlaysVisible, - leftMargin: (backImageWidth - backTextWidth) / 2 + "visible": overlaysVisible, + "leftMargin": (backImageWidth - backTextWidth) / 2 }; - Overlays.editOverlay(redirectOopsText, oopsTextProperties); - Overlays.editOverlay(tryAgainImageNeutral, properties); - Overlays.editOverlay(backImageNeutral, properties); - Overlays.editOverlay(tryAgainImageHover, {visible: false}); - Overlays.editOverlay(backImageHover, {visible: false}); - Overlays.editOverlay(tryAgainText, tryAgainTextProperties); - Overlays.editOverlay(backText, backTextProperties); + Entities.editEntity(redirectOopsText, oopsTextProperties); + Entities.editEntity(tryAgainImageNeutral, properties); + Entities.editEntity(backImageNeutral, properties); + Entities.editEntity(tryAgainImageHover, {"visible": false}); + Entities.editEntity(backImageHover, {"visible": false}); + Entities.editEntity(tryAgainText, tryAgainTextProperties); + Entities.editEntity(backText, backTextProperties); } @@ -215,13 +248,13 @@ function cleanup() { Script.clearInterval(timer); timer = null; - Overlays.deleteOverlay(redirectOopsText); - Overlays.deleteOverlay(tryAgainImageNeutral); - Overlays.deleteOverlay(backImageNeutral); - Overlays.deleteOverlay(tryAgainImageHover); - Overlays.deleteOverlay(backImageHover); - Overlays.deleteOverlay(tryAgainText); - Overlays.deleteOverlay(backText); + Entities.deleteEntity(redirectOopsText); + Entities.deleteEntity(tryAgainImageNeutral); + Entities.deleteEntity(backImageNeutral); + Entities.deleteEntity(tryAgainImageHover); + Entities.deleteEntity(backImageHover); + Entities.deleteEntity(tryAgainText); + Entities.deleteEntity(backText); } toggleOverlays(true); @@ -233,12 +266,12 @@ return; } if (overlayID === backImageNeutral && location.canGoBack()) { - Overlays.editOverlay(backImageNeutral, {visible: false}); - Overlays.editOverlay(backImageHover, {visible: true}); + Entities.editEntity(backImageNeutral, {"visible": false}); + Entities.editEntity(backImageHover, {"visible": true}); } if (overlayID === tryAgainImageNeutral) { - Overlays.editOverlay(tryAgainImageNeutral, {visible: false}); - Overlays.editOverlay(tryAgainImageHover, {visible: true}); + Entities.editEntity(tryAgainImageNeutral, {"visible": false}); + Entities.editEntity(tryAgainImageHover, {"visible": true}); } }); @@ -248,12 +281,12 @@ return; } if (overlayID === backImageHover) { - Overlays.editOverlay(backImageHover, {visible: false}); - Overlays.editOverlay(backImageNeutral, {visible: true}); + Entities.editEntity(backImageHover, {"visible": false}); + Entities.editEntity(backImageNeutral, {"visible": true}); } if (overlayID === tryAgainImageHover) { - Overlays.editOverlay(tryAgainImageHover, {visible: false}); - Overlays.editOverlay(tryAgainImageNeutral, {visible: true}); + Entities.editEntity(tryAgainImageHover, {"visible": false}); + Entities.editEntity(tryAgainImageNeutral, {"visible": true}); } }); diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js index ee6efc618ce..762bcfb3dd0 100644 --- a/scripts/system/request-service.js +++ b/scripts/system/request-service.js @@ -1,20 +1,22 @@ "use strict"; // -// request-service.js +// request-service.js // -// Created by Howard Stearns on May 22, 2018 -// Copyright 2018 High Fidelity, Inc +// Created by Howard Stearns on May 22, 2018 +// Copyright 2018 High Fidelity, Inc +// Copyright 2023 Overte e.V. // -// Distributed under the Apache License, Version 2.0 -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // (function() { // BEGIN LOCAL_SCOPE // QML has its own XMLHttpRequest, but: // - npm request is easier to use. - // - It is not easy to hack QML's XMLHttpRequest to use our Directory Services, and to supply the user's auth when contacting it. - // a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript. + // - 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 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/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index a38febaa77a..1957ecea4d9 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -7,6 +7,7 @@ // // Created by Seth Alves 2016-9-29 // Copyright 2016 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -50,14 +51,14 @@ if (!UIWebTablet) { return false; } - if (Overlays.getProperty(HMD.tabletID, "type") !== "model") { + if (Entities.getEntityProperties(HMD.tabletID, ["type"]).type !== "Model") { if (debugTablet) { - print("TABLET is invalid due to frame: " + JSON.stringify(Overlays.getProperty(HMD.tabletID, "type"))); + print("TABLET is invalid due to frame: " + JSON.stringify(Entities.getEntityProperties(HMD.tabletID, ["type"]).type)); } return false; } - if (Overlays.getProperty(HMD.homeButtonID, "type") !== "circle3d" || - Overlays.getProperty(HMD.tabletScreenID, "type") !== "web3d") { + if (Entities.getEntityProperties(HMD.homeButtonID, ["type"]).type !== "Gizmo" || + Entities.getEntityProperties(HMD.tabletScreenID, ["type"]).type !== "Web") { if (debugTablet) { print("TABLET is invalid due to other"); } @@ -138,10 +139,10 @@ } tabletProperties.visible = true; tabletProperties.ignorePickIntersection = false; - Overlays.editOverlay(HMD.tabletID, tabletProperties); - Overlays.editOverlay(HMD.homeButtonID, { visible: true, ignorePickIntersection: false }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: true, ignorePickIntersection: false }); - Overlays.editOverlay(HMD.tabletScreenID, { visible: true, ignorePickIntersection: false, maxFPS: 90 }); + Entities.editEntity(HMD.tabletID, tabletProperties); + Entities.editEntity(HMD.homeButtonID, { "visible": true, "ignorePickIntersection": false }); + Entities.editEntity(HMD.homeButtonHighlightID, { "visible": true, "ignorePickIntersection": false }); + Entities.editEntity(HMD.tabletScreenID, { "visible": true, "ignorePickIntersection": false, "maxFPS": 90 }); updateTabletWidthFromSettings(true); } gTablet.tabletShown = true; @@ -158,10 +159,10 @@ print("TABLET hide"); } - Overlays.editOverlay(HMD.tabletID, { visible: false, ignorePickIntersection: true }); - Overlays.editOverlay(HMD.homeButtonID, { visible: false, ignorePickIntersection: true }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: false, ignorePickIntersection: true }); - Overlays.editOverlay(HMD.tabletScreenID, { visible: false, ignorePickIntersection: true, maxFPS: 1 }); + Entities.editEntity(HMD.tabletID, { "visible": false, "ignorePickIntersection": true }); + Entities.editEntity(HMD.homeButtonID, { "visible": false, "ignorePickIntersection": true }); + Entities.editEntity(HMD.homeButtonHighlightID, { "visible": false, "ignorePickIntersection": true }); + Entities.editEntity(HMD.tabletScreenID, { "visible": false, "ignorePickIntersection": true, "maxFPS": 1 }); } function closeTabletUI() { @@ -249,20 +250,20 @@ tabletShown = false; // also cause the stylus model to be loaded - var tmpStylusID = Overlays.addOverlay("model", { - name: "stylus", - url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", - loadPriority: 10.0, - position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})), - dimensions: { x: 0.01, y: 0.01, z: 0.2 }, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false, - lifetime: 3 - }); + var tmpStylusID = Entities.addEntity({ + "type": "Model", + "name": "stylus", + "modelURL": Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", + "position": Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {"x": 0, "y": 0.1, "z": -2})), + "dimensions": { "x": 0.01, "y": 0.01, "z": 0.2 }, + "primitiveMode": "solid", + "visible": true, + "ignorePickIntersection": true, + "renderLayer": "world", + "lifetime": 3 + }, "local"); Script.setTimeout(function() { - Overlays.deleteOverlay(tmpStylusID); + Entities.deleteEntity(tmpStylusID); }, 300); } else if (!tabletShown) { hideTabletUI(); @@ -330,7 +331,6 @@ var tabletID = HMD.tabletID; Entities.deleteEntity(tabletID); - Overlays.deleteOverlay(tabletID); HMD.tabletID = null; HMD.homeButtonID = null; HMD.homeButtonHighlightID = null; diff --git a/tests-manual/controllers/CMakeLists.txt b/tests-manual/controllers/CMakeLists.txt index 932826c8de1..d7379577428 100644 --- a/tests-manual/controllers/CMakeLists.txt +++ b/tests-manual/controllers/CMakeLists.txt @@ -1,9 +1,13 @@ -# FIXME Disabling test on OSX because of unexpected link error +# Copyright 2015-2018, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# FIXME Disabling test on OSX because of unexpected link error 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..31ec9776069 100644 --- a/tests-manual/entities/CMakeLists.txt +++ b/tests-manual/entities/CMakeLists.txt @@ -1,8 +1,11 @@ +# Copyright 2015-2018, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 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..27eb893c079 100644 --- a/tests-manual/gpu-textures/CMakeLists.txt +++ b/tests-manual/gpu-textures/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2015-2018, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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..aa4e4d497b2 100644 --- a/tests-manual/gpu/CMakeLists.txt +++ b/tests-manual/gpu/CMakeLists.txt @@ -1,6 +1,10 @@ +# Copyright 2015-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 227914c2baf..399f0e77734 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -1,7 +1,11 @@ +# Copyright 2015-2019, High Fidelity, Inc. +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared animation gpu hfm model-serializers graphics networking test-utils image) + link_hifi_libraries(shared animation gpu hfm model-serializers graphics networking test-utils image script-engine) package_libraries_for_deployment() endmacro () diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt index 41dec1dcd20..0925a390664 100644 --- a/tests/audio/CMakeLists.txt +++ b/tests/audio/CMakeLists.txt @@ -1,7 +1,11 @@ +# Copyright 2015, High Fidelity, Inc. +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + # Declare dependencies macro (SETUP_TESTCASE_DEPENDENCIES) # link in the shared libraries - link_hifi_libraries(shared audio audio-client plugins networking) + link_hifi_libraries(shared audio audio-client plugins networking script-engine) package_libraries_for_deployment() endmacro () diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt index 86fe6c4c31c..be6df7739eb 100644 --- a/tests/octree/CMakeLists.txt +++ b/tests/octree/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright 2014-2018, High Fidelity, Inc. +# Copyright 2022-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 # Declare dependencies macro (setup_testcase_dependencies) @@ -7,4 +10,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..ef89394776c 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright 2014-2018, High Fidelity, Inc. +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 # Declare dependencies macro (SETUP_TESTCASE_DEPENDENCIES) @@ -6,4 +9,4 @@ macro (SETUP_TESTCASE_DEPENDENCIES) package_libraries_for_deployment() endmacro () -setup_hifi_testcase(Script) +setup_hifi_testcase() diff --git a/tests/script-engine/CMakeLists.txt b/tests/script-engine/CMakeLists.txt new file mode 100644 index 00000000000..1e4428239c8 --- /dev/null +++ b/tests/script-engine/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# Declare dependencies +macro (setup_testcase_dependencies) + # V8TODO: replace most link_hifi_libraries with include_hifi_library_headers + # link in the shared libraries + link_hifi_libraries(shared test-utils script-engine networking octree avatars entities model-networking material-networking model-serializers graphics gpu ktx shaders hfm image procedural) + + package_libraries_for_deployment() +endmacro () + +setup_hifi_testcase(Network) diff --git a/tests/script-engine/src/ScriptEngineTests.cpp b/tests/script-engine/src/ScriptEngineTests.cpp new file mode 100644 index 00000000000..8ad353f8464 --- /dev/null +++ b/tests/script-engine/src/ScriptEngineTests.cpp @@ -0,0 +1,335 @@ +// +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include +#include + + +#include "ScriptEngineTests.h" +#include "DependencyManager.h" + +#include "ScriptEngines.h" +#include "ScriptEngine.h" +#include "ScriptCache.h" +#include "ScriptManager.h" + +#include "ResourceManager.h" +#include "ResourceRequestObserver.h" +#include "StatTracker.h" + +#include "NodeList.h" +#include "../../../libraries/entities/src/EntityScriptingInterface.h" +//#include "../../../libraries/entities/src/EntityScriptingInterface.h" + +QTEST_MAIN(ScriptEngineTests) + + + + + + +void ScriptEngineTests::initTestCase() { + // AudioClient starts networking, but for the purposes of the tests here we don't care, + // so just got to use some port. + //int listenPort = 10000; + + //DependencyManager::registerInheritance(); + //DependencyManager::set(NodeType::Agent, listenPort); + DependencyManager::set(ScriptManager::NETWORKLESS_TEST_SCRIPT, QUrl("")); + DependencyManager::set(); + // DependencyManager::set(); + // DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + // DependencyManager::set(true); + + +} + +ScriptManagerPointer ScriptEngineTests::makeManager(const QString &scriptSource, const QString &scriptFilename) { + ScriptManagerPointer sm = newScriptManager(ScriptManager::NETWORKLESS_TEST_SCRIPT, scriptSource, scriptFilename); + + + sm->setAbortOnUncaughtException(true); + + connect(sm.get(), &ScriptManager::scriptLoaded, [](const QString& filename){ + qWarning() << "Loaded script" << filename; + }); + + + connect(sm.get(), &ScriptManager::errorLoadingScript, [](const QString& filename){ + qWarning() << "Failed to load script" << filename; + }); + + connect(sm.get(), &ScriptManager::printedMessage, [](const QString& message, const QString& engineName){ + qDebug() << "Printed message from engine" << engineName << ": " << message; + }); + + connect(sm.get(), &ScriptManager::infoMessage, [](const QString& message, const QString& engineName){ + qInfo() << "Info message from engine" << engineName << ": " << message; + }); + + connect(sm.get(), &ScriptManager::warningMessage, [](const QString& message, const QString& engineName){ + qWarning() << "Warning from engine" << engineName << ": " << message; + }); + + connect(sm.get(), &ScriptManager::errorMessage, [](const QString& message, const QString& engineName){ + qCritical() << "Error from engine" << engineName << ": " << message; + }); + + connect(sm.get(), &ScriptManager::finished, [](const QString& fileNameString, ScriptManagerPointer smp){ + qInfo() << "Finished running script" << fileNameString; + }); + + connect(sm.get(), &ScriptManager::runningStateChanged, [sm](){ + qInfo() << "Running state changed. Running = " << sm->isRunning() << "; Stopped = " << sm->isStopped() << "; Finished = " << sm->isFinished(); + }); + + connect(sm.get(), &ScriptManager::unhandledException, [](std::shared_ptr exception){ + qWarning() << "Exception from engine: " << exception; + }); + + + return sm; +} + +void ScriptEngineTests::testTrivial() { + auto sm = makeManager("print(\"script works!\"); Script.stop(true);", "testTrivial.js"); + QString printed; + + QVERIFY(!sm->isRunning()); + QVERIFY(!sm->isStopped()); + QVERIFY(!sm->isFinished()); + + + connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){ + printed.append(message); + }); + + + sm->run(); + + QVERIFY(!sm->isRunning()); + QVERIFY(!sm->isStopped()); + QVERIFY(sm->isFinished()); + QVERIFY(printed == "script works!"); + +} + +void ScriptEngineTests::testSyntaxError() { + auto sm = makeManager("this is not good syntax", "testSyntaxError.js"); + bool exceptionHappened = false; + + connect(sm.get(), &ScriptManager::unhandledException, [&exceptionHappened](std::shared_ptr exception){ + exceptionHappened = true; + }); + + + sm->run(); + + std::shared_ptr ex = sm->getUncaughtException(); + + qDebug() << "Exception:" << ex; + + QVERIFY(exceptionHappened); + QVERIFY(ex); + QVERIFY(ex && ex->errorMessage.contains("SyntaxError")); +} + + +void ScriptEngineTests::testRuntimeError() { + auto sm = makeManager("nonexisting();", "testRuntimeError.js"); + bool exceptionHappened = false; + + connect(sm.get(), &ScriptManager::unhandledException, [&exceptionHappened](std::shared_ptr exception){ + exceptionHappened = true; + }); + + + sm->run(); + + std::shared_ptr ex = sm->getUncaughtException(); + + qDebug() << "Exception:" << ex; + + QVERIFY(exceptionHappened); + QVERIFY(ex); + QVERIFY(ex && ex->errorMessage.contains("ReferenceError")); + +} + +void ScriptEngineTests::testJSThrow() { + auto sm = makeManager("throw(42);", "testThrow.js"); + sm->run(); + + std::shared_ptr ex = sm->getUncaughtException(); + + qDebug() << "Exception:" << ex; + + auto runtime_ex = std::dynamic_pointer_cast(ex); + + QVERIFY(ex); + QVERIFY(runtime_ex); + QVERIFY(runtime_ex && runtime_ex->thrownValue.toInt32() == 42); +} + +void ScriptEngineTests::testRegisterClass() { + QString printed; + auto sm = makeManager("print(testClass.invokableFunc(4)); Script.stop(true);", "testClass.js"); + connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){ + printed.append(message); + }); + + sm->engine()->registerGlobalObject("testClass", new TestClass()); + + sm->run(); + + auto ex = sm->getUncaughtException(); + + QVERIFY(!ex); + QVERIFY(printed == "14"); + +} + +void ScriptEngineTests::testInvokeNonInvokable() { + auto sm = makeManager("print(testClass.nonInvokableFunc(4)); Script.stop(true);", "testClass.js"); + sm->engine()->registerGlobalObject("testClass", new TestClass()); + + sm->run(); + auto ex = sm->getUncaughtException(); + + QVERIFY(ex); + QVERIFY(ex && ex->errorMessage.contains("TypeError")); +} + +void ScriptEngineTests::testRaiseException() { + auto sm = makeManager("testClass.doRaiseTest(); Script.stop(true);", "testRaise.js"); + sm->engine()->registerGlobalObject("testClass", new TestClass(sm->engine())); + + sm->run(); + auto ex = sm->getUncaughtException(); + + QVERIFY(ex); + QVERIFY(ex && ex->errorMessage.contains("Exception test")); +} + +void ScriptEngineTests::testRaiseExceptionAndCatch() { + QString script = + "try {" + " testClass.doRaiseTest();" + "} catch (err) {" + " if (err === \"Exception test!\") {" + " print(\"Caught!\");" + " }" + "}" + "Script.stop(true);"; + + QString printed; + auto sm = makeManager(script, "testRaiseCatch.js"); + + connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){ + printed.append(message); + }); + + + sm->engine()->registerGlobalObject("testClass", new TestClass(sm->engine())); + + sm->run(); + auto ex = sm->getUncaughtException(); + + QVERIFY(!ex); + QVERIFY(printed == "Caught!"); +} + + +void ScriptEngineTests::testSignal() { + QString script = + "var count = 0;" + "Script.update.connect(function(deltaTime) {" + " count++;" + " print(deltaTime);" + " if (count >= 10) {" + " Script.stop(true);" + " }" + "});"; + + QStringList printed; + auto sm = makeManager(script, "testSignal.js"); + + connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){ + printed.append(message); + }); + + sm->run(); + QVERIFY(printed.length() >= 10); +} + +void ScriptEngineTests::testSignalWithException() { + QString script = + "var count = 0;" + "Script.update.connect(function(deltaTime) {" + " count++;" + " print(deltaTime);" + " if (count >= 3) {" + " Script.stop(true);" + " }" + " nonExist();" + "});"; + + QStringList printed; + int exceptionCount = 0; + + auto sm = makeManager(script, "testSignalWithException.js"); + + connect(sm.get(), &ScriptManager::printedMessage, [&printed](const QString& message, const QString& engineName){ + printed.append(message); + }); + + connect(sm.get(), &ScriptManager::unhandledException, [&exceptionCount](std::shared_ptr exception){ + exceptionCount++; + }); + + + sm->run(); + QVERIFY(printed.length() >= 3); + QVERIFY(exceptionCount >= 3); +} + +void ScriptEngineTests::testQuat() { + QString script = + "var x;\n" + "print(JSON.stringify(Quat.IDENTITY));\n" + "print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY)));\n" + "print(JSON.stringify(Quat.getUp(Quat.IDENTITY)));\n" + "print(JSON.stringify(Quat.getUp(x)));\n" + "Script.stop(true);\n"; + + int printCount = 0; + QStringList answers{ + "{\"x\":0,\"y\":0,\"z\":0,\"w\":1}", + "{\"x\":0,\"y\":0,\"z\":0}", + "{\"x\":0,\"y\":1,\"z\":0}", + "" + }; + + auto sm = makeManager(script, "testQuat.js"); + + connect(sm.get(), &ScriptManager::printedMessage, [&printCount, answers](const QString& message, const QString& engineName){ + QCOMPARE(message, answers[printCount++]); + }); + + connect(sm.get(), &ScriptManager::unhandledException, [](std::shared_ptr exception){ + QVERIFY(exception->errorMessage.contains("undefined to glm::quat")); + }); + + + sm->run(); +} + diff --git a/tests/script-engine/src/ScriptEngineTests.h b/tests/script-engine/src/ScriptEngineTests.h new file mode 100644 index 00000000000..f20d6d0a7ec --- /dev/null +++ b/tests/script-engine/src/ScriptEngineTests.h @@ -0,0 +1,75 @@ +// +// SciptEngineTests.h +// tests/script-engine/src +// +// Created by Dale Glass +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef overte_ScriptingEngineTests_h +#define overte_ScriptingEngineTests_h + +#include +#include "ScriptManager.h" +#include "ScriptEngine.h" + +using ScriptManagerPointer = std::shared_ptr; + + +class TestClass : public QObject { + Q_OBJECT + + public: + TestClass() {}; + + TestClass(ScriptEnginePointer ptr) : _engine(ptr) {}; + + Q_INVOKABLE int invokableFunc(int val) { + qDebug() << "invokableFunc called with value" << val; + return val + 10; + } + + Q_INVOKABLE void doRaiseTest() { + qDebug() << "About to raise an exception"; + _engine->raiseException("Exception test!"); + } + + + int nonInvokableFunc(int val) { + qCritical() << "nonInvokableFunc called with value" << val; + return val + 20; + } + + private: + ScriptEnginePointer _engine; + +}; + + +class ScriptEngineTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void testTrivial(); + void testSyntaxError(); + void testRuntimeError(); + void testJSThrow(); + void testRegisterClass(); + void testInvokeNonInvokable(); + void testRaiseException(); + void testRaiseExceptionAndCatch(); + void testSignal(); + void testSignalWithException(); + void testQuat(); + + +private: + ScriptManagerPointer makeManager(const QString &source, const QString &filename); + +}; + +#endif // overte_ScriptingEngineTests_h diff --git a/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 b/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 index 65b268311d6..a80207471e8 100644 --- a/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 +++ b/tools/ci-scripts/linux-ci/Dockerfile_build_ubuntu-22.04 @@ -15,7 +15,7 @@ RUN echo UTC >/etc/timezone RUN apt-get update && apt-get -y install tzdata # Install Overte domain-server and assignment-client build dependencies -RUN apt-get -y install curl ninja-build git cmake g++ libssl-dev libqt5websockets5-dev qtscript5-dev qtdeclarative5-dev qtmultimedia5-dev python3-distutils python3-distro mesa-common-dev libgl1-mesa-dev libsystem-dev +RUN apt-get -y install curl ninja-build git cmake g++ libssl-dev libqt5websockets5-dev qtscript5-dev qtdeclarative5-dev qtmultimedia5-dev python3-distutils python3-distro mesa-common-dev libgl1-mesa-dev libsystemd-dev # Install Overte tools build dependencies RUN apt-get -y install libqt5webchannel5-dev qtwebengine5-dev libqt5xmlpatterns5-dev # Install Overte Interface build dependencies diff --git a/tools/doxygen/Doxyfile b/tools/doxygen/Doxyfile index 90ddb030bb9..cb150954c78 100644 --- a/tools/doxygen/Doxyfile +++ b/tools/doxygen/Doxyfile @@ -1,3 +1,10 @@ +// +// Copyright © 1997-2022 by Dimitri van Heesch +// Copyright 2023 Overte e.V. +// +// SPDX-License-Identifier: GPL-2.0-only +// + # Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system @@ -973,7 +980,12 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = + +# The ScriptingInterface files are a JS interface, and documented in JSDoc +# instead of Doxygen. + +EXCLUDE_PATTERNS = *ScriptingInterface.h \ + *ScriptingInterface.cpp # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -2354,7 +2366,7 @@ DIA_PATH = # and usage relations if the target is undocumented or is not a class. # The default value is: YES. -HIDE_UNDOC_RELATIONS = YES +HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: @@ -2363,7 +2375,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = NO +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2545,7 +2557,7 @@ DIRECTORY_GRAPH = YES # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. @@ -2557,7 +2569,7 @@ DOT_IMAGE_FORMAT = png # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -INTERACTIVE_SVG = NO +INTERACTIVE_SVG = YES # The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. diff --git a/tools/node-builder/Dockerfile_Ubuntu_18.04_node b/tools/node-builder/Dockerfile_Ubuntu_18.04_node new file mode 100644 index 00000000000..e3ab1cd78b0 --- /dev/null +++ b/tools/node-builder/Dockerfile_Ubuntu_18.04_node @@ -0,0 +1,57 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# Docker file for building Overte Node packages for Ubuntu 18.04 + +# Some steps for actually using this: +# - Adjust this file to include the number of threads you want to use (-j10), and the Node version. +# Keep in mind that building Node requires a lot of memory. You should have over 1.2GiB of system memory available per thread. +# - Run the build process with something like `PROGRESS_NO_TRUNC=1 DOCKER_BUILDKIT=1 BUILDKIT_STEP_LOG_MAX_SIZE=-1 docker build --progress plain -t overte-node:node-18.14.2 -f Dockerfile_Ubuntu_18.04_node .` +# Buildkit is used to cache intermittent steps in case you need to modify something afterwards. +# - Once the build has completed, create a container from the image and export the created Node package. +# `docker create --name extract overte-node:node-18.14.2` +# `docker cp extract:node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz /path/on/host` +# `docker rm extract` + +FROM ubuntu:18.04 +LABEL maintainer="Julian Groß (julian.gro@overte.org)" +LABEL description="Development image for Overte Node packages for Ubuntu 18.04." + +# Don't use any frontend when installalling packages during the creation of this container +ARG DEBIAN_FRONTEND=noninteractive + +RUN echo UTC >/etc/timezone + +# Enable source repositories for apt +RUN sed -Ei 's/^# deb-src /deb-src /' /etc/apt/sources.list + +# Update package list +RUN apt-get update + +# Upgrade packages +RUN apt-get -y upgrade + +RUN apt-get -y install git + +# Clone and enter single branch of Node +RUN git clone --recursive https://github.com/nodejs/node.git -b v18.14.2 --single-branch + + +# Install node system build dependencies +RUN apt-get -y build-dep nodejs + + +RUN mkdir node-install +WORKDIR node +# --gdb currently doesn't build on aarch64 +# Ubuntu 18.04 has a too old compiler to build on aarch64 +#RUN ./configure --gdb --shared --prefix=../node-install/ +RUN ./configure --gdb --shared --debug --debug-lib --v8-with-dchecks --v8-enable-object-print --prefix=../node-install/ + +RUN make -j10 +RUN make -j10 install + +RUN cp config.status ../node-install/ + +WORKDIR .. +RUN XZ_OPT='-T0' tar -Jcvf node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz node-install diff --git a/tools/node-builder/Dockerfile_Ubuntu_20.04_aarch64_node b/tools/node-builder/Dockerfile_Ubuntu_20.04_aarch64_node new file mode 100644 index 00000000000..111851fbfee --- /dev/null +++ b/tools/node-builder/Dockerfile_Ubuntu_20.04_aarch64_node @@ -0,0 +1,57 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# Docker file for building Overte Node packages for Ubuntu 20.04 + +# Some steps for actually using this: +# - Adjust this file to include the number of threads you want to use (-j10), and the Node version. +# Keep in mind that building Node requires a lot of memory. You should have over 1.2GiB of system memory available per thread. +# - Run the build process with something like `PROGRESS_NO_TRUNC=1 DOCKER_BUILDKIT=1 BUILDKIT_STEP_LOG_MAX_SIZE=-1 docker build --progress plain -t overte-node:node-18.16.0 -f Dockerfile_Ubuntu_20.04_aarch64_node .` +# Buildkit is used to cache intermittent steps in case you need to modify something afterwards. +# - Once the build has completed, create a container from the image and export the created Node package. +# `docker create --name extract overte-node:node-18.16.0` +# `docker cp extract:node-install-18.16.0-ubuntu-20.04-aarch64-release.tar.xz /path/on/host` +# `docker rm extract` + +FROM ubuntu:20.04 +LABEL maintainer="Julian Groß (julian.gro@overte.org)" +LABEL description="Development image for Overte Node packages for Ubuntu 20.04." + +# Don't use any frontend when installalling packages during the creation of this container +ARG DEBIAN_FRONTEND=noninteractive + +RUN echo UTC >/etc/timezone + +# Enable source repositories for apt +RUN sed -Ei 's/^# deb-src /deb-src /' /etc/apt/sources.list + +# Update package list +RUN apt-get update + +# Upgrade packages +RUN apt-get -y upgrade + +RUN apt-get -y install git + +# Clone and enter single branch of Node +RUN git clone --recursive https://github.com/nodejs/node.git -b v18.16.0 --single-branch + + +# Install node system build dependencies +RUN apt-get -y build-dep nodejs + + +RUN mkdir node-install +WORKDIR node +# --gdb currently doesn't build on aarch64 +# Ubuntu 18.04 has a too old compiler to build on aarch64 +#RUN ./configure --gdb --shared --prefix=../node-install/ +RUN ./configure --shared --debug --debug-lib --v8-with-dchecks --v8-enable-object-print --prefix=../node-install/ + +RUN make -j16 +RUN make -j16 install + +RUN cp config.status ../node-install/ + +WORKDIR .. +RUN XZ_OPT='-T0' tar -Jcvf node-install-18.16.0-ubuntu-20.04-aarch64-release.tar.xz node-install diff --git a/tools/node-builder/README.md b/tools/node-builder/README.md new file mode 100644 index 00000000000..a8cb97e2851 --- /dev/null +++ b/tools/node-builder/README.md @@ -0,0 +1,121 @@ +# Copyright 2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + +# General +This document describes the process to build Node 18.14.2 for usage as scripting engine.. + +Reference: https://github.com/nodejs/node/blob/main/BUILDING.md + +## Requirements +### Windows +1. git +2. nasm-2.16.01-installer-x64.exe + +### Linux +1. git + +### Mac +TODO + +## Build Process + + +### General +The build is performed in source. +Build products are installed to the node-install folder. +Before running configure, make sure that the node-build folder is empty. + + +### Windows +Make sure that the directory you are using to build Node is not deeply nested. It is quite possible to run into the windows MAX_PATH limit when building Node. For example: `c:\msys64\home\ajt\code\hifi\tools\node-builder\node-build` is too long. `c:\n\node-build\` is a better choice. + +Build: +'.\vcbuild.bat release package dll nonpm' + +Rename the installation directory to 'node-build' + +After building copy 'deps\v8\include\cppgc' and 'deps\v8\include\libplatform' to 'node-build\include'. +Copy 'libnode.lib' and 'v8_libplatform.lib' to 'node-build' + +#### Preparing source files +Get the source: +`git clone --recursive https://github.com/nodejs/node.git -b v18.14.2 --single-branch` + +#### Configuring +TODO + +#### Make +TODO + +#### Uploading + +Create a xz tar file called node-install-18.14.2-windows-release.tar.gz from the node-install folder. + +Using 7-Zip: +* `cd` to the *qt5* folder. +* `7z a -ttar qnode-install-18.14.2-windows-release.tar node-install` +* `7z a -txz node-install-18.14.2-windows-release.tar.xz node-install-18.14.2-windows-release.tar` + +Upload node-install-18.14.2-windows.tar.xz to build-deps.overte.org, under the dependencies/node directory. + + +### Linux +#### Preparing source files +```bash +git clone --recursive https://github.com/nodejs/node.git -b v18.14.2 --single-branch +``` + +#### Configuring +```bash +mkdir node-install +``` + +release: +```bash +cd node +./configure --gdb --shared --prefix=../node-install/ +``` + +debug: +```bash +cd node +./configure --gdb --shared --debug --debug-lib --v8-with-dchecks --v8-enable-object-print --prefix=../node-install/ +``` + +#### Make +Replace `4` with the amount of threads you want to use. Keep in mind that the Node build process uses a lot of memory. It is recommended to have at least 1.2 GiB per thread. +```bash +make -j4 +``` + +Now Node can be installed to node-install: +```bash +make -j4 install +``` + +#### Uploading +1. Tar and xz node-install to create the package. Replace `ubuntu-18.04` with the relevant system and `amd64` with the relevant architecture. +```bash +tar -Jcvf node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz node-install +``` +2. Upload node-install-18.14.2-ubuntu-18.04-amd64-release.tar.xz to https://build-deps.overte.org/dependencies/node/ + + + +### Mac +#### Preparing source files +```bash +git clone --recursive https://github.com/nodejs/node.git -b v18.14.2 --single-branch +``` + +#### Configuring +TODO + +#### Make +TODO + +#### Uploading +```bash +tar -Jcvf node-install-18.14.2-macOSXSDK10.14-macos-release.tar.xz node-install +``` +Upload node-install-18.14.2-macOSXSDK10.14-macos-release.tar.xz to build-deps.overte.org, under the dependencies/node directory. diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 4f48570428b..4ffdd13d268 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -1,8 +1,13 @@ +# Copyright 2017-2019, High Fidelity, Inc. +# Copyright 2021-2023 Overte e.V. +# SPDX-License-Identifier: Apache-2.0 + 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() diff --git a/tools/oven/src/ui/ModesWidget.cpp b/tools/oven/src/ui/ModesWidget.cpp index 1fdfce2c97a..f3fcf33a499 100644 --- a/tools/oven/src/ui/ModesWidget.cpp +++ b/tools/oven/src/ui/ModesWidget.cpp @@ -4,9 +4,11 @@ // // Created by Stephen Birarda on 4/7/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 // #include "ModesWidget.h" @@ -49,6 +51,7 @@ void ModesWidget::setupUI() { void ModesWidget::showModelBakingWidget() { auto stackedWidget = qobject_cast(parentWidget()); + Q_ASSERT(stackedWidget != nullptr); // add a new widget for model baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget)); @@ -56,6 +59,7 @@ void ModesWidget::showModelBakingWidget() { void ModesWidget::showDomainBakingWidget() { auto stackedWidget = qobject_cast(parentWidget()); + Q_ASSERT(stackedWidget != nullptr); // add a new widget for domain baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new DomainBakeWidget)); @@ -63,6 +67,7 @@ void ModesWidget::showDomainBakingWidget() { void ModesWidget::showSkyboxBakingWidget() { auto stackedWidget = qobject_cast(parentWidget()); + Q_ASSERT(stackedWidget != nullptr); // add a new widget for skybox baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new SkyboxBakeWidget)); diff --git a/tools/qt-builder/Dockerfile_Ubuntu_20.04_Qt5 b/tools/qt-builder/Dockerfile_Ubuntu_20.04_Qt5 index b05f96f05d6..94bda6d4107 100644 --- a/tools/qt-builder/Dockerfile_Ubuntu_20.04_Qt5 +++ b/tools/qt-builder/Dockerfile_Ubuntu_20.04_Qt5 @@ -2,13 +2,13 @@ # Some steps for actually using this: # - Check which commit you are building https://invent.kde.org/qt/qt/qt5/-/tree/kde/5.15 -# - Adjust this file to include the commit hash you are building, the date, the number of threads you want to use (-j10), and the Qt and QtWebEngine versions. +# - Adjust this file to include the commit hash you are building, the date, the number of threads you want to use (-j10), the platform, and the Qt and QtWebEngine versions. # Keep in mind that building Qt requires a lot of memory. You should have over 1.2GiB of system memory available per thread. -# - Run the build process with something like `PROGRESS_NO_TRUNC=1 DOCKER_BUILDKIT=1 BUILDKIT_STEP_LOG_MAX_SIZE=-1 docker build --progress plain -t overte-qt5:5.15.5-2022.08.12-kde_0b4d44f2ff1103349bac22b9b207cfcc1f50a53a -f Dockerfile_Ubuntu_20.04_Qt5 .` +# - Run the build process with something like `PROGRESS_NO_TRUNC=1 DOCKER_BUILDKIT=1 BUILDKIT_STEP_LOG_MAX_SIZE=-1 docker build --progress plain -t overte-qt5:5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2 -f Dockerfile_Ubuntu_20.04_Qt5 .` # Buildkit is used to cache intermittent steps in case you need to modify something afterwards. # - Once the build has completed, create a container from the image and export the created Qt package. -# `docker create --name extract overte-qt5:5.15.5-2022.08.12-kde_0b4d44f2ff1103349bac22b9b207cfcc1f50a53a` -# `docker cp extract:qt5-install-5.15.5-2022.08.12-kde_0b4d44f2ff1103349bac22b9b207cfcc1f50a53a-ubuntu-20.04-amd64.tar.xz /path/on/host` +# `docker create --name extract overte-qt5:5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2` +# `docker cp extract:qt5-install-5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2-ubuntu-20.04-amd64.tar.xz /path/on/host` # `docker rm extract` FROM ubuntu:20.04 @@ -47,27 +47,27 @@ RUN apt-get -y install git python gperf flex bison pkg-config mesa-utils libgl1- RUN mkdir qt5-install && mkdir qt5-build WORKDIR qt5-build -RUN ../qt5/configure -force-debug-info -release -opensource -confirm-license -platform linux-g++-64 -recheck-all -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -skip qtlottie -skip qtquick3d -skip qtpim -skip qtdocgallery -no-warnings-are-errors -no-pch -no-icu -prefix ../qt5-install +RUN ../qt5/configure -force-debug-info -release -opensource -confirm-license -platform linux-g++ -recheck-all -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -skip qtlottie -skip qtquick3d -skip qtpim -skip qtdocgallery -no-warnings-are-errors -no-pch -no-icu -prefix ../qt5-install -RUN NINJAFLAGS='-j10' make -j10 +RUN NINJAFLAGS='-j16' make -j16 -RUN make -j10 module-qtscript +RUN make -j16 module-qtscript -RUN make -j10 install +RUN make -j16 install WORKDIR ./qtscript -RUN make -j10 install +RUN make -j16 install WORKDIR ../../qt5-install RUN find . -name \*.prl -exec sed -i -e '/^QMAKE_PRL_BUILD_DIR/d' {} \; # Overwrite QtWebengine version to work around version conflicts -RUN find . -name \Qt5WebEngine*Config.cmake -exec sed -i '' -e 's/5\.15\.11/5\.15\.5/g' {} \; -RUN cp lib/libQt5WebEngine.so.5.15.11 lib/libQt5WebEngine.so.5.15.5 -RUN cp lib/libQt5WebEngineCore.so.5.15.11 lib/libQt5WebEngineCore.so.5.15.5 -RUN cp lib/libQt5WebEngineWidgets.so.5.15.11 lib/libQt5WebEngineWidgets.so.5.15.5 -RUN cp lib/libQt5Pdf.so.5.15.11 lib/libQt5Pdf.so.5.15.5 -RUN cp lib/libQt5PdfWidgets.so.5.15.11 lib/libQt5PdfWidgets.so.5.15.5 +RUN find . -name \Qt5WebEngine*Config.cmake -exec sed -i '' -e 's/5\.15\.14/5\.15\.9/g' {} \; +RUN cp lib/libQt5WebEngine.so.5.15.14 lib/libQt5WebEngine.so.5.15.9 +RUN cp lib/libQt5WebEngineCore.so.5.15.14 lib/libQt5WebEngineCore.so.5.15.9 +RUN cp lib/libQt5WebEngineWidgets.so.5.15.14 lib/libQt5WebEngineWidgets.so.5.15.9 +RUN cp lib/libQt5Pdf.so.5.15.14 lib/libQt5Pdf.so.5.15.9 +RUN cp lib/libQt5PdfWidgets.so.5.15.14 lib/libQt5PdfWidgets.so.5.15.9 COPY ./qt.conf ./bin/ @@ -75,4 +75,4 @@ COPY ./qt.conf ./bin/ RUN cp ../qt5-build/config.summary ./ WORKDIR .. -RUN XZ_OPT='-T0' tar -Jcvf qt5-install-5.15.5-2022.08.12-kde_0b4d44f2ff1103349bac22b9b207cfcc1f50a53a-ubuntu-20.04-amd64.tar.xz qt5-install +RUN XZ_OPT='-T0' tar -Jcvf qt5-install-5.15.9-2023.05.21-kde_fb3ec282151b1ee281a24f0545a40ac6438537c2-ubuntu-20.04-amd64.tar.xz qt5-install diff --git a/unpublishedScripts/marketplace/camera-move/app-camera-move.js b/unpublishedScripts/marketplace/camera-move/app-camera-move.js index f9361c6091e..ff6ad88c7d4 100644 --- a/unpublishedScripts/marketplace/camera-move/app-camera-move.js +++ b/unpublishedScripts/marketplace/camera-move/app-camera-move.js @@ -354,7 +354,7 @@ function main() { settingsApp.isActiveChanged.connect(function(isActive) { updateButtonText(); if (Overlays.getOverlayType(HMD.tabletScreenID)) { - var fromMode = Overlays.getProperty(HMD.tabletScreenID, 'inputMode'), + var fromMode = Entities.getEntityProperties(HMD.tabletScreenID, ['inputMode']).inputMode, inputMode = isActive ? "Mouse" : "Touch"; log('switching HMD.tabletScreenID from inputMode', fromMode, 'to', inputMode); Overlays.editOverlay(HMD.tabletScreenID, { inputMode: inputMode });