diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 63e7a1a174..978fa29552 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -25,14 +25,14 @@ macro(SET_PACKAGING_PARAMETERS) set(BUILD_VERSION ${RELEASE_NUMBER}) set(BUILD_ORGANIZATION "High Fidelity") set(HIGH_FIDELITY_PROTOCOL "hifi") - set(INTERFACE_BUNDLE_NAME "High Fidelity") + set(INTERFACE_BUNDLE_NAME "Interface") set(INTERFACE_ICON_PREFIX "interface") elseif (RELEASE_TYPE STREQUAL "PR") set(DEPLOY_PACKAGE TRUE) set(PR_BUILD 1) set(BUILD_VERSION "PR${RELEASE_NUMBER}") set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}") - set(INTERFACE_BUNDLE_NAME "High Fidelity") + set(INTERFACE_BUNDLE_NAME "Interface") set(INTERFACE_ICON_PREFIX "interface-beta") else () set(DEV_BUILD 1) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 16928f3dee..a5c485ebe2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -12,6 +12,7 @@ #include "DomainServer.h" #include +#include #include #include @@ -42,7 +43,7 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923; -const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; +const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), @@ -59,8 +60,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _webAuthenticationStateSet(), _cookieSessionHash(), _automaticNetworkingSetting(), - _settingsManager(), - _iceServerSocket(ICE_SERVER_DEFAULT_HOSTNAME, ICE_SERVER_DEFAULT_PORT) + _settingsManager() { qInstallMessageHandler(LogHandler::verboseMessageHandler); @@ -241,7 +241,7 @@ void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { // so fire off that request now auto& accountManager = AccountManager::getInstance(); - // ask our auth endpoint for our balance + // get callbacks for temporary domain result JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess"; @@ -369,7 +369,9 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); @@ -432,7 +434,9 @@ void DomainServer::setupAutomaticNetworking() { setupICEHeartbeatForFullNetworking(); } - if (!resetAccountManagerAccessToken()) { + _hasAccessToken = resetAccountManagerAccessToken(); + + if (!_hasAccessToken) { qDebug() << "Will not send heartbeat to Metaverse API without an access token."; qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; @@ -482,6 +486,9 @@ void DomainServer::setupAutomaticNetworking() { void DomainServer::setupICEHeartbeatForFullNetworking() { auto limitedNodeList = DependencyManager::get(); + // lookup the available ice-server hosts now + updateICEServerAddresses(); + // call our sendHeartbeatToIceServer immediately anytime a local or public socket changes connect(limitedNodeList.data(), &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); @@ -512,6 +519,12 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { } } +void DomainServer::updateICEServerAddresses() { + if (_iceAddressLookupID == -1) { + _iceAddressLookupID = QHostInfo::lookupHost(ICE_SERVER_DEFAULT_HOSTNAME, this, SLOT(handleICEHostInfo(QHostInfo))); + } +} + void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; @@ -1018,8 +1031,10 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString()); } + void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; + auto nodeList = DependencyManager::get(); const QUuid& domainID = nodeList->getSessionUUID(); @@ -1065,6 +1080,46 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } +void DomainServer::sendICEServerAddressToMetaverseAPI() { + if (!_iceServerSocket.isNull()) { + auto nodeList = DependencyManager::get(); + const QUuid& domainID = nodeList->getSessionUUID(); + + const QString ICE_SERVER_ADDRESS = "ice_server_address"; + + QJsonObject domainObject; + + // we're using full automatic networking and we have a current ice-server socket, use that now + domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString(); + + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); + + // make sure we hear about failure so we can retry + JSONCallbackParameters callbackParameters; + callbackParameters.errorCallbackReceiver = this; + callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate"; + + qDebug() << "Updating ice-server address in High Fidelity Metaverse API to" << _iceServerSocket.getAddress().toString(); + + static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; + + AccountManager::getInstance().sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + AccountManagerAuth::Optional, + QNetworkAccessManager::PutOperation, + callbackParameters, + domainUpdateJSON.toUtf8()); + } +} + +void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestReply) { + const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000; + + qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was" << requestReply.errorString(); + qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds"; + + QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI())); +} + void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { @@ -1084,6 +1139,32 @@ void DomainServer::sendHeartbeatToIceServer() { return; } + const int FAILOVER_NO_REPLY_ICE_HEARTBEATS { 3 }; + + // increase the count of no reply ICE heartbeats and check the current value + ++_noReplyICEHeartbeats; + + if (_noReplyICEHeartbeats > FAILOVER_NO_REPLY_ICE_HEARTBEATS) { + qWarning() << "There have been" << _noReplyICEHeartbeats - 1 << "heartbeats sent with no reply from the ice-server"; + qWarning() << "Clearing the current ice-server socket and selecting a new candidate ice-server"; + + // add the current address to our list of failed addresses + _failedIceServerAddresses << _iceServerSocket.getAddress(); + + // if we've failed to hear back for three heartbeats, we clear the current ice-server socket and attempt + // to randomize a new one + _iceServerSocket.clear(); + + // reset the number of no reply ICE hearbeats + _noReplyICEHeartbeats = 0; + + // reset the connection flag for ICE server + _connectedToICEServer = false; + + // randomize our ice-server address (and simultaneously look up any new hostnames for available ice-servers) + randomizeICEServerAddress(true); + } + // NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with // QDataStream and the possibility of IPv6 address for the sockets. if (!_iceServerHeartbeatPacket) { @@ -1135,6 +1216,11 @@ void DomainServer::sendHeartbeatToIceServer() { // send the heartbeat packet to the ice server now limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket); + + } else { + qDebug() << "Not sending ice-server heartbeat since there is no selected ice-server."; + qDebug() << "Waiting for" << ICE_SERVER_DEFAULT_HOSTNAME << "host lookup response"; + } } @@ -2009,8 +2095,7 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer message) { static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3; - static int numHeartbeatDenials = 0; - if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) { + if (++_numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) { qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server" << "- re-generating keypair now"; @@ -2019,7 +2104,20 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointergetSessionUUID()); // reset our number of heartbeat denials - numHeartbeatDenials = 0; + _numHeartbeatDenials = 0; + } + + // even though we can't get into this ice-server it is responding to us, so we reset our number of no-reply heartbeats + _noReplyICEHeartbeats = 0; +} + +void DomainServer::processICEServerHeartbeatACK(QSharedPointer message) { + // we don't do anything with this ACK other than use it to tell us to keep talking to the same ice-server + _noReplyICEHeartbeats = 0; + + if (!_connectedToICEServer) { + _connectedToICEServer = true; + qInfo() << "Connected to ice-server at" << _iceServerSocket; } } @@ -2033,3 +2131,89 @@ void DomainServer::handleKeypairChange() { sendHeartbeatToIceServer(); } } + +void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { + // clear the ICE address lookup ID so that it can fire again + _iceAddressLookupID = -1; + + if (hostInfo.error() != QHostInfo::NoError) { + qWarning() << "IP address lookup failed for" << ICE_SERVER_DEFAULT_HOSTNAME << ":" << hostInfo.errorString(); + + // if we don't have an ICE server to use yet, trigger a retry + if (_iceServerSocket.isNull()) { + const int ICE_ADDRESS_LOOKUP_RETRY_MS = 1000; + + QTimer::singleShot(ICE_ADDRESS_LOOKUP_RETRY_MS, this, SLOT(updateICEServerAddresses())); + } + + } else { + int countBefore = _iceServerAddresses.count(); + + _iceServerAddresses = hostInfo.addresses(); + + if (countBefore == 0) { + qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << ICE_SERVER_DEFAULT_HOSTNAME; + } + + if (_iceServerSocket.isNull()) { + // we don't have a candidate ice-server yet, pick now (without triggering a host lookup since we just did one) + randomizeICEServerAddress(false); + } + } +} + +void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) { + if (shouldTriggerHostLookup) { + updateICEServerAddresses(); + } + + // create a list by removing the already failed ice-server addresses + auto candidateICEAddresses = _iceServerAddresses; + + auto it = candidateICEAddresses.begin(); + + while (it != candidateICEAddresses.end()) { + if (_failedIceServerAddresses.contains(*it)) { + // we already tried this address and it failed, remove it from list of candidates + it = candidateICEAddresses.erase(it); + } else { + // keep this candidate, it hasn't failed yet + ++it; + } + } + + if (candidateICEAddresses.empty()) { + // we ended up with an empty list since everything we've tried has failed + // so clear the set of failed addresses and start going through them again + + qWarning() << "All current ice-server addresses have failed - re-attempting all current addresses for" + << ICE_SERVER_DEFAULT_HOSTNAME; + + _failedIceServerAddresses.clear(); + candidateICEAddresses = _iceServerAddresses; + } + + // of the list of available addresses that we haven't tried, pick a random one + int maxIndex = candidateICEAddresses.size() - 1; + int indexToTry = 0; + + if (maxIndex > 0) { + static std::random_device randomDevice; + static std::mt19937 generator(randomDevice()); + std::uniform_int_distribution<> distribution(0, maxIndex); + + indexToTry = distribution(generator); + } + + _iceServerSocket = HifiSockAddr { candidateICEAddresses[indexToTry], ICE_SERVER_DEFAULT_PORT }; + qInfo() << "Set candidate ice-server socket to" << _iceServerSocket; + + // clear our number of hearbeat denials, this should be re-set on ice-server change + _numHeartbeatDenials = 0; + + // immediately fire an ICE heartbeat once we've picked a candidate ice-server + sendHeartbeatToIceServer(); + + // immediately send an update to the metaverse API when our ice-server changes + sendICEServerAddressToMetaverseAPI(); +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 3a83e8696b..93bb5de494 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -62,6 +62,7 @@ public slots: void processPathQueryPacket(QSharedPointer packet); void processNodeDisconnectRequestPacket(QSharedPointer message); void processICEServerHeartbeatDenialPacket(QSharedPointer message); + void processICEServerHeartbeatACK(QSharedPointer message); private slots: void aboutToQuit(); @@ -81,6 +82,15 @@ private slots: void queuedQuit(QString quitMessage, int exitCode); void handleKeypairChange(); + + void updateICEServerAddresses(); + void handleICEHostInfo(const QHostInfo& hostInfo); + + void sendICEServerAddressToMetaverseAPI(); + void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply); + +signals: + void iceServerChanged(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); @@ -95,6 +105,8 @@ private: void setupICEHeartbeatForFullNetworking(); void sendHeartbeatToDataServer(const QString& networkAddress); + void randomizeICEServerAddress(bool shouldTriggerHostLookup); + unsigned int countConnectedUsers(); void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr); @@ -157,7 +169,16 @@ private: std::unique_ptr _iceServerHeartbeatPacket; QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer - + + QList _iceServerAddresses; + QSet _failedIceServerAddresses; + int _iceAddressLookupID { -1 }; + int _noReplyICEHeartbeats { 0 }; + int _numHeartbeatDenials { 0 }; + bool _connectedToICEServer { false }; + + bool _hasAccessToken { false }; + friend class DomainGatekeeper; }; diff --git a/examples/away.js b/examples/away.js index 643edbd149..9c5aed98fa 100644 --- a/examples/away.js +++ b/examples/away.js @@ -23,6 +23,7 @@ var OVERLAY_DATA = { color: {red: 255, green: 255, blue: 255}, alpha: 1 }; +var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away var lastOverlayPosition = { x: 0, y: 0, z: 0}; var OVERLAY_DATA_HMD = { @@ -34,49 +35,26 @@ var OVERLAY_DATA_HMD = { alpha: 1, scale: 2, isFacingAvatar: true, - drawInFront: true + drawInFront: true }; -// ANIMATION -// We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect -// using an animation graph with a state that we turn on and off through the animation var defined with that state. -var awayAnimationHandlerId, activeAnimationHandlerId, stopper; +var AWAY_INTRO = { + url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", + playbackRate: 30.0, + loopFlag: false, + startFrame: 0.0, + endFrame: 83.0 +}; + +// prefetch the kneel animation so it's resident in memory when we need it. +MyAvatar.prefetchAnimation(AWAY_INTRO.url); + function playAwayAnimation() { - function animateAway() { - return {isAway: true, isNotAway: false, isNotMoving: false, ikOverlayAlpha: 0.0}; - } - if (stopper) { - stopper = false; - MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); // do it now, before making new assignment - } - awayAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateAway, null); + MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame); } + function stopAwayAnimation() { - MyAvatar.removeAnimationStateHandler(awayAnimationHandlerId); - if (stopper) { - print('WARNING: unexpected double stop'); - return; - } - // How do we know when to turn ikOverlayAlpha back on? - // It cannot be as soon as we want to stop the away animation, because then things will look goofy as we come out of that animation. - // (Imagine an away animation that sits or kneels, and then stands back up when coming out of it. If head is at the HMD, then it won't - // want to track the standing up animation.) - // The anim graph will trigger awayOutroOnDone when awayOutro is finished. - var backToNormal = false; - stopper = true; - function animateActive(state) { - if (state.awayOutroOnDone) { - backToNormal = true; - stopper = false; - } else if (state.ikOverlayAlpha) { - // Once the right state gets reflected back to us, we don't need the hander any more. - // But we are locked against handler changes during the execution of a handler, so remove asynchronously. - Script.setTimeout(function () { MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); }, 0); - } - // It might be cool to "come back to life" by fading the ik overlay back in over a short time. But let's see how this goes. - return {isAway: false, isNotAway: true, ikOverlayAlpha: backToNormal ? 1.0 : 0.0}; // IWBNI we had a way of deleting an anim var. - } - activeAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateActive, ['ikOverlayAlpha', 'awayOutroOnDone']); + MyAvatar.restoreAnimation(); } // OVERLAY @@ -112,15 +90,17 @@ function showOverlay() { 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), + Overlays.editOverlay(overlay, { 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}); } + hideOverlay(); function maybeMoveOverlay() { @@ -150,11 +130,18 @@ function maybeMoveOverlay() { } } +function ifAvatarMovedGoActive() { + if (Vec3.distance(MyAvatar.position, avatarPosition) > AVATAR_MOVE_FOR_ACTIVE_DISTANCE) { + goActive(); + } +} + // MAIN CONTROL var wasMuted, isAway; var wasOverlaysVisible = Menu.isOptionChecked("Overlays"); var eventMappingName = "io.highfidelity.away"; // goActive on hand controller button events, too. var eventMapping = Controller.newMapping(eventMappingName); +var avatarPosition = MyAvatar.position; // backward compatible version of getting HMD.mounted, so it works in old clients function safeGetHMDMounted() { @@ -163,12 +150,14 @@ function safeGetHMDMounted() { } return HMD.mounted; } + var wasHmdMounted = safeGetHMDMounted(); function goAway() { if (isAway) { return; } + isAway = true; print('going "away"'); wasMuted = AudioDevice.getMuted(); @@ -191,6 +180,9 @@ function goAway() { Reticle.visible = false; } wasHmdMounted = safeGetHMDMounted(); // always remember the correct state + + avatarPosition = MyAvatar.position; + Script.update.connect(ifAvatarMovedGoActive); } function goActive() { @@ -216,6 +208,8 @@ function goActive() { Reticle.position = HMD.getHUDLookAtPosition2D(); } wasHmdMounted = safeGetHMDMounted(); // always remember the correct state + + Script.update.disconnect(ifAvatarMovedGoActive); } function maybeGoActive(event) { @@ -263,7 +257,7 @@ Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); // Note peek() so as to not interfere with other mappings. -eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive); +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(goActive); eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index b66ccaa057..84c48ac5f9 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -27,19 +27,14 @@ const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000; const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000; -const quint16 ICE_SERVER_MONITORING_PORT = 40110; - IceServer::IceServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _id(QUuid::createUuid()), _serverSocket(), - _activePeers(), - _httpManager(QHostAddress::AnyIPv4, ICE_SERVER_MONITORING_PORT, QString("%1/web/").arg(QCoreApplication::applicationDirPath()), this), - _lastInactiveCheckTimestamp(QDateTime::currentMSecsSinceEpoch()) + _activePeers() { // start the ice-server socket qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; - qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_PORT; _serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT); // set processPacket as the verified packet callback for the udt::Socket @@ -82,6 +77,11 @@ void IceServer::processPacket(std::unique_ptr packet) { if (peer) { // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + + // we have an active and verified heartbeating peer + // send them an ACK packet so they know that they are being heard and ready for ICE + static auto ackPacket = NLPacket::create(PacketType::ICEServerHeartbeatACK); + _serverSocket.writePacket(*ackPacket, nlPacket->getSenderSockAddr()); } else { // we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied); @@ -164,39 +164,42 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { } bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) { - // check if we have a public key for this domain ID - if we do not then fire off the request for it - auto it = _domainPublicKeys.find(domainID); - if (it != _domainPublicKeys.end()) { + // make sure we're not already waiting for a public key for this domain-server + if (!_pendingPublicKeyRequests.contains(domainID)) { + // check if we have a public key for this domain ID - if we do not then fire off the request for it + auto it = _domainPublicKeys.find(domainID); + if (it != _domainPublicKeys.end()) { - // attempt to verify the signature for this heartbeat - const auto rsaPublicKey = it->second.get(); + // attempt to verify the signature for this heartbeat + const auto rsaPublicKey = it->second.get(); - if (rsaPublicKey) { - auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); - int verificationResult = RSA_verify(NID_sha256, - reinterpret_cast(hashedPlaintext.constData()), - hashedPlaintext.size(), - reinterpret_cast(signature.constData()), - signature.size(), - rsaPublicKey); + if (rsaPublicKey) { + auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); + int verificationResult = RSA_verify(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.constData()), + signature.size(), + rsaPublicKey); + + if (verificationResult == 1) { + // this is the only success case - we return true here to indicate that the heartbeat is verified + return true; + } else { + qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API."; + } - if (verificationResult == 1) { - // this is the only success case - we return true here to indicate that the heartbeat is verified - return true; } else { - qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API."; + // we can't let this user in since we couldn't convert their public key to an RSA key we could use + qWarning() << "Public key for" << domainID << "is not a usable RSA* public key."; + qWarning() << "Re-requesting public key from API"; } - - } else { - // we can't let this user in since we couldn't convert their public key to an RSA key we could use - qWarning() << "Public key for" << domainID << "is not a usable RSA* public key."; - qWarning() << "Re-requesting public key from API"; } - } - // we could not verify this heartbeat (missing public key, could not load public key, bad actor) - // ask the metaverse API for the right public key and return false to indicate that this is not verified - requestDomainPublicKey(domainID); + // we could not verify this heartbeat (missing public key, could not load public key, bad actor) + // ask the metaverse API for the right public key and return false to indicate that this is not verified + requestDomainPublicKey(domainID); + } return false; } @@ -214,6 +217,9 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) { qDebug() << "Requesting public key for domain with ID" << domainID; + // add this to the set of pending public key requests + _pendingPublicKeyRequests.insert(domainID); + networkAccessManager.get(publicKeyRequest); } @@ -266,6 +272,9 @@ void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString(); } + // remove this domain ID from the list of pending public key requests + _pendingPublicKeyRequests.remove(domainID); + reply->deleteLater(); } @@ -282,8 +291,6 @@ void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSoc void IceServer::clearInactivePeers() { NetworkPeerHash::iterator peerItem = _activePeers.begin(); - _lastInactiveCheckTimestamp = QDateTime::currentMSecsSinceEpoch(); - while (peerItem != _activePeers.end()) { SharedNetworkPeer peer = peerItem.value(); @@ -301,25 +308,3 @@ void IceServer::clearInactivePeers() { } } } - -bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { - // We need an HTTP handler in order to monitor the health of the ice server - // The correct functioning of the ICE server will be determined by its HTTP availability, - - if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { - if (url.path() == "/status") { - // figure out if we respond with 0 (we're good) or 1 (we think we're in trouble) - - const quint64 MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET = 10 * 1000; - - auto sinceLastInactiveCheck = QDateTime::currentMSecsSinceEpoch() - _lastInactiveCheckTimestamp; - int statusNumber = (sinceLastInactiveCheck > MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET) ? 1 : 0; - - connection->respond(HTTPConnection::StatusCode200, QByteArray::number(statusNumber)); - - return true; - } - } - - return false; -} diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 7d1d05324c..2aa9a875a7 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -28,11 +28,10 @@ class QNetworkReply; -class IceServer : public QCoreApplication, public HTTPRequestHandler { +class IceServer : public QCoreApplication { Q_OBJECT public: IceServer(int argc, char* argv[]); - bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: void clearInactivePeers(); void publicKeyReplyFinished(QNetworkReply* reply); @@ -52,13 +51,11 @@ private: using NetworkPeerHash = QHash; NetworkPeerHash _activePeers; - HTTPManager _httpManager; - using RSAUniquePtr = std::unique_ptr>; using DomainPublicKeyHash = std::unordered_map; DomainPublicKeyHash _domainPublicKeys; - quint64 _lastInactiveCheckTimestamp; + QSet _pendingPublicKeyRequests; }; #endif // hifi_IceServer_h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 2e50502840..fcade5980c 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -211,6 +211,10 @@ if (WIN32) add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine) endif() +if (UNIX) + target_link_libraries(${TARGET_NAME} pthread) +endif(UNIX) + # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index a4c0a7c446..40e8ec74a6 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -246,7 +246,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -266,7 +265,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -285,7 +283,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -304,7 +301,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -323,7 +319,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -342,7 +337,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -361,7 +355,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -380,7 +373,6 @@ { "var": "isMovingRight", "state": "strafeRight" }, { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -388,37 +380,11 @@ { "var": "isInAirRun", "state": "inAirRun" } ] }, - { - "id": "awayIntro", - "interpTarget": 30, - "interpDuration": 30, - "transitions": [ - { "var": "isNotAway", "state": "awayOutro" }, - { "var": "awayIntroOnDone", "state": "away"} - ] - }, - { - "id": "away", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isNotAway", "state": "awayOutro" } - ] - }, - { - "id": "awayOutro", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "awayOutroOnDone", "state": "idle" } - ] - }, { "id": "fly", "interpTarget": 6, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotFlying", "state": "idle" } ] }, @@ -427,7 +393,6 @@ "interpTarget": 0, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotTakeoff", "state": "inAirStand" } ] }, @@ -436,7 +401,6 @@ "interpTarget": 0, "interpDuration": 6, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotTakeoff", "state": "inAirRun" } ] }, @@ -446,7 +410,6 @@ "interpDuration": 6, "interpType": "snapshotPrev", "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotInAir", "state": "landStandImpact" } ] }, @@ -456,7 +419,6 @@ "interpDuration": 6, "interpType": "snapshotPrev", "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isNotInAir", "state": "landRun" } ] }, @@ -465,7 +427,6 @@ "interpTarget": 6, "interpDuration": 4, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -483,7 +444,6 @@ { "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isTurningRight", "state": "turnRight" }, { "var": "isTurningLeft", "state": "turnLeft" }, - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -497,7 +457,6 @@ "interpTarget": 1, "interpDuration": 7, "transitions": [ - { "var": "isAway", "state": "awayIntro" }, { "var": "isFlying", "state": "fly" }, { "var": "isTakeoffStand", "state": "takeoffStand" }, { "var": "isTakeoffRun", "state": "takeoffRun" }, @@ -754,42 +713,6 @@ } ] }, - { - "id": "awayIntro", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 0.0, - "endFrame": 83.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "away", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 83.0, - "endFrame": 84.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "awayOutro", - "type": "clip", - "data": { - "url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx", - "startFrame": 84.0, - "endFrame": 167.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, { "id": "fly", "type": "clip", diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d83480fa0a..3a0b0998c5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1238,8 +1238,6 @@ Application::~Application() { _physicsEngine->setCharacterController(nullptr); - ModelEntityItem::cleanupLoadedAnimations(); - // remove avatars from physics engine DependencyManager::get()->clearOtherAvatars(); VectorOfMotionStates motionStates; @@ -3198,7 +3196,19 @@ void Application::cameraMenuChanged() { } } +void Application::resetPhysicsReadyInformation() { + // we've changed domains or cleared out caches or something. we no longer know enough about the + // collision information of nearby entities to make running bullet be safe. + _fullSceneReceivedCounter = 0; + _fullSceneCounterAtLastPhysicsCheck = 0; + _nearbyEntitiesCountAtLastPhysicsCheck = 0; + _nearbyEntitiesStabilityCount = 0; + _physicsEnabled = false; +} + + void Application::reloadResourceCaches() { + resetPhysicsReadyInformation(); // Clear entities out of view frustum _viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE)); _viewFrustum.setOrientation(glm::quat()); @@ -3255,21 +3265,34 @@ void Application::update(float deltaTime) { updateLOD(); - if (!_physicsEnabled && _processOctreeStatsCounter > 0) { + if (!_physicsEnabled) { + // we haven't yet enabled physics. we wait until we think we have all the collision information + // for nearby entities before starting bullet up. + quint64 now = usecTimestampNow(); + bool timeout = false; + const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND; + if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) { + timeout = true; + } - // process octree stats packets are sent in between full sends of a scene. - // We keep physics disabled until we've received a full scene and everything near the avatar in that - // scene is ready to compute its collision shape. + if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { + // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway + _lastPhysicsCheckTime = now; + _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; - if (nearbyEntitiesAreReadyForPhysics()) { - _physicsEnabled = true; - getMyAvatar()->updateMotionBehaviorFromMenu(); - } else { - auto characterController = getMyAvatar()->getCharacterController(); - if (characterController) { - // if we have a character controller, disable it here so the avatar doesn't get stuck due to - // a non-loading collision hull. - characterController->setEnabled(false); + // process octree stats packets are sent in between full sends of a scene (this isn't currently true). + // We keep physics disabled until we've received a full scene and everything near the avatar in that + // scene is ready to compute its collision shape. + if (nearbyEntitiesAreReadyForPhysics()) { + _physicsEnabled = true; + getMyAvatar()->updateMotionBehaviorFromMenu(); + } else { + auto characterController = getMyAvatar()->getCharacterController(); + if (characterController) { + // if we have a character controller, disable it here so the avatar doesn't get stuck due to + // a non-loading collision hull. + characterController->setEnabled(false); + } } } } @@ -3421,7 +3444,7 @@ void Application::update(float deltaTime) { getEntities()->getTree()->withWriteLock([&] { PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); - _entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID()); + _entitySimulation.handleOutgoingChanges(outgoingChanges); avatarManager->handleOutgoingChanges(outgoingChanges); }); @@ -4154,7 +4177,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; // reset the environment so that we don't erroneously end up with multiple - _physicsEnabled = false; + resetPhysicsReadyInformation(); // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { @@ -4178,7 +4201,7 @@ void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); clearDomainOctreeDetails(); // disable physics until we have enough information about our new location to not cause craziness. - _physicsEnabled = false; + resetPhysicsReadyInformation(); } @@ -4311,15 +4334,31 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { entityTree->findEntities(box, entities); }); - foreach (EntityItemPointer entity, entities) { - if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); - qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); - return false; - } + // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply + // a heuristic to try to decide when we actually know about all of the nearby entities. + uint32_t nearbyCount = entities.size(); + if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { + _nearbyEntitiesStabilityCount++; + } else { + _nearbyEntitiesStabilityCount = 0; } - return true; + _nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount; + + const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3; + if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) { + // We've seen the same number of nearby entities for several stats packets in a row. assume we've got all + // the local entities. + foreach (EntityItemPointer entity, entities) { + if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); + qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); + return false; + } + } + return true; + } + return false; } int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) { @@ -4336,6 +4375,10 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; statsMessageLength = octreeStats.unpackFromPacket(message); + if (octreeStats.isFullScene()) { + _fullSceneReceivedCounter++; + } + // see if this is the first we've heard of this node... NodeToJurisdictionMap* jurisdiction = nullptr; QString serverType; @@ -4367,8 +4410,6 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer }); }); - _processOctreeStatsCounter++; - return statsMessageLength; } @@ -4499,10 +4540,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { } void Application::setSessionUUID(const QUuid& sessionUUID) const { - // HACK: until we swap the library dependency order between physics and entities - // we cache the sessionID in two distinct places for physics. - Physics::setSessionUUID(sessionUUID); // TODO: remove this one - _physicsEngine->setSessionUUID(sessionUUID); + Physics::setSessionUUID(sessionUUID); } bool Application::askToSetAvatarUrl(const QString& url) { @@ -5158,13 +5196,6 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } -void Application::crashApplication() { - qCDebug(interfaceapp) << "Intentionally crashed Interface"; - QObject* object = nullptr; - bool value = object->isWindowType(); - Q_UNUSED(value); -} - void Application::deadlockApplication() { qCDebug(interfaceapp) << "Intentionally deadlocked Interface"; // Using a loop that will *technically* eventually exit (in ~600 billion years) diff --git a/interface/src/Application.h b/interface/src/Application.h index 0d170bd0c2..6bfad21525 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -271,11 +271,12 @@ public slots: void toggleOverlays(); void setOverlaysVisible(bool visible); + void resetPhysicsReadyInformation(); + void reloadResourceCaches(); void updateHeartbeat() const; - static void crashApplication(); static void deadlockApplication(); void rotationModeChanged() const; @@ -514,7 +515,11 @@ private: std::map> _preRenderLambdas; std::mutex _preRenderLambdasLock; - std::atomic _processOctreeStatsCounter { 0 }; + std::atomic _fullSceneReceivedCounter { 0 }; // how many times have we received a full-scene octree stats packet + uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready + uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready + uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same + quint64 _lastPhysicsCheckTime { 0 }; // when did we last check to see if physics was ready bool _keyboardDeviceHasFocus { true }; diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp new file mode 100644 index 0000000000..1fd534ffac --- /dev/null +++ b/interface/src/CrashReporter.cpp @@ -0,0 +1,135 @@ +// +// CrashReporter.cpp +// interface/src +// +// Created by Ryan Huffman on 11 April 2016. +// Copyright 2016 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 +// + + +#ifdef HAS_BUGSPLAT +#include "CrashReporter.h" + +#include +#include + +#include + +#include + +// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information +// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li +// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so +// that the applicaiton can handle it itself. +// The CAPIHook class referenced in the above article is not openly available, but a similar implementation +// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/ +// The below has been adapted to work with different library and functions. +BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn) +{ + HMODULE lib = LoadLibrary(library); + if (lib == NULL) return FALSE; + void *pOrgEntry = GetProcAddress(lib, function); + if (pOrgEntry == NULL) return FALSE; + + DWORD dwOldProtect = 0; + SIZE_T jmpSize = 5; +#ifdef _M_X64 + jmpSize = 13; +#endif + BOOL bProt = VirtualProtect(pOrgEntry, jmpSize, + PAGE_EXECUTE_READWRITE, &dwOldProtect); + BYTE newJump[20]; + void *pNewFunc = fn; +#ifdef _M_IX86 + DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; + dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32 + DWORD dwNewEntryAddr = (DWORD)pNewFunc; + DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; + // JMP rel32: Jump near, relative, displacement relative to next instruction. + newJump[0] = 0xE9; // JMP rel32 + memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc)); +#elif _M_X64 + // We must use R10 or R11, because these are "scratch" registers + // which need not to be preserved accross function calls + // For more info see: Register Usage for x64 64-Bit + // http://msdn.microsoft.com/en-us/library/ms794547.aspx + // Thanks to Matthew Smith!!! + newJump[0] = 0x49; // MOV R11, ... + newJump[1] = 0xBB; // ... + memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc)); + //pCur += sizeof (ULONG_PTR); + newJump[10] = 0x41; // JMP R11, ... + newJump[11] = 0xFF; // ... + newJump[12] = 0xE3; // ... +#endif + SIZE_T bytesWritten; + BOOL bRet = WriteProcessMemory(GetCurrentProcess(), + pOrgEntry, newJump, jmpSize, &bytesWritten); + + if (bProt != FALSE) + { + DWORD dwBuf; + VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf); + } + return bRet; +} + + +void handleSignal(int signal) { + // Throw so BugSplat can handle + throw(signal); +} + +void handlePureVirtualCall() { + // Throw so BugSplat can handle + throw("ERROR: Pure virtual call"); +} + +void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file, + unsigned int line, uintptr_t pReserved ) { + // Throw so BugSplat can handle + throw("ERROR: Invalid parameter"); +} + +int handleNewError(size_t size) { + // Throw so BugSplat can handle + throw("ERROR: Errors calling new"); +} + +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI noop_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { + return nullptr; +} + +_purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) { + return nullptr; +} + +static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; + +CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version) + : mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS) +{ + signal(SIGSEGV, handleSignal); + signal(SIGABRT, handleSignal); + _set_purecall_handler(handlePureVirtualCall); + _set_invalid_parameter_handler(handleInvalidParameter); + _set_new_mode(1); + _set_new_handler(handleNewError); + + // Disable WER popup + //SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + //_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + // QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that. + if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) { + qWarning() << "Failed to patch _set_purecall_handler"; + } + // Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling. + if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) { + qWarning() << "Failed to patch setUnhandledExceptionFilter"; + } +} +#endif diff --git a/interface/src/CrashReporter.h b/interface/src/CrashReporter.h new file mode 100644 index 0000000000..5f02066a74 --- /dev/null +++ b/interface/src/CrashReporter.h @@ -0,0 +1,32 @@ +// +// CrashReporter.h +// interface/src +// +// Created by Ryan Huffman on 11 April 2016. +// Copyright 2016 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 +// + +#pragma once + +#ifndef hifi_CrashReporter_h +#define hifi_CrashReporter_h + +#include + +#ifdef HAS_BUGSPLAT + +#include + +class CrashReporter { +public: + CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version); + + MiniDmpSender mpSender; +}; + +#endif + +#endif // hifi_CrashReporter_h \ No newline at end of file diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index 8ace11c0a0..1869980270 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "InterfaceActionFactory.h" @@ -66,6 +67,8 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt if (action) { action->deserialize(data); if (action->lifetimeIsOver()) { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during action creation.*"); qDebug() << "InterfaceActionFactory::factoryBA lifetimeIsOver during action creation --" << action->getExpires() << "<" << usecTimestampNow(); return nullptr; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7be498623e..1fa4a6fe29 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -13,8 +13,11 @@ #include #include +#include + #include #include +#include #include #include #include @@ -587,10 +590,41 @@ Menu::Menu() { // Developer > Display Crash Options addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); - // Developer > Crash Application - addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); - // Developer > Deadlock Application - addActionToQMenuAndActionHash(developerMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); + + // Developer > Crash >>> + MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); + + addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction); + connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::pureVirtualCall(); }); }); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree); + connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doubleFree(); }); }); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort); + connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doAbort(); }); }); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference); + connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::nullDeref(); }); }); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess); + connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::outOfBoundsVectorCrash(); }); }); + + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault); + connect(action, &QAction::triggered, qApp, []() { crash::newFault(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::newFault(); }); }); // Developer > Log... addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 2c1d2c9ee7..600632d125 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -66,7 +66,18 @@ namespace MenuOption { const QString CopyAddress = "Copy Address to Clipboard"; const QString CopyPath = "Copy Path to Clipboard"; const QString CoupleEyelids = "Couple Eyelids"; - const QString CrashInterface = "Crash Interface"; + const QString CrashPureVirtualFunction = "Pure Virtual Function Call"; + const QString CrashPureVirtualFunctionThreaded = "Pure Virtual Function Call (threaded)"; + const QString CrashDoubleFree = "Double Free"; + const QString CrashDoubleFreeThreaded = "Double Free (threaded)"; + const QString CrashNullDereference = "Null Dereference"; + const QString CrashNullDereferenceThreaded = "Null Dereference (threaded)"; + const QString CrashAbort = "Abort"; + const QString CrashAbortThreaded = "Abort (threaded)"; + const QString CrashOutOfBoundsVectorAccess = "Out of Bounds Vector Access"; + const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; + const QString CrashNewFault = "New Fault"; + const QString CrashNewFaultThreaded = "New Fault (threaded)"; const QString DeadlockInterface = "Deadlock Interface"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e31a874ba3..2cacb81ce4 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -134,12 +134,9 @@ glm::quat Avatar::getWorldAlignedOrientation () const { } AABox Avatar::getBounds() const { - // Our skeleton models are rigged, and this method call safely produces the static bounds of the model. - // Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed, - // and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the - // real model bounds, if this is unrealistically small. - if (!_skeletonModel->isRenderable()) { - return AABox(getPosition(), getUniformScale()); // approximately 2m tall, scaled to user request. + if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) { + // approximately 2m tall, scaled to user request. + return AABox(getPosition() - glm::vec3(getUniformScale()), getUniformScale() * 2.0f); } return _skeletonModel->getRenderableMeshBound(); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index be56ad12b1..09369dc25a 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -25,26 +25,23 @@ #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" +#include #ifdef HAS_BUGSPLAT #include #include +#include #endif - int main(int argc, const char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes - #if HAS_BUGSPLAT - // Prevent other threads from hijacking the Exception filter, and allocate 4MB up-front that may be useful in - // low-memory scenarios. - static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; - static const char* BUG_SPLAT_DATABASE = "interface_alpha"; - static const char* BUG_SPLAT_APPLICATION_NAME = "Interface"; - MiniDmpSender mpSender { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, qPrintable(BuildInfo::VERSION), - nullptr, BUG_SPLAT_FLAGS }; + static QString BUG_SPLAT_DATABASE = "interface_alpha"; + static QString BUG_SPLAT_APPLICATION_NAME = "Interface"; + CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION }; #endif + disableQtBearerPoll(); // Fixes wifi ping spikes + QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); bool instanceMightBeRunning = true; @@ -168,27 +165,27 @@ int main(int argc, const char* argv[]) { server.removeServer(applicationName); server.listen(applicationName); - QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection); + QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); #ifdef HAS_BUGSPLAT AccountManager& accountManager = AccountManager::getInstance(); - mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); - QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&mpSender](const QString& newUsername) { - mpSender.setDefaultUserName(qPrintable(newUsername)); + crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); + QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { + crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername)); }); // BugSplat WILL NOT work with file paths that do not use OS native separators. auto logger = app.getLogger(); auto logPath = QDir::toNativeSeparators(logger->getFilename()); - mpSender.sendAdditionalFile(qPrintable(logPath)); + crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath)); QMetaObject::Connection connection; - connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&mpSender, &connection](QString newFilename) { + connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) { // We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB // zipped limit, so we disconnect here. QObject::disconnect(connection); auto rolledLogPath = QDir::toNativeSeparators(newFilename); - mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); + crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); }); #endif diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index 7dac0247bd..e8d63a6d99 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -122,7 +122,7 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR DownloadInfoResult GlobalServicesScriptingInterface::getDownloadInfo() { DownloadInfoResult result; - foreach(Resource* resource, ResourceCache::getLoadingRequests()) { + foreach(const auto& resource, ResourceCache::getLoadingRequests()) { result.downloading.append(resource->getProgress() * 100.0f); } result.pending = ResourceCache::getPendingRequestCount(); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 39a3a1f192..ec4b2280b6 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -196,7 +196,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(audioMixerPps, -1); } - QList loadingRequests = ResourceCache::getLoadingRequests(); + auto loadingRequests = ResourceCache::getLoadingRequests(); STAT_UPDATE(downloads, loadingRequests.size()); STAT_UPDATE(downloadLimit, ResourceCache::getRequestLimit()) STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount()); @@ -214,7 +214,7 @@ void Stats::updateStats(bool force) { // If the urls have changed, update the list if (shouldUpdateUrls) { _downloadUrls.clear(); - foreach (Resource* resource, loadingRequests) { + foreach (const auto& resource, loadingRequests) { _downloadUrls << resource->getURL().toString(); } emit downloadUrlsChanged(); diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 70cf7e248f..df39c55e5d 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -568,12 +568,13 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, } AnimNodeLoader::AnimNodeLoader(const QUrl& url) : - _url(url), - _resource(nullptr) { - - _resource = new Resource(url); - connect(_resource, &Resource::loaded, this, &AnimNodeLoader::onRequestDone); - connect(_resource, &Resource::failed, this, &AnimNodeLoader::onRequestError); + _url(url) +{ + _resource = QSharedPointer::create(url); + _resource->setSelf(_resource); + connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); + connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError); + _resource->ensureLoading(); } AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& jsonUrl) { diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index d6fdfa7e2c..6db001c261 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -13,9 +13,9 @@ #include +#include #include #include -#include #include "AnimNode.h" @@ -41,7 +41,8 @@ protected slots: protected: QUrl _url; - Resource* _resource; + QSharedPointer _resource; + private: // no copies diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 02d3f8873e..a502e608e2 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -22,6 +22,7 @@ AnimationCache::AnimationCache(QObject* parent) : { const qint64 ANIMATION_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES; setUnusedResourceCacheSize(ANIMATION_DEFAULT_UNUSED_MAX_SIZE); + setObjectName("AnimationCache"); } AnimationPointer AnimationCache::getAnimation(const QUrl& url) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6a8f190808..a57f0b01d2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -48,36 +48,47 @@ const glm::vec3 DEFAULT_NECK_POS(0.0f, 0.70f, 0.0f); void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - // find an unused AnimClip clipNode - std::shared_ptr clip; - if (_userAnimState == UserAnimState::None || _userAnimState == UserAnimState::B) { - _userAnimState = UserAnimState::A; - clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimA")); - } else if (_userAnimState == UserAnimState::A) { - _userAnimState = UserAnimState::B; - clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimB")); + UserAnimState::ClipNodeEnum clipNodeEnum; + if (_userAnimState.clipNodeEnum == UserAnimState::None || _userAnimState.clipNodeEnum == UserAnimState::B) { + clipNodeEnum = UserAnimState::A; + } else { + clipNodeEnum = UserAnimState::B; } - // set parameters - clip->setLoopFlag(loop); - clip->setStartFrame(firstFrame); - clip->setEndFrame(lastFrame); - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; - clip->setTimeScale(timeScale); - clip->loadURL(url); + if (_animNode) { + // find an unused AnimClip clipNode + std::shared_ptr clip; + if (clipNodeEnum == UserAnimState::A) { + clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimA")); + } else { + clip = std::dynamic_pointer_cast(_animNode->findByName("userAnimB")); + } - _currentUserAnimURL = url; + if (clip) { + // set parameters + clip->setLoopFlag(loop); + clip->setStartFrame(firstFrame); + clip->setEndFrame(lastFrame); + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; + clip->setTimeScale(timeScale); + clip->loadURL(url); + } + } + + // store current user anim state. + _userAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", false); - _animVars.set("userAnimA", _userAnimState == UserAnimState::A); - _animVars.set("userAnimB", _userAnimState == UserAnimState::B); + _animVars.set("userAnimA", clipNodeEnum == UserAnimState::A); + _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } void Rig::restoreAnimation() { - if (_currentUserAnimURL != "") { - _currentUserAnimURL = ""; + if (_userAnimState.clipNodeEnum != UserAnimState::None) { + _userAnimState.clipNodeEnum = UserAnimState::None; + // notify the userAnimStateMachine the desired state. _animVars.set("userAnimNone", true); _animVars.set("userAnimA", false); @@ -1129,6 +1140,14 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { _animNode = nodeIn; _animNode->setSkeleton(_animSkeleton); + + if (_userAnimState.clipNodeEnum != UserAnimState::None) { + // restore the user animation we had before reset. + UserAnimState origState = _userAnimState; + _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; + overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + } + }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 3a27b9304b..cbf4696723 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -289,13 +289,28 @@ public: RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; float _desiredStateAge { 0.0f }; - enum class UserAnimState { - None = 0, - A, - B + + struct UserAnimState { + enum ClipNodeEnum { + None = 0, + A, + B + }; + + UserAnimState() : clipNodeEnum(UserAnimState::None) {} + UserAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : + clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} + + ClipNodeEnum clipNodeEnum; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; }; - UserAnimState _userAnimState { UserAnimState::None }; - QString _currentUserAnimURL; + + UserAnimState _userAnimState; + float _leftHandOverlayAlpha { 0.0f }; float _rightHandOverlayAlpha { 0.0f }; diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index abcdb2da7c..2752c6669f 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -21,6 +21,7 @@ SoundCache::SoundCache(QObject* parent) : { const qint64 SOUND_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES; setUnusedResourceCacheSize(SOUND_DEFAULT_UNUSED_MAX_SIZE); + setObjectName("SoundCache"); } SharedSoundPointer SoundCache::getSound(const QUrl& url) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 879ff01056..7955b20728 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -274,11 +274,11 @@ bool RenderableModelEntityItem::getAnimationFrame() { if (!hasRenderAnimation() || !_jointMappingCompleted) { return false; } - AnimationPointer myAnimation = getAnimation(getRenderAnimationURL()); // FIXME: this could be optimized - if (myAnimation && myAnimation->isLoaded()) { - const QVector& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy - auto& fbxJoints = myAnimation->getGeometry().joints; + if (_animation && _animation->isLoaded()) { + + const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + auto& fbxJoints = _animation->getGeometry().joints; int frameCount = frames.size(); if (frameCount > 0) { @@ -748,6 +748,7 @@ glm::vec3 RenderableModelEntityItem::getAbsoluteJointTranslationInObjectFrame(in bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { bool result = false; _jointDataLock.withWriteLock([&] { + _jointRotationsExplicitlySet = true; resizeJointArrays(); if (index >= 0 && index < _absoluteJointRotationsInObjectFrame.size() && _absoluteJointRotationsInObjectFrame[index] != rotation) { @@ -764,6 +765,7 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) { bool result = false; _jointDataLock.withWriteLock([&] { + _jointTranslationsExplicitlySet = true; resizeJointArrays(); if (index >= 0 && index < _absoluteJointTranslationsInObjectFrame.size() && _absoluteJointTranslationsInObjectFrame[index] != translation) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index bf55c829e9..59208d209d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -82,7 +82,7 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - // These operate on a copy of the renderAnimationProperties, so they can be accessed + // These operate on a copy of the animationProperties, so they can be accessed // without having the entityTree lock. bool hasRenderAnimation() const { return !_renderAnimationProperties.getURL().isEmpty(); } const QString& getRenderAnimationURL() const { return _renderAnimationProperties.getURL(); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6731bcc9fb..1f5db65089 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -24,6 +24,7 @@ #include #include // usecTimestampNow() #include +#include #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" @@ -516,8 +517,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef EntityTreePointer tree = getTree(); if (tree && tree->isDeletedEntity(_id)) { #ifdef WANT_DEBUG - qDebug() << "Received packet for previously deleted entity [" << _id << "] ignoring. " - "(inside " << __FUNCTION__ << ")"; + qCDebug(entities) << "Received packet for previously deleted entity [" << _id << "] ignoring. " + "(inside " << __FUNCTION__ << ")"; #endif ignoreServerPacket = true; } @@ -1685,7 +1686,7 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi _allActionsDataCache = newDataCache; _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; } else { - qDebug() << "EntityItem::addActionInternal -- serializeActions failed"; + qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; } return success; } @@ -1706,7 +1707,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; } else { - qDebug() << "EntityItem::updateAction failed"; + qCDebug(entities) << "EntityItem::updateAction failed"; } }); return success; @@ -1777,7 +1778,7 @@ void EntityItem::deserializeActionsInternal() { quint64 now = usecTimestampNow(); if (!_element) { - qDebug() << "EntityItem::deserializeActionsInternal -- no _element"; + qCDebug(entities) << "EntityItem::deserializeActionsInternal -- no _element"; return; } @@ -1805,14 +1806,13 @@ void EntityItem::deserializeActionsInternal() { continue; } - updated << actionID; - if (_objectActions.contains(actionID)) { EntityActionPointer action = _objectActions[actionID]; // TODO: make sure types match? there isn't currently a way to // change the type of an existing action. action->deserialize(serializedAction); action->locallyAddedButNotYetReceived = false; + updated << actionID; } else { auto actionFactory = DependencyManager::get(); EntityItemPointer entity = getThisPointer(); @@ -1820,8 +1820,13 @@ void EntityItem::deserializeActionsInternal() { if (action) { entity->addActionInternal(simulation, action); action->locallyAddedButNotYetReceived = false; + updated << actionID; } else { - qDebug() << "EntityItem::deserializeActionsInternal -- action creation failed"; + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex(".*action creation failed for.*"); + qCDebug(entities) << "EntityItem::deserializeActionsInternal -- action creation failed for" + << getID() << getName(); + removeActionInternal(actionID, nullptr); } } } @@ -1897,7 +1902,8 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const { serializedActionsStream << serializedActions; if (result.size() >= _maxActionsDataSize) { - qDebug() << "EntityItem::serializeActions size is too large -- " << result.size() << ">=" << _maxActionsDataSize; + qCDebug(entities) << "EntityItem::serializeActions size is too large -- " + << result.size() << ">=" << _maxActionsDataSize; success = false; return; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 32aac1efe5..92849d6e2f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -541,13 +541,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); - - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); - // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 7f87c4e0b1..8270dc7e69 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -699,7 +699,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case glm::vec3 penetration; - if (success && entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { + if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { glm::vec3 dimensions = entity->getDimensions(); @@ -764,7 +764,7 @@ void EntityTreeElement::getEntities(const AACube& cube, QVector // // If the entities AABox touches the search cube then consider it to be found - if (success && entityBox.touches(box)) { + if (!success || entityBox.touches(box)) { foundEntities.push_back(entity); } }); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index e5511c0b25..2ec1f5e9ed 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -205,37 +205,18 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit } -QMap ModelEntityItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s) - -void ModelEntityItem::cleanupLoadedAnimations() { - foreach(AnimationPointer animation, _loadedAnimations) { - animation.clear(); - } - _loadedAnimations.clear(); -} - -AnimationPointer ModelEntityItem::getAnimation(const QString& url) { - AnimationPointer animation; - - // if we don't already have this model then create it and initialize it - if (_loadedAnimations.find(url) == _loadedAnimations.end()) { - animation = DependencyManager::get()->getAnimation(url); - _loadedAnimations[url] = animation; - } else { - animation = _loadedAnimations[url]; - } - return animation; -} - void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { // if we don't have animation, or we're already joint mapped then bail early if (!hasAnimation() || jointsMapped()) { return; } - AnimationPointer myAnimation = getAnimation(_animationProperties.getURL()); - if (myAnimation && myAnimation->isLoaded()) { - QStringList animationJointNames = myAnimation->getJointNames(); + if (!_animation || _animation->getURL().toString() != getAnimationURL()) { + _animation = DependencyManager::get()->getAnimation(getAnimationURL()); + } + + if (_animation && _animation->isLoaded()) { + QStringList animationJointNames = _animation->getJointNames(); if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { _jointMapping.resize(modelJointNames.size()); @@ -404,6 +385,7 @@ void ModelEntityItem::resizeJointArrays(int newSize) { void ModelEntityItem::setJointRotations(const QVector& rotations) { _jointDataLock.withWriteLock([&] { + _jointRotationsExplicitlySet = rotations.size() > 0; resizeJointArrays(rotations.size()); for (int index = 0; index < rotations.size(); index++) { if (_absoluteJointRotationsInObjectFrameSet[index]) { @@ -416,6 +398,7 @@ void ModelEntityItem::setJointRotations(const QVector& rotations) { void ModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { _jointDataLock.withWriteLock([&] { + _jointRotationsExplicitlySet = rotationsSet.size() > 0; resizeJointArrays(rotationsSet.size()); for (int index = 0; index < rotationsSet.size(); index++) { _absoluteJointRotationsInObjectFrameSet[index] = rotationsSet[index]; @@ -425,6 +408,7 @@ void ModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { void ModelEntityItem::setJointTranslations(const QVector& translations) { _jointDataLock.withWriteLock([&] { + _jointTranslationsExplicitlySet = translations.size() > 0; resizeJointArrays(translations.size()); for (int index = 0; index < translations.size(); index++) { if (_absoluteJointTranslationsInObjectFrameSet[index]) { @@ -437,6 +421,7 @@ void ModelEntityItem::setJointTranslations(const QVector& translation void ModelEntityItem::setJointTranslationsSet(const QVector& translationsSet) { _jointDataLock.withWriteLock([&] { + _jointTranslationsExplicitlySet = translationsSet.size() > 0; resizeJointArrays(translationsSet.size()); for (int index = 0; index < translationsSet.size(); index++) { _absoluteJointTranslationsInObjectFrameSet[index] = translationsSet[index]; @@ -447,7 +432,9 @@ void ModelEntityItem::setJointTranslationsSet(const QVector& translationsS QVector ModelEntityItem::getJointRotations() const { QVector result; _jointDataLock.withReadLock([&] { - result = _absoluteJointRotationsInObjectFrame; + if (_jointRotationsExplicitlySet) { + result = _absoluteJointRotationsInObjectFrame; + } }); return result; } @@ -455,15 +442,20 @@ QVector ModelEntityItem::getJointRotations() const { QVector ModelEntityItem::getJointRotationsSet() const { QVector result; _jointDataLock.withReadLock([&] { - result = _absoluteJointRotationsInObjectFrameSet; + if (_jointRotationsExplicitlySet) { + result = _absoluteJointRotationsInObjectFrameSet; + } }); + return result; } QVector ModelEntityItem::getJointTranslations() const { QVector result; _jointDataLock.withReadLock([&] { - result = _absoluteJointTranslationsInObjectFrame; + if (_jointTranslationsExplicitlySet) { + result = _absoluteJointTranslationsInObjectFrame; + } }); return result; } @@ -471,7 +463,9 @@ QVector ModelEntityItem::getJointTranslations() const { QVector ModelEntityItem::getJointTranslationsSet() const { QVector result; _jointDataLock.withReadLock([&] { - result = _absoluteJointTranslationsInObjectFrameSet; + if (_jointTranslationsExplicitlySet) { + result = _absoluteJointTranslationsInObjectFrameSet; + } }); return result; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index d0e0909b27..29730bf4df 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -105,6 +105,7 @@ public: void mapJoints(const QStringList& modelJointNames); bool jointsMapped() const { return _jointMappingURL == getAnimationURL() && _jointMappingCompleted; } + AnimationPointer getAnimation() const { return _animation; } bool getAnimationIsPlaying() const { return _animationLoop.getRunning(); } float getAnimationCurrentFrame() const { return _animationLoop.getCurrentFrame(); } float getAnimationFPS() const { return _animationLoop.getFPS(); } @@ -115,8 +116,6 @@ public: virtual bool shouldBePhysical() const; - static void cleanupLoadedAnimations(); - virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } @@ -140,9 +139,13 @@ protected: // they aren't currently updated from data in the model/rig, and they don't have a direct effect // on what's rendered. ReadWriteLockable _jointDataLock; + + bool _jointRotationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations QVector _absoluteJointRotationsInObjectFrame; QVector _absoluteJointRotationsInObjectFrameSet; // ever set? QVector _absoluteJointRotationsInObjectFrameDirty; // needs a relay to model/rig? + + bool _jointTranslationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations QVector _absoluteJointTranslationsInObjectFrame; QVector _absoluteJointTranslationsInObjectFrameSet; // ever set? QVector _absoluteJointTranslationsInObjectFrameDirty; // needs a relay to model/rig? @@ -156,6 +159,7 @@ protected: QUrl _parsedModelURL; QString _compoundShapeURL; + AnimationPointer _animation; AnimationPropertyGroup _animationProperties; AnimationLoop _animationLoop; @@ -168,11 +172,6 @@ protected: bool _jointMappingCompleted; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints QString _jointMappingURL; - - static AnimationPointer getAnimation(const QString& url); - static QMap _loadedAnimations; - static AnimationCache _animationCache; - }; #endif // hifi_ModelEntityItem_h diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index b8deef9f27..a565b249d8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -46,6 +46,7 @@ private slots: private: GeometryResource::Pointer _geometryResource; + QMetaObject::Connection _connection; }; void GeometryMappingResource::downloadFinished(const QByteArray& data) { @@ -77,21 +78,28 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { if (_geometryResource->isLoaded()) { onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); } else { - connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded); + if (_connection) { + disconnect(_connection); + } + + _connection = connect(_geometryResource.data(), &Resource::finished, + this, &GeometryMappingResource::onGeometryMappingLoaded); } } } void GeometryMappingResource::onGeometryMappingLoaded(bool success) { - if (success) { + if (success && _geometryResource) { _geometry = _geometryResource->_geometry; _shapes = _geometryResource->_shapes; _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; - } - // Avoid holding onto extra references - _geometryResource.reset(); + // Avoid holding onto extra references + _geometryResource.reset(); + // Make sure connection will not trigger again + disconnect(_connection); // FIXME Should not have to do this + } finishedLoading(success); } @@ -226,6 +234,7 @@ void GeometryDefinitionResource::setGeometryDefinition(void* fbxGeometry) { ModelCache::ModelCache() { const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE); + setObjectName("ModelCache"); } QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index f314f6cb06..e482c20b11 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -34,6 +34,7 @@ TextureCache::TextureCache() { const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; setUnusedResourceCacheSize(TEXTURE_DEFAULT_UNUSED_MAX_SIZE); + setObjectName("TextureCache"); } TextureCache::~TextureCache() { diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 0011f3da76..8298a2dad4 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -21,7 +21,7 @@ #include "HifiSockAddr.h" const QString ICE_SERVER_HOSTNAME = "localhost"; -const int ICE_SERVER_DEFAULT_PORT = 7337; +const quint16 ICE_SERVER_DEFAULT_PORT = 7337; const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; const int MAX_ICE_CONNECTION_ATTEMPTS = 5; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 2273902263..9611e2ec65 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -20,6 +20,7 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" +#include "NodeList.h" #include "ResourceCache.h" @@ -27,32 +28,80 @@ (((x) > (max)) ? (max) :\ (x))) -ResourceCache::ResourceCache(QObject* parent) : - QObject(parent) { +ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { + auto& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, + this, &ResourceCache::clearATPAssets, Qt::DirectConnection); } ResourceCache::~ResourceCache() { clearUnusedResource(); } +void ResourceCache::clearATPAssets() { + { + QWriteLocker locker(&_resourcesLock); + for (auto& url : _resources.keys()) { + // If this is an ATP resource + if (url.scheme() == URL_SCHEME_ATP) { + + // Remove it from the resource hash + auto resource = _resources.take(url); + if (auto strongRef = resource.lock()) { + // Make sure the resource won't reinsert itself + strongRef->setCache(nullptr); + } + } + } + } + { + QWriteLocker locker(&_unusedResourcesLock); + for (auto& resource : _unusedResources.values()) { + if (resource->getURL().scheme() == URL_SCHEME_ATP) { + _unusedResources.remove(resource->getLRUKey()); + } + } + } + { + QWriteLocker locker(&_resourcesToBeGottenLock); + for (auto& url : _resourcesToBeGotten) { + if (url.scheme() == URL_SCHEME_ATP) { + _resourcesToBeGotten.removeAll(url); + } + } + } + + +} + void ResourceCache::refreshAll() { // Clear all unused resources so we don't have to reload them clearUnusedResource(); resetResourceCounters(); - + + _resourcesLock.lockForRead(); + auto resourcesCopy = _resources; + _resourcesLock.unlock(); + // Refresh all remaining resources in use - foreach (auto resource, _resources) { - if (!resource.isNull()) { - resource.data()->refresh(); + foreach (QSharedPointer resource, resourcesCopy) { + if (resource) { + resource->refresh(); } } } void ResourceCache::refresh(const QUrl& url) { - QSharedPointer resource = _resources.value(url); - if (!resource.isNull()) { + QSharedPointer resource; + { + QReadLocker locker(&_resourcesLock); + resource = _resources.value(url).lock(); + } + + if (resource) { resource->refresh(); } else { + QWriteLocker locker(&_resourcesLock); _resources.remove(url); resetResourceCounters(); } @@ -103,8 +152,12 @@ void ResourceCache::checkAsynchronousGets() { QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { - QSharedPointer resource = _resources.value(url); - if (!resource.isNull()) { + QSharedPointer resource; + { + QReadLocker locker(&_resourcesLock); + resource = _resources.value(url).lock(); + } + if (resource) { removeUnusedResource(resource); return resource; } @@ -123,7 +176,10 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); resource->setSelf(resource); resource->setCache(this); - _resources.insert(url, resource); + { + QWriteLocker locker(&_resourcesLock); + _resources.insert(url, resource); + } removeUnusedResource(resource); resource->ensureLoading(); @@ -137,21 +193,29 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) { } void ResourceCache::addUnusedResource(const QSharedPointer& resource) { - // If it doesn't fit or its size is unknown, leave the cache alone. + // If it doesn't fit or its size is unknown, remove it from the cache. if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) { resource->setCache(nullptr); + + _totalResourcesSize -= resource->getBytes(); + _resources.remove(resource->getURL()); + resetResourceCounters(); + return; } reserveUnusedResource(resource->getBytes()); resource->setLRUKey(++_lastLRUKey); - _unusedResources.insert(resource->getLRUKey(), resource); _unusedResourcesSize += resource->getBytes(); resetResourceCounters(); + + QWriteLocker locker(&_unusedResourcesLock); + _unusedResources.insert(resource->getLRUKey(), resource); } void ResourceCache::removeUnusedResource(const QSharedPointer& resource) { + QWriteLocker locker(&_unusedResourcesLock); if (_unusedResources.contains(resource->getLRUKey())) { _unusedResources.remove(resource->getLRUKey()); _unusedResourcesSize -= resource->getBytes(); @@ -160,6 +224,7 @@ void ResourceCache::removeUnusedResource(const QSharedPointer& resourc } void ResourceCache::reserveUnusedResource(qint64 resourceSize) { + QWriteLocker locker(&_unusedResourcesLock); while (!_unusedResources.empty() && _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { // unload the oldest resource @@ -179,6 +244,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { void ResourceCache::clearUnusedResource() { // the unused resources may themselves reference resources that will be added to the unused // list on destruction, so keep clearing until there are no references left + QWriteLocker locker(&_unusedResourcesLock); while (!_unusedResources.isEmpty()) { foreach (const QSharedPointer& resource, _unusedResources) { resource->setCache(nullptr); @@ -198,19 +264,28 @@ void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize emit dirty(); } -void ResourceCacheSharedItems::appendActiveRequest(Resource* resource) { +void ResourceCacheSharedItems::appendActiveRequest(QWeakPointer resource) { Lock lock(_mutex); _loadingRequests.append(resource); } -void ResourceCacheSharedItems::appendPendingRequest(Resource* resource) { +void ResourceCacheSharedItems::appendPendingRequest(QWeakPointer resource) { Lock lock(_mutex); _pendingRequests.append(resource); } -QList> ResourceCacheSharedItems::getPendingRequests() const { - Lock lock(_mutex); - return _pendingRequests; +QList> ResourceCacheSharedItems::getPendingRequests() { + QList> result; + + { + Lock lock(_mutex); + foreach(QSharedPointer resource, _pendingRequests) { + if (resource) { + result.append(resource); + } + } + } + return result; } uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { @@ -218,23 +293,33 @@ uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { return _pendingRequests.size(); } -QList ResourceCacheSharedItems::getLoadingRequests() const { - Lock lock(_mutex); - return _loadingRequests; +QList> ResourceCacheSharedItems::getLoadingRequests() { + QList> result; + + { + Lock lock(_mutex); + foreach(QSharedPointer resource, _loadingRequests) { + if (resource) { + result.append(resource); + } + } + } + return result; } -void ResourceCacheSharedItems::removeRequest(Resource* resource) { +void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { Lock lock(_mutex); - _loadingRequests.removeOne(resource); + _loadingRequests.removeAll(resource); } -Resource* ResourceCacheSharedItems::getHighestPendingRequest() { +QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { Lock lock(_mutex); // look for the highest priority pending request int highestIndex = -1; float highestPriority = -FLT_MAX; + QSharedPointer highestResource; for (int i = 0; i < _pendingRequests.size();) { - Resource* resource = _pendingRequests.at(i).data(); + auto resource = _pendingRequests.at(i).lock(); if (!resource) { _pendingRequests.removeAt(i); continue; @@ -243,16 +328,25 @@ Resource* ResourceCacheSharedItems::getHighestPendingRequest() { if (priority >= highestPriority) { highestPriority = priority; highestIndex = i; + highestResource = resource; } i++; } if (highestIndex >= 0) { - return _pendingRequests.takeAt(highestIndex); + _pendingRequests.takeAt(highestIndex); } - return nullptr; + return highestResource; } -bool ResourceCache::attemptRequest(Resource* resource) { +QList> ResourceCache::getLoadingRequests() { + return DependencyManager::get()->getLoadingRequests(); +} + +int ResourceCache::getPendingRequestCount() { + return DependencyManager::get()->getPendingRequestsCount(); +} + +bool ResourceCache::attemptRequest(QSharedPointer resource) { auto sharedItems = DependencyManager::get(); if (_requestsActive >= _requestLimit) { @@ -267,7 +361,7 @@ bool ResourceCache::attemptRequest(Resource* resource) { return true; } -void ResourceCache::requestCompleted(Resource* resource) { +void ResourceCache::requestCompleted(QWeakPointer resource) { auto sharedItems = DependencyManager::get(); sharedItems->removeRequest(resource); --_requestsActive; @@ -291,11 +385,6 @@ Resource::Resource(const QUrl& url, bool delayLoad) : _request(nullptr) { init(); - - // start loading immediately unless instructed otherwise - if (!(_startedLoading || delayLoad)) { - QTimer::singleShot(0, this, &Resource::ensureLoading); - } } Resource::~Resource() { @@ -303,7 +392,7 @@ Resource::~Resource() { _request->disconnect(this); _request->deleteLater(); _request = nullptr; - ResourceCache::requestCompleted(this); + ResourceCache::requestCompleted(_self); } } @@ -356,7 +445,7 @@ void Resource::refresh() { _request->disconnect(this); _request->deleteLater(); _request = nullptr; - ResourceCache::requestCompleted(this); + ResourceCache::requestCompleted(_self); } init(); @@ -401,7 +490,7 @@ void Resource::init() { void Resource::attemptRequest() { _startedLoading = true; - ResourceCache::attemptRequest(this); + ResourceCache::attemptRequest(_self); } void Resource::finishedLoading(bool success) { @@ -422,18 +511,22 @@ void Resource::setSize(const qint64& bytes) { } void Resource::reinsert() { + QWriteLocker locker(&_cache->_resourcesLock); _cache->_resources.insert(_url, _self); } void Resource::makeRequest() { - Q_ASSERT(!_request); + if (_request) { + _request->disconnect(); + _request->deleteLater(); + } _request = ResourceManager::createResourceRequest(this, _activeUrl); if (!_request) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); - ResourceCache::requestCompleted(this); + ResourceCache::requestCompleted(_self); finishedLoading(false); return; } @@ -465,7 +558,7 @@ void Resource::handleReplyFinished() { return; } - ResourceCache::requestCompleted(this); + ResourceCache::requestCompleted(_self); auto result = _request->getResult(); if (result == ResourceRequest::Success) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 84eba1cdc0..aea1e91099 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -62,21 +62,20 @@ class ResourceCacheSharedItems : public Dependency { using Mutex = std::mutex; using Lock = std::unique_lock; public: - void appendPendingRequest(Resource* newRequest); - void appendActiveRequest(Resource* newRequest); - void removeRequest(Resource* doneRequest); - QList> getPendingRequests() const; + void appendPendingRequest(QWeakPointer newRequest); + void appendActiveRequest(QWeakPointer newRequest); + void removeRequest(QWeakPointer doneRequest); + QList> getPendingRequests(); uint32_t getPendingRequestsCount() const; - QList getLoadingRequests() const; - Resource* getHighestPendingRequest(); + QList> getLoadingRequests(); + QSharedPointer getHighestPendingRequest(); private: - ResourceCacheSharedItems() { } - virtual ~ResourceCacheSharedItems() { } + ResourceCacheSharedItems() = default; mutable Mutex _mutex; - QList> _pendingRequests; - QList _loadingRequests; + QList> _pendingRequests; + QList> _loadingRequests; }; @@ -105,13 +104,11 @@ public: void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize); qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; } - static const QList getLoadingRequests() - { return DependencyManager::get()->getLoadingRequests(); } + static QList> getLoadingRequests(); - static int getPendingRequestCount() - { return DependencyManager::get()->getPendingRequestsCount(); } + static int getPendingRequestCount(); - ResourceCache(QObject* parent = NULL); + ResourceCache(QObject* parent = nullptr); virtual ~ResourceCache(); void refreshAll(); @@ -126,6 +123,9 @@ public slots: protected slots: void updateTotalSize(const qint64& oldSize, const qint64& newSize); +private slots: + void clearATPAssets(); + protected: /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable @@ -143,8 +143,8 @@ protected: /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading /// \return true if the resource began loading, otherwise false if the resource is in the pending queue - static bool attemptRequest(Resource* resource); - static void requestCompleted(Resource* resource); + static bool attemptRequest(QSharedPointer resource); + static void requestCompleted(QWeakPointer resource); static bool attemptHighestPriorityRequest(); private: @@ -154,6 +154,7 @@ private: void clearUnusedResource(); void resetResourceCounters(); + QReadWriteLock _resourcesLock { QReadWriteLock::Recursive }; QHash> _resources; int _lastLRUKey = 0; @@ -161,7 +162,7 @@ private: static int _requestsActive; void getResourceAsynchronously(const QUrl& url); - QReadWriteLock _resourcesToBeGottenLock; + QReadWriteLock _resourcesToBeGottenLock { QReadWriteLock::Recursive }; QQueue _resourcesToBeGotten; std::atomic _numTotalResources { 0 }; @@ -171,6 +172,7 @@ private: std::atomic _unusedResourcesSize { 0 }; qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; + QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive }; QMap> _unusedResources; }; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 8fc8e160b0..f33dbc161d 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -110,6 +110,9 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q } Q_ASSERT(request); + if (parent) { + QObject::connect(parent, &QObject::destroyed, request, &QObject::deleteLater); + } request->moveToThread(&_thread); return request; } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index df1a6f1bec..e4aab94090 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -34,8 +34,8 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken << PacketType::DomainSettingsRequest << PacketType::DomainSettings << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat - << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied - << PacketType::AssignmentClientStatus << PacketType::StopNode + << PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply + << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; const QSet RELIABLE_PACKETS = QSet(); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index a2bd0d4d2d..b98a87e439 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -93,7 +93,8 @@ public: MessagesUnsubscribe, ICEServerHeartbeatDenied, AssetMappingOperation, - AssetMappingOperationReply + AssetMappingOperationReply, + ICEServerHeartbeatACK }; }; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a9dcb4a16c..cd6c54675d 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -94,7 +94,7 @@ void EntityMotionState::updateServerPhysicsVariables() { } // virtual -bool EntityMotionState::handleEasyChanges(uint32_t& flags) { +void EntityMotionState::handleEasyChanges(uint32_t& flags) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags); @@ -137,8 +137,6 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) { if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { _body->activate(); } - - return true; } @@ -223,7 +221,6 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); } @@ -255,11 +252,13 @@ btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } -bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { +bool EntityMotionState::isCandidateForOwnership() const { assert(_body); assert(_entity); assert(entityTreeIsLocked()); - return _outgoingPriority != 0 || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit(); + return _outgoingPriority != 0 + || Physics::getSessionUUID() == _entity->getSimulatorID() + || _entity->actionDataNeedsTransmit(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -384,7 +383,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } -bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID) { +bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. assert(_entity); @@ -399,7 +398,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s return true; } - if (_entity->getSimulatorID() != sessionID) { + if (_entity->getSimulatorID() != Physics::getSessionUUID()) { // we don't own the simulation bool shouldBid = _outgoingPriority > 0 && // but we would like to own it and usecTimestampNow() > _nextOwnershipBid; // it is time to bid again @@ -415,7 +414,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s return remoteSimulationOutOfSync(simulationStep); } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { assert(_entity); assert(entityTreeIsLocked()); @@ -514,9 +513,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); _outgoingPriority = 0; - } else if (sessionID != _entity->getSimulatorID()) { + } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { // we don't own the simulation for this entity yet, but we're sending a bid for it - properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); + properties.setSimulationOwner(Physics::getSessionUUID(), + glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; _outgoingPriority = 0; // reset outgoing priority whenever we bid } else if (_outgoingPriority != _entity->getSimulationPriority()) { @@ -526,7 +526,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.clearSimulationOwner(); } else { // we just need to change the priority - properties.setSimulationOwner(sessionID, _outgoingPriority); + properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); } } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index ac16ec6d5d..938f3e84c1 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,7 +29,7 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(); - virtual bool handleEasyChanges(uint32_t& flags) override; + virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; /// \return PhysicsMotionType based on params set in EntityItem @@ -43,10 +43,10 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans) override; - bool isCandidateForOwnership(const QUuid& sessionID) const; + bool isCandidateForOwnership() const; bool remoteSimulationOutOfSync(uint32_t simulationStep); - bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID); - void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); + bool shouldSendUpdate(uint32_t simulationStep); + void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() override; virtual void clearIncomingDirtyFlags() override; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 482c3146f8..0468e02cb8 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -164,7 +164,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -bool ObjectMotionState::handleEasyChanges(uint32_t& flags) { +void ObjectMotionState::handleEasyChanges(uint32_t& flags) { if (flags & Simulation::DIRTY_POSITION) { btTransform worldTrans = _body->getWorldTransform(); btVector3 newPosition = glmToBullet(getObjectPosition()); @@ -183,6 +183,10 @@ bool ObjectMotionState::handleEasyChanges(uint32_t& flags) { worldTrans.setRotation(newRotation); } _body->setWorldTransform(worldTrans); + if (!(flags & HARD_DIRTY_PHYSICS_FLAGS) && _body->isStaticObject()) { + // force activate static body so its Aabb is updated later + _body->activate(true); + } } else if (flags & Simulation::DIRTY_ROTATION) { btTransform worldTrans = _body->getWorldTransform(); btQuaternion newRotation = glmToBullet(getObjectRotation()); @@ -192,6 +196,10 @@ bool ObjectMotionState::handleEasyChanges(uint32_t& flags) { } worldTrans.setRotation(newRotation); _body->setWorldTransform(worldTrans); + if (!(flags & HARD_DIRTY_PHYSICS_FLAGS) && _body->isStaticObject()) { + // force activate static body so its Aabb is updated later + _body->activate(true); + } } if (flags & Simulation::DIRTY_LINEAR_VELOCITY) { @@ -232,8 +240,6 @@ bool ObjectMotionState::handleEasyChanges(uint32_t& flags) { if (flags & Simulation::DIRTY_MASS) { updateBodyMassProperties(); } - - return true; } bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index bb78eb12d6..4293df6b3f 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -50,11 +50,12 @@ const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_MOTION_TY Simulation::DIRTY_COLLISION_GROUP); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES | Simulation::DIRTY_MASS | Simulation::DIRTY_MATERIAL | - Simulation::DIRTY_SIMULATOR_ID | Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); + Simulation::DIRTY_SIMULATOR_ID | Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY | + Simulation::DIRTY_PHYSICS_ACTIVATION); + // These are the set of incoming flags that the PhysicsEngine needs to hear about: -const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | - Simulation::DIRTY_PHYSICS_ACTIVATION); +const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS); // These are the outgoing flags that the PhysicsEngine can affect: const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES; @@ -80,7 +81,7 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual bool handleEasyChanges(uint32_t& flags); + virtual void handleEasyChanges(uint32_t& flags); virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 2d219915c8..70ca512646 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -251,7 +251,7 @@ void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) _pendingChanges.clear(); } -void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) { +void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates) { QMutexLocker lock(&_mutex); // walk the motionStates looking for those that correspond to entities @@ -261,7 +261,7 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& EntityMotionState* entityState = static_cast(state); EntityItemPointer entity = entityState->getEntity(); assert(entity.get()); - if (entityState->isCandidateForOwnership(sessionID)) { + if (entityState->isCandidateForOwnership()) { _outgoingChanges.insert(entityState); } _entitiesToSort.insert(entity); @@ -272,7 +272,7 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& if (_lastStepSendPackets != numSubsteps) { _lastStepSendPackets = numSubsteps; - if (sessionID.isNull()) { + if (Physics::getSessionUUID().isNull()) { // usually don't get here, but if so --> nothing to do _outgoingChanges.clear(); return; @@ -282,12 +282,12 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; - if (!state->isCandidateForOwnership(sessionID)) { + if (!state->isCandidateForOwnership()) { // prune stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { + } else if (state->shouldSendUpdate(numSubsteps)) { // update - state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); + state->sendUpdate(_entityPacketSender, numSubsteps); ++stateItr; } else { ++stateItr; diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 7138fb924c..935ee57115 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -54,7 +54,7 @@ public: void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); - void handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID); + void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 0040c19c3d..54419a9c1a 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -20,7 +20,6 @@ PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), - _sessionID(), _myAvatarController(nullptr) { } @@ -50,6 +49,13 @@ void PhysicsEngine::init() { // default gravity of the world is zero, so each object must specify its own gravity // TODO: set up gravity zones _dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f)); + + // By default Bullet will update the Aabb's of all objects every frame, even statics. + // This can waste CPU cycles so we configure Bullet to only update ACTIVE objects here. + // However, this means when a static object is moved we must manually update its Aabb + // in order for its broadphase collision queries to work correctly. Look at how we use + // _activeStaticBodies to track and update the Aabb's of moved static objects. + _dynamicsWorld->setForceUpdateAllAabbs(false); } } @@ -189,12 +195,18 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob stillNeedChange.push_back(object); } } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - if (object->handleEasyChanges(flags)) { - object->clearIncomingDirtyFlags(); - } else { - stillNeedChange.push_back(object); - } + object->handleEasyChanges(flags); + object->clearIncomingDirtyFlags(); } + if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { + _activeStaticBodies.push_back(object->getRigidBody()); + } + } + // active static bodies have changed (in an Easy way) and need their Aabbs updated + // but we've configured Bullet to NOT update them automatically (for improved performance) + // so we must do it ourselves + for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { + _dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]); } return stillNeedChange; } @@ -286,20 +298,20 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const ObjectMotionState* motionStateB = static_cast(objectB->getUserPointer()); if (motionStateB && - ((motionStateA && motionStateA->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || + ((motionStateA && motionStateA->getSimulatorID() == Physics::getSessionUUID() && !objectA->isStaticObject()) || (objectA == characterObject))) { // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. - if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != _sessionID) { + if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != Physics::getSessionUUID()) { quint8 priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateB->bump(priorityA); } } else if (motionStateA && - ((motionStateB && motionStateB->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || + ((motionStateB && motionStateB->getSimulatorID() == Physics::getSessionUUID() && !objectB->isStaticObject()) || (objectB == characterObject))) { // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. - if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != _sessionID) { + if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != Physics::getSessionUUID()) { quint8 priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateA->bump(priorityB); } @@ -333,7 +345,7 @@ void PhysicsEngine::updateContactMap() { _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0)); } - if (!_sessionID.isNull()) { + if (!Physics::getSessionUUID().isNull()) { doOwnershipInfection(objectA, objectB); } } @@ -388,6 +400,12 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { const VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() { BT_PROFILE("copyOutgoingChanges"); + // Bullet will not deactivate static objects (it doesn't expect them to be active) + // so we must deactivate them ourselves + for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { + _activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING); + } + _activeStaticBodies.clear(); _dynamicsWorld->synchronizeMotionStates(); _hasOutgoingChanges = false; return _dynamicsWorld->getChangedMotionStates(); diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index f644d6f5b2..72a41b391a 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -13,9 +13,9 @@ #define hifi_PhysicsEngine_h #include +#include #include -#include #include #include @@ -41,7 +41,7 @@ public: }; typedef std::map ContactMap; -typedef QVector CollisionEvents; +typedef std::vector CollisionEvents; class PhysicsEngine { public: @@ -87,8 +87,6 @@ public: void removeAction(const QUuid actionID); void forEachAction(std::function actor); - void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } - private: void addObjectToDynamicsWorld(ObjectMotionState* motionState); @@ -110,9 +108,9 @@ private: ContactMap _contactMap; CollisionEvents _collisionEvents; QHash _objectActions; + std::vector _activeStaticBodies; glm::vec3 _originOffset; - QUuid _sessionID; CharacterController* _myAvatarController; @@ -121,7 +119,6 @@ private: bool _dumpNextStats = false; bool _hasOutgoingChanges = false; - }; typedef std::shared_ptr PhysicsEnginePointer; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 81e9378815..9c85952107 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -54,9 +54,6 @@ TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, } } -TextRenderer3D::~TextRenderer3D() { -} - glm::vec2 TextRenderer3D::computeExtent(const QString& str) const { if (_font) { return _font->computeExtent(str); diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 9d48ca1a6c..175802ef6e 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -12,11 +12,10 @@ #ifndef hifi_TextRenderer3D_h #define hifi_TextRenderer3D_h +#include #include #include - - namespace gpu { class Batch; } @@ -34,8 +33,6 @@ public: static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE, bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1); - ~TextRenderer3D(); - glm::vec2 computeExtent(const QString& str) const; float getFontSize() const; // Pixel size @@ -55,7 +52,7 @@ private: // text color glm::vec4 _color; - Font* _font; + std::shared_ptr _font; }; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 3c460fdd99..e7604544bd 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -47,15 +47,15 @@ struct QuadBuilder { -static QHash LOADED_FONTS; +static QHash LOADED_FONTS; -Font* Font::load(QIODevice& fontFile) { - Font* result = new Font(); - result->read(fontFile); - return result; +Font::Pointer Font::load(QIODevice& fontFile) { + Pointer font = std::make_shared(); + font->read(fontFile); + return font; } -Font* Font::load(const QString& family) { +Font::Pointer Font::load(const QString& family) { if (!LOADED_FONTS.contains(family)) { static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 351bc63163..5b6b4f2a43 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -17,6 +17,8 @@ class Font { public: + using Pointer = std::shared_ptr; + Font(); void read(QIODevice& path); @@ -29,8 +31,8 @@ public: const glm::vec4* color, EffectType effectType, const glm::vec2& bound, bool layered = false); - static Font* load(QIODevice& fontFile); - static Font* load(const QString& family); + static Pointer load(QIODevice& fontFile); + static Pointer load(const QString& family); private: QStringList tokenizeForWrapping(const QString& str) const; diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h new file mode 100644 index 0000000000..dae39d4a99 --- /dev/null +++ b/libraries/shared/src/CrashHelpers.h @@ -0,0 +1,82 @@ +// +// CrashHelpers.h +// libraries/shared/src +// +// Created by Ryan Huffman on 11 April 2016. +// Copyright 2016 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 +// + +#pragma once + +#ifndef hifi_CrashHelpers_h +#define hifi_CrashHelpers_h + +namespace crash { + +class B; +class A { +public: + A(B* b) : _b(b) { } + ~A(); + virtual void virtualFunction() = 0; + +private: + B* _b; +}; + +class B : public A { +public: + B() : A(this) { } + virtual void virtualFunction() { } +}; + +A::~A() { + _b->virtualFunction(); +} + +void pureVirtualCall() { + qDebug() << "About to make a pure virtual call"; + B b; +} + +void doubleFree() { + qDebug() << "About to double delete memory"; + int* blah = new int(200); + delete blah; + delete blah; +} + +void nullDeref() { + qDebug() << "About to dereference a null pointer"; + int* p = nullptr; + *p = 1; +} + +void doAbort() { + qDebug() << "About to abort"; + abort(); +} + +void outOfBoundsVectorCrash() { + qDebug() << "std::vector out of bounds crash!"; + std::vector v; + v[0] = 42; +} + +void newFault() { + qDebug() << "About to crash inside new fault"; + + // Force crash with multiple large allocations + while (true) { + const size_t GIGABYTE = 1024 * 1024 * 1024; + new char[GIGABYTE]; + } + +} + +} + +#endif // hifi_CrashHelpers_h diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index d21d88d212..53abb3827d 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -463,14 +463,6 @@ glm::vec2 getFacingDir2D(const glm::mat4& m) { } } -bool isNaN(glm::vec3 value) { - return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); -} - -bool isNaN(glm::quat value) { - return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); -} - glm::mat4 orthoInverse(const glm::mat4& m) { glm::mat4 r = m; r[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 469ca1fc81..8b1446d4e5 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -229,8 +229,8 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda glm::vec2 getFacingDir2D(const glm::quat& rot); glm::vec2 getFacingDir2D(const glm::mat4& m); -bool isNaN(glm::vec3 value); -bool isNaN(glm::quat value); +inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } +inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } glm::mat4 orthoInverse(const glm::mat4& m); diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index 6d320e69cb..794f338dc5 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -51,10 +51,10 @@ const int16_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 14; const int16_t BULLET_COLLISION_MASK_DEFAULT = ~ BULLET_COLLISION_GROUP_COLLISIONLESS; // STATIC does not collide with itself (as optimization of physics simulation) -const int16_t BULLET_COLLISION_MASK_STATIC = ~ (BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_STATIC); +const int16_t BULLET_COLLISION_MASK_STATIC = ~ (BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_KINEMATIC | BULLET_COLLISION_GROUP_STATIC); const int16_t BULLET_COLLISION_MASK_DYNAMIC = BULLET_COLLISION_MASK_DEFAULT; -const int16_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_DEFAULT; +const int16_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC; // MY_AVATAR does not collide with itself const int16_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bdc5d4c60d..8f68e20222 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -247,12 +247,6 @@ int getNthBit(unsigned char byte, int ordinal) { return ERROR_RESULT; } -bool isBetween(int64_t value, int64_t max, int64_t min) { - return ((value <= max) && (value >= min)); -} - - - void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) { //assert(value <= 3 && value >= 0); byte |= ((value & 3) << (6 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 @@ -260,12 +254,7 @@ void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) { bool isInEnvironment(const char* environment) { char* environmentString = getenv("HIFI_ENVIRONMENT"); - - if (environmentString && strcmp(environmentString, environment) == 0) { - return true; - } else { - return false; - } + return (environmentString && strcmp(environmentString, environment) == 0); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -632,10 +621,6 @@ void debug::checkDeadBeef(void* memoryVoid, int size) { assert(memcmp((unsigned char*)memoryVoid, DEADBEEF, std::min(size, DEADBEEF_SIZE)) != 0); } -bool isNaN(float value) { - return value != value; -} - QString formatUsecTime(float usecs, int prec) { static const quint64 SECONDS_PER_MINUTE = 60; static const quint64 USECS_PER_MINUTE = USECS_PER_SECOND * SECONDS_PER_MINUTE; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 8fb65a5247..e9201b4a92 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -180,11 +180,11 @@ private: static int DEADBEEF_SIZE; }; -bool isBetween(int64_t value, int64_t max, int64_t min); - +/// \return true when value is between max and min +inline bool isBetween(int64_t value, int64_t max, int64_t min) { return ((value <= max) && (value >= min)); } /// \return bool is the float NaN -bool isNaN(float value); +inline bool isNaN(float value) { return value != value; } QString formatUsecTime(float usecs, int prec = 3); QString formatSecondsElapsed(float seconds); diff --git a/libraries/shared/src/Transform.cpp b/libraries/shared/src/Transform.cpp index a3a3c05731..c51b3dae4b 100644 --- a/libraries/shared/src/Transform.cpp +++ b/libraries/shared/src/Transform.cpp @@ -150,7 +150,3 @@ QJsonObject Transform::toJson(const Transform& transform) { } return result; } - -bool Transform::containsNaN() const { - return isNaN(_rotation) || isNaN(_scale) || isNaN(_translation); -} diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h index 1024173cbd..1e1d10c54b 100644 --- a/libraries/shared/src/Transform.h +++ b/libraries/shared/src/Transform.h @@ -145,7 +145,7 @@ public: Vec4 transform(const Vec4& pos) const; Vec3 transform(const Vec3& pos) const; - bool containsNaN() const; + bool containsNaN() const { return isNaN(_rotation) || isNaN(glm::dot(_scale, _translation)); } protected: diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 2b10892f82..f36b8891ff 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -11,7 +11,6 @@ #include -#include #include #include #include diff --git a/tests/networking/src/ResourceTests.cpp b/tests/networking/src/ResourceTests.cpp index f18f676398..e6dcf18230 100644 --- a/tests/networking/src/ResourceTests.cpp +++ b/tests/networking/src/ResourceTests.cpp @@ -34,7 +34,7 @@ void ResourceTests::initTestCase() { networkAccessManager.setCache(cache); } -static Resource* resource = nullptr; +static QSharedPointer resource; static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) { @@ -55,7 +55,8 @@ void ResourceTests::downloadFirst() { // download the Mery fst file QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); - resource = new Resource(meryUrl, false); + resource = QSharedPointer::create(meryUrl, false); + resource->setSelf(resource); const int timeout = 1000; QEventLoop loop; @@ -67,6 +68,8 @@ void ResourceTests::downloadFirst() { loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); timer.start(); + + resource->ensureLoading(); loop.exec(); QVERIFY(resource->isLoaded()); @@ -76,7 +79,8 @@ void ResourceTests::downloadAgain() { // download the Mery fst file QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); - resource = new Resource(meryUrl, false); + resource = QSharedPointer::create(meryUrl, false); + resource->setSelf(resource); const int timeout = 1000; QEventLoop loop; @@ -88,6 +92,8 @@ void ResourceTests::downloadAgain() { loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); timer.start(); + + resource->ensureLoading(); loop.exec(); QVERIFY(resource->isLoaded());