mirror of
https://github.com/overte-org/overte.git
synced 2025-07-22 16:53:31 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into red
This commit is contained in:
commit
298300e42f
62 changed files with 1099 additions and 527 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ var OVERLAY_DATA = {
|
|||
color: {red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
};
|
||||
var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away
|
||||
|
||||
var lastOverlayPosition = { x: 0, y: 0, z: 0};
|
||||
var OVERLAY_DATA_HMD = {
|
||||
|
@ -34,49 +35,26 @@ var OVERLAY_DATA_HMD = {
|
|||
alpha: 1,
|
||||
scale: 2,
|
||||
isFacingAvatar: true,
|
||||
drawInFront: true
|
||||
drawInFront: true
|
||||
};
|
||||
|
||||
// ANIMATION
|
||||
// We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect
|
||||
// using an animation graph with a state that we turn on and off through the animation var defined with that state.
|
||||
var awayAnimationHandlerId, activeAnimationHandlerId, stopper;
|
||||
var AWAY_INTRO = {
|
||||
url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx",
|
||||
playbackRate: 30.0,
|
||||
loopFlag: false,
|
||||
startFrame: 0.0,
|
||||
endFrame: 83.0
|
||||
};
|
||||
|
||||
// prefetch the kneel animation so it's resident in memory when we need it.
|
||||
MyAvatar.prefetchAnimation(AWAY_INTRO.url);
|
||||
|
||||
function playAwayAnimation() {
|
||||
function animateAway() {
|
||||
return {isAway: true, isNotAway: false, isNotMoving: false, ikOverlayAlpha: 0.0};
|
||||
}
|
||||
if (stopper) {
|
||||
stopper = false;
|
||||
MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); // do it now, before making new assignment
|
||||
}
|
||||
awayAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateAway, null);
|
||||
MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame);
|
||||
}
|
||||
|
||||
function stopAwayAnimation() {
|
||||
MyAvatar.removeAnimationStateHandler(awayAnimationHandlerId);
|
||||
if (stopper) {
|
||||
print('WARNING: unexpected double stop');
|
||||
return;
|
||||
}
|
||||
// How do we know when to turn ikOverlayAlpha back on?
|
||||
// It cannot be as soon as we want to stop the away animation, because then things will look goofy as we come out of that animation.
|
||||
// (Imagine an away animation that sits or kneels, and then stands back up when coming out of it. If head is at the HMD, then it won't
|
||||
// want to track the standing up animation.)
|
||||
// The anim graph will trigger awayOutroOnDone when awayOutro is finished.
|
||||
var backToNormal = false;
|
||||
stopper = true;
|
||||
function animateActive(state) {
|
||||
if (state.awayOutroOnDone) {
|
||||
backToNormal = true;
|
||||
stopper = false;
|
||||
} else if (state.ikOverlayAlpha) {
|
||||
// Once the right state gets reflected back to us, we don't need the hander any more.
|
||||
// But we are locked against handler changes during the execution of a handler, so remove asynchronously.
|
||||
Script.setTimeout(function () { MyAvatar.removeAnimationStateHandler(activeAnimationHandlerId); }, 0);
|
||||
}
|
||||
// It might be cool to "come back to life" by fading the ik overlay back in over a short time. But let's see how this goes.
|
||||
return {isAway: false, isNotAway: true, ikOverlayAlpha: backToNormal ? 1.0 : 0.0}; // IWBNI we had a way of deleting an anim var.
|
||||
}
|
||||
activeAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateActive, ['ikOverlayAlpha', 'awayOutroOnDone']);
|
||||
MyAvatar.restoreAnimation();
|
||||
}
|
||||
|
||||
// OVERLAY
|
||||
|
@ -112,15 +90,17 @@ function showOverlay() {
|
|||
var screen = Controller.getViewportDimensions();
|
||||
|
||||
// keep the overlay it's natural size and always center it...
|
||||
Overlays.editOverlay(overlay, { visible: true,
|
||||
x: ((screen.x - OVERLAY_WIDTH) / 2),
|
||||
Overlays.editOverlay(overlay, { visible: true,
|
||||
x: ((screen.x - OVERLAY_WIDTH) / 2),
|
||||
y: ((screen.y - OVERLAY_HEIGHT) / 2) });
|
||||
}
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
Overlays.editOverlay(overlay, {visible: false});
|
||||
Overlays.editOverlay(overlayHMD, {visible: false});
|
||||
}
|
||||
|
||||
hideOverlay();
|
||||
|
||||
function maybeMoveOverlay() {
|
||||
|
@ -150,11 +130,18 @@ function maybeMoveOverlay() {
|
|||
}
|
||||
}
|
||||
|
||||
function ifAvatarMovedGoActive() {
|
||||
if (Vec3.distance(MyAvatar.position, avatarPosition) > AVATAR_MOVE_FOR_ACTIVE_DISTANCE) {
|
||||
goActive();
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN CONTROL
|
||||
var wasMuted, isAway;
|
||||
var wasOverlaysVisible = Menu.isOptionChecked("Overlays");
|
||||
var eventMappingName = "io.highfidelity.away"; // goActive on hand controller button events, too.
|
||||
var eventMapping = Controller.newMapping(eventMappingName);
|
||||
var avatarPosition = MyAvatar.position;
|
||||
|
||||
// backward compatible version of getting HMD.mounted, so it works in old clients
|
||||
function safeGetHMDMounted() {
|
||||
|
@ -163,12 +150,14 @@ function safeGetHMDMounted() {
|
|||
}
|
||||
return HMD.mounted;
|
||||
}
|
||||
|
||||
var wasHmdMounted = safeGetHMDMounted();
|
||||
|
||||
function goAway() {
|
||||
if (isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAway = true;
|
||||
print('going "away"');
|
||||
wasMuted = AudioDevice.getMuted();
|
||||
|
@ -191,6 +180,9 @@ function goAway() {
|
|||
Reticle.visible = false;
|
||||
}
|
||||
wasHmdMounted = safeGetHMDMounted(); // always remember the correct state
|
||||
|
||||
avatarPosition = MyAvatar.position;
|
||||
Script.update.connect(ifAvatarMovedGoActive);
|
||||
}
|
||||
|
||||
function goActive() {
|
||||
|
@ -216,6 +208,8 @@ function goActive() {
|
|||
Reticle.position = HMD.getHUDLookAtPosition2D();
|
||||
}
|
||||
wasHmdMounted = safeGetHMDMounted(); // always remember the correct state
|
||||
|
||||
Script.update.disconnect(ifAvatarMovedGoActive);
|
||||
}
|
||||
|
||||
function maybeGoActive(event) {
|
||||
|
@ -263,7 +257,7 @@ Script.update.connect(maybeGoAway);
|
|||
Controller.mousePressEvent.connect(goActive);
|
||||
Controller.keyPressEvent.connect(maybeGoActive);
|
||||
// Note peek() so as to not interfere with other mappings.
|
||||
eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(goActive);
|
||||
eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -211,6 +211,10 @@ if (WIN32)
|
|||
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} pthread)
|
||||
endif(UNIX)
|
||||
|
||||
# assume we are using a Qt build without bearer management
|
||||
add_definitions(-DQT_NO_BEARERMANAGEMENT)
|
||||
|
||||
|
|
|
@ -246,7 +246,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -266,7 +265,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -285,7 +283,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -304,7 +301,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -323,7 +319,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -342,7 +337,6 @@
|
|||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -361,7 +355,6 @@
|
|||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -380,7 +373,6 @@
|
|||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -388,37 +380,11 @@
|
|||
{ "var": "isInAirRun", "state": "inAirRun" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "awayIntro",
|
||||
"interpTarget": 30,
|
||||
"interpDuration": 30,
|
||||
"transitions": [
|
||||
{ "var": "isNotAway", "state": "awayOutro" },
|
||||
{ "var": "awayIntroOnDone", "state": "away"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "away",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isNotAway", "state": "awayOutro" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "awayOutro",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "awayOutroOnDone", "state": "idle" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fly",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotFlying", "state": "idle" }
|
||||
]
|
||||
},
|
||||
|
@ -427,7 +393,6 @@
|
|||
"interpTarget": 0,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotTakeoff", "state": "inAirStand" }
|
||||
]
|
||||
},
|
||||
|
@ -436,7 +401,6 @@
|
|||
"interpTarget": 0,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotTakeoff", "state": "inAirRun" }
|
||||
]
|
||||
},
|
||||
|
@ -446,7 +410,6 @@
|
|||
"interpDuration": 6,
|
||||
"interpType": "snapshotPrev",
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotInAir", "state": "landStandImpact" }
|
||||
]
|
||||
},
|
||||
|
@ -456,7 +419,6 @@
|
|||
"interpDuration": 6,
|
||||
"interpType": "snapshotPrev",
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isNotInAir", "state": "landRun" }
|
||||
]
|
||||
},
|
||||
|
@ -465,7 +427,6 @@
|
|||
"interpTarget": 6,
|
||||
"interpDuration": 4,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -483,7 +444,6 @@
|
|||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" },
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -497,7 +457,6 @@
|
|||
"interpTarget": 1,
|
||||
"interpDuration": 7,
|
||||
"transitions": [
|
||||
{ "var": "isAway", "state": "awayIntro" },
|
||||
{ "var": "isFlying", "state": "fly" },
|
||||
{ "var": "isTakeoffStand", "state": "takeoffStand" },
|
||||
{ "var": "isTakeoffRun", "state": "takeoffRun" },
|
||||
|
@ -754,42 +713,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "awayIntro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 83.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "away",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx",
|
||||
"startFrame": 83.0,
|
||||
"endFrame": 84.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "awayOutro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/kneel.fbx",
|
||||
"startFrame": 84.0,
|
||||
"endFrame": 167.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "fly",
|
||||
"type": "clip",
|
||||
|
|
|
@ -1238,8 +1238,6 @@ Application::~Application() {
|
|||
|
||||
_physicsEngine->setCharacterController(nullptr);
|
||||
|
||||
ModelEntityItem::cleanupLoadedAnimations();
|
||||
|
||||
// remove avatars from physics engine
|
||||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||
VectorOfMotionStates motionStates;
|
||||
|
@ -3198,7 +3196,19 @@ void Application::cameraMenuChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::resetPhysicsReadyInformation() {
|
||||
// we've changed domains or cleared out caches or something. we no longer know enough about the
|
||||
// collision information of nearby entities to make running bullet be safe.
|
||||
_fullSceneReceivedCounter = 0;
|
||||
_fullSceneCounterAtLastPhysicsCheck = 0;
|
||||
_nearbyEntitiesCountAtLastPhysicsCheck = 0;
|
||||
_nearbyEntitiesStabilityCount = 0;
|
||||
_physicsEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
void Application::reloadResourceCaches() {
|
||||
resetPhysicsReadyInformation();
|
||||
// Clear entities out of view frustum
|
||||
_viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||
_viewFrustum.setOrientation(glm::quat());
|
||||
|
@ -3255,21 +3265,34 @@ void Application::update(float deltaTime) {
|
|||
|
||||
updateLOD();
|
||||
|
||||
if (!_physicsEnabled && _processOctreeStatsCounter > 0) {
|
||||
if (!_physicsEnabled) {
|
||||
// we haven't yet enabled physics. we wait until we think we have all the collision information
|
||||
// for nearby entities before starting bullet up.
|
||||
quint64 now = usecTimestampNow();
|
||||
bool timeout = false;
|
||||
const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND;
|
||||
if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) {
|
||||
timeout = true;
|
||||
}
|
||||
|
||||
// process octree stats packets are sent in between full sends of a scene.
|
||||
// We keep physics disabled until we've received a full scene and everything near the avatar in that
|
||||
// scene is ready to compute its collision shape.
|
||||
if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) {
|
||||
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
||||
_lastPhysicsCheckTime = now;
|
||||
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
|
||||
|
||||
if (nearbyEntitiesAreReadyForPhysics()) {
|
||||
_physicsEnabled = true;
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
} else {
|
||||
auto characterController = getMyAvatar()->getCharacterController();
|
||||
if (characterController) {
|
||||
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
|
||||
// a non-loading collision hull.
|
||||
characterController->setEnabled(false);
|
||||
// process octree stats packets are sent in between full sends of a scene (this isn't currently true).
|
||||
// We keep physics disabled until we've received a full scene and everything near the avatar in that
|
||||
// scene is ready to compute its collision shape.
|
||||
if (nearbyEntitiesAreReadyForPhysics()) {
|
||||
_physicsEnabled = true;
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
} else {
|
||||
auto characterController = getMyAvatar()->getCharacterController();
|
||||
if (characterController) {
|
||||
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
|
||||
// a non-loading collision hull.
|
||||
characterController->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3421,7 +3444,7 @@ void Application::update(float deltaTime) {
|
|||
getEntities()->getTree()->withWriteLock([&] {
|
||||
PerformanceTimer perfTimer("handleOutgoingChanges");
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
|
||||
_entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID());
|
||||
_entitySimulation.handleOutgoingChanges(outgoingChanges);
|
||||
avatarManager->handleOutgoingChanges(outgoingChanges);
|
||||
});
|
||||
|
||||
|
@ -4154,7 +4177,7 @@ void Application::clearDomainOctreeDetails() {
|
|||
qCDebug(interfaceapp) << "Clearing domain octree details...";
|
||||
// reset the environment so that we don't erroneously end up with multiple
|
||||
|
||||
_physicsEnabled = false;
|
||||
resetPhysicsReadyInformation();
|
||||
|
||||
// reset our node to stats and node to jurisdiction maps... since these must be changing...
|
||||
_entityServerJurisdictions.withWriteLock([&] {
|
||||
|
@ -4178,7 +4201,7 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
updateWindowTitle();
|
||||
clearDomainOctreeDetails();
|
||||
// disable physics until we have enough information about our new location to not cause craziness.
|
||||
_physicsEnabled = false;
|
||||
resetPhysicsReadyInformation();
|
||||
}
|
||||
|
||||
|
||||
|
@ -4311,15 +4334,31 @@ bool Application::nearbyEntitiesAreReadyForPhysics() {
|
|||
entityTree->findEntities(box, entities);
|
||||
});
|
||||
|
||||
foreach (EntityItemPointer entity, entities) {
|
||||
if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
|
||||
qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
|
||||
return false;
|
||||
}
|
||||
// For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply
|
||||
// a heuristic to try to decide when we actually know about all of the nearby entities.
|
||||
uint32_t nearbyCount = entities.size();
|
||||
if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) {
|
||||
_nearbyEntitiesStabilityCount++;
|
||||
} else {
|
||||
_nearbyEntitiesStabilityCount = 0;
|
||||
}
|
||||
return true;
|
||||
_nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount;
|
||||
|
||||
const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3;
|
||||
if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) {
|
||||
// We've seen the same number of nearby entities for several stats packets in a row. assume we've got all
|
||||
// the local entities.
|
||||
foreach (EntityItemPointer entity, entities) {
|
||||
if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
|
||||
qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) {
|
||||
|
@ -4336,6 +4375,10 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
|
|||
OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID];
|
||||
statsMessageLength = octreeStats.unpackFromPacket(message);
|
||||
|
||||
if (octreeStats.isFullScene()) {
|
||||
_fullSceneReceivedCounter++;
|
||||
}
|
||||
|
||||
// see if this is the first we've heard of this node...
|
||||
NodeToJurisdictionMap* jurisdiction = nullptr;
|
||||
QString serverType;
|
||||
|
@ -4367,8 +4410,6 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
|
|||
});
|
||||
});
|
||||
|
||||
_processOctreeStatsCounter++;
|
||||
|
||||
return statsMessageLength;
|
||||
}
|
||||
|
||||
|
@ -4499,10 +4540,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
|
|||
}
|
||||
|
||||
void Application::setSessionUUID(const QUuid& sessionUUID) const {
|
||||
// HACK: until we swap the library dependency order between physics and entities
|
||||
// we cache the sessionID in two distinct places for physics.
|
||||
Physics::setSessionUUID(sessionUUID); // TODO: remove this one
|
||||
_physicsEngine->setSessionUUID(sessionUUID);
|
||||
Physics::setSessionUUID(sessionUUID);
|
||||
}
|
||||
|
||||
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||
|
@ -5158,13 +5196,6 @@ mat4 Application::getHMDSensorPose() const {
|
|||
return mat4();
|
||||
}
|
||||
|
||||
void Application::crashApplication() {
|
||||
qCDebug(interfaceapp) << "Intentionally crashed Interface";
|
||||
QObject* object = nullptr;
|
||||
bool value = object->isWindowType();
|
||||
Q_UNUSED(value);
|
||||
}
|
||||
|
||||
void Application::deadlockApplication() {
|
||||
qCDebug(interfaceapp) << "Intentionally deadlocked Interface";
|
||||
// Using a loop that will *technically* eventually exit (in ~600 billion years)
|
||||
|
|
|
@ -271,11 +271,12 @@ public slots:
|
|||
void toggleOverlays();
|
||||
void setOverlaysVisible(bool visible);
|
||||
|
||||
void resetPhysicsReadyInformation();
|
||||
|
||||
void reloadResourceCaches();
|
||||
|
||||
void updateHeartbeat() const;
|
||||
|
||||
static void crashApplication();
|
||||
static void deadlockApplication();
|
||||
|
||||
void rotationModeChanged() const;
|
||||
|
@ -514,7 +515,11 @@ private:
|
|||
std::map<void*, std::function<void()>> _preRenderLambdas;
|
||||
std::mutex _preRenderLambdasLock;
|
||||
|
||||
std::atomic<uint32_t> _processOctreeStatsCounter { 0 };
|
||||
std::atomic<uint32_t> _fullSceneReceivedCounter { 0 }; // how many times have we received a full-scene octree stats packet
|
||||
uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready
|
||||
uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready
|
||||
uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same
|
||||
quint64 _lastPhysicsCheckTime { 0 }; // when did we last check to see if physics was ready
|
||||
|
||||
bool _keyboardDeviceHasFocus { true };
|
||||
|
||||
|
|
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
|
|
@ -14,6 +14,7 @@
|
|||
#include <avatar/AvatarActionHold.h>
|
||||
#include <ObjectActionOffset.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "InterfaceActionFactory.h"
|
||||
|
||||
|
@ -66,6 +67,8 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt
|
|||
if (action) {
|
||||
action->deserialize(data);
|
||||
if (action->lifetimeIsOver()) {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during action creation.*");
|
||||
qDebug() << "InterfaceActionFactory::factoryBA lifetimeIsOver during action creation --"
|
||||
<< action->getExpires() << "<" << usecTimestampNow();
|
||||
return nullptr;
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
#include <QMenuBar>
|
||||
#include <QShortcut>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <AddressManager.h>
|
||||
#include <AudioClient.h>
|
||||
#include <CrashHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <PathUtils.h>
|
||||
|
@ -587,10 +590,41 @@ Menu::Menu() {
|
|||
|
||||
// Developer > Display Crash Options
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||
// Developer > Crash Application
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication()));
|
||||
// Developer > Deadlock Application
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
|
||||
// Developer > Crash >>>
|
||||
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
|
||||
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::pureVirtualCall(); }); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doubleFree(); }); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doAbort(); }); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::nullDeref(); }); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::outOfBoundsVectorCrash(); }); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::newFault(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::newFault(); }); });
|
||||
|
||||
// Developer > Log...
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,
|
||||
|
|
|
@ -66,7 +66,18 @@ namespace MenuOption {
|
|||
const QString CopyAddress = "Copy Address to Clipboard";
|
||||
const QString CopyPath = "Copy Path to Clipboard";
|
||||
const QString CoupleEyelids = "Couple Eyelids";
|
||||
const QString CrashInterface = "Crash Interface";
|
||||
const QString CrashPureVirtualFunction = "Pure Virtual Function Call";
|
||||
const QString CrashPureVirtualFunctionThreaded = "Pure Virtual Function Call (threaded)";
|
||||
const QString CrashDoubleFree = "Double Free";
|
||||
const QString CrashDoubleFreeThreaded = "Double Free (threaded)";
|
||||
const QString CrashNullDereference = "Null Dereference";
|
||||
const QString CrashNullDereferenceThreaded = "Null Dereference (threaded)";
|
||||
const QString CrashAbort = "Abort";
|
||||
const QString CrashAbortThreaded = "Abort (threaded)";
|
||||
const QString CrashOutOfBoundsVectorAccess = "Out of Bounds Vector Access";
|
||||
const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)";
|
||||
const QString CrashNewFault = "New Fault";
|
||||
const QString CrashNewFaultThreaded = "New Fault (threaded)";
|
||||
const QString DeadlockInterface = "Deadlock Interface";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DeleteBookmark = "Delete Bookmark...";
|
||||
|
|
|
@ -134,12 +134,9 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
|
|||
}
|
||||
|
||||
AABox Avatar::getBounds() const {
|
||||
// Our skeleton models are rigged, and this method call safely produces the static bounds of the model.
|
||||
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
|
||||
// and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the
|
||||
// real model bounds, if this is unrealistically small.
|
||||
if (!_skeletonModel->isRenderable()) {
|
||||
return AABox(getPosition(), getUniformScale()); // approximately 2m tall, scaled to user request.
|
||||
if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) {
|
||||
// approximately 2m tall, scaled to user request.
|
||||
return AABox(getPosition() - glm::vec3(getUniformScale()), getUniformScale() * 2.0f);
|
||||
}
|
||||
return _skeletonModel->getRenderableMeshBound();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -48,36 +48,47 @@ const glm::vec3 DEFAULT_NECK_POS(0.0f, 0.70f, 0.0f);
|
|||
|
||||
void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {
|
||||
|
||||
// find an unused AnimClip clipNode
|
||||
std::shared_ptr<AnimClip> clip;
|
||||
if (_userAnimState == UserAnimState::None || _userAnimState == UserAnimState::B) {
|
||||
_userAnimState = UserAnimState::A;
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimA"));
|
||||
} else if (_userAnimState == UserAnimState::A) {
|
||||
_userAnimState = UserAnimState::B;
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimB"));
|
||||
UserAnimState::ClipNodeEnum clipNodeEnum;
|
||||
if (_userAnimState.clipNodeEnum == UserAnimState::None || _userAnimState.clipNodeEnum == UserAnimState::B) {
|
||||
clipNodeEnum = UserAnimState::A;
|
||||
} else {
|
||||
clipNodeEnum = UserAnimState::B;
|
||||
}
|
||||
|
||||
// set parameters
|
||||
clip->setLoopFlag(loop);
|
||||
clip->setStartFrame(firstFrame);
|
||||
clip->setEndFrame(lastFrame);
|
||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
|
||||
clip->setTimeScale(timeScale);
|
||||
clip->loadURL(url);
|
||||
if (_animNode) {
|
||||
// find an unused AnimClip clipNode
|
||||
std::shared_ptr<AnimClip> clip;
|
||||
if (clipNodeEnum == UserAnimState::A) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimA"));
|
||||
} else {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimB"));
|
||||
}
|
||||
|
||||
_currentUserAnimURL = url;
|
||||
if (clip) {
|
||||
// set parameters
|
||||
clip->setLoopFlag(loop);
|
||||
clip->setStartFrame(firstFrame);
|
||||
clip->setEndFrame(lastFrame);
|
||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
|
||||
clip->setTimeScale(timeScale);
|
||||
clip->loadURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
// store current user anim state.
|
||||
_userAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };
|
||||
|
||||
// notify the userAnimStateMachine the desired state.
|
||||
_animVars.set("userAnimNone", false);
|
||||
_animVars.set("userAnimA", _userAnimState == UserAnimState::A);
|
||||
_animVars.set("userAnimB", _userAnimState == UserAnimState::B);
|
||||
_animVars.set("userAnimA", clipNodeEnum == UserAnimState::A);
|
||||
_animVars.set("userAnimB", clipNodeEnum == UserAnimState::B);
|
||||
}
|
||||
|
||||
void Rig::restoreAnimation() {
|
||||
if (_currentUserAnimURL != "") {
|
||||
_currentUserAnimURL = "";
|
||||
if (_userAnimState.clipNodeEnum != UserAnimState::None) {
|
||||
_userAnimState.clipNodeEnum = UserAnimState::None;
|
||||
|
||||
// notify the userAnimStateMachine the desired state.
|
||||
_animVars.set("userAnimNone", true);
|
||||
_animVars.set("userAnimA", false);
|
||||
|
@ -1129,6 +1140,14 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
|
||||
_animNode = nodeIn;
|
||||
_animNode->setSkeleton(_animSkeleton);
|
||||
|
||||
if (_userAnimState.clipNodeEnum != UserAnimState::None) {
|
||||
// restore the user animation we had before reset.
|
||||
UserAnimState origState = _userAnimState;
|
||||
_userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
|
||||
overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
|
||||
}
|
||||
|
||||
});
|
||||
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
|
||||
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
|
|
|
@ -289,13 +289,28 @@ public:
|
|||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
float _desiredStateAge { 0.0f };
|
||||
enum class UserAnimState {
|
||||
None = 0,
|
||||
A,
|
||||
B
|
||||
|
||||
struct UserAnimState {
|
||||
enum ClipNodeEnum {
|
||||
None = 0,
|
||||
A,
|
||||
B
|
||||
};
|
||||
|
||||
UserAnimState() : clipNodeEnum(UserAnimState::None) {}
|
||||
UserAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
|
||||
clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}
|
||||
|
||||
ClipNodeEnum clipNodeEnum;
|
||||
QString url;
|
||||
float fps;
|
||||
bool loop;
|
||||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
UserAnimState _userAnimState { UserAnimState::None };
|
||||
QString _currentUserAnimURL;
|
||||
|
||||
UserAnimState _userAnimState;
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
float _rightHandOverlayAlpha { 0.0f };
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include <SharedUtil.h> // usecTimestampNow()
|
||||
#include <SoundCache.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "EntityScriptingInterface.h"
|
||||
#include "EntitiesLogging.h"
|
||||
|
@ -516,8 +517,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
EntityTreePointer tree = getTree();
|
||||
if (tree && tree->isDeletedEntity(_id)) {
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "Received packet for previously deleted entity [" << _id << "] ignoring. "
|
||||
"(inside " << __FUNCTION__ << ")";
|
||||
qCDebug(entities) << "Received packet for previously deleted entity [" << _id << "] ignoring. "
|
||||
"(inside " << __FUNCTION__ << ")";
|
||||
#endif
|
||||
ignoreServerPacket = true;
|
||||
}
|
||||
|
@ -1685,7 +1686,7 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi
|
|||
_allActionsDataCache = newDataCache;
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
} else {
|
||||
qDebug() << "EntityItem::addActionInternal -- serializeActions failed";
|
||||
qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed";
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
@ -1706,7 +1707,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI
|
|||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
} else {
|
||||
qDebug() << "EntityItem::updateAction failed";
|
||||
qCDebug(entities) << "EntityItem::updateAction failed";
|
||||
}
|
||||
});
|
||||
return success;
|
||||
|
@ -1777,7 +1778,7 @@ void EntityItem::deserializeActionsInternal() {
|
|||
quint64 now = usecTimestampNow();
|
||||
|
||||
if (!_element) {
|
||||
qDebug() << "EntityItem::deserializeActionsInternal -- no _element";
|
||||
qCDebug(entities) << "EntityItem::deserializeActionsInternal -- no _element";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1805,14 +1806,13 @@ void EntityItem::deserializeActionsInternal() {
|
|||
continue;
|
||||
}
|
||||
|
||||
updated << actionID;
|
||||
|
||||
if (_objectActions.contains(actionID)) {
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
// TODO: make sure types match? there isn't currently a way to
|
||||
// change the type of an existing action.
|
||||
action->deserialize(serializedAction);
|
||||
action->locallyAddedButNotYetReceived = false;
|
||||
updated << actionID;
|
||||
} else {
|
||||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||
EntityItemPointer entity = getThisPointer();
|
||||
|
@ -1820,8 +1820,13 @@ void EntityItem::deserializeActionsInternal() {
|
|||
if (action) {
|
||||
entity->addActionInternal(simulation, action);
|
||||
action->locallyAddedButNotYetReceived = false;
|
||||
updated << actionID;
|
||||
} else {
|
||||
qDebug() << "EntityItem::deserializeActionsInternal -- action creation failed";
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex(".*action creation failed for.*");
|
||||
qCDebug(entities) << "EntityItem::deserializeActionsInternal -- action creation failed for"
|
||||
<< getID() << getName();
|
||||
removeActionInternal(actionID, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1897,7 +1902,8 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const {
|
|||
serializedActionsStream << serializedActions;
|
||||
|
||||
if (result.size() >= _maxActionsDataSize) {
|
||||
qDebug() << "EntityItem::serializeActions size is too large -- " << result.size() << ">=" << _maxActionsDataSize;
|
||||
qCDebug(entities) << "EntityItem::serializeActions size is too large -- "
|
||||
<< result.size() << ">=" << _maxActionsDataSize;
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -699,7 +699,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc
|
|||
|
||||
// if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
|
||||
glm::vec3 penetration;
|
||||
if (success && entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) {
|
||||
if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) {
|
||||
|
||||
glm::vec3 dimensions = entity->getDimensions();
|
||||
|
||||
|
@ -764,7 +764,7 @@ void EntityTreeElement::getEntities(const AACube& cube, QVector<EntityItemPointe
|
|||
//
|
||||
|
||||
// If the entities AABox touches the search cube then consider it to be found
|
||||
if (success && entityBox.touches(cube)) {
|
||||
if (!success || entityBox.touches(cube)) {
|
||||
foundEntities.push_back(entity);
|
||||
}
|
||||
});
|
||||
|
@ -790,7 +790,7 @@ void EntityTreeElement::getEntities(const AABox& box, QVector<EntityItemPointer>
|
|||
//
|
||||
|
||||
// If the entities AABox touches the search cube then consider it to be found
|
||||
if (success && entityBox.touches(box)) {
|
||||
if (!success || entityBox.touches(box)) {
|
||||
foundEntities.push_back(entity);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,32 +28,80 @@
|
|||
(((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 {
|
||||
QWriteLocker locker(&_resourcesLock);
|
||||
_resources.remove(url);
|
||||
resetResourceCounters();
|
||||
}
|
||||
|
@ -103,8 +152,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 +176,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();
|
||||
|
||||
|
@ -137,21 +193,29 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) {
|
|||
}
|
||||
|
||||
void ResourceCache::addUnusedResource(const QSharedPointer<Resource>& resource) {
|
||||
// If it doesn't fit or its size is unknown, leave the cache alone.
|
||||
// If it doesn't fit or its size is unknown, remove it from the cache.
|
||||
if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) {
|
||||
resource->setCache(nullptr);
|
||||
|
||||
_totalResourcesSize -= resource->getBytes();
|
||||
_resources.remove(resource->getURL());
|
||||
resetResourceCounters();
|
||||
|
||||
return;
|
||||
}
|
||||
reserveUnusedResource(resource->getBytes());
|
||||
|
||||
resource->setLRUKey(++_lastLRUKey);
|
||||
_unusedResources.insert(resource->getLRUKey(), resource);
|
||||
_unusedResourcesSize += resource->getBytes();
|
||||
|
||||
resetResourceCounters();
|
||||
|
||||
QWriteLocker locker(&_unusedResourcesLock);
|
||||
_unusedResources.insert(resource->getLRUKey(), resource);
|
||||
}
|
||||
|
||||
void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resource) {
|
||||
QWriteLocker locker(&_unusedResourcesLock);
|
||||
if (_unusedResources.contains(resource->getLRUKey())) {
|
||||
_unusedResources.remove(resource->getLRUKey());
|
||||
_unusedResourcesSize -= resource->getBytes();
|
||||
|
@ -160,6 +224,7 @@ void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resourc
|
|||
}
|
||||
|
||||
void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
|
||||
QWriteLocker locker(&_unusedResourcesLock);
|
||||
while (!_unusedResources.empty() &&
|
||||
_unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) {
|
||||
// unload the oldest resource
|
||||
|
@ -179,6 +244,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
|
|||
void ResourceCache::clearUnusedResource() {
|
||||
// the unused resources may themselves reference resources that will be added to the unused
|
||||
// list on destruction, so keep clearing until there are no references left
|
||||
QWriteLocker locker(&_unusedResourcesLock);
|
||||
while (!_unusedResources.isEmpty()) {
|
||||
foreach (const QSharedPointer<Resource>& resource, _unusedResources) {
|
||||
resource->setCache(nullptr);
|
||||
|
@ -198,19 +264,28 @@ void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize
|
|||
emit dirty();
|
||||
}
|
||||
|
||||
void ResourceCacheSharedItems::appendActiveRequest(Resource* resource) {
|
||||
void ResourceCacheSharedItems::appendActiveRequest(QWeakPointer<Resource> 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 {
|
||||
|
@ -218,23 +293,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;
|
||||
|
@ -243,16 +328,25 @@ Resource* ResourceCacheSharedItems::getHighestPendingRequest() {
|
|||
if (priority >= highestPriority) {
|
||||
highestPriority = priority;
|
||||
highestIndex = i;
|
||||
highestResource = resource;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (highestIndex >= 0) {
|
||||
return _pendingRequests.takeAt(highestIndex);
|
||||
_pendingRequests.takeAt(highestIndex);
|
||||
}
|
||||
return nullptr;
|
||||
return highestResource;
|
||||
}
|
||||
|
||||
bool ResourceCache::attemptRequest(Resource* resource) {
|
||||
QList<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) {
|
||||
|
@ -267,7 +361,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;
|
||||
|
@ -291,11 +385,6 @@ Resource::Resource(const QUrl& url, bool delayLoad) :
|
|||
_request(nullptr) {
|
||||
|
||||
init();
|
||||
|
||||
// start loading immediately unless instructed otherwise
|
||||
if (!(_startedLoading || delayLoad)) {
|
||||
QTimer::singleShot(0, this, &Resource::ensureLoading);
|
||||
}
|
||||
}
|
||||
|
||||
Resource::~Resource() {
|
||||
|
@ -303,7 +392,7 @@ Resource::~Resource() {
|
|||
_request->disconnect(this);
|
||||
_request->deleteLater();
|
||||
_request = nullptr;
|
||||
ResourceCache::requestCompleted(this);
|
||||
ResourceCache::requestCompleted(_self);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,7 +445,7 @@ void Resource::refresh() {
|
|||
_request->disconnect(this);
|
||||
_request->deleteLater();
|
||||
_request = nullptr;
|
||||
ResourceCache::requestCompleted(this);
|
||||
ResourceCache::requestCompleted(_self);
|
||||
}
|
||||
|
||||
init();
|
||||
|
@ -401,7 +490,7 @@ void Resource::init() {
|
|||
|
||||
void Resource::attemptRequest() {
|
||||
_startedLoading = true;
|
||||
ResourceCache::attemptRequest(this);
|
||||
ResourceCache::attemptRequest(_self);
|
||||
}
|
||||
|
||||
void Resource::finishedLoading(bool success) {
|
||||
|
@ -422,18 +511,22 @@ void Resource::setSize(const qint64& bytes) {
|
|||
}
|
||||
|
||||
void Resource::reinsert() {
|
||||
QWriteLocker locker(&_cache->_resourcesLock);
|
||||
_cache->_resources.insert(_url, _self);
|
||||
}
|
||||
|
||||
|
||||
void Resource::makeRequest() {
|
||||
Q_ASSERT(!_request);
|
||||
if (_request) {
|
||||
_request->disconnect();
|
||||
_request->deleteLater();
|
||||
}
|
||||
|
||||
_request = ResourceManager::createResourceRequest(this, _activeUrl);
|
||||
|
||||
if (!_request) {
|
||||
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||
ResourceCache::requestCompleted(this);
|
||||
ResourceCache::requestCompleted(_self);
|
||||
finishedLoading(false);
|
||||
return;
|
||||
}
|
||||
|
@ -465,7 +558,7 @@ void Resource::handleReplyFinished() {
|
|||
return;
|
||||
}
|
||||
|
||||
ResourceCache::requestCompleted(this);
|
||||
ResourceCache::requestCompleted(_self);
|
||||
|
||||
auto result = _request->getResult();
|
||||
if (result == ResourceRequest::Success) {
|
||||
|
|
|
@ -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:
|
||||
|
@ -154,6 +154,7 @@ private:
|
|||
void clearUnusedResource();
|
||||
void resetResourceCounters();
|
||||
|
||||
QReadWriteLock _resourcesLock { QReadWriteLock::Recursive };
|
||||
QHash<QUrl, QWeakPointer<Resource>> _resources;
|
||||
int _lastLRUKey = 0;
|
||||
|
||||
|
@ -161,7 +162,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 };
|
||||
|
@ -171,6 +172,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;
|
||||
|
|
|
@ -54,9 +54,6 @@ TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight,
|
|||
}
|
||||
}
|
||||
|
||||
TextRenderer3D::~TextRenderer3D() {
|
||||
}
|
||||
|
||||
glm::vec2 TextRenderer3D::computeExtent(const QString& str) const {
|
||||
if (_font) {
|
||||
return _font->computeExtent(str);
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
#ifndef hifi_TextRenderer3D_h
|
||||
#define hifi_TextRenderer3D_h
|
||||
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <QColor>
|
||||
|
||||
|
||||
|
||||
namespace gpu {
|
||||
class Batch;
|
||||
}
|
||||
|
@ -34,8 +33,6 @@ public:
|
|||
static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE,
|
||||
bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1);
|
||||
|
||||
~TextRenderer3D();
|
||||
|
||||
glm::vec2 computeExtent(const QString& str) const;
|
||||
float getFontSize() const; // Pixel size
|
||||
|
||||
|
@ -55,7 +52,7 @@ private:
|
|||
// text color
|
||||
glm::vec4 _color;
|
||||
|
||||
Font* _font;
|
||||
std::shared_ptr<Font> _font;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -47,15 +47,15 @@ struct QuadBuilder {
|
|||
|
||||
|
||||
|
||||
static QHash<QString, Font*> LOADED_FONTS;
|
||||
static QHash<QString, Font::Pointer> LOADED_FONTS;
|
||||
|
||||
Font* Font::load(QIODevice& fontFile) {
|
||||
Font* result = new Font();
|
||||
result->read(fontFile);
|
||||
return result;
|
||||
Font::Pointer Font::load(QIODevice& fontFile) {
|
||||
Pointer font = std::make_shared<Font>();
|
||||
font->read(fontFile);
|
||||
return font;
|
||||
}
|
||||
|
||||
Font* Font::load(const QString& family) {
|
||||
Font::Pointer Font::load(const QString& family) {
|
||||
if (!LOADED_FONTS.contains(family)) {
|
||||
|
||||
static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" };
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
class Font {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<Font>;
|
||||
|
||||
Font();
|
||||
|
||||
void read(QIODevice& path);
|
||||
|
@ -29,8 +31,8 @@ public:
|
|||
const glm::vec4* color, EffectType effectType,
|
||||
const glm::vec2& bound, bool layered = false);
|
||||
|
||||
static Font* load(QIODevice& fontFile);
|
||||
static Font* load(const QString& family);
|
||||
static Pointer load(QIODevice& fontFile);
|
||||
static Pointer load(const QString& family);
|
||||
|
||||
private:
|
||||
QStringList tokenizeForWrapping(const QString& str) const;
|
||||
|
|
82
libraries/shared/src/CrashHelpers.h
Normal file
82
libraries/shared/src/CrashHelpers.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// CrashHelpers.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Ryan Huffman on 11 April 2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_CrashHelpers_h
|
||||
#define hifi_CrashHelpers_h
|
||||
|
||||
namespace crash {
|
||||
|
||||
class B;
|
||||
class A {
|
||||
public:
|
||||
A(B* b) : _b(b) { }
|
||||
~A();
|
||||
virtual void virtualFunction() = 0;
|
||||
|
||||
private:
|
||||
B* _b;
|
||||
};
|
||||
|
||||
class B : public A {
|
||||
public:
|
||||
B() : A(this) { }
|
||||
virtual void virtualFunction() { }
|
||||
};
|
||||
|
||||
A::~A() {
|
||||
_b->virtualFunction();
|
||||
}
|
||||
|
||||
void pureVirtualCall() {
|
||||
qDebug() << "About to make a pure virtual call";
|
||||
B b;
|
||||
}
|
||||
|
||||
void doubleFree() {
|
||||
qDebug() << "About to double delete memory";
|
||||
int* blah = new int(200);
|
||||
delete blah;
|
||||
delete blah;
|
||||
}
|
||||
|
||||
void nullDeref() {
|
||||
qDebug() << "About to dereference a null pointer";
|
||||
int* p = nullptr;
|
||||
*p = 1;
|
||||
}
|
||||
|
||||
void doAbort() {
|
||||
qDebug() << "About to abort";
|
||||
abort();
|
||||
}
|
||||
|
||||
void outOfBoundsVectorCrash() {
|
||||
qDebug() << "std::vector out of bounds crash!";
|
||||
std::vector<int> v;
|
||||
v[0] = 42;
|
||||
}
|
||||
|
||||
void newFault() {
|
||||
qDebug() << "About to crash inside new fault";
|
||||
|
||||
// Force crash with multiple large allocations
|
||||
while (true) {
|
||||
const size_t GIGABYTE = 1024 * 1024 * 1024;
|
||||
new char[GIGABYTE];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // hifi_CrashHelpers_h
|
|
@ -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