Merge pull request #5778 from jherico/homer

Fixing some entity race condition crashes
This commit is contained in:
Seth Alves 2015-09-11 17:54:53 -07:00
commit e65ef93663
39 changed files with 1535 additions and 1452 deletions

View file

@ -156,20 +156,20 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
packet->pos(), maxSize); packet->pos(), maxSize);
} }
quint64 startLock = usecTimestampNow(); quint64 startProcess, startLock = usecTimestampNow();
_myServer->getOctree()->lockForWrite(); int editDataBytesRead;
quint64 startProcess = usecTimestampNow(); _myServer->getOctree()->withWriteLock([&] {
int editDataBytesRead = startProcess = usecTimestampNow();
_myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode); editDataBytesRead =
_myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode);
});
quint64 endProcess = usecTimestampNow();
if (debugProcessPacket) { if (debugProcessPacket) {
qDebug() << "OctreeInboundPacketProcessor::processPacket() after processEditPacketData()..." qDebug() << "OctreeInboundPacketProcessor::processPacket() after processEditPacketData()..."
<< "editDataBytesRead=" << editDataBytesRead; << "editDataBytesRead=" << editDataBytesRead;
} }
_myServer->getOctree()->unlock();
quint64 endProcess = usecTimestampNow();
editsInPacket++; editsInPacket++;
quint64 thisProcessTime = endProcess - startProcess; quint64 thisProcessTime = endProcess - startProcess;
quint64 thisLockWaitTime = startProcess - startLock; quint64 thisLockWaitTime = startProcess - startLock;

View file

@ -416,83 +416,83 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
if (!nodeData->elementBag.isEmpty()) { if (!nodeData->elementBag.isEmpty()) {
quint64 lockWaitStart = usecTimestampNow(); quint64 lockWaitStart = usecTimestampNow();
_myServer->getOctree()->lockForRead(); _myServer->getOctree()->withReadLock([&]{
quint64 lockWaitEnd = usecTimestampNow(); quint64 lockWaitEnd = usecTimestampNow();
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
quint64 encodeStart = usecTimestampNow(); quint64 encodeStart = usecTimestampNow();
OctreeElementPointer subTree = nodeData->elementBag.extract(); OctreeElementPointer subTree = nodeData->elementBag.extract();
/* TODO: Looking for a way to prevent locking and encoding a tree that is not /* TODO: Looking for a way to prevent locking and encoding a tree that is not
// going to result in any packets being sent... // going to result in any packets being sent...
// //
// If our node is root, and the root hasn't changed, and our view hasn't changed, // If our node is root, and the root hasn't changed, and our view hasn't changed,
// and we've already seen at least one duplicate packet, then we probably don't need // and we've already seen at least one duplicate packet, then we probably don't need
// to lock the tree and encode, because the result should be that no bytes will be // to lock the tree and encode, because the result should be that no bytes will be
// encoded, and this will be a duplicate packet from the last one we sent... // encoded, and this will be a duplicate packet from the last one we sent...
OctreeElementPointer root = _myServer->getOctree()->getRoot(); OctreeElementPointer root = _myServer->getOctree()->getRoot();
bool skipEncode = false; bool skipEncode = false;
if ( if (
(subTree == root) (subTree == root)
&& (nodeData->getLastRootTimestamp() == root->getLastChanged()) && (nodeData->getLastRootTimestamp() == root->getLastChanged())
&& !viewFrustumChanged && !viewFrustumChanged
&& (nodeData->getDuplicatePacketCount() > 0) && (nodeData->getDuplicatePacketCount() > 0)
) { ) {
qDebug() << "is root, root not changed, view not changed, already seen a duplicate!" qDebug() << "is root, root not changed, view not changed, already seen a duplicate!"
<< "Can we skip it?"; << "Can we skip it?";
skipEncode = true; skipEncode = true;
}
*/
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
float octreeSizeScale = nodeData->getOctreeSizeScale();
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData);
// TODO: should this include the lock time or not? This stat is sent down to the client,
// it seems like it may be a good idea to include the lock time as part of the encode time
// are reported to client. Since you can encode without the lock
nodeData->stats.encodeStarted();
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
quint64 encodeEnd = usecTimestampNow();
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
// sent the entire scene. We want to know this below so we'll actually write this content into
// the packet and send it
completedScene = nodeData->elementBag.isEmpty();
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case.
if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) {
if (_packetData.hasContent() && bytesWritten == 0 &&
params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
} }
} else { */
// in compressed mode and we are trying to pack more... and we don't care if the _packetData has
// content or not... because in this case even if we were unable to pack any data, we want to drop
// below to our sendNow logic, but we do want to track that we attempted to pack extra
extraPackingAttempts++;
if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
}
}
nodeData->stats.encodeStopped(); bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
_myServer->getOctree()->unlock(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
float octreeSizeScale = nodeData->getOctreeSizeScale();
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData);
// TODO: should this include the lock time or not? This stat is sent down to the client,
// it seems like it may be a good idea to include the lock time as part of the encode time
// are reported to client. Since you can encode without the lock
nodeData->stats.encodeStarted();
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
quint64 encodeEnd = usecTimestampNow();
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
// sent the entire scene. We want to know this below so we'll actually write this content into
// the packet and send it
completedScene = nodeData->elementBag.isEmpty();
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case.
if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) {
if (_packetData.hasContent() && bytesWritten == 0 &&
params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
}
} else {
// in compressed mode and we are trying to pack more... and we don't care if the _packetData has
// content or not... because in this case even if we were unable to pack any data, we want to drop
// below to our sendNow logic, but we do want to track that we attempted to pack extra
extraPackingAttempts++;
if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
}
}
nodeData->stats.encodeStopped();
});
} else { } else {
// If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0 // If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0
bytesWritten = 0; bytesWritten = 0;

View file

@ -0,0 +1,175 @@
// entityEditStressTest.js
//
// Created by Seiji Emery on 8/31/15
// Copyright 2015 High Fidelity, Inc.
//
// Stress tests the client + server-side entity trees by spawning huge numbers of entities in
// close proximity to your avatar and updating them continuously (ie. applying position edits),
// with the intent of discovering crashes and other bugs related to the entity, scripting,
// rendering, networking, and/or physics subsystems.
//
// This script was originally created to find + diagnose an a clientside crash caused by improper
// locking of the entity tree, but can be reused for other purposes.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var NUM_ENTITIES = 20000; // number of entities to spawn
var ENTITY_SPAWN_LIMIT = 1000;
var ENTITY_SPAWN_INTERVAL = 0.1;
var UPDATE_INTERVAL = 0.05; // Re-randomize the entity's position every x seconds / ms
var ENTITY_LIFETIME = 30; // Entity timeout (when/if we crash, we need the entities to delete themselves)
var KEEPALIVE_INTERVAL = 5; // Refreshes the timeout every X seconds
var RADIUS = 5.0; // Spawn within this radius (square)
var Y_OFFSET = 1.5; // Spawn at an offset below the avatar
var TEST_ENTITY_NAME = "EntitySpawnTest";
(function () {
this.makeEntity = function (properties) {
var entity = Entities.addEntity(properties);
// print("spawning entity: " + JSON.stringify(properties));
return {
update: function (properties) {
Entities.editEntity(entity, properties);
},
destroy: function () {
Entities.deleteEntity(entity)
},
getAge: function () {
return Entities.getEntityProperties(entity).age;
}
};
}
this.randomPositionXZ = function (center, radius) {
return {
x: center.x + (Math.random() * radius * 2.0) - radius,
y: center.y,
z: center.z + (Math.random() * radius * 2.0) - radius
};
}
this.randomColor = function () {
var shade = Math.floor(Math.random() * 255);
var hue = Math.floor(Math.random() * (255 - shade));
return {
red: shade + hue,
green: shade,
blue: shade
};
}
this.randomDimensions = function () {
return {
x: 0.1 + Math.random() * 0.5,
y: 0.1 + Math.random() * 0.1,
z: 0.1 + Math.random() * 0.5
};
}
})();
(function () {
var entities = [];
var entitiesToCreate = 0;
var entitiesSpawned = 0;
function clear () {
var ids = Entities.findEntities(MyAvatar.position, 50);
var that = this;
ids.forEach(function(id) {
var properties = Entities.getEntityProperties(id);
if (properties.name == TEST_ENTITY_NAME) {
Entities.deleteEntity(id);
}
}, this);
}
function createEntities () {
print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")");
entitiesToCreate = NUM_ENTITIES;
Script.update.connect(spawnEntities);
}
var spawnTimer = 0.0;
function spawnEntities (dt) {
if (entitiesToCreate <= 0) {
Script.update.disconnect(spawnEntities);
print("Finished spawning entities");
}
else if ((spawnTimer -= dt) < 0.0){
spawnTimer = ENTITY_SPAWN_INTERVAL;
var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT);
print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")");
entitiesToCreate -= n;
var center = MyAvatar.position;
center.y -= Y_OFFSET;
for (; n > 0; --n) {
entities.push(makeEntity({
type: "Box",
name: TEST_ENTITY_NAME,
position: randomPositionXZ(center, RADIUS),
color: randomColor(),
dimensions: randomDimensions(),
lifetime: ENTITY_LIFETIME
}));
}
}
}
function despawnEntities () {
print("despawning entities");
entities.forEach(function (entity) {
entity.destroy();
});
entities = [];
}
var keepAliveTimer = 0.0;
var updateTimer = 0.0;
// Runs the following entity updates:
// a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and
// b) re-randomizes its position every UPDATE_INTERVAL seconds.
// This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again).
function updateEntities (dt) {
var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false;
var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false;
if (updateLifetime || updateProperties) {
var center = MyAvatar.position;
center.y -= Y_OFFSET;
entities.forEach((updateLifetime && updateProperties && function (entity) {
entity.update({
lifetime: entity.getAge() + ENTITY_LIFETIME,
position: randomPositionXZ(center, RADIUS)
});
}) || (updateLifetime && function (entity) {
entity.update({
lifetime: entity.getAge() + ENTITY_LIFETIME
});
}) || (updateProperties && function (entity) {
entity.update({
position: randomPositionXZ(center, RADIUS)
});
}) || null, this);
}
}
function init () {
Script.update.disconnect(init);
clear();
createEntities();
Script.update.connect(updateEntities);
Script.scriptEnding.connect(despawnEntities);
}
Script.update.connect(init);
})();

View file

@ -0,0 +1,68 @@
// createBoxes.js
// part of bubblewand
//
// Created by James B. Pollack @imgntn -- 09/07/2015
// Copyright 2015 High Fidelity, Inc.
//
// Loads a wand model and attaches the bubble wand behavior.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/utilities.js");
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/libraries/utils.js");
var bubbleModel = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/bubble/bubble.fbx?' + randInt(0, 10000);;
//var scriptURL'http://hifi-public.s3.amazonaws.com/james/bubblewand/scripts/wand.js?'+randInt(0,10000);
//for local testing
//var scriptURL = "http://localhost:8080/scripts/setRecurringTimeout.js?" + randInt(0, 10000);
var scriptURL='http://hifi-public.s3.amazonaws.com/james/debug/timeouts/setRecurringTimeout.js?'+ randInt(0, 10000);
//create the wand in front of the avatar
var boxes=[];
var TEST_ENTITY_NAME = "TimerScript";
var TOTAL_ENTITIES = 100;
for (var i = 0; i < TOTAL_ENTITIES; i++) {
var box = Entities.addEntity({
type: "Box",
name: TEST_ENTITY_NAME,
position: {
x: randInt(0, 100) - 50 + MyAvatar.position.x,
y: randInt(0, 100) - 50 + MyAvatar.position.x,
z: randInt(0, 100) - 50 + MyAvatar.position.x,
},
dimensions: {
x: 1,
y: 1,
z: 1,
},
color: {
red: 255,
green: 0,
blue: 0,
},
//must be enabled to be grabbable in the physics engine
collisionsWillMove: true,
shapeType: 'box',
lifetime:60,
script: scriptURL
});
boxes.push(box)
}
function cleanup() {
while (boxes.length > 0) {
Entities.deleteEntity(boxes.pop());
}
}
Script.scriptEnding.connect(cleanup);

View file

@ -864,9 +864,7 @@ void Application::emptyLocalCache() {
Application::~Application() { Application::~Application() {
EntityTreePointer tree = _entities.getTree(); EntityTreePointer tree = _entities.getTree();
tree->lockForWrite(); tree->setSimulation(NULL);
_entities.getTree()->setSimulation(NULL);
tree->unlock();
_octreeProcessor.terminate(); _octreeProcessor.terminate();
_entityEditSender.terminate(); _entityEditSender.terminate();
@ -888,7 +886,9 @@ Application::~Application() {
// remove avatars from physics engine // remove avatars from physics engine
DependencyManager::get<AvatarManager>()->clearOtherAvatars(); DependencyManager::get<AvatarManager>()->clearOtherAvatars();
_physicsEngine->deleteObjects(DependencyManager::get<AvatarManager>()->getObjectsToDelete()); VectorOfMotionStates motionStates;
DependencyManager::get<AvatarManager>()->getObjectsToDelete(motionStates);
_physicsEngine->deleteObjects(motionStates);
DependencyManager::destroy<OffscreenUi>(); DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<AvatarManager>(); DependencyManager::destroy<AvatarManager>();
@ -2887,46 +2887,40 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("physics"); PerformanceTimer perfTimer("physics");
_myAvatar->relayDriveKeysToCharacterController(); _myAvatar->relayDriveKeysToCharacterController();
_entitySimulation.lock(); static VectorOfMotionStates motionStates;
_physicsEngine->deleteObjects(_entitySimulation.getObjectsToDelete()); _entitySimulation.getObjectsToDelete(motionStates);
_entitySimulation.unlock(); _physicsEngine->deleteObjects(motionStates);
_entities.getTree()->lockForWrite(); _entities.getTree()->withWriteLock([&] {
_entitySimulation.lock(); _entitySimulation.getObjectsToAdd(motionStates);
_physicsEngine->addObjects(_entitySimulation.getObjectsToAdd()); _physicsEngine->addObjects(motionStates);
_entitySimulation.unlock();
_entities.getTree()->unlock();
_entities.getTree()->lockForWrite(); });
_entitySimulation.lock(); _entities.getTree()->withWriteLock([&] {
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(_entitySimulation.getObjectsToChange()); _entitySimulation.getObjectsToChange(motionStates);
_entitySimulation.setObjectsToChange(stillNeedChange); VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
_entitySimulation.unlock(); _entitySimulation.setObjectsToChange(stillNeedChange);
_entities.getTree()->unlock(); });
_entitySimulation.lock();
_entitySimulation.applyActionChanges(); _entitySimulation.applyActionChanges();
_entitySimulation.unlock();
AvatarManager* avatarManager = DependencyManager::get<AvatarManager>().data(); AvatarManager* avatarManager = DependencyManager::get<AvatarManager>().data();
_physicsEngine->deleteObjects(avatarManager->getObjectsToDelete()); avatarManager->getObjectsToDelete(motionStates);
_physicsEngine->addObjects(avatarManager->getObjectsToAdd()); _physicsEngine->deleteObjects(motionStates);
_physicsEngine->changeObjects(avatarManager->getObjectsToChange()); avatarManager->getObjectsToAdd(motionStates);
_physicsEngine->addObjects(motionStates);
_entities.getTree()->lockForWrite(); avatarManager->getObjectsToChange(motionStates);
_physicsEngine->stepSimulation(); _physicsEngine->changeObjects(motionStates);
_entities.getTree()->unlock();
_entities.getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
});
if (_physicsEngine->hasOutgoingChanges()) { if (_physicsEngine->hasOutgoingChanges()) {
_entities.getTree()->lockForWrite(); _entities.getTree()->withWriteLock([&] {
_entitySimulation.lock(); _entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID());
_entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID()); avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges());
_entitySimulation.unlock(); });
_entities.getTree()->unlock();
_entities.getTree()->lockForWrite();
avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges());
_entities.getTree()->unlock();
auto collisionEvents = _physicsEngine->getCollisionEvents(); auto collisionEvents = _physicsEngine->getCollisionEvents();
avatarManager->handleCollisionEvents(collisionEvents); avatarManager->handleCollisionEvents(collisionEvents);
@ -3045,27 +3039,21 @@ int Application::sendNackPackets() {
return; return;
} }
_octreeSceneStatsLock.lockForRead(); QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers;
_octreeServerSceneStats.withReadLock([&] {
// retreive octree scene stats of this node
if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) {
return;
}
// get sequence number stats of node, prune its missing set, and make a copy of the missing set
SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats();
sequenceNumberStats.pruneMissingSet();
missingSequenceNumbers = sequenceNumberStats.getMissingSet();
});
// retreive octree scene stats of this node
if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) {
_octreeSceneStatsLock.unlock();
return;
}
// get sequence number stats of node, prune its missing set, and make a copy of the missing set
SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats();
sequenceNumberStats.pruneMissingSet();
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = sequenceNumberStats.getMissingSet();
_octreeSceneStatsLock.unlock();
// construct nack packet(s) for this node // construct nack packet(s) for this node
auto it = missingSequenceNumbers.constBegin(); foreach(const OCTREE_PACKET_SEQUENCE& missingNumber, missingSequenceNumbers) {
while (it != missingSequenceNumbers.constEnd()) {
OCTREE_PACKET_SEQUENCE missingNumber = *it;
nackPacketList->writePrimitive(missingNumber); nackPacketList->writePrimitive(missingNumber);
++it;
} }
if (nackPacketList->getNumPackets()) { if (nackPacketList->getNumPackets()) {
@ -3798,13 +3786,13 @@ void Application::clearDomainOctreeDetails() {
// reset the environment so that we don't erroneously end up with multiple // reset the environment so that we don't erroneously end up with multiple
// reset our node to stats and node to jurisdiction maps... since these must be changing... // reset our node to stats and node to jurisdiction maps... since these must be changing...
_entityServerJurisdictions.lockForWrite(); _entityServerJurisdictions.withWriteLock([&] {
_entityServerJurisdictions.clear(); _entityServerJurisdictions.clear();
_entityServerJurisdictions.unlock(); });
_octreeSceneStatsLock.lockForWrite(); _octreeServerSceneStats.withWriteLock([&] {
_octreeServerSceneStats.clear(); _octreeServerSceneStats.clear();
_octreeSceneStatsLock.unlock(); });
// reset the model renderer // reset the model renderer
_entities.clear(); _entities.clear();
@ -3874,29 +3862,31 @@ void Application::nodeKilled(SharedNodePointer node) {
QUuid nodeUUID = node->getUUID(); QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node... // see if this is the first we've heard of this node...
_entityServerJurisdictions.lockForRead(); _entityServerJurisdictions.withReadLock([&] {
if (_entityServerJurisdictions.find(nodeUUID) != _entityServerJurisdictions.end()) { if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) {
return;
}
unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails; VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails); voxelDetailsForCode(rootCode, rootDetails);
_entityServerJurisdictions.unlock();
qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]",
(double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s);
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server });
_entityServerJurisdictions.lockForWrite();
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
_entityServerJurisdictions.withWriteLock([&] {
_entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID)); _entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID));
} });
_entityServerJurisdictions.unlock();
// also clean up scene stats for that server // also clean up scene stats for that server
_octreeSceneStatsLock.lockForWrite(); _octreeServerSceneStats.withWriteLock([&] {
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
_octreeServerSceneStats.erase(nodeUUID); _octreeServerSceneStats.erase(nodeUUID);
} }
_octreeSceneStatsLock.unlock(); });
} else if (node->getType() == NodeType::AvatarMixer) { } else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars // our avatar mixer has gone away - clear the hash of avatars
DependencyManager::get<AvatarManager>()->clearOtherAvatars(); DependencyManager::get<AvatarManager>()->clearOtherAvatars();
@ -3914,12 +3904,12 @@ void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer
const QUuid& nodeUUID = sendingNode->getUUID(); const QUuid& nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node... // now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite(); _octreeServerSceneStats.withWriteLock([&] {
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID]; OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec()); stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec());
} }
_octreeSceneStatsLock.unlock(); });
} }
} }
@ -3933,43 +3923,42 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
const QUuid& nodeUUID = sendingNode->getUUID(); const QUuid& nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node... // now that we know the node ID, let's add these stats to the stats for that node...
_octreeSceneStatsLock.lockForWrite(); _octreeServerSceneStats.withWriteLock([&] {
OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID];
OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; statsMessageLength = octreeStats.unpackFromPacket(packet);
statsMessageLength = octreeStats.unpackFromPacket(packet);
_octreeSceneStatsLock.unlock();
// see if this is the first we've heard of this node... // see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL; NodeToJurisdictionMap* jurisdiction = NULL;
QString serverType; QString serverType;
if (sendingNode->getType() == NodeType::EntityServer) { if (sendingNode->getType() == NodeType::EntityServer) {
jurisdiction = &_entityServerJurisdictions; jurisdiction = &_entityServerJurisdictions;
serverType = "Entity"; serverType = "Entity";
} }
jurisdiction->lockForRead(); jurisdiction->withReadLock([&] {
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) { if (jurisdiction->find(nodeUUID) != jurisdiction->end()) {
jurisdiction->unlock(); return;
}
VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType), qPrintable(serverType),
(double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s);
} else { });
jurisdiction->unlock(); // store jurisdiction details for later use
} // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// store jurisdiction details for later use // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it // details from the OctreeSceneStats to construct the JurisdictionMap
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the JurisdictionMap jurisdictionMap;
// details from the OctreeSceneStats to construct the JurisdictionMap jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
JurisdictionMap jurisdictionMap; jurisdiction->withWriteLock([&] {
jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); (*jurisdiction)[nodeUUID] = jurisdictionMap;
jurisdiction->lockForWrite(); });
(*jurisdiction)[nodeUUID] = jurisdictionMap; });
jurisdiction->unlock();
return statsMessageLength; return statsMessageLength;
} }

View file

@ -253,8 +253,6 @@ public:
bool importSVOFromURL(const QString& urlString); bool importSVOFromURL(const QString& urlString);
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }
ToolWindow* getToolWindow() { return _toolWindow ; } ToolWindow* getToolWindow() { return _toolWindow ; }
@ -615,7 +613,6 @@ private:
NodeToJurisdictionMap _entityServerJurisdictions; NodeToJurisdictionMap _entityServerJurisdictions;
NodeToOctreeSceneStats _octreeServerSceneStats; NodeToOctreeSceneStats _octreeServerSceneStats;
QReadWriteLock _octreeSceneStatsLock;
ControllerScriptingInterface _controllerScriptingInterface; ControllerScriptingInterface _controllerScriptingInterface;
QPointer<LogDialog> _logDialog; QPointer<LogDialog> _logDialog;

View file

@ -44,43 +44,42 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
return; return;
} }
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); glm::quat rotation;
glm::vec3 position;
if (!tryLockForRead()) { glm::vec3 offset;
// don't risk hanging the thread running the physics simulation bool gotLock = withTryReadLock([&]{
return; auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
} glm::vec3 palmPosition;
glm::quat palmRotation;
glm::vec3 palmPosition; if (_hand == "right") {
glm::quat palmRotation; palmPosition = myAvatar->getRightPalmPosition();
if (_hand == "right") { palmRotation = myAvatar->getRightPalmRotation();
palmPosition = myAvatar->getRightPalmPosition(); } else {
palmRotation = myAvatar->getRightPalmRotation(); palmPosition = myAvatar->getLeftPalmPosition();
} else { palmRotation = myAvatar->getLeftPalmRotation();
palmPosition = myAvatar->getLeftPalmPosition();
palmRotation = myAvatar->getLeftPalmRotation();
}
auto rotation = palmRotation * _relativeRotation;
auto offset = rotation * _relativePosition;
auto position = palmPosition + offset;
unlock();
if (!tryLockForWrite()) {
return;
}
if (_positionalTarget != position || _rotationalTarget != rotation) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
} }
_positionalTarget = position;
_rotationalTarget = rotation;
}
unlock();
ObjectActionSpring::updateActionWorker(deltaTimeStep); rotation = palmRotation * _relativeRotation;
offset = rotation * _relativePosition;
position = palmPosition + offset;
});
if (gotLock) {
gotLock = withTryWriteLock([&]{
if (_positionalTarget != position || _rotationalTarget != rotation) {
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
}
_positionalTarget = position;
_rotationalTarget = rotation;
}
});
}
if (gotLock) {
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
} }
@ -117,18 +116,18 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|| relativeRotation != _relativeRotation || relativeRotation != _relativeRotation
|| timeScale != _linearTimeScale || timeScale != _linearTimeScale
|| hand != _hand) { || hand != _hand) {
lockForWrite(); withWriteLock([&] {
_relativePosition = relativePosition; _relativePosition = relativePosition;
_relativeRotation = relativeRotation; _relativeRotation = relativeRotation;
const float MIN_TIMESCALE = 0.1f; const float MIN_TIMESCALE = 0.1f;
_linearTimeScale = glm::min(MIN_TIMESCALE, timeScale); _linearTimeScale = glm::min(MIN_TIMESCALE, timeScale);
_angularTimeScale = _linearTimeScale; _angularTimeScale = _linearTimeScale;
_hand = hand; _hand = hand;
_mine = true; _mine = true;
_active = true; _active = true;
activateBody(); activateBody();
unlock(); });
} }
return true; return true;
} }
@ -136,17 +135,17 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
QVariantMap AvatarActionHold::getArguments() { QVariantMap AvatarActionHold::getArguments() {
QVariantMap arguments; QVariantMap arguments;
lockForRead(); withReadLock([&]{
if (_mine) { if (!_mine) {
arguments = ObjectActionSpring::getArguments();
return;
}
arguments["relativePosition"] = glmToQMap(_relativePosition); arguments["relativePosition"] = glmToQMap(_relativePosition);
arguments["relativeRotation"] = glmToQMap(_relativeRotation); arguments["relativeRotation"] = glmToQMap(_relativeRotation);
arguments["timeScale"] = _linearTimeScale; arguments["timeScale"] = _linearTimeScale;
arguments["hand"] = _hand; arguments["hand"] = _hand;
} else { });
unlock();
return ObjectActionSpring::getArguments();
}
unlock();
return arguments; return arguments;
} }

View file

@ -242,37 +242,33 @@ QVector<AvatarManager::LocalLight> AvatarManager::getLocalLights() const {
return _localLights; return _localLights;
} }
VectorOfMotionStates& AvatarManager::getObjectsToDelete() { void AvatarManager::getObjectsToDelete(VectorOfMotionStates& result) {
_tempMotionStates.clear(); result.clear();
_tempMotionStates.swap(_motionStatesToDelete); result.swap(_motionStatesToDelete);
return _tempMotionStates;
} }
VectorOfMotionStates& AvatarManager::getObjectsToAdd() { void AvatarManager::getObjectsToAdd(VectorOfMotionStates& result) {
_tempMotionStates.clear(); result.clear();
for (auto motionState : _motionStatesToAdd) { for (auto motionState : _motionStatesToAdd) {
_tempMotionStates.push_back(motionState); result.push_back(motionState);
} }
_motionStatesToAdd.clear(); _motionStatesToAdd.clear();
return _tempMotionStates;
} }
VectorOfMotionStates& AvatarManager::getObjectsToChange() { void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) {
_tempMotionStates.clear(); result.clear();
for (auto state : _avatarMotionStates) { for (auto state : _avatarMotionStates) {
if (state->_dirtyFlags > 0) { if (state->_dirtyFlags > 0) {
_tempMotionStates.push_back(state); result.push_back(state);
} }
} }
return _tempMotionStates;
} }
void AvatarManager::handleOutgoingChanges(VectorOfMotionStates& motionStates) { void AvatarManager::handleOutgoingChanges(const VectorOfMotionStates& motionStates) {
// TODO: extract the MyAvatar results once we use a MotionState for it. // TODO: extract the MyAvatar results once we use a MotionState for it.
} }
void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) { void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents) {
for (Collision collision : collisionEvents) { for (Collision collision : collisionEvents) {
// TODO: Current physics uses null idA or idB for non-entities. The plan is to handle MOTIONSTATE_TYPE_AVATAR, // TODO: Current physics uses null idA or idB for non-entities. The plan is to handle MOTIONSTATE_TYPE_AVATAR,
// and then MOTIONSTATE_TYPE_MYAVATAR. As it is, this code only covers the case of my avatar (in which case one // and then MOTIONSTATE_TYPE_MYAVATAR. As it is, this code only covers the case of my avatar (in which case one

View file

@ -52,11 +52,11 @@ public:
Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights); Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights);
Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const; Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const;
VectorOfMotionStates& getObjectsToDelete(); void getObjectsToDelete(VectorOfMotionStates& motionStates);
VectorOfMotionStates& getObjectsToAdd(); void getObjectsToAdd(VectorOfMotionStates& motionStates);
VectorOfMotionStates& getObjectsToChange(); void getObjectsToChange(VectorOfMotionStates& motionStates);
void handleOutgoingChanges(VectorOfMotionStates& motionStates); void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
void handleCollisionEvents(CollisionEvents& collisionEvents); void handleCollisionEvents(const CollisionEvents& collisionEvents);
void updateAvatarPhysicsShape(const QUuid& id); void updateAvatarPhysicsShape(const QUuid& id);
@ -87,7 +87,6 @@ private:
SetOfAvatarMotionStates _avatarMotionStates; SetOfAvatarMotionStates _avatarMotionStates;
SetOfMotionStates _motionStatesToAdd; SetOfMotionStates _motionStatesToAdd;
VectorOfMotionStates _motionStatesToDelete; VectorOfMotionStates _motionStatesToDelete;
VectorOfMotionStates _tempMotionStates;
}; };
Q_DECLARE_METATYPE(AvatarManager::LocalLight) Q_DECLARE_METATYPE(AvatarManager::LocalLight)

View file

@ -196,30 +196,30 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) {
unsigned long totalInternal = 0; unsigned long totalInternal = 0;
unsigned long totalLeaves = 0; unsigned long totalLeaves = 0;
Application::getInstance()->lockOctreeSceneStats();
NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats(); NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats();
for(NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) { sceneStats->withReadLock([&] {
//const QUuid& uuid = i->first; for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) {
OctreeSceneStats& stats = i->second; //const QUuid& uuid = i->first;
serverCount++; OctreeSceneStats& stats = i->second;
serverCount++;
// calculate server node totals // calculate server node totals
totalNodes += stats.getTotalElements(); totalNodes += stats.getTotalElements();
totalInternal += stats.getTotalInternal(); totalInternal += stats.getTotalInternal();
totalLeaves += stats.getTotalLeaves(); totalLeaves += stats.getTotalLeaves();
// Sending mode // Sending mode
if (serverCount > 1) { if (serverCount > 1) {
sendingMode << ","; sendingMode << ",";
}
if (stats.isMoving()) {
sendingMode << "M";
movingServerCount++;
} else {
sendingMode << "S";
}
} }
if (stats.isMoving()) { });
sendingMode << "M";
movingServerCount++;
} else {
sendingMode << "S";
}
}
Application::getInstance()->unlockOctreeSceneStats();
sendingMode << " - " << serverCount << " servers"; sendingMode << " - " << serverCount << " servers";
if (movingServerCount > 0) { if (movingServerCount > 0) {
sendingMode << " <SCENE NOT STABLE>"; sendingMode << " <SCENE NOT STABLE>";
@ -398,45 +398,44 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're // lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction // missing at least one jurisdiction
serverJurisdictions.lockForRead(); serverJurisdictions.withReadLock([&] {
if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) {
serverDetails << " unknown jurisdiction "; serverDetails << " unknown jurisdiction ";
serverJurisdictions.unlock(); return;
} else { }
const JurisdictionMap& map = serverJurisdictions[nodeUUID]; const JurisdictionMap& map = serverJurisdictions[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode(); unsigned char* rootCode = map.getRootOctalCode();
if (rootCode) { if (rootCode) {
QString rootCodeHex = octalCodeToHexString(rootCode); QString rootCodeHex = octalCodeToHexString(rootCode);
VoxelPositionSize rootDetails; VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails); voxelDetailsForCode(rootCode, rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverDetails << " jurisdiction: " serverDetails << " jurisdiction: "
<< qPrintable(rootCodeHex) << qPrintable(rootCodeHex)
<< " [" << " ["
<< rootDetails.x << ", " << rootDetails.x << ", "
<< rootDetails.y << ", " << rootDetails.y << ", "
<< rootDetails.z << ": " << rootDetails.z << ": "
<< rootDetails.s << "] "; << rootDetails.s << "] ";
} else { } else {
serverDetails << " jurisdiction has no rootCode"; serverDetails << " jurisdiction has no rootCode";
} // root code } // root code
serverJurisdictions.unlock(); });
} // jurisdiction
// now lookup stats details for this server... // now lookup stats details for this server...
if (_extraServerDetails[serverCount-1] != LESS) { if (_extraServerDetails[serverCount-1] != LESS) {
Application::getInstance()->lockOctreeSceneStats();
NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats(); NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats();
if (sceneStats->find(nodeUUID) != sceneStats->end()) { sceneStats->withReadLock([&] {
OctreeSceneStats& stats = sceneStats->at(nodeUUID); if (sceneStats->find(nodeUUID) != sceneStats->end()) {
OctreeSceneStats& stats = sceneStats->at(nodeUUID);
switch (_extraServerDetails[serverCount-1]) {
switch (_extraServerDetails[serverCount - 1]) {
case MOST: { case MOST: {
extraDetails << "<br/>" ; extraDetails << "<br/>";
float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC;
float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC;
float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND;
@ -445,20 +444,20 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
if (lastFullSendInSeconds > 0) { if (lastFullSendInSeconds > 0) {
lastFullPPS = lastFullPackets / lastFullSendInSeconds; lastFullPPS = lastFullPackets / lastFullSendInSeconds;
} }
QString lastFullEncodeString = locale.toString(lastFullEncode); QString lastFullEncodeString = locale.toString(lastFullEncode);
QString lastFullSendString = locale.toString(lastFullSend); QString lastFullSendString = locale.toString(lastFullSend);
QString lastFullPacketsString = locale.toString(lastFullPackets); QString lastFullPacketsString = locale.toString(lastFullPackets);
QString lastFullBytesString = locale.toString((uint)stats.getLastFullTotalBytes()); QString lastFullBytesString = locale.toString((uint)stats.getLastFullTotalBytes());
QString lastFullPPSString = locale.toString(lastFullPPS); QString lastFullPPSString = locale.toString(lastFullPPS);
extraDetails << "<br/>" << "Last Full Scene... " << extraDetails << "<br/>" << "Last Full Scene... " <<
"Encode: " << qPrintable(lastFullEncodeString) << " ms " << "Encode: " << qPrintable(lastFullEncodeString) << " ms " <<
"Send: " << qPrintable(lastFullSendString) << " ms " << "Send: " << qPrintable(lastFullSendString) << " ms " <<
"Packets: " << qPrintable(lastFullPacketsString) << " " << "Packets: " << qPrintable(lastFullPacketsString) << " " <<
"Bytes: " << qPrintable(lastFullBytesString) << " " << "Bytes: " << qPrintable(lastFullBytesString) << " " <<
"Rate: " << qPrintable(lastFullPPSString) << " PPS"; "Rate: " << qPrintable(lastFullPPSString) << " PPS";
for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) {
OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i);
OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item);
@ -469,14 +468,14 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
QString totalString = locale.toString((uint)stats.getTotalElements()); QString totalString = locale.toString((uint)stats.getTotalElements());
QString internalString = locale.toString((uint)stats.getTotalInternal()); QString internalString = locale.toString((uint)stats.getTotalInternal());
QString leavesString = locale.toString((uint)stats.getTotalLeaves()); QString leavesString = locale.toString((uint)stats.getTotalLeaves());
serverDetails << "<br/>" << "Node UUID: " << qPrintable(nodeUUID.toString()) << " "; serverDetails << "<br/>" << "Node UUID: " << qPrintable(nodeUUID.toString()) << " ";
serverDetails << "<br/>" << "Elements: " << serverDetails << "<br/>" << "Elements: " <<
qPrintable(totalString) << " total " << qPrintable(totalString) << " total " <<
qPrintable(internalString) << " internal " << qPrintable(internalString) << " internal " <<
qPrintable(leavesString) << " leaves "; qPrintable(leavesString) << " leaves ";
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets()); QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
@ -487,12 +486,12 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
QString incomingEarlyString = locale.toString((uint)seqStats.getEarly()); QString incomingEarlyString = locale.toString((uint)seqStats.getEarly());
QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost()); QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost());
QString incomingRecovered = locale.toString((uint)seqStats.getRecovered()); QString incomingRecovered = locale.toString((uint)seqStats.getRecovered());
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC; int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage()); QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
QString incomingPingTimeString = locale.toString(node->getPingMs()); QString incomingPingTimeString = locale.toString(node->getPingMs());
QString incomingClockSkewString = locale.toString(clockSkewInMS); QString incomingClockSkewString = locale.toString(clockSkewInMS);
serverDetails << "<br/>" << "Incoming Packets: " << qPrintable(incomingPacketsString) << serverDetails << "<br/>" << "Incoming Packets: " << qPrintable(incomingPacketsString) <<
"/ Lost: " << qPrintable(incomingLikelyLostString) << "/ Lost: " << qPrintable(incomingLikelyLostString) <<
"/ Recovered: " << qPrintable(incomingRecovered); "/ Recovered: " << qPrintable(incomingRecovered);
@ -501,36 +500,36 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
"/ Early: " << qPrintable(incomingEarlyString) << "/ Early: " << qPrintable(incomingEarlyString) <<
"/ Late: " << qPrintable(incomingLateString) << "/ Late: " << qPrintable(incomingLateString) <<
"/ Unreasonable: " << qPrintable(incomingUnreasonableString); "/ Unreasonable: " << qPrintable(incomingUnreasonableString);
serverDetails << "<br/>" << serverDetails << "<br/>" <<
" Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs"; " Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs";
serverDetails << "<br/>" << serverDetails << "<br/>" <<
" Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs"; " Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs";
serverDetails << "<br/>" << serverDetails << "<br/>" <<
" Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs"; " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs";
serverDetails << "<br/>" << "Incoming" << serverDetails << "<br/>" << "Incoming" <<
" Bytes: " << qPrintable(incomingBytesString) << " Bytes: " << qPrintable(incomingBytesString) <<
" Wasted Bytes: " << qPrintable(incomingWastedBytesString); " Wasted Bytes: " << qPrintable(incomingWastedBytesString);
serverDetails << extraDetails.str(); serverDetails << extraDetails.str();
if (_extraServerDetails[serverCount-1] == MORE) { if (_extraServerDetails[serverCount - 1] == MORE) {
linkDetails << " " << " [<a href='most-" << serverCount << "'>most...</a>]"; linkDetails << " " << " [<a href='most-" << serverCount << "'>most...</a>]";
linkDetails << " " << " [<a href='less-" << serverCount << "'>less...</a>]"; linkDetails << " " << " [<a href='less-" << serverCount << "'>less...</a>]";
} else { } else {
linkDetails << " " << " [<a href='more-" << serverCount << "'>less...</a>]"; linkDetails << " " << " [<a href='more-" << serverCount << "'>less...</a>]";
linkDetails << " " << " [<a href='less-" << serverCount << "'>least...</a>]"; linkDetails << " " << " [<a href='less-" << serverCount << "'>least...</a>]";
} }
} break; } break;
case LESS: { case LESS: {
// nothing // nothing
} break; } break;
}
} }
} });
Application::getInstance()->unlockOctreeSceneStats();
} else { } else {
linkDetails << " " << " [<a href='more-" << serverCount << "'>more...</a>]"; linkDetails << " " << " [<a href='more-" << serverCount << "'>more...</a>]";
linkDetails << " " << " [<a href='most-" << serverCount << "'>most...</a>]"; linkDetails << " " << " [<a href='most-" << serverCount << "'>most...</a>]";

View file

@ -342,16 +342,17 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
QVector<EntityItemID> entitiesContainingAvatar; QVector<EntityItemID> entitiesContainingAvatar;
// find the entities near us // find the entities near us
_tree->lockForRead(); // don't let someone else change our tree while we search // don't let someone else change our tree while we search
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities); _tree->withReadLock([&] {
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
// create a list of entities that actually contain the avatar's position // create a list of entities that actually contain the avatar's position
foreach(EntityItemPointer entity, foundEntities) { foreach(EntityItemPointer entity, foundEntities) {
if (entity->contains(avatarPosition)) { if (entity->contains(avatarPosition)) {
entitiesContainingAvatar << entity->getEntityItemID(); entitiesContainingAvatar << entity->getEntityItemID();
}
} }
} });
_tree->unlock();
// Note: at this point we don't need to worry about the tree being locked, because we only deal with // Note: at this point we don't need to worry about the tree being locked, because we only deal with
// EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts // EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts
@ -502,22 +503,19 @@ void EntityTreeRenderer::render(RenderArgs* renderArgs) {
if (_tree && !_shuttingDown) { if (_tree && !_shuttingDown) {
renderArgs->_renderer = this; renderArgs->_renderer = this;
_tree->withReadLock([&] {
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
_bestZone = NULL; // NOTE: Is this what we want?
_bestZoneVolume = std::numeric_limits<float>::max();
_tree->lockForRead(); // FIX ME: right now the renderOperation does the following:
// 1) determining the best zone (not really rendering)
// 2) render the debug cell details
// we should clean this up
_tree->recurseTreeWithOperation(renderOperation, renderArgs);
// Whenever you're in an intersection between zones, we will always choose the smallest zone. applyZonePropertiesToScene(_bestZone);
_bestZone = NULL; // NOTE: Is this what we want? });
_bestZoneVolume = std::numeric_limits<float>::max();
// FIX ME: right now the renderOperation does the following:
// 1) determining the best zone (not really rendering)
// 2) render the debug cell details
// we should clean this up
_tree->recurseTreeWithOperation(renderOperation, renderArgs);
applyZonePropertiesToScene(_bestZone);
_tree->unlock();
} }
deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup
} }
@ -646,22 +644,14 @@ void EntityTreeRenderer::renderElement(OctreeElementPointer element, RenderArgs*
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
EntityItems& entityItems = entityTreeElement->getEntities();
uint16_t numberOfEntities = entityItems.size();
bool isShadowMode = args->_renderMode == RenderArgs::SHADOW_RENDER_MODE; bool isShadowMode = args->_renderMode == RenderArgs::SHADOW_RENDER_MODE;
if (!isShadowMode && _displayModelElementProxy && numberOfEntities > 0) { if (!isShadowMode && _displayModelElementProxy && entityTreeElement->size() > 0) {
renderElementProxy(entityTreeElement, args); renderElementProxy(entityTreeElement, args);
} }
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItemPointer entityItem = entityItems[i];
if (entityItem->isVisible()) {
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
if (entityItem->isVisible()) {
// NOTE: Zone Entities are a special case we handle here... // NOTE: Zone Entities are a special case we handle here...
if (entityItem->getType() == EntityTypes::Zone) { if (entityItem->getType() == EntityTypes::Zone) {
if (entityItem->contains(_viewState->getAvatarPosition())) { if (entityItem->contains(_viewState->getAvatarPosition())) {
@ -685,7 +675,8 @@ void EntityTreeRenderer::renderElement(OctreeElementPointer element, RenderArgs*
} }
} }
} }
} });
} }
float EntityTreeRenderer::getSizeScale() const { float EntityTreeRenderer::getSizeScale() const {

View file

@ -1496,15 +1496,16 @@ void EntityItem::clearSimulationOwnership() {
bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) { bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) {
lockForWrite(); bool result;
checkWaitingToRemove(simulation); withWriteLock([&] {
checkWaitingToRemove(simulation);
bool result = addActionInternal(simulation, action); result = addActionInternal(simulation, action);
if (!result) { if (!result) {
removeActionInternal(action->getID()); removeActionInternal(action->getID());
} }
});
unlock();
return result; return result;
} }
@ -1531,33 +1532,33 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi
} }
bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) { bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) {
lockForWrite(); bool success = false;
checkWaitingToRemove(simulation); withWriteLock([&] {
checkWaitingToRemove(simulation);
if (!_objectActions.contains(actionID)) { if (!_objectActions.contains(actionID)) {
unlock(); return;
return false; }
}
EntityActionPointer action = _objectActions[actionID];
bool success = action->updateArguments(arguments); EntityActionPointer action = _objectActions[actionID];
if (success) {
_allActionsDataCache = serializeActions(success);
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
} else {
qDebug() << "EntityItem::updateAction failed";
}
unlock(); success = action->updateArguments(arguments);
if (success) {
_allActionsDataCache = serializeActions(success);
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
} else {
qDebug() << "EntityItem::updateAction failed";
}
});
return success; return success;
} }
bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) { bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) {
lockForWrite(); bool success = false;
checkWaitingToRemove(simulation); withWriteLock([&] {
checkWaitingToRemove(simulation);
bool success = removeActionInternal(actionID); success = removeActionInternal(actionID);
unlock(); });
return success; return success;
} }
@ -1586,29 +1587,29 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s
} }
bool EntityItem::clearActions(EntitySimulation* simulation) { bool EntityItem::clearActions(EntitySimulation* simulation) {
lockForWrite(); withWriteLock([&] {
QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin(); QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin();
while (i != _objectActions.end()) { while (i != _objectActions.end()) {
const QUuid id = i.key(); const QUuid id = i.key();
EntityActionPointer action = _objectActions[id]; EntityActionPointer action = _objectActions[id];
i = _objectActions.erase(i); i = _objectActions.erase(i);
action->setOwnerEntity(nullptr); action->setOwnerEntity(nullptr);
action->removeFromSimulation(simulation); action->removeFromSimulation(simulation);
} }
// empty _serializedActions means no actions for the EntityItem // empty _serializedActions means no actions for the EntityItem
_actionsToRemove.clear(); _actionsToRemove.clear();
_allActionsDataCache.clear(); _allActionsDataCache.clear();
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
unlock(); });
return true; return true;
} }
void EntityItem::deserializeActions() { void EntityItem::deserializeActions() {
assertUnlocked(); assertUnlocked();
lockForWrite(); withWriteLock([&] {
deserializeActionsInternal(); deserializeActionsInternal();
unlock(); });
} }
@ -1682,9 +1683,9 @@ void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) {
void EntityItem::setActionData(QByteArray actionData) { void EntityItem::setActionData(QByteArray actionData) {
assertUnlocked(); assertUnlocked();
lockForWrite(); withWriteLock([&] {
setActionDataInternal(actionData); setActionDataInternal(actionData);
unlock(); });
} }
void EntityItem::setActionDataInternal(QByteArray actionData) { void EntityItem::setActionDataInternal(QByteArray actionData) {
@ -1738,117 +1739,23 @@ const QByteArray EntityItem::getActionDataInternal() const {
} }
const QByteArray EntityItem::getActionData() const { const QByteArray EntityItem::getActionData() const {
QByteArray result;
assertUnlocked(); assertUnlocked();
lockForRead(); withReadLock([&] {
auto result = getActionDataInternal(); result = getActionDataInternal();
unlock(); });
return result; return result;
} }
QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const { QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
QVariantMap result; QVariantMap result;
lockForRead(); withReadLock([&] {
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
result = action->getArguments();
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
}
});
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
result = action->getArguments();
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
}
unlock();
return result; return result;
} }
#define ENABLE_LOCKING 1
#ifdef ENABLE_LOCKING
void EntityItem::lockForRead() const {
_lock.lockForRead();
}
bool EntityItem::tryLockForRead() const {
return _lock.tryLockForRead();
}
void EntityItem::lockForWrite() const {
_lock.lockForWrite();
}
bool EntityItem::tryLockForWrite() const {
return _lock.tryLockForWrite();
}
void EntityItem::unlock() const {
_lock.unlock();
}
bool EntityItem::isLocked() const {
bool readSuccess = tryLockForRead();
if (readSuccess) {
unlock();
}
bool writeSuccess = tryLockForWrite();
if (writeSuccess) {
unlock();
}
if (readSuccess && writeSuccess) {
return false; // if we can take both kinds of lock, there was no previous lock
}
return true; // either read or write failed, so there is some lock in place.
}
bool EntityItem::isWriteLocked() const {
bool readSuccess = tryLockForRead();
if (readSuccess) {
unlock();
return false;
}
bool writeSuccess = tryLockForWrite();
if (writeSuccess) {
unlock();
return false;
}
return true; // either read or write failed, so there is some lock in place.
}
bool EntityItem::isUnlocked() const {
// this can't be sure -- this may get unlucky and hit locks from other threads. what we're actually trying
// to discover is if *this* thread hasn't locked the EntityItem. Try repeatedly to take both kinds of lock.
bool readSuccess = false;
for (int i=0; i<80; i++) {
readSuccess = tryLockForRead();
if (readSuccess) {
unlock();
break;
}
QThread::usleep(200);
}
bool writeSuccess = false;
if (readSuccess) {
for (int i=0; i<80; i++) {
writeSuccess = tryLockForWrite();
if (writeSuccess) {
unlock();
break;
}
QThread::usleep(300);
}
}
if (readSuccess && writeSuccess) {
return true; // if we can take both kinds of lock, there was no previous lock
}
return false;
}
#else
void EntityItem::lockForRead() const { }
bool EntityItem::tryLockForRead() const { return true; }
void EntityItem::lockForWrite() const { }
bool EntityItem::tryLockForWrite() const { return true; }
void EntityItem::unlock() const { }
bool EntityItem::isLocked() const { return true; }
bool EntityItem::isWriteLocked() const { return true; }
bool EntityItem::isUnlocked() const { return true; }
#endif

View file

@ -69,28 +69,31 @@ const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f;
#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10))
#define debugTreeVector(V) V << "[" << V << " in meters ]" #define debugTreeVector(V) V << "[" << V << " in meters ]"
#if DEBUG //#if DEBUG
#define assertLocked() assert(isLocked()) // #define assertLocked() assert(isLocked())
#else //#else
#define assertLocked() // #define assertLocked()
#endif //#endif
//
#if DEBUG //#if DEBUG
#define assertWriteLocked() assert(isWriteLocked()) // #define assertWriteLocked() assert(isWriteLocked())
#else //#else
#define assertWriteLocked() // #define assertWriteLocked()
#endif //#endif
//
#if DEBUG //#if DEBUG
#define assertUnlocked() assert(isUnlocked()) // #define assertUnlocked() assert(isUnlocked())
#else //#else
#define assertUnlocked() // #define assertUnlocked()
#endif //#endif
#define assertLocked()
#define assertUnlocked()
#define assertWriteLocked()
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features. /// one directly, instead you must only construct one of it's derived classes with additional features.
class EntityItem : public std::enable_shared_from_this<EntityItem> { class EntityItem : public std::enable_shared_from_this<EntityItem>, public ReadWriteLockable {
// These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted. // These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted.
// To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by // To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by
// the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to // the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to
@ -515,16 +518,6 @@ protected:
void checkWaitingToRemove(EntitySimulation* simulation = nullptr); void checkWaitingToRemove(EntitySimulation* simulation = nullptr);
mutable QSet<QUuid> _actionsToRemove; mutable QSet<QUuid> _actionsToRemove;
mutable bool _actionDataDirty = false; mutable bool _actionDataDirty = false;
mutable QReadWriteLock _lock;
void lockForRead() const;
bool tryLockForRead() const;
void lockForWrite() const;
bool tryLockForWrite() const;
void unlock() const;
bool isLocked() const;
bool isWriteLocked() const;
bool isUnlocked() const;
}; };
#endif // hifi_EntityItem_h #endif // hifi_EntityItem_h

View file

@ -72,23 +72,23 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
// If we have a local entity tree set, then also update it. // If we have a local entity tree set, then also update it.
bool success = true; bool success = true;
if (_entityTree) { if (_entityTree) {
_entityTree->lockForWrite(); _entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
if (entity) { if (entity) {
// This Node is creating a new object. If it's in motion, set this Node as the simulator. // This Node is creating a new object. If it's in motion, set this Node as the simulator.
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID(); const QUuid myNodeID = nodeList->getSessionUUID();
propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
// and make note of it now, so we can act on it right away. // and make note of it now, so we can act on it right away.
entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
entity->setLastBroadcast(usecTimestampNow()); entity->setLastBroadcast(usecTimestampNow());
} else { } else {
qCDebug(entities) << "script failed to add new Entity to local Octree"; qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false; success = false;
} }
_entityTree->unlock(); });
} }
// queue the packet // queue the packet
@ -102,28 +102,26 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) { EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
EntityItemProperties results; EntityItemProperties results;
if (_entityTree) { if (_entityTree) {
_entityTree->lockForRead(); _entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity));
if (entity) {
results = entity->getProperties();
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity)); // TODO: improve sitting points and naturalDimensions in the future,
// for now we've included the old sitting points model behavior for entity types that are models
if (entity) { // we've also added this hack for setting natural dimensions of models
results = entity->getProperties(); if (entity->getType() == EntityTypes::Model) {
const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity);
// TODO: improve sitting points and naturalDimensions in the future, if (geometry) {
// for now we've included the old sitting points model behavior for entity types that are models results.setSittingPoints(geometry->sittingPoints);
// we've also added this hack for setting natural dimensions of models Extents meshExtents = geometry->getUnscaledMeshExtents();
if (entity->getType() == EntityTypes::Model) { results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum);
const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum);
if (geometry) { }
results.setSittingPoints(geometry->sittingPoints);
Extents meshExtents = geometry->getUnscaledMeshExtents();
results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum);
results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum);
} }
}
} }
_entityTree->unlock(); });
} }
return results; return results;
@ -132,56 +130,59 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) { QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) {
EntityItemID entityID(id); EntityItemID entityID(id);
// If we have a local entity tree set, then also update it. // If we have a local entity tree set, then also update it.
if (_entityTree) { if (!_entityTree) {
_entityTree->lockForWrite(); queueEntityMessage(PacketType::EntityEdit, entityID, properties);
bool updatedEntity = _entityTree->updateEntity(entityID, properties); return id;
_entityTree->unlock(); }
if (updatedEntity) { bool updatedEntity = false;
_entityTree->lockForRead(); _entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); updatedEntity = _entityTree->updateEntity(entityID, properties);
if (entity) { });
// make sure the properties has a type, so that the encode can know which properties to include
properties.setType(entity->getType());
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges;
if (hasPhysicsChanges) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
if (entity->getSimulatorID() == myNodeID) {
// we think we already own the simulation, so make sure to send ALL TerseUpdate properties
if (hasTerseUpdateChanges) {
entity->getAllTerseUpdateProperties(properties);
}
// TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object
// is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update
// and instead let the physics simulation decide when to send a terse update. This would remove
// the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling
// balls" test. However, even if we solve this problem we still need to provide a "slerp the visible
// proxy toward the true physical position" feature to hide the final glitches in the remote watcher's
// simulation.
if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) { if (!updatedEntity) {
// we re-assert our simulation ownership at a higher priority
properties.setSimulationOwner(myNodeID,
glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY));
}
} else {
// we make a bid for simulation ownership
properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
entity->flagForOwnership();
}
}
entity->setLastBroadcast(usecTimestampNow());
}
_entityTree->unlock();
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
return QUuid(); return QUuid();
} }
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
// make sure the properties has a type, so that the encode can know which properties to include
properties.setType(entity->getType());
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges;
if (hasPhysicsChanges) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
if (entity->getSimulatorID() == myNodeID) {
// we think we already own the simulation, so make sure to send ALL TerseUpdate properties
if (hasTerseUpdateChanges) {
entity->getAllTerseUpdateProperties(properties);
}
// TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object
// is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update
// and instead let the physics simulation decide when to send a terse update. This would remove
// the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling
// balls" test. However, even if we solve this problem we still need to provide a "slerp the visible
// proxy toward the true physical position" feature to hide the final glitches in the remote watcher's
// simulation.
if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) {
// we re-assert our simulation ownership at a higher priority
properties.setSimulationOwner(myNodeID,
glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY));
}
} else {
// we make a bid for simulation ownership
properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
entity->flagForOwnership();
}
}
entity->setLastBroadcast(usecTimestampNow());
}
});
queueEntityMessage(PacketType::EntityEdit, entityID, properties); queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id; return id;
} }
@ -192,18 +193,16 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
// If we have a local entity tree set, then also update it. // If we have a local entity tree set, then also update it.
if (_entityTree) { if (_entityTree) {
_entityTree->lockForWrite(); _entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) {
if (entity) { if (entity->getLocked()) {
if (entity->getLocked()) { shouldDelete = false;
shouldDelete = false; } else {
} else { _entityTree->deleteEntity(entityID);
_entityTree->deleteEntity(entityID); }
} }
} });
_entityTree->unlock();
} }
// if at this point, we know the id, and we should still delete the entity, send the update to the entity server // if at this point, we know the id, and we should still delete the entity, send the update to the entity server
@ -215,9 +214,10 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
EntityItemID result; EntityItemID result;
if (_entityTree) { if (_entityTree) {
_entityTree->lockForRead(); EntityItemPointer closestEntity;
EntityItemPointer closestEntity = _entityTree->findClosestEntity(center, radius); _entityTree->withReadLock([&] {
_entityTree->unlock(); closestEntity = _entityTree->findClosestEntity(center, radius);
});
if (closestEntity) { if (closestEntity) {
result = closestEntity->getEntityItemID(); result = closestEntity->getEntityItemID();
} }
@ -228,19 +228,19 @@ QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float
void EntityScriptingInterface::dumpTree() const { void EntityScriptingInterface::dumpTree() const {
if (_entityTree) { if (_entityTree) {
_entityTree->lockForRead(); _entityTree->withReadLock([&] {
_entityTree->dumpTree(); _entityTree->dumpTree();
_entityTree->unlock(); });
} }
} }
QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const {
QVector<QUuid> result; QVector<QUuid> result;
if (_entityTree) { if (_entityTree) {
_entityTree->lockForRead();
QVector<EntityItemPointer> entities; QVector<EntityItemPointer> entities;
_entityTree->findEntities(center, radius, entities); _entityTree->withReadLock([&] {
_entityTree->unlock(); _entityTree->findEntities(center, radius, entities);
});
foreach (EntityItemPointer entity, entities) { foreach (EntityItemPointer entity, entities) {
result << entity->getEntityItemID(); result << entity->getEntityItemID();
@ -252,11 +252,11 @@ QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, f
QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const { QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const {
QVector<QUuid> result; QVector<QUuid> result;
if (_entityTree) { if (_entityTree) {
_entityTree->lockForRead();
AABox box(corner, dimensions);
QVector<EntityItemPointer> entities; QVector<EntityItemPointer> entities;
_entityTree->findEntities(box, entities); _entityTree->withReadLock([&] {
_entityTree->unlock(); AABox box(corner, dimensions);
_entityTree->findEntities(box, entities);
});
foreach (EntityItemPointer entity, entities) { foreach (EntityItemPointer entity, entities) {
result << entity->getEntityItemID(); result << entity->getEntityItemID();
@ -432,9 +432,10 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
} }
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity); auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
_entityTree->lockForWrite(); bool result;
bool result = actor(*polyVoxEntity); _entityTree->withWriteLock([&] {
_entityTree->unlock(); result = actor(*polyVoxEntity);
});
return result; return result;
} }
@ -457,15 +458,17 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
auto now = usecTimestampNow(); auto now = usecTimestampNow();
auto lineEntity = std::static_pointer_cast<LineEntityItem>(entity); auto lineEntity = std::static_pointer_cast<LineEntityItem>(entity);
_entityTree->lockForWrite(); bool success;
bool success = actor(*lineEntity); _entityTree->withWriteLock([&] {
entity->setLastEdited(now); success = actor(*lineEntity);
entity->setLastBroadcast(now); entity->setLastEdited(now);
_entityTree->unlock(); entity->setLastBroadcast(now);
});
_entityTree->lockForRead(); EntityItemProperties properties;
EntityItemProperties properties = entity->getProperties(); _entityTree->withReadLock([&] {
_entityTree->unlock(); properties = entity->getProperties();
});
properties.setLinePointsDirty(); properties.setLinePointsDirty();
properties.setLastEdited(now); properties.setLastEdited(now);
@ -543,36 +546,39 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
return false; return false;
} }
_entityTree->lockForWrite(); EntityItemPointer entity;
bool success;
_entityTree->withWriteLock([&] {
EntitySimulation* simulation = _entityTree->getSimulation();
entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qDebug() << "actionWorker -- unknown entity" << entityID;
return;
}
EntitySimulation* simulation = _entityTree->getSimulation(); if (!simulation) {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); qDebug() << "actionWorker -- no simulation" << entityID;
if (!entity) { return;
qDebug() << "actionWorker -- unknown entity" << entityID; }
_entityTree->unlock();
return false;
}
if (!simulation) { success = actor(simulation, entity);
qDebug() << "actionWorker -- no simulation" << entityID; if (success) {
_entityTree->unlock(); _entityTree->entityChanged(entity);
return false; }
} });
bool success = actor(simulation, entity);
if (success) {
_entityTree->entityChanged(entity);
}
_entityTree->unlock();
// transmit the change // transmit the change
_entityTree->lockForRead(); if (success) {
EntityItemProperties properties = entity->getProperties(); EntityItemProperties properties;
_entityTree->unlock(); _entityTree->withReadLock([&] {
properties.setActionDataDirty(); properties = entity->getProperties();
auto now = usecTimestampNow(); });
properties.setLastEdited(now);
queueEntityMessage(PacketType::EntityEdit, entityID, properties); properties.setActionDataDirty();
auto now = usecTimestampNow();
properties.setLastEdited(now);
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
}
return success; return success;
} }

View file

@ -27,6 +27,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) {
} }
void EntitySimulation::updateEntities() { void EntitySimulation::updateEntities() {
QMutexLocker lock(&_mutex);
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
// these methods may accumulate entries in _entitiesToBeDeleted // these methods may accumulate entries in _entitiesToBeDeleted
@ -38,7 +39,7 @@ void EntitySimulation::updateEntities() {
} }
void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) { void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
QMutexLocker lock(&_mutex);
for (auto entity : _entitiesToDelete) { for (auto entity : _entitiesToDelete) {
// this entity is still in its tree, so we insert into the external list // this entity is still in its tree, so we insert into the external list
entitiesToDelete.push_back(entity); entitiesToDelete.push_back(entity);
@ -145,6 +146,7 @@ void EntitySimulation::sortEntitiesThatMoved() {
} }
void EntitySimulation::addEntity(EntityItemPointer entity) { void EntitySimulation::addEntity(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
assert(entity); assert(entity);
entity->deserializeActions(); entity->deserializeActions();
if (entity->isMortal()) { if (entity->isMortal()) {
@ -168,6 +170,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
} }
void EntitySimulation::removeEntity(EntityItemPointer entity) { void EntitySimulation::removeEntity(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
assert(entity); assert(entity);
_entitiesToUpdate.remove(entity); _entitiesToUpdate.remove(entity);
_mortalEntities.remove(entity); _mortalEntities.remove(entity);
@ -181,6 +184,7 @@ void EntitySimulation::removeEntity(EntityItemPointer entity) {
} }
void EntitySimulation::changeEntity(EntityItemPointer entity) { void EntitySimulation::changeEntity(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
assert(entity); assert(entity);
if (!entity->_simulated) { if (!entity->_simulated) {
// This entity was either never added to the simulation or has been removed // This entity was either never added to the simulation or has been removed
@ -232,6 +236,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
} }
void EntitySimulation::clearEntities() { void EntitySimulation::clearEntities() {
QMutexLocker lock(&_mutex);
_mortalEntities.clear(); _mortalEntities.clear();
_nextExpiry = quint64(-1); _nextExpiry = quint64(-1);
_entitiesToUpdate.clear(); _entitiesToUpdate.clear();
@ -263,26 +268,24 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) {
} }
void EntitySimulation::addAction(EntityActionPointer action) { void EntitySimulation::addAction(EntityActionPointer action) {
lock(); QMutexLocker lock(&_mutex);
_actionsToAdd += action; _actionsToAdd += action;
unlock();
} }
void EntitySimulation::removeAction(const QUuid actionID) { void EntitySimulation::removeAction(const QUuid actionID) {
lock(); QMutexLocker lock(&_mutex);
_actionsToRemove += actionID; _actionsToRemove += actionID;
unlock();
} }
void EntitySimulation::removeActions(QList<QUuid> actionIDsToRemove) { void EntitySimulation::removeActions(QList<QUuid> actionIDsToRemove) {
lock(); QMutexLocker lock(&_mutex);
_actionsToRemove += actionIDsToRemove; foreach(QUuid uuid, actionIDsToRemove) {
unlock(); _actionsToRemove.insert(uuid);
}
} }
void EntitySimulation::applyActionChanges() { void EntitySimulation::applyActionChanges() {
lock(); QMutexLocker lock(&_mutex);
_actionsToAdd.clear(); _actionsToAdd.clear();
_actionsToRemove.clear(); _actionsToRemove.clear();
unlock();
} }

View file

@ -47,22 +47,18 @@ public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
virtual ~EntitySimulation() { setEntityTree(NULL); } virtual ~EntitySimulation() { setEntityTree(NULL); }
void lock() { _mutex.lock(); }
void unlock() { _mutex.unlock(); }
/// \param tree pointer to EntityTree which is stored internally /// \param tree pointer to EntityTree which is stored internally
void setEntityTree(EntityTreePointer tree); void setEntityTree(EntityTreePointer tree);
void updateEntities(); void updateEntities();
friend class EntityTree; // friend class EntityTree;
virtual void addAction(EntityActionPointer action); virtual void addAction(EntityActionPointer action);
virtual void removeAction(const QUuid actionID); virtual void removeAction(const QUuid actionID);
virtual void removeActions(QList<QUuid> actionIDsToRemove); virtual void removeActions(QList<QUuid> actionIDsToRemove);
virtual void applyActionChanges(); virtual void applyActionChanges();
protected: // these only called by the EntityTree?
/// \param entity pointer to EntityItem to be added /// \param entity pointer to EntityItem to be added
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
void addEntity(EntityItemPointer entity); void addEntity(EntityItemPointer entity);
@ -79,6 +75,7 @@ protected: // these only called by the EntityTree?
void clearEntities(); void clearEntities();
void moveSimpleKinematics(const quint64& now); void moveSimpleKinematics(const quint64& now);
protected: // these only called by the EntityTree?
public: public:
@ -90,7 +87,6 @@ signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
protected: protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class // These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places. // calls them in the right places.
virtual void updateEntitiesInternal(const quint64& now) = 0; virtual void updateEntitiesInternal(const quint64& now) = 0;
@ -103,7 +99,15 @@ protected:
void callUpdateOnEntitiesThatNeedIt(const quint64& now); void callUpdateOnEntitiesThatNeedIt(const quint64& now);
void sortEntitiesThatMoved(); void sortEntitiesThatMoved();
QMutex _mutex; QMutex _mutex{ QMutex::Recursive };
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
QList<EntityActionPointer> _actionsToAdd;
QSet<QUuid> _actionsToRemove;
private:
void moveSimpleKinematics();
// back pointer to EntityTree structure // back pointer to EntityTree structure
EntityTreePointer _entityTree; EntityTreePointer _entityTree;
@ -113,17 +117,11 @@ protected:
SetOfEntities _allEntities; // tracks all entities added the simulation SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry SetOfEntities _mortalEntities; // entities that have an expiry
quint64 _nextExpiry; quint64 _nextExpiry;
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
private:
void moveSimpleKinematics();
protected:
QList<EntityActionPointer> _actionsToAdd;
QList<QUuid> _actionsToRemove;
}; };
#endif // hifi_EntitySimulation_h #endif // hifi_EntitySimulation_h

View file

@ -58,9 +58,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
// this would be a good place to clean up our entities... // this would be a good place to clean up our entities...
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->clearEntities(); _simulation->clearEntities();
_simulation->unlock();
} }
foreach (EntityTreeElementPointer element, _entityToElementMap) { foreach (EntityTreeElementPointer element, _entityToElementMap) {
element->cleanupEntities(); element->cleanupEntities();
@ -88,9 +86,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
assert(entity); assert(entity);
// check to see if we need to simulate this entity.. // check to see if we need to simulate this entity..
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->addEntity(entity); _simulation->addEntity(entity);
_simulation->unlock();
} }
_isDirty = true; _isDirty = true;
maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL());
@ -217,9 +213,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
if (newFlags) { if (newFlags) {
if (_simulation) { if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) { if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->lock();
_simulation->changeEntity(entity); _simulation->changeEntity(entity);
_simulation->unlock();
} }
} else { } else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
@ -301,18 +295,18 @@ void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisio
} }
void EntityTree::setSimulation(EntitySimulation* simulation) { void EntityTree::setSimulation(EntitySimulation* simulation) {
if (simulation) { this->withWriteLock([&] {
// assert that the simulation's backpointer has already been properly connected if (simulation) {
assert(simulation->getEntityTree().get() == this); // assert that the simulation's backpointer has already been properly connected
} assert(simulation->getEntityTree().get() == this);
if (_simulation && _simulation != simulation) { }
// It's important to clearEntities() on the simulation since taht will update each if (_simulation && _simulation != simulation) {
// EntityItem::_simulationState correctly so as to not confuse the next _simulation. // It's important to clearEntities() on the simulation since taht will update each
_simulation->lock(); // EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->clearEntities(); _simulation->clearEntities();
_simulation->unlock(); }
} _simulation = simulation;
_simulation = simulation; });
} }
void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) { void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) {
@ -391,9 +385,6 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) { void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) {
const RemovedEntities& entities = theOperator.getEntities(); const RemovedEntities& entities = theOperator.getEntities();
if (_simulation) {
_simulation->lock();
}
foreach(const EntityToDeleteDetails& details, entities) { foreach(const EntityToDeleteDetails& details, entities) {
EntityItemPointer theEntity = details.entity; EntityItemPointer theEntity = details.entity;
@ -410,9 +401,6 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
_simulation->removeEntity(theEntity); _simulation->removeEntity(theEntity);
} }
} }
if (_simulation) {
_simulation->unlock();
}
} }
@ -463,10 +451,10 @@ bool EntityTree::findNearPointOperation(OctreeElementPointer element, void* extr
EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) { EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };
lockForRead(); withReadLock([&] {
// NOTE: This should use recursion, since this is a spatial operation // NOTE: This should use recursion, since this is a spatial operation
recurseTreeWithOperation(findNearPointOperation, &args); recurseTreeWithOperation(findNearPointOperation, &args);
unlock(); });
return args.closestEntity; return args.closestEntity;
} }
@ -718,33 +706,29 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::entityChanged(EntityItemPointer entity) {
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->changeEntity(entity); _simulation->changeEntity(entity);
_simulation->unlock();
} }
} }
void EntityTree::update() { void EntityTree::update() {
if (_simulation) { if (_simulation) {
lockForWrite(); withWriteLock([&] {
_simulation->lock(); _simulation->updateEntities();
_simulation->updateEntities(); VectorOfEntities pendingDeletes;
VectorOfEntities pendingDeletes; _simulation->getEntitiesToDelete(pendingDeletes);
_simulation->getEntitiesToDelete(pendingDeletes);
_simulation->unlock();
if (pendingDeletes.size() > 0) { if (pendingDeletes.size() > 0) {
// translate into list of ID's // translate into list of ID's
QSet<EntityItemID> idsToDelete; QSet<EntityItemID> idsToDelete;
for (auto entity : pendingDeletes) { for (auto entity : pendingDeletes) {
idsToDelete.insert(entity->getEntityItemID()); idsToDelete.insert(entity->getEntityItemID());
}
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
} }
});
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
}
unlock();
} }
} }
@ -864,36 +848,35 @@ void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) {
// TODO: consider consolidating processEraseMessageDetails() and processEraseMessage() // TODO: consider consolidating processEraseMessageDetails() and processEraseMessage()
int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode) { int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode) {
lockForWrite(); withWriteLock([&] {
packet.seek(sizeof(OCTREE_PACKET_FLAGS) + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME));
packet.seek(sizeof(OCTREE_PACKET_FLAGS) + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME)); uint16_t numberOfIDs = 0; // placeholder for now
packet.readPrimitive(&numberOfIDs);
uint16_t numberOfIDs = 0; // placeholder for now if (numberOfIDs > 0) {
packet.readPrimitive(&numberOfIDs); QSet<EntityItemID> entityItemIDsToDelete;
if (numberOfIDs > 0) {
QSet<EntityItemID> entityItemIDsToDelete;
for (size_t i = 0; i < numberOfIDs; i++) { for (size_t i = 0; i < numberOfIDs; i++) {
if (NUM_BYTES_RFC4122_UUID > packet.bytesLeftToRead()) {
qCDebug(entities) << "EntityTree::processEraseMessage().... bailing because not enough bytes in buffer";
break; // bail to prevent buffer overflow
}
QUuid entityID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
EntityItemID entityItemID(entityID);
entityItemIDsToDelete << entityItemID;
if (wantEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID;
}
if (NUM_BYTES_RFC4122_UUID > packet.bytesLeftToRead()) {
qCDebug(entities) << "EntityTree::processEraseMessage().... bailing because not enough bytes in buffer";
break; // bail to prevent buffer overflow
} }
deleteEntities(entityItemIDsToDelete, true, true);
QUuid entityID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
EntityItemID entityItemID(entityID);
entityItemIDsToDelete << entityItemID;
if (wantEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID;
}
} }
deleteEntities(entityItemIDsToDelete, true, true); });
}
unlock();
return packet.pos(); return packet.pos();
} }
@ -1038,12 +1021,10 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) { bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) {
SendEntitiesOperationArgs* args = static_cast<SendEntitiesOperationArgs*>(extraData); SendEntitiesOperationArgs* args = static_cast<SendEntitiesOperationArgs*>(extraData);
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
const EntityItems& entities = entityTreeElement->getEntities();
for (int i = 0; i < entities.size(); i++) {
EntityItemID newID(QUuid::createUuid()); EntityItemID newID(QUuid::createUuid());
args->newEntityIDs->append(newID); args->newEntityIDs->append(newID);
EntityItemProperties properties = entities[i]->getProperties(); EntityItemProperties properties = entityItem->getProperties();
properties.setPosition(properties.getPosition() + args->root); properties.setPosition(properties.getPosition() + args->root);
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
@ -1052,12 +1033,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
// also update the local tree instantly (note: this is not our tree, but an alternate tree) // also update the local tree instantly (note: this is not our tree, but an alternate tree)
if (args->localTree) { if (args->localTree) {
args->localTree->lockForWrite(); args->localTree->withWriteLock([&] {
args->localTree->addEntity(newID, properties); args->localTree->addEntity(newID, properties);
args->localTree->unlock(); });
} }
} });
return true; return true;
} }

View file

@ -24,8 +24,6 @@ EntityTreeElement::EntityTreeElement(unsigned char* octalCode) : OctreeElement()
EntityTreeElement::~EntityTreeElement() { EntityTreeElement::~EntityTreeElement() {
_octreeMemoryUsage -= sizeof(EntityTreeElement); _octreeMemoryUsage -= sizeof(EntityTreeElement);
delete _entityItems;
_entityItems = NULL;
} }
// This will be called primarily on addChildAt(), which means we're adding a child of our // This will be called primarily on addChildAt(), which means we're adding a child of our
@ -59,7 +57,6 @@ OctreeElementPointer EntityTreeElement::createNewElement(unsigned char* octalCod
void EntityTreeElement::init(unsigned char* octalCode) { void EntityTreeElement::init(unsigned char* octalCode) {
OctreeElement::init(octalCode); OctreeElement::init(octalCode);
_entityItems = new EntityItems;
_octreeMemoryUsage += sizeof(EntityTreeElement); _octreeMemoryUsage += sizeof(EntityTreeElement);
} }
@ -91,7 +88,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
// Check to see if this element yet has encode data... if it doesn't create it // Check to see if this element yet has encode data... if it doesn't create it
if (!extraEncodeData->contains(this)) { if (!extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElementPointer child = getChildAtIndex(i); EntityTreeElementPointer child = getChildAtIndex(i);
if (!child) { if (!child) {
@ -104,10 +101,9 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
} }
} }
} }
for (uint16_t i = 0; i < _entityItems->size(); i++) { forEachEntity([&](EntityItemPointer entity) {
EntityItemPointer entity = (*_entityItems)[i];
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
} });
// TODO: some of these inserts might be redundant!!! // TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData); extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
@ -269,7 +265,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
} else { } else {
// if there wasn't one already, then create one // if there wasn't one already, then create one
entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); entityTreeElementExtraEncodeData->elementCompleted = !hasContent();
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElementPointer child = getChildAtIndex(i); EntityTreeElementPointer child = getChildAtIndex(i);
@ -284,10 +280,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
} }
} }
} }
for (uint16_t i = 0; i < _entityItems->size(); i++) { forEachEntity([&](EntityItemPointer entity) {
EntityItemPointer entity = (*_entityItems)[i];
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
} });
} }
//assert(extraEncodeData); //assert(extraEncodeData);
@ -299,81 +294,84 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// write our entities out... first determine which of the entities are in view based on our params // write our entities out... first determine which of the entities are in view based on our params
uint16_t numberOfEntities = 0; uint16_t numberOfEntities = 0;
uint16_t actualNumberOfEntities = 0; uint16_t actualNumberOfEntities = 0;
QVector<uint16_t> indexesOfEntitiesToInclude; 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 // 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 // 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. // need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) { if (!entityTreeElementExtraEncodeData->elementCompleted) {
for (uint16_t i = 0; i < _entityItems->size(); i++) { for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = (*_entityItems)[i]; EntityItemPointer entity = _entityItems[i];
bool includeThisEntity = true; bool includeThisEntity = true;
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) { if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) {
includeThisEntity = false; includeThisEntity = false;
} }
if (hadElementExtraData) { if (hadElementExtraData) {
includeThisEntity = includeThisEntity && includeThisEntity = includeThisEntity &&
entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID());
} }
if (includeThisEntity && params.viewFrustum) { if (includeThisEntity && params.viewFrustum) {
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of // 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 // 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 its view // 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. // frustum culling on rendering.
AACube entityCube = entity->getMaximumAACube(); AACube entityCube = entity->getMaximumAACube();
if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
includeThisEntity = false; // out of view, don't include it includeThisEntity = false; // out of view, don't include it
}
}
if (includeThisEntity) {
indexesOfEntitiesToInclude << i;
numberOfEntities++;
} }
} }
if (includeThisEntity) {
indexesOfEntitiesToInclude << i;
numberOfEntities++;
}
} }
}
int numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); numberOfEntitiesOffset = packetData->getUncompressedByteOffset();
bool successAppendEntityCount = packetData->appendValue(numberOfEntities); bool successAppendEntityCount = packetData->appendValue(numberOfEntities);
if (successAppendEntityCount) { if (successAppendEntityCount) {
foreach (uint16_t i, indexesOfEntitiesToInclude) { foreach(uint16_t i, indexesOfEntitiesToInclude) {
EntityItemPointer entity = (*_entityItems)[i]; EntityItemPointer entity = _entityItems[i];
LevelDetails entityLevel = packetData->startLevel(); LevelDetails entityLevel = packetData->startLevel();
OctreeElement::AppendState appendEntityState = OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData,
entity->appendEntityData(packetData, params, entityTreeElementExtraEncodeData); params, entityTreeElementExtraEncodeData);
// If none of this entity data was able to be appended, then discard it // If none of this entity data was able to be appended, then discard it
// and don't include it in our entity count // and don't include it in our entity count
if (appendEntityState == OctreeElement::NONE) { if (appendEntityState == OctreeElement::NONE) {
packetData->discardLevel(entityLevel); packetData->discardLevel(entityLevel);
} else { } else {
// If either ALL or some of it got appended, then end the level (commit it) // 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 // and include the entity in our final count of entities
packetData->endLevel(entityLevel); packetData->endLevel(entityLevel);
actualNumberOfEntities++; actualNumberOfEntities++;
} }
// If the entity item got completely appended, then we can remove it from the extra encode data // If the entity item got completely appended, then we can remove it from the extra encode data
if (appendEntityState == OctreeElement::COMPLETED) { if (appendEntityState == OctreeElement::COMPLETED) {
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
} }
// If any part of the entity items didn't fit, then the element is considered partial // 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 // 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. // added itself to the extra encode data.
if (appendEntityState != OctreeElement::COMPLETED) { if (appendEntityState != OctreeElement::COMPLETED) {
appendElementState = OctreeElement::PARTIAL; 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;
} }
} 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 // 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 // then we need to do some additional processing, namely make sure our extraEncodeData is up to date for
@ -500,56 +498,41 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
// only called if we do intersect our bounding cube, but find if we actually intersect with entities... // only called if we do intersect our bounding cube, but find if we actually intersect with entities...
int entityNumber = 0; int entityNumber = 0;
EntityItems::iterator entityItr = _entityItems->begin();
EntityItems::const_iterator entityEnd = _entityItems->end();
bool somethingIntersected = false; bool somethingIntersected = false;
forEachEntity([&](EntityItemPointer entity) {
//float bestEntityDistance = distance;
while(entityItr != entityEnd) {
EntityItemPointer entity = (*entityItr);
AABox entityBox = entity->getAABox(); AABox entityBox = entity->getAABox();
float localDistance; float localDistance;
BoxFace localFace; BoxFace localFace;
// if the ray doesn't intersect with our cube, we can stop searching! // if the ray doesn't intersect with our cube, we can stop searching!
if (entityBox.findRayIntersection(origin, direction, localDistance, localFace)) { if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace)) {
return;
}
// extents is the entity relative, scaled, centered extents of the entity // extents is the entity relative, scaled, centered extents of the entity
glm::mat4 rotation = glm::mat4_cast(entity->getRotation()); glm::mat4 rotation = glm::mat4_cast(entity->getRotation());
glm::mat4 translation = glm::translate(entity->getPosition()); glm::mat4 translation = glm::translate(entity->getPosition());
glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 entityToWorldMatrix = translation * rotation;
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 dimensions = entity->getDimensions(); glm::vec3 dimensions = entity->getDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint); glm::vec3 corner = -(dimensions * registrationPoint);
AABox entityFrameBox(corner, dimensions); AABox entityFrameBox(corner, dimensions);
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) {
if (localDistance < distance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedRayIntersection()) {
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
localFace, intersectedObject, precisionPicking)) {
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) {
if (localDistance < distance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedRayIntersection()) {
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
localFace, intersectedObject, precisionPicking)) {
if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)entity.get();
somethingIntersected = true;
}
}
} else {
// if the entity type doesn't support a detailed intersection, then just return the non-AABox results
if (localDistance < distance) { if (localDistance < distance) {
distance = localDistance; distance = localDistance;
face = localFace; face = localFace;
@ -557,75 +540,79 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
somethingIntersected = true; somethingIntersected = true;
} }
} }
} else {
// if the entity type doesn't support a detailed intersection, then just return the non-AABox results
if (localDistance < distance) {
distance = localDistance;
face = localFace;
*intersectedObject = (void*)entity.get();
somethingIntersected = true;
}
} }
} }
} }
++entityItr;
entityNumber++; entityNumber++;
} });
return somethingIntersected; return somethingIntersected;
} }
// TODO: change this to use better bounding shape for entity than sphere // TODO: change this to use better bounding shape for entity than sphere
bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float radius, bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const { glm::vec3& penetration, void** penetratedObject) const {
EntityItems::iterator entityItr = _entityItems->begin(); bool result = false;
EntityItems::const_iterator entityEnd = _entityItems->end(); withReadLock([&] {
while(entityItr != entityEnd) { foreach(EntityItemPointer entity, _entityItems) {
EntityItemPointer entity = (*entityItr); glm::vec3 entityCenter = entity->getPosition();
glm::vec3 entityCenter = entity->getPosition(); float entityRadius = entity->getRadius();
float entityRadius = entity->getRadius();
// don't penetrate yourself // don't penetrate yourself
if (entityCenter == center && entityRadius == radius) { if (entityCenter == center && entityRadius == radius) {
return false; return;
} }
if (findSphereSpherePenetration(center, radius, entityCenter, entityRadius, penetration)) { if (findSphereSpherePenetration(center, radius, entityCenter, entityRadius, penetration)) {
// return true on first valid entity penetration // return true on first valid entity penetration
*penetratedObject = (void*)(entity.get());
*penetratedObject = (void*)(entity.get());
result = true;
return true; return;
}
} }
++entityItr; });
} return result;
return false;
} }
EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const { EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const {
EntityItemPointer closestEntity = NULL; EntityItemPointer closestEntity = NULL;
float closestEntityDistance = FLT_MAX; float closestEntityDistance = FLT_MAX;
uint16_t numberOfEntities = _entityItems->size(); withReadLock([&] {
for (uint16_t i = 0; i < numberOfEntities; i++) { foreach(EntityItemPointer entity, _entityItems) {
float distanceToEntity = glm::distance(position, (*_entityItems)[i]->getPosition()); float distanceToEntity = glm::distance2(position, entity->getPosition());
if (distanceToEntity < closestEntityDistance) { if (distanceToEntity < closestEntityDistance) {
closestEntity = (*_entityItems)[i]; closestEntity = entity;
}
} }
} });
return closestEntity; return closestEntity;
} }
// TODO: change this to use better bounding shape for entity than sphere // TODO: change this to use better bounding shape for entity than sphere
void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<EntityItemPointer>& foundEntities) const { void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<EntityItemPointer>& foundEntities) const {
uint16_t numberOfEntities = _entityItems->size(); float compareRadius = searchRadius * searchRadius;
for (uint16_t i = 0; i < numberOfEntities; i++) { forEachEntity([&](EntityItemPointer entity) {
EntityItemPointer entity = (*_entityItems)[i]; // For iteration like this, avoid the use of square roots by comparing distances squared
float distance = glm::length(entity->getPosition() - searchPosition); float distanceSquared = glm::length2(entity->getPosition() - searchPosition);
if (distance < searchRadius + entity->getRadius()) { float otherRadius = entity->getRadius();
if (distanceSquared < (compareRadius + (otherRadius * otherRadius))) {
foundEntities.push_back(entity); foundEntities.push_back(entity);
} }
} });
} }
// TODO: change this to use better bounding shape for entity than sphere // TODO: change this to use better bounding shape for entity than sphere
void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer>& foundEntities) { void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer>& foundEntities) {
EntityItems::iterator entityItr = _entityItems->begin();
EntityItems::iterator entityEnd = _entityItems->end();
AACube entityCube; AACube entityCube;
while(entityItr != entityEnd) { forEachEntity([&](EntityItemPointer entity) {
EntityItemPointer entity = (*entityItr);
float radius = entity->getRadius(); float radius = entity->getRadius();
// NOTE: we actually do cube-cube collision queries here, which is sloppy but good enough for now // NOTE: we actually do cube-cube collision queries here, which is sloppy but good enough for now
// TODO: decide whether to replace entityCube-cube query with sphere-cube (requires a square root // TODO: decide whether to replace entityCube-cube query with sphere-cube (requires a square root
@ -634,64 +621,57 @@ void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer
if (entityCube.touches(box)) { if (entityCube.touches(box)) {
foundEntities.push_back(entity); foundEntities.push_back(entity);
} }
++entityItr; });
}
} }
EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const {
EntityItemPointer foundEntity = NULL; EntityItemPointer foundEntity = NULL;
uint16_t numberOfEntities = _entityItems->size(); withReadLock([&] {
for (uint16_t i = 0; i < numberOfEntities; i++) { foreach(EntityItemPointer entity, _entityItems) {
if ((*_entityItems)[i]->getEntityItemID() == id) { if (entity->getEntityItemID() == id) {
foundEntity = (*_entityItems)[i]; foundEntity = entity;
break; break;
}
} }
} });
return foundEntity;
}
EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) {
EntityItemPointer foundEntity = NULL;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
if ((*_entityItems)[i]->getEntityItemID() == id) {
foundEntity = (*_entityItems)[i];
break;
}
}
return foundEntity; return foundEntity;
} }
void EntityTreeElement::cleanupEntities() { void EntityTreeElement::cleanupEntities() {
uint16_t numberOfEntities = _entityItems->size(); withWriteLock([&] {
for (uint16_t i = 0; i < numberOfEntities; i++) { foreach(EntityItemPointer entity, _entityItems) {
EntityItemPointer entity = (*_entityItems)[i]; // NOTE: We explicitly don't delete the EntityItem here because since we only
entity->_element = NULL; // access it by smart pointers, when we remove it from the _entityItems
// we know that it will be deleted.
// NOTE: We explicitly don't delete the EntityItem here because since we only //delete entity;
// access it by smart pointers, when we remove it from the _entityItems entity->_element = NULL;
// we know that it will be deleted. }
//delete entity; _entityItems.clear();
} });
_entityItems->clear();
} }
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
bool foundEntity = false; bool foundEntity = false;
uint16_t numberOfEntities = _entityItems->size(); withWriteLock([&] {
for (uint16_t i = 0; i < numberOfEntities; i++) { uint16_t numberOfEntities = _entityItems.size();
if ((*_entityItems)[i]->getEntityItemID() == id) { for (uint16_t i = 0; i < numberOfEntities; i++) {
foundEntity = true; EntityItemPointer& entity = _entityItems[i];
(*_entityItems)[i]->_element = NULL; if (entity->getEntityItemID() == id) {
_entityItems->removeAt(i); foundEntity = true;
break; entity->_element = NULL;
_entityItems.removeAt(i);
break;
}
} }
} });
return foundEntity; return foundEntity;
} }
bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) { bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) {
int numEntries = _entityItems->removeAll(entity); int numEntries = 0;
withWriteLock([&] {
numEntries = _entityItems.removeAll(entity);
});
if (numEntries > 0) { if (numEntries > 0) {
assert(entity->_element.get() == this); assert(entity->_element.get() == this);
entity->_element = NULL; entity->_element = NULL;
@ -813,7 +793,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
void EntityTreeElement::addEntityItem(EntityItemPointer entity) { void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
assert(entity); assert(entity);
assert(entity->_element == nullptr); assert(entity->_element == nullptr);
_entityItems->push_back(entity); withWriteLock([&] {
_entityItems.push_back(entity);
});
entity->_element = getThisPointer(); entity->_element = getThisPointer();
} }
@ -846,30 +828,39 @@ bool EntityTreeElement::pruneChildren() {
} }
void EntityTreeElement::expandExtentsToContents(Extents& extents) { void EntityTreeElement::expandExtentsToContents(Extents& extents) {
if (_entityItems->size()) { withReadLock([&] {
for (uint16_t i = 0; i < _entityItems->size(); i++) { foreach(EntityItemPointer entity, _entityItems) {
EntityItemPointer entity = (*_entityItems)[i];
extents.add(entity->getAABox()); extents.add(entity->getAABox());
} }
} });
} }
uint16_t EntityTreeElement::size() const {
uint16_t result = 0;
withReadLock([&] {
result = _entityItems.size();
});
return result;
}
void EntityTreeElement::debugDump() { void EntityTreeElement::debugDump() {
qCDebug(entities) << "EntityTreeElement..."; qCDebug(entities) << "EntityTreeElement...";
qCDebug(entities) << " cube:" << _cube; qCDebug(entities) << " cube:" << _cube;
qCDebug(entities) << " has child elements:" << getChildCount(); qCDebug(entities) << " has child elements:" << getChildCount();
if (_entityItems->size()) {
qCDebug(entities) << " has entities:" << _entityItems->size(); withReadLock([&] {
qCDebug(entities) << "--------------------------------------------------"; if (_entityItems.size()) {
for (uint16_t i = 0; i < _entityItems->size(); i++) { qCDebug(entities) << " has entities:" << _entityItems.size();
EntityItemPointer entity = (*_entityItems)[i]; qCDebug(entities) << "--------------------------------------------------";
entity->debugDump(); for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = _entityItems[i];
entity->debugDump();
}
qCDebug(entities) << "--------------------------------------------------";
} else {
qCDebug(entities) << " NO entities!";
} }
qCDebug(entities) << "--------------------------------------------------"; });
} else {
qCDebug(entities) << " NO entities!";
}
} }

View file

@ -75,7 +75,7 @@ public:
}; };
class EntityTreeElement : public OctreeElement { class EntityTreeElement : public OctreeElement, ReadWriteLockable {
friend class EntityTree; // to allow createElement to new us... friend class EntityTree; // to allow createElement to new us...
EntityTreeElement(unsigned char* octalCode = NULL); EntityTreeElement(unsigned char* octalCode = NULL);
@ -149,10 +149,18 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius, virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const; glm::vec3& penetration, void** penetratedObject) const;
const EntityItems& getEntities() const { return *_entityItems; }
EntityItems& getEntities() { return *_entityItems; }
bool hasEntities() const { return _entityItems ? _entityItems->size() > 0 : false; } template <typename F>
void forEachEntity(F f) const {
withReadLock([&] {
foreach(EntityItemPointer entityItem, _entityItems) {
f(entityItem);
}
});
}
virtual uint16_t size() const;
bool hasEntities() const { return size() > 0; }
void setTree(EntityTreePointer tree) { _myTree = tree; } void setTree(EntityTreePointer tree) { _myTree = tree; }
EntityTreePointer getTree() const { return _myTree; } EntityTreePointer getTree() const { return _myTree; }
@ -177,8 +185,6 @@ public:
EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const;
void getEntitiesInside(const AACube& box, QVector<EntityItemPointer>& foundEntities); void getEntitiesInside(const AACube& box, QVector<EntityItemPointer>& foundEntities);
EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id);
void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities
bool removeEntityWithEntityItemID(const EntityItemID& id); bool removeEntityWithEntityItemID(const EntityItemID& id);
bool removeEntityItem(EntityItemPointer entity); bool removeEntityItem(EntityItemPointer entity);
@ -217,7 +223,7 @@ public:
protected: protected:
virtual void init(unsigned char * octalCode); virtual void init(unsigned char * octalCode);
EntityTreePointer _myTree; EntityTreePointer _myTree;
EntityItems* _entityItems; EntityItems _entityItems;
}; };
#endif // hifi_EntityTreeElement_h #endif // hifi_EntityTreeElement_h

View file

@ -33,10 +33,9 @@ void EntityTreeHeadlessViewer::init() {
void EntityTreeHeadlessViewer::update() { void EntityTreeHeadlessViewer::update() {
if (_tree) { if (_tree) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
if (tree->tryLockForWrite()) { tree->withTryWriteLock([&] {
tree->update(); tree->update();
tree->unlock(); });
}
} }
} }

View file

@ -39,12 +39,13 @@ bool RecurseOctreeToMapOperator::preRecursion(OctreeElementPointer element) {
} }
bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) { bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) {
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
const EntityItems& entities = entityTreeElement->getEntities();
EntityItemProperties defaultProperties;
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
QVariantList entitiesQList = qvariant_cast<QVariantList>(_map["Entities"]); QVariantList entitiesQList = qvariant_cast<QVariantList>(_map["Entities"]);
foreach (EntityItemPointer entityItem, entities) { entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
EntityItemProperties properties = entityItem->getProperties(); EntityItemProperties properties = entityItem->getProperties();
QScriptValue qScriptValues; QScriptValue qScriptValues;
if (_skipDefaultValues) { if (_skipDefaultValues) {
@ -53,7 +54,8 @@ bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) {
qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties); qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties);
} }
entitiesQList << qScriptValues.toVariant(); entitiesQList << qScriptValues.toVariant();
} });
_map["Entities"] = entitiesQList; _map["Entities"] = entitiesQList;
if (element == _top) { if (element == _top) {
_withinTop = false; _withinTop = false;

View file

@ -18,7 +18,8 @@
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/QUuid> #include <QtCore/QUuid>
#include <QReadWriteLock>
#include <shared/ReadWriteLockable.h>
#include <NLPacket.h> #include <NLPacket.h>
#include <Node.h> #include <Node.h>
@ -78,7 +79,7 @@ private:
/// Map between node IDs and their reported JurisdictionMap. Typically used by classes that need to know which nodes are /// Map between node IDs and their reported JurisdictionMap. Typically used by classes that need to know which nodes are
/// managing which jurisdictions. /// managing which jurisdictions.
class NodeToJurisdictionMap : public QMap<QUuid, JurisdictionMap>, public QReadWriteLock {}; class NodeToJurisdictionMap : public QMap<QUuid, JurisdictionMap>, public ReadWriteLockable {};
typedef QMap<QUuid, JurisdictionMap>::iterator NodeToJurisdictionMapIterator; typedef QMap<QUuid, JurisdictionMap>::iterator NodeToJurisdictionMapIterator;

View file

@ -60,7 +60,6 @@ Octree::Octree(bool shouldReaverage) :
_isDirty(true), _isDirty(true),
_shouldReaverage(shouldReaverage), _shouldReaverage(shouldReaverage),
_stopImport(false), _stopImport(false),
_lock(QReadWriteLock::Recursive),
_isViewing(false), _isViewing(false),
_isServer(false) _isServer(false)
{ {
@ -502,9 +501,7 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long
void Octree::deleteOctreeElementAt(float x, float y, float z, float s) { void Octree::deleteOctreeElementAt(float x, float y, float z, float s) {
unsigned char* octalCode = pointToOctalCode(x,y,z,s); unsigned char* octalCode = pointToOctalCode(x,y,z,s);
lockForWrite();
deleteOctalCodeFromTree(octalCode); deleteOctalCodeFromTree(octalCode);
unlock();
delete[] octalCode; // cleanup memory delete[] octalCode; // cleanup memory
} }
@ -529,7 +526,9 @@ void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool colla
args.deleteLastChild = false; args.deleteLastChild = false;
args.pathChanged = false; args.pathChanged = false;
deleteOctalCodeFromTreeRecursion(_rootElement, &args); withWriteLock([&] {
deleteOctalCodeFromTreeRecursion(_rootElement, &args);
});
} }
void Octree::deleteOctalCodeFromTreeRecursion(OctreeElementPointer element, void* extraData) { void Octree::deleteOctalCodeFromTreeRecursion(OctreeElementPointer element, void* extraData) {
@ -725,29 +724,15 @@ bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
intersectedObject, false, precisionPicking}; intersectedObject, false, precisionPicking};
distance = FLT_MAX; distance = FLT_MAX;
bool gotLock = false; bool requireLock = lockType == Octree::Lock;
if (lockType == Octree::Lock) { bool lockResult = withReadLock([&]{
lockForRead(); recurseTreeWithOperation(findRayIntersectionOp, &args);
gotLock = true; }, requireLock);
} else if (lockType == Octree::TryLock) {
gotLock = tryLockForRead();
if (!gotLock) {
if (accurateResult) {
*accurateResult = false; // if user asked to accuracy or result, let them know this is inaccurate
}
return args.found; // if we wanted to tryLock, and we couldn't then just bail...
}
}
recurseTreeWithOperation(findRayIntersectionOp, &args);
if (gotLock) {
unlock();
}
if (accurateResult) { if (accurateResult) {
*accurateResult = true; // if user asked to accuracy or result, let them know this is accurate *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
} }
return args.found; return args.found;
} }
@ -793,31 +778,16 @@ bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::v
NULL }; NULL };
penetration = glm::vec3(0.0f, 0.0f, 0.0f); penetration = glm::vec3(0.0f, 0.0f, 0.0f);
bool gotLock = false; bool requireLock = lockType == Octree::Lock;
if (lockType == Octree::Lock) { bool lockResult = withReadLock([&]{
lockForRead(); recurseTreeWithOperation(findSpherePenetrationOp, &args);
gotLock = true; if (penetratedObject) {
} else if (lockType == Octree::TryLock) { *penetratedObject = args.penetratedObject;
gotLock = tryLockForRead();
if (!gotLock) {
if (accurateResult) {
*accurateResult = false; // if user asked to accuracy or result, let them know this is inaccurate
}
return args.found; // if we wanted to tryLock, and we couldn't then just bail...
} }
} }, requireLock);
recurseTreeWithOperation(findSpherePenetrationOp, &args);
if (penetratedObject) {
*penetratedObject = args.penetratedObject;
}
if (gotLock) {
unlock();
}
if (accurateResult) { if (accurateResult) {
*accurateResult = true; // if user asked to accuracy or result, let them know this is accurate *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
} }
return args.found; return args.found;
} }
@ -896,40 +866,24 @@ bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end
CapsuleArgs args = { start, end, radius, penetration, false }; CapsuleArgs args = { start, end, radius, penetration, false };
penetration = glm::vec3(0.0f, 0.0f, 0.0f); penetration = glm::vec3(0.0f, 0.0f, 0.0f);
bool gotLock = false;
if (lockType == Octree::Lock) {
lockForRead();
gotLock = true;
} else if (lockType == Octree::TryLock) {
gotLock = tryLockForRead();
if (!gotLock) {
if (accurateResult) {
*accurateResult = false; // if user asked to accuracy or result, let them know this is inaccurate
}
return args.found; // if we wanted to tryLock, and we couldn't then just bail...
}
}
recurseTreeWithOperation(findCapsulePenetrationOp, &args); bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
if (gotLock) { recurseTreeWithOperation(findCapsulePenetrationOp, &args);
unlock(); }, requireLock);
}
if (accurateResult) { if (accurateResult) {
*accurateResult = true; // if user asked to accuracy or result, let them know this is accurate *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
} }
return args.found; return args.found;
} }
bool Octree::findContentInCube(const AACube& cube, CubeList& cubes) { bool Octree::findContentInCube(const AACube& cube, CubeList& cubes) {
if (!tryLockForRead()) { return withTryReadLock([&]{
return false; ContentArgs args = { cube, &cubes };
} recurseTreeWithOperation(findContentInCubeOp, &args);
ContentArgs args = { cube, &cubes }; });
recurseTreeWithOperation(findContentInCubeOp, &args);
unlock();
return true;
} }
class GetElementEnclosingArgs { class GetElementEnclosingArgs {
@ -959,29 +913,15 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc
args.point = point; args.point = point;
args.element = NULL; args.element = NULL;
bool gotLock = false; bool requireLock = lockType == Octree::Lock;
if (lockType == Octree::Lock) { bool lockResult = withReadLock([&]{
lockForRead(); recurseTreeWithOperation(getElementEnclosingOperation, (void*)&args);
gotLock = true; }, requireLock);
} else if (lockType == Octree::TryLock) {
gotLock = tryLockForRead();
if (!gotLock) {
if (accurateResult) {
*accurateResult = false; // if user asked to accuracy or result, let them know this is inaccurate
}
return args.element; // if we wanted to tryLock, and we couldn't then just bail...
}
}
recurseTreeWithOperation(getElementEnclosingOperation, (void*)&args);
if (gotLock) {
unlock();
}
if (accurateResult) { if (accurateResult) {
*accurateResult = false; // if user asked to accuracy or result, let them know this is inaccurate *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
} }
return args.element; return args.element;
} }
@ -2204,11 +2144,11 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element)
while (!elementBag.isEmpty()) { while (!elementBag.isEmpty()) {
OctreeElementPointer subTree = elementBag.extract(); OctreeElementPointer subTree = elementBag.extract();
lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
params.extraEncodeData = &extraEncodeData; withReadLock([&] {
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params); params.extraEncodeData = &extraEncodeData;
unlock(); bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
});
// if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again // if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again
if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) { if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) {

View file

@ -12,9 +12,11 @@
#ifndef hifi_Octree_h #ifndef hifi_Octree_h
#define hifi_Octree_h #define hifi_Octree_h
#include <set>
#include <SimpleMovingAverage.h>
#include <memory> #include <memory>
#include <set>
#include <QHash>
#include <QObject>
class CoverageMap; class CoverageMap;
class ReadBitstreamToTreeParams; class ReadBitstreamToTreeParams;
@ -25,6 +27,10 @@ class OctreePacketData;
class Shape; class Shape;
typedef std::shared_ptr<Octree> OctreePointer; typedef std::shared_ptr<Octree> OctreePointer;
#include <shared/ReadWriteLockable.h>
#include <SimpleMovingAverage.h>
#include "JurisdictionMap.h" #include "JurisdictionMap.h"
#include "ViewFrustum.h" #include "ViewFrustum.h"
#include "OctreeElement.h" #include "OctreeElement.h"
@ -32,9 +38,6 @@ typedef std::shared_ptr<Octree> OctreePointer;
#include "OctreePacketData.h" #include "OctreePacketData.h"
#include "OctreeSceneStats.h" #include "OctreeSceneStats.h"
#include <QHash>
#include <QObject>
#include <QReadWriteLock>
extern QVector<QString> PERSIST_EXTENSIONS; extern QVector<QString> PERSIST_EXTENSIONS;
@ -216,7 +219,7 @@ public:
{} {}
}; };
class Octree : public QObject, public std::enable_shared_from_this<Octree> { class Octree : public QObject, public std::enable_shared_from_this<Octree>, public ReadWriteLockable {
Q_OBJECT Q_OBJECT
public: public:
Octree(bool shouldReaverage = false); Octree(bool shouldReaverage = false);
@ -289,17 +292,10 @@ public:
void clearDirtyBit() { _isDirty = false; } void clearDirtyBit() { _isDirty = false; }
void setDirtyBit() { _isDirty = true; } void setDirtyBit() { _isDirty = true; }
// Octree does not currently handle its own locking, caller must use these to lock/unlock
void lockForRead() { _lock.lockForRead(); }
bool tryLockForRead() { return _lock.tryLockForRead(); }
void lockForWrite() { _lock.lockForWrite(); }
bool tryLockForWrite() { return _lock.tryLockForWrite(); }
void unlock() { _lock.unlock(); }
// output hints from the encode process // output hints from the encode process
typedef enum { typedef enum {
Lock, Lock,
TryLock, TryLock
NoLock
} lockType; } lockType;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -409,8 +405,6 @@ protected:
bool _shouldReaverage; bool _shouldReaverage;
bool _stopImport; bool _stopImport;
QReadWriteLock _lock;
bool _isViewing; bool _isViewing;
bool _isServer; bool _isServer;
}; };

View file

@ -51,11 +51,11 @@ bool OctreeEditPacketSender::serversExist() const {
if (_serverJurisdictions) { if (_serverJurisdictions) {
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're // lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction // missing at least one jurisdiction
_serverJurisdictions->lockForRead(); _serverJurisdictions->withReadLock([&] {
if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) { if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) {
atLeastOneJurisdictionMissing = true; atLeastOneJurisdictionMissing = true;
} }
_serverJurisdictions->unlock(); });
} }
hasServers = true; hasServers = true;
} }
@ -178,10 +178,10 @@ void OctreeEditPacketSender::queuePacketToNodes(std::unique_ptr<NLPacket> packet
bool isMyJurisdiction = true; bool isMyJurisdiction = true;
// we need to get the jurisdiction for this // we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server // here we need to get the "pending packet" for this server
_serverJurisdictions->lockForRead(); _serverJurisdictions->withReadLock([&] {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
_serverJurisdictions->unlock(); });
if (isMyJurisdiction) { if (isMyJurisdiction) {
// make a copy of this packet for this node and queue // make a copy of this packet for this node and queue
@ -236,15 +236,15 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray&
} else if (_serverJurisdictions) { } else if (_serverJurisdictions) {
// we need to get the jurisdiction for this // we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server // here we need to get the "pending packet" for this server
_serverJurisdictions->lockForRead(); _serverJurisdictions->withReadLock([&] {
if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) { if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(reinterpret_cast<const unsigned char*>(editMessage.data()), isMyJurisdiction = (map.isMyJurisdiction(reinterpret_cast<const unsigned char*>(editMessage.data()),
CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
} else { } else {
isMyJurisdiction = false; isMyJurisdiction = false;
} }
_serverJurisdictions->unlock(); });
} }
if (isMyJurisdiction) { if (isMyJurisdiction) {
std::unique_ptr<NLPacket>& bufferedPacket = _pendingEditPackets[nodeUUID]; std::unique_ptr<NLPacket>& bufferedPacket = _pendingEditPackets[nodeUUID];

View file

@ -43,11 +43,11 @@ void OctreeHeadlessViewer::queryOctree() {
qCDebug(octree) << "---------------"; qCDebug(octree) << "---------------";
qCDebug(octree) << "_jurisdictionListener=" << _jurisdictionListener; qCDebug(octree) << "_jurisdictionListener=" << _jurisdictionListener;
qCDebug(octree) << "Jurisdictions..."; qCDebug(octree) << "Jurisdictions...";
jurisdictions.lockForRead(); jurisdictions.withReadLock([&] {
for (NodeToJurisdictionMapIterator i = jurisdictions.begin(); i != jurisdictions.end(); ++i) { for (NodeToJurisdictionMapIterator i = jurisdictions.begin(); i != jurisdictions.end(); ++i) {
qCDebug(octree) << i.key() << ": " << &i.value(); qCDebug(octree) << i.key() << ": " << &i.value();
} }
jurisdictions.unlock(); });
qCDebug(octree) << "---------------"; qCDebug(octree) << "---------------";
} }
@ -84,28 +84,30 @@ void OctreeHeadlessViewer::queryOctree() {
// if we haven't heard from this voxel server, go ahead and send it a query, so we // if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction... // can get the jurisdiction...
jurisdictions.lockForRead(); VoxelPositionSize rootDetails;
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { bool foundRootDetails = false;
jurisdictions.unlock(); jurisdictions.withReadLock([&] {
unknownJurisdictionServers++; if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
} else { unknownJurisdictionServers++;
return;
}
const JurisdictionMap& map = (jurisdictions)[nodeUUID]; const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode(); unsigned char* rootCode = map.getRootOctalCode();
if (!rootCode) {
return;
}
if (rootCode) { voxelDetailsForCode(rootCode, rootDetails);
VoxelPositionSize rootDetails; foundRootDetails = true;
voxelDetailsForCode(rootCode, rootDetails); });
jurisdictions.unlock();
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) { if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inViewServers++; inViewServers++;
}
} else {
jurisdictions.unlock();
} }
} }
} }
@ -149,35 +151,38 @@ void OctreeHeadlessViewer::queryOctree() {
// if we haven't heard from this voxel server, go ahead and send it a query, so we // if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction... // can get the jurisdiction...
jurisdictions.lockForRead(); VoxelPositionSize rootDetails;
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { bool foundRootDetails = false;
jurisdictions.unlock(); jurisdictions.withReadLock([&] {
unknownView = true; // assume it's in view if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
if (wantExtraDebugging) { unknownView = true; // assume it's in view
qCDebug(octree) << "no known jurisdiction for node " << *node << ", assume it's visible."; if (wantExtraDebugging) {
qCDebug(octree) << "no known jurisdiction for node " << *node << ", assume it's visible.";
}
return;
} }
} else {
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode(); unsigned char* rootCode = map.getRootOctalCode();
if (rootCode) { if (!rootCode) {
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
jurisdictions.unlock();
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
} else {
jurisdictions.unlock();
if (wantExtraDebugging) { if (wantExtraDebugging) {
qCDebug(octree) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; qCDebug(octree) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
} }
return;
}
voxelDetailsForCode(rootCode, rootDetails);
foundRootDetails = true;
});
if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
} }
} }

View file

@ -127,18 +127,17 @@ bool OctreePersistThread::process() {
bool persistantFileRead; bool persistantFileRead;
_tree->lockForWrite(); _tree->withWriteLock([&] {
{
PerformanceWarning warn(true, "Loading Octree File", true); PerformanceWarning warn(true, "Loading Octree File", true);
// First check to make sure "lock" file doesn't exist. If it does exist, then // First check to make sure "lock" file doesn't exist. If it does exist, then
// our last save crashed during the save, and we want to load our most recent backup. // our last save crashed during the save, and we want to load our most recent backup.
QString lockFileName = _filename + ".lock"; QString lockFileName = _filename + ".lock";
std::ifstream lockFile(qPrintable(lockFileName), std::ios::in|std::ios::binary|std::ios::ate); std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate);
if(lockFile.is_open()) { if (lockFile.is_open()) {
qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName
<< "-- Attempting to restore from previous backup file."; << "-- Attempting to restore from previous backup file.";
// This is where we should attempt to find the most recent backup and restore from // This is where we should attempt to find the most recent backup and restore from
// that file as our persist file. // that file as our persist file.
restoreFromMostRecentBackup(); restoreFromMostRecentBackup();
@ -151,8 +150,7 @@ bool OctreePersistThread::process() {
persistantFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); persistantFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit()));
_tree->pruneTree(); _tree->pruneTree();
} });
_tree->unlock();
quint64 loadDone = usecTimestampNow(); quint64 loadDone = usecTimestampNow();
_loadTimeUSecs = loadDone - loadStarted; _loadTimeUSecs = loadDone - loadStarted;
@ -233,13 +231,12 @@ void OctreePersistThread::aboutToFinish() {
void OctreePersistThread::persist() { void OctreePersistThread::persist() {
if (_tree->isDirty()) { if (_tree->isDirty()) {
_tree->lockForWrite();
{ _tree->withWriteLock([&] {
qCDebug(octree) << "pruning Octree before saving..."; qCDebug(octree) << "pruning Octree before saving...";
_tree->pruneTree(); _tree->pruneTree();
qCDebug(octree) << "DONE pruning Octree before saving..."; qCDebug(octree) << "DONE pruning Octree before saving...";
} });
_tree->unlock();
qCDebug(octree) << "persist operation calling backup..."; qCDebug(octree) << "persist operation calling backup...";
backup(); // handle backup if requested backup(); // handle backup if requested

View file

@ -117,35 +117,35 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN
// ask the VoxelTree to read the bitstream into the tree // ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
sourceUUID, sourceNode, false, packet.getVersion()); sourceUUID, sourceNode, false, packet.getVersion());
quint64 startLock = usecTimestampNow(); quint64 startUncompress, startLock = usecTimestampNow();
quint64 startReadBitsteam, endReadBitsteam;
// FIXME STUTTER - there may be an opportunity to bump this lock outside of the // FIXME STUTTER - there may be an opportunity to bump this lock outside of the
// loop to reduce the amount of locking/unlocking we're doing // loop to reduce the amount of locking/unlocking we're doing
_tree->lockForWrite(); _tree->withWriteLock([&] {
quint64 startUncompress = usecTimestampNow(); startUncompress = usecTimestampNow();
OctreePacketData packetData(packetIsCompressed); OctreePacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(reinterpret_cast<unsigned char*>(packet.getPayload() + packet.pos()), packetData.loadFinalizedContent(reinterpret_cast<unsigned char*>(packet.getPayload() + packet.pos()),
sectionLength); sectionLength);
if (extraDebugging) { if (extraDebugging) {
qCDebug(octree, "OctreeRenderer::processDatagram() ... Got Packet Section" qCDebug(octree, "OctreeRenderer::processDatagram() ... Got Packet Section"
" color:%s compressed:%s sequence: %u flight:%d usec size:%lld data:%lld" " color:%s compressed:%s sequence: %u flight:%d usec size:%lld data:%lld"
" subsection:%d sectionLength:%d uncompressed:%d", " subsection:%d sectionLength:%d uncompressed:%d",
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed), debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
sequence, flightTime, packet.getDataSize(), packet.bytesLeftToRead(), subsection, sectionLength, sequence, flightTime, packet.getDataSize(), packet.bytesLeftToRead(), subsection, sectionLength,
packetData.getUncompressedSize()); packetData.getUncompressedSize());
} }
if (extraDebugging) { if (extraDebugging) {
qCDebug(octree) << "OctreeRenderer::processDatagram() ******* START _tree->readBitstreamToTree()..."; qCDebug(octree) << "OctreeRenderer::processDatagram() ******* START _tree->readBitstreamToTree()...";
} }
quint64 startReadBitsteam = usecTimestampNow(); startReadBitsteam = usecTimestampNow();
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); _tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
quint64 endReadBitsteam = usecTimestampNow(); endReadBitsteam = usecTimestampNow();
if (extraDebugging) { if (extraDebugging) {
qCDebug(octree) << "OctreeRenderer::processDatagram() ******* END _tree->readBitstreamToTree()..."; qCDebug(octree) << "OctreeRenderer::processDatagram() ******* END _tree->readBitstreamToTree()...";
} }
_tree->unlock(); });
// seek forwards in packet // seek forwards in packet
packet.seek(packet.pos() + sectionLength); packet.seek(packet.pos() + sectionLength);
@ -211,17 +211,17 @@ bool OctreeRenderer::renderOperation(OctreeElementPointer element, void* extraDa
void OctreeRenderer::render(RenderArgs* renderArgs) { void OctreeRenderer::render(RenderArgs* renderArgs) {
if (_tree) { if (_tree) {
renderArgs->_renderer = this; renderArgs->_renderer = this;
_tree->lockForRead(); _tree->withReadLock([&] {
_tree->recurseTreeWithOperation(renderOperation, renderArgs); _tree->recurseTreeWithOperation(renderOperation, renderArgs);
_tree->unlock(); });
} }
} }
void OctreeRenderer::clear() { void OctreeRenderer::clear() {
if (_tree) { if (_tree) {
_tree->lockForWrite(); _tree->withWriteLock([&] {
_tree->eraseAllOctreeElements(); _tree->eraseAllOctreeElements();
_tree->unlock(); });
} }
} }

View file

@ -15,6 +15,7 @@
#include <stdint.h> #include <stdint.h>
#include <NodeList.h> #include <NodeList.h>
#include <shared/ReadWriteLockable.h>
#include "JurisdictionMap.h" #include "JurisdictionMap.h"
#include "OctreePacketData.h" #include "OctreePacketData.h"
@ -281,7 +282,7 @@ private:
/// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent /// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent
/// which octree stats /// which octree stats
typedef std::map<QUuid, OctreeSceneStats> NodeToOctreeSceneStats; class NodeToOctreeSceneStats : public std::map<QUuid, OctreeSceneStats>, public ReadWriteLockable {};
typedef std::map<QUuid, OctreeSceneStats>::iterator NodeToOctreeSceneStatsIterator; typedef NodeToOctreeSceneStats::iterator NodeToOctreeSceneStatsIterator;
#endif // hifi_OctreeSceneStats_h #endif // hifi_OctreeSceneStats_h

View file

@ -33,22 +33,10 @@ const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
bool EntityMotionState::entityTreeIsLocked() const { bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity ? _entity->getElement() : nullptr; EntityTreeElementPointer element = _entity ? _entity->getElement() : nullptr;
EntityTreePointer tree = element ? element->getTree() : nullptr; EntityTreePointer tree = element ? element->getTree() : nullptr;
if (tree) { if (!tree) {
bool readSuccess = tree->tryLockForRead();
if (readSuccess) {
tree->unlock();
}
bool writeSuccess = tree->tryLockForWrite();
if (writeSuccess) {
tree->unlock();
}
if (readSuccess && writeSuccess) {
return false; // if we can take either kind of lock, there was no tree lock.
}
return true; // either read or write failed, so there is some lock in place.
} else {
return true; return true;
} }
return true;
} }
#else #else
bool entityTreeIsLocked() { bool entityTreeIsLocked() {

View file

@ -17,12 +17,14 @@
#include <btBulletDynamicsCommon.h> #include <btBulletDynamicsCommon.h>
#include <shared/ReadWriteLockable.h>
#include "ObjectMotionState.h" #include "ObjectMotionState.h"
#include "BulletUtil.h" #include "BulletUtil.h"
#include "EntityActionInterface.h" #include "EntityActionInterface.h"
class ObjectAction : public btActionInterface, public EntityActionInterface { class ObjectAction : public btActionInterface, public EntityActionInterface, public ReadWriteLockable {
public: public:
ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity); ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectAction(); virtual ~ObjectAction();
@ -57,14 +59,7 @@ protected:
virtual void setAngularVelocity(glm::vec3 angularVelocity); virtual void setAngularVelocity(glm::vec3 angularVelocity);
virtual void activateBody(); virtual void activateBody();
void lockForRead() { _lock.lockForRead(); }
bool tryLockForRead() { return _lock.tryLockForRead(); }
void lockForWrite() { _lock.lockForWrite(); }
bool tryLockForWrite() { return _lock.tryLockForWrite(); }
void unlock() { _lock.unlock(); }
private: private:
QReadWriteLock _lock;
protected: protected:
bool _active; bool _active;

View file

@ -33,56 +33,49 @@ ObjectActionOffset::~ObjectActionOffset() {
} }
void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) { void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
if (!tryLockForRead()) { withTryReadLock([&]{
// don't risk hanging the thread running the physics simulation auto ownerEntity = _ownerEntity.lock();
return; if (!ownerEntity) {
} return;
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
unlock();
return;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* rigidBody = motionState->getRigidBody();
if (!rigidBody) {
unlock();
qDebug() << "ObjectActionOffset::updateActionWorker no rigidBody";
return;
}
const float MAX_LINEAR_TIMESCALE = 600.0f; // 10 minutes is a long time
if (_positionalTargetSet && _linearTimeScale < MAX_LINEAR_TIMESCALE) {
glm::vec3 objectPosition = bulletToGLM(rigidBody->getCenterOfMassPosition());
glm::vec3 springAxis = objectPosition - _pointToOffsetFrom; // from anchor to object
float distance = glm::length(springAxis);
if (distance > FLT_EPSILON) {
springAxis /= distance; // normalize springAxis
// compute (critically damped) target velocity of spring relaxation
glm::vec3 offset = (distance - _linearDistance) * springAxis;
glm::vec3 targetVelocity = (-1.0f / _linearTimeScale) * offset;
// compute current velocity and its parallel component
glm::vec3 currentVelocity = bulletToGLM(rigidBody->getLinearVelocity());
glm::vec3 parallelVelocity = glm::dot(currentVelocity, springAxis) * springAxis;
// we blend the parallel component with the spring's target velocity to get the new velocity
float blend = deltaTimeStep / _linearTimeScale;
if (blend > 1.0f) {
blend = 1.0f;
}
rigidBody->setLinearVelocity(glmToBullet(currentVelocity + blend * (targetVelocity - parallelVelocity)));
} }
}
unlock(); void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
return;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* rigidBody = motionState->getRigidBody();
if (!rigidBody) {
qDebug() << "ObjectActionOffset::updateActionWorker no rigidBody";
return;
}
const float MAX_LINEAR_TIMESCALE = 600.0f; // 10 minutes is a long time
if (_positionalTargetSet && _linearTimeScale < MAX_LINEAR_TIMESCALE) {
glm::vec3 objectPosition = bulletToGLM(rigidBody->getCenterOfMassPosition());
glm::vec3 springAxis = objectPosition - _pointToOffsetFrom; // from anchor to object
float distance = glm::length(springAxis);
if (distance > FLT_EPSILON) {
springAxis /= distance; // normalize springAxis
// compute (critically damped) target velocity of spring relaxation
glm::vec3 offset = (distance - _linearDistance) * springAxis;
glm::vec3 targetVelocity = (-1.0f / _linearTimeScale) * offset;
// compute current velocity and its parallel component
glm::vec3 currentVelocity = bulletToGLM(rigidBody->getLinearVelocity());
glm::vec3 parallelVelocity = glm::dot(currentVelocity, springAxis) * springAxis;
// we blend the parallel component with the spring's target velocity to get the new velocity
float blend = deltaTimeStep / _linearTimeScale;
if (blend > 1.0f) {
blend = 1.0f;
}
rigidBody->setLinearVelocity(glmToBullet(currentVelocity + blend * (targetVelocity - parallelVelocity)));
}
}
});
} }
@ -112,25 +105,26 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
if (_pointToOffsetFrom != pointToOffsetFrom if (_pointToOffsetFrom != pointToOffsetFrom
|| _linearTimeScale != linearTimeScale || _linearTimeScale != linearTimeScale
|| _linearDistance != linearDistance) { || _linearDistance != linearDistance) {
lockForWrite();
_pointToOffsetFrom = pointToOffsetFrom; withWriteLock([&] {
_linearTimeScale = linearTimeScale; _pointToOffsetFrom = pointToOffsetFrom;
_linearDistance = linearDistance; _linearTimeScale = linearTimeScale;
_positionalTargetSet = true; _linearDistance = linearDistance;
_active = true; _positionalTargetSet = true;
activateBody(); _active = true;
unlock(); activateBody();
});
} }
return true; return true;
} }
QVariantMap ObjectActionOffset::getArguments() { QVariantMap ObjectActionOffset::getArguments() {
QVariantMap arguments; QVariantMap arguments;
lockForRead(); withReadLock([&] {
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom); arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
arguments["linearTimeScale"] = _linearTimeScale; arguments["linearTimeScale"] = _linearTimeScale;
arguments["linearDistance"] = _linearDistance; arguments["linearDistance"] = _linearDistance;
unlock(); });
return arguments; return arguments;
} }

View file

@ -38,74 +38,72 @@ ObjectActionSpring::~ObjectActionSpring() {
} }
void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
if (!tryLockForRead()) { auto lockResult = withTryReadLock([&]{
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
return;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* rigidBody = motionState->getRigidBody();
if (!rigidBody) {
qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody";
return;
}
const float MAX_TIMESCALE = 600.0f; // 10 min is a long time
if (_linearTimeScale < MAX_TIMESCALE) {
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
float offsetLength = offset.length();
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
if (offsetLength > 0) {
float speed = (offsetLength > FLT_EPSILON) ? glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED) : 0.0f;
targetVelocity = (-speed / offsetLength) * offset;
}
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setLinearVelocity(targetVelocity);
}
if (_angularTimeScale < MAX_TIMESCALE) {
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
btQuaternion bodyRotation = rigidBody->getOrientation();
auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget));
const float ALMOST_ONE = 0.99999f;
if (glm::abs(alignmentDot) < ALMOST_ONE) {
btQuaternion target = glmToBullet(_rotationalTarget);
if (alignmentDot < 0.0f) {
target = -target;
}
// if dQ is the incremental rotation that gets an object from Q0 to Q1 then:
//
// Q1 = dQ * Q0
//
// solving for dQ gives:
//
// dQ = Q1 * Q0^
btQuaternion deltaQ = target * bodyRotation.inverse();
float angle = deltaQ.getAngle();
const float MIN_ANGLE = 1.0e-4f;
if (angle > MIN_ANGLE) {
targetVelocity = (angle / _angularTimeScale) * deltaQ.getAxis();
}
}
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setAngularVelocity(targetVelocity);
}
});
if (!lockResult) {
// don't risk hanging the thread running the physics simulation // don't risk hanging the thread running the physics simulation
qDebug() << "ObjectActionSpring::updateActionWorker lock failed"; qDebug() << "ObjectActionSpring::updateActionWorker lock failed";
return; return;
} }
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (!physicsInfo) {
unlock();
return;
}
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* rigidBody = motionState->getRigidBody();
if (!rigidBody) {
unlock();
qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody";
return;
}
const float MAX_TIMESCALE = 600.0f; // 10 min is a long time
if (_linearTimeScale < MAX_TIMESCALE) {
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
float offsetLength = offset.length();
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
if (offsetLength > 0) {
float speed = (offsetLength > FLT_EPSILON) ? glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED) : 0.0f;
targetVelocity = (-speed / offsetLength) * offset;
}
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setLinearVelocity(targetVelocity);
}
if (_angularTimeScale < MAX_TIMESCALE) {
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
btQuaternion bodyRotation = rigidBody->getOrientation();
auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget));
const float ALMOST_ONE = 0.99999f;
if (glm::abs(alignmentDot) < ALMOST_ONE) {
btQuaternion target = glmToBullet(_rotationalTarget);
if (alignmentDot < 0.0f) {
target = -target;
}
// if dQ is the incremental rotation that gets an object from Q0 to Q1 then:
//
// Q1 = dQ * Q0
//
// solving for dQ gives:
//
// dQ = Q1 * Q0^
btQuaternion deltaQ = target * bodyRotation.inverse();
float angle = deltaQ.getAngle();
const float MIN_ANGLE = 1.0e-4f;
if (angle > MIN_ANGLE) {
targetVelocity = (angle / _angularTimeScale) * deltaQ.getAxis();
}
}
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setAngularVelocity(targetVelocity);
}
unlock();
} }
const float MIN_TIMESCALE = 0.1f; const float MIN_TIMESCALE = 0.1f;
@ -144,29 +142,27 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|| rotationalTarget != _rotationalTarget || rotationalTarget != _rotationalTarget
|| angularTimeScale != _angularTimeScale) { || angularTimeScale != _angularTimeScale) {
// something changed // something changed
lockForWrite(); withWriteLock([&] {
_positionalTarget = positionalTarget; _positionalTarget = positionalTarget;
_linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale)); _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
_rotationalTarget = rotationalTarget; _rotationalTarget = rotationalTarget;
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
_active = true; _active = true;
activateBody(); activateBody();
unlock(); });
} }
return true; return true;
} }
QVariantMap ObjectActionSpring::getArguments() { QVariantMap ObjectActionSpring::getArguments() {
QVariantMap arguments; QVariantMap arguments;
lockForRead(); withReadLock([&] {
arguments["linearTimeScale"] = _linearTimeScale;
arguments["targetPosition"] = glmToQMap(_positionalTarget);
arguments["linearTimeScale"] = _linearTimeScale; arguments["targetRotation"] = glmToQMap(_rotationalTarget);
arguments["targetPosition"] = glmToQMap(_positionalTarget); arguments["angularTimeScale"] = _angularTimeScale;
});
arguments["targetRotation"] = glmToQMap(_rotationalTarget);
arguments["angularTimeScale"] = _angularTimeScale;
unlock();
return arguments; return arguments;
} }

View file

@ -121,8 +121,9 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
// end EntitySimulation overrides // end EntitySimulation overrides
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToDelete() { void PhysicalEntitySimulation::getObjectsToDelete(VectorOfMotionStates& result) {
_tempVector.clear(); result.clear();
QMutexLocker lock(&_mutex);
for (auto stateItr : _pendingRemoves) { for (auto stateItr : _pendingRemoves) {
EntityMotionState* motionState = &(*stateItr); EntityMotionState* motionState = &(*stateItr);
_pendingChanges.remove(motionState); _pendingChanges.remove(motionState);
@ -134,14 +135,14 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToDelete() {
entity->setPhysicsInfo(nullptr); entity->setPhysicsInfo(nullptr);
motionState->clearObjectBackPointer(); motionState->clearObjectBackPointer();
} }
_tempVector.push_back(motionState); result.push_back(motionState);
} }
_pendingRemoves.clear(); _pendingRemoves.clear();
return _tempVector;
} }
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() { void PhysicalEntitySimulation::getObjectsToAdd(VectorOfMotionStates& result) {
_tempVector.clear(); result.clear();
QMutexLocker lock(&_mutex);
SetOfEntities::iterator entityItr = _pendingAdds.begin(); SetOfEntities::iterator entityItr = _pendingAdds.begin();
while (entityItr != _pendingAdds.end()) { while (entityItr != _pendingAdds.end()) {
EntityItemPointer entity = *entityItr; EntityItemPointer entity = *entityItr;
@ -160,7 +161,7 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() {
EntityMotionState* motionState = new EntityMotionState(shape, entity); EntityMotionState* motionState = new EntityMotionState(shape, entity);
entity->setPhysicsInfo(static_cast<void*>(motionState)); entity->setPhysicsInfo(static_cast<void*>(motionState));
_physicalObjects.insert(motionState); _physicalObjects.insert(motionState);
_tempVector.push_back(motionState); result.push_back(motionState);
entityItr = _pendingAdds.erase(entityItr); entityItr = _pendingAdds.erase(entityItr);
} else { } else {
//qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); //qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
@ -170,26 +171,27 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() {
++entityItr; ++entityItr;
} }
} }
return _tempVector;
} }
void PhysicalEntitySimulation::setObjectsToChange(VectorOfMotionStates& objectsToChange) { void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) {
QMutexLocker lock(&_mutex);
for (auto object : objectsToChange) { for (auto object : objectsToChange) {
_pendingChanges.insert(static_cast<EntityMotionState*>(object)); _pendingChanges.insert(static_cast<EntityMotionState*>(object));
} }
} }
VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) {
_tempVector.clear(); result.clear();
QMutexLocker lock(&_mutex);
for (auto stateItr : _pendingChanges) { for (auto stateItr : _pendingChanges) {
EntityMotionState* motionState = &(*stateItr); EntityMotionState* motionState = &(*stateItr);
_tempVector.push_back(motionState); result.push_back(motionState);
} }
_pendingChanges.clear(); _pendingChanges.clear();
return _tempVector;
} }
void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID) { void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) {
QMutexLocker lock(&_mutex);
// walk the motionStates looking for those that correspond to entities // walk the motionStates looking for those that correspond to entities
for (auto stateItr : motionStates) { for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr); ObjectMotionState* state = &(*stateItr);
@ -231,7 +233,7 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio
} }
} }
void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionEvents) { void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& collisionEvents) {
for (auto collision : collisionEvents) { for (auto collision : collisionEvents) {
// NOTE: The collision event is always aligned such that idA is never NULL. // NOTE: The collision event is always aligned such that idA is never NULL.
// however idB may be NULL. // however idB may be NULL.
@ -244,29 +246,30 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE
void PhysicalEntitySimulation::addAction(EntityActionPointer action) { void PhysicalEntitySimulation::addAction(EntityActionPointer action) {
if (_physicsEngine) { if (_physicsEngine) {
lock(); // FIXME put fine grain locking into _physicsEngine
const QUuid& actionID = action->getID(); {
if (_physicsEngine->getActionByID(actionID)) { QMutexLocker lock(&_mutex);
qDebug() << "warning -- PhysicalEntitySimulation::addAction -- adding an action that was already in _physicsEngine"; const QUuid& actionID = action->getID();
if (_physicsEngine->getActionByID(actionID)) {
qDebug() << "warning -- PhysicalEntitySimulation::addAction -- adding an action that was already in _physicsEngine";
}
} }
unlock();
EntitySimulation::addAction(action); EntitySimulation::addAction(action);
} }
} }
void PhysicalEntitySimulation::applyActionChanges() { void PhysicalEntitySimulation::applyActionChanges() {
if (_physicsEngine) { if (_physicsEngine) {
lock(); // FIXME put fine grain locking into _physicsEngine
foreach (QUuid actionToRemove, _actionsToRemove) { QMutexLocker lock(&_mutex);
foreach(QUuid actionToRemove, _actionsToRemove) {
_physicsEngine->removeAction(actionToRemove); _physicsEngine->removeAction(actionToRemove);
} }
_actionsToRemove.clear();
foreach (EntityActionPointer actionToAdd, _actionsToAdd) { foreach (EntityActionPointer actionToAdd, _actionsToAdd) {
if (!_actionsToRemove.contains(actionToAdd->getID())) { if (!_actionsToRemove.contains(actionToAdd->getID())) {
_physicsEngine->addAction(actionToAdd); _physicsEngine->addAction(actionToAdd);
} }
} }
_actionsToAdd.clear();
unlock();
} }
EntitySimulation::applyActionChanges();
} }

View file

@ -32,25 +32,25 @@ public:
void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender); void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender);
virtual void addAction(EntityActionPointer action); virtual void addAction(EntityActionPointer action) override;
virtual void applyActionChanges(); virtual void applyActionChanges() override;
protected: // only called by EntitySimulation protected: // only called by EntitySimulation
// overrides for EntitySimulation // overrides for EntitySimulation
virtual void updateEntitiesInternal(const quint64& now); virtual void updateEntitiesInternal(const quint64& now) override;
virtual void addEntityInternal(EntityItemPointer entity); virtual void addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity); virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity); virtual void changeEntityInternal(EntityItemPointer entity) override;
virtual void clearEntitiesInternal(); virtual void clearEntitiesInternal() override;
public: public:
VectorOfMotionStates& getObjectsToDelete(); void getObjectsToDelete(VectorOfMotionStates& result);
VectorOfMotionStates& getObjectsToAdd(); void getObjectsToAdd(VectorOfMotionStates& result);
void setObjectsToChange(VectorOfMotionStates& objectsToChange); void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
VectorOfMotionStates& getObjectsToChange(); void getObjectsToChange(VectorOfMotionStates& result);
void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); void handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID);
void handleCollisionEvents(CollisionEvents& collisionEvents); void handleCollisionEvents(const CollisionEvents& collisionEvents);
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
@ -64,7 +64,6 @@ private:
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
VectorOfMotionStates _tempVector; // temporary array reference, valid immediately after getObjectsToRemove() (and friends)
PhysicsEnginePointer _physicsEngine = nullptr; PhysicsEnginePointer _physicsEngine = nullptr;
EntityEditPacketSender* _entityPacketSender = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr;

View file

@ -150,7 +150,7 @@ void PhysicsEngine::removeObject(ObjectMotionState* object) {
_dynamicsWorld->removeRigidBody(body); _dynamicsWorld->removeRigidBody(body);
} }
void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) { void PhysicsEngine::deleteObjects(const VectorOfMotionStates& objects) {
for (auto object : objects) { for (auto object : objects) {
removeObject(object); removeObject(object);
@ -165,7 +165,7 @@ void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) {
} }
// Same as above, but takes a Set instead of a Vector. Should only be called during teardown. // Same as above, but takes a Set instead of a Vector. Should only be called during teardown.
void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) { void PhysicsEngine::deleteObjects(const SetOfMotionStates& objects) {
for (auto object : objects) { for (auto object : objects) {
btRigidBody* body = object->getRigidBody(); btRigidBody* body = object->getRigidBody();
removeObject(object); removeObject(object);
@ -179,13 +179,13 @@ void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) {
} }
} }
void PhysicsEngine::addObjects(VectorOfMotionStates& objects) { void PhysicsEngine::addObjects(const VectorOfMotionStates& objects) {
for (auto object : objects) { for (auto object : objects) {
addObject(object); addObject(object);
} }
} }
VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& objects) {
VectorOfMotionStates stillNeedChange; VectorOfMotionStates stillNeedChange;
for (auto object : objects) { for (auto object : objects) {
uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
@ -328,7 +328,7 @@ void PhysicsEngine::updateContactMap() {
} }
} }
CollisionEvents& PhysicsEngine::getCollisionEvents() { const CollisionEvents& PhysicsEngine::getCollisionEvents() {
const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10; const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10;
_collisionEvents.clear(); _collisionEvents.clear();
@ -374,7 +374,7 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() {
return _collisionEvents; return _collisionEvents;
} }
VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() { const VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
BT_PROFILE("copyOutgoingChanges"); BT_PROFILE("copyOutgoingChanges");
_dynamicsWorld->synchronizeMotionStates(); _dynamicsWorld->synchronizeMotionStates();
_hasOutgoingChanges = false; _hasOutgoingChanges = false;

View file

@ -56,10 +56,10 @@ public:
void addObject(ObjectMotionState* motionState); void addObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState); void removeObject(ObjectMotionState* motionState);
void deleteObjects(VectorOfMotionStates& objects); void deleteObjects(const VectorOfMotionStates& objects);
void deleteObjects(SetOfMotionStates& objects); // only called during teardown void deleteObjects(const SetOfMotionStates& objects); // only called during teardown
void addObjects(VectorOfMotionStates& objects); void addObjects(const VectorOfMotionStates& objects);
VectorOfMotionStates changeObjects(VectorOfMotionStates& objects); VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects);
void reinsertObject(ObjectMotionState* object); void reinsertObject(ObjectMotionState* object);
void stepSimulation(); void stepSimulation();
@ -68,10 +68,10 @@ public:
bool hasOutgoingChanges() const { return _hasOutgoingChanges; } bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
/// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop. /// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop.
VectorOfMotionStates& getOutgoingChanges(); const VectorOfMotionStates& getOutgoingChanges();
/// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop. /// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop.
CollisionEvents& getCollisionEvents(); const CollisionEvents& getCollisionEvents();
/// \brief prints timings for last frame if stats have been requested. /// \brief prints timings for last frame if stats have been requested.
void dumpStatsIfNecessary(); void dumpStatsIfNecessary();

View file

@ -0,0 +1,10 @@
//
// Created by Bradley Austin Davis on 2015/09/10
// Copyright 2013-2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ReadWriteLockable.h"

View file

@ -0,0 +1,62 @@
//
// Created by Bradley Austin Davis on 2015/09/10
// Copyright 2013-2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_ReadWriteLockable_h
#define hifi_ReadWriteLockable_h
#include <QtCore/QReadWriteLock>
class ReadWriteLockable {
public:
template <typename F>
bool withWriteLock(F f, bool require = true) {
if (!require) {
bool result = _lock.tryLockForWrite();
if (result) {
f();
_lock.unlock();
}
return result;
}
QWriteLocker locker(&_lock);
f();
return true;
}
template <typename F>
bool withTryWriteLock(F f) {
return withWriteLock(f, false);
}
template <typename F>
bool withReadLock(F f, bool require = true) const {
if (!require) {
bool result = _lock.tryLockForRead();
if (result) {
f();
_lock.unlock();
}
return result;
}
QReadLocker locker(&_lock);
f();
return true;
}
template <typename F>
bool withTryReadLock(F f) const {
return withReadLock(f, false);
}
private:
mutable QReadWriteLock _lock{ QReadWriteLock::Recursive };
};
#endif