mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 12:04:18 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into fix/cache-tallies
This commit is contained in:
commit
fc9de4b7e6
45 changed files with 750 additions and 308 deletions
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
135
interface/src/CrashReporter.cpp
Normal file
135
interface/src/CrashReporter.cpp
Normal 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
|
32
interface/src/CrashReporter.h
Normal file
32
interface/src/CrashReporter.h
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -93,7 +93,8 @@ public:
|
|||
MessagesUnsubscribe,
|
||||
ICEServerHeartbeatDenied,
|
||||
AssetMappingOperation,
|
||||
AssetMappingOperationReply
|
||||
AssetMappingOperationReply,
|
||||
ICEServerHeartbeatACK
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -150,7 +150,3 @@ QJsonObject Transform::toJson(const Transform& transform) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Transform::containsNaN() const {
|
||||
return isNaN(_rotation) || isNaN(_scale) || isNaN(_translation);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <AnimNodeLoader.h>
|
||||
#include <AnimInverseKinematics.h>
|
||||
#include <AnimBlendLinear.h>
|
||||
#include <AnimationLogging.h>
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue