Merge branch 'master' of github.com:highfidelity/hifi into fix/cache-tallies

This commit is contained in:
Zach Pomerantz 2016-04-15 00:37:03 -07:00
commit fc9de4b7e6
45 changed files with 750 additions and 308 deletions

View file

@ -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)

View file

@ -12,6 +12,7 @@
#include "DomainServer.h"
#include <memory>
#include <random>
#include <QDir>
#include <QJsonDocument>
@ -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<LimitedNodeList>();
// 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<Assignment::Type>& 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<LimitedNodeList>();
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<LimitedNodeList>();
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<ReceivedMes
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> 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(QSharedPointer<Received
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
// 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<ReceivedMessage> 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();
}

View file

@ -62,6 +62,7 @@ public slots:
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> 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<NLPacket> _iceServerHeartbeatPacket;
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer
QList<QHostAddress> _iceServerAddresses;
QSet<QHostAddress> _failedIceServerAddresses;
int _iceAddressLookupID { -1 };
int _noReplyICEHeartbeats { 0 };
int _numHeartbeatDenials { 0 };
bool _connectedToICEServer { false };
bool _hasAccessToken { false };
friend class DomainGatekeeper;
};

View file

@ -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<udt::Packet> 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<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<const unsigned char*>(signature.constData()),
signature.size(),
rsaPublicKey);
if (rsaPublicKey) {
auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
int verificationResult = RSA_verify(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<const unsigned char*>(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;
}

View file

@ -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<QUuid, SharedNetworkPeer>;
NetworkPeerHash _activePeers;
HTTPManager _httpManager;
using RSAUniquePtr = std::unique_ptr<RSA, std::function<void(RSA*)>>;
using DomainPublicKeyHash = std::unordered_map<QUuid, RSAUniquePtr>;
DomainPublicKeyHash _domainPublicKeys;
quint64 _lastInactiveCheckTimestamp;
QSet<QUuid> _pendingPublicKeyRequests;
};
#endif // hifi_IceServer_h

View file

@ -1238,8 +1238,6 @@ Application::~Application() {
_physicsEngine->setCharacterController(nullptr);
ModelEntityItem::cleanupLoadedAnimations();
// remove avatars from physics engine
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
VectorOfMotionStates motionStates;
@ -3446,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);
});
@ -4542,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) {

View file

@ -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 <new.h>
#include <Windows.h>
#include <csignal>
#include <QDebug>
// 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

View file

@ -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 <QString>
#ifdef HAS_BUGSPLAT
#include <BugSplat.h>
class CrashReporter {
public:
CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version);
MiniDmpSender mpSender;
};
#endif
#endif // hifi_CrashReporter_h

View file

@ -25,26 +25,23 @@
#include "InterfaceLogging.h"
#include "UserActivityLogger.h"
#include "MainWindow.h"
#include <thread>
#ifdef HAS_BUGSPLAT
#include <BuildInfo.h>
#include <BugSplat.h>
#include <CrashReporter.h>
#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

View file

@ -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();

View file

@ -196,7 +196,7 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(audioMixerPps, -1);
}
QList<Resource*> 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();

View file

@ -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<Resource>::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) {

View file

@ -13,9 +13,9 @@
#include <memory>
#include <QNetworkReply>
#include <QString>
#include <QUrl>
#include <QtNetwork/QNetworkReply>
#include "AnimNode.h"
@ -41,7 +41,8 @@ protected slots:
protected:
QUrl _url;
Resource* _resource;
QSharedPointer<Resource> _resource;
private:
// no copies

View file

@ -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) {

View file

@ -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) {

View file

@ -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<FBXAnimationFrame>& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy
auto& fbxJoints = myAnimation->getGeometry().joints;
if (_animation && _animation->isLoaded()) {
const QVector<FBXAnimationFrame>& 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) {

View file

@ -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(); }

View file

@ -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);

View file

@ -205,37 +205,18 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
}
QMap<QString, AnimationPointer> 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<AnimationCache>()->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<AnimationCache>()->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<glm::quat>& 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<glm::quat>& rotations) {
void ModelEntityItem::setJointRotationsSet(const QVector<bool>& 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<bool>& rotationsSet) {
void ModelEntityItem::setJointTranslations(const QVector<glm::vec3>& 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<glm::vec3>& translation
void ModelEntityItem::setJointTranslationsSet(const QVector<bool>& 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<bool>& translationsS
QVector<glm::quat> ModelEntityItem::getJointRotations() const {
QVector<glm::quat> result;
_jointDataLock.withReadLock([&] {
result = _absoluteJointRotationsInObjectFrame;
if (_jointRotationsExplicitlySet) {
result = _absoluteJointRotationsInObjectFrame;
}
});
return result;
}
@ -455,15 +442,20 @@ QVector<glm::quat> ModelEntityItem::getJointRotations() const {
QVector<bool> ModelEntityItem::getJointRotationsSet() const {
QVector<bool> result;
_jointDataLock.withReadLock([&] {
result = _absoluteJointRotationsInObjectFrameSet;
if (_jointRotationsExplicitlySet) {
result = _absoluteJointRotationsInObjectFrameSet;
}
});
return result;
}
QVector<glm::vec3> ModelEntityItem::getJointTranslations() const {
QVector<glm::vec3> result;
_jointDataLock.withReadLock([&] {
result = _absoluteJointTranslationsInObjectFrame;
if (_jointTranslationsExplicitlySet) {
result = _absoluteJointTranslationsInObjectFrame;
}
});
return result;
}
@ -471,7 +463,9 @@ QVector<glm::vec3> ModelEntityItem::getJointTranslations() const {
QVector<bool> ModelEntityItem::getJointTranslationsSet() const {
QVector<bool> result;
_jointDataLock.withReadLock([&] {
result = _absoluteJointTranslationsInObjectFrameSet;
if (_jointTranslationsExplicitlySet) {
result = _absoluteJointTranslationsInObjectFrameSet;
}
});
return result;
}

View file

@ -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<glm::quat> _absoluteJointRotationsInObjectFrame;
QVector<bool> _absoluteJointRotationsInObjectFrameSet; // ever set?
QVector<bool> _absoluteJointRotationsInObjectFrameDirty; // needs a relay to model/rig?
bool _jointTranslationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations
QVector<glm::vec3> _absoluteJointTranslationsInObjectFrame;
QVector<bool> _absoluteJointTranslationsInObjectFrameSet; // ever set?
QVector<bool> _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<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
QString _jointMappingURL;
static AnimationPointer getAnimation(const QString& url);
static QMap<QString, AnimationPointer> _loadedAnimations;
static AnimationCache _animationCache;
};
#endif // hifi_ModelEntityItem_h

View file

@ -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<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,

View file

@ -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() {

View file

@ -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;

View file

@ -20,6 +20,7 @@
#include "NetworkAccessManager.h"
#include "NetworkLogging.h"
#include "NodeList.h"
#include "ResourceCache.h"
@ -27,30 +28,77 @@
(((x) > (max)) ? (max) :\
(x)))
ResourceCache::ResourceCache(QObject* parent) :
QObject(parent) {
ResourceCache::ResourceCache(QObject* parent) : QObject(parent) {
auto& domainHandler = DependencyManager::get<NodeList>()->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> resource, resourcesCopy) {
if (resource) {
resource->refresh();
}
}
}
void ResourceCache::refresh(const QUrl& url) {
QSharedPointer<Resource> resource = _resources.value(url);
if (!resource.isNull()) {
QSharedPointer<Resource> resource;
{
QReadLocker locker(&_resourcesLock);
resource = _resources.value(url).lock();
}
if (resource) {
resource->refresh();
} else {
removeResource(url);
@ -103,8 +151,12 @@ void ResourceCache::checkAsynchronousGets() {
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback,
bool delayLoad, void* extra) {
QSharedPointer<Resource> resource = _resources.value(url);
if (!resource.isNull()) {
QSharedPointer<Resource> resource;
{
QReadLocker locker(&_resourcesLock);
resource = _resources.value(url).lock();
}
if (resource) {
removeUnusedResource(resource);
return resource;
}
@ -123,7 +175,10 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), delayLoad, extra);
resource->setSelf(resource);
resource->setCache(this);
_resources.insert(url, resource);
{
QWriteLocker locker(&_resourcesLock);
_resources.insert(url, resource);
}
removeUnusedResource(resource);
resource->ensureLoading();
@ -147,13 +202,16 @@ void ResourceCache::addUnusedResource(const QSharedPointer<Resource>& resource)
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>& resource) {
QWriteLocker locker(&_unusedResourcesLock);
if (_unusedResources.contains(resource->getLRUKey())) {
_unusedResources.remove(resource->getLRUKey());
_unusedResourcesSize -= resource->getBytes();
@ -162,6 +220,7 @@ void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resourc
}
void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
QWriteLocker locker(&_unusedResourcesLock);
while (!_unusedResources.empty() &&
_unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) {
// unload the oldest resource
@ -180,6 +239,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>& resource, _unusedResources) {
resource->setCache(nullptr);
@ -195,6 +255,7 @@ void ResourceCache::resetResourceCounters() {
}
void ResourceCache::removeResource(const QUrl& url, qint64 size) {
QWriteLocker locker(&_unusedResourcesLock);
_resources.remove(url);
_totalResourcesSize -= size;
}
@ -204,19 +265,28 @@ void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize
emit dirty();
}
void ResourceCacheSharedItems::appendActiveRequest(Resource* resource) {
void ResourceCacheSharedItems::appendActiveRequest(QWeakPointer<Resource> resource) {
Lock lock(_mutex);
_loadingRequests.append(resource);
}
void ResourceCacheSharedItems::appendPendingRequest(Resource* resource) {
void ResourceCacheSharedItems::appendPendingRequest(QWeakPointer<Resource> resource) {
Lock lock(_mutex);
_pendingRequests.append(resource);
}
QList<QPointer<Resource>> ResourceCacheSharedItems::getPendingRequests() const {
Lock lock(_mutex);
return _pendingRequests;
QList<QSharedPointer<Resource>> ResourceCacheSharedItems::getPendingRequests() {
QList<QSharedPointer<Resource>> result;
{
Lock lock(_mutex);
foreach(QSharedPointer<Resource> resource, _pendingRequests) {
if (resource) {
result.append(resource);
}
}
}
return result;
}
uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const {
@ -224,23 +294,33 @@ uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const {
return _pendingRequests.size();
}
QList<Resource*> ResourceCacheSharedItems::getLoadingRequests() const {
Lock lock(_mutex);
return _loadingRequests;
QList<QSharedPointer<Resource>> ResourceCacheSharedItems::getLoadingRequests() {
QList<QSharedPointer<Resource>> result;
{
Lock lock(_mutex);
foreach(QSharedPointer<Resource> resource, _loadingRequests) {
if (resource) {
result.append(resource);
}
}
}
return result;
}
void ResourceCacheSharedItems::removeRequest(Resource* resource) {
void ResourceCacheSharedItems::removeRequest(QWeakPointer<Resource> resource) {
Lock lock(_mutex);
_loadingRequests.removeOne(resource);
_loadingRequests.removeAll(resource);
}
Resource* ResourceCacheSharedItems::getHighestPendingRequest() {
QSharedPointer<Resource> ResourceCacheSharedItems::getHighestPendingRequest() {
Lock lock(_mutex);
// look for the highest priority pending request
int highestIndex = -1;
float highestPriority = -FLT_MAX;
QSharedPointer<Resource> 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;
@ -249,16 +329,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<QSharedPointer<Resource>> ResourceCache::getLoadingRequests() {
return DependencyManager::get<ResourceCacheSharedItems>()->getLoadingRequests();
}
int ResourceCache::getPendingRequestCount() {
return DependencyManager::get<ResourceCacheSharedItems>()->getPendingRequestsCount();
}
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
if (_requestsActive >= _requestLimit) {
@ -273,7 +362,7 @@ bool ResourceCache::attemptRequest(Resource* resource) {
return true;
}
void ResourceCache::requestCompleted(Resource* resource) {
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->removeRequest(resource);
--_requestsActive;
@ -297,11 +386,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() {
@ -309,7 +393,7 @@ Resource::~Resource() {
_request->disconnect(this);
_request->deleteLater();
_request = nullptr;
ResourceCache::requestCompleted(this);
ResourceCache::requestCompleted(_self);
}
}
@ -362,7 +446,7 @@ void Resource::refresh() {
_request->disconnect(this);
_request->deleteLater();
_request = nullptr;
ResourceCache::requestCompleted(this);
ResourceCache::requestCompleted(_self);
}
init();
@ -412,7 +496,7 @@ void Resource::init() {
void Resource::attemptRequest() {
_startedLoading = true;
ResourceCache::attemptRequest(this);
ResourceCache::attemptRequest(_self);
}
void Resource::finishedLoading(bool success) {
@ -433,18 +517,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;
}
@ -476,7 +564,7 @@ void Resource::handleReplyFinished() {
return;
}
ResourceCache::requestCompleted(this);
ResourceCache::requestCompleted(_self);
auto result = _request->getResult();
if (result == ResourceRequest::Success) {

View file

@ -62,21 +62,20 @@ class ResourceCacheSharedItems : public Dependency {
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
public:
void appendPendingRequest(Resource* newRequest);
void appendActiveRequest(Resource* newRequest);
void removeRequest(Resource* doneRequest);
QList<QPointer<Resource>> getPendingRequests() const;
void appendPendingRequest(QWeakPointer<Resource> newRequest);
void appendActiveRequest(QWeakPointer<Resource> newRequest);
void removeRequest(QWeakPointer<Resource> doneRequest);
QList<QSharedPointer<Resource>> getPendingRequests();
uint32_t getPendingRequestsCount() const;
QList<Resource*> getLoadingRequests() const;
Resource* getHighestPendingRequest();
QList<QSharedPointer<Resource>> getLoadingRequests();
QSharedPointer<Resource> getHighestPendingRequest();
private:
ResourceCacheSharedItems() { }
virtual ~ResourceCacheSharedItems() { }
ResourceCacheSharedItems() = default;
mutable Mutex _mutex;
QList<QPointer<Resource>> _pendingRequests;
QList<Resource*> _loadingRequests;
QList<QWeakPointer<Resource>> _pendingRequests;
QList<QWeakPointer<Resource>> _loadingRequests;
};
@ -105,13 +104,11 @@ public:
void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize);
qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }
static const QList<Resource*> getLoadingRequests()
{ return DependencyManager::get<ResourceCacheSharedItems>()->getLoadingRequests(); }
static QList<QSharedPointer<Resource>> getLoadingRequests();
static int getPendingRequestCount()
{ return DependencyManager::get<ResourceCacheSharedItems>()->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> resource);
static void requestCompleted(QWeakPointer<Resource> resource);
static bool attemptHighestPriorityRequest();
private:
@ -155,6 +155,7 @@ private:
void resetResourceCounters();
void removeResource(const QUrl& url, qint64 size = 0);
QReadWriteLock _resourcesLock { QReadWriteLock::Recursive };
QHash<QUrl, QWeakPointer<Resource>> _resources;
int _lastLRUKey = 0;
@ -162,7 +163,7 @@ private:
static int _requestsActive;
void getResourceAsynchronously(const QUrl& url);
QReadWriteLock _resourcesToBeGottenLock;
QReadWriteLock _resourcesToBeGottenLock { QReadWriteLock::Recursive };
QQueue<QUrl> _resourcesToBeGotten;
std::atomic<size_t> _numTotalResources { 0 };
@ -172,6 +173,7 @@ private:
std::atomic<qint64> _unusedResourcesSize { 0 };
qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE;
QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive };
QMap<int, QSharedPointer<Resource>> _unusedResources;
};

View file

@ -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;
}

View file

@ -34,8 +34,8 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
<< 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<PacketType> RELIABLE_PACKETS = QSet<PacketType>();

View file

@ -93,7 +93,8 @@ public:
MessagesUnsubscribe,
ICEServerHeartbeatDenied,
AssetMappingOperation,
AssetMappingOperationReply
AssetMappingOperationReply,
ICEServerHeartbeatACK
};
};

View file

@ -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<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
properties.setSimulationOwner(Physics::getSessionUUID(),
glm::max<uint8_t>(_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);
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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<EntityMotionState*>(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<EntityMotionState*>::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;

View file

@ -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; }

View file

@ -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<ObjectMotionState*>(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();

View file

@ -13,9 +13,9 @@
#define hifi_PhysicsEngine_h
#include <stdint.h>
#include <vector>
#include <QUuid>
#include <QVector>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
@ -41,7 +41,7 @@ public:
};
typedef std::map<ContactKey, ContactInfo> ContactMap;
typedef QVector<Collision> CollisionEvents;
typedef std::vector<Collision> CollisionEvents;
class PhysicsEngine {
public:
@ -87,8 +87,6 @@ public:
void removeAction(const QUuid actionID);
void forEachAction(std::function<void(EntityActionPointer)> actor);
void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; }
private:
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
@ -110,9 +108,9 @@ private:
ContactMap _contactMap;
CollisionEvents _collisionEvents;
QHash<QUuid, EntityActionPointer> _objectActions;
std::vector<btRigidBody*> _activeStaticBodies;
glm::vec3 _originOffset;
QUuid _sessionID;
CharacterController* _myAvatarController;
@ -121,7 +119,6 @@ private:
bool _dumpNextStats = false;
bool _hasOutgoingChanges = false;
};
typedef std::shared_ptr<PhysicsEngine> PhysicsEnginePointer;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -150,7 +150,3 @@ QJsonObject Transform::toJson(const Transform& transform) {
}
return result;
}
bool Transform::containsNaN() const {
return isNaN(_rotation) || isNaN(_scale) || isNaN(_translation);
}

View file

@ -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:

View file

@ -11,7 +11,6 @@
#include <glm/gtx/transform.hpp>
#include <AnimNodeLoader.h>
#include <AnimInverseKinematics.h>
#include <AnimBlendLinear.h>
#include <AnimationLogging.h>

View file

@ -34,7 +34,7 @@ void ResourceTests::initTestCase() {
networkAccessManager.setCache(cache);
}
static Resource* resource = nullptr;
static QSharedPointer<Resource> 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<Resource>::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<Resource>::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());