diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index fb88df0171..f83545c25c 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -63,7 +63,7 @@ struct AssetMeta { AssetMeta() { } - BakeVersion bakeVersion; + BakeVersion bakeVersion { INITIAL_BAKE_VERSION }; bool failedLastBake { false }; QString lastBakeErrors; }; diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index f2690e0a7d..7bf6f05d9f 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -41,6 +41,9 @@ if (APPLE) elseif (WIN32) set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library") +elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library") else () set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 8e7db44b7d..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,7 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width property bool interactive: false @@ -29,6 +30,10 @@ Item { id: hifi } + function stop() { + webViewCore.stop(); + } + function unfocus() { webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { console.log('unfocus completed: ', result); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 931c64e1ef..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -21,6 +21,10 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive + function stop() { + webroot.stop(); + } + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. /* diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index e8737d92ae..96d92e91e9 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -9,11 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/**jsdoc - * The LOD class manages your Level of Detail functions within interface - * @namespace LODManager - */ - #ifndef hifi_LODManager_h #define hifi_LODManager_h @@ -39,10 +34,32 @@ const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; class AABox; +/**jsdoc + * The LOD class manages your Level of Detail functions within Interface. + * @namespace LODManager + * @property {number} presentTime Read-only. + * @property {number} engineRunTime Read-only. + * @property {number} gpuTime Read-only. + * @property {number} avgRenderTime Read-only. + * @property {number} fps Read-only. + * @property {number} lodLevel Read-only. + * @property {number} lodDecreaseFPS Read-only. + * @property {number} lodIncreaseFPS Read-only. + */ + class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) + Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + Q_PROPERTY(float lodLevel READ getLODLevel) + Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) + Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + public: /**jsdoc @@ -138,28 +155,6 @@ public: */ Q_INVOKABLE float getLODIncreaseFPS() const; - /**jsdoc - * @namespace LODManager - * @property {number} presentTime Read-only. - * @property {number} engineRunTime Read-only. - * @property {number} gpuTime Read-only. - * @property {number} avgRenderTime Read-only. - * @property {number} fps Read-only. - * @property {number} lodLevel Read-only. - * @property {number} lodDecreaseFPS Read-only. - * @property {number} lodIncreaseFPS Read-only. - */ - - Q_PROPERTY(float presentTime READ getPresentTime) - Q_PROPERTY(float engineRunTime READ getEngineRunTime) - Q_PROPERTY(float gpuTime READ getGPUTime) - Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) - Q_PROPERTY(float fps READ getMaxTheoreticalFPS) - Q_PROPERTY(float lodLevel READ getLODLevel) - - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) - Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) - float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } float getGPUTime() const { return _gpuTime; } diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 3e0e4adf18..35e6ca1c92 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -615,9 +615,15 @@ void Wallet::updateImageProvider() { securityImageProvider->setSecurityImage(_securityImage); // inform tablet security image provider - QQmlEngine* tabletEngine = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine(); - securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); - securityImageProvider->setSecurityImage(_securityImage); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + if (tablet) { + OffscreenQmlSurface* tabletSurface = tablet->getTabletSurface(); + if (tabletSurface) { + QQmlEngine* tabletEngine = tabletSurface->getSurfaceContext()->engine(); + securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); + securityImageProvider->setSecurityImage(_securityImage); + } + } } void Wallet::chooseSecurityImage(const QString& filename) { diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index f19caa8478..4fceda3b04 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -28,7 +28,7 @@ class ScriptEngine; /**jsdoc * The Controller API provides facilities to interact with computer and controller hardware. * - *
Functions:
+ *
Functions
* *

Properties

*
    @@ -143,6 +143,61 @@ class ScriptEngine; *
  • {@link Controller.stopInputPlayback|stopInputPlayback}
  • *
* + *
Entity Methods:
+ * + *

The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script + * methods, if present in the entity being interacted with.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Method NameDescriptionExample
startFarTrigger
continueFarTrigger
stopFarTrigger
These methods are called when a user is more than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A light switch that can be toggled on and off from a distance.
startNearTrigger
continueNearTrigger
stopNearTrigger
These methods are called when a user is less than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A doorbell that can be rung when a user is near.
startDistanceGrab
continueDistanceGrab
These methods are called when a user is more than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A comet that emits icy particle trails when a user is dragging it through the sky.
startNearGrab
continueNearGrab
These methods are called when a user is less than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A ball that glows when it's being held close.
releaseGrabThis method is called when a user releases the trigger when having been either distance or near grabbing an + * entity.Turn off the ball glow or comet trail with the user finishes grabbing it.
startEquip
continueEquip
releaseEquip
These methods are called when a user starts, continues, or stops equipping an entity.A glass that stays in the user's hand after the trigger is clicked.
+ *

All the entity methods are called with the following two arguments:

+ *
    + *
  • The entity ID.
  • + *
  • A string, "hand,userID" — where "hand" is "left" or "right", and "userID" + * is the user's {@link MyAvatar|MyAvatar.sessionUUID}.
  • + *
+ * * @namespace Controller * * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 87bf09a252..789a2a2bdf 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -58,9 +58,8 @@ void AddressBarDialog::loadHome() { qDebug() << "Called LoadHome"; auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); - const QString DEFAULT_HOME_LOCATION = "localhost"; if (homeLocation == "") { - homeLocation = DEFAULT_HOME_LOCATION; + homeLocation = DEFAULT_HIFI_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 172ec9411a..d60c5ba4ab 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -150,6 +150,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { // fall through to OnTime case } + // FALLTHRU case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer if (message.getType() == PacketType::SilentAudioFrame diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 86a43c0c13..3c3858e2ba 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -39,7 +39,9 @@ class UserInputMapper; * methods. *
  • Use {@link Controller.parseMapping} or {@link Controller.loadMapping} to load a {@link Controller.MappingJSON}.
  • * - *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take effect. + * + *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take + * effect.

    * *

    Mappings and their routes are applied according to the following rules:

    *
      @@ -49,7 +51,7 @@ class UserInputMapper; * output that already has a route the new route is ignored. *
    • New mappings override previous mappings: each output is processed using the route in the most recently enabled * mapping that contains that output.
    • - *

      + *
    * * @class MappingObject */ diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 0336638068..d33f3e3383 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -29,7 +29,8 @@ class ScriptingInterface; *

    A route in a {@link MappingObject} used by the {@link Controller} API.

    * *

    Create a route using {@link MappingObject} methods and apply this object's methods to process it, terminating with - * {@link RouteObject#to} to apply it to a Standard control, action, or script function.

    + * {@link RouteObject#to} to apply it to a Standard control, action, or script function. Note: Loops are not + * permitted.

    * *

    Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route * types.

    diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f333e805ce..693e3d0cf4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -308,12 +308,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { - if (item && item->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - }); + _webSurface->load(_lastSourceUrl); } _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -323,32 +318,21 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; + ContentType contentType{ ContentType::NoContent }; withWriteLock([&] { webSurface.swap(_webSurface); + std::swap(contentType, _contentType); }); if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - // Explicitly set the web URL to an empty string, in an effort to get a - // faster shutdown of any chromium processes interacting with audio - if (rootItem && _contentType == ContentType::HtmlContent) { - rootItem->setProperty(URL_PROPERTY, ""); - } - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + if (rootItem && contentType == ContentType::HtmlContent) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } webSurface->pause(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b56f367e0a..3149527216 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1402,6 +1402,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c case PacketType::EntityAdd: isAdd = true; // fall through to next case + // FALLTHRU case PacketType::EntityPhysics: case PacketType::EntityEdit: { quint64 startDecode = 0, endDecode = 0; diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp index 511f253193..e6adff0df9 100644 --- a/libraries/fbx/src/FBXWriter.cpp +++ b/libraries/fbx/src/FBXWriter.cpp @@ -142,7 +142,6 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { out << prop.toInt(); break; - encodeNode(out, FBXNode()); case QMetaType::Float: out.device()->write("F", 1); out << prop.toFloat(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 31500be682..0660f02fd6 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -302,6 +302,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; + // check if we were passed a sourceNode hint or if we need to look it up if (!sourceNode) { // figure out which node this is from @@ -314,6 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && + !isDomainServer() && sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { @@ -608,6 +610,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newCo { QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(matchingNode->getLocalID()); _nodeHash.unsafe_erase(it); } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7ba7cca96d..d3583687e8 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -750,6 +750,7 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; // Fall through to other cases } + // FALLTHRU case ResourceRequest::Result::ServerUnavailable: { _attempts++; _attemptsRemaining--; @@ -768,6 +769,7 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { } // fall through to final failure } + // FALLTHRU default: { _attemptsRemaining = 0; qCDebug(networking) << "Error loading " << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index c2bacd4949..68f21eea87 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -447,7 +447,12 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); - if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { + // shouldSendUpdate() sould NOT be triggering updates to maintain the queryAACube of dynamic entities. + // The server is supposed to predict the transform of such moving things. The client performs a "double prediction" + // where it predicts what the the server is doing, and only sends updates whent the entity's true transform + // differs significantly. That is what the remoteSimulationOutOfSync() logic is all about. + if (_entity->dynamicDataNeedsTransmit() || + (!_entity->getDynamic() && _entity->queryAACubeNeedsUpdate())) { return true; } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index ab7c2ec252..3ea3616b15 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -153,6 +153,8 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); + clearOwnershipData(); + // delete the MotionStates for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast(&(*stateItr)); @@ -165,7 +167,6 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { _physicalObjects.clear(); // clear all other lists specific to this derived class - clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 9253c41b39..2fde057ca8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -90,19 +90,23 @@ SharedObject::~SharedObject() { _renderControl = nullptr; } + if (_rootItem) { + delete _rootItem; + _rootItem = nullptr; + } + if (_quickWindow) { _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; } - // _rootItem is parented to the quickWindow, so needs no explicit destruction - //if (_rootItem) { - // delete _rootItem; - // _rootItem = nullptr; - //} - - releaseEngine(_qmlContext->engine()); + if (_qmlContext) { + auto engine = _qmlContext->engine(); + delete _qmlContext; + _qmlContext = nullptr; + releaseEngine(engine); + } } void SharedObject::create(OffscreenSurface* surface) { @@ -210,9 +214,9 @@ QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { if (!globalEngine) { Q_ASSERT(0 == globalEngineRefCount); globalEngine = new QQmlEngine(); - surface->initializeQmlEngine(result); - ++globalEngineRefCount; + surface->initializeEngine(result); } + ++globalEngineRefCount; result = globalEngine; #else result = new QQmlEngine(); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..f0a13cc62b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -2161,6 +2161,32 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& }, forceRedownload); } +/**jsdoc + * Triggered when the script starts for a user. + *

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

    + *
    Available in:Client Entity ScriptsServer 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. + * }); + */ // 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) { @@ -2345,6 +2371,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co processDeferredEntityLoads(entityScript, entityID); } +/**jsdoc + * Triggered when the script terminates for a user. + *

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

    + *
    Available in:Client Entity ScriptsServer Entity Scripts
    + * @function Entities.unload + * @returns {Signal} + */ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/shared/src/Gzip.cpp b/libraries/shared/src/Gzip.cpp index a77f459fc9..25e214fffb 100644 --- a/libraries/shared/src/Gzip.cpp +++ b/libraries/shared/src/Gzip.cpp @@ -60,6 +60,7 @@ bool gunzip(QByteArray source, QByteArray &destination) { switch (status) { case Z_NEED_DICT: status = Z_DATA_ERROR; + // FALLTHRU case Z_DATA_ERROR: case Z_MEM_ERROR: case Z_STREAM_ERROR: diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 93e2c6c4c2..1e1326968f 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -102,6 +102,9 @@ private: }; inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } const auto& tracer = DependencyManager::get(); if (tracer) { tracer->traceEvent(category, name, type, id, args, extra); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index ac4194ed55..48e778c063 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -115,6 +115,7 @@ private: class UrlHandler : public QObject { Q_OBJECT public: + UrlHandler(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE bool canHandleUrl(const QString& url) { static auto handler = dynamic_cast(qApp); return handler && handler->canAcceptURL(url); @@ -223,6 +224,17 @@ void AudioHandler::run() { qDebug() << "QML Audio changed to " << _newTargetDevice; } +OffscreenQmlSurface::~OffscreenQmlSurface() { + clearFocusItem(); +} + +void OffscreenQmlSurface::clearFocusItem() { + if (_currentFocusItem) { + disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); + } + _currentFocusItem = nullptr; +} + void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); new QQmlFileSelector(engine); @@ -246,7 +258,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); - rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext)); rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); @@ -545,17 +557,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } void OffscreenQmlSurface::focusDestroyed(QObject* obj) { - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); - } - _currentFocusItem = nullptr; + clearFocusItem(); } void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + clearFocusItem(); + QQuickItem* item = static_cast(object); if (!item) { setFocusText(false); - _currentFocusItem = nullptr; return; } @@ -563,10 +573,6 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { qApp->sendEvent(object, &query); setFocusText(query.value(Qt::ImEnabled).toBool()); - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, 0); - } - // Raise and lower keyboard for QML text fields. // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files. const char* READ_ONLY_PROPERTY = "readOnly"; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 9fa86f12a3..b95a8f117d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -22,7 +22,8 @@ class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: - + ~OffscreenQmlSurface(); + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -58,6 +59,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; void onRootContextCreated(QQmlContext* qmlContext) override; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6d2b1f129b..c99c8d401a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1291,6 +1291,7 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); } Script.scriptEnding.connect(function () { diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index b5e866ccce..ce1c150ed4 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -23,5 +23,9 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment() -endif() \ No newline at end of file +endif() diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml new file mode 100644 index 0000000000..5bd29ef457 --- /dev/null +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -0,0 +1,47 @@ +// +// WebEntityView.qml +// +// Created by Kunal Gosar on 16 March 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 +// + +import QtQuick 2.5 +import QtWebEngine 1.5 +import Hifi 1.0 + +/* +TestItem { + Rectangle { + anchors.fill: parent + anchors.margins: 10 + color: "blue" + property string url: "" + ColorAnimation on color { + loops: Animation.Infinite; from: "blue"; to: "yellow"; duration: 1000 + } + } +} +*/ + +WebEngineView { + id: webViewCore + objectName: "webEngineView" + width: parent !== null ? parent.width : undefined + height: parent !== null ? parent.height : undefined + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } +} diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 022f7290f4..349ac55d88 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -28,52 +28,87 @@ #include #include #include - +#include #include #include +#include + #include -#include #include #include #include #include #include #include +#include +#include +namespace gl { +extern void initModuleGl(); +} -class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { +class QTestItem : public QQuickItem { + Q_OBJECT +public: + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { qDebug() << __FUNCTION__; } + ~QTestItem() { qDebug() << __FUNCTION__; } +}; + +QUrl getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +using QmlPtr = QSharedPointer; +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; }; class TestWindow : public QWindow { - public: TestWindow(); - private: - using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - OffscreenQmlSurface _offscreenQml; + std::array, DIVISIONS_X> _surfaces; + QOpenGLFunctions_4_5_Core _glf; - uint32_t _currentTexture{ 0 }; - GLsync _readFence{ 0 }; std::function _discardLamdba; QSize _size; + size_t _surfaceCount{ 0 }; GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; + uint64_t _createStopTime; void initGl(); + void updateSurfaces(); + void buildSurface(QmlInfo& qmlInfo, bool allowVideo); + void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; TestWindow::TestWindow() { - setSurfaceType(QSurface::OpenGLSurface); + Setting::init(); + setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); @@ -83,13 +118,16 @@ TestWindow::TestWindow() { QSurfaceFormat::setDefaultFormat(format); setFormat(format); + qmlRegisterType("Hifi", 1, 0, "TestItem"); + show(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); auto timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); - timer->setInterval(5); + timer->setInterval(30); connect(timer, &QTimer::timeout, [&] { draw(); }); timer->start(); @@ -97,7 +135,6 @@ TestWindow::TestWindow() { timer->stop(); _aboutToQuit = true; }); - } void TestWindow::initGl() { @@ -105,6 +142,7 @@ void TestWindow::initGl() { if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::initModuleGl(); _glf.initializeOpenGLFunctions(); _glf.glCreateFramebuffers(1, &_fbo); @@ -113,15 +151,97 @@ void TestWindow::initGl() { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = _offscreenQml.getDiscardLambda(); - _offscreenQml.resize({ 640, 480 }); - _offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml")); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString getSourceUrl(bool video) { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void TestWindow::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void TestWindow::updateSurfaces() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + void TestWindow::draw() { if (_aboutToQuit) { return; @@ -140,38 +260,30 @@ void TestWindow::draw() { return; } + updateSurfaces(); + + auto size = this->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - - TextureAndFence newTextureAndFence; - if (_offscreenQml.fetchTexture(newTextureAndFence)) { - if (_currentTexture) { - _discardLamdba(_currentTexture, _readFence); - _readFence = 0; + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, qmlInfo.texture, 0); + _glf.glBlitNamedFramebuffer(_fbo, 0, + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); } - - _currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0); } - - auto diff = _size - _qmlSize; - diff /= 2; - auto qmlExtent = diff + _qmlSize; - - if (_currentTexture) { - _glf.glBlitNamedFramebuffer(_fbo, 0, - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - - if (_readFence) { - _glf.glDeleteSync(_readFence); - } - _readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _glf.glFlush(); - _glContext.swapBuffers(this); } @@ -180,11 +292,9 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { } int main(int argc, char** argv) { - setupHifiApplication("QML Test"); - QGuiApplication app(argc, argv); TestWindow window; - app.exec(); - return 0; + return app.exec(); } +#include "main.moc" diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index fd4d8d88dd..d688474379 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -27,6 +27,10 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment()