diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0a455891f9..1e17467c3b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -63,7 +63,9 @@ AvatarMixer::~AvatarMixer() { _broadcastThread.wait(); } -const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; +// An 80% chance of sending a identity packet within a 5 second interval. +// assuming 60 htz update rate. +const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present @@ -243,6 +245,46 @@ void AvatarMixer::broadcastAvatarData() { return; } + // make sure we send out identity and billboard packets to and from new arrivals. + bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); + + // we will also force a send of billboard or identity packet + // if either has changed in the last frame + if (otherNodeData->getBillboardChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp + || distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { + + QByteArray rfcUUID = otherNode->getUUID().toRfc4122(); + QByteArray billboard = otherNodeData->getAvatar().getBillboard(); + + auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size()); + billboardPacket->write(rfcUUID); + billboardPacket->write(billboard); + + nodeList->sendPacket(std::move(billboardPacket), *node); + + ++_sumBillboardPackets; + } + + if (otherNodeData->getIdentityChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp + || distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { + + QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); + + auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); + + individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122()); + + identityPacket->write(individualData); + + nodeList->sendPacket(std::move(identityPacket), *node); + + ++_sumIdentityPackets; + } + AvatarData& otherAvatar = otherNodeData->getAvatar(); // Decide whether to send this avatar's data based on it's distance from us @@ -254,10 +296,10 @@ void AvatarMixer::broadcastAvatarData() { // potentially update the max full rate distance for this frame maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); - if (distanceToAvatar != 0.0f + if (distanceToAvatar != 0.0f && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { - return; - } + return; + } AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); @@ -291,53 +333,11 @@ void AvatarMixer::broadcastAvatarData() { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += - avatarPacketList->write(otherAvatar.toByteArray(false, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO)); + avatarPacketList->write(otherAvatar.toByteArray(false, distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO)); avatarPacketList->endSegment(); - - // if the receiving avatar has just connected make sure we send out the mesh and billboard - // for this avatar (assuming they exist) - bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets(); - - // we will also force a send of billboard or identity packet - // if either has changed in the last frame - - if (otherNodeData->getBillboardChangeTimestamp() > 0 - && (forceSend - || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp - || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { - - QByteArray rfcUUID = otherNode->getUUID().toRfc4122(); - QByteArray billboard = otherNodeData->getAvatar().getBillboard(); - - auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size()); - billboardPacket->write(rfcUUID); - billboardPacket->write(billboard); - - nodeList->sendPacket(std::move(billboardPacket), *node); - - ++_sumBillboardPackets; - } - - if (otherNodeData->getIdentityChangeTimestamp() > 0 - && (forceSend - || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp - || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { - - QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); - - auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); - - individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122()); - - identityPacket->write(individualData); - - nodeList->sendPacket(std::move(identityPacket), *node); - - ++_sumIdentityPackets; - } }); - + // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); @@ -484,7 +484,7 @@ void AvatarMixer::sendStatsPacket() { // add the key to ask the domain-server for a username replacement, if it has it avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID()); - + avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth(); avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundBandwidth(); @@ -537,7 +537,7 @@ void AvatarMixer::run() { qDebug() << "Waiting for domain settings from domain-server."; // block until we get the settingsRequestComplete signal - + QEventLoop loop; connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 0a9be20691..9d78d92463 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,15 +16,17 @@ int AvatarMixerClientData::parseData(NLPacket& packet) { // pull the sequence number from the data first packet.readPrimitive(&_lastReceivedSequenceNumber); - + // compute the offset to the data payload return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); } -bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() { - bool oldValue = _hasReceivedFirstPackets; - _hasReceivedFirstPackets = true; - return oldValue; +bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { + if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) { + _hasReceivedFirstPacketsFrom.insert(uuid); + return false; + } + return true; } uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { @@ -45,9 +47,9 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["avg_other_av_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond(); jsonObject["avg_other_av_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond(); jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; - + jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; - + jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate(); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index e68a54c265..1f5e8fa77a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -34,25 +35,25 @@ class AvatarMixerClientData : public NodeData { public: int parseData(NLPacket& packet); AvatarData& getAvatar() { return _avatar; } - - bool checkAndSetHasReceivedFirstPackets(); + + bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } - + uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } - + quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; } - + void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; } float getFullRateDistance() const { return _fullRateDistance; } - + void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; } float getMaxAvatarDistance() const { return _maxAvatarDistance; } @@ -73,31 +74,31 @@ public: void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; } void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); } - + float getOutboundAvatarDataKbps() const { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } - + void loadJSONStats(QJsonObject& jsonObject) const; private: AvatarData _avatar; - + uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; + std::unordered_set _hasReceivedFirstPacketsFrom; - bool _hasReceivedFirstPackets = false; quint64 _billboardChangeTimestamp = 0; quint64 _identityChangeTimestamp = 0; - + float _fullRateDistance = FLT_MAX; float _maxAvatarDistance = FLT_MAX; - + int _numAvatarsSentLastFrame = 0; int _numFramesSinceAdjustment = 0; SimpleMovingAverage _otherAvatarStarves; SimpleMovingAverage _otherAvatarSkips; int _numOutOfOrderSends = 0; - + SimpleMovingAverage _avgOtherAvatarDataRate; }; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 1d92ed917f..7c8d8f0e01 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -17,6 +17,7 @@ #include "OctreeSendThread.h" #include "OctreeServer.h" #include "OctreeServerConsts.h" +#include "OctreeLogging.h" quint64 startSceneSleepTime = 0; quint64 endSceneSleepTime = 0; @@ -572,14 +573,12 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus OctreeServer::trackInsideTime((float)elapsedInsideUsecs); } - if (somethingToSend && _myServer->wantsVerboseDebug()) { - qDebug() << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval - << " maxPacketsPerInterval = " << maxPacketsPerInterval - << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; + qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval + << " maxPacketsPerInterval = " << maxPacketsPerInterval + << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; } - // Here's where we can/should allow the server to send other data... // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b5fd9f2b20..127c121cf3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -272,6 +272,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerMessageListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); + packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); @@ -1826,3 +1827,24 @@ void DomainServer::processPathQueryPacket(QSharedPointer packet) { } } } + +void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer packet) { + // This packet has been matched to a source node and they're asking not to be in the domain anymore + auto limitedNodeList = DependencyManager::get(); + + const QUuid& nodeUUID = packet->getSourceID(); + + qDebug() << "Received a disconnect request from node with UUID" << nodeUUID; + + if (limitedNodeList->killNodeWithUUID(nodeUUID)) { + static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID); + + removedNodePacket->reset(); + removedNodePacket->write(nodeUUID.toRfc4122()); + + // broadcast out the DomainServerRemovedNode message + limitedNodeList->eachNode([&limitedNodeList](const SharedNodePointer& otherNode){ + limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode); + }); + } +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index df42bf3ad9..e5b3d3b3fd 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -60,7 +60,8 @@ public slots: void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); - + void processNodeDisconnectRequestPacket(QSharedPointer packet); + private slots: void aboutToQuit(); diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index 60b72446bb..ba066d9750 100644 --- a/examples/acScripts/ControlACs.js +++ b/examples/acScripts/ControlACs.js @@ -33,15 +33,6 @@ var SHOW = 4; var HIDE = 5; var LOAD = 6; -var COLORS = []; -COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 }; -COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 }; -COLORS[STOP] = { red: STOP, green: 0, blue: 0 }; -COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 }; -COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 }; -COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 }; - - var windowDimensions = Controller.getViewportDimensions(); var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/"; @@ -138,6 +129,7 @@ function setupToolBars() { } function sendCommand(id, action) { + if (action === SHOW) { toolBars[id].selectTool(onOffIcon[id], false); toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); @@ -154,24 +146,29 @@ function sendCommand(id, action) { return; } - if (id === (toolBars.length - 1)) { - for (i = 0; i < NUM_AC; i++) { - sendCommand(i, action); - } - return; - } + if (id === (toolBars.length - 1)) + id = -1; - var position = { x: controlEntityPosition.x + id * controlEntitySize, - y: controlEntityPosition.y, z: controlEntityPosition.z }; - Entities.addEntity({ - name: "Actor Controller", - userData: clip_url, + var controlEntity = Entities.addEntity({ + name: 'New Actor Controller', type: "Box", - position: position, - dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize }, - color: COLORS[action], - lifetime: 5 - }); + color: { red: 0, green: 0, blue: 0 }, + position: controlEntityPosition, + dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize }, + visible: false, + lifetime: 10, + userData: JSON.stringify({ + idKey: { + uD_id: id + }, + actionKey: { + uD_action: action + }, + clipKey: { + uD_url: clip_url + } + }) + }); } function mousePressEvent(event) { @@ -191,8 +188,12 @@ function mousePressEvent(event) { sendCommand(i, PLAY_LOOP); } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, LOAD); + } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + input_text = Window.prompt("Insert the url of the clip: ",""); + if (!(input_text === "" || input_text === null)) { + clip_url = input_text; + sendCommand(i, LOAD); + } } else { // Check individual controls for (i = 0; i < NUM_AC; i++) { @@ -210,7 +211,7 @@ function mousePressEvent(event) { sendCommand(i, STOP); } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); - if(!(input_text === "" || input_text === null)){ + if (!(input_text === "" || input_text === null)) { clip_url = input_text; sendCommand(i, LOAD); } diff --git a/examples/acScripts/ControlledAC.js b/examples/acScripts/ControlledAC.js index 4eecc11136..41a8a2b257 100644 --- a/examples/acScripts/ControlledAC.js +++ b/examples/acScripts/ControlledAC.js @@ -38,18 +38,6 @@ var SHOW = 4; var HIDE = 5; var LOAD = 6; -var COLORS = []; -COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 }; -COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 }; -COLORS[STOP] = { red: STOP, green: 0, blue: 0 }; -COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 }; -COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 }; -COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 }; - -controlEntityPosition.x += id * controlEntitySize; - -Avatar.loadRecording(clip_url); - Avatar.setPlayFromCurrentLocation(playFromCurrentLocation); Avatar.setPlayerUseDisplayName(useDisplayName); Avatar.setPlayerUseAttachments(useAttachments); @@ -67,27 +55,27 @@ function setupEntityViewer() { EntityViewer.queryOctree(); } -function getAction(controlEntity) { - clip_url = controlEntity.userData; +function getAction(controlEntity) { + if (controlEntity === null) { + return DO_NOTHING; + } + + var userData = JSON.parse(Entities.getEntityProperties(controlEntity, ["userData"]).userData); - if (controlEntity === null || - controlEntity.position.x !== controlEntityPosition.x || - controlEntity.position.y !== controlEntityPosition.y || - controlEntity.position.z !== controlEntityPosition.z || - controlEntity.dimensions.x !== controlEntitySize) { + var uD_id = userData.idKey.uD_id; + var uD_action = userData.actionKey.uD_action; + var uD_url = userData.clipKey.uD_url; + + Entities.deleteEntity((Entities.getEntityProperties(controlEntity)).id); + + if (uD_id === id || uD_id === -1) { + if (uD_action === 6) + clip_url = uD_url; + + return uD_action; + } else { return DO_NOTHING; - } - - for (i in COLORS) { - if (controlEntity.color.red === COLORS[i].red && - controlEntity.color.green === COLORS[i].green && - controlEntity.color.blue === COLORS[i].blue) { - Entities.deleteEntity(controlEntity.id); - return parseInt(i); - } - } - - return DO_NOTHING; + } } count = 100; // This is necessary to wait for the audio mixer to connect @@ -100,7 +88,7 @@ function update(event) { var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize); - var action = getAction(Entities.getEntityProperties(controlEntity)); + var action = getAction(controlEntity); switch(action) { case PLAY: diff --git a/examples/grab.js b/examples/grab.js index ee6c3c4de5..1637e1bcf2 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -506,6 +506,7 @@ Grabber.prototype.activateEntity = function(entityID, grabbedProperties) { if (data["refCount"] == 1) { data["gravity"] = grabbedProperties.gravity; data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; + data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; var whileHeldProperties = {gravity: {x:0, y:0, z:0}}; if (invertSolidWhileHeld) { whileHeldProperties["ignoreForCollisions"] = ! grabbedProperties.ignoreForCollisions; @@ -522,7 +523,8 @@ Grabber.prototype.deactivateEntity = function(entityID) { if (data["refCount"] < 1) { Entities.editEntity(entityID, { gravity: data["gravity"], - ignoreForCollisions: data["ignoreForCollisions"] + ignoreForCollisions: data["ignoreForCollisions"], + collisionsWillMove: data["collisionsWillMove"] }); data = null; } diff --git a/examples/grabInspector.js b/examples/grabInspector.js new file mode 100644 index 0000000000..8a027f819a --- /dev/null +++ b/examples/grabInspector.js @@ -0,0 +1,137 @@ +// +// grabInspector.js +// examples +// +// Created by Seth Alves on 2015-9-30. +// Copyright 2015 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 +// + +Script.include("libraries/utils.js"); + +var INSPECT_RADIUS = 10; +var overlays = {}; + +var toType = function(obj) { + return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() +} + +function grabDataToString(grabData) { + var result = ""; + + for (var argumentName in grabData) { + if (grabData.hasOwnProperty(argumentName)) { + if (argumentName == "type") { + continue; + } + var arg = grabData[argumentName]; + var argType = toType(arg); + var argString = arg; + if (argType == "object") { + if (Object.keys(arg).length == 3) { + argString = vec3toStr(arg, 1); + } + } else if (argType == "number") { + argString = arg.toFixed(2); + } + result += argumentName + ": " + // + toType(arg) + " -- " + + argString + "\n"; + } + } + + return result; +} + + + +function updateOverlay(entityID, grabText) { + var properties = Entities.getEntityProperties(entityID, ["position", "dimensions"]); + var position = Vec3.sum(properties.position, {x:0, y:properties.dimensions.y, z:0}); + if (entityID in overlays) { + var overlay = overlays[entityID]; + Overlays.editOverlay(overlay, { + text: grabText, + position: position + }); + } else { + var lines = grabText.split(/\r\n|\r|\n/); + + var maxLineLength = lines[0].length; + for (var i = 1; i < lines.length; i++) { + if (lines[i].length > maxLineLength) { + maxLineLength = lines[i].length; + } + } + + var textWidth = maxLineLength * 0.034; // XXX how to know this? + var textHeight = .5; + var numberOfLines = lines.length; + var textMargin = 0.05; + var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; + + overlays[entityID] = Overlays.addOverlay("text3d", { + position: position, + dimensions: { x: textWidth, y: textHeight }, + backgroundColor: { red: 0, green: 0, blue: 0}, + color: { red: 255, green: 255, blue: 255}, + topMargin: textMargin, + leftMargin: textMargin, + bottomMargin: textMargin, + rightMargin: textMargin, + text: grabText, + lineHeight: lineHeight, + alpha: 0.9, + backgroundAlpha: 0.9, + ignoreRayIntersection: true, + visible: true, + isFacingAvatar: true + }); + } +} + + +function cleanup() { + for (var entityID in overlays) { + Overlays.deleteOverlay(overlays[entityID]); + } +} + + +Script.setInterval(function() { + var nearbyEntities = Entities.findEntities(MyAvatar.position, INSPECT_RADIUS); + for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) { + var entityID = nearbyEntities[entityIndex]; + var userData = getEntityUserData(entityID); + var grabData = userData["grabKey"] + + // {"grabbableKey":{"invertSolidWhileHeld":true}, + // "grabKey":{"activated":true,"avatarId":"{6ea8b092-10e0-4058-888b-6facc40d0fe9}","refCount":1,"gravity":{"x":0,"y":0,"z":0},"ignoreForCollisions":0,"collisionsWillMove":1} + // } + + if (typeof grabData != 'undefined') { + var grabText = grabDataToString(grabData); + updateOverlay(entityID, grabText); + } else { + if (entityID in overlays) { + Overlays.deleteOverlay(overlays[entityID]); + delete overlays[entityID]; + } + } + } + + // if an entity is too far away, remove its overlay + for (var entityID in overlays) { + var position = Entities.getEntityProperties(entityID, ["position"]).position; + if (Vec3.distance(position, MyAvatar.position) > INSPECT_RADIUS) { + Overlays.deleteOverlay(overlays[entityID]); + delete overlays[entityID]; + } + } + +}, 100); + + +Script.scriptEnding.connect(cleanup); diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index 6d49030a28..40476626e8 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -176,14 +176,13 @@ function formatTime(time) { var SEC_PER_MIN = 60; var MSEC_PER_SEC = 1000; - var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); - time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); + var hours = Math.floor(time / (SEC_PER_MIN * MIN_PER_HOUR)); + time -= hours * (SEC_PER_MIN * MIN_PER_HOUR); - var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN)); - time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN); + var minutes = Math.floor(time / (SEC_PER_MIN)); + time -= minutes * (SEC_PER_MIN); - var seconds = Math.floor(time / MSEC_PER_SEC); - seconds = time / MSEC_PER_SEC; + var seconds = time; var text = ""; text += (hours > 0) ? hours + ":" : diff --git a/examples/utilities/tools/renderEngineDebug.js b/examples/utilities/tools/renderEngineDebug.js index 3271741985..cca97b7184 100755 --- a/examples/utilities/tools/renderEngineDebug.js +++ b/examples/utilities/tools/renderEngineDebug.js @@ -62,10 +62,24 @@ var overlaysCounter = new CounterWidget(panel, "Overlays", ); -panel.newCheckbox("Display status", - function(value) { Scene.setEngineDisplayItemStatus(value); }, - function() { return Scene.doEngineDisplayItemStatus(); }, - function(value) { return (value); } +// see libraries/render/src/render/Engine.h +var showDisplayStatusFlag = 1; +var showNetworkStatusFlag = 2; + +panel.newCheckbox("Display status", + function(value) { Scene.setEngineDisplayItemStatus(value ? + Scene.doEngineDisplayItemStatus() | showDisplayStatusFlag : + Scene.doEngineDisplayItemStatus() & ~showDisplayStatusFlag); }, + function() { return (Scene.doEngineDisplayItemStatus() & showDisplayStatusFlag) > 0; }, + function(value) { return (value & showDisplayStatusFlag) > 0; } +); + +panel.newCheckbox("Network/Physics status", + function(value) { Scene.setEngineDisplayItemStatus(value ? + Scene.doEngineDisplayItemStatus() | showNetworkStatusFlag : + Scene.doEngineDisplayItemStatus() & ~showNetworkStatusFlag); }, + function() { return (Scene.doEngineDisplayItemStatus() & showNetworkStatusFlag) > 0; }, + function(value) { return (value & showNetworkStatusFlag) > 0; } ); var tickTackPeriod = 500; diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index a6a28caa23..f65ff4a8cf 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -55,8 +55,7 @@ bool IceServer::packetVersionMatch(const udt::Packet& packet) { if (headerVersion == versionForPacketType(headerType)) { return true; } else { - qDebug() << "Packet version mismatch for packet" << headerType - << "(" << nameForPacketType(headerType) << ") from" << packet.getSenderSockAddr(); + qDebug() << "Packet version mismatch for packet" << headerType << " from" << packet.getSenderSockAddr(); return false; } diff --git a/interface/resources/icons/statusIconAtlas.svg b/interface/resources/icons/statusIconAtlas.svg new file mode 100644 index 0000000000..72f9bc4af7 --- /dev/null +++ b/interface/resources/icons/statusIconAtlas.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7a564bbbf0..d54813135e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -804,8 +804,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : void Application::aboutToQuit() { emit beforeAboutToQuit(); + getActiveDisplayPlugin()->deactivate(); + _aboutToQuit = true; + cleanupBeforeQuit(); } @@ -831,8 +834,14 @@ void Application::cleanupBeforeQuit() { _entities.clear(); // this will allow entity scripts to properly shutdown + auto nodeList = DependencyManager::get(); + + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); + // tell the packet receiver we're shutting down, so it can drop packets - DependencyManager::get()->getPacketReceiver().setShouldDropPackets(true); + nodeList->getPacketReceiver().setShouldDropPackets(true); _entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts ScriptEngine::stopAllScripts(this); // stop all currently running global scripts @@ -852,9 +861,6 @@ void Application::cleanupBeforeQuit() { saveSettings(); _window->saveGeometry(); - // let the avatar mixer know we're out - MyAvatar::sendKillAvatar(); - // stop the AudioClient QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); @@ -3499,10 +3505,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { renderDebugFlags = (RenderArgs::DebugFlags) (renderDebugFlags | (int)RenderArgs::RENDER_DEBUG_HULLS); } - if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) { - renderDebugFlags = - (RenderArgs::DebugFlags) (renderDebugFlags | (int)RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP); - } renderArgs->_debugFlags = renderDebugFlags; //ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges); } @@ -3562,6 +3564,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se renderContext._maxDrawnOverlay3DItems = sceneInterface->getEngineMaxDrawnOverlay3DItems(); renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus(); + if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) { + renderContext._drawItemStatus |= render::showNetworkStatusFlag; + } renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect(); renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 7ca25dad42..8587c01d6a 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -271,6 +271,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { auto ownerEntity = _ownerEntity.lock(); if (ownerEntity) { ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); } }); activateBody(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 780a95a36a..a940e82f11 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -608,7 +608,7 @@ float MyAvatar::recorderElapsed() { if (!_recorder) { return 0; } - return (float)_recorder->position() / MSECS_PER_SECOND; + return (float)_recorder->position() / (float) MSECS_PER_SECOND; } QMetaObject::Connection _audioClientRecorderConnection; @@ -1024,11 +1024,6 @@ int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { return buffer.size(); } -void MyAvatar::sendKillAvatar() { - auto killPacket = NLPacket::create(PacketType::KillAvatar, 0); - DependencyManager::get()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer); -} - void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1687049f68..6e81ebfa69 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -162,8 +162,6 @@ public: eyeContactTarget getEyeContactTarget(); - static void sendKillAvatar(); - Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; } Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); } Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index adf471c50e..a47d5f663e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -804,12 +804,12 @@ float AvatarData::playerElapsed() { return 0; } if (QThread::currentThread() != thread()) { - qint64 result; + float result; QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); + Q_RETURN_ARG(float, result)); return result; } - return (float)_player->position() / MSECS_PER_SECOND; + return (float)_player->position() / (float) MSECS_PER_SECOND; } float AvatarData::playerLength() { @@ -817,12 +817,12 @@ float AvatarData::playerLength() { return 0; } if (QThread::currentThread() != thread()) { - qint64 result; + float result; QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); + Q_RETURN_ARG(float, result)); return result; } - return _player->length() / MSECS_PER_SECOND; + return (float)_player->length() / (float) MSECS_PER_SECOND; } void AvatarData::loadRecording(const QString& filename) { @@ -1513,7 +1513,8 @@ void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { recordingBasis = std::make_shared(); recordingBasis->setRotation(getOrientation()); recordingBasis->setTranslation(getPosition()); - recordingBasis->setScale(getTargetScale()); + // TODO: find a different way to record/playback the Scale of the avatar + //recordingBasis->setScale(getTargetScale()); } _recordingBasis = recordingBasis; } @@ -1532,7 +1533,7 @@ Transform AvatarData::getTransform() const { static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform"); static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform"); -static const QString JSON_AVATAR_JOINT_ROTATIONS = QStringLiteral("jointRotations"); +static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray"); static const QString JSON_AVATAR_HEAD = QStringLiteral("head"); static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); @@ -1544,6 +1545,24 @@ static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +QJsonValue toJsonValue(const JointData& joint) { + QJsonArray result; + result.push_back(toJsonValue(joint.rotation)); + result.push_back(toJsonValue(joint.translation)); + return result; +} + +JointData jointDataFromJsonValue(const QJsonValue& json) { + JointData result; + if (json.isArray()) { + QJsonArray array = json.toArray(); + result.rotation = quatFromJsonValue(array[0]); + result.rotationSet = true; + result.translation = vec3FromJsonValue(array[1]); + result.translationSet = false; + } + return result; +} // Every frame will store both a basis for the recording and a relative transform // This allows the application to decide whether playback should be relative to an avatar's @@ -1575,13 +1594,16 @@ QByteArray avatarStateToFrame(const AvatarData* _avatar) { root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); } + } else { + root[JSON_AVATAR_RELATIVE] = Transform::toJson(_avatar->getTransform()); } - QJsonArray jointRotations; - for (const auto& jointRotation : _avatar->getJointRotations()) { - jointRotations.push_back(toJsonValue(jointRotation)); + // Skeleton pose + QJsonArray jointArray; + for (const auto& joint : _avatar->getRawJointData()) { + jointArray.push_back(toJsonValue(joint)); } - root[JSON_AVATAR_JOINT_ROTATIONS] = jointRotations; + root[JSON_AVATAR_JOINT_ARRAY] = jointArray; const HeadData* head = _avatar->getHeadData(); if (head) { @@ -1643,24 +1665,34 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) { auto worldTransform = currentBasis->worldTransform(relativeTransform); _avatar->setPosition(worldTransform.getTranslation()); _avatar->setOrientation(worldTransform.getRotation()); - _avatar->setTargetScale(worldTransform.getScale().x); + + // TODO: find a way to record/playback the Scale of the avatar + //_avatar->setTargetScale(worldTransform.getScale().x); } -#if 0 + if (root.contains(JSON_AVATAR_ATTACHEMENTS)) { // FIXME de-serialize attachment data } // Joint rotations are relative to the avatar, so they require no basis correction - if (root.contains(JSON_AVATAR_JOINT_ROTATIONS)) { - QVector jointRotations; - QJsonArray jointRotationsJson = root[JSON_AVATAR_JOINT_ROTATIONS].toArray(); - jointRotations.reserve(jointRotationsJson.size()); - for (const auto& jointRotationJson : jointRotationsJson) { - jointRotations.push_back(quatFromJsonValue(jointRotationJson)); + if (root.contains(JSON_AVATAR_JOINT_ARRAY)) { + QVector jointArray; + QJsonArray jointArrayJson = root[JSON_AVATAR_JOINT_ARRAY].toArray(); + jointArray.reserve(jointArrayJson.size()); + for (const auto& jointJson : jointArrayJson) { + jointArray.push_back(jointDataFromJsonValue(jointJson)); } + + QVector jointRotations; + jointRotations.reserve(jointArray.size()); + for (const auto& joint : jointArray) { + jointRotations.push_back(joint.rotation); + } + _avatar->setJointRotations(jointRotations); } +#if 0 // Most head data is relative to the avatar, and needs no basis correction, // but the lookat vector does need correction HeadData* head = _avatar->_headData; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 7716a16a1c..26bc9d83ff 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -457,6 +457,9 @@ public: bool translationSet = false; }; +QJsonValue toJsonValue(const JointData& joint); +JointData jointDataFromJsonValue(const QJsonValue& q); + class AttachmentData { public: QUrl modelURL; diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 077f28350b..5d82311bcc 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -20,7 +20,6 @@ #include #include -#include "RenderableDebugableEntityItem.h" #include "../render-utils/simple_vert.h" #include "../render-utils/simple_frag.h" @@ -63,6 +62,4 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { } else { DependencyManager::get()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor); } - - RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp deleted file mode 100644 index f103aaed4c..0000000000 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// -// RenderableDebugableEntityItem.cpp -// libraries/entities-renderer/src/ -// -// Created by Seth Alves on 5/1/15. -// Copyright 2014 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 -// - - -#include "RenderableDebugableEntityItem.h" - -#include - -#include -#include -#include - - -void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, - float puffedOut, glm::vec4& color) { - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - - auto shapeTransform = entity->getTransformToCenter(); - if (puffedOut != 0.0f) { - shapeTransform.postScale(1.0f + puffedOut); - } - batch.setModelTransform(Transform()); // we want to include the scale as well - DependencyManager::get()->renderWireCubeInstance(batch, shapeTransform, color); -} - -void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { - if (args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP) { - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - - batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well - - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - bool highlightSimulationOwnership = (entity->getSimulatorID() == myNodeID); - if (highlightSimulationOwnership) { - glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); - renderBoundingBox(entity, args, 0.08f, greenColor); - } - - quint64 now = usecTimestampNow(); - if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { - glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); - renderBoundingBox(entity, args, 0.16f, redColor); - } - - if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) { - glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); - renderBoundingBox(entity, args, 0.24f, yellowColor); - } - - ObjectMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState && motionState->isActive()) { - glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); - renderBoundingBox(entity, args, 0.32f, blueColor); - } - } -} diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h deleted file mode 100644 index 2680d882f5..0000000000 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// RenderableDebugableEntityItem.h -// libraries/entities-renderer/src/ -// -// Created by Seth Alves on 5/1/15. -// Copyright 2014 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 -// - -#ifndef hifi_RenderableDebugableEntityItem_h -#define hifi_RenderableDebugableEntityItem_h - -#include - -class RenderableDebugableEntityItem { -public: - static void renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color); - static void render(EntityItem* entity, RenderArgs* args); -}; - -#endif // hifi_RenderableDebugableEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index bf9710857a..5504268dce 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -10,6 +10,7 @@ // +#include #include "RenderableEntityItem.h" namespace render { @@ -40,5 +41,60 @@ namespace render { } } +void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) { + statusGetters.push_back([entity] () -> render::Item::Status::Value { + quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); + const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); + float normalizedDelta = delta * WAIT_THRESHOLD_INV; + // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD + // Color is red if last update is after WAIT_THRESHOLD, green otherwise (120 deg is green) + return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? + render::Item::Status::Value::GREEN : + render::Item::Status::Value::RED), + (unsigned char) RenderItemStatusIcon::PACKET_RECEIVED); + }); + statusGetters.push_back([entity] () -> render::Item::Status::Value { + quint64 delta = usecTimestampNow() - entity->getLastBroadcast(); + const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); + float normalizedDelta = delta * WAIT_THRESHOLD_INV; + // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD + // Color is Magenta if last update is after WAIT_THRESHOLD, cyan otherwise (180 deg is green) + return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? + render::Item::Status::Value::MAGENTA : + render::Item::Status::Value::CYAN), + (unsigned char)RenderItemStatusIcon::PACKET_SENT); + }); + statusGetters.push_back([entity] () -> render::Item::Status::Value { + ObjectMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState && motionState->isActive()) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, + (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, + (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); + }); + + statusGetters.push_back([entity] () -> render::Item::Status::Value { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID); + + if (weOwnSimulation) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, + (unsigned char)RenderItemStatusIcon::SIMULATION_OWNER); + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, + (unsigned char)RenderItemStatusIcon::SIMULATION_OWNER); + }); + + statusGetters.push_back([entity] () -> render::Item::Status::Value { + if (entity->hasActions()) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); + }); +} diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 1832ef28c3..212b71759f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -15,13 +15,26 @@ #include #include +// These or the icon "name" used by the render item status value, they correspond to the atlas texture used by the DrawItemStatus +// job in the current rendering pipeline defined as of now (11/2015) in render-utils/RenderDeferredTask.cpp. +enum class RenderItemStatusIcon { + ACTIVE_IN_BULLET = 0, + PACKET_SENT = 1, + PACKET_RECEIVED = 2, + SIMULATION_OWNER = 3, + HAS_ACTIONS = 4, + NONE = 255 +}; + +void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters); + class RenderableEntityItemProxy { public: RenderableEntityItemProxy(EntityItemPointer entity) : entity(entity) { } typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - + EntityItemPointer entity; }; @@ -36,19 +49,23 @@ class SimpleRenderableEntityItem { public: bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { _myItem = scene->allocateID(); - + auto renderData = std::make_shared(self); auto renderPayload = std::make_shared(renderData); - + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(self, statusGetters); + renderPayload->addStatusGetters(statusGetters); + pendingChanges.resetItem(_myItem, renderPayload); - + return true; } void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_myItem); } - + private: render::ItemID _myItem; }; @@ -62,5 +79,4 @@ private: \ SimpleRenderableEntityItem _renderHelper; - #endif // hifi_RenderableEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 3735690c33..f39c31e22b 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -54,6 +54,4 @@ void RenderableLineEntityItem::render(RenderArgs* args) { DependencyManager::get()->bindSimpleProgram(batch); DependencyManager::get()->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID); } - - RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.h b/libraries/entities-renderer/src/RenderableLineEntityItem.h index 09f98ca364..ba990046a0 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.h @@ -13,7 +13,6 @@ #define hifi_RenderableLineEntityItem_h #include -#include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" #include diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a7c2ff09f4..fa98c38482 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -21,7 +21,9 @@ #include "EntityTreeRenderer.h" #include "EntitiesRendererLogging.h" +#include "RenderableEntityItem.h" #include "RenderableModelEntityItem.h" +#include "RenderableEntityItem.h" EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); @@ -179,25 +181,6 @@ namespace render { } } -void makeEntityItemStatusGetters(RenderableModelEntityItem* entity, render::Item::Status::Getters& statusGetters) { - statusGetters.push_back([entity] () -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); - const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); - float normalizedDelta = delta * WAIT_THRESHOLD_INV; - // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD - // Color is red if last update is after WAIT_THRESHOLD, green otherwise (120 deg is green) - return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? render::Item::Status::Value::GREEN : render::Item::Status::Value::RED)); - }); - statusGetters.push_back([entity] () -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastBroadcast(); - const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); - float normalizedDelta = delta * WAIT_THRESHOLD_INV; - // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD - // Color is Magenta if last update is after WAIT_THRESHOLD, cyan otherwise (180 deg is green) - return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? render::Item::Status::Value::MAGENTA : render::Item::Status::Value::CYAN)); - }); -} - bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { _myMetaItem = scene->allocateID(); @@ -209,11 +192,11 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p if (_model) { render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(this, statusGetters); + makeEntityItemStatusGetters(shared_from_this(), statusGetters); // note: we don't care if the model fails to add items, we always added our meta item and therefore we return // true so that the system knows our meta item is in the scene! - _model->addToScene(scene, pendingChanges, statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); } return true; @@ -245,14 +228,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene - if (_model->needsFixupInScene()) { + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { + _showCollisionHull = shouldShowCollisionHull; render::PendingChanges pendingChanges; _model->removeFromScene(scene, pendingChanges); render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(this, statusGetters); - _model->addToScene(scene, pendingChanges, statusGetters); + makeEntityItemStatusGetters(shared_from_this(), statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); scene->enqueuePendingChanges(pendingChanges); } @@ -274,7 +259,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { EntityTreeRenderer* renderer = static_cast(args->_renderer); getModel(renderer); } - + if (_model) { // handle animations.. if (hasAnimation()) { @@ -320,11 +305,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } } } else { - glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); - RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); + static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + gpu::Batch& batch = *args->_batch; + auto shapeTransform = getTransformToCenter(); + batch.setModelTransform(Transform()); // we want to include the scale as well + DependencyManager::get()->renderWireCubeInstance(batch, shapeTransform, greenColor); } - - RenderableDebugableEntityItem::render(this, args); } Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 64629ad291..1ce07da599 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -16,7 +16,6 @@ #include #include -#include "RenderableDebugableEntityItem.h" class Model; class EntityTreeRenderer; @@ -85,6 +84,8 @@ private: bool _dimensionsInitialized = true; render::ItemID _myMetaItem; + + bool _showCollisionHull = false; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 41cf3b9bbf..05fca343fd 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -139,6 +139,9 @@ bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, _renderItemId = scene->allocateID(); auto renderData = ParticlePayload::Pointer(particlePayload); auto renderPayload = render::PayloadPointer(new ParticlePayload::Payload(renderData)); + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(shared_from_this(), statusGetters); + renderPayload->addStatusGetters(statusGetters); pendingChanges.resetItem(_renderItemId, renderPayload); _scene = scene; return true; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 7bec8f2b03..32418199b8 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -153,6 +153,4 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) { batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); - - RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 2832053639..c8a47cce0c 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -14,7 +14,6 @@ #include #include -#include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" #include #include diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index fd5a9a6b4a..2e8b8920ce 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -538,8 +538,6 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); - - RenderableDebugableEntityItem::render(this, args); } bool RenderablePolyVoxEntityItem::addToScene(EntityItemPointer self, @@ -551,6 +549,10 @@ bool RenderablePolyVoxEntityItem::addToScene(EntityItemPointer self, auto renderData = PolyVoxPayload::Pointer(renderItem); auto renderPayload = std::make_shared(renderData); + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(shared_from_this(), statusGetters); + renderPayload->addStatusGetters(statusGetters); + pendingChanges.resetItem(_myItem, renderPayload); return true; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index ef44ba5ab0..9d0931a47e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -21,7 +21,6 @@ #include #include "PolyVoxEntityItem.h" -#include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" #include "gpu/Context.h" diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 246cd2fea7..0400ecb999 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -20,7 +20,6 @@ #include #include -#include "RenderableDebugableEntityItem.h" #include "../render-utils/simple_vert.h" #include "../render-utils/simple_frag.h" @@ -70,7 +69,4 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { batch.setModelTransform(Transform()); DependencyManager::get()->renderSolidSphereInstance(batch, modelTransform, sphereColor); } - - - RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index ff56bef46b..b7b91c9b3a 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -19,6 +19,8 @@ #include #include +#include "RenderableEntityItem.h" + // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; @@ -112,7 +114,9 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); render::PendingChanges pendingChanges; _model->removeFromScene(scene, pendingChanges); - _model->addToScene(scene, pendingChanges); + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(shared_from_this(), statusGetters); + _model->addToScene(scene, pendingChanges, false); scene->enqueuePendingChanges(pendingChanges); @@ -203,7 +207,11 @@ bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, std::shared_pt auto renderData = std::make_shared(self); auto renderPayload = std::make_shared(renderData); - + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(shared_from_this(), statusGetters); + renderPayload->addStatusGetters(statusGetters); + pendingChanges.resetItem(_myMetaItem, renderPayload); return true; } diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index 549aacbd0a..ce9a93a6ac 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -304,3 +304,24 @@ QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType) entityActionType = (EntityActionType)actionTypeAsInt; return stream; } + +QString serializedActionsToDebugString(QByteArray data) { + if (data.size() == 0) { + return QString(); + } + QVector serializedActions; + QDataStream serializedActionsStream(data); + serializedActionsStream >> serializedActions; + + QString result; + foreach(QByteArray serializedAction, serializedActions) { + QDataStream serializedActionStream(serializedAction); + EntityActionType actionType; + QUuid actionID; + serializedActionStream >> actionType; + serializedActionStream >> actionID; + result += EntityActionInterface::actionTypeToString(actionType) + "-" + actionID.toString() + " "; + } + + return result; +} diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index b257df3325..01292e3840 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -89,4 +89,6 @@ typedef std::shared_ptr EntityActionPointer; QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType); QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType); +QString serializedActionsToDebugString(QByteArray data); + #endif // hifi_EntityActionInterface_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index cce3045049..f032dcd347 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -630,6 +630,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef dataAt += bytes; bytesRead += bytes; + if (wantTerseEditLogging() && _simulationOwner != newSimOwner) { + qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << newSimOwner; + } if (_simulationOwner.set(newSimOwner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; } @@ -704,17 +707,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - - { // When we own the simulation we don't accept updates to the entity's actions - // but since we're using macros below we have to temporarily modify overwriteLocalData. - // NOTE: this prevents userB from adding an action to an object1 when UserA - // has simulation ownership of it. - // TODO: figure out how to allow multiple users to update actions simultaneously - bool oldOverwrite = overwriteLocalData; - overwriteLocalData = overwriteLocalData && !weOwnSimulation; - READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData); - overwriteLocalData = oldOverwrite; - } + READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -737,7 +730,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and // use our simulation helper routine to get a best estimate of where the entity should be. float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND); - + // we want to extrapolate the motion forward to compensate for packet travel time, but // we don't want the side effect of flag setting. simulateKinematicMotion(skipTimeForward, false); @@ -745,7 +738,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (overwriteLocalData) { if (!_simulationOwner.matchesValidID(myNodeID)) { - _lastSimulated = now; } } @@ -996,6 +988,11 @@ EntityTreePointer EntityItem::getTree() const { return tree; } +bool EntityItem::wantTerseEditLogging() { + EntityTreePointer tree = getTree(); + return tree ? tree->wantTerseEditLogging() : false; +} + glm::mat4 EntityItem::getEntityToWorldMatrix() const { glm::mat4 translation = glm::translate(getPosition()); glm::mat4 rotation = glm::mat4_cast(getRotation()); @@ -1492,20 +1489,35 @@ void EntityItem::updateCreated(uint64_t value) { } void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { + if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) { + qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; + } _simulationOwner.set(id, priority); } void EntityItem::setSimulationOwner(const SimulationOwner& owner) { + if (wantTerseEditLogging() && _simulationOwner != owner) { + qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << owner; + } + _simulationOwner.set(owner); } void EntityItem::updateSimulatorID(const QUuid& value) { + if (wantTerseEditLogging() && _simulationOwner.getID() != value) { + qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << value; + } + if (_simulationOwner.setID(value)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; } } void EntityItem::clearSimulationOwnership() { + if (wantTerseEditLogging() && !_simulationOwner.isNull()) { + qCDebug(entities) << "sim ownership for" << getDebugName() << "is now null"; + } + _simulationOwner.clear(); // don't bother setting the DIRTY_SIMULATOR_ID flag because clearSimulationOwnership() // is only ever called entity-server-side and the flags are only used client-side @@ -1607,6 +1619,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s bool success = true; serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; + setActionDataNeedsTransmit(true); return success; } return false; @@ -1649,7 +1662,7 @@ void EntityItem::deserializeActionsInternal() { return; } - EntityTreePointer entityTree = _element ? _element->getTree() : nullptr; + EntityTreePointer entityTree = getTree(); assert(entityTree); EntitySimulation* simulation = entityTree ? entityTree->getSimulation() : nullptr; assert(simulation); @@ -1770,6 +1783,7 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const { serializedActionsStream << serializedActions; if (result.size() >= _maxActionsDataSize) { + qDebug() << "EntityItem::serializeActions size is too large -- " << result.size() << ">=" << _maxActionsDataSize; success = false; return; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 858dc7e326..5b47198e97 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -307,6 +307,7 @@ public: QString getName() const { return _name; } void setName(const QString& value) { _name = value; } + QString getDebugName() { return _name != "" ? _name : getID().toString(); } bool getVisible() const { return _visible; } void setVisible(bool value) { _visible = value; } @@ -381,6 +382,7 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; + bool wantTerseEditLogging(); static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; } @@ -406,7 +408,13 @@ public: QList getActionIDs() { return _objectActions.keys(); } QVariantMap getActionArguments(const QUuid& actionID) const; void deserializeActions(); + void setActionDataDirty(bool value) const { _actionDataDirty = value; } + bool actionDataDirty() const { return _actionDataDirty; } + + void setActionDataNeedsTransmit(bool value) const { _actionDataNeedsTransmit = value; } + bool actionDataNeedsTransmit() const { return _actionDataNeedsTransmit; } + bool shouldSuppressLocationEdits() const; void setSourceUUID(const QUuid& sourceUUID) { _sourceUUID = sourceUUID; } @@ -439,7 +447,7 @@ protected: mutable bool _recalcAABox = true; mutable bool _recalcMinAACube = true; mutable bool _recalcMaxAACube = true; - + float _glowLevel; float _localRenderAlpha; float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3 @@ -510,6 +518,7 @@ protected: void checkWaitingToRemove(EntitySimulation* simulation = nullptr); mutable QSet _actionsToRemove; mutable bool _actionDataDirty = false; + mutable bool _actionDataNeedsTransmit = false; // _previouslyDeletedActions is used to avoid an action being re-added due to server round-trip lag static quint64 _rememberDeletedActionTime; mutable QHash _previouslyDeletedActions; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 06e9b66dd2..2ff9d9391b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -611,7 +611,8 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, const QVariantMap& arguments) { QUuid actionID = QUuid::createUuid(); auto actionFactory = DependencyManager::get(); - bool success = actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + bool success = false; + actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { // create this action even if the entity doesn't have physics info. it will often be the // case that a script adds an action immediately after an object is created, and the physicsInfo // is computed asynchronously. @@ -623,16 +624,16 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, return false; } EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments); - if (action) { - entity->addAction(simulation, action); - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - if (entity->getSimulatorID() != myNodeID) { - entity->flagForOwnership(); - } - return true; + if (!action) { + return false; } - return false; + success = entity->addAction(simulation, action); + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getSimulatorID() != myNodeID) { + entity->flagForOwnership(); + } + return false; // Physics will cause a packet to be sent, so don't send from here. }); if (success) { return actionID; @@ -656,9 +657,12 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& } bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) { - return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { - return entity->removeAction(simulation, actionID); + bool success = false; + actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + success = entity->removeAction(simulation, actionID); + return false; // Physics will cause a packet to be sent, so don't send from here. }); + return success; } QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index c2f635c95f..027972549a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -198,6 +198,10 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI properties.setVelocityChanged(false); properties.setAngularVelocityChanged(false); properties.setAccelerationChanged(false); + + if (wantTerseEditLogging()) { + qCDebug(entities) << senderNode->getUUID() << "physical edits suppressed"; + } } } // else client accepts what the server says @@ -612,6 +616,14 @@ EntityItemPointer EntityTree::findEntityByEntityItemID(const EntityItemID& entit } void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties) { + static quint64 lastTerseLog = 0; + quint64 now = usecTimestampNow(); + + if (now - lastTerseLog > USECS_PER_SECOND) { + qCDebug(entities) << "-------------------------"; + } + lastTerseLog = now; + if (properties.simulationOwnerChanged()) { int simIndex = changedProperties.indexOf("simulationOwner"); if (simIndex >= 0) { @@ -619,6 +631,79 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + glm::vec3 value = properties.getVelocity(); + QString changeHint = "0"; + if (value.x + value.y + value.z > 0) { + changeHint = "+"; + } else if (value.x + value.y + value.z < 0) { + changeHint = "-"; + } + changedProperties[index] = QString("velocity:") + changeHint; + } + } + + if (properties.gravityChanged()) { + int index = changedProperties.indexOf("gravity"); + if (index >= 0) { + glm::vec3 value = properties.getGravity(); + QString changeHint = "0"; + if (value.x + value.y + value.z > 0) { + changeHint = "+"; + } else if (value.x + value.y + value.z < 0) { + changeHint = "-"; + } + changedProperties[index] = QString("gravity:") + changeHint; + } + } + + if (properties.actionDataChanged()) { + int index = changedProperties.indexOf("actionData"); + if (index >= 0) { + QByteArray value = properties.getActionData(); + QString changeHint = serializedActionsToDebugString(value); + changedProperties[index] = QString("actionData:") + changeHint; + } + } + + if (properties.ignoreForCollisionsChanged()) { + int index = changedProperties.indexOf("ignoreForCollisions"); + if (index >= 0) { + bool value = properties.getIgnoreForCollisions(); + QString changeHint = "0"; + if (value) { + changeHint = "1"; + } + changedProperties[index] = QString("ignoreForCollisions:") + changeHint; + } + } + + if (properties.collisionsWillMoveChanged()) { + int index = changedProperties.indexOf("collisionsWillMove"); + if (index >= 0) { + bool value = properties.getCollisionsWillMove(); + QString changeHint = "0"; + if (value) { + changeHint = "1"; + } + changedProperties[index] = QString("collisionsWillMove:") + changeHint; + } + } + + if (properties.lockedChanged()) { + int index = changedProperties.indexOf("locked"); + if (index >= 0) { + bool value = properties.getLocked(); + QString changeHint = "0"; + if (value) { + changeHint = "1"; + } + changedProperties[index] = QString("locked:") + changeHint; + } + } } int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, @@ -673,7 +758,8 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi if (wantTerseEditLogging()) { QList changedProperties = properties.listChangedProperties(); fixupTerseEditLogging(properties, changedProperties); - qCDebug(entities) << "edit" << entityItemID.toString() << changedProperties; + qCDebug(entities) << senderNode->getUUID() << "edit" << + existingEntity->getDebugName() << changedProperties; } endLogging = usecTimestampNow(); @@ -703,7 +789,7 @@ int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* edi if (wantTerseEditLogging()) { QList changedProperties = properties.listChangedProperties(); fixupTerseEditLogging(properties, changedProperties); - qCDebug(entities) << "add" << entityItemID.toString() << changedProperties; + qCDebug(entities) << senderNode->getUUID() << "add" << entityItemID << changedProperties; } endLogging = usecTimestampNow(); diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index d6957873e2..24f6784954 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -157,7 +157,7 @@ void SimulationOwner::test() { } bool SimulationOwner::operator!=(const SimulationOwner& other) { - return (_id != other._id && _priority != other._priority); + return (_id != other._id || _priority != other._priority); } SimulationOwner& SimulationOwner::operator=(const SimulationOwner& other) { diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index f330b0fd07..8d53d6e2e7 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -331,7 +331,7 @@ public: template Iterator begin() { return Iterator(&edit(0), _stride); } template Iterator end() { return Iterator(&edit(getNum()), _stride); } - template Iterator cbegin() const { return Iterator(&get(0), _stride); } + template Iterator cbegin() const { return Iterator(&get(), _stride); } template Iterator cend() const { return Iterator(&get(getNum()), _stride); } // the number of elements of the specified type fitting in the view size diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index df024b361d..f7d26f25c5 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -46,7 +46,13 @@ DomainHandler::DomainHandler(QObject* parent) : connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); } -void DomainHandler::clearConnectionInfo() { +void DomainHandler::disconnect() { + // if we're currently connected to a domain, send a disconnect packet on our way out + if (_isConnected) { + sendDisconnectPacket(); + } + + // clear member variables that hold the connection state to a domain _uuid = QUuid(); _connectionToken = QUuid(); @@ -60,6 +66,18 @@ void DomainHandler::clearConnectionInfo() { setIsConnected(false); } +void DomainHandler::sendDisconnectPacket() { + // The DomainDisconnect packet is not verified - we're relying on the eventual addition of DTLS to the + // domain-server connection to stop greifing here + + // construct the disconnect packet once (an empty packet but sourced with our current session UUID) + static auto disconnectPacket = NLPacket::create(PacketType::DomainDisconnectRequest, 0); + + // send the disconnect packet to the current domain server + auto nodeList = DependencyManager::get(); + nodeList->sendUnreliablePacket(*disconnectPacket, _sockAddr); +} + void DomainHandler::clearSettings() { _settingsObject = QJsonObject(); _failedSettingsRequests = 0; @@ -67,7 +85,7 @@ void DomainHandler::clearSettings() { void DomainHandler::softReset() { qCDebug(networking) << "Resetting current domain connection information."; - clearConnectionInfo(); + disconnect(); clearSettings(); } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 9dd4254c30..49bab6dc28 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -35,7 +35,7 @@ class DomainHandler : public QObject { public: DomainHandler(QObject* parent = 0); - void clearConnectionInfo(); + void disconnect(); void clearSettings(); const QUuid& getUUID() const { return _uuid; } @@ -113,6 +113,7 @@ signals: void settingsReceiveFail(); private: + void sendDisconnectPacket(); void hardReset(); QUuid _uuid; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 75d42f55cb..fdb5049f00 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -441,7 +441,7 @@ void LimitedNodeList::reset() { _nodeSocket.clearConnections(); } -void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { +bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { QReadLocker readLocker(&_nodeMutex); NodeHash::iterator it = _nodeHash.find(nodeUUID); @@ -456,7 +456,10 @@ void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { } handleNodeKill(matchingNode); + return true; } + + return false; } void LimitedNodeList::processKillNode(NLPacket& packet) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 2488b0cf8c..1aacd27572 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -230,7 +230,7 @@ public slots: virtual void sendSTUNRequest(); void sendPingPackets(); - void killNodeWithUUID(const QUuid& nodeUUID); + bool killNodeWithUUID(const QUuid& nodeUUID); signals: void dataSent(quint8 channelType, int bytes); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b262904c63..e03ac47854 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -103,6 +103,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::DomainServerPathResponse, this, "processDomainServerPathResponse"); + packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode"); } qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination) { @@ -218,6 +219,10 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) } void NodeList::sendDomainServerCheckIn() { + if (_isShuttingDown) { + qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; + } + if (_publicSockAddr.isNull()) { // we don't know our public socket and we need to send it to the domain server qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; @@ -513,6 +518,13 @@ void NodeList::processDomainServerAddedNode(QSharedPointer packet) { parseNodeFromPacketStream(packetStream); } +void NodeList::processDomainServerRemovedNode(QSharedPointer packet) { + // read the UUID from the packet, remove it if it exists + QUuid nodeUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + qDebug() << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID); + killNodeWithUUID(nodeUUID); +} + void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { // setup variables to read into from QDataStream qint8 nodeType; diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 3aae3e3dfc..5b9a4e5ae5 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -66,6 +66,8 @@ public: void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); + + void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } public slots: void reset(); @@ -74,6 +76,7 @@ public slots: void processDomainServerList(QSharedPointer packet); void processDomainServerAddedNode(QSharedPointer packet); + void processDomainServerRemovedNode(QSharedPointer packet); void processDomainServerPathResponse(QSharedPointer packet); void processDomainServerConnectionTokenPacket(QSharedPointer packet); @@ -114,6 +117,7 @@ private: DomainHandler _domainHandler; int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; + bool _isShuttingDown { false }; }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 0422c03297..6855c2eec3 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -33,14 +33,19 @@ void ThreadedAssignment::setFinished(bool isFinished) { if (_isFinished) { qDebug() << "ThreadedAssignment::setFinished(true) called - finishing up."; - - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + + auto nodeList = DependencyManager::get(); + + auto& packetReceiver = nodeList->getPacketReceiver(); // we should de-register immediately for any of our packets packetReceiver.unregisterListener(this); // we should also tell the packet receiver to drop packets while we're cleaning up packetReceiver.setShouldDropPackets(true); + + // send a disconnect packet to the domain + nodeList->getDomainHandler().disconnect(); if (_domainServerTimer) { // stop the domain-server check in timer by calling deleteLater so it gets cleaned up on NL thread diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 5f57d5e5a5..a7878667bc 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -14,11 +14,13 @@ #include #include +#include const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack - << PacketType::DomainListRequest << PacketType::StopNode; + << PacketType::DomainListRequest << PacketType::StopNode + << PacketType::DomainDisconnectRequest; const QSet NON_SOURCED_PACKETS = QSet() << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment @@ -29,7 +31,8 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::DomainSettingsRequest << PacketType::DomainSettings << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat << PacketType::ICEPing << PacketType::ICEPingReply - << PacketType::AssignmentClientStatus << PacketType::StopNode; + << PacketType::AssignmentClientStatus << PacketType::StopNode + << PacketType::DomainServerRemovedNode; const QSet RELIABLE_PACKETS = QSet(); @@ -46,66 +49,17 @@ PacketVersion versionForPacketType(PacketType packetType) { } } -#define PACKET_TYPE_NAME_LOOKUP(x) case x: return QString(#x); - -QString nameForPacketType(PacketType packetType) { - switch (packetType) { - PACKET_TYPE_NAME_LOOKUP(PacketType::Unknown); - PACKET_TYPE_NAME_LOOKUP(PacketType::StunResponse); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainList); - PACKET_TYPE_NAME_LOOKUP(PacketType::Ping); - PACKET_TYPE_NAME_LOOKUP(PacketType::PingReply); - PACKET_TYPE_NAME_LOOKUP(PacketType::KillAvatar); - PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarData); - PACKET_TYPE_NAME_LOOKUP(PacketType::InjectAudio); - PACKET_TYPE_NAME_LOOKUP(PacketType::MixedAudio); - PACKET_TYPE_NAME_LOOKUP(PacketType::MicrophoneAudioNoEcho); - PACKET_TYPE_NAME_LOOKUP(PacketType::MicrophoneAudioWithEcho); - PACKET_TYPE_NAME_LOOKUP(PacketType::BulkAvatarData); - PACKET_TYPE_NAME_LOOKUP(PacketType::SilentAudioFrame); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainListRequest); - PACKET_TYPE_NAME_LOOKUP(PacketType::RequestAssignment); - PACKET_TYPE_NAME_LOOKUP(PacketType::CreateAssignment); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainConnectionDenied); - PACKET_TYPE_NAME_LOOKUP(PacketType::MuteEnvironment); - PACKET_TYPE_NAME_LOOKUP(PacketType::AudioStreamStats); - PACKET_TYPE_NAME_LOOKUP(PacketType::OctreeStats); - PACKET_TYPE_NAME_LOOKUP(PacketType::Jurisdiction); - PACKET_TYPE_NAME_LOOKUP(PacketType::JurisdictionRequest); - PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarIdentity); - PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarBillboard); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainConnectRequest); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerRequireDTLS); - PACKET_TYPE_NAME_LOOKUP(PacketType::NodeJsonStats); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityQuery); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityData); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityErase); - PACKET_TYPE_NAME_LOOKUP(PacketType::OctreeDataNack); - PACKET_TYPE_NAME_LOOKUP(PacketType::StopNode); - PACKET_TYPE_NAME_LOOKUP(PacketType::AudioEnvironment); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityEditNack); - PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerHeartbeat); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerAddedNode); - PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerQuery); - PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerPeerInformation); - PACKET_TYPE_NAME_LOOKUP(PacketType::ICEPing); - PACKET_TYPE_NAME_LOOKUP(PacketType::ICEPingReply); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityAdd); - PACKET_TYPE_NAME_LOOKUP(PacketType::EntityEdit); - PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerConnectionToken); - default: - return QString("Type: ") + QString::number((int)packetType); - } - return QString("unexpected"); -} - uint qHash(const PacketType& key, uint seed) { - // seems odd that Qt couldn't figure out this cast itself, but this fixes a compile error after switch to - // strongly typed enum for PacketType + // seems odd that Qt couldn't figure out this cast itself, but this fixes a compile error after switch + // to strongly typed enum for PacketType return qHash((quint8) key, seed); } QDebug operator<<(QDebug debug, const PacketType& type) { - debug.nospace() << (uint8_t) type << " (" << qPrintable(nameForPacketType(type)) << ")"; + QMetaObject metaObject = PacketTypeEnum::staticMetaObject; + QMetaEnum metaEnum = metaObject.enumerator(metaObject.enumeratorOffset()); + QString typeName = metaEnum.valueToKey((int) type); + + debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index efcf965bb7..6ad52eafa7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -18,70 +18,81 @@ #include #include +#include #include #include -// If adding a new packet packetType, you can replace one marked usable or add at the end. -// If you want the name of the packet packetType to be available for debugging or logging, update nameForPacketType() as well -// This enum must hold 256 or fewer packet types (so the value is <= 255) since it is statically typed as a uint8_t -enum class PacketType : uint8_t { - Unknown, - StunResponse, - DomainList, - Ping, - PingReply, - KillAvatar, - AvatarData, - InjectAudio, - MixedAudio, - MicrophoneAudioNoEcho, - MicrophoneAudioWithEcho, - BulkAvatarData, - SilentAudioFrame, - DomainListRequest, - RequestAssignment, - CreateAssignment, - DomainConnectionDenied, - MuteEnvironment, - AudioStreamStats, - DomainServerPathQuery, - DomainServerPathResponse, - DomainServerAddedNode, - ICEServerPeerInformation, - ICEServerQuery, - OctreeStats, - Jurisdiction, - JurisdictionRequest, - AssignmentClientStatus, - NoisyMute, - AvatarIdentity, - AvatarBillboard, - DomainConnectRequest, - DomainServerRequireDTLS, - NodeJsonStats, - OctreeDataNack, - StopNode, - AudioEnvironment, - EntityEditNack, - ICEServerHeartbeat, - ICEPing, - ICEPingReply, - EntityData, - EntityQuery, - EntityAdd, - EntityErase, - EntityEdit, - DomainServerConnectionToken, - DomainSettingsRequest, - DomainSettings, - AssetGet, - AssetGetReply, - AssetUpload, - AssetUploadReply, - AssetGetInfo, - AssetGetInfoReply +// The enums are inside this PacketTypeEnum for run-time conversion of enum value to string via +// Q_ENUMS, without requiring a macro that is called for each enum value. +class PacketTypeEnum { + Q_GADGET + Q_ENUMS(Value) +public: + // If adding a new packet packetType, you can replace one marked usable or add at the end. + // This enum must hold 256 or fewer packet types (so the value is <= 255) since it is statically typed as a uint8_t + enum class Value : uint8_t { + Unknown, + StunResponse, + DomainList, + Ping, + PingReply, + KillAvatar, + AvatarData, + InjectAudio, + MixedAudio, + MicrophoneAudioNoEcho, + MicrophoneAudioWithEcho, + BulkAvatarData, + SilentAudioFrame, + DomainListRequest, + RequestAssignment, + CreateAssignment, + DomainConnectionDenied, + MuteEnvironment, + AudioStreamStats, + DomainServerPathQuery, + DomainServerPathResponse, + DomainServerAddedNode, + ICEServerPeerInformation, + ICEServerQuery, + OctreeStats, + Jurisdiction, + JurisdictionRequest, + AssignmentClientStatus, + NoisyMute, + AvatarIdentity, + AvatarBillboard, + DomainConnectRequest, + DomainServerRequireDTLS, + NodeJsonStats, + OctreeDataNack, + StopNode, + AudioEnvironment, + EntityEditNack, + ICEServerHeartbeat, + ICEPing, + ICEPingReply, + EntityData, + EntityQuery, + EntityAdd, + EntityErase, + EntityEdit, + DomainServerConnectionToken, + DomainSettingsRequest, + DomainSettings, + AssetGet, + AssetGetReply, + AssetUpload, + AssetUploadReply, + AssetGetInfo, + AssetGetInfoReply, + DomainDisconnectRequest, + DomainServerRemovedNode + }; }; +using PacketType = PacketTypeEnum::Value; + const int NUM_BYTES_MD5_HASH = 16; typedef char PacketVersion; @@ -90,7 +101,6 @@ extern const QSet NON_VERIFIED_PACKETS; extern const QSet NON_SOURCED_PACKETS; extern const QSet RELIABLE_PACKETS; -QString nameForPacketType(PacketType packetType); PacketVersion versionForPacketType(PacketType packetType); uint qHash(const PacketType& key, uint seed); diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index cceb3ba706..fe92fe7745 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1890,8 +1890,8 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr versionForPacketType(expectedDataPacketType()), gotVersion); } } else { - qCDebug(octree) << "SVO file type mismatch. Expected: " << nameForPacketType(expectedType) - << " Got: " << nameForPacketType(gotType); + qCDebug(octree) << "SVO file type mismatch. Expected: " << expectedType + << " Got: " << gotType; } } else { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index c8a0f87b6d..181ae7060e 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -112,8 +112,8 @@ bool EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) _outgoingPriority = NO_PRORITY; } else { _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; - if (engine->getSessionID() == _entity->getSimulatorID() || _entity->getSimulationPriority() > _outgoingPriority) { - // we own the simulation or our priority looses to remote + if (engine->getSessionID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) { + // we own the simulation or our priority looses to (or ties with) remote _outgoingPriority = NO_PRORITY; } } @@ -244,7 +244,7 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { return false; } assert(entityTreeIsLocked()); - return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID(); + return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -292,7 +292,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverPosition += dt * _serverVelocity; } - if (_serverActionData != _entity->getActionData()) { + if (_entity->actionDataNeedsTransmit()) { setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY); return true; } @@ -370,11 +370,15 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s assert(_body); assert(entityTreeIsLocked()); + if (_entity->actionDataNeedsTransmit()) { + return true; + } + if (_entity->getSimulatorID() != sessionID) { // we don't own the simulation, but maybe we should... if (_outgoingPriority != NO_PRORITY) { if (_outgoingPriority < _entity->getSimulationPriority()) { - // our priority looses to remote, so we don't bother to bid + // our priority loses to remote, so we don't bother to bid _outgoingPriority = NO_PRORITY; return false; } @@ -456,7 +460,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - properties.setActionData(_serverActionData); + if (_entity->actionDataNeedsTransmit()) { + _entity->setActionDataNeedsTransmit(false); + properties.setActionData(_serverActionData); + } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); @@ -526,7 +533,7 @@ void EntityMotionState::clearIncomingDirtyFlags() { } } -// virtual +// virtual quint8 EntityMotionState::getSimulationPriority() const { if (_entity) { return _entity->getSimulationPriority(); diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index 2cfd98497b..5c2999a0a1 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -130,6 +130,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { auto ownerEntity = _ownerEntity.lock(); if (ownerEntity) { ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); } }); activateBody(); diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index c1cd2db5ca..d9d6a323a7 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -163,6 +163,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { auto ownerEntity = _ownerEntity.lock(); if (ownerEntity) { ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); } }); activateBody(); diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp index 4349a39732..10209c26d7 100644 --- a/libraries/recording/src/recording/Deck.cpp +++ b/libraries/recording/src/recording/Deck.cpp @@ -25,6 +25,8 @@ void Deck::queueClip(ClipPointer clip, Time timeOffset) { // FIXME if the time offset is not zero, wrap the clip in a OffsetClip wrapper _clips.push_back(clip); + + _length = std::max(_length, clip->duration()); } void Deck::play() { diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h index 85f5246a4e..f0f53ce144 100644 --- a/libraries/recording/src/recording/Frame.h +++ b/libraries/recording/src/recording/Frame.h @@ -27,7 +27,7 @@ public: static const FrameType TYPE_INVALID = 0xFFFF; static const FrameType TYPE_HEADER = 0x0; FrameType type { TYPE_INVALID }; - Time timeOffset { 0 }; + Time timeOffset { 0 }; // milliseconds QByteArray data; Frame() {} diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index fa1d556cee..5b81c68e99 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -501,8 +501,10 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr scen } -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges) { - if (!_meshGroupsKnown && isLoaded()) { +bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, bool showCollisionHull) { + + if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { + _showCollisionHull = showCollisionHull; segregateMeshGroups(); } @@ -525,8 +527,12 @@ bool Model::addToScene(std::shared_ptr scene, render::PendingChan return somethingAdded; } -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters) { - if (!_meshGroupsKnown && isLoaded()) { +bool Model::addToScene(std::shared_ptr scene, + render::PendingChanges& pendingChanges, + render::Item::Status::Getters& statusGetters, + bool showCollisionHull) { + if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { + _showCollisionHull = showCollisionHull; segregateMeshGroups(); } @@ -1139,8 +1145,14 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } void Model::segregateMeshGroups() { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const std::vector>& networkMeshes = _geometry->getMeshes(); + QSharedPointer networkGeometry; + if (_showCollisionHull && _collisionGeometry && _collisionGeometry->isLoaded()) { + networkGeometry = _collisionGeometry; + } else { + networkGeometry = _geometry; + } + const FBXGeometry& geometry = networkGeometry->getFBXGeometry(); + const std::vector>& networkMeshes = networkGeometry->getMeshes(); _rig->makeAnimSkeleton(geometry); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index f5d5f40363..b154ae2b52 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -76,10 +76,13 @@ public: return !_needsReload && isRenderable() && isActive() && isLoaded(); } bool initWhenReady(render::ScenePointer scene); - bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, - render::Item::Status::Getters& statusGetters); + bool showCollisionHull = false); + bool addToScene(std::shared_ptr scene, + render::PendingChanges& pendingChanges, + render::Item::Status::Getters& statusGetters, + bool showCollisionHull = false); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); } @@ -368,6 +371,7 @@ private: bool _readyWhenAdded = false; bool _needsReload = true; bool _needsUpdateClusterMatrices = true; + bool _showCollisionHull = false; friend class MeshPartPayload; protected: diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 845c96372c..e65018ad3d 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -12,6 +12,7 @@ #include "RenderDeferredTask.h" #include +#include #include #include #include @@ -111,8 +112,11 @@ RenderDeferredTask::RenderDeferredTask() : Task() { _jobs.push_back(Job(new DepthSortItems::JobModel("DepthSortTransparent", _jobs.back().getOutput(), DepthSortItems(false)))); _jobs.push_back(Job(new DrawTransparentDeferred::JobModel("TransparentDeferred", _jobs.back().getOutput()))); - _jobs.push_back(Job(new render::DrawStatus::JobModel("DrawStatus", renderedOpaques))); + // Grab a texture map representing the different status icons and assign that to the drawStatsuJob + auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; + auto statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath); + _jobs.push_back(Job(new render::DrawStatus::JobModel("DrawStatus", renderedOpaques, DrawStatus(statusIconMap)))); _jobs.back().setEnabled(false); _drawStatusJobIndex = _jobs.size() - 1; @@ -387,4 +391,4 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const }); args->_batch = nullptr; -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 04483fc037..6daa90b1ed 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -97,7 +97,12 @@ public: int _drawStatusJobIndex = -1; int _drawHitEffectJobIndex = -1; - void setDrawItemStatus(bool draw) { if (_drawStatusJobIndex >= 0) { _jobs[_drawStatusJobIndex].setEnabled(draw); } } + void setDrawItemStatus(int draw) { + if (_drawStatusJobIndex >= 0) { + _jobs[_drawStatusJobIndex].setEnabled(draw > 0); + } + } + bool doDrawItemStatus() const { if (_drawStatusJobIndex >= 0) { return _jobs[_drawStatusJobIndex].isEnabled(); } else { return false; } } void setDrawHitEffect(bool draw) { if (_drawHitEffectJobIndex >= 0) { _jobs[_drawHitEffectJobIndex].setEnabled(draw); } } diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 57e21a1511..5e8fd74e5f 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -29,7 +29,7 @@ using namespace render; -const gpu::PipelinePointer& DrawStatus::getDrawItemBoundsPipeline() { +const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { if (!_drawItemBoundsPipeline) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(drawItemBounds_vert))); auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawItemBounds_frag))); @@ -56,18 +56,20 @@ const gpu::PipelinePointer& DrawStatus::getDrawItemBoundsPipeline() { return _drawItemBoundsPipeline; } -const gpu::PipelinePointer& DrawStatus::getDrawItemStatusPipeline() { +const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() { if (!_drawItemStatusPipeline) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(drawItemStatus_vert))); auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(drawItemStatus_frag))); gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("iconStatusMap"), 0)); gpu::Shader::makeProgram(*program, slotBindings); _drawItemStatusPosLoc = program->getUniforms().findLocation("inBoundPos"); _drawItemStatusDimLoc = program->getUniforms().findLocation("inBoundDim"); - _drawItemStatusValueLoc = program->getUniforms().findLocation("inStatus"); + _drawItemStatusValue0Loc = program->getUniforms().findLocation("inStatus0"); + _drawItemStatusValue1Loc = program->getUniforms().findLocation("inStatus1"); auto state = std::make_shared(); @@ -84,11 +86,23 @@ const gpu::PipelinePointer& DrawStatus::getDrawItemStatusPipeline() { return _drawItemStatusPipeline; } -void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems) { +void DrawStatus::setStatusIconMap(const gpu::TexturePointer& map) { + _statusIconMap = map; +} + +const gpu::TexturePointer DrawStatus::getStatusIconMap() const { + return _statusIconMap; +} + +void DrawStatus::run(const SceneContextPointer& sceneContext, + const RenderContextPointer& renderContext, + const ItemIDsBounds& inItems) { assert(renderContext->args); assert(renderContext->args->_viewFrustum); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; + const int NUM_STATUS_VEC4_PER_ITEM = 2; + const int VEC4_LENGTH = 4; // FIrst thing, we collect the bound and the status for all the items we want to render int nbItems = 0; @@ -101,7 +115,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex } _itemBounds->resize((inItems.size() * sizeof(AABox))); - _itemStatus->resize((inItems.size() * sizeof(glm::vec4))); + _itemStatus->resize((inItems.size() * NUM_STATUS_VEC4_PER_ITEM * sizeof(glm::vec4))); AABox* itemAABox = reinterpret_cast (_itemBounds->editData()); glm::ivec4* itemStatus = reinterpret_cast (_itemStatus->editData()); for (auto& item : inItems) { @@ -112,11 +126,31 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex (*itemAABox).setBox(item.bounds.getCorner(), 0.1f); } auto& itemScene = scene->getItem(item.id); - (*itemStatus) = itemScene.getStatusPackedValues(); + + auto itemStatusPointer = itemScene.getStatus(); + if (itemStatusPointer) { + // Query the current status values, this is where the statusGetter lambda get called + auto&& currentStatusValues = itemStatusPointer->getCurrentValues(); + int valueNum = 0; + for (int vec4Num = 0; vec4Num < NUM_STATUS_VEC4_PER_ITEM; vec4Num++) { + (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); + for (int component = 0; component < VEC4_LENGTH; component++) { + valueNum = vec4Num * VEC4_LENGTH + component; + if (valueNum < (int)currentStatusValues.size()) { + (*itemStatus)[component] = currentStatusValues[valueNum].getPackedData(); + } + } + itemStatus++; + } + } else { + (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); + itemStatus++; + (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); + itemStatus++; + } nbItems++; itemAABox++; - itemStatus++; } } } @@ -131,6 +165,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -144,20 +179,28 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex const unsigned int VEC3_ADRESS_OFFSET = 3; - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i)); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); + if ((renderContext->_drawItemStatus & showDisplayStatusFlag) > 0) { + for (int i = 0; i < nbItems; i++) { + batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i)); + batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); - batch.draw(gpu::LINES, 24, 0); + batch.draw(gpu::LINES, 24, 0); + } } + batch.setResourceTexture(0, gpu::TextureView(getStatusIconMap(), 0)); + batch.setPipeline(getDrawItemStatusPipeline()); - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*) (itemAABox + i)); - batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); - batch._glUniform4iv(_drawItemStatusValueLoc, 1, (const int*) (itemStatus + i)); - batch.draw(gpu::TRIANGLES, 24, 0); + if ((renderContext->_drawItemStatus & showNetworkStatusFlag) > 0) { + for (int i = 0; i < nbItems; i++) { + batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*) (itemAABox + i)); + batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET); + batch._glUniform4iv(_drawItemStatusValue0Loc, 1, (const int*)(itemStatus + NUM_STATUS_VEC4_PER_ITEM * i)); + batch._glUniform4iv(_drawItemStatusValue1Loc, 1, (const int*)(itemStatus + NUM_STATUS_VEC4_PER_ITEM * i + 1)); + batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0); + } } + batch.setResourceTexture(0, 0); }); } diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index a96b897f5c..1239819911 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -21,22 +21,30 @@ namespace render { int _drawItemBoundDimLoc = -1; int _drawItemStatusPosLoc = -1; int _drawItemStatusDimLoc = -1; - int _drawItemStatusValueLoc = -1; + int _drawItemStatusValue0Loc = -1; + int _drawItemStatusValue1Loc = -1; gpu::Stream::FormatPointer _drawItemFormat; gpu::PipelinePointer _drawItemBoundsPipeline; gpu::PipelinePointer _drawItemStatusPipeline; gpu::BufferPointer _itemBounds; gpu::BufferPointer _itemStatus; + gpu::TexturePointer _statusIconMap; public: + DrawStatus() {} + DrawStatus(const gpu::TexturePointer statusIconMap) { setStatusIconMap(statusIconMap); } + void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemIDsBounds& inItems); typedef Job::ModelI JobModel; - const gpu::PipelinePointer& getDrawItemBoundsPipeline(); - const gpu::PipelinePointer& getDrawItemStatusPipeline(); + const gpu::PipelinePointer getDrawItemBoundsPipeline(); + const gpu::PipelinePointer getDrawItemStatusPipeline(); + + void setStatusIconMap(const gpu::TexturePointer& map); + const gpu::TexturePointer getStatusIconMap() const; }; } diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index ed51273f88..3f628c3a02 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -143,7 +143,7 @@ public: const Varying getInput() const { return _input; } - ModelI(const std::string& name, const Varying& input): Concept(name), _input(input) {} + ModelI(const std::string& name, const Varying& input, Data data = Data()) : Concept(name), _data(data), _input(input) {} ModelI(const std::string& name, Data data): Concept(name), _data(data) {} void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 5da7956b22..7c11246cff 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -25,6 +25,10 @@ public: }; typedef std::shared_ptr SceneContextPointer; +// see examples/utilities/tools/renderEngineDebug.js +const int showDisplayStatusFlag = 1; +const int showNetworkStatusFlag = 2; + class RenderContext { public: @@ -49,7 +53,7 @@ public: int _numDrawnOverlay3DItems = 0; int _maxDrawnOverlay3DItems = -1; - bool _drawItemStatus = false; + int _drawItemStatus = 0; bool _drawHitEffect = false; bool _occlusionStatus = false; diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index fb6782e011..18cf1d8335 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -72,17 +72,20 @@ void Item::Status::Value::setScale(float scale) { void Item::Status::Value::setColor(float hue) { // Convert the HUe from range [0, 360] to signed normalized value const float HUE_MAX = 360.0f; - _color = (std::numeric_limits::max() - 1) * 0.5f * (1.0f + std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX); + _color = (std::numeric_limits::max()) * (std::max(std::min(hue, HUE_MAX), 0.0f) / HUE_MAX); +} +void Item::Status::Value::setIcon(unsigned char icon) { + _icon = icon; } -void Item::Status::getPackedValues(glm::ivec4& values) const { - for (unsigned int i = 0; i < (unsigned int)values.length(); i++) { - if (i < _values.size()) { - values[i] = _values[i]().getPackedData(); - } else { - values[i] = Value::INVALID.getPackedData(); - } +Item::Status::Values Item::Status::getCurrentValues() const { + Values currentValues(_values.size()); + auto currentValue = currentValues.begin(); + for (auto& getter : _values) { + (*currentValue) = getter(); + currentValue++; } + return currentValues; } void Item::PayloadInterface::addStatusGetter(const Status::Getter& getter) { @@ -110,15 +113,6 @@ void Item::resetPayload(const PayloadPointer& payload) { } } -glm::ivec4 Item::getStatusPackedValues() const { - glm::ivec4 values(Status::Value::INVALID.getPackedData()); - auto& status = getStatus(); - if (status) { - status->getPackedValues(values); - }; - return values; -} - void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) { _resetItems.push_back(id); _resetPayloads.push_back(payload); diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index ab71a583b4..6ddd60cce8 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -206,17 +206,21 @@ public: // It can be scaled in the range [0, 1] and the color hue in the range [0, 360] representing the color wheel hue class Value { unsigned short _scale = 0xFFFF; - unsigned short _color = 0xFFFF; + unsigned char _color = 0xFF; + unsigned char _icon = 0xFF; public: const static Value INVALID; // Invalid value meanss the status won't show Value() {} - Value(float scale, float hue) { setScale(scale); setColor(hue); } + Value(float scale, float hue, unsigned char icon = 0xFF) { setScale(scale); setColor(hue); setIcon(icon); } // It can be scaled in the range [0, 1] void setScale(float scale); // the color hue in the range [0, 360] representing the color wheel hue void setColor(float hue); + // the icon to display in the range [0, 255], where 0 means no icon, just filled quad and anything else would + // hopefully have an icon available to display (see DrawStatusJob) + void setIcon(unsigned char icon); // Standard color Hue static const float RED; // 0.0f; @@ -237,7 +241,10 @@ public: void addGetter(const Getter& getter) { _values.push_back(getter); } - void getPackedValues(glm::ivec4& values) const; + size_t getNumValues() const { return _values.size(); } + + using Values = std::vector ; + Values getCurrentValues() const; }; typedef std::shared_ptr StatusPointer; @@ -301,7 +308,6 @@ public: // Access the status const StatusPointer& getStatus() const { return _payload->getStatus(); } - glm::ivec4 getStatusPackedValues() const; protected: PayloadPointer _payload; diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 4cc45db2ec..40cf450363 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -12,9 +12,20 @@ // in vec4 varColor; +in vec3 varTexcoord; out vec4 outFragColor; +uniform sampler2D _icons; +vec2 getIconTexcoord(float icon, vec2 uv) { + const vec2 ICON_COORD_SIZE = vec2(0.0625, 1.0); + return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); +} void main(void) { - outFragColor = varColor; + if (varTexcoord.z < 254.5) { + outFragColor = texture(_icons, getIconTexcoord(varTexcoord.z, varTexcoord.xy)) * varColor; + } else { + vec2 centerDir = varTexcoord.xy * 2.0f - 1.0f; + outFragColor = vec4(varColor.xyz, 1.0 - step(1.0f, dot(centerDir.xy, centerDir.xy))); + } } diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index 178293575d..cb4ae7ebd2 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -17,10 +17,12 @@ <$declareStandardTransform()$> out vec4 varColor; +out vec3 varTexcoord; uniform vec3 inBoundPos; uniform vec3 inBoundDim; -uniform ivec4 inStatus; +uniform ivec4 inStatus0; +uniform ivec4 inStatus1; vec3 paintRainbow(float normalizedHue) { float v = normalizedHue * 6.f; @@ -43,16 +45,28 @@ vec3 paintRainbow(float normalizedHue) { } } -vec2 unpackStatus(int v) { - return vec2(clamp(float(int((v >> 0) & 0xFFFF) - 32727) / 32727.0, -1.0, 1.0), - clamp(float(int((v >> 16) & 0xFFFF) - 32727) / 32727.0, -1.0, 1.0)); +const int INVALID_STATUS = int(0xFFFFFFFF); +const int MAX_NUM_ICONS = 8; +const int ICONS_PER_ROW = 4; + +int getIconStatus(int icon) { + if (icon < ICONS_PER_ROW) { + return inStatus0[icon]; + } else if (icon < MAX_NUM_ICONS) { + return inStatus1[icon - ICONS_PER_ROW]; + } + return INVALID_STATUS; +} + +vec3 unpackStatus(int v) { + return vec3(clamp(float(int((v >> 0) & 0xFFFF) - 32727) / 32727.0, -1.0, 1.0), + clamp(float(uint((v >> 16) & 0xFF)) / 255.0, 0.0, 1.0), + clamp(float(int((v >> 24) & 0xFF)), 0.0, 255.0)); } void main(void) { - const vec2 ICON_PIXEL_SIZE = vec2(10, 10); - const vec2 MARGIN_PIXEL_SIZE = vec2(2, 2); - const int NUM_VERTICES = 6; - const vec4 UNIT_QUAD[NUM_VERTICES] = vec4[NUM_VERTICES]( + const int NUM_VERTICES_PER_ICON = 6; + const vec4 UNIT_QUAD[NUM_VERTICES_PER_ICON] = vec4[NUM_VERTICES_PER_ICON]( vec4(-1.0, -1.0, 0.0, 1.0), vec4(1.0, -1.0, 0.0, 1.0), vec4(-1.0, 1.0, 0.0, 1.0), @@ -61,6 +75,17 @@ void main(void) { vec4(1.0, 1.0, 0.0, 1.0) ); + const vec2 ICON_PIXEL_SIZE = vec2(20, 20); + const vec2 MARGIN_PIXEL_SIZE = vec2(2, 2); + const vec2 ICON_GRID_SLOTS[MAX_NUM_ICONS] = vec2[MAX_NUM_ICONS](vec2(-1.5, 0.5), + vec2(-0.5, 0.5), + vec2(0.5, 0.5), + vec2(1.5, 0.5), + vec2(-1.5,-0.5), + vec2(-0.5,-0.5), + vec2(0.5, -0.5), + vec2(1.5, -0.5)); + // anchor point in clip space vec4 anchorPoint = vec4(inBoundPos, 1.0) + vec4(inBoundDim, 0.0) * vec4(0.5, 0.5, 0.5, 0.0); TransformCamera cam = getTransformCamera(); @@ -68,36 +93,43 @@ void main(void) { <$transformModelToClipPos(cam, obj, anchorPoint, anchorPoint)$> // Which icon are we dealing with ? - int iconNum = gl_VertexID / NUM_VERTICES; + int iconNum = gl_VertexID / NUM_VERTICES_PER_ICON; + int packedIconStatus = getIconStatus(iconNum); // if invalid, just kill - if (inStatus[iconNum] == 0xFFFFFFFF) { + if (packedIconStatus == INVALID_STATUS) { gl_Position = anchorPoint; varColor = vec4(1.0); return; } + // Which quad vertex pos? + int twoTriID = gl_VertexID - iconNum * NUM_VERTICES_PER_ICON; + vec4 quadPos = UNIT_QUAD[twoTriID]; + // unpack to get x and y satus - vec2 iconStatus = unpackStatus(inStatus[iconNum]); + vec3 iconStatus = unpackStatus(packedIconStatus); // Use the status for showing a color varColor = vec4(paintRainbow(abs(iconStatus.y)), 1.0); + // Pass the texcoord and the z texcoord is representing the texture icon + varTexcoord = vec3((quadPos.xy + 1.0) * 0.5, iconStatus.z); + // Also changes the size of the notification vec2 iconScale = ICON_PIXEL_SIZE; - iconScale = max(vec2(1, 1), (iconScale * iconStatus.x)); + iconScale = max(vec2(0, 0), (iconScale * iconStatus.x)); //Offset icon to the right based on the iconNum - vec2 offset = vec2(iconNum * (ICON_PIXEL_SIZE.x + MARGIN_PIXEL_SIZE.x), 0); + vec2 gridOffset = ICON_GRID_SLOTS[iconNum]; + vec2 offset = gridOffset * (ICON_PIXEL_SIZE + MARGIN_PIXEL_SIZE); // Final position in pixel space - int twoTriID = gl_VertexID - iconNum * NUM_VERTICES; - vec4 pos = UNIT_QUAD[twoTriID]; - vec2 quadPixelPos = offset.xy + pos.xy * 0.5 * iconScale; + vec2 quadPixelPos = offset.xy + quadPos.xy * 0.5 * iconScale; vec4 viewport; <$transformCameraViewport(cam, viewport)$>; vec2 pixelToClip = vec2(2.0 / viewport.z, 2.0 / viewport.w); gl_Position = anchorPoint + (anchorPoint.w * vec4(quadPixelPos * pixelToClip, 0.0, 0.0)); -} \ No newline at end of file +} diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 95919d6c0c..6be0ce44a8 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -107,8 +107,8 @@ public: Q_INVOKABLE void setEngineMaxDrawnOverlay3DItems(int count) { _maxDrawnOverlay3DItems = count; } Q_INVOKABLE int getEngineMaxDrawnOverlay3DItems() { return _maxDrawnOverlay3DItems; } - Q_INVOKABLE void setEngineDisplayItemStatus(bool display) { _drawItemStatus = display; } - Q_INVOKABLE bool doEngineDisplayItemStatus() { return _drawItemStatus; } + Q_INVOKABLE void setEngineDisplayItemStatus(int display) { _drawItemStatus = display; } + Q_INVOKABLE int doEngineDisplayItemStatus() { return _drawItemStatus; } Q_INVOKABLE void setEngineDisplayHitEffect(bool display) { _drawHitEffect = display; } Q_INVOKABLE bool doEngineDisplayHitEffect() { return _drawHitEffect; } @@ -143,7 +143,7 @@ protected: int _maxDrawnTransparentItems = -1; int _maxDrawnOverlay3DItems = -1; - bool _drawItemStatus = false; + int _drawItemStatus = 0; bool _drawHitEffect = false; diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 25eed96490..fcacf7aaed 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -68,17 +68,16 @@ public: class RenderArgs { public: typedef std::function ShoudRenderFunctor; - + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT }; enum DebugFlags { RENDER_DEBUG_NONE = 0, - RENDER_DEBUG_HULLS = 1, - RENDER_DEBUG_SIMULATION_OWNERSHIP = 2, + RENDER_DEBUG_HULLS = 1 }; - + RenderArgs(std::shared_ptr context = nullptr, OctreeRenderer* renderer = nullptr, ViewFrustum* viewFrustum = nullptr, diff --git a/libraries/shared/src/shared/QTryReadLocker.h b/libraries/shared/src/shared/QTryReadLocker.h new file mode 100644 index 0000000000..a9ab2a1e68 --- /dev/null +++ b/libraries/shared/src/shared/QTryReadLocker.h @@ -0,0 +1,92 @@ +// +// QTryReadLocker.h +// shared/src/shared/QTryReadLocker.h +// +// Created by Clément Brisset on 10/29/15. +// Copyright 2015 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 +// + +#ifndef hifi_QTryReadLocker_h +#define hifi_QTryReadLocker_h + +#include + +class QTryReadLocker { +public: + QTryReadLocker(QReadWriteLock* readWriteLock); + QTryReadLocker(QReadWriteLock* readWriteLock, int timeout); + ~QTryReadLocker(); + + bool isLocked() const; + + void unlock(); + bool tryRelock(); + bool tryRelock(int timeout); + + QReadWriteLock* readWriteLock() const; + +private: + Q_DISABLE_COPY(QTryReadLocker) + quintptr _val; +}; + +// Implementation +inline QTryReadLocker::QTryReadLocker(QReadWriteLock* areadWriteLock) : + _val(reinterpret_cast(areadWriteLock)) +{ + Q_ASSERT_X((_val & quintptr(1u)) == quintptr(0), + "QTryReadLocker", "QTryReadLocker pointer is misaligned"); + tryRelock(); +} + +inline QTryReadLocker::QTryReadLocker(QReadWriteLock* areadWriteLock, int timeout) : + _val(reinterpret_cast(areadWriteLock)) +{ + Q_ASSERT_X((_val & quintptr(1u)) == quintptr(0), + "QTryReadLocker", "QTryReadLocker pointer is misaligned"); + tryRelock(timeout); +} + +inline QTryReadLocker::~QTryReadLocker() { + unlock(); +} + +inline bool QTryReadLocker::isLocked() const { + return (_val & quintptr(1u)) == quintptr(1u); +} + +inline void QTryReadLocker::unlock() { + if (_val && isLocked()) { + _val &= ~quintptr(1u); + readWriteLock()->unlock(); + } +} + +inline bool QTryReadLocker::tryRelock() { + if (_val && !isLocked()) { + if (readWriteLock()->tryLockForRead()) { + _val |= quintptr(1u); + return true; + } + } + return false; +} + +inline bool QTryReadLocker::tryRelock(int timeout) { + if (_val && !isLocked()) { + if (readWriteLock()->tryLockForRead(timeout)) { + _val |= quintptr(1u); + return true; + } + } + return false; +} + +inline QReadWriteLock* QTryReadLocker::readWriteLock() const { + return reinterpret_cast(_val & ~quintptr(1u)); +} + +#endif // hifi_QTryReadLocker_h \ No newline at end of file diff --git a/libraries/shared/src/shared/QTryWriteLocker.h b/libraries/shared/src/shared/QTryWriteLocker.h new file mode 100644 index 0000000000..cbfc335da1 --- /dev/null +++ b/libraries/shared/src/shared/QTryWriteLocker.h @@ -0,0 +1,92 @@ +// +// QTryWriteLocker.h +// shared/src/shared/QTryWriteLocker.h +// +// Created by Clément Brisset on 10/29/15. +// Copyright 2015 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 +// + +#ifndef hifi_QTryWriteLocker_h +#define hifi_QTryWriteLocker_h + +#include + +class QTryWriteLocker { +public: + QTryWriteLocker(QReadWriteLock* readWriteLock); + QTryWriteLocker(QReadWriteLock* readWriteLock, int timeout); + ~QTryWriteLocker(); + + bool isLocked() const; + + void unlock(); + bool tryRelock(); + bool tryRelock(int timeout); + + QReadWriteLock* readWriteLock() const; + +private: + Q_DISABLE_COPY(QTryWriteLocker) + quintptr _val; +}; + +// Implementation +inline QTryWriteLocker::QTryWriteLocker(QReadWriteLock* readWriteLock) : + _val(reinterpret_cast(readWriteLock)) +{ + Q_ASSERT_X((_val & quintptr(1u)) == quintptr(0), + "QTryWriteLocker", "QTryWriteLocker pointer is misaligned"); + tryRelock(); +} + +inline QTryWriteLocker::QTryWriteLocker(QReadWriteLock* readWriteLock, int timeout) : + _val(reinterpret_cast(readWriteLock)) +{ + Q_ASSERT_X((_val & quintptr(1u)) == quintptr(0), + "QTryWriteLocker", "QTryWriteLocker pointer is misaligned"); + tryRelock(timeout); +} + +inline QTryWriteLocker::~QTryWriteLocker() { + unlock(); +} + +inline bool QTryWriteLocker::isLocked() const { + return (_val & quintptr(1u)) == quintptr(1u); +} + +inline void QTryWriteLocker::unlock() { + if (_val && isLocked()) { + _val &= ~quintptr(1u); + readWriteLock()->unlock(); + } +} + +inline bool QTryWriteLocker::tryRelock() { + if (_val && !isLocked()) { + if (readWriteLock()->tryLockForWrite()) { + _val |= quintptr(1u); + return true; + } + } + return false; +} + +inline bool QTryWriteLocker::tryRelock(int timeout) { + if (_val && !isLocked()) { + if (readWriteLock()->tryLockForWrite(timeout)) { + _val |= quintptr(1u); + return true; + } + } + return false; +} + +inline QReadWriteLock* QTryWriteLocker::readWriteLock() const { + return reinterpret_cast(_val & ~quintptr(1u)); +} + +#endif // hifi_QTryWriteLocker_h \ No newline at end of file diff --git a/libraries/shared/src/shared/ReadWriteLockable.h b/libraries/shared/src/shared/ReadWriteLockable.h index 98dff4a841..07b46bb92a 100644 --- a/libraries/shared/src/shared/ReadWriteLockable.h +++ b/libraries/shared/src/shared/ReadWriteLockable.h @@ -9,54 +9,118 @@ #pragma once #ifndef hifi_ReadWriteLockable_h #define hifi_ReadWriteLockable_h + +#include + #include +#include "QTryReadLocker.h" +#include "QTryWriteLocker.h" + class ReadWriteLockable { public: + // Write locks template - bool withWriteLock(F f, bool require = true) const { - if (!require) { - bool result = _lock.tryLockForWrite(); - if (result) { - f(); - _lock.unlock(); - } - return result; - } - - QWriteLocker locker(&_lock); - f(); - return true; - } + void withWriteLock(F&& f) const; + + template + bool withWriteLock(F&& f, bool require) const; template - bool withTryWriteLock(F f) const { - return withWriteLock(f, false); - } - + bool withTryWriteLock(F&& f) const; + template - bool withReadLock(F f, bool require = true) const { - if (!require) { - bool result = _lock.tryLockForRead(); - if (result) { - f(); - _lock.unlock(); - } - return result; - } - - QReadLocker locker(&_lock); - f(); - return true; - } - + bool withTryWriteLock(F&& f, int timeout) const; + + // Read locks template - bool withTryReadLock(F f) const { - return withReadLock(f, false); - } + void withReadLock(F&& f) const; + + template + bool withReadLock(F&& f, bool require) const; + + template + bool withTryReadLock(F&& f) const; + + template + bool withTryReadLock(F&& f, int timeout) const; private: - mutable QReadWriteLock _lock{ QReadWriteLock::Recursive }; + mutable QReadWriteLock _lock { QReadWriteLock::Recursive }; }; +// ReadWriteLockable +template +inline void ReadWriteLockable::withWriteLock(F&& f) const { + QWriteLocker locker(&_lock); + f(); +} + +template +inline bool ReadWriteLockable::withWriteLock(F&& f, bool require) const { + if (require) { + withWriteLock(std::forward(f)); + return true; + } else { + return withTryReadLock(std::forward(f)); + } +} + +template +inline bool ReadWriteLockable::withTryWriteLock(F&& f) const { + QTryWriteLocker locker(&_lock); + if (locker.isLocked()) { + f(); + return true; + } + return false; +} + +template +inline bool ReadWriteLockable::withTryWriteLock(F&& f, int timeout) const { + QTryWriteLocker locker(&_lock, timeout); + if (locker.isLocked()) { + f(); + return true; + } + return false; +} + +template +inline void ReadWriteLockable::withReadLock(F&& f) const { + QReadLocker locker(&_lock); + f(); +} + +template +inline bool ReadWriteLockable::withReadLock(F&& f, bool require) const { + if (require) { + withReadLock(std::forward(f)); + return true; + } else { + return withTryReadLock(std::forward(f)); + } +} + +template +inline bool ReadWriteLockable::withTryReadLock(F&& f) const { + QTryReadLocker locker(&_lock); + if (locker.isLocked()) { + f(); + return true; + } + return false; +} + +template +inline bool ReadWriteLockable::withTryReadLock(F&& f, int timeout) const { + QTryReadLocker locker(&_lock, timeout); + if (locker.isLocked()) { + f(); + return true; + } + return false; +} + + #endif