mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 03:33:25 +02:00
Merge remote-tracking branch 'origin/master' into android_places_goto
This commit is contained in:
commit
8235f999d8
84 changed files with 846 additions and 2231 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -91,3 +91,6 @@ interface/compiledResources
|
|||
|
||||
# GPUCache
|
||||
interface/resources/GPUCache/*
|
||||
|
||||
# package lock file for JSDoc tool
|
||||
tools/jsdoc/package-lock.json
|
||||
|
|
|
@ -11,7 +11,7 @@ Should you choose not to install Qt5 via a package manager that handles dependen
|
|||
## Ubuntu 16.04 specific build guide
|
||||
|
||||
### Prepare environment
|
||||
|
||||
hifiqt5.10.1
|
||||
Install qt:
|
||||
```bash
|
||||
wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb
|
||||
|
|
|
@ -380,7 +380,7 @@ void Agent::executeScript() {
|
|||
|
||||
using namespace recording;
|
||||
static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME);
|
||||
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) {
|
||||
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) {
|
||||
|
||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel();
|
||||
|
|
|
@ -48,8 +48,6 @@ private:
|
|||
void preDistributionProcessing() override;
|
||||
bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
|
||||
bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
|
||||
void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
|
||||
bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
|
||||
|
||||
DiffTraversal _traversal;
|
||||
EntityPriorityQueue _sendQueue;
|
||||
|
|
|
@ -304,23 +304,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
|||
return numPackets;
|
||||
}
|
||||
|
||||
void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
|
||||
// If we're starting a full scene, then definitely we want to empty the elementBag
|
||||
if (isFullScene) {
|
||||
nodeData->elementBag.deleteAll();
|
||||
}
|
||||
|
||||
// This is the start of "resending" the scene.
|
||||
bool dontRestartSceneOnMove = false; // this is experimental
|
||||
if (dontRestartSceneOnMove) {
|
||||
if (nodeData->elementBag.isEmpty()) {
|
||||
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
||||
}
|
||||
} else {
|
||||
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
||||
}
|
||||
}
|
||||
|
||||
/// Version of octree element distributor that sends the deepest LOD level at once
|
||||
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
||||
OctreeServer::didPacketDistributor(this);
|
||||
|
@ -366,16 +349,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
// the current view frustum for things to send.
|
||||
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
|
||||
|
||||
// if our view has changed, we need to reset these things...
|
||||
if (viewFrustumChanged) {
|
||||
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
|
||||
nodeData->dumpOutOfView();
|
||||
}
|
||||
}
|
||||
|
||||
// track completed scenes and send out the stats packet accordingly
|
||||
nodeData->stats.sceneCompleted();
|
||||
nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged());
|
||||
_myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData);
|
||||
|
||||
// TODO: add these to stats page
|
||||
|
@ -389,111 +364,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
// TODO: add these to stats page
|
||||
//::startSceneSleepTime = _usleepTime;
|
||||
|
||||
nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE);
|
||||
// start tracking our stats
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot());
|
||||
|
||||
preStartNewScene(nodeData, isFullScene);
|
||||
}
|
||||
|
||||
// If we have something in our elementBag, then turn them into packets and send them out...
|
||||
if (shouldTraverseAndSend(nodeData)) {
|
||||
quint64 start = usecTimestampNow();
|
||||
quint64 start = usecTimestampNow();
|
||||
|
||||
_myServer->getOctree()->withReadLock([&]{
|
||||
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
||||
});
|
||||
_myServer->getOctree()->withReadLock([&]{
|
||||
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
||||
});
|
||||
|
||||
// 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
|
||||
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
|
||||
int specialPacketsSent = 0;
|
||||
int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
|
||||
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
|
||||
_truePacketsSent += specialPacketsSent;
|
||||
_trueBytesSent += specialBytesSent;
|
||||
_packetsSentThisInterval += specialPacketsSent;
|
||||
// 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
|
||||
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
|
||||
int specialPacketsSent = 0;
|
||||
int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
|
||||
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
|
||||
_truePacketsSent += specialPacketsSent;
|
||||
_trueBytesSent += specialBytesSent;
|
||||
_packetsSentThisInterval += specialPacketsSent;
|
||||
|
||||
_totalPackets += specialPacketsSent;
|
||||
_totalBytes += specialBytesSent;
|
||||
_totalPackets += specialPacketsSent;
|
||||
_totalBytes += specialBytesSent;
|
||||
|
||||
_totalSpecialPackets += specialPacketsSent;
|
||||
_totalSpecialBytes += specialBytesSent;
|
||||
_totalSpecialPackets += specialPacketsSent;
|
||||
_totalSpecialBytes += specialBytesSent;
|
||||
}
|
||||
|
||||
// calculate max number of packets that can be sent during this interval
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
|
||||
// Re-send packets that were nacked by the client
|
||||
while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
|
||||
const NLPacket* packet = nodeData->getNextNackedPacket();
|
||||
if (packet) {
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
|
||||
int numBytes = packet->getDataSize();
|
||||
_truePacketsSent++;
|
||||
_trueBytesSent += numBytes;
|
||||
_packetsSentThisInterval++;
|
||||
|
||||
_totalPackets++;
|
||||
_totalBytes += numBytes;
|
||||
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
|
||||
}
|
||||
}
|
||||
|
||||
// calculate max number of packets that can be sent during this interval
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / USECS_PER_MSEC;
|
||||
OctreeServer::trackLoopTime(elapsedmsec);
|
||||
|
||||
// Re-send packets that were nacked by the client
|
||||
while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
|
||||
const NLPacket* packet = nodeData->getNextNackedPacket();
|
||||
if (packet) {
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
|
||||
int numBytes = packet->getDataSize();
|
||||
_truePacketsSent++;
|
||||
_trueBytesSent += numBytes;
|
||||
_packetsSentThisInterval++;
|
||||
// if we've sent everything, then we want to remember that we've sent all
|
||||
// the octree elements from the current view frustum
|
||||
if (!hasSomethingToSend(nodeData)) {
|
||||
nodeData->setViewSent(true);
|
||||
|
||||
_totalPackets++;
|
||||
_totalBytes += numBytes;
|
||||
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
|
||||
}
|
||||
// If this was a full scene then make sure we really send out a stats packet at this point so that
|
||||
// the clients will know the scene is stable
|
||||
if (isFullScene) {
|
||||
nodeData->stats.sceneCompleted();
|
||||
handlePacketSend(node, nodeData, true);
|
||||
}
|
||||
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / USECS_PER_MSEC;
|
||||
OctreeServer::trackLoopTime(elapsedmsec);
|
||||
|
||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||
// the octree elements from the current view frustum
|
||||
if (!hasSomethingToSend(nodeData)) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
|
||||
// If this was a full scene then make sure we really send out a stats packet at this point so that
|
||||
// the clients will know the scene is stable
|
||||
if (isFullScene) {
|
||||
nodeData->stats.sceneCompleted();
|
||||
handlePacketSend(node, nodeData, true);
|
||||
}
|
||||
}
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
}
|
||||
|
||||
return _truePacketsSent;
|
||||
}
|
||||
|
||||
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
|
||||
bool somethingToSend = false;
|
||||
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||
if (!nodeData->elementBag.isEmpty()) {
|
||||
quint64 encodeStart = usecTimestampNow();
|
||||
quint64 lockWaitStart = encodeStart;
|
||||
|
||||
_myServer->getOctree()->withReadLock([&]{
|
||||
OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart));
|
||||
|
||||
OctreeElementPointer subTree = nodeData->elementBag.extract();
|
||||
if (subTree) {
|
||||
// NOTE: this is where the tree "contents" are actually packed
|
||||
nodeData->stats.encodeStarted();
|
||||
_myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
|
||||
nodeData->stats.encodeStopped();
|
||||
|
||||
somethingToSend = true;
|
||||
}
|
||||
});
|
||||
|
||||
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
|
||||
} else {
|
||||
OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME);
|
||||
OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
|
||||
}
|
||||
return somethingToSend;
|
||||
}
|
||||
|
||||
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
|
||||
// calculate max number of packets that can be sent during this interval
|
||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
|
@ -502,21 +440,12 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
|||
int extraPackingAttempts = 0;
|
||||
|
||||
// init params once outside the while loop
|
||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient +
|
||||
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
||||
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
||||
isFullScene, nodeData);
|
||||
EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData);
|
||||
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
|
||||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
|
||||
};
|
||||
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
||||
if (viewFrustumChanged) {
|
||||
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
|
||||
}
|
||||
|
||||
bool somethingToSend = true; // assume we have something
|
||||
bool hadSomething = hasSomethingToSend(nodeData);
|
||||
|
@ -536,8 +465,8 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
|||
extraPackingAttempts++;
|
||||
}
|
||||
|
||||
// If the bag had contents but is now empty then we know we've sent the entire scene.
|
||||
bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
|
||||
// If we had something to send, but now we don't, then we know we've sent the entire scene.
|
||||
bool completedScene = hadSomething;
|
||||
if (completedScene || lastNodeDidntFit) {
|
||||
// we probably want to flush what has accumulated in nodeData but:
|
||||
// do we have more data to send? and is there room?
|
||||
|
|
|
@ -54,7 +54,7 @@ protected:
|
|||
|
||||
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||
bool viewFrustumChanged, bool isFullScene);
|
||||
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
|
||||
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0;
|
||||
|
||||
OctreePacketData _packetData;
|
||||
QWeakPointer<Node> _node;
|
||||
|
@ -63,14 +63,12 @@ protected:
|
|||
|
||||
private:
|
||||
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
||||
virtual void preDistributionProcessing() {};
|
||||
virtual void preDistributionProcessing() = 0;
|
||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
|
||||
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
||||
|
||||
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
|
||||
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
|
||||
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
|
||||
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
|
||||
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0;
|
||||
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0;
|
||||
|
||||
int _truePacketsSent { 0 }; // available for debug stats
|
||||
int _trueBytesSent { 0 }; // available for debug stats
|
||||
|
|
|
@ -876,10 +876,6 @@ void OctreeServer::parsePayload() {
|
|||
}
|
||||
}
|
||||
|
||||
OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) {
|
||||
return std::unique_ptr<OctreeSendThread>(new OctreeSendThread(this, node));
|
||||
}
|
||||
|
||||
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
|
||||
auto sendThread = newSendThread(node);
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ protected:
|
|||
void beginRunning(QByteArray replaceData);
|
||||
|
||||
UniqueSendThread createSendThread(const SharedNodePointer& node);
|
||||
virtual UniqueSendThread newSendThread(const SharedNodePointer& node);
|
||||
virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0;
|
||||
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
|
|
|
@ -451,11 +451,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
QUuid hintNodeID;
|
||||
QUuid existingNodeID;
|
||||
|
||||
// in case this is a node that's failing to connect
|
||||
// double check we don't have the same node whose sockets match exactly already in the list
|
||||
limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){
|
||||
|
||||
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
|
||||
// we have a node that already has these exact sockets - this can occur if a node
|
||||
// is failing to connect to the domain
|
||||
|
@ -465,15 +466,20 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
auto existingNodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (existingNodeData->getUsername() == username) {
|
||||
hintNodeID = node->getUUID();
|
||||
qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID();
|
||||
existingNodeID = node->getUUID();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!existingNodeID.isNull()) {
|
||||
limitedNodeList->killNodeWithUUID(existingNodeID);
|
||||
}
|
||||
|
||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
// set the edit rights for this user
|
||||
newNode->setPermissions(userPerms);
|
||||
|
|
|
@ -2903,7 +2903,7 @@ void DomainServer::updateReplicatedNodes() {
|
|||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->eachMatchingNode([this](const SharedNodePointer& otherNode) -> bool {
|
||||
nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool {
|
||||
return otherNode->getType() == NodeType::Agent;
|
||||
}, [this](const SharedNodePointer& otherNode) {
|
||||
auto shouldReplicate = shouldReplicateNode(*otherNode);
|
||||
|
|
Binary file not shown.
|
@ -30,6 +30,16 @@ Item {
|
|||
color: "black"
|
||||
opacity: 0.5
|
||||
radius: popupRadius
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
acceptedButtons: Qt.LeftButton;
|
||||
propagateComposedEvents: false;
|
||||
onClicked: {
|
||||
letterbox.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: textContainer;
|
||||
|
@ -38,6 +48,14 @@ Item {
|
|||
anchors.centerIn: parent
|
||||
radius: popupRadius
|
||||
color: "white"
|
||||
|
||||
// Prevent dismissing the popup by clicking on the textContainer
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
propagateComposedEvents: false;
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
width: parent.width - 50
|
||||
|
@ -135,11 +153,4 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
letterbox.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ FocusScope {
|
|||
id: tabletMenu
|
||||
objectName: "tabletMenu"
|
||||
|
||||
width: 480
|
||||
height: 720
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
property var rootMenu: Menu { objectName:"rootMenu" }
|
||||
property var point: Qt.point(50, 50);
|
||||
|
|
|
@ -16,6 +16,8 @@ import "."
|
|||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
objectName: "tabletMenuHandlerItem"
|
||||
|
||||
StackView {
|
||||
|
|
|
@ -40,10 +40,10 @@ FocusScope {
|
|||
id: listView
|
||||
x: 0
|
||||
y: 0
|
||||
width: 480
|
||||
height: 720
|
||||
contentWidth: 480
|
||||
contentHeight: 720
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentWidth: parent.width
|
||||
contentHeight: parent.height
|
||||
objectName: "menuList"
|
||||
|
||||
topMargin: hifi.dimensions.menuPadding.y
|
||||
|
|
|
@ -1100,10 +1100,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
|
||||
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
|
||||
float distance = glm::distance(myAvatarPosition, position);
|
||||
bool shouldMute = !audioClient->isMuted() && (distance < radius);
|
||||
|
||||
if (shouldMute) {
|
||||
audioClient->toggleMute();
|
||||
if (distance < radius) {
|
||||
audioClient->setMuted(true);
|
||||
audioScriptingInterface->environmentMuted();
|
||||
}
|
||||
});
|
||||
|
@ -1528,7 +1527,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
if (state) {
|
||||
if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) {
|
||||
DependencyManager::get<AudioClient>()->toggleMute();
|
||||
auto audioClient = DependencyManager::get<AudioClient>();
|
||||
audioClient->setMuted(!audioClient->isMuted());
|
||||
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
|
||||
cycleCamera();
|
||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
|
@ -3476,7 +3476,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
|
||||
case Qt::Key_M:
|
||||
if (isMeta) {
|
||||
DependencyManager::get<AudioClient>()->toggleMute();
|
||||
auto audioClient = DependencyManager::get<AudioClient>();
|
||||
audioClient->setMuted(!audioClient->isMuted());
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -5137,7 +5138,7 @@ void Application::update(float deltaTime) {
|
|||
if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) {
|
||||
if (_lastFaceTrackerUpdate > 0
|
||||
&& ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) {
|
||||
audioClient->toggleMute();
|
||||
audioClient->setMuted(true);
|
||||
_lastFaceTrackerUpdate = 0;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -31,10 +31,41 @@ using namespace crashpad;
|
|||
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
|
||||
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
|
||||
|
||||
static std::wstring gIPCPipe;
|
||||
|
||||
extern QString qAppFileName();
|
||||
|
||||
// crashpad::AnnotationList* crashpadAnnotations { nullptr };
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
|
||||
static const DWORD EXTERNAL_EXCEPTION_CODE{ 0xe06d7363 };
|
||||
static const DWORD HEAP_CORRUPTION_CODE{ 0xc0000374 };
|
||||
|
||||
auto exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
|
||||
if (exceptionCode == EXTERNAL_EXCEPTION_CODE) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (exceptionCode == HEAP_CORRUPTION_CODE) {
|
||||
qCritical() << "VectoredExceptionHandler: Heap corruption:" << QString::number(exceptionCode, 16);
|
||||
|
||||
CrashpadClient client;
|
||||
if (gIPCPipe.length()) {
|
||||
bool rc = client.SetHandlerIPCPipe(gIPCPipe);
|
||||
qCritical() << "SetHandlerIPCPipe = " << rc;
|
||||
} else {
|
||||
qCritical() << "No IPC Pipe was previously defined for crash handler.";
|
||||
}
|
||||
qCritical() << "Calling DumpAndCrash()";
|
||||
client.DumpAndCrash(pExceptionInfo);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool startCrashHandler() {
|
||||
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
|
||||
return false;
|
||||
|
@ -76,7 +107,12 @@ bool startCrashHandler() {
|
|||
// Enable automated uploads.
|
||||
database->GetSettings()->SetUploadsEnabled(true);
|
||||
|
||||
return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
gIPCPipe = client.GetHandlerIPCPipe();
|
||||
|
||||
AddVectoredExceptionHandler(0, vectoredExceptionHandler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
|
|
|
@ -750,32 +750,32 @@ Menu::Menu() {
|
|||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::pureVirtualCall(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::pureVirtualCall).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doubleFree(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doubleFree).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doAbort(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doAbort).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::nullDeref(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::nullDeref).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::outOfBoundsVectorCrash(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::outOfBoundsVectorCrash).join(); });
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::newFault(); });
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::newFault(); }); });
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); });
|
||||
|
||||
// Developer > Log...
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,
|
||||
|
|
|
@ -50,110 +50,162 @@ float Audio::loudnessToLevel(float loudness) {
|
|||
|
||||
Audio::Audio() : _devices(_contextIsHMD) {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged);
|
||||
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::onNoiseReductionChanged);
|
||||
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
|
||||
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
|
||||
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
|
||||
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::onInputVolumeChanged);
|
||||
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
|
||||
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
|
||||
enableNoiseReduction(enableNoiseReductionSetting.get());
|
||||
}
|
||||
|
||||
bool Audio::startRecording(const QString& filepath) {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
return client->startRecording(filepath);
|
||||
return resultWithWriteLock<bool>([&] {
|
||||
return client->startRecording(filepath);
|
||||
});
|
||||
}
|
||||
|
||||
bool Audio::getRecording() {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
return client->getRecording();
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return client->getRecording();
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::stopRecording() {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
client->stopRecording();
|
||||
withWriteLock([&] {
|
||||
client->stopRecording();
|
||||
});
|
||||
}
|
||||
|
||||
bool Audio::isMuted() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _isMuted;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::setMuted(bool isMuted) {
|
||||
if (_isMuted != isMuted) {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "toggleMute");
|
||||
bool changed = false;
|
||||
withWriteLock([&] {
|
||||
if (_isMuted != isMuted) {
|
||||
_isMuted = isMuted;
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit mutedChanged(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::onMutedChanged() {
|
||||
bool isMuted = DependencyManager::get<AudioClient>()->isMuted();
|
||||
if (_isMuted != isMuted) {
|
||||
_isMuted = isMuted;
|
||||
emit mutedChanged(_isMuted);
|
||||
}
|
||||
bool Audio::noiseReductionEnabled() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _enableNoiseReduction;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::enableNoiseReduction(bool enable) {
|
||||
if (_enableNoiseReduction != enable) {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable));
|
||||
enableNoiseReductionSetting.set(enable);
|
||||
bool changed = false;
|
||||
withWriteLock([&] {
|
||||
if (_enableNoiseReduction != enable) {
|
||||
_enableNoiseReduction = enable;
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable), Q_ARG(bool, false));
|
||||
enableNoiseReductionSetting.set(enable);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit noiseReductionChanged(enable);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::onNoiseReductionChanged() {
|
||||
bool noiseReductionEnabled = DependencyManager::get<AudioClient>()->isNoiseReductionEnabled();
|
||||
if (_enableNoiseReduction != noiseReductionEnabled) {
|
||||
_enableNoiseReduction = noiseReductionEnabled;
|
||||
emit noiseReductionChanged(_enableNoiseReduction);
|
||||
}
|
||||
float Audio::getInputVolume() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _inputVolume;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::setInputVolume(float volume) {
|
||||
// getInputVolume will not reflect changes synchronously, so clamp beforehand
|
||||
volume = glm::clamp(volume, 0.0f, 1.0f);
|
||||
|
||||
if (_inputVolume != volume) {
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume));
|
||||
bool changed = false;
|
||||
withWriteLock([&] {
|
||||
if (_inputVolume != volume) {
|
||||
_inputVolume = volume;
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume), Q_ARG(bool, false));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit inputVolumeChanged(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::onInputVolumeChanged(float volume) {
|
||||
if (_inputVolume != volume) {
|
||||
_inputVolume = volume;
|
||||
emit inputVolumeChanged(_inputVolume);
|
||||
}
|
||||
float Audio::getInputLevel() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _inputLevel;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::onInputLoudnessChanged(float loudness) {
|
||||
float level = loudnessToLevel(loudness);
|
||||
|
||||
if (_inputLevel != level) {
|
||||
_inputLevel = level;
|
||||
emit inputLevelChanged(_inputLevel);
|
||||
bool changed = false;
|
||||
withWriteLock([&] {
|
||||
if (_inputLevel != level) {
|
||||
_inputLevel = level;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit inputLevelChanged(level);
|
||||
}
|
||||
}
|
||||
|
||||
QString Audio::getContext() const {
|
||||
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
|
||||
return resultWithReadLock<QString>([&] {
|
||||
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::onContextChanged() {
|
||||
bool changed = false;
|
||||
bool isHMD = qApp->isHMDMode();
|
||||
if (_contextIsHMD != isHMD) {
|
||||
_contextIsHMD = isHMD;
|
||||
emit contextChanged(getContext());
|
||||
withWriteLock([&] {
|
||||
if (_contextIsHMD != isHMD) {
|
||||
_contextIsHMD = isHMD;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::setReverb(bool enable) {
|
||||
DependencyManager::get<AudioClient>()->setReverb(enable);
|
||||
withWriteLock([&] {
|
||||
DependencyManager::get<AudioClient>()->setReverb(enable);
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::setReverbOptions(const AudioEffectOptions* options) {
|
||||
DependencyManager::get<AudioClient>()->setReverbOptions(options);
|
||||
withWriteLock([&] {
|
||||
DependencyManager::get<AudioClient>()->setReverbOptions(options);
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||
_devices.chooseInputDevice(device, isHMD);
|
||||
withWriteLock([&] {
|
||||
_devices.chooseInputDevice(device, isHMD);
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||
_devices.chooseOutputDevice(device, isHMD);
|
||||
withWriteLock([&] {
|
||||
_devices.chooseOutputDevice(device, isHMD);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
#include "AudioEffectOptions.h"
|
||||
#include "SettingHandle.h"
|
||||
#include "AudioFileWav.h"
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
|
||||
namespace scripting {
|
||||
|
||||
class Audio : public AudioScriptingInterface {
|
||||
class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
|
@ -40,16 +41,13 @@ public:
|
|||
|
||||
virtual ~Audio() {}
|
||||
|
||||
bool isMuted() const { return _isMuted; }
|
||||
bool noiseReductionEnabled() const { return _enableNoiseReduction; }
|
||||
float getInputVolume() const { return _inputVolume; }
|
||||
float getInputLevel() const { return _inputLevel; }
|
||||
bool isMuted() const;
|
||||
bool noiseReductionEnabled() const;
|
||||
float getInputVolume() const;
|
||||
float getInputLevel() const;
|
||||
QString getContext() const;
|
||||
|
||||
void setMuted(bool muted);
|
||||
void enableNoiseReduction(bool enable);
|
||||
void showMicMeter(bool show);
|
||||
void setInputVolume(float volume);
|
||||
|
||||
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
|
@ -72,9 +70,9 @@ public slots:
|
|||
void onContextChanged();
|
||||
|
||||
private slots:
|
||||
void onMutedChanged();
|
||||
void onNoiseReductionChanged();
|
||||
void onInputVolumeChanged(float volume);
|
||||
void setMuted(bool muted);
|
||||
void enableNoiseReduction(bool enable);
|
||||
void setInputVolume(float volume);
|
||||
void onInputLoudnessChanged(float loudness);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -188,7 +188,7 @@ void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD
|
|||
|
||||
for (auto i = 0; i < _devices.size(); ++i) {
|
||||
std::shared_ptr<AudioDevice> device = _devices[i];
|
||||
bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
|
||||
bool& isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
|
||||
if (isSelected && device->info != selectedDevice) {
|
||||
isSelected = false;
|
||||
} else if (device->info == selectedDevice) {
|
||||
|
@ -259,7 +259,7 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
|
|||
|
||||
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
|
||||
for (bool isHMD : {false, true}) {
|
||||
auto &backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
||||
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
||||
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
|
||||
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||
selectedDevice = deviceInfo;
|
||||
|
@ -278,7 +278,7 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
|
|||
|
||||
for (bool isHMD : {false, true}) {
|
||||
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||
bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
|
||||
bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
|
||||
|
||||
if (!selectedDevice.isNull()) {
|
||||
isSelected = (device.info == selectedDevice);
|
||||
|
|
|
@ -531,8 +531,8 @@ private slots:
|
|||
signals:
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to domain that
|
||||
* isn't running.
|
||||
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to a domain
|
||||
* that isn't running.
|
||||
* @function Window.domainChanged
|
||||
* @param {string} domainURL - The domain's URL.
|
||||
* @returns {Signal}
|
||||
|
|
|
@ -757,7 +757,7 @@ void AudioClient::Gate::flush() {
|
|||
|
||||
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
|
||||
if (!_muted) {
|
||||
toggleMute();
|
||||
setMuted(true);
|
||||
|
||||
// have the audio scripting interface emit a signal to say we were muted by the mixer
|
||||
emit mutedByMixer();
|
||||
|
@ -1384,15 +1384,21 @@ void AudioClient::sendMuteEnvironmentPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioClient::toggleMute() {
|
||||
_muted = !_muted;
|
||||
emit muteToggled();
|
||||
void AudioClient::setMuted(bool muted, bool emitSignal) {
|
||||
if (_muted != muted) {
|
||||
_muted = muted;
|
||||
if (emitSignal) {
|
||||
emit muteToggled(_muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::setNoiseReduction(bool enable) {
|
||||
void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
|
||||
if (_isNoiseGateEnabled != enable) {
|
||||
_isNoiseGateEnabled = enable;
|
||||
emit noiseReductionChanged();
|
||||
if (emitSignal) {
|
||||
emit noiseReductionChanged(_isNoiseGateEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2018,9 +2024,11 @@ void AudioClient::startThread() {
|
|||
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
|
||||
}
|
||||
|
||||
void AudioClient::setInputVolume(float volume) {
|
||||
void AudioClient::setInputVolume(float volume, bool emitSignal) {
|
||||
if (_audioInput && volume != (float)_audioInput->volume()) {
|
||||
_audioInput->setVolume(volume);
|
||||
emit inputVolumeChanged(_audioInput->volume());
|
||||
if (emitSignal) {
|
||||
emit inputVolumeChanged(_audioInput->volume());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,13 +189,13 @@ public slots:
|
|||
void reset();
|
||||
void audioMixerKilled();
|
||||
|
||||
void toggleMute();
|
||||
void setMuted(bool muted, bool emitSignal = true);
|
||||
bool isMuted() { return _muted; }
|
||||
|
||||
virtual bool setIsStereoInput(bool stereo) override;
|
||||
virtual bool isStereoInput() override { return _isStereoInput; }
|
||||
|
||||
void setNoiseReduction(bool isNoiseGateEnabled);
|
||||
void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
|
||||
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
|
||||
|
||||
bool getLocalEcho() { return _shouldEchoLocally; }
|
||||
|
@ -218,7 +218,7 @@ public slots:
|
|||
bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
|
||||
|
||||
float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; }
|
||||
void setInputVolume(float volume);
|
||||
void setInputVolume(float volume, bool emitSignal = true);
|
||||
void setReverb(bool reverb);
|
||||
void setReverbOptions(const AudioEffectOptions* options);
|
||||
|
||||
|
@ -229,8 +229,8 @@ public slots:
|
|||
|
||||
signals:
|
||||
void inputVolumeChanged(float volume);
|
||||
void muteToggled();
|
||||
void noiseReductionChanged();
|
||||
void muteToggled(bool muted);
|
||||
void noiseReductionChanged(bool noiseReductionEnabled);
|
||||
void mutedByMixer();
|
||||
void inputReceived(const QByteArray& inputSamples);
|
||||
void inputLoudnessChanged(float loudness);
|
||||
|
|
|
@ -278,7 +278,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
|
|||
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
|
||||
// and the current rendering load)
|
||||
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [this](QQmlContext* surfaceContext) {
|
||||
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) {
|
||||
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
|
||||
surfaceContext->setContextProperty("desktop", QVariant());
|
||||
// Let us interact with the keyboard
|
||||
|
|
|
@ -120,7 +120,6 @@ public:
|
|||
void markAsChangedOnServer();
|
||||
quint64 getLastChangedOnServer() const;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -695,8 +695,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* @typedef {object} Entities.EntityProperties-Material
|
||||
* @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append <code>?name</code> to the URL, the
|
||||
* material with that name in the {@link MaterialResource} will be applied to the entity. <br />
|
||||
* Alternatively, set the property value to <code>"userData"</code> to use the {@link Entities.EntityProperties|userData}
|
||||
* entity property to live edit the material resource values.
|
||||
* Alternatively, set the property value to <code>"materialData"</code> to use the <code>materialData</code> property
|
||||
* for the {@link MaterialResource} values.
|
||||
* @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is
|
||||
* applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of
|
||||
* <code>0</code>.
|
||||
|
@ -710,6 +710,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* <code>{ x: 0, y: 0 }</code> – <code>{ x: 1, y: 1 }</code>.
|
||||
* @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space.
|
||||
* @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees.
|
||||
* @property {string} materialData="" - Used to store {@link MaterialResource} data as a JSON string. You can use
|
||||
* <code>JSON.parse()</code> to parse the string into a JavaScript object which you can manipulate the properties of, and
|
||||
* use <code>JSON.stringify()</code> to convert the object into a string to put in the property.
|
||||
* @example <caption>Color a sphere using a Material entity.</caption>
|
||||
* var entityID = Entities.addEntity({
|
||||
* type: "Sphere",
|
||||
|
@ -722,13 +725,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* var materialID = Entities.addEntity({
|
||||
* type: "Material",
|
||||
* parentID: entityID,
|
||||
* materialURL: "userData",
|
||||
* materialURL: "materialData",
|
||||
* priority: 1,
|
||||
* userData: JSON.stringify({
|
||||
* materialData: JSON.stringify({
|
||||
* materialVersion: 1,
|
||||
* materials: {
|
||||
* // Can only set albedo on a Shape entity.
|
||||
* // Value overrides entity's "color" property.
|
||||
* albedo: [1.0, 0, 0]
|
||||
* albedo: [1.0, 1.0, 0] // Yellow
|
||||
* }
|
||||
* }),
|
||||
* });
|
||||
|
|
|
@ -88,7 +88,6 @@ public:
|
|||
|
||||
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
|
||||
// own definition. Implement these to allow your octree based server to support editing
|
||||
virtual bool getWantSVOfileVersions() const override { return true; }
|
||||
virtual PacketType expectedDataPacketType() const override { return PacketType::EntityData; }
|
||||
virtual bool handlesEditPacketType(PacketType packetType) const override;
|
||||
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties);
|
||||
|
@ -107,11 +106,7 @@ public:
|
|||
|
||||
virtual bool rootElementHasData() const override { return true; }
|
||||
|
||||
// the root at least needs to store the number of entities in the packet/buffer
|
||||
virtual int minimumRequiredRootDataBytes() const override { return sizeof(uint16_t); }
|
||||
virtual bool suppressEmptySubtrees() const override { return false; }
|
||||
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override;
|
||||
virtual bool mustIncludeAllChildData() const override { return false; }
|
||||
|
||||
virtual void update() override { update(true); }
|
||||
|
||||
|
|
|
@ -67,455 +67,6 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) {
|
||||
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
assert(entityNodeData);
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
// Check to see if this element yet has encode data... if it doesn't create it
|
||||
if (!extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData { new EntityTreeElementExtraEncodeData() };
|
||||
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0);
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
EntityTreeElementPointer child = getChildAtIndex(i);
|
||||
if (!child) {
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed
|
||||
} else {
|
||||
if (child->hasEntities()) {
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING
|
||||
} else {
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachEntity([&](EntityItemPointer entity) {
|
||||
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
|
||||
});
|
||||
|
||||
// TODO: some of these inserts might be redundant!!!
|
||||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const {
|
||||
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
assert(entityNodeData);
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
||||
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
|
||||
|
||||
// If we haven't completely sent the child yet, then we should include it
|
||||
return !childCompleted;
|
||||
}
|
||||
|
||||
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
|
||||
// the child data for this element
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
|
||||
EntityTreeElementPointer childElement = getChildAtIndex(childIndex);
|
||||
if (childElement->alreadyFullyEncoded(params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // if we don't know otherwise than recurse!
|
||||
}
|
||||
|
||||
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
assert(entityNodeData);
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
||||
// If we know that ALL subtrees below us have already been recursed, then we don't
|
||||
// need to recurse this child.
|
||||
return entityTreeElementExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const {
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
assert(entityNodeData);
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
||||
if (childAppendState == OctreeElement::COMPLETED) {
|
||||
entityTreeElementExtraEncodeData->childCompleted[childIndex] = true;
|
||||
}
|
||||
} else {
|
||||
assert(false); // this shouldn't happen!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const {
|
||||
const bool wantDebug = false;
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
|
||||
}
|
||||
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
assert(entityNodeData);
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
assert(extraEncodeData->contains(this));
|
||||
|
||||
EntityTreeElementExtraEncodeDataPointer thisExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
||||
// Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion()
|
||||
// which means, it's possible that our parent element hasn't finished encoding OUR data... so
|
||||
// in this case, our children may be complete, and we should clean up their encode data...
|
||||
// but not necessarily cleanup our own encode data...
|
||||
//
|
||||
// If we're really complete here's what must be true...
|
||||
// 1) our own data must be complete
|
||||
// 2) the data for all our immediate children must be complete.
|
||||
// However, the following might also be the case...
|
||||
// 1) it's ok for our child trees to not yet be fully encoded/complete...
|
||||
// SO LONG AS... the our child's node is in the bag ready for encoding
|
||||
|
||||
bool someChildTreeNotComplete = false;
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
EntityTreeElementPointer childElement = getChildAtIndex(i);
|
||||
if (childElement) {
|
||||
|
||||
// why would this ever fail???
|
||||
// If we've encoding this element before... but we're coming back a second time in an attempt to
|
||||
// encode our parent... this might happen.
|
||||
if (extraEncodeData->contains(childElement.get())) {
|
||||
EntityTreeElementExtraEncodeDataPointer childExtraEncodeData
|
||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[childElement.get()]);
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << "checking child: " << childElement->_cube;
|
||||
qCDebug(entities) << " childElement->isLeaf():" << childElement->isLeaf();
|
||||
qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
|
||||
}
|
||||
childExtraEncodeData->subtreeCompleted = true;
|
||||
}
|
||||
|
||||
if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) {
|
||||
someChildTreeNotComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << "for this element: " << _cube;
|
||||
qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
|
||||
if (thisExtraEncodeData->subtreeCompleted) {
|
||||
qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const {
|
||||
|
||||
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
|
||||
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(params.nodeData);
|
||||
Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null");
|
||||
|
||||
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
|
||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL;
|
||||
bool hadElementExtraData = false;
|
||||
if (extraEncodeData && extraEncodeData->contains(this)) {
|
||||
entityTreeElementExtraEncodeData =
|
||||
std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
hadElementExtraData = true;
|
||||
} else {
|
||||
// if there wasn't one already, then create one
|
||||
entityTreeElementExtraEncodeData.reset(new EntityTreeElementExtraEncodeData());
|
||||
entityTreeElementExtraEncodeData->elementCompleted = !hasContent();
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
EntityTreeElementPointer child = getChildAtIndex(i);
|
||||
if (!child) {
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed
|
||||
} else {
|
||||
if (child->hasEntities()) {
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = false;
|
||||
} else {
|
||||
// if the child doesn't have enities, it is completed
|
||||
entityTreeElementExtraEncodeData->childCompleted[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
forEachEntity([&](EntityItemPointer entity) {
|
||||
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
|
||||
});
|
||||
}
|
||||
|
||||
//assert(extraEncodeData);
|
||||
//assert(extraEncodeData->contains(this));
|
||||
//entityTreeElementExtraEncodeData = std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||
|
||||
LevelDetails elementLevel = packetData->startLevel();
|
||||
|
||||
// write our entities out... first determine which of the entities are in view based on our params
|
||||
uint16_t numberOfEntities = 0;
|
||||
uint16_t actualNumberOfEntities = 0;
|
||||
int numberOfEntitiesOffset = 0;
|
||||
withReadLock([&] {
|
||||
QVector<uint16_t> indexesOfEntitiesToInclude;
|
||||
|
||||
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// need to handle the case where our sibling elements need encoding but we don't.
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
|
||||
|
||||
// we have an EntityNodeData instance
|
||||
// so we should assume that means we might have JSON filters to check
|
||||
auto jsonFilters = entityNodeData->getJSONParameters();
|
||||
|
||||
|
||||
for (uint16_t i = 0; i < _entityItems.size(); i++) {
|
||||
EntityItemPointer entity = _entityItems[i];
|
||||
bool includeThisEntity = true;
|
||||
|
||||
if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) {
|
||||
includeThisEntity = false;
|
||||
}
|
||||
|
||||
// if this entity has been updated since our last full send and there are json filters, check them
|
||||
if (includeThisEntity && !jsonFilters.isEmpty()) {
|
||||
|
||||
// if params include JSON filters, check if this entity matches
|
||||
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
|
||||
|
||||
if (entityMatchesFilters) {
|
||||
// make sure this entity is in the set of entities sent last frame
|
||||
entityNodeData->insertSentFilteredEntity(entity->getID());
|
||||
} else if (entityNodeData->sentFilteredEntity(entity->getID())) {
|
||||
// this entity matched in the previous frame - we send it still so the client realizes it just
|
||||
// fell outside of their filter
|
||||
entityNodeData->removeSentFilteredEntity(entity->getID());
|
||||
} else if (!entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
|
||||
// we don't send this entity because
|
||||
// (1) it didn't match our filter
|
||||
// (2) it didn't match our filter last frame
|
||||
// (3) it isn't one the JSON query flags told us we should still include
|
||||
includeThisEntity = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeThisEntity && hadElementExtraData) {
|
||||
includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
|
||||
}
|
||||
|
||||
// we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum
|
||||
// which can sometimes not be the case when JSON filters are sent
|
||||
if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) {
|
||||
|
||||
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of
|
||||
// simulation changing what's visible. consider the case where the entity contains an angular velocity
|
||||
// the entity may not be in view and then in view a frame later, let the client side handle it's view
|
||||
// frustum culling on rendering.
|
||||
bool success;
|
||||
AACube entityCube = entity->getQueryAACube(success);
|
||||
if (!success || !params.viewFrustum.cubeIntersectsKeyhole(entityCube)) {
|
||||
includeThisEntity = false; // out of view, don't include it
|
||||
} else {
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
success = true;
|
||||
// we can't cull a parent-entity by its dimensions because the child may be larger. we need to
|
||||
// avoid sending details about a child but not the parent. the parent's queryAACube should have
|
||||
// been adjusted to encompass the queryAACube of the child.
|
||||
AABox entityBounds = entity->hasChildren() ? AABox(entityCube) : entity->getAABox(success);
|
||||
if (!success) {
|
||||
// if this entity is a child of an avatar, the entity-server wont be able to determine its
|
||||
// AABox. If this happens, fall back to the queryAACube.
|
||||
entityBounds = AABox(entityCube);
|
||||
}
|
||||
auto renderAccuracy = calculateRenderAccuracy(params.viewFrustum.getPosition(),
|
||||
entityBounds,
|
||||
params.octreeElementSizeScale,
|
||||
params.boundaryLevelAdjust);
|
||||
if (renderAccuracy <= 0.0f) {
|
||||
includeThisEntity = false; // too small, don't include it
|
||||
|
||||
#ifdef WANT_LOD_DEBUGGING
|
||||
qCDebug(entities) << "skipping entity - TOO SMALL - \n"
|
||||
<< "......id:" << entity->getID() << "\n"
|
||||
<< "....name:" << entity->getName() << "\n"
|
||||
<< "..bounds:" << entityBounds << "\n"
|
||||
<< "....cell:" << getAACube();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeThisEntity) {
|
||||
#ifdef WANT_LOD_DEBUGGING
|
||||
qCDebug(entities) << "including entity - \n"
|
||||
<< "......id:" << entity->getID() << "\n"
|
||||
<< "....name:" << entity->getName() << "\n"
|
||||
<< "....cell:" << getAACube();
|
||||
#endif
|
||||
indexesOfEntitiesToInclude << i;
|
||||
numberOfEntities++;
|
||||
} else {
|
||||
// if the extra data included this entity, and we've decided to not include the entity, then
|
||||
// we can treat it as if it was completed.
|
||||
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numberOfEntitiesOffset = packetData->getUncompressedByteOffset();
|
||||
bool successAppendEntityCount = packetData->appendValue(numberOfEntities);
|
||||
|
||||
if (successAppendEntityCount) {
|
||||
foreach(uint16_t i, indexesOfEntitiesToInclude) {
|
||||
EntityItemPointer entity = _entityItems[i];
|
||||
LevelDetails entityLevel = packetData->startLevel();
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData,
|
||||
params, entityTreeElementExtraEncodeData);
|
||||
|
||||
// If none of this entity data was able to be appended, then discard it
|
||||
// and don't include it in our entity count
|
||||
if (appendEntityState == OctreeElement::NONE) {
|
||||
packetData->discardLevel(entityLevel);
|
||||
} else {
|
||||
// If either ALL or some of it got appended, then end the level (commit it)
|
||||
// and include the entity in our final count of entities
|
||||
packetData->endLevel(entityLevel);
|
||||
actualNumberOfEntities++;
|
||||
|
||||
// If the entity item got completely appended, then we can remove it from the extra encode data
|
||||
if (appendEntityState == OctreeElement::COMPLETED) {
|
||||
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
|
||||
}
|
||||
}
|
||||
|
||||
// If any part of the entity items didn't fit, then the element is considered partial
|
||||
// NOTE: if the entity item didn't fit or only partially fit, then the entity item should have
|
||||
// added itself to the extra encode data.
|
||||
if (appendEntityState != OctreeElement::COMPLETED) {
|
||||
appendElementState = OctreeElement::PARTIAL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state
|
||||
appendElementState = OctreeElement::NONE;
|
||||
}
|
||||
});
|
||||
|
||||
// If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData
|
||||
// then we need to do some additional processing, namely make sure our extraEncodeData is up to date for
|
||||
// this octree element.
|
||||
if (extraEncodeData && entityTreeElementExtraEncodeData) {
|
||||
|
||||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// Only our parent can remove our extra data in these cases and only after it knows that all of its
|
||||
// children have been encoded.
|
||||
//
|
||||
// FIXME -- this comment seems wrong....
|
||||
//
|
||||
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
|
||||
// since that will signal that the entire element needs to be encoded on the next attempt
|
||||
if (appendElementState == OctreeElement::NONE) {
|
||||
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted && entityTreeElementExtraEncodeData->entities.size() == 0) {
|
||||
// TODO: we used to delete the extra encode data here. But changing the logic around
|
||||
// this is now a dead code branch. Clean this up!
|
||||
} else {
|
||||
// TODO: some of these inserts might be redundant!!!
|
||||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
} else {
|
||||
|
||||
// If we weren't previously completed, check to see if we are
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
// If all of our items have been encoded, then we are complete as an element.
|
||||
if (entityTreeElementExtraEncodeData->entities.size() == 0) {
|
||||
entityTreeElementExtraEncodeData->elementCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: some of these inserts might be redundant!!!
|
||||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if no entities at all were able to fit
|
||||
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
|
||||
|
||||
// If we wrote fewer entities than we expected, update the number of entities in our packet
|
||||
bool successUpdateEntityCount = true;
|
||||
if (numberOfEntities != actualNumberOfEntities) {
|
||||
successUpdateEntityCount = packetData->updatePriorBytes(numberOfEntitiesOffset,
|
||||
(const unsigned char*)&actualNumberOfEntities, sizeof(actualNumberOfEntities));
|
||||
}
|
||||
|
||||
// If we weren't able to update our entity count, or we couldn't fit any entities, then
|
||||
// we should discard our element and return a result of NONE
|
||||
if (!successUpdateEntityCount) {
|
||||
packetData->discardLevel(elementLevel);
|
||||
appendElementState = OctreeElement::NONE;
|
||||
} else {
|
||||
if (noEntitiesFit) {
|
||||
//appendElementState = OctreeElement::PARTIAL;
|
||||
packetData->discardLevel(elementLevel);
|
||||
appendElementState = OctreeElement::NONE;
|
||||
} else {
|
||||
packetData->endLevel(elementLevel);
|
||||
}
|
||||
}
|
||||
return appendElementState;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::containsEntityBounds(EntityItemPointer entity) const {
|
||||
bool success;
|
||||
auto queryCube = entity->getQueryAACube(success);
|
||||
|
|
|
@ -121,17 +121,6 @@ public:
|
|||
virtual bool requiresSplit() const override { return false; }
|
||||
|
||||
virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const override;
|
||||
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) override;
|
||||
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const override;
|
||||
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const override;
|
||||
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const override;
|
||||
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const override;
|
||||
|
||||
bool alreadyFullyEncoded(EncodeBitstreamParams& params) const;
|
||||
|
||||
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
|
||||
virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const override;
|
||||
|
||||
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
|
||||
/// from the network.
|
||||
|
|
|
@ -186,7 +186,6 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_IS_SPOTLIGHT;
|
||||
|
|
|
@ -128,7 +128,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
|
|
|
@ -26,7 +26,6 @@ class LineEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -89,8 +89,6 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_MATERIAL_URL;
|
||||
|
|
|
@ -32,7 +32,6 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -144,7 +144,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
@ -721,4 +720,4 @@ bool ModelEntityItem::isAnimatingSomething() const {
|
|||
_animationProperties.getRunning() &&
|
||||
(_animationProperties.getFPS() != 0.0f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -503,8 +503,6 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
|
|
@ -216,8 +216,6 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
|
|
|
@ -26,7 +26,6 @@ class PolyLineEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -183,8 +183,6 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_VOXEL_VOLUME_SIZE;
|
||||
|
|
|
@ -26,7 +26,6 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -188,8 +188,6 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SHAPE;
|
||||
|
|
|
@ -98,8 +98,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_TEXT;
|
||||
|
|
|
@ -30,7 +30,6 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -83,8 +83,6 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SOURCE_URL;
|
||||
|
|
|
@ -29,7 +29,6 @@ public:
|
|||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -191,8 +191,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ public:
|
|||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
virtual bool setSubClassProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
|
|
|
@ -194,7 +194,7 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
|
|||
} else if (key == "emissiveMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setEmissiveMap(value.toString());
|
||||
material->setEmissiveMap(baseUrl.resolved(value.toString()));
|
||||
}
|
||||
} else if (key == "albedoMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
|
|
|
@ -34,15 +34,16 @@ const QString GET_PLACE = "/api/v1/places/%1";
|
|||
*
|
||||
* @namespace location
|
||||
* @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not
|
||||
* connected to the domain.
|
||||
* connected to the domain or are in a serverless domain.
|
||||
* <em>Read-only.</em>
|
||||
* @property {Uuid} domainId - Synonym for <code>domainId</code>. <em>Read-only.</em> <strong>Deprecated:</strong> This property
|
||||
* is deprecated and will soon be removed.
|
||||
* @property {string} hostname - The name of the domain for your current metaverse address (e.g., <code>"AvatarIsland"</code>,
|
||||
* <code>localhost</code>, or an IP address).
|
||||
* <code>localhost</code>, or an IP address). Is blank if you're in a serverless domain.
|
||||
* <em>Read-only.</em>
|
||||
* @property {string} href - Your current metaverse address (e.g., <code>"hifi://avatarisland/15,-10,26/0,0,0,1"</code>)
|
||||
* regardless of whether or not you're connected to the domain.
|
||||
* regardless of whether or not you're connected to the domain. Starts with <code>"file:///"</code> if you're in a
|
||||
* serverless domain.
|
||||
* <em>Read-only.</em>
|
||||
* @property {boolean} isConnected - <code>true</code> if you're connected to the domain in your current <code>href</code>
|
||||
* metaverse address, otherwise <code>false</code>.
|
||||
|
|
94
libraries/networking/src/HMACAuth.cpp
Normal file
94
libraries/networking/src/HMACAuth.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// HMACAuth.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Simon Walton on 3/19/2018.
|
||||
// Copyright 2018 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 <openssl/opensslv.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#include "HMACAuth.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000
|
||||
HMACAuth::HMACAuth(AuthMethod authMethod)
|
||||
: _hmacContext(HMAC_CTX_new())
|
||||
, _authMethod(authMethod) { }
|
||||
|
||||
HMACAuth::~HMACAuth()
|
||||
{
|
||||
HMAC_CTX_free(_hmacContext);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
HMACAuth::HMACAuth(AuthMethod authMethod)
|
||||
: _hmacContext(new HMAC_CTX())
|
||||
, _authMethod(authMethod) {
|
||||
HMAC_CTX_init(_hmacContext);
|
||||
}
|
||||
|
||||
HMACAuth::~HMACAuth() {
|
||||
HMAC_CTX_cleanup(_hmacContext);
|
||||
delete _hmacContext;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool HMACAuth::setKey(const char* keyValue, int keyLen) {
|
||||
const EVP_MD* sslStruct = nullptr;
|
||||
|
||||
switch (_authMethod) {
|
||||
case MD5:
|
||||
sslStruct = EVP_md5();
|
||||
break;
|
||||
|
||||
case SHA1:
|
||||
sslStruct = EVP_sha1();
|
||||
break;
|
||||
|
||||
case SHA224:
|
||||
sslStruct = EVP_sha224();
|
||||
break;
|
||||
|
||||
case SHA256:
|
||||
sslStruct = EVP_sha256();
|
||||
break;
|
||||
|
||||
case RIPEMD160:
|
||||
sslStruct = EVP_ripemd160();
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
QMutexLocker lock(&_lock);
|
||||
return (bool) HMAC_Init_ex(_hmacContext, keyValue, keyLen, sslStruct, nullptr);
|
||||
}
|
||||
|
||||
bool HMACAuth::setKey(const QUuid& uidKey) {
|
||||
const QByteArray rfcBytes(uidKey.toRfc4122());
|
||||
return setKey(rfcBytes.constData(), rfcBytes.length());
|
||||
}
|
||||
|
||||
bool HMACAuth::addData(const char* data, int dataLen) {
|
||||
QMutexLocker lock(&_lock);
|
||||
return (bool) HMAC_Update(_hmacContext, reinterpret_cast<const unsigned char*>(data), dataLen);
|
||||
}
|
||||
|
||||
HMACAuth::HMACHash HMACAuth::result() {
|
||||
HMACHash hashValue(EVP_MAX_MD_SIZE);
|
||||
unsigned int hashLen;
|
||||
QMutexLocker lock(&_lock);
|
||||
HMAC_Final(_hmacContext, &hashValue[0], &hashLen);
|
||||
hashValue.resize((size_t) hashLen);
|
||||
// Clear state for possible reuse.
|
||||
HMAC_Init_ex(_hmacContext, nullptr, 0, nullptr, nullptr);
|
||||
return hashValue;
|
||||
}
|
40
libraries/networking/src/HMACAuth.h
Normal file
40
libraries/networking/src/HMACAuth.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// HMACAuth.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Simon Walton on 3/19/2018.
|
||||
// Copyright 2018 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_HMACAuth_h
|
||||
#define hifi_HMACAuth_h
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <QtCore/QMutex>
|
||||
|
||||
class QUuid;
|
||||
|
||||
class HMACAuth {
|
||||
public:
|
||||
enum AuthMethod { MD5, SHA1, SHA224, SHA256, RIPEMD160 };
|
||||
using HMACHash = std::vector<unsigned char>;
|
||||
|
||||
explicit HMACAuth(AuthMethod authMethod = MD5);
|
||||
~HMACAuth();
|
||||
|
||||
bool setKey(const char* keyValue, int keyLen);
|
||||
bool setKey(const QUuid& uidKey);
|
||||
bool addData(const char* data, int dataLen);
|
||||
HMACHash result();
|
||||
|
||||
private:
|
||||
QMutex _lock;
|
||||
struct hmac_ctx_st* _hmacContext;
|
||||
AuthMethod _authMethod;
|
||||
};
|
||||
|
||||
#endif // hifi_HMACAuth_h
|
|
@ -36,6 +36,7 @@
|
|||
#include "HifiSockAddr.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "udt/Packet.h"
|
||||
#include "HMACAuth.h"
|
||||
|
||||
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
|
||||
|
||||
|
@ -314,7 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
|
|||
if (verifiedPacket && !ignoreVerification) {
|
||||
|
||||
QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet);
|
||||
QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret());
|
||||
QByteArray expectedHash = NLPacket::hashForPacketAndHMAC(packet, sourceNode->getAuthenticateHash());
|
||||
|
||||
// check if the md5 hash in the header matches the hash we would expect
|
||||
if (packetHeaderHash != expectedHash) {
|
||||
|
@ -354,15 +355,15 @@ void LimitedNodeList::collectPacketStats(const NLPacket& packet) {
|
|||
_numCollectedBytes += packet.getDataSize();
|
||||
}
|
||||
|
||||
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) {
|
||||
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) {
|
||||
if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) {
|
||||
packet.writeSourceID(getSessionUUID());
|
||||
}
|
||||
|
||||
if (!connectionSecret.isNull()
|
||||
if (hmacAuth
|
||||
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
|
||||
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
|
||||
packet.writeVerificationHashGivenSecret(connectionSecret);
|
||||
packet.writeVerificationHash(*hmacAuth);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,17 +379,17 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node&
|
|||
emit dataSent(destinationNode.getType(), packet.getDataSize());
|
||||
destinationNode.recordBytesSent(packet.getDataSize());
|
||||
|
||||
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret());
|
||||
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), &destinationNode.getAuthenticateHash());
|
||||
}
|
||||
|
||||
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret) {
|
||||
HMACAuth* hmacAuth) {
|
||||
Q_ASSERT(!packet.isPartOfMessage());
|
||||
Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket",
|
||||
"Trying to send a reliable packet unreliably.");
|
||||
|
||||
collectPacketStats(packet);
|
||||
fillPacketHeader(packet, connectionSecret);
|
||||
fillPacketHeader(packet, hmacAuth);
|
||||
|
||||
return _nodeSocket.writePacket(packet, sockAddr);
|
||||
}
|
||||
|
@ -401,7 +402,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
|
|||
emit dataSent(destinationNode.getType(), packet->getDataSize());
|
||||
destinationNode.recordBytesSent(packet->getDataSize());
|
||||
|
||||
return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret());
|
||||
return sendPacket(std::move(packet), *activeSocket, &destinationNode.getAuthenticateHash());
|
||||
} else {
|
||||
qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending";
|
||||
return ERROR_SENDING_PACKET_BYTES;
|
||||
|
@ -409,18 +410,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
|
|||
}
|
||||
|
||||
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret) {
|
||||
HMACAuth* hmacAuth) {
|
||||
Q_ASSERT(!packet->isPartOfMessage());
|
||||
if (packet->isReliable()) {
|
||||
collectPacketStats(*packet);
|
||||
fillPacketHeader(*packet, connectionSecret);
|
||||
fillPacketHeader(*packet, hmacAuth);
|
||||
|
||||
auto size = packet->getDataSize();
|
||||
_nodeSocket.writePacket(std::move(packet), sockAddr);
|
||||
|
||||
return size;
|
||||
} else {
|
||||
return sendUnreliablePacket(*packet, sockAddr, connectionSecret);
|
||||
return sendUnreliablePacket(*packet, sockAddr, hmacAuth);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,13 +430,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
|
|||
|
||||
if (activeSocket) {
|
||||
qint64 bytesSent = 0;
|
||||
auto connectionSecret = destinationNode.getConnectionSecret();
|
||||
auto& connectionHash = destinationNode.getAuthenticateHash();
|
||||
|
||||
// close the last packet in the list
|
||||
packetList.closeCurrentPacket();
|
||||
|
||||
while (!packetList._packets.empty()) {
|
||||
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket, connectionSecret);
|
||||
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket,
|
||||
&connectionHash);
|
||||
}
|
||||
|
||||
emit dataSent(destinationNode.getType(), bytesSent);
|
||||
|
@ -448,14 +450,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
|
|||
}
|
||||
|
||||
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret) {
|
||||
HMACAuth* hmacAuth) {
|
||||
qint64 bytesSent = 0;
|
||||
|
||||
// close the last packet in the list
|
||||
packetList.closeCurrentPacket();
|
||||
|
||||
while (!packetList._packets.empty()) {
|
||||
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, connectionSecret);
|
||||
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, hmacAuth);
|
||||
}
|
||||
|
||||
return bytesSent;
|
||||
|
@ -483,7 +485,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList,
|
|||
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
|
||||
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
|
||||
collectPacketStats(*nlPacket);
|
||||
fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret());
|
||||
fillPacketHeader(*nlPacket, &destinationNode.getAuthenticateHash());
|
||||
}
|
||||
|
||||
return _nodeSocket.writePacketList(std::move(packetList), *activeSocket);
|
||||
|
@ -506,7 +508,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
|
|||
auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket()
|
||||
: overridenSockAddr;
|
||||
|
||||
return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret());
|
||||
return sendPacket(std::move(packet), destinationSockAddr, &destinationNode.getAuthenticateHash());
|
||||
}
|
||||
|
||||
int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
@ -569,9 +571,10 @@ void LimitedNodeList::reset() {
|
|||
|
||||
// we need to make sure any socket connections are gone so wait on that here
|
||||
_nodeSocket.clearConnections();
|
||||
_connectionIDs.clear();
|
||||
}
|
||||
|
||||
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
|
||||
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) {
|
||||
QReadLocker readLocker(&_nodeMutex);
|
||||
|
||||
NodeHash::iterator it = _nodeHash.find(nodeUUID);
|
||||
|
@ -585,7 +588,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
|
|||
_nodeHash.unsafe_erase(it);
|
||||
}
|
||||
|
||||
handleNodeKill(matchingNode);
|
||||
handleNodeKill(matchingNode, newConnectionID);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -600,7 +603,7 @@ void LimitedNodeList::processKillNode(ReceivedMessage& message) {
|
|||
killNodeWithUUID(nodeUUID);
|
||||
}
|
||||
|
||||
void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
|
||||
void LimitedNodeList::handleNodeKill(const SharedNodePointer& node, ConnectionID nextConnectionID) {
|
||||
qCDebug(networking) << "Killed" << *node;
|
||||
node->stopPingTimer();
|
||||
emit nodeKilled(node);
|
||||
|
@ -608,6 +611,15 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
|
|||
if (auto activeSocket = node->getActiveSocket()) {
|
||||
_nodeSocket.cleanupConnection(*activeSocket);
|
||||
}
|
||||
|
||||
auto it = _connectionIDs.find(node->getUUID());
|
||||
if (it != _connectionIDs.end()) {
|
||||
if (nextConnectionID == NULL_CONNECTION_ID) {
|
||||
it->second++;
|
||||
} else {
|
||||
it->second = nextConnectionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
||||
|
@ -629,6 +641,11 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
|||
|
||||
return matchingNode;
|
||||
} else {
|
||||
auto it = _connectionIDs.find(uuid);
|
||||
if (it == _connectionIDs.end()) {
|
||||
_connectionIDs[uuid] = INITIAL_CONNECTION_ID;
|
||||
}
|
||||
|
||||
// we didn't have this node, so add them
|
||||
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket);
|
||||
newNode->setIsReplicated(isReplicated);
|
||||
|
@ -703,13 +720,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(PingType_t pingType) {
|
||||
int packetSize = sizeof(PingType_t) + sizeof(quint64);
|
||||
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) {
|
||||
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t);
|
||||
|
||||
auto pingPacket = NLPacket::create(PacketType::Ping, packetSize);
|
||||
|
||||
pingPacket->writePrimitive(pingType);
|
||||
pingPacket->writePrimitive(usecTimestampNow());
|
||||
pingPacket->writePrimitive(_connectionIDs[nodeId]);
|
||||
|
||||
return pingPacket;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ const QHostAddress DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME = QHostAddress::Lo
|
|||
|
||||
const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
|
||||
|
||||
using ConnectionID = int64_t;
|
||||
const ConnectionID NULL_CONNECTION_ID { -1 };
|
||||
const ConnectionID INITIAL_CONNECTION_ID { 0 };
|
||||
|
||||
typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair;
|
||||
typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
|
||||
|
||||
|
@ -128,22 +132,20 @@ public:
|
|||
virtual QUuid getDomainUUID() const { assert(false); return QUuid(); }
|
||||
virtual HifiSockAddr getDomainSockAddr() const { assert(false); return HifiSockAddr(); }
|
||||
|
||||
// use sendUnreliablePacket to send an unrelaible packet (that you do not need to move)
|
||||
// use sendUnreliablePacket to send an unreliable packet (that you do not need to move)
|
||||
// either to a node (via its active socket) or to a manual sockaddr
|
||||
qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode);
|
||||
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
|
||||
|
||||
// use sendPacket to send a moved unreliable or reliable NL packet to a node's active socket or manual sockaddr
|
||||
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode);
|
||||
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
|
||||
|
||||
// use sendUnreliableUnorderedPacketList to unreliably send separate packets from the packet list
|
||||
// either to a node's active socket or to a manual sockaddr
|
||||
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode);
|
||||
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
HMACAuth* hmacAuth = nullptr);
|
||||
|
||||
// use sendPacketList to send reliable packet lists (ordered or unordered) to a node's active socket
|
||||
// or to a manual sock addr
|
||||
|
@ -180,7 +182,7 @@ public:
|
|||
void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond);
|
||||
void resetPacketStats();
|
||||
|
||||
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
||||
std::unique_ptr<NLPacket> constructPingPacket(const QUuid& nodeId, PingType_t pingType = PingType::Agnostic);
|
||||
std::unique_ptr<NLPacket> constructPingReplyPacket(ReceivedMessage& message);
|
||||
|
||||
static std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
||||
|
@ -319,7 +321,7 @@ public slots:
|
|||
void startSTUNPublicSocketUpdate();
|
||||
virtual void sendSTUNRequest();
|
||||
|
||||
bool killNodeWithUUID(const QUuid& nodeUUID);
|
||||
bool killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID = NULL_CONNECTION_ID);
|
||||
|
||||
signals:
|
||||
void dataSent(quint8 channelType, int bytes);
|
||||
|
@ -364,14 +366,14 @@ protected:
|
|||
qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
void collectPacketStats(const NLPacket& packet);
|
||||
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
|
||||
void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr);
|
||||
|
||||
void setLocalSocket(const HifiSockAddr& sockAddr);
|
||||
|
||||
bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode = nullptr);
|
||||
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);
|
||||
|
||||
void handleNodeKill(const SharedNodePointer& node);
|
||||
void handleNodeKill(const SharedNodePointer& node, ConnectionID newConnectionID = NULL_CONNECTION_ID);
|
||||
|
||||
void stopInitialSTUNUpdate(bool success);
|
||||
|
||||
|
@ -418,6 +420,7 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
std::unordered_map<QUuid, ConnectionID> _connectionIDs;
|
||||
|
||||
private slots:
|
||||
void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp);
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "NLPacket.h"
|
||||
|
||||
#include "HMACAuth.h"
|
||||
|
||||
int NLPacket::localHeaderSize(PacketType type) {
|
||||
bool nonSourced = PacketTypeEnum::getNonSourcedPackets().contains(type);
|
||||
bool nonVerified = PacketTypeEnum::getNonVerifiedPackets().contains(type);
|
||||
|
@ -149,18 +151,12 @@ QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) {
|
|||
return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH);
|
||||
}
|
||||
|
||||
QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) {
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
|
||||
QByteArray NLPacket::hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash) {
|
||||
int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
|
||||
+ NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH;
|
||||
|
||||
// add the packet payload and the connection UUID
|
||||
hash.addData(packet.getData() + offset, packet.getDataSize() - offset);
|
||||
hash.addData(connectionSecret.toRfc4122());
|
||||
|
||||
// return the hash
|
||||
return hash.result();
|
||||
auto hashResult { hash.result() };
|
||||
return QByteArray((const char*) hashResult.data(), (int) hashResult.size());
|
||||
}
|
||||
|
||||
void NLPacket::writeTypeAndVersion() {
|
||||
|
@ -212,13 +208,14 @@ void NLPacket::writeSourceID(const QUuid& sourceID) const {
|
|||
_sourceID = sourceID;
|
||||
}
|
||||
|
||||
void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const {
|
||||
void NLPacket::writeVerificationHash(HMACAuth& hmacAuth) const {
|
||||
Q_ASSERT(!PacketTypeEnum::getNonSourcedPackets().contains(_type) &&
|
||||
!PacketTypeEnum::getNonVerifiedPackets().contains(_type));
|
||||
|
||||
auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
|
||||
+ NUM_BYTES_RFC4122_UUID;
|
||||
QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret);
|
||||
|
||||
QByteArray verificationHash = hashForPacketAndHMAC(*this, hmacAuth);
|
||||
|
||||
memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size());
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include "udt/Packet.h"
|
||||
|
||||
class HMACAuth;
|
||||
|
||||
class NLPacket : public udt::Packet {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -71,7 +73,7 @@ public:
|
|||
|
||||
static QUuid sourceIDInHeader(const udt::Packet& packet);
|
||||
static QByteArray verificationHashInHeader(const udt::Packet& packet);
|
||||
static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret);
|
||||
static QByteArray hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash);
|
||||
|
||||
PacketType getType() const { return _type; }
|
||||
void setType(PacketType type);
|
||||
|
@ -82,7 +84,7 @@ public:
|
|||
const QUuid& getSourceID() const { return _sourceID; }
|
||||
|
||||
void writeSourceID(const QUuid& sourceID) const;
|
||||
void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const;
|
||||
void writeVerificationHash(HMACAuth& hmacAuth) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -86,10 +86,10 @@ NodeType_t NodeType::fromString(QString type) {
|
|||
|
||||
|
||||
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||
const HifiSockAddr& localSocket, QObject* parent) :
|
||||
const HifiSockAddr& localSocket, QObject* parent) :
|
||||
NetworkPeer(uuid, publicSocket, localSocket, parent),
|
||||
_type(type),
|
||||
_pingMs(-1), // "Uninitialized"
|
||||
_authenticateHash(new HMACAuth), _pingMs(-1), // "Uninitialized"
|
||||
_clockSkewUsec(0),
|
||||
_mutex(),
|
||||
_clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples
|
||||
|
@ -108,6 +108,7 @@ void Node::setType(char type) {
|
|||
_symmetricSocket.setObjectName(typeString);
|
||||
}
|
||||
|
||||
|
||||
void Node::updateClockSkewUsec(qint64 clockSkewSample) {
|
||||
_clockSkewMovingPercentile.updatePercentile(clockSkewSample);
|
||||
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
|
||||
|
@ -192,3 +193,12 @@ QDebug operator<<(QDebug debug, const Node& node) {
|
|||
debug.nospace() << node.getPublicSocket() << "/" << node.getLocalSocket();
|
||||
return debug.nospace();
|
||||
}
|
||||
|
||||
void Node::setConnectionSecret(const QUuid& connectionSecret) {
|
||||
if (_connectionSecret == connectionSecret) {
|
||||
return;
|
||||
}
|
||||
|
||||
_connectionSecret = connectionSecret;
|
||||
_authenticateHash->setKey(_connectionSecret);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "SimpleMovingAverage.h"
|
||||
#include "MovingPercentile.h"
|
||||
#include "NodePermissions.h"
|
||||
#include "HMACAuth.h"
|
||||
|
||||
class Node : public NetworkPeer {
|
||||
Q_OBJECT
|
||||
|
@ -55,7 +56,8 @@ public:
|
|||
void setIsUpstream(bool isUpstream) { _isUpstream = isUpstream; }
|
||||
|
||||
const QUuid& getConnectionSecret() const { return _connectionSecret; }
|
||||
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
|
||||
void setConnectionSecret(const QUuid& connectionSecret);
|
||||
HMACAuth& getAuthenticateHash() const { return *_authenticateHash; }
|
||||
|
||||
NodeData* getLinkedData() const { return _linkedData.get(); }
|
||||
void setLinkedData(std::unique_ptr<NodeData> linkedData) { _linkedData = std::move(linkedData); }
|
||||
|
@ -97,6 +99,7 @@ private:
|
|||
NodeType_t _type;
|
||||
|
||||
QUuid _connectionSecret;
|
||||
std::unique_ptr<HMACAuth> _authenticateHash;
|
||||
std::unique_ptr<NodeData> _linkedData;
|
||||
bool _isReplicated { false };
|
||||
int _pingMs;
|
||||
|
|
|
@ -214,6 +214,20 @@ void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, Shared
|
|||
sendingNode->setSymmetricSocket(senderSockAddr);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t connectionID;
|
||||
|
||||
message->readPrimitive(&connectionID);
|
||||
|
||||
auto it = _connectionIDs.find(sendingNode->getUUID());
|
||||
if (it != _connectionIDs.end()) {
|
||||
if (connectionID > it->second) {
|
||||
qDebug() << "Received a ping packet with a larger connection id (" << connectionID << ">" << it->second << ") from "
|
||||
<< sendingNode->getUUID();
|
||||
killNodeWithUUID(sendingNode->getUUID(), connectionID);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NodeList::processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
@ -705,16 +719,18 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
|
|||
if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) {
|
||||
qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second.";
|
||||
}
|
||||
|
||||
auto nodeID = node->getUUID();
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
auto localPingPacket = constructPingPacket(PingType::Local);
|
||||
auto localPingPacket = constructPingPacket(nodeID, PingType::Local);
|
||||
sendPacket(std::move(localPingPacket), *node, node->getLocalSocket());
|
||||
|
||||
auto publicPingPacket = constructPingPacket(PingType::Public);
|
||||
auto publicPingPacket = constructPingPacket(nodeID, PingType::Public);
|
||||
sendPacket(std::move(publicPingPacket), *node, node->getPublicSocket());
|
||||
|
||||
if (!node->getSymmetricSocket().isNull()) {
|
||||
auto symmetricPingPacket = constructPingPacket(PingType::Symmetric);
|
||||
auto symmetricPingPacket = constructPingPacket(nodeID, PingType::Symmetric);
|
||||
sendPacket(std::move(symmetricPingPacket), *node, node->getSymmetricSocket());
|
||||
}
|
||||
|
||||
|
@ -784,7 +800,7 @@ void NodeList::sendKeepAlivePings() {
|
|||
auto type = node->getType();
|
||||
return !node->isUpstream() && _nodeTypesOfInterest.contains(type) && !NodeType::isDownstream(type);
|
||||
}, [&](const SharedNodePointer& node) {
|
||||
sendPacket(constructPingPacket(), *node);
|
||||
sendPacket(constructPingPacket(node->getUUID()), *node);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ int packetTypeMetaTypeId = qRegisterMetaType<PacketType>();
|
|||
|
||||
PacketVersion versionForPacketType(PacketType packetType) {
|
||||
switch (packetType) {
|
||||
case PacketType::StunResponse:
|
||||
return 17;
|
||||
case PacketType::DomainList:
|
||||
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
|
||||
case PacketType::EntityAdd:
|
||||
|
@ -40,8 +42,21 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FBXReaderNodeReparenting);
|
||||
case PacketType::MessagesData:
|
||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||
// ICE packets
|
||||
case PacketType::ICEServerPeerInformation:
|
||||
return 17;
|
||||
case PacketType::ICEServerHeartbeatACK:
|
||||
return 17;
|
||||
case PacketType::ICEServerQuery:
|
||||
return 17;
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::ICEPing:
|
||||
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
|
||||
case PacketType::ICEPingReply:
|
||||
return 17;
|
||||
case PacketType::ICEServerHeartbeatDenied:
|
||||
return 17;
|
||||
case PacketType::AssetMappingOperation:
|
||||
case PacketType::AssetMappingOperationReply:
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::RedirectedMappings);
|
||||
|
@ -71,12 +86,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::MicrophoneAudioWithEcho:
|
||||
case PacketType::AudioStreamStats:
|
||||
return static_cast<PacketVersion>(AudioVersion::HighDynamicRangeVolume);
|
||||
case PacketType::ICEPing:
|
||||
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
|
||||
case PacketType::DomainSettings:
|
||||
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
|
||||
case PacketType::Ping:
|
||||
return static_cast<PacketVersion>(PingVersion::IncludeConnectionID);
|
||||
default:
|
||||
return 17;
|
||||
return 18;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -323,4 +323,8 @@ enum class IcePingVersion : PacketVersion {
|
|||
SendICEPeerID = 18
|
||||
};
|
||||
|
||||
enum class PingVersion : PacketVersion {
|
||||
IncludeConnectionID = 18
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
|
||||
#include "Octree.h"
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElementBag.h"
|
||||
#include "OctreeLogging.h"
|
||||
#include "OctreeQueryNode.h"
|
||||
#include "OctreeUtils.h"
|
||||
|
@ -57,7 +56,6 @@ Octree::Octree(bool shouldReaverage) :
|
|||
_rootElement(NULL),
|
||||
_isDirty(true),
|
||||
_shouldReaverage(shouldReaverage),
|
||||
_stopImport(false),
|
||||
_isViewing(false),
|
||||
_isServer(false)
|
||||
{
|
||||
|
@ -463,131 +461,6 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t buffe
|
|||
// skip bitstream to new startPoint
|
||||
bitstreamAt += theseBytesRead;
|
||||
bytesRead += theseBytesRead;
|
||||
|
||||
if (args.wantImportProgress) {
|
||||
emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Octree::deleteOctreeElementAt(float x, float y, float z, float s) {
|
||||
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
|
||||
deleteOctalCodeFromTree(octalCode);
|
||||
delete[] octalCode; // cleanup memory
|
||||
}
|
||||
|
||||
class DeleteOctalCodeFromTreeArgs {
|
||||
public:
|
||||
bool collapseEmptyTrees;
|
||||
const unsigned char* codeBuffer;
|
||||
int lengthOfCode;
|
||||
bool deleteLastChild;
|
||||
bool pathChanged;
|
||||
};
|
||||
|
||||
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
||||
// this only finds and deletes the element from the tree.
|
||||
void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) {
|
||||
// recurse the tree while decoding the codeBuffer, once you find the element in question, recurse
|
||||
// back and implement color reaveraging, and marking of lastChanged
|
||||
DeleteOctalCodeFromTreeArgs args;
|
||||
args.collapseEmptyTrees = collapseEmptyTrees;
|
||||
args.codeBuffer = codeBuffer;
|
||||
args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer);
|
||||
args.deleteLastChild = false;
|
||||
args.pathChanged = false;
|
||||
|
||||
withWriteLock([&] {
|
||||
deleteOctalCodeFromTreeRecursion(_rootElement, &args);
|
||||
});
|
||||
}
|
||||
|
||||
void Octree::deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData) {
|
||||
DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData;
|
||||
|
||||
int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode());
|
||||
|
||||
// Since we traverse the tree in code order, we know that if our code
|
||||
// matches, then we've reached our target element.
|
||||
if (lengthOfElementCode == args->lengthOfCode) {
|
||||
// we've reached our target, depending on how we're called we may be able to operate on it
|
||||
// it here, we need to recurse up, and delete it there. So we handle these cases the same to keep
|
||||
// the logic consistent.
|
||||
args->deleteLastChild = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, we know we haven't reached our target element yet, so keep looking
|
||||
int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer);
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(childIndex);
|
||||
|
||||
// If there is no child at the target location, and the current parent element is a colored leaf,
|
||||
// then it means we were asked to delete a child out of a larger leaf voxel.
|
||||
// We support this by breaking up the parent voxel into smaller pieces.
|
||||
if (!childElement && element->requiresSplit()) {
|
||||
// we need to break up ancestors until we get to the right level
|
||||
OctreeElementPointer ancestorElement = element;
|
||||
while (true) {
|
||||
int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer);
|
||||
|
||||
// we end up with all the children, even the one we want to delete
|
||||
ancestorElement->splitChildren();
|
||||
|
||||
int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode());
|
||||
|
||||
// If we've reached the parent of the target, then stop breaking up children
|
||||
if (lengthOfAncestorElement == (args->lengthOfCode - 1)) {
|
||||
|
||||
// since we created all the children when we split, we need to delete this target one
|
||||
ancestorElement->deleteChildAtIndex(index);
|
||||
break;
|
||||
}
|
||||
ancestorElement = ancestorElement->getChildAtIndex(index);
|
||||
}
|
||||
_isDirty = true;
|
||||
args->pathChanged = true;
|
||||
|
||||
// ends recursion, unwinds up stack
|
||||
return;
|
||||
}
|
||||
|
||||
// if we don't have a child and we reach this point, then we actually know that the parent
|
||||
// isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and
|
||||
// we can safely return, ending the recursion and unwinding
|
||||
if (!childElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got this far then we have a child for the branch we're looking for, but we're not there yet
|
||||
// recurse till we get there
|
||||
deleteOctalCodeFromTreeRecursion(childElement, args);
|
||||
|
||||
// If the lower level determined it needs to be deleted, then we should delete now.
|
||||
if (args->deleteLastChild) {
|
||||
element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element
|
||||
|
||||
// track our tree dirtiness
|
||||
_isDirty = true;
|
||||
|
||||
// track that path has changed
|
||||
args->pathChanged = true;
|
||||
|
||||
// If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want
|
||||
// to delete this element. This will collapse the empty tree above us.
|
||||
if (args->collapseEmptyTrees && element->getChildCount() == 0) {
|
||||
// Can't delete the root this way.
|
||||
if (element == _rootElement) {
|
||||
args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything
|
||||
}
|
||||
} else {
|
||||
args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything
|
||||
}
|
||||
}
|
||||
|
||||
// If the lower level did some work, then we need to let this element know, so it can
|
||||
// do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc
|
||||
if (args->pathChanged) {
|
||||
element->handleSubtreeChanged(shared_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,720 +729,6 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc
|
|||
return args.element;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int Octree::encodeTreeBitstream(const OctreeElementPointer& element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params) {
|
||||
|
||||
// How many bytes have we written so far at this level;
|
||||
int bytesWritten = 0;
|
||||
|
||||
// you can't call this without a valid element
|
||||
if (!element) {
|
||||
qCDebug(octree, "WARNING! encodeTreeBitstream() called with element=NULL");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
// you can't call this without a valid nodeData
|
||||
auto octreeQueryNode = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||
if (!octreeQueryNode) {
|
||||
qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
||||
if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) {
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
// write the octal code
|
||||
bool roomForOctalCode = false; // assume the worst
|
||||
int codeLength = 1; // assume root
|
||||
if (params.chopLevels) {
|
||||
unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels);
|
||||
roomForOctalCode = packetData->startSubTree(newCode);
|
||||
|
||||
if (newCode) {
|
||||
codeLength = numberOfThreeBitSectionsInCode(newCode);
|
||||
delete[] newCode;
|
||||
} else {
|
||||
codeLength = 1;
|
||||
}
|
||||
} else {
|
||||
roomForOctalCode = packetData->startSubTree(element->getOctalCode());
|
||||
codeLength = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode()));
|
||||
}
|
||||
|
||||
// If the octalcode couldn't fit, then we can return, because no nodes below us will fit...
|
||||
if (!roomForOctalCode) {
|
||||
bag.insert(element);
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
bytesWritten += codeLength; // keep track of byte count
|
||||
|
||||
int currentEncodeLevel = 0;
|
||||
|
||||
// record some stats, this is the one element that we won't record below in the recursion function, so we need to
|
||||
// track it here
|
||||
octreeQueryNode->stats.traversed(element);
|
||||
|
||||
ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
|
||||
currentEncodeLevel, parentLocationThisView);
|
||||
|
||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
||||
// if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// reason couldn't be written... so reset them here... This isn't true for the non-color included case
|
||||
if (suppressEmptySubtrees() && childBytesWritten == 2) {
|
||||
childBytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT...
|
||||
}
|
||||
|
||||
// if we wrote child bytes, then return our result of all bytes written
|
||||
if (childBytesWritten) {
|
||||
bytesWritten += childBytesWritten;
|
||||
} else {
|
||||
// otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code
|
||||
bytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
if (bytesWritten == 0) {
|
||||
packetData->discardSubTree();
|
||||
} else {
|
||||
packetData->endSubTree();
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::intersection& parentLocationThisView) const {
|
||||
|
||||
|
||||
const bool wantDebug = false;
|
||||
|
||||
// The append state of this level/element.
|
||||
OctreeElement::AppendState elementAppendState = OctreeElement::COMPLETED; // assume the best
|
||||
|
||||
// How many bytes have we written so far at this level;
|
||||
int bytesAtThisLevel = 0;
|
||||
|
||||
// you can't call this without a valid element
|
||||
if (!element) {
|
||||
qCDebug(octree, "WARNING! encodeTreeBitstreamRecursion() called with element=NULL");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// you can't call this without a valid nodeData
|
||||
auto octreeQueryNode = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||
if (!octreeQueryNode) {
|
||||
qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
||||
// Keep track of how deep we've encoded.
|
||||
currentEncodeLevel++;
|
||||
|
||||
params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached);
|
||||
|
||||
// If we've reached our max Search Level, then stop searching.
|
||||
if (currentEncodeLevel >= params.maxEncodeLevel) {
|
||||
params.stopReason = EncodeBitstreamParams::TOO_DEEP;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) {
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
// If we're too far away for our render level, then just return
|
||||
if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) {
|
||||
octreeQueryNode->stats.skippedDistance(element);
|
||||
params.stopReason = EncodeBitstreamParams::LOD_SKIP;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see
|
||||
// if we are INSIDE, INTERSECT, or OUTSIDE
|
||||
if (parentLocationThisView != ViewFrustum::INSIDE) {
|
||||
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
|
||||
nodeLocationThisView = element->computeViewIntersection(params.viewFrustum);
|
||||
}
|
||||
|
||||
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
||||
// although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if
|
||||
// we're out of view
|
||||
if (nodeLocationThisView == ViewFrustum::OUTSIDE) {
|
||||
octreeQueryNode->stats.skippedOutOfView(element);
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view
|
||||
// because we don't send nodes from the previously know in view frustum.
|
||||
bool wasInView = false;
|
||||
|
||||
if (params.deltaView) {
|
||||
ViewFrustum::intersection location = element->computeViewIntersection(params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (element->isLeaf()) {
|
||||
wasInView = location != ViewFrustum::OUTSIDE;
|
||||
} else {
|
||||
wasInView = location == ViewFrustum::INSIDE;
|
||||
}
|
||||
|
||||
// If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't
|
||||
// tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer
|
||||
// to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it
|
||||
// as "was in view"...
|
||||
if (wasInView) {
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) {
|
||||
// This would have been invisible... but now should be visible (we wouldn't be here otherwise)...
|
||||
wasInView = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we were previously in the view, then we normally will return out of here and stop recursing. But
|
||||
// if we're in deltaView mode, and this element has changed since it was last sent, then we do
|
||||
// need to send it.
|
||||
if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) {
|
||||
octreeQueryNode->stats.skippedWasInView(element);
|
||||
params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
||||
// then we can also bail early and save bits
|
||||
if (!params.forceSendScene && !params.deltaView &&
|
||||
!element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) {
|
||||
|
||||
octreeQueryNode->stats.skippedNoChange(element);
|
||||
|
||||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
||||
|
||||
// At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level
|
||||
// is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees.
|
||||
// There could be sub trees below this point, which might take many more bytes, but that's ok, because we can
|
||||
// always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet
|
||||
// for the remaining sub trees.
|
||||
unsigned char childrenExistInTreeBits = 0;
|
||||
unsigned char childrenExistInPacketBits = 0;
|
||||
unsigned char childrenDataBits = 0;
|
||||
|
||||
// Make our local buffer large enough to handle writing at this level in case we need to.
|
||||
LevelDetails thisLevelKey = packetData->startLevel();
|
||||
int requiredBytes = sizeof(childrenDataBits) + sizeof(childrenExistInPacketBits);
|
||||
if (params.includeExistsBits) {
|
||||
requiredBytes += sizeof(childrenExistInTreeBits);
|
||||
}
|
||||
|
||||
// If this datatype allows root elements to include data, and this is the root, then ask the tree for the
|
||||
// minimum bytes needed for root data and reserve those also
|
||||
if (element == _rootElement && rootElementHasData()) {
|
||||
requiredBytes += minimumRequiredRootDataBytes();
|
||||
}
|
||||
|
||||
bool continueThisLevel = packetData->reserveBytes(requiredBytes);
|
||||
|
||||
// If we can't reserve our minimum bytes then we can discard this level and return as if none of this level fits
|
||||
if (!continueThisLevel) {
|
||||
packetData->discardLevel(thisLevelKey);
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
bag.insert(element);
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
int inViewCount = 0;
|
||||
int inViewNotLeafCount = 0;
|
||||
int inViewWithColorCount = 0;
|
||||
|
||||
OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
||||
float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
|
||||
// if the caller wants to include childExistsBits, then include them
|
||||
if (params.includeExistsBits && childElement) {
|
||||
childrenExistInTreeBits += (1 << (7 - i));
|
||||
}
|
||||
|
||||
sortedChildren[i] = childElement;
|
||||
indexOfChildren[i] = i;
|
||||
distancesToChildren[i] = 0.0f;
|
||||
|
||||
// track stats
|
||||
// must check childElement here, because it could be we got here with no childElement
|
||||
if (childElement) {
|
||||
octreeQueryNode->stats.traversed(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
// for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
|
||||
// add them to our distance ordered array of children
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
OctreeElementPointer childElement = sortedChildren[i];
|
||||
int originalIndex = indexOfChildren[i];
|
||||
|
||||
bool childIsInView = (childElement &&
|
||||
(params.recurseEverything || !octreeQueryNode->getUsesFrustum() ||
|
||||
(nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are
|
||||
(nodeLocationThisView == ViewFrustum::INTERSECT &&
|
||||
childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view
|
||||
));
|
||||
|
||||
if (!childIsInView) {
|
||||
// must check childElement here, because it could be we got here because there was no childElement
|
||||
if (childElement) {
|
||||
octreeQueryNode->stats.skippedOutOfView(childElement);
|
||||
}
|
||||
} else {
|
||||
// Before we consider this further, let's see if it's in our LOD scope...
|
||||
float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 :
|
||||
boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
if (!(distancesToChildren[i] < boundaryDistance)) {
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
octreeQueryNode->stats.skippedDistance(childElement);
|
||||
} else {
|
||||
inViewCount++;
|
||||
|
||||
// track children in view as existing and not a leaf, if they're a leaf,
|
||||
// we don't care about recursing deeper on them, and we don't consider their
|
||||
// subtree to exist
|
||||
if (!(childElement && childElement->isLeaf())) {
|
||||
childrenExistInPacketBits += (1 << (7 - originalIndex));
|
||||
inViewNotLeafCount++;
|
||||
}
|
||||
|
||||
bool childIsOccluded = false; // assume it's not occluded
|
||||
|
||||
bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ||
|
||||
childElement->calculateShouldRender(params.viewFrustum,
|
||||
params.octreeElementSizeScale, params.boundaryLevelAdjust);
|
||||
|
||||
// track some stats
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (!shouldRender && childElement->isLeaf()) {
|
||||
octreeQueryNode->stats.skippedDistance(childElement);
|
||||
}
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (childIsOccluded) {
|
||||
octreeQueryNode->stats.skippedOccluded(childElement);
|
||||
}
|
||||
|
||||
// track children with actual color, only if the child wasn't previously in view!
|
||||
if (shouldRender && !childIsOccluded) {
|
||||
bool childWasInView = false;
|
||||
|
||||
if (childElement && params.deltaView) {
|
||||
ViewFrustum::intersection location = childElement->computeViewIntersection(params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (childElement->isLeaf()) {
|
||||
childWasInView = location != ViewFrustum::OUTSIDE;
|
||||
} else {
|
||||
childWasInView = location == ViewFrustum::INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
// If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items.
|
||||
// Or if we were previously in the view, but this element has changed since it was last sent, then we do
|
||||
// need to send it.
|
||||
if (!childWasInView ||
|
||||
(params.deltaView &&
|
||||
childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){
|
||||
|
||||
childrenDataBits += (1 << (7 - originalIndex));
|
||||
inViewWithColorCount++;
|
||||
} else {
|
||||
// otherwise just track stats of the items we discarded
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (childWasInView) {
|
||||
octreeQueryNode->stats.skippedWasInView(childElement);
|
||||
} else {
|
||||
octreeQueryNode->stats.skippedNoChange(childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the childrenDataBits indicates that there is an array of child element data included in this packet.
|
||||
// We will write this bit mask but we may come back later and update the bits that are actually included
|
||||
packetData->releaseReservedBytes(sizeof(childrenDataBits));
|
||||
continueThisLevel = packetData->appendBitMask(childrenDataBits);
|
||||
|
||||
int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits));
|
||||
unsigned char actualChildrenDataBits = 0;
|
||||
|
||||
assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail
|
||||
bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count
|
||||
|
||||
octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits
|
||||
|
||||
// NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data
|
||||
element->initializeExtraEncodeData(params);
|
||||
|
||||
// write the child element data...
|
||||
// NOTE: the format of the bitstream is generally this:
|
||||
// [octalcode]
|
||||
// [bitmask for existence of child data]
|
||||
// N x [child data]
|
||||
// [bitmask for existence of child elements in tree]
|
||||
// [bitmask for existence of child elements in buffer]
|
||||
// N x [ ... tree for children ...]
|
||||
//
|
||||
// This section of the code, is writing the "N x [child data]" portion of this bitstream
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenDataBits, i)) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
|
||||
// the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already
|
||||
// processed and sent the data bits for. Let our tree subclass determine if it really wants to send the
|
||||
// data for this child at this point
|
||||
if (childElement && element->shouldIncludeChildData(i, params)) {
|
||||
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
|
||||
// a childElement may "partially" write it's data. for example, the model server where the entire
|
||||
// contents of the element may be larger than can fit in a single MTU/packetData. In this case,
|
||||
// we want to allow the appendElementData() to respond that it produced partial data, which should be
|
||||
// written, but that the childElement needs to be reprocessed in an additional pass or passes
|
||||
// to be completed.
|
||||
LevelDetails childDataLevelKey = packetData->startLevel();
|
||||
|
||||
OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params);
|
||||
|
||||
// allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state
|
||||
element->updateEncodedData(i, childAppendState, params);
|
||||
|
||||
// Continue this level so long as some part of this child element was appended.
|
||||
bool childFit = (childAppendState != OctreeElement::NONE);
|
||||
|
||||
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
|
||||
// the data type wants to bail on this element level completely
|
||||
if (!childFit && mustIncludeAllChildData()) {
|
||||
continueThisLevel = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
|
||||
// this child data
|
||||
if (childFit) {
|
||||
actualChildrenDataBits += (1 << (7 - i));
|
||||
continueThisLevel = packetData->endLevel(childDataLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(childDataLevelKey);
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
// If this child was partially appended, then consider this element to be partially appended
|
||||
if (childAppendState == OctreeElement::PARTIAL) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (childAppendState != OctreeElement::NONE) {
|
||||
octreeQueryNode->stats.colorSent(childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mustIncludeAllChildData() && !continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
|
||||
if (continueThisLevel && actualChildrenDataBits != childrenDataBits) {
|
||||
// repair the child data mask
|
||||
continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits);
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
|
||||
// if the caller wants to include childExistsBits, then include them even if not in view, put them before the
|
||||
// childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits
|
||||
if (continueThisLevel && params.includeExistsBits) {
|
||||
packetData->releaseReservedBytes(sizeof(childrenExistInTreeBits));
|
||||
continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits);
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
|
||||
|
||||
octreeQueryNode->stats.existsBitsWritten();
|
||||
} else {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
|
||||
// write the child exist bits
|
||||
if (continueThisLevel) {
|
||||
packetData->releaseReservedBytes(sizeof(childrenExistInPacketBits));
|
||||
continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits);
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
|
||||
|
||||
octreeQueryNode->stats.existsInPacketBitsWritten();
|
||||
} else {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
|
||||
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
||||
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
||||
|
||||
//
|
||||
// NOTE: the format of the bitstream is generally this:
|
||||
// [octalcode]
|
||||
// [bitmask for existence of child data]
|
||||
// N x [child data]
|
||||
// [bitmask for existence of child elements in tree]
|
||||
// [bitmask for existence of child elements in buffer]
|
||||
// N x [ ... tree for children ...]
|
||||
//
|
||||
// This section of the code, is writing the "N x [ ... tree for children ...]" portion of this bitstream
|
||||
//
|
||||
if (continueThisLevel && keepDiggingDeeper) {
|
||||
|
||||
// at this point, we need to iterate the children who are in view, even if not colored
|
||||
// and we need to determine if there's a deeper tree below them that we care about.
|
||||
//
|
||||
// Since this recursive function assumes we're already writing, we know we've already written our
|
||||
// childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if
|
||||
// we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is
|
||||
// write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they
|
||||
// write something, we keep them in the bits, if they don't, we take them out.
|
||||
//
|
||||
// we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was!
|
||||
int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits));
|
||||
|
||||
// we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the
|
||||
// final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes,
|
||||
// and then later reshuffle these sections of our output buffer back into normal order. This allows us to make
|
||||
// a single recursive pass in distance sorted order, but retain standard order in our encoded packet
|
||||
|
||||
// for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
|
||||
// add them to our distance ordered array of children
|
||||
for (int indexByDistance = 0; indexByDistance < NUMBER_OF_CHILDREN; indexByDistance++) {
|
||||
OctreeElementPointer childElement = sortedChildren[indexByDistance];
|
||||
int originalIndex = indexOfChildren[indexByDistance];
|
||||
|
||||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
|
||||
int thisLevel = currentEncodeLevel;
|
||||
|
||||
int childTreeBytesOut = 0;
|
||||
|
||||
// NOTE: some octree styles (like models and particles) will store content in parent elements, and child
|
||||
// elements. In this case, if we stop recursion when we include any data (the colorbits should really be
|
||||
// called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep
|
||||
// recursing, by returning TRUE in recurseChildrenWithData().
|
||||
|
||||
if (params.recurseEverything || !octreeQueryNode->getUsesFrustum()
|
||||
|| recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) {
|
||||
|
||||
// Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this
|
||||
// will be true. But if the tree has already been encoded, we will skip this.
|
||||
if (element->shouldRecurseChildTree(originalIndex, params)) {
|
||||
childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params,
|
||||
thisLevel, nodeLocationThisView);
|
||||
} else {
|
||||
childTreeBytesOut = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
|
||||
// basically, the children below don't contain any info.
|
||||
|
||||
// if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color
|
||||
// byte and the child exist byte.
|
||||
//
|
||||
assert(childTreeBytesOut != 1);
|
||||
|
||||
// if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because...
|
||||
// if it had colors it would write 1 byte for the color mask,
|
||||
// and at least a color's worth of bytes for the element of colors.
|
||||
// if it had child trees (with something in them) then it would have the 1 byte for child mask
|
||||
// and some number of bytes of lower children...
|
||||
// so, if the child returns 2 bytes out, we can actually consider that an empty tree also!!
|
||||
//
|
||||
// we can make this act like no bytes out, by just resetting the bytes out in this case
|
||||
if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) {
|
||||
childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
||||
|
||||
}
|
||||
|
||||
bytesAtThisLevel += childTreeBytesOut;
|
||||
|
||||
// If we had previously started writing, and if the child DIDN'T write any bytes,
|
||||
// then we want to remove their bit from the childExistsPlaceHolder bitmask
|
||||
if (childTreeBytesOut == 0) {
|
||||
|
||||
// remove this child's bit...
|
||||
childrenExistInPacketBits -= (1 << (7 - originalIndex));
|
||||
|
||||
// repair the child exists mask
|
||||
continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits);
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childExistsPlaceHolder";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
|
||||
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
|
||||
if (childrenExistInPacketBits == 0) {
|
||||
octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
if (wantDebug) {
|
||||
qCDebug(octree) << " WARNING line:" << __LINE__;
|
||||
qCDebug(octree) << " breaking the child recursion loop with continueThisLevel=false!!!";
|
||||
qCDebug(octree) << " AFTER attempting to updatePriorBitMask() for empty sub tree....";
|
||||
qCDebug(octree) << " IS THIS ACCEPTABLE!!!!";
|
||||
}
|
||||
break; // can't continue...
|
||||
}
|
||||
|
||||
// Note: no need to move the pointer, cause we already stored this
|
||||
} // end if (childTreeBytesOut == 0)
|
||||
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
|
||||
} // end for
|
||||
} // end keepDiggingDeeper
|
||||
|
||||
// If we made it this far, then we've written all of our child data... if this element is the root
|
||||
// element, then we also allow the root element to write out it's data...
|
||||
if (continueThisLevel && element == _rootElement && rootElementHasData()) {
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
|
||||
// release the bytes we reserved...
|
||||
packetData->releaseReservedBytes(minimumRequiredRootDataBytes());
|
||||
|
||||
LevelDetails rootDataLevelKey = packetData->startLevel();
|
||||
OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params);
|
||||
|
||||
bool partOfRootFit = (rootAppendState != OctreeElement::NONE);
|
||||
bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED);
|
||||
|
||||
if (partOfRootFit) {
|
||||
continueThisLevel = packetData->endLevel(rootDataLevelKey);
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << " UNEXPECTED ROOT ELEMENT -- could not packetData->endLevel(rootDataLevelKey) -- line:" << __LINE__;
|
||||
}
|
||||
} else {
|
||||
packetData->discardLevel(rootDataLevelKey);
|
||||
}
|
||||
|
||||
if (!allOfRootFit) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
// do we really ever NOT want to continue this level???
|
||||
//continueThisLevel = (rootAppendState == OctreeElement::COMPLETED);
|
||||
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
|
||||
octreeQueryNode->stats.colorSent(element);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
|
||||
// if we were unable to fit this level in our packet, then rewind and add it to the element bag for
|
||||
// sending later...
|
||||
if (continueThisLevel) {
|
||||
continueThisLevel = packetData->endLevel(thisLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(thisLevelKey);
|
||||
|
||||
if (!mustIncludeAllChildData()) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
|
||||
// This happens if the element could not be written at all. In the case of Octree's that support partial
|
||||
// element data, continueThisLevel will be true. So this only happens if the full element needs to be
|
||||
// added back to the element bag.
|
||||
if (!continueThisLevel) {
|
||||
if (!mustIncludeAllChildData()) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element.";
|
||||
qCDebug(octree) << " If the datatype requires all child data, then this might happen. Otherwise" ;
|
||||
qCDebug(octree) << " this is an unexpected case and we should research a potential logic error." ;
|
||||
}
|
||||
|
||||
bag.insert(element);
|
||||
|
||||
// don't need to check element here, because we can't get here with no element
|
||||
octreeQueryNode->stats.didntFit(element);
|
||||
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
bytesAtThisLevel = 0; // didn't fit
|
||||
} else {
|
||||
|
||||
// assuming we made it here with continueThisLevel == true, we STILL might want
|
||||
// to add our element back to the bag for additional encoding, specifically if
|
||||
// the appendState is PARTIAL, in this case, we re-add our element to the bag
|
||||
// and assume that the appendElementData() has stored any required state data
|
||||
// in the params extraEncodeData
|
||||
if (elementAppendState == OctreeElement::PARTIAL) {
|
||||
bag.insert(element);
|
||||
}
|
||||
}
|
||||
|
||||
// If our element is completed let the element know so it can do any cleanup it of extra wants
|
||||
if (elementAppendState == OctreeElement::COMPLETED) {
|
||||
element->elementEncodeComplete(params);
|
||||
}
|
||||
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
bool Octree::readFromFile(const char* fileName) {
|
||||
QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS);
|
||||
|
||||
|
@ -1588,14 +747,10 @@ bool Octree::readFromFile(const char* fileName) {
|
|||
QFileInfo fileInfo(qFileName);
|
||||
uint64_t fileLength = fileInfo.size();
|
||||
|
||||
emit importSize(1.0f, 1.0f, 1.0f);
|
||||
emit importProgress(0);
|
||||
|
||||
qCDebug(octree) << "Loading file" << qFileName << "...";
|
||||
|
||||
bool success = readFromStream(fileLength, fileInputStream);
|
||||
|
||||
emit importProgress(100);
|
||||
file.close();
|
||||
|
||||
return success;
|
||||
|
@ -1836,7 +991,3 @@ bool Octree::countOctreeElementsOperation(const OctreeElementPointer& element, v
|
|||
(*(uint64_t*)extraData)++;
|
||||
return true; // keep going
|
||||
}
|
||||
|
||||
void Octree::cancelImport() {
|
||||
_stopImport = true;
|
||||
}
|
||||
|
|
|
@ -49,126 +49,62 @@ public:
|
|||
|
||||
// Callback function, for recuseTreeWithOperation
|
||||
using RecurseOctreeOperation = std::function<bool(const OctreeElementPointer&, void*)>;
|
||||
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
|
||||
typedef QHash<uint, AACube> CubeList;
|
||||
|
||||
const bool NO_EXISTS_BITS = false;
|
||||
const bool WANT_EXISTS_BITS = true;
|
||||
const bool COLLAPSE_EMPTY_TREE = true;
|
||||
const bool DONT_COLLAPSE = false;
|
||||
|
||||
const int DONT_CHOP = 0;
|
||||
const int NO_BOUNDARY_ADJUST = 0;
|
||||
const int LOW_RES_MOVING_ADJUST = 1;
|
||||
|
||||
#define IGNORE_COVERAGE_MAP NULL
|
||||
|
||||
class EncodeBitstreamParams {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
ViewFrustum lastViewFrustum;
|
||||
int maxEncodeLevel;
|
||||
int maxLevelReached;
|
||||
bool includeExistsBits;
|
||||
int chopLevels;
|
||||
bool deltaView;
|
||||
bool recurseEverything { false };
|
||||
int boundaryLevelAdjust;
|
||||
float octreeElementSizeScale;
|
||||
bool forceSendScene;
|
||||
NodeData* nodeData;
|
||||
|
||||
// output hints from the encode process
|
||||
typedef enum {
|
||||
UNKNOWN,
|
||||
DIDNT_FIT,
|
||||
NULL_NODE,
|
||||
NULL_NODE_DATA,
|
||||
TOO_DEEP,
|
||||
LOD_SKIP,
|
||||
OUT_OF_VIEW,
|
||||
WAS_IN_VIEW,
|
||||
NO_CHANGE,
|
||||
OCCLUDED,
|
||||
FINISHED
|
||||
} reason;
|
||||
reason stopReason;
|
||||
|
||||
EncodeBitstreamParams(
|
||||
int maxEncodeLevel = INT_MAX,
|
||||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
int chopLevels = 0,
|
||||
bool useDeltaView = false,
|
||||
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
|
||||
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
bool forceSendScene = true,
|
||||
NodeData* nodeData = nullptr) :
|
||||
maxEncodeLevel(maxEncodeLevel),
|
||||
maxLevelReached(0),
|
||||
EncodeBitstreamParams(bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
NodeData* nodeData = nullptr) :
|
||||
includeExistsBits(includeExistsBits),
|
||||
chopLevels(chopLevels),
|
||||
deltaView(useDeltaView),
|
||||
boundaryLevelAdjust(boundaryLevelAdjust),
|
||||
octreeElementSizeScale(octreeElementSizeScale),
|
||||
forceSendScene(forceSendScene),
|
||||
nodeData(nodeData),
|
||||
stopReason(UNKNOWN)
|
||||
{
|
||||
lastViewFrustum.invalidate();
|
||||
}
|
||||
|
||||
void displayStopReason() {
|
||||
printf("StopReason: ");
|
||||
switch (stopReason) {
|
||||
default:
|
||||
case UNKNOWN: qDebug("UNKNOWN"); break;
|
||||
|
||||
case DIDNT_FIT: qDebug("DIDNT_FIT"); break;
|
||||
case NULL_NODE: qDebug("NULL_NODE"); break;
|
||||
case TOO_DEEP: qDebug("TOO_DEEP"); break;
|
||||
case LOD_SKIP: qDebug("LOD_SKIP"); break;
|
||||
case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break;
|
||||
case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break;
|
||||
case NO_CHANGE: qDebug("NO_CHANGE"); break;
|
||||
case OCCLUDED: qDebug("OCCLUDED"); break;
|
||||
case FINISHED: qDebug("FINISHED"); break;
|
||||
}
|
||||
}
|
||||
|
||||
QString getStopReason() {
|
||||
switch (stopReason) {
|
||||
default:
|
||||
case UNKNOWN: return QString("UNKNOWN"); break;
|
||||
|
||||
case DIDNT_FIT: return QString("DIDNT_FIT"); break;
|
||||
case NULL_NODE: return QString("NULL_NODE"); break;
|
||||
case TOO_DEEP: return QString("TOO_DEEP"); break;
|
||||
case LOD_SKIP: return QString("LOD_SKIP"); break;
|
||||
case OUT_OF_VIEW: return QString("OUT_OF_VIEW"); break;
|
||||
case WAS_IN_VIEW: return QString("WAS_IN_VIEW"); break;
|
||||
case NO_CHANGE: return QString("NO_CHANGE"); break;
|
||||
case OCCLUDED: return QString("OCCLUDED"); break;
|
||||
case FINISHED: return QString("FINISHED"); break;
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(const QUuid& dataID, quint64 itemLastEdited)> trackSend { [](const QUuid&, quint64){} };
|
||||
};
|
||||
|
||||
class ReadElementBufferToTreeArgs {
|
||||
public:
|
||||
const unsigned char* buffer;
|
||||
int length;
|
||||
bool destructive;
|
||||
bool pathChanged;
|
||||
};
|
||||
|
||||
class ReadBitstreamToTreeParams {
|
||||
public:
|
||||
bool includeExistsBits;
|
||||
OctreeElementPointer destinationElement;
|
||||
QUuid sourceUUID;
|
||||
SharedNodePointer sourceNode;
|
||||
bool wantImportProgress;
|
||||
PacketVersion bitstreamVersion;
|
||||
int elementsPerPacket = 0;
|
||||
int entitiesPerPacket = 0;
|
||||
|
||||
|
@ -176,15 +112,11 @@ public:
|
|||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
OctreeElementPointer destinationElement = NULL,
|
||||
QUuid sourceUUID = QUuid(),
|
||||
SharedNodePointer sourceNode = SharedNodePointer(),
|
||||
bool wantImportProgress = false,
|
||||
PacketVersion bitstreamVersion = 0) :
|
||||
SharedNodePointer sourceNode = SharedNodePointer()) :
|
||||
includeExistsBits(includeExistsBits),
|
||||
destinationElement(destinationElement),
|
||||
sourceUUID(sourceUUID),
|
||||
sourceNode(sourceNode),
|
||||
wantImportProgress(wantImportProgress),
|
||||
bitstreamVersion(bitstreamVersion)
|
||||
sourceNode(sourceNode)
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -199,7 +131,6 @@ public:
|
|||
|
||||
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
|
||||
// own definition. Implement these to allow your octree based server to support editing
|
||||
virtual bool getWantSVOfileVersions() const { return false; }
|
||||
virtual PacketType expectedDataPacketType() const { return PacketType::Unknown; }
|
||||
virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); }
|
||||
virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
|
||||
|
@ -209,12 +140,8 @@ public:
|
|||
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
||||
|
||||
virtual bool recurseChildrenWithData() const { return true; }
|
||||
virtual bool rootElementHasData() const { return false; }
|
||||
virtual int minimumRequiredRootDataBytes() const { return 0; }
|
||||
virtual bool suppressEmptySubtrees() const { return true; }
|
||||
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { }
|
||||
virtual bool mustIncludeAllChildData() const { return true; }
|
||||
|
||||
virtual void update() { } // nothing to do by default
|
||||
|
||||
|
@ -223,11 +150,8 @@ public:
|
|||
virtual void eraseAllOctreeElements(bool createNewRoot = true);
|
||||
|
||||
virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||
void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||
void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer());
|
||||
|
||||
void deleteOctreeElementAt(float x, float y, float z, float s);
|
||||
|
||||
/// Find the voxel at position x,y,z,s
|
||||
/// \return pointer to the OctreeElement or NULL if none at x,y,z,s.
|
||||
OctreeElementPointer getOctreeElementAt(float x, float y, float z, float s) const;
|
||||
|
@ -250,8 +174,6 @@ public:
|
|||
|
||||
void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject);
|
||||
|
||||
int encodeTreeBitstream(const OctreeElementPointer& element, OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params) ;
|
||||
|
||||
bool isDirty() const { return _isDirty; }
|
||||
void clearDirtyBit() { _isDirty = false; }
|
||||
|
@ -344,22 +266,10 @@ public:
|
|||
|
||||
void incrementPersistDataVersion() { _persistDataVersion++; }
|
||||
|
||||
signals:
|
||||
void importSize(float x, float y, float z);
|
||||
void importProgress(int progress);
|
||||
|
||||
public slots:
|
||||
void cancelImport();
|
||||
|
||||
|
||||
protected:
|
||||
void deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData);
|
||||
|
||||
int encodeTreeBitstreamRecursion(const OctreeElementPointer& element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::intersection& parentLocationThisView) const;
|
||||
|
||||
static bool countOctreeElementsOperation(const OctreeElementPointer& element, void* extraData);
|
||||
|
||||
OctreeElementPointer nodeForOctalCode(const OctreeElementPointer& ancestorElement, const unsigned char* needleCode, OctreeElementPointer* parentOfFoundElement) const;
|
||||
|
@ -374,7 +284,6 @@ protected:
|
|||
|
||||
bool _isDirty;
|
||||
bool _shouldReaverage;
|
||||
bool _stopImport;
|
||||
|
||||
bool _isViewing;
|
||||
bool _isServer;
|
||||
|
|
|
@ -457,33 +457,6 @@ ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrust
|
|||
return viewFrustum.calculateCubeKeyholeIntersection(_cube);
|
||||
}
|
||||
|
||||
// There are two types of nodes for which we want to "render"
|
||||
// 1) Leaves that are in the LOD
|
||||
// 2) Non-leaves are more complicated though... usually you don't want to render them, but if their children
|
||||
// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE
|
||||
// in the LOD, and others that are not. In this case we want to render the parent, and none of the children.
|
||||
//
|
||||
// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest"
|
||||
// corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of.
|
||||
// By doing this, we don't need to test each child voxel's position vs the LOD boundary
|
||||
bool OctreeElement::calculateShouldRender(const ViewFrustum& viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const {
|
||||
bool shouldRender = false;
|
||||
|
||||
if (hasContent()) {
|
||||
float furthestDistance = furthestDistanceToCamera(viewFrustum);
|
||||
float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize);
|
||||
bool inChildBoundary = (furthestDistance <= childBoundary);
|
||||
if (hasDetailedContent() && inChildBoundary) {
|
||||
shouldRender = true;
|
||||
} else {
|
||||
float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary
|
||||
bool inBoundary = (furthestDistance <= boundary);
|
||||
shouldRender = inBoundary && !inChildBoundary;
|
||||
}
|
||||
}
|
||||
return shouldRender;
|
||||
}
|
||||
|
||||
// Calculates the distance to the furthest point of the voxel to the camera
|
||||
// does as much math as possible in voxel scale and then scales up to TREE_SCALE at end
|
||||
float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const {
|
||||
|
|
|
@ -85,16 +85,6 @@ public:
|
|||
typedef enum { COMPLETED, PARTIAL, NONE } AppendState;
|
||||
|
||||
virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const { }
|
||||
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { }
|
||||
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
|
||||
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { }
|
||||
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { }
|
||||
|
||||
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
|
||||
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
|
||||
{ return COMPLETED; }
|
||||
|
||||
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
|
||||
/// from the network.
|
||||
|
@ -139,9 +129,6 @@ public:
|
|||
float distanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
|
||||
bool calculateShouldRender(const ViewFrustum& viewFrustum,
|
||||
float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
|
||||
|
||||
// points are assumed to be in Voxel Coordinates (not TREE_SCALE'd)
|
||||
float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this.
|
||||
float distanceToPoint(const glm::vec3& point) const;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// OctreeElementBag.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/25/2013.
|
||||
// Copyright 2013 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 "OctreeElementBag.h"
|
||||
#include <OctalCode.h>
|
||||
|
||||
void OctreeElementBag::deleteAll() {
|
||||
_bagElements = Bag();
|
||||
}
|
||||
|
||||
/// does the bag contain elements?
|
||||
/// if all of the contained elements are expired, they will not report as empty, and
|
||||
/// a single last item will be returned by extract as a null pointer
|
||||
bool OctreeElementBag::isEmpty() {
|
||||
return _bagElements.empty();
|
||||
}
|
||||
|
||||
void OctreeElementBag::insert(const OctreeElementPointer& element) {
|
||||
_bagElements[element.get()] = element;
|
||||
}
|
||||
|
||||
OctreeElementPointer OctreeElementBag::extract() {
|
||||
OctreeElementPointer result;
|
||||
|
||||
// Find the first element still alive
|
||||
Bag::iterator it = _bagElements.begin();
|
||||
while (it != _bagElements.end() && !result) {
|
||||
result = it->second.lock();
|
||||
it = _bagElements.erase(it);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -16,30 +16,8 @@
|
|||
#ifndef hifi_OctreeElementBag_h
|
||||
#define hifi_OctreeElementBag_h
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "OctreeElement.h"
|
||||
|
||||
class OctreeElementBag {
|
||||
using Bag = std::unordered_map<OctreeElement*, OctreeElementWeakPointer>;
|
||||
|
||||
public:
|
||||
void insert(const OctreeElementPointer& element); // put a element into the bag
|
||||
|
||||
OctreeElementPointer extract(); /// pull a element out of the bag (could come in any order) and if all of the
|
||||
/// elements have expired, a single null pointer will be returned
|
||||
|
||||
bool isEmpty(); /// does the bag contain elements,
|
||||
/// if all of the contained elements are expired, they will not report as empty, and
|
||||
/// a single last item will be returned by extract as a null pointer
|
||||
|
||||
void deleteAll();
|
||||
size_t size() const { return _bagElements.size(); }
|
||||
|
||||
private:
|
||||
Bag _bagElements;
|
||||
};
|
||||
|
||||
class OctreeElementExtraEncodeDataBase {
|
||||
public:
|
||||
OctreeElementExtraEncodeDataBase() {}
|
||||
|
|
|
@ -117,7 +117,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe
|
|||
if (sectionLength) {
|
||||
// ask the VoxelTree to read the bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL,
|
||||
sourceUUID, sourceNode, false, message.getVersion());
|
||||
sourceUUID, sourceNode);
|
||||
quint64 startUncompress, startLock = usecTimestampNow();
|
||||
quint64 startReadBitsteam, endReadBitsteam;
|
||||
// FIXME STUTTER - there may be an opportunity to bump this lock outside of the
|
||||
|
|
|
@ -144,11 +144,6 @@ void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const {
|
|||
viewOut = _currentViewFrustum;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::copyLastKnownViewFrustum(ViewFrustum& viewOut) const {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
viewOut = _lastKnownViewFrustum;
|
||||
}
|
||||
|
||||
bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||
// if shutting down, return immediately
|
||||
if (_isShuttingDown) {
|
||||
|
@ -229,70 +224,6 @@ void OctreeQueryNode::setViewSent(bool viewSent) {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::updateLastKnownViewFrustum() {
|
||||
// if shutting down, return immediately
|
||||
if (_isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum);
|
||||
|
||||
if (frustumChanges) {
|
||||
// save our currentViewFrustum into our lastKnownViewFrustum
|
||||
_lastKnownViewFrustum = _currentViewFrustum;
|
||||
}
|
||||
}
|
||||
|
||||
// save that we know the view has been sent.
|
||||
setLastTimeBagEmpty();
|
||||
}
|
||||
|
||||
|
||||
bool OctreeQueryNode::moveShouldDump() const {
|
||||
// if shutting down, return immediately
|
||||
if (_isShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition();
|
||||
glm::vec3 newPosition = _currentViewFrustum.getPosition();
|
||||
|
||||
// theoretically we could make this slightly larger but relative to avatar scale.
|
||||
const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f;
|
||||
return glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::dumpOutOfView() {
|
||||
// if shutting down, return immediately
|
||||
if (_isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
int stillInView = 0;
|
||||
int outOfView = 0;
|
||||
OctreeElementBag tempBag;
|
||||
ViewFrustum viewCopy;
|
||||
copyCurrentViewFrustum(viewCopy);
|
||||
while (OctreeElementPointer elementToCheck = elementBag.extract()) {
|
||||
if (elementToCheck->isInView(viewCopy)) {
|
||||
tempBag.insert(elementToCheck);
|
||||
stillInView++;
|
||||
} else {
|
||||
outOfView++;
|
||||
}
|
||||
}
|
||||
if (stillInView > 0) {
|
||||
while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) {
|
||||
if (elementToKeepInBag->isInView(viewCopy)) {
|
||||
elementBag.insert(elementToKeepInBag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::packetSent(const NLPacket& packet) {
|
||||
_sentPacketHistory.packetSent(_sequenceNumber, packet);
|
||||
_sequenceNumber++;
|
||||
|
|
|
@ -46,23 +46,14 @@ public:
|
|||
bool shouldSuppressDuplicatePacket();
|
||||
|
||||
unsigned int getAvailable() const { return _octreePacket->bytesAvailableForWrite(); }
|
||||
int getMaxSearchLevel() const { return _maxSearchLevel; }
|
||||
void resetMaxSearchLevel() { _maxSearchLevel = 1; }
|
||||
void incrementMaxSearchLevel() { _maxSearchLevel++; }
|
||||
|
||||
int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; }
|
||||
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
|
||||
|
||||
OctreeElementBag elementBag;
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
void copyCurrentViewFrustum(ViewFrustum& viewOut) const;
|
||||
void copyLastKnownViewFrustum(ViewFrustum& viewOut) const;
|
||||
|
||||
// These are not classic setters because they are calculating and maintaining state
|
||||
// which is set asynchronously through the network receive
|
||||
bool updateCurrentViewFrustum();
|
||||
void updateLastKnownViewFrustum();
|
||||
|
||||
bool getViewSent() const { return _viewSent; }
|
||||
void setViewSent(bool viewSent);
|
||||
|
@ -70,24 +61,13 @@ public:
|
|||
bool getViewFrustumChanging() const { return _viewFrustumChanging; }
|
||||
bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; }
|
||||
|
||||
bool moveShouldDump() const;
|
||||
|
||||
quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }
|
||||
void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; }
|
||||
|
||||
bool hasLodChanged() const { return _lodChanged; }
|
||||
|
||||
OctreeSceneStats stats;
|
||||
|
||||
void dumpOutOfView();
|
||||
|
||||
quint64 getLastRootTimestamp() const { return _lastRootTimestamp; }
|
||||
void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; }
|
||||
unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; }
|
||||
int getDuplicatePacketCount() const { return _duplicatePacketCount; }
|
||||
|
||||
void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; }
|
||||
|
||||
void nodeKilled();
|
||||
bool isShuttingDown() const { return _isShuttingDown; }
|
||||
|
||||
|
@ -118,18 +98,11 @@ private:
|
|||
int _duplicatePacketCount { 0 };
|
||||
quint64 _firstSuppressedPacket { usecTimestampNow() };
|
||||
|
||||
int _maxSearchLevel { 1 };
|
||||
int _maxLevelReachedInLastSearch { 1 };
|
||||
|
||||
mutable QMutex _viewMutex { QMutex::Recursive };
|
||||
ViewFrustum _currentViewFrustum;
|
||||
ViewFrustum _lastKnownViewFrustum;
|
||||
quint64 _lastTimeBagEmpty { 0 };
|
||||
bool _viewFrustumChanging { false };
|
||||
bool _viewFrustumJustStoppedChanging { true };
|
||||
|
||||
OctreeSendThread* _octreeSendThread { nullptr };
|
||||
|
||||
// watch for LOD changes
|
||||
int _lastClientBoundaryLevelAdjust { 0 };
|
||||
float _lastClientOctreeSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
|
||||
|
@ -138,16 +111,12 @@ private:
|
|||
|
||||
OCTREE_PACKET_SEQUENCE _sequenceNumber { 0 };
|
||||
|
||||
quint64 _lastRootTimestamp { 0 };
|
||||
|
||||
PacketType _myPacketType { PacketType::Unknown };
|
||||
bool _isShuttingDown { false };
|
||||
|
||||
SentPacketHistory _sentPacketHistory;
|
||||
QQueue<OCTREE_PACKET_SEQUENCE> _nackedSequenceNumbers;
|
||||
|
||||
quint64 _sceneSendStartTime = 0;
|
||||
|
||||
std::array<char, udt::MAX_PACKET_SIZE> _lastOctreePayload;
|
||||
|
||||
QJsonObject _lastCheckJSONParameters;
|
||||
|
|
|
@ -284,10 +284,6 @@ void OctreeSceneStats::didntFit(const OctreeElementPointer& element) {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeSceneStats::colorBitsWritten() {
|
||||
_colorBitsWritten++;
|
||||
}
|
||||
|
||||
void OctreeSceneStats::existsBitsWritten() {
|
||||
_existsBitsWritten++;
|
||||
}
|
||||
|
|
|
@ -79,9 +79,6 @@ public:
|
|||
/// Track that a element was due to be sent, but didn't fit in the packet and was moved to next packet
|
||||
void didntFit(const OctreeElementPointer& element);
|
||||
|
||||
/// Track that the color bitmask was was sent as part of computation of a scene
|
||||
void colorBitsWritten();
|
||||
|
||||
/// Track that the exists in tree bitmask was was sent as part of computation of a scene
|
||||
void existsBitsWritten();
|
||||
|
||||
|
|
|
@ -248,22 +248,6 @@ void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectio
|
|||
}
|
||||
}
|
||||
|
||||
unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels) {
|
||||
int codeLength = numberOfThreeBitSectionsInCode(originalOctalCode);
|
||||
unsigned char* newCode = NULL;
|
||||
if (codeLength > chopLevels) {
|
||||
int newLength = codeLength - chopLevels;
|
||||
newCode = new unsigned char[newLength+1];
|
||||
*newCode = newLength; // set the length byte
|
||||
|
||||
for (int section = chopLevels; section < codeLength; section++) {
|
||||
char sectionValue = getOctalCodeSectionValue(originalOctalCode, section);
|
||||
setOctalCodeSectionValue(newCode, section - chopLevels, sectionValue);
|
||||
}
|
||||
}
|
||||
return newCode;
|
||||
}
|
||||
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) {
|
||||
if (!possibleAncestor || !possibleDescendent) {
|
||||
return false;
|
||||
|
|
|
@ -40,8 +40,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2;
|
|||
/// \param int maxBytes number of bytes that octalCode is expected to be, -1 if unknown
|
||||
int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH);
|
||||
|
||||
unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels);
|
||||
|
||||
const int CHECK_NODE_ONLY = -1;
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent,
|
||||
int descendentsChild = CHECK_NODE_ONLY);
|
||||
|
|
|
@ -1303,7 +1303,7 @@ Script.scriptEnding.connect(function () {
|
|||
Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE));
|
||||
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE));
|
||||
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL));
|
||||
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM));
|
||||
Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS));
|
||||
|
||||
|
||||
progressDialog.cleanup();
|
||||
|
|
|
@ -15,7 +15,7 @@ Script.include(Script.resolvePath("../libraries/controllers.js"));
|
|||
Script.include(Script.resolvePath("../libraries/Xform.js"));
|
||||
|
||||
var Y_AXIS = {x: 0, y: 1, z: 0};
|
||||
var DEFAULT_DPI = 34;
|
||||
var DEFAULT_DPI = 31;
|
||||
var DEFAULT_WIDTH = 0.4375;
|
||||
var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees
|
||||
var SENSOR_TO_ROOM_MATRIX = -2;
|
||||
|
@ -31,12 +31,12 @@ var DELAY_FOR_30HZ = 33; // milliseconds
|
|||
|
||||
|
||||
// will need to be recaclulated if dimensions of fbx model change.
|
||||
var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269};
|
||||
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
|
||||
|
||||
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png";
|
||||
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png";
|
||||
// var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx";
|
||||
var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx";
|
||||
var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx";
|
||||
|
||||
// returns object with two fields:
|
||||
// * position - position in front of the user
|
||||
|
@ -44,14 +44,28 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-
|
|||
function calcSpawnInfo(hand, landscape) {
|
||||
var finalPosition;
|
||||
|
||||
var LEFT_HAND = Controller.Standard.LeftHand;
|
||||
var sensorToWorldScale = MyAvatar.sensorToWorldScale;
|
||||
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
|
||||
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
|
||||
var dominantHandRotation = MyAvatar.getDominantHand() === "right" ? -20 : 20;
|
||||
var offsetRotation = Quat.fromPitchYawRollDegrees(0, dominantHandRotation, 0);
|
||||
var forward = Vec3.multiplyQbyV(offsetRotation, Quat.getForward(Quat.cancelOutRollAndPitch(headRot)));
|
||||
var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale;
|
||||
finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward));
|
||||
var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
|
||||
var headRot = Quat.cancelOutRollAndPitch((HMD.active && Camera.mode === "first person") ?
|
||||
HMD.orientation : Camera.orientation);
|
||||
|
||||
var right = Quat.getRight(headRot);
|
||||
var forward = Quat.getForward(headRot);
|
||||
var up = Quat.getUp(headRot);
|
||||
|
||||
var FORWARD_OFFSET = 0.5 * sensorToWorldScale;
|
||||
var UP_OFFSET = -0.16 * sensorToWorldScale;
|
||||
var RIGHT_OFFSET = ((hand === LEFT_HAND) ? -0.18 : 0.18) * sensorToWorldScale;
|
||||
|
||||
var forwardPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward));
|
||||
var lateralPosition = Vec3.sum(forwardPosition, Vec3.multiply(RIGHT_OFFSET, right));
|
||||
finalPosition = Vec3.sum(lateralPosition, Vec3.multiply(UP_OFFSET, up));
|
||||
|
||||
var MY_EYES = { x: 0.0, y: 0.15, z: 0.0 };
|
||||
var lookAtEndPosition = Vec3.sum(Vec3.multiply(RIGHT_OFFSET, right), Vec3.multiply(FORWARD_OFFSET, forward));
|
||||
var orientation = Quat.lookAt(MY_EYES, lookAtEndPosition, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
|
||||
|
||||
return {
|
||||
position: finalPosition,
|
||||
rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180)
|
||||
|
@ -119,11 +133,11 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
Overlays.deleteOverlay(this.webOverlayID);
|
||||
}
|
||||
|
||||
var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model.
|
||||
var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model.
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET;
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor;
|
||||
var screenWidth = 0.9275 * tabletWidth;
|
||||
var screenHeight = 0.8983 * tabletHeight;
|
||||
this.webOverlayID = Overlays.addOverlay("web3d", {
|
||||
name: "WebTablet Web",
|
||||
url: url,
|
||||
|
@ -139,7 +153,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
visible: visible
|
||||
});
|
||||
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003;
|
||||
var HOME_BUTTON_Y_OFFSET = (tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor;
|
||||
// FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here
|
||||
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
|
||||
this.homeButtonID = Overlays.addOverlay("circle3d", {
|
||||
|
@ -277,8 +291,8 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) {
|
|||
var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale;
|
||||
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
|
||||
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
var screenWidth = 0.9275 * tabletWidth;
|
||||
var screenHeight = 0.8983 * tabletHeight;
|
||||
Overlays.editOverlay(this.webOverlayID, {
|
||||
rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW),
|
||||
dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1}
|
||||
|
|
|
@ -373,7 +373,6 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
|
|||
if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale;
|
||||
var sensorScaleOffsetOverride = 1;
|
||||
var SENSOR_TO_ROOM_MATRIX = 65534;
|
||||
|
@ -383,8 +382,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
|
|||
}
|
||||
|
||||
// will need to be recaclulated if dimensions of fbx model change.
|
||||
var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269};
|
||||
var DEFAULT_DPI = 34;
|
||||
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
|
||||
var DEFAULT_DPI = 31;
|
||||
var DEFAULT_WIDTH = 0.4375;
|
||||
|
||||
// scale factor of natural tablet dimensions.
|
||||
|
@ -402,9 +401,10 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
|
|||
// update webOverlay
|
||||
var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model.
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET;
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor;
|
||||
print(WEB_ENTITY_Y_OFFSET);
|
||||
var screenWidth = 0.9275 * tabletWidth;
|
||||
var screenHeight = 0.8983 * tabletHeight;
|
||||
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;
|
||||
Overlays.editOverlay(HMD.tabletScreenID, {
|
||||
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
|
@ -413,7 +413,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
|
|||
});
|
||||
|
||||
// update homeButton
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride;
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride;
|
||||
// FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here
|
||||
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
|
||||
Overlays.editOverlay(HMD.homeButtonID, {
|
||||
|
|
454
server-console/package-lock.json
generated
454
server-console/package-lock.json
generated
|
@ -4,6 +4,12 @@
|
|||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.2.tgz",
|
||||
"integrity": "sha512-A6Uv1anbsCvrRDtaUXS2xZ5tlzD+Kg7yMRlSLFDy3z0r7KlGXDzL14vELXIAgpk2aJbU3XeZZQRcEkLkowT92g==",
|
||||
"dev": true
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
|
@ -40,12 +46,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
|
||||
},
|
||||
"any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
|
||||
"dev": true
|
||||
},
|
||||
"array-find-index": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz",
|
||||
|
@ -148,12 +148,6 @@
|
|||
"lru-cache": "4.0.1"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz",
|
||||
"integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=",
|
||||
"dev": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz",
|
||||
|
@ -217,16 +211,6 @@
|
|||
"hoek": "2.16.3"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
|
||||
"integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "0.3.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
|
@ -515,29 +499,70 @@
|
|||
"jsbn": "0.1.0"
|
||||
}
|
||||
},
|
||||
"electron-download": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-2.1.1.tgz",
|
||||
"integrity": "sha1-AH07HyrTco0nzP5PhJayY/kTijE=",
|
||||
"electron": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.8.4.tgz",
|
||||
"integrity": "sha512-2f1cx0G3riMFODXFftF5AHXy+oHfhpntZHTDN66Hxtl09gmEr42B3piNEod9MEmw72f75LX2JfeYceqq1PF8cA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.2.0",
|
||||
"home-path": "1.0.3",
|
||||
"@types/node": "8.10.2",
|
||||
"electron-download": "3.3.0",
|
||||
"extract-zip": "1.5.0"
|
||||
}
|
||||
},
|
||||
"electron-download": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz",
|
||||
"integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"fs-extra": "0.30.0",
|
||||
"home-path": "1.0.5",
|
||||
"minimist": "1.2.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"mv": "2.1.1",
|
||||
"nugget": "1.6.2",
|
||||
"path-exists": "1.0.0",
|
||||
"rc": "1.1.6"
|
||||
"nugget": "2.0.1",
|
||||
"path-exists": "2.1.0",
|
||||
"rc": "1.1.6",
|
||||
"semver": "5.5.0",
|
||||
"sumchecker": "1.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "0.7.1"
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
|
||||
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.3",
|
||||
"jsonfile": "2.2.3",
|
||||
"klaw": "1.3.1",
|
||||
"path-is-absolute": "1.0.0",
|
||||
"rimraf": "2.6.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
|
||||
"dev": true
|
||||
},
|
||||
"sumchecker": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz",
|
||||
"integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"es6-promise": "4.2.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -579,9 +604,9 @@
|
|||
}
|
||||
},
|
||||
"electron-packager": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-11.0.0.tgz",
|
||||
"integrity": "sha512-ufyYMe3Gt6IEZm9RuG+KK3Nh+V2jZHWg9gihp8wylUNtleQihECIXtQdpPJxH9740XFERVPraNEaa7cZvDzpyw==",
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.0.0.tgz",
|
||||
"integrity": "sha1-uC0k14ovIUA7v9FmpbFWmJTVzQw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asar": "0.14.2",
|
||||
|
@ -590,20 +615,25 @@
|
|||
"electron-osx-sign": "0.4.8",
|
||||
"extract-zip": "1.5.0",
|
||||
"fs-extra": "5.0.0",
|
||||
"galactus": "0.2.0",
|
||||
"get-package-info": "1.0.0",
|
||||
"mz": "2.7.0",
|
||||
"nodeify": "1.0.1",
|
||||
"parse-author": "2.0.0",
|
||||
"pify": "3.0.0",
|
||||
"plist": "2.1.0",
|
||||
"pruner": "0.0.7",
|
||||
"rcedit": "0.9.0",
|
||||
"rcedit": "1.0.0",
|
||||
"resolve": "1.5.0",
|
||||
"sanitize-filename": "1.6.1",
|
||||
"semver": "5.5.0",
|
||||
"yargs-parser": "8.1.0"
|
||||
"yargs-parser": "9.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
|
@ -726,6 +756,12 @@
|
|||
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
|
||||
"dev": true
|
||||
},
|
||||
"rcedit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.0.0.tgz",
|
||||
"integrity": "sha512-W7DNa34x/3OgWyDHsI172AG/Lr/lZ+PkavFkHj0QhhkBRcV9QTmRJE1tDKrWkx8XHPSBsmZkNv9OKue6pncLFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
|
@ -746,19 +782,18 @@
|
|||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz",
|
||||
"integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=",
|
||||
"dev": true
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz",
|
||||
"integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-prebuilt": {
|
||||
"version": "0.37.5",
|
||||
"resolved": "https://registry.npmjs.org/electron-prebuilt/-/electron-prebuilt-0.37.5.tgz",
|
||||
"integrity": "sha1-OkGJgod4FdOnrB+bLi9KcPQg/3A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"electron-download": "2.1.1",
|
||||
"extract-zip": "1.5.0"
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
|
||||
|
@ -787,6 +822,12 @@
|
|||
"is-arrayish": "0.2.1"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
|
||||
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
|
@ -873,6 +914,62 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"flora-colossus": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-0.0.2.tgz",
|
||||
"integrity": "sha1-fRvimh8X+k8isb1hSC+Gw04HuQE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "3.1.0",
|
||||
"fs-extra": "4.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
|
||||
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.3",
|
||||
"jsonfile": "4.0.0",
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
|
@ -904,6 +1001,63 @@
|
|||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"galactus": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.0.tgz",
|
||||
"integrity": "sha1-w9Y7pVAkZv5A6mfMaJCFs90kqPw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "3.1.0",
|
||||
"flora-colossus": "0.0.2",
|
||||
"fs-extra": "4.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
|
||||
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.3",
|
||||
"jsonfile": "4.0.0",
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
|
@ -1107,9 +1261,9 @@
|
|||
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
|
||||
},
|
||||
"home-path": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.3.tgz",
|
||||
"integrity": "sha1-ns5Z/sPwMubRC1Q0/uJk30wt4y8=",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz",
|
||||
"integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=",
|
||||
"dev": true
|
||||
},
|
||||
"hosted-git-info": {
|
||||
|
@ -1461,15 +1615,6 @@
|
|||
"mime-db": "1.22.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
|
||||
"integrity": "sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.3"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
|
@ -1723,61 +1868,9 @@
|
|||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
|
||||
"dev": true
|
||||
},
|
||||
"mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "0.5.1",
|
||||
"ncp": "2.0.0",
|
||||
"rimraf": "2.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inflight": "1.0.4",
|
||||
"inherits": "2.0.1",
|
||||
"minimatch": "3.0.0",
|
||||
"once": "1.3.3",
|
||||
"path-is-absolute": "1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "6.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"any-promise": "1.3.0",
|
||||
"object-assign": "4.0.1",
|
||||
"thenify-all": "1.6.0"
|
||||
}
|
||||
},
|
||||
"ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"node-notifier": {
|
||||
|
@ -1843,27 +1936,27 @@
|
|||
}
|
||||
},
|
||||
"nugget": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz",
|
||||
"integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz",
|
||||
"integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.2.0",
|
||||
"debug": "2.6.9",
|
||||
"minimist": "1.2.0",
|
||||
"pretty-bytes": "1.0.4",
|
||||
"progress-stream": "1.2.0",
|
||||
"request": "2.71.0",
|
||||
"single-line-log": "0.4.1",
|
||||
"single-line-log": "1.1.2",
|
||||
"throttleit": "0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "0.7.1"
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"throttleit": {
|
||||
|
@ -1966,10 +2059,13 @@
|
|||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz",
|
||||
"integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=",
|
||||
"dev": true
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
|
||||
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pinkie-promise": "2.0.1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.0",
|
||||
|
@ -2070,46 +2166,6 @@
|
|||
"is-promise": "1.0.1"
|
||||
}
|
||||
},
|
||||
"pruner": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pruner/-/pruner-0.0.7.tgz",
|
||||
"integrity": "sha1-NF+8s+gHARY6HXrfVrrCKaWh5ME=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs-extra": "4.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
|
||||
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.3",
|
||||
"jsonfile": "4.0.0",
|
||||
"universalify": "0.1.1"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
|
@ -2153,12 +2209,6 @@
|
|||
"strip-json-comments": "1.0.4"
|
||||
}
|
||||
},
|
||||
"rcedit": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.9.0.tgz",
|
||||
"integrity": "sha1-ORDfVzRTmeKwMl9KUZAH+J5V7xw=",
|
||||
"dev": true
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||
|
@ -2288,10 +2338,13 @@
|
|||
"dev": true
|
||||
},
|
||||
"single-line-log": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz",
|
||||
"integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=",
|
||||
"dev": true
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz",
|
||||
"integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "1.0.1"
|
||||
}
|
||||
},
|
||||
"sntp": {
|
||||
"version": "1.0.9",
|
||||
|
@ -2476,24 +2529,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"thenify": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
|
||||
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"any-promise": "1.3.0"
|
||||
}
|
||||
},
|
||||
"thenify-all": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"thenify": "3.3.0"
|
||||
}
|
||||
},
|
||||
"throttleit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
|
||||
|
@ -2694,23 +2729,6 @@
|
|||
"y18n": "3.2.1"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz",
|
||||
"integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
""
|
||||
],
|
||||
"devDependencies": {
|
||||
"electron-packager": "^11.0.0",
|
||||
"electron-prebuilt": "0.37.5"
|
||||
"electron-packager": "^12.0.0",
|
||||
"electron": "1.8.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -25,6 +25,7 @@
|
|||
"dependencies": {
|
||||
"always-tail": "0.2.0",
|
||||
"cheerio": "^0.19.0",
|
||||
"electron-log": "1.1.1",
|
||||
"extend": "^3.0.0",
|
||||
"fs-extra": "^1.0.0",
|
||||
"node-notifier": "^5.2.1",
|
||||
|
@ -32,7 +33,6 @@
|
|||
"request": "^2.67.0",
|
||||
"request-progress": "1.0.2",
|
||||
"tar-fs": "^1.12.0",
|
||||
"yargs": "^3.30.0",
|
||||
"electron-log": "1.1.1"
|
||||
"yargs": "^3.30.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ function ready() {
|
|||
console.log("Ready");
|
||||
|
||||
const electron = require('electron');
|
||||
const remote = require('remote');
|
||||
const remote = electron.remote;
|
||||
window.$ = require('./vendor/jquery/jquery-2.1.4.min.js');
|
||||
|
||||
$(".state").hide();
|
||||
|
|
|
@ -8,9 +8,9 @@ const nativeImage = electron.nativeImage;
|
|||
const notifier = require('node-notifier');
|
||||
const util = require('util');
|
||||
const dialog = electron.dialog;
|
||||
const Menu = require('menu');
|
||||
const Tray = require('tray');
|
||||
const shell = require('shell');
|
||||
const Menu = electron.Menu;
|
||||
const Tray = electron.Tray;
|
||||
const shell = electron.shell;
|
||||
const os = require('os');
|
||||
const childProcess = require('child_process');
|
||||
const path = require('path');
|
||||
|
|
|
@ -226,7 +226,7 @@ Process.prototype = extend(Process.prototype, {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
var signal = force ? 'SIGKILL' : null;
|
||||
var signal = force ? 'SIGKILL' : 'SIGTERM';
|
||||
this.child.kill(signal);
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,6 @@ int main(int argc, char** argv) {
|
|||
QByteArray packet = file.readAll();
|
||||
EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties());
|
||||
ReadBitstreamToTreeParams params;
|
||||
params.bitstreamVersion = 33;
|
||||
|
||||
auto start = usecTimestampNow();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
|
|
Loading…
Reference in a new issue