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);
}
quint64 startLock = usecTimestampNow();
_myServer->getOctree()->lockForWrite();
quint64 startProcess = usecTimestampNow();
int editDataBytesRead =
quint64 startProcess, startLock = usecTimestampNow();
int editDataBytesRead;
_myServer->getOctree()->withWriteLock([&] {
startProcess = usecTimestampNow();
editDataBytesRead =
_myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode);
});
quint64 endProcess = usecTimestampNow();
if (debugProcessPacket) {
qDebug() << "OctreeInboundPacketProcessor::processPacket() after processEditPacketData()..."
<< "editDataBytesRead=" << editDataBytesRead;
}
_myServer->getOctree()->unlock();
quint64 endProcess = usecTimestampNow();
editsInPacket++;
quint64 thisProcessTime = endProcess - startProcess;
quint64 thisLockWaitTime = startProcess - startLock;

View file

@ -416,7 +416,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
if (!nodeData->elementBag.isEmpty()) {
quint64 lockWaitStart = usecTimestampNow();
_myServer->getOctree()->lockForRead();
_myServer->getOctree()->withReadLock([&]{
quint64 lockWaitEnd = usecTimestampNow();
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
quint64 encodeStart = usecTimestampNow();
@ -492,7 +492,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
}
nodeData->stats.encodeStopped();
_myServer->getOctree()->unlock();
});
} else {
// If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 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() {
EntityTreePointer tree = _entities.getTree();
tree->lockForWrite();
_entities.getTree()->setSimulation(NULL);
tree->unlock();
tree->setSimulation(NULL);
_octreeProcessor.terminate();
_entityEditSender.terminate();
@ -888,7 +886,9 @@ Application::~Application() {
// remove avatars from physics engine
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<AvatarManager>();
@ -2887,46 +2887,40 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("physics");
_myAvatar->relayDriveKeysToCharacterController();
_entitySimulation.lock();
_physicsEngine->deleteObjects(_entitySimulation.getObjectsToDelete());
_entitySimulation.unlock();
static VectorOfMotionStates motionStates;
_entitySimulation.getObjectsToDelete(motionStates);
_physicsEngine->deleteObjects(motionStates);
_entities.getTree()->lockForWrite();
_entitySimulation.lock();
_physicsEngine->addObjects(_entitySimulation.getObjectsToAdd());
_entitySimulation.unlock();
_entities.getTree()->unlock();
_entities.getTree()->withWriteLock([&] {
_entitySimulation.getObjectsToAdd(motionStates);
_physicsEngine->addObjects(motionStates);
_entities.getTree()->lockForWrite();
_entitySimulation.lock();
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(_entitySimulation.getObjectsToChange());
});
_entities.getTree()->withWriteLock([&] {
_entitySimulation.getObjectsToChange(motionStates);
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
_entitySimulation.setObjectsToChange(stillNeedChange);
_entitySimulation.unlock();
_entities.getTree()->unlock();
});
_entitySimulation.lock();
_entitySimulation.applyActionChanges();
_entitySimulation.unlock();
AvatarManager* avatarManager = DependencyManager::get<AvatarManager>().data();
_physicsEngine->deleteObjects(avatarManager->getObjectsToDelete());
_physicsEngine->addObjects(avatarManager->getObjectsToAdd());
_physicsEngine->changeObjects(avatarManager->getObjectsToChange());
avatarManager->getObjectsToDelete(motionStates);
_physicsEngine->deleteObjects(motionStates);
avatarManager->getObjectsToAdd(motionStates);
_physicsEngine->addObjects(motionStates);
avatarManager->getObjectsToChange(motionStates);
_physicsEngine->changeObjects(motionStates);
_entities.getTree()->lockForWrite();
_entities.getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
_entities.getTree()->unlock();
});
if (_physicsEngine->hasOutgoingChanges()) {
_entities.getTree()->lockForWrite();
_entitySimulation.lock();
_entities.getTree()->withWriteLock([&] {
_entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), _physicsEngine->getSessionID());
_entitySimulation.unlock();
_entities.getTree()->unlock();
_entities.getTree()->lockForWrite();
avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges());
_entities.getTree()->unlock();
});
auto collisionEvents = _physicsEngine->getCollisionEvents();
avatarManager->handleCollisionEvents(collisionEvents);
@ -3045,27 +3039,21 @@ int Application::sendNackPackets() {
return;
}
_octreeSceneStatsLock.lockForRead();
QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers;
_octreeServerSceneStats.withReadLock([&] {
// 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();
missingSequenceNumbers = sequenceNumberStats.getMissingSet();
});
// construct nack packet(s) for this node
auto it = missingSequenceNumbers.constBegin();
while (it != missingSequenceNumbers.constEnd()) {
OCTREE_PACKET_SEQUENCE missingNumber = *it;
foreach(const OCTREE_PACKET_SEQUENCE& missingNumber, missingSequenceNumbers) {
nackPacketList->writePrimitive(missingNumber);
++it;
}
if (nackPacketList->getNumPackets()) {
@ -3798,13 +3786,13 @@ void Application::clearDomainOctreeDetails() {
// 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...
_entityServerJurisdictions.lockForWrite();
_entityServerJurisdictions.withWriteLock([&] {
_entityServerJurisdictions.clear();
_entityServerJurisdictions.unlock();
});
_octreeSceneStatsLock.lockForWrite();
_octreeServerSceneStats.withWriteLock([&] {
_octreeServerSceneStats.clear();
_octreeSceneStatsLock.unlock();
});
// reset the model renderer
_entities.clear();
@ -3874,29 +3862,31 @@ void Application::nodeKilled(SharedNodePointer node) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
_entityServerJurisdictions.lockForRead();
if (_entityServerJurisdictions.find(nodeUUID) != _entityServerJurisdictions.end()) {
_entityServerJurisdictions.withReadLock([&] {
if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) {
return;
}
unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
_entityServerJurisdictions.unlock();
qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]",
(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();
_entityServerJurisdictions.withWriteLock([&] {
_entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID));
}
_entityServerJurisdictions.unlock();
});
// also clean up scene stats for that server
_octreeSceneStatsLock.lockForWrite();
_octreeServerSceneStats.withWriteLock([&] {
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
_octreeServerSceneStats.erase(nodeUUID);
}
_octreeSceneStatsLock.unlock();
});
} else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
@ -3914,12 +3904,12 @@ void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer
const QUuid& nodeUUID = sendingNode->getUUID();
// 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()) {
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec());
}
_octreeSceneStatsLock.unlock();
});
}
}
@ -3933,13 +3923,10 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
const QUuid& nodeUUID = sendingNode->getUUID();
// 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];
statsMessageLength = octreeStats.unpackFromPacket(packet);
_octreeSceneStatsLock.unlock();
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
QString serverType;
@ -3948,9 +3935,10 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
serverType = "Entity";
}
jurisdiction->lockForRead();
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
jurisdiction->unlock();
jurisdiction->withReadLock([&] {
if (jurisdiction->find(nodeUUID) != jurisdiction->end()) {
return;
}
VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
@ -3958,18 +3946,19 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType),
(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
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
jurisdiction->lockForWrite();
jurisdiction->withWriteLock([&] {
(*jurisdiction)[nodeUUID] = jurisdictionMap;
jurisdiction->unlock();
});
});
return statsMessageLength;
}

View file

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

View file

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

View file

@ -242,37 +242,33 @@ QVector<AvatarManager::LocalLight> AvatarManager::getLocalLights() const {
return _localLights;
}
VectorOfMotionStates& AvatarManager::getObjectsToDelete() {
_tempMotionStates.clear();
_tempMotionStates.swap(_motionStatesToDelete);
return _tempMotionStates;
void AvatarManager::getObjectsToDelete(VectorOfMotionStates& result) {
result.clear();
result.swap(_motionStatesToDelete);
}
VectorOfMotionStates& AvatarManager::getObjectsToAdd() {
_tempMotionStates.clear();
void AvatarManager::getObjectsToAdd(VectorOfMotionStates& result) {
result.clear();
for (auto motionState : _motionStatesToAdd) {
_tempMotionStates.push_back(motionState);
result.push_back(motionState);
}
_motionStatesToAdd.clear();
return _tempMotionStates;
}
VectorOfMotionStates& AvatarManager::getObjectsToChange() {
_tempMotionStates.clear();
void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) {
result.clear();
for (auto state : _avatarMotionStates) {
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.
}
void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) {
void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents) {
for (Collision collision : collisionEvents) {
// 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

View file

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

View file

@ -196,8 +196,8 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) {
unsigned long totalInternal = 0;
unsigned long totalLeaves = 0;
Application::getInstance()->lockOctreeSceneStats();
NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats();
sceneStats->withReadLock([&] {
for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) {
//const QUuid& uuid = i->first;
OctreeSceneStats& stats = i->second;
@ -219,7 +219,7 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) {
sendingMode << "S";
}
}
Application::getInstance()->unlockOctreeSceneStats();
});
sendingMode << " - " << serverCount << " servers";
if (movingServerCount > 0) {
sendingMode << " <SCENE NOT STABLE>";
@ -398,11 +398,11 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction
serverJurisdictions.lockForRead();
serverJurisdictions.withReadLock([&] {
if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) {
serverDetails << " unknown jurisdiction ";
serverJurisdictions.unlock();
} else {
return;
}
const JurisdictionMap& map = serverJurisdictions[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
@ -423,13 +423,12 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
} else {
serverDetails << " jurisdiction has no rootCode";
} // root code
serverJurisdictions.unlock();
} // jurisdiction
});
// now lookup stats details for this server...
if (_extraServerDetails[serverCount-1] != LESS) {
Application::getInstance()->lockOctreeSceneStats();
NodeToOctreeSceneStats* sceneStats = Application::getInstance()->getOcteeSceneStats();
sceneStats->withReadLock([&] {
if (sceneStats->find(nodeUUID) != sceneStats->end()) {
OctreeSceneStats& stats = sceneStats->at(nodeUUID);
@ -530,7 +529,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
} break;
}
}
Application::getInstance()->unlockOctreeSceneStats();
});
} else {
linkDetails << " " << " [<a href='more-" << serverCount << "'>more...</a>]";
linkDetails << " " << " [<a href='most-" << serverCount << "'>most...</a>]";

View file

@ -342,7 +342,8 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
QVector<EntityItemID> entitiesContainingAvatar;
// 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
_tree->withReadLock([&] {
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
// create a list of entities that actually contain the avatar's position
@ -351,7 +352,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
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
// EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts
@ -502,9 +503,7 @@ void EntityTreeRenderer::render(RenderArgs* renderArgs) {
if (_tree && !_shuttingDown) {
renderArgs->_renderer = this;
_tree->lockForRead();
_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();
@ -516,8 +515,7 @@ void EntityTreeRenderer::render(RenderArgs* renderArgs) {
_tree->recurseTreeWithOperation(renderOperation, renderArgs);
applyZonePropertiesToScene(_bestZone);
_tree->unlock();
});
}
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);
EntityItems& entityItems = entityTreeElement->getEntities();
uint16_t numberOfEntities = entityItems.size();
bool isShadowMode = args->_renderMode == RenderArgs::SHADOW_RENDER_MODE;
if (!isShadowMode && _displayModelElementProxy && numberOfEntities > 0) {
if (!isShadowMode && _displayModelElementProxy && entityTreeElement->size() > 0) {
renderElementProxy(entityTreeElement, args);
}
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItemPointer entityItem = entityItems[i];
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
if (entityItem->isVisible()) {
// NOTE: Zone Entities are a special case we handle here...
if (entityItem->getType() == EntityTypes::Zone) {
if (entityItem->contains(_viewState->getAvatarPosition())) {
@ -685,7 +675,8 @@ void EntityTreeRenderer::renderElement(OctreeElementPointer element, RenderArgs*
}
}
}
}
});
}
float EntityTreeRenderer::getSizeScale() const {

View file

@ -1496,15 +1496,16 @@ void EntityItem::clearSimulationOwnership() {
bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) {
lockForWrite();
bool result;
withWriteLock([&] {
checkWaitingToRemove(simulation);
bool result = addActionInternal(simulation, action);
result = addActionInternal(simulation, action);
if (!result) {
removeActionInternal(action->getID());
}
});
unlock();
return result;
}
@ -1531,33 +1532,33 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi
}
bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) {
lockForWrite();
bool success = false;
withWriteLock([&] {
checkWaitingToRemove(simulation);
if (!_objectActions.contains(actionID)) {
unlock();
return false;
return;
}
EntityActionPointer action = _objectActions[actionID];
bool success = action->updateArguments(arguments);
success = action->updateArguments(arguments);
if (success) {
_allActionsDataCache = serializeActions(success);
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
} else {
qDebug() << "EntityItem::updateAction failed";
}
unlock();
});
return success;
}
bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) {
lockForWrite();
bool success = false;
withWriteLock([&] {
checkWaitingToRemove(simulation);
bool success = removeActionInternal(actionID);
unlock();
success = removeActionInternal(actionID);
});
return success;
}
@ -1586,7 +1587,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s
}
bool EntityItem::clearActions(EntitySimulation* simulation) {
lockForWrite();
withWriteLock([&] {
QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
const QUuid id = i.key();
@ -1599,16 +1600,16 @@ bool EntityItem::clearActions(EntitySimulation* simulation) {
_actionsToRemove.clear();
_allActionsDataCache.clear();
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
unlock();
});
return true;
}
void EntityItem::deserializeActions() {
assertUnlocked();
lockForWrite();
withWriteLock([&] {
deserializeActionsInternal();
unlock();
});
}
@ -1682,9 +1683,9 @@ void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) {
void EntityItem::setActionData(QByteArray actionData) {
assertUnlocked();
lockForWrite();
withWriteLock([&] {
setActionDataInternal(actionData);
unlock();
});
}
void EntityItem::setActionDataInternal(QByteArray actionData) {
@ -1738,117 +1739,23 @@ const QByteArray EntityItem::getActionDataInternal() const {
}
const QByteArray EntityItem::getActionData() const {
QByteArray result;
assertUnlocked();
lockForRead();
auto result = getActionDataInternal();
unlock();
withReadLock([&] {
result = getActionDataInternal();
});
return result;
}
QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
QVariantMap result;
lockForRead();
withReadLock([&] {
if (_objectActions.contains(actionID)) {
EntityActionPointer action = _objectActions[actionID];
result = action->getArguments();
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
}
unlock();
});
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 debugTreeVector(V) V << "[" << V << " in meters ]"
#if DEBUG
#define assertLocked() assert(isLocked())
#else
//#if DEBUG
// #define assertLocked() assert(isLocked())
//#else
// #define assertLocked()
//#endif
//
//#if DEBUG
// #define assertWriteLocked() assert(isWriteLocked())
//#else
// #define assertWriteLocked()
//#endif
//
//#if DEBUG
// #define assertUnlocked() assert(isUnlocked())
//#else
// #define assertUnlocked()
//#endif
#define assertLocked()
#endif
#if DEBUG
#define assertWriteLocked() assert(isWriteLocked())
#else
#define assertWriteLocked()
#endif
#if DEBUG
#define assertUnlocked() assert(isUnlocked())
#else
#define assertUnlocked()
#endif
#define assertWriteLocked()
/// 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
/// 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.
// 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
@ -515,16 +518,6 @@ protected:
void checkWaitingToRemove(EntitySimulation* simulation = nullptr);
mutable QSet<QUuid> _actionsToRemove;
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

View file

@ -72,7 +72,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
// If we have a local entity tree set, then also update it.
bool success = true;
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
if (entity) {
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
@ -88,7 +88,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
}
_entityTree->unlock();
});
}
// queue the packet
@ -102,10 +102,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
EntityItemProperties results;
if (_entityTree) {
_entityTree->lockForRead();
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity));
if (entity) {
results = entity->getProperties();
@ -123,7 +121,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
}
}
_entityTree->unlock();
});
}
return results;
@ -132,13 +130,22 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) {
EntityItemID entityID(id);
// If we have a local entity tree set, then also update it.
if (_entityTree) {
_entityTree->lockForWrite();
bool updatedEntity = _entityTree->updateEntity(entityID, properties);
_entityTree->unlock();
if (!_entityTree) {
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
if (updatedEntity) {
_entityTree->lockForRead();
bool updatedEntity = false;
_entityTree->withWriteLock([&] {
updatedEntity = _entityTree->updateEntity(entityID, properties);
});
if (!updatedEntity) {
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
@ -175,13 +182,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper
}
entity->setLastBroadcast(usecTimestampNow());
}
_entityTree->unlock();
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
return QUuid();
}
});
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
@ -192,8 +193,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
// If we have a local entity tree set, then also update it.
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
if (entity->getLocked()) {
@ -202,8 +202,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
_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
@ -215,9 +214,10 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
EntityItemID result;
if (_entityTree) {
_entityTree->lockForRead();
EntityItemPointer closestEntity = _entityTree->findClosestEntity(center, radius);
_entityTree->unlock();
EntityItemPointer closestEntity;
_entityTree->withReadLock([&] {
closestEntity = _entityTree->findClosestEntity(center, radius);
});
if (closestEntity) {
result = closestEntity->getEntityItemID();
}
@ -228,19 +228,19 @@ QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float
void EntityScriptingInterface::dumpTree() const {
if (_entityTree) {
_entityTree->lockForRead();
_entityTree->withReadLock([&] {
_entityTree->dumpTree();
_entityTree->unlock();
});
}
}
QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const {
QVector<QUuid> result;
if (_entityTree) {
_entityTree->lockForRead();
QVector<EntityItemPointer> entities;
_entityTree->withReadLock([&] {
_entityTree->findEntities(center, radius, entities);
_entityTree->unlock();
});
foreach (EntityItemPointer entity, entities) {
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> result;
if (_entityTree) {
_entityTree->lockForRead();
AABox box(corner, dimensions);
QVector<EntityItemPointer> entities;
_entityTree->withReadLock([&] {
AABox box(corner, dimensions);
_entityTree->findEntities(box, entities);
_entityTree->unlock();
});
foreach (EntityItemPointer entity, entities) {
result << entity->getEntityItemID();
@ -432,9 +432,10 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
_entityTree->lockForWrite();
bool result = actor(*polyVoxEntity);
_entityTree->unlock();
bool result;
_entityTree->withWriteLock([&] {
result = actor(*polyVoxEntity);
});
return result;
}
@ -457,15 +458,17 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
auto now = usecTimestampNow();
auto lineEntity = std::static_pointer_cast<LineEntityItem>(entity);
_entityTree->lockForWrite();
bool success = actor(*lineEntity);
bool success;
_entityTree->withWriteLock([&] {
success = actor(*lineEntity);
entity->setLastEdited(now);
entity->setLastBroadcast(now);
_entityTree->unlock();
});
_entityTree->lockForRead();
EntityItemProperties properties = entity->getProperties();
_entityTree->unlock();
EntityItemProperties properties;
_entityTree->withReadLock([&] {
properties = entity->getProperties();
});
properties.setLinePointsDirty();
properties.setLastEdited(now);
@ -543,36 +546,39 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
return false;
}
_entityTree->lockForWrite();
EntityItemPointer entity;
bool success;
_entityTree->withWriteLock([&] {
EntitySimulation* simulation = _entityTree->getSimulation();
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qDebug() << "actionWorker -- unknown entity" << entityID;
_entityTree->unlock();
return false;
return;
}
if (!simulation) {
qDebug() << "actionWorker -- no simulation" << entityID;
_entityTree->unlock();
return false;
return;
}
bool success = actor(simulation, entity);
success = actor(simulation, entity);
if (success) {
_entityTree->entityChanged(entity);
}
_entityTree->unlock();
});
// transmit the change
_entityTree->lockForRead();
EntityItemProperties properties = entity->getProperties();
_entityTree->unlock();
if (success) {
EntityItemProperties properties;
_entityTree->withReadLock([&] {
properties = entity->getProperties();
});
properties.setActionDataDirty();
auto now = usecTimestampNow();
properties.setLastEdited(now);
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
}
return success;
}

View file

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

View file

@ -47,22 +47,18 @@ public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
void lock() { _mutex.lock(); }
void unlock() { _mutex.unlock(); }
/// \param tree pointer to EntityTree which is stored internally
void setEntityTree(EntityTreePointer tree);
void updateEntities();
friend class EntityTree;
// friend class EntityTree;
virtual void addAction(EntityActionPointer action);
virtual void removeAction(const QUuid actionID);
virtual void removeActions(QList<QUuid> actionIDsToRemove);
virtual void applyActionChanges();
protected: // these only called by the EntityTree?
/// \param entity pointer to EntityItem to be added
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
void addEntity(EntityItemPointer entity);
@ -79,6 +75,7 @@ protected: // these only called by the EntityTree?
void clearEntities();
void moveSimpleKinematics(const quint64& now);
protected: // these only called by the EntityTree?
public:
@ -90,7 +87,6 @@ signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places.
virtual void updateEntitiesInternal(const quint64& now) = 0;
@ -103,7 +99,15 @@ protected:
void callUpdateOnEntitiesThatNeedIt(const quint64& now);
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
EntityTreePointer _entityTree;
@ -113,17 +117,11 @@ protected:
SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry
quint64 _nextExpiry;
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 _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
private:
void moveSimpleKinematics();
protected:
QList<EntityActionPointer> _actionsToAdd;
QList<QUuid> _actionsToRemove;
};
#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...
if (_simulation) {
_simulation->lock();
_simulation->clearEntities();
_simulation->unlock();
}
foreach (EntityTreeElementPointer element, _entityToElementMap) {
element->cleanupEntities();
@ -88,9 +86,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
assert(entity);
// check to see if we need to simulate this entity..
if (_simulation) {
_simulation->lock();
_simulation->addEntity(entity);
_simulation->unlock();
}
_isDirty = true;
maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL());
@ -217,9 +213,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
if (newFlags) {
if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->lock();
_simulation->changeEntity(entity);
_simulation->unlock();
}
} else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
@ -301,6 +295,7 @@ void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisio
}
void EntityTree::setSimulation(EntitySimulation* simulation) {
this->withWriteLock([&] {
if (simulation) {
// assert that the simulation's backpointer has already been properly connected
assert(simulation->getEntityTree().get() == this);
@ -308,11 +303,10 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
if (_simulation && _simulation != simulation) {
// It's important to clearEntities() on the simulation since taht will update each
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->lock();
_simulation->clearEntities();
_simulation->unlock();
}
_simulation = simulation;
});
}
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) {
const RemovedEntities& entities = theOperator.getEntities();
if (_simulation) {
_simulation->lock();
}
foreach(const EntityToDeleteDetails& details, entities) {
EntityItemPointer theEntity = details.entity;
@ -410,9 +401,6 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
_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) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };
lockForRead();
withReadLock([&] {
// NOTE: This should use recursion, since this is a spatial operation
recurseTreeWithOperation(findNearPointOperation, &args);
unlock();
});
return args.closestEntity;
}
@ -718,20 +706,16 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
void EntityTree::entityChanged(EntityItemPointer entity) {
if (_simulation) {
_simulation->lock();
_simulation->changeEntity(entity);
_simulation->unlock();
}
}
void EntityTree::update() {
if (_simulation) {
lockForWrite();
_simulation->lock();
withWriteLock([&] {
_simulation->updateEntities();
VectorOfEntities pendingDeletes;
_simulation->getEntitiesToDelete(pendingDeletes);
_simulation->unlock();
if (pendingDeletes.size() > 0) {
// translate into list of ID's
@ -744,7 +728,7 @@ void EntityTree::update() {
// delete these things the roundabout way
deleteEntities(idsToDelete, true);
}
unlock();
});
}
}
@ -864,8 +848,7 @@ void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) {
// TODO: consider consolidating processEraseMessageDetails() and processEraseMessage()
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));
uint16_t numberOfIDs = 0; // placeholder for now
@ -893,7 +876,7 @@ int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& s
}
deleteEntities(entityItemIDsToDelete, true, true);
}
unlock();
});
return packet.pos();
}
@ -1038,12 +1021,10 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) {
SendEntitiesOperationArgs* args = static_cast<SendEntitiesOperationArgs*>(extraData);
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
const EntityItems& entities = entityTreeElement->getEntities();
for (int i = 0; i < entities.size(); i++) {
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
EntityItemID newID(QUuid::createUuid());
args->newEntityIDs->append(newID);
EntityItemProperties properties = entities[i]->getProperties();
EntityItemProperties properties = entityItem->getProperties();
properties.setPosition(properties.getPosition() + args->root);
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)
if (args->localTree) {
args->localTree->lockForWrite();
args->localTree->withWriteLock([&] {
args->localTree->addEntity(newID, properties);
args->localTree->unlock();
});
}
}
});
return true;
}

View file

@ -24,8 +24,6 @@ EntityTreeElement::EntityTreeElement(unsigned char* octalCode) : OctreeElement()
EntityTreeElement::~EntityTreeElement() {
_octreeMemoryUsage -= sizeof(EntityTreeElement);
delete _entityItems;
_entityItems = NULL;
}
// 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) {
OctreeElement::init(octalCode);
_entityItems = new EntityItems;
_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
if (!extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0);
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElementPointer child = getChildAtIndex(i);
if (!child) {
@ -104,10 +101,9 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
}
}
}
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItemPointer entity = (*_entityItems)[i];
forEachEntity([&](EntityItemPointer entity) {
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
}
});
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
@ -269,7 +265,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
} else {
// if there wasn't one already, then create one
entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData();
entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0);
entityTreeElementExtraEncodeData->elementCompleted = !hasContent();
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
EntityTreeElementPointer child = getChildAtIndex(i);
@ -284,10 +280,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
}
}
}
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItemPointer entity = (*_entityItems)[i];
forEachEntity([&](EntityItemPointer entity) {
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
}
});
}
//assert(extraEncodeData);
@ -299,14 +294,16 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// write our entities out... first determine which of the entities are in view based on our params
uint16_t numberOfEntities = 0;
uint16_t actualNumberOfEntities = 0;
int numberOfEntitiesOffset = 0;
withReadLock([&] {
QVector<uint16_t> indexesOfEntitiesToInclude;
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItemPointer entity = (*_entityItems)[i];
for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = _entityItems[i];
bool includeThisEntity = true;
if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) {
@ -322,7 +319,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// we want to use the maximum possible box for this, so that we don't have to worry about the nuance of
// simulation changing what's visible. consider the case where the entity contains an angular velocity
// the entity may not be in view and then in view a frame later, let the client side handle 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.
AACube entityCube = entity->getMaximumAACube();
if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
@ -337,15 +334,15 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
}
}
int numberOfEntitiesOffset = packetData->getUncompressedByteOffset();
numberOfEntitiesOffset = packetData->getUncompressedByteOffset();
bool successAppendEntityCount = packetData->appendValue(numberOfEntities);
if (successAppendEntityCount) {
foreach(uint16_t i, indexesOfEntitiesToInclude) {
EntityItemPointer entity = (*_entityItems)[i];
EntityItemPointer entity = _entityItems[i];
LevelDetails entityLevel = packetData->startLevel();
OctreeElement::AppendState appendEntityState =
entity->appendEntityData(packetData, params, entityTreeElementExtraEncodeData);
OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData,
params, entityTreeElementExtraEncodeData);
// If none of this entity data was able to be appended, then discard it
// and don't include it in our entity count
@ -374,6 +371,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state
appendElementState = OctreeElement::NONE;
}
});
// If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData
// then we need to do some additional processing, namely make sure our extraEncodeData is up to date for
@ -500,22 +498,16 @@ 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...
int entityNumber = 0;
EntityItems::iterator entityItr = _entityItems->begin();
EntityItems::const_iterator entityEnd = _entityItems->end();
bool somethingIntersected = false;
//float bestEntityDistance = distance;
while(entityItr != entityEnd) {
EntityItemPointer entity = (*entityItr);
forEachEntity([&](EntityItemPointer entity) {
AABox entityBox = entity->getAABox();
float localDistance;
BoxFace localFace;
// 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
glm::mat4 rotation = glm::mat4_cast(entity->getRotation());
@ -559,73 +551,68 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
}
}
}
}
++entityItr;
entityNumber++;
}
});
return somethingIntersected;
}
// TODO: change this to use better bounding shape for entity than sphere
bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const {
EntityItems::iterator entityItr = _entityItems->begin();
EntityItems::const_iterator entityEnd = _entityItems->end();
while(entityItr != entityEnd) {
EntityItemPointer entity = (*entityItr);
bool result = false;
withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
glm::vec3 entityCenter = entity->getPosition();
float entityRadius = entity->getRadius();
// don't penetrate yourself
if (entityCenter == center && entityRadius == radius) {
return false;
return;
}
if (findSphereSpherePenetration(center, radius, entityCenter, entityRadius, penetration)) {
// return true on first valid entity penetration
*penetratedObject = (void*)(entity.get());
return true;
result = true;
return;
}
++entityItr;
}
return false;
});
return result;
}
EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const {
EntityItemPointer closestEntity = NULL;
float closestEntityDistance = FLT_MAX;
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
float distanceToEntity = glm::distance(position, (*_entityItems)[i]->getPosition());
withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
float distanceToEntity = glm::distance2(position, entity->getPosition());
if (distanceToEntity < closestEntityDistance) {
closestEntity = (*_entityItems)[i];
closestEntity = entity;
}
}
});
return closestEntity;
}
// 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 {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItemPointer entity = (*_entityItems)[i];
float distance = glm::length(entity->getPosition() - searchPosition);
if (distance < searchRadius + entity->getRadius()) {
float compareRadius = searchRadius * searchRadius;
forEachEntity([&](EntityItemPointer entity) {
// For iteration like this, avoid the use of square roots by comparing distances squared
float distanceSquared = glm::length2(entity->getPosition() - searchPosition);
float otherRadius = entity->getRadius();
if (distanceSquared < (compareRadius + (otherRadius * otherRadius))) {
foundEntities.push_back(entity);
}
}
});
}
// TODO: change this to use better bounding shape for entity than sphere
void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer>& foundEntities) {
EntityItems::iterator entityItr = _entityItems->begin();
EntityItems::iterator entityEnd = _entityItems->end();
AACube entityCube;
while(entityItr != entityEnd) {
EntityItemPointer entity = (*entityItr);
forEachEntity([&](EntityItemPointer entity) {
float radius = entity->getRadius();
// 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
@ -634,64 +621,57 @@ void EntityTreeElement::getEntities(const AACube& box, QVector<EntityItemPointer
if (entityCube.touches(box)) {
foundEntities.push_back(entity);
}
++entityItr;
}
});
}
EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const {
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;
}
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];
withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
if (entity->getEntityItemID() == id) {
foundEntity = entity;
break;
}
}
});
return foundEntity;
}
void EntityTreeElement::cleanupEntities() {
uint16_t numberOfEntities = _entityItems->size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItemPointer entity = (*_entityItems)[i];
entity->_element = NULL;
withWriteLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
// NOTE: We explicitly don't delete the EntityItem here because since we only
// access it by smart pointers, when we remove it from the _entityItems
// we know that it will be deleted.
//delete entity;
entity->_element = NULL;
}
_entityItems->clear();
_entityItems.clear();
});
}
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
bool foundEntity = false;
uint16_t numberOfEntities = _entityItems->size();
withWriteLock([&] {
uint16_t numberOfEntities = _entityItems.size();
for (uint16_t i = 0; i < numberOfEntities; i++) {
if ((*_entityItems)[i]->getEntityItemID() == id) {
EntityItemPointer& entity = _entityItems[i];
if (entity->getEntityItemID() == id) {
foundEntity = true;
(*_entityItems)[i]->_element = NULL;
_entityItems->removeAt(i);
entity->_element = NULL;
_entityItems.removeAt(i);
break;
}
}
});
return foundEntity;
}
bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) {
int numEntries = _entityItems->removeAll(entity);
int numEntries = 0;
withWriteLock([&] {
numEntries = _entityItems.removeAll(entity);
});
if (numEntries > 0) {
assert(entity->_element.get() == this);
entity->_element = NULL;
@ -813,7 +793,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
assert(entity);
assert(entity->_element == nullptr);
_entityItems->push_back(entity);
withWriteLock([&] {
_entityItems.push_back(entity);
});
entity->_element = getThisPointer();
}
@ -846,30 +828,39 @@ bool EntityTreeElement::pruneChildren() {
}
void EntityTreeElement::expandExtentsToContents(Extents& extents) {
if (_entityItems->size()) {
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItemPointer entity = (*_entityItems)[i];
withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
extents.add(entity->getAABox());
}
}
});
}
uint16_t EntityTreeElement::size() const {
uint16_t result = 0;
withReadLock([&] {
result = _entityItems.size();
});
return result;
}
void EntityTreeElement::debugDump() {
qCDebug(entities) << "EntityTreeElement...";
qCDebug(entities) << " cube:" << _cube;
qCDebug(entities) << " has child elements:" << getChildCount();
if (_entityItems->size()) {
qCDebug(entities) << " has entities:" << _entityItems->size();
withReadLock([&] {
if (_entityItems.size()) {
qCDebug(entities) << " has entities:" << _entityItems.size();
qCDebug(entities) << "--------------------------------------------------";
for (uint16_t i = 0; i < _entityItems->size(); i++) {
EntityItemPointer entity = (*_entityItems)[i];
for (uint16_t i = 0; i < _entityItems.size(); i++) {
EntityItemPointer entity = _entityItems[i];
entity->debugDump();
}
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...
EntityTreeElement(unsigned char* octalCode = NULL);
@ -149,10 +149,18 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
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; }
EntityTreePointer getTree() const { return _myTree; }
@ -177,8 +185,6 @@ public:
EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const;
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
bool removeEntityWithEntityItemID(const EntityItemID& id);
bool removeEntityItem(EntityItemPointer entity);
@ -217,7 +223,7 @@ public:
protected:
virtual void init(unsigned char * octalCode);
EntityTreePointer _myTree;
EntityItems* _entityItems;
EntityItems _entityItems;
};
#endif // hifi_EntityTreeElement_h

View file

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

View file

@ -39,12 +39,13 @@ bool RecurseOctreeToMapOperator::preRecursion(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"]);
foreach (EntityItemPointer entityItem, entities) {
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
EntityItemProperties properties = entityItem->getProperties();
QScriptValue qScriptValues;
if (_skipDefaultValues) {
@ -53,7 +54,8 @@ bool RecurseOctreeToMapOperator::postRecursion(OctreeElementPointer element) {
qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties);
}
entitiesQList << qScriptValues.toVariant();
}
});
_map["Entities"] = entitiesQList;
if (element == _top) {
_withinTop = false;

View file

@ -18,7 +18,8 @@
#include <QtCore/QString>
#include <QtCore/QUuid>
#include <QReadWriteLock>
#include <shared/ReadWriteLockable.h>
#include <NLPacket.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
/// 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;

View file

@ -60,7 +60,6 @@ Octree::Octree(bool shouldReaverage) :
_isDirty(true),
_shouldReaverage(shouldReaverage),
_stopImport(false),
_lock(QReadWriteLock::Recursive),
_isViewing(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) {
unsigned char* octalCode = pointToOctalCode(x,y,z,s);
lockForWrite();
deleteOctalCodeFromTree(octalCode);
unlock();
delete[] octalCode; // cleanup memory
}
@ -529,7 +526,9 @@ void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool colla
args.deleteLastChild = false;
args.pathChanged = false;
withWriteLock([&] {
deleteOctalCodeFromTreeRecursion(_rootElement, &args);
});
}
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};
distance = FLT_MAX;
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...
}
}
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(findRayIntersectionOp, &args);
if (gotLock) {
unlock();
}
}, requireLock);
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;
}
@ -793,31 +778,16 @@ bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::v
NULL };
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...
}
}
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(findSpherePenetrationOp, &args);
if (penetratedObject) {
*penetratedObject = args.penetratedObject;
}
if (gotLock) {
unlock();
}
}, requireLock);
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;
}
@ -896,40 +866,24 @@ bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end
CapsuleArgs args = { start, end, radius, penetration, false };
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...
}
}
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(findCapsulePenetrationOp, &args);
if (gotLock) {
unlock();
}
}, requireLock);
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;
}
bool Octree::findContentInCube(const AACube& cube, CubeList& cubes) {
if (!tryLockForRead()) {
return false;
}
return withTryReadLock([&]{
ContentArgs args = { cube, &cubes };
recurseTreeWithOperation(findContentInCubeOp, &args);
unlock();
return true;
});
}
class GetElementEnclosingArgs {
@ -959,29 +913,15 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc
args.point = point;
args.element = NULL;
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.element; // if we wanted to tryLock, and we couldn't then just bail...
}
}
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&]{
recurseTreeWithOperation(getElementEnclosingOperation, (void*)&args);
if (gotLock) {
unlock();
}
}, requireLock);
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;
}
@ -2204,11 +2144,11 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element)
while (!elementBag.isEmpty()) {
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);
withReadLock([&] {
params.extraEncodeData = &extraEncodeData;
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
unlock();
});
// 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)) {

View file

@ -12,9 +12,11 @@
#ifndef hifi_Octree_h
#define hifi_Octree_h
#include <set>
#include <SimpleMovingAverage.h>
#include <memory>
#include <set>
#include <QHash>
#include <QObject>
class CoverageMap;
class ReadBitstreamToTreeParams;
@ -25,6 +27,10 @@ class OctreePacketData;
class Shape;
typedef std::shared_ptr<Octree> OctreePointer;
#include <shared/ReadWriteLockable.h>
#include <SimpleMovingAverage.h>
#include "JurisdictionMap.h"
#include "ViewFrustum.h"
#include "OctreeElement.h"
@ -32,9 +38,6 @@ typedef std::shared_ptr<Octree> OctreePointer;
#include "OctreePacketData.h"
#include "OctreeSceneStats.h"
#include <QHash>
#include <QObject>
#include <QReadWriteLock>
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
public:
Octree(bool shouldReaverage = false);
@ -289,17 +292,10 @@ public:
void clearDirtyBit() { _isDirty = false; }
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
typedef enum {
Lock,
TryLock,
NoLock
TryLock
} lockType;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -409,8 +405,6 @@ protected:
bool _shouldReaverage;
bool _stopImport;
QReadWriteLock _lock;
bool _isViewing;
bool _isServer;
};

View file

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

View file

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

View file

@ -127,8 +127,7 @@ bool OctreePersistThread::process() {
bool persistantFileRead;
_tree->lockForWrite();
{
_tree->withWriteLock([&] {
PerformanceWarning warn(true, "Loading Octree File", true);
// First check to make sure "lock" file doesn't exist. If it does exist, then
@ -151,8 +150,7 @@ bool OctreePersistThread::process() {
persistantFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit()));
_tree->pruneTree();
}
_tree->unlock();
});
quint64 loadDone = usecTimestampNow();
_loadTimeUSecs = loadDone - loadStarted;
@ -233,13 +231,12 @@ void OctreePersistThread::aboutToFinish() {
void OctreePersistThread::persist() {
if (_tree->isDirty()) {
_tree->lockForWrite();
{
_tree->withWriteLock([&] {
qCDebug(octree) << "pruning Octree before saving...";
_tree->pruneTree();
qCDebug(octree) << "DONE pruning Octree before saving...";
}
_tree->unlock();
});
qCDebug(octree) << "persist operation calling backup...";
backup(); // handle backup if requested

View file

@ -117,12 +117,12 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN
// ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
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
// loop to reduce the amount of locking/unlocking we're doing
_tree->lockForWrite();
quint64 startUncompress = usecTimestampNow();
_tree->withWriteLock([&] {
startUncompress = usecTimestampNow();
OctreePacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(reinterpret_cast<unsigned char*>(packet.getPayload() + packet.pos()),
@ -139,13 +139,13 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN
if (extraDebugging) {
qCDebug(octree) << "OctreeRenderer::processDatagram() ******* START _tree->readBitstreamToTree()...";
}
quint64 startReadBitsteam = usecTimestampNow();
startReadBitsteam = usecTimestampNow();
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
quint64 endReadBitsteam = usecTimestampNow();
endReadBitsteam = usecTimestampNow();
if (extraDebugging) {
qCDebug(octree) << "OctreeRenderer::processDatagram() ******* END _tree->readBitstreamToTree()...";
}
_tree->unlock();
});
// seek forwards in packet
packet.seek(packet.pos() + sectionLength);
@ -211,17 +211,17 @@ bool OctreeRenderer::renderOperation(OctreeElementPointer element, void* extraDa
void OctreeRenderer::render(RenderArgs* renderArgs) {
if (_tree) {
renderArgs->_renderer = this;
_tree->lockForRead();
_tree->withReadLock([&] {
_tree->recurseTreeWithOperation(renderOperation, renderArgs);
_tree->unlock();
});
}
}
void OctreeRenderer::clear() {
if (_tree) {
_tree->lockForWrite();
_tree->withWriteLock([&] {
_tree->eraseAllOctreeElements();
_tree->unlock();
});
}
}

View file

@ -15,6 +15,7 @@
#include <stdint.h>
#include <NodeList.h>
#include <shared/ReadWriteLockable.h>
#include "JurisdictionMap.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
/// which octree stats
typedef std::map<QUuid, OctreeSceneStats> NodeToOctreeSceneStats;
typedef std::map<QUuid, OctreeSceneStats>::iterator NodeToOctreeSceneStatsIterator;
class NodeToOctreeSceneStats : public std::map<QUuid, OctreeSceneStats>, public ReadWriteLockable {};
typedef NodeToOctreeSceneStats::iterator NodeToOctreeSceneStatsIterator;
#endif // hifi_OctreeSceneStats_h

View file

@ -33,22 +33,10 @@ const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity ? _entity->getElement() : nullptr;
EntityTreePointer tree = element ? element->getTree() : nullptr;
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 {
if (!tree) {
return true;
}
return true;
}
#else
bool entityTreeIsLocked() {

View file

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

View file

@ -33,11 +33,7 @@ ObjectActionOffset::~ObjectActionOffset() {
}
void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
if (!tryLockForRead()) {
// don't risk hanging the thread running the physics simulation
return;
}
withTryReadLock([&]{
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
@ -45,14 +41,12 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
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;
}
@ -81,8 +75,7 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
rigidBody->setLinearVelocity(glmToBullet(currentVelocity + blend * (targetVelocity - parallelVelocity)));
}
}
unlock();
});
}
@ -112,25 +105,26 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
if (_pointToOffsetFrom != pointToOffsetFrom
|| _linearTimeScale != linearTimeScale
|| _linearDistance != linearDistance) {
lockForWrite();
withWriteLock([&] {
_pointToOffsetFrom = pointToOffsetFrom;
_linearTimeScale = linearTimeScale;
_linearDistance = linearDistance;
_positionalTargetSet = true;
_active = true;
activateBody();
unlock();
});
}
return true;
}
QVariantMap ObjectActionOffset::getArguments() {
QVariantMap arguments;
lockForRead();
withReadLock([&] {
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
arguments["linearTimeScale"] = _linearTimeScale;
arguments["linearDistance"] = _linearDistance;
unlock();
});
return arguments;
}

View file

@ -38,12 +38,7 @@ ObjectActionSpring::~ObjectActionSpring() {
}
void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
if (!tryLockForRead()) {
// don't risk hanging the thread running the physics simulation
qDebug() << "ObjectActionSpring::updateActionWorker lock failed";
return;
}
auto lockResult = withTryReadLock([&]{
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
@ -51,13 +46,11 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
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;
}
@ -105,7 +98,12 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setAngularVelocity(targetVelocity);
}
unlock();
});
if (!lockResult) {
// don't risk hanging the thread running the physics simulation
qDebug() << "ObjectActionSpring::updateActionWorker lock failed";
return;
}
}
const float MIN_TIMESCALE = 0.1f;
@ -144,29 +142,27 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|| rotationalTarget != _rotationalTarget
|| angularTimeScale != _angularTimeScale) {
// something changed
lockForWrite();
withWriteLock([&] {
_positionalTarget = positionalTarget;
_linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
_rotationalTarget = rotationalTarget;
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
_active = true;
activateBody();
unlock();
});
}
return true;
}
QVariantMap ObjectActionSpring::getArguments() {
QVariantMap arguments;
lockForRead();
withReadLock([&] {
arguments["linearTimeScale"] = _linearTimeScale;
arguments["targetPosition"] = glmToQMap(_positionalTarget);
arguments["targetRotation"] = glmToQMap(_rotationalTarget);
arguments["angularTimeScale"] = _angularTimeScale;
unlock();
});
return arguments;
}

View file

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

View file

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

View file

@ -150,7 +150,7 @@ void PhysicsEngine::removeObject(ObjectMotionState* object) {
_dynamicsWorld->removeRigidBody(body);
}
void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) {
void PhysicsEngine::deleteObjects(const VectorOfMotionStates& objects) {
for (auto object : objects) {
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.
void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) {
void PhysicsEngine::deleteObjects(const SetOfMotionStates& objects) {
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
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) {
addObject(object);
}
}
VectorOfMotionStates PhysicsEngine::changeObjects(VectorOfMotionStates& objects) {
VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& objects) {
VectorOfMotionStates stillNeedChange;
for (auto object : objects) {
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;
_collisionEvents.clear();
@ -374,7 +374,7 @@ CollisionEvents& PhysicsEngine::getCollisionEvents() {
return _collisionEvents;
}
VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
const VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
BT_PROFILE("copyOutgoingChanges");
_dynamicsWorld->synchronizeMotionStates();
_hasOutgoingChanges = false;

View file

@ -56,10 +56,10 @@ public:
void addObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState);
void deleteObjects(VectorOfMotionStates& objects);
void deleteObjects(SetOfMotionStates& objects); // only called during teardown
void addObjects(VectorOfMotionStates& objects);
VectorOfMotionStates changeObjects(VectorOfMotionStates& objects);
void deleteObjects(const VectorOfMotionStates& objects);
void deleteObjects(const SetOfMotionStates& objects); // only called during teardown
void addObjects(const VectorOfMotionStates& objects);
VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects);
void reinsertObject(ObjectMotionState* object);
void stepSimulation();
@ -68,10 +68,10 @@ public:
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
/// \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.
CollisionEvents& getCollisionEvents();
const CollisionEvents& getCollisionEvents();
/// \brief prints timings for last frame if stats have been requested.
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