//// PhysicalEntitySimulation.cpp// libraries/physics/src//// Created by Andrew Meadows 2015.04.27// Copyright 2015 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "PhysicalEntitySimulation.h" #include <Profile.h> #include "PhysicsHelpers.h"#include "PhysicsLogging.h"#include "ShapeManager.h" PhysicalEntitySimulation::PhysicalEntitySimulation() {} PhysicalEntitySimulation::~PhysicalEntitySimulation() {} void PhysicalEntitySimulation::init( EntityTreePointer tree, PhysicsEnginePointer physicsEngine, EntityEditPacketSender* packetSender) { assert(tree); setEntityTree(tree); assert(physicsEngine); _physicsEngine = physicsEngine; assert(packetSender); _entityPacketSender = packetSender;} // begin EntitySimulation overridesvoid PhysicalEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) { EntitySimulation::addEntityToInternalLists(entity); entity->deserializeActions(); // TODO: do this elsewhere uint8_t region = _space->getRegion(entity->getSpaceIndex()); bool maybeShouldBePhysical = (region < workload::Region::R3 || region == workload::Region::UNKNOWN) && entity->shouldBePhysical(); bool canBeKinematic = region <= workload::Region::R3; if (maybeShouldBePhysical) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { motionState->setRegion(region); } else { _entitiesToAddToPhysics.insert(entity); } } else if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } }} void PhysicalEntitySimulation::removeEntityFromInternalLists(EntityItemPointer entity) { _entitiesToAddToPhysics.remove(entity); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); } if (entity->isDead() && entity->getElement()) { _deadEntitiesToRemoveFromTree.insert(entity); } if (entity->isAvatarEntity()) { _deadAvatarEntities.insert(entity); } EntitySimulation::removeEntityFromInternalLists(entity);} void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { assert(motionState); if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { for (uint32_t i = 0; i < _owned.size(); ++i) { if (_owned[i] == motionState) { _owned[i]->clearOwnershipState(); _owned.remove(i); } } } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { for (uint32_t i = 0; i < _bids.size(); ++i) { if (_bids[i] == motionState) { _bids[i]->clearOwnershipState(); _bids.remove(i); } } }} void PhysicalEntitySimulation::clearOwnershipData() { for (uint32_t i = 0; i < _owned.size(); ++i) { _owned[i]->clearOwnershipState(); } _owned.clear(); for (uint32_t i = 0; i < _bids.size(); ++i) { _bids[i]->clearOwnershipState(); } _bids.clear();} void PhysicalEntitySimulation::takeDeadAvatarEntities(SetOfEntities& deadEntities) { _deadAvatarEntities.swap(deadEntities); _deadAvatarEntities.clear();} void PhysicalEntitySimulation::processChangedEntity(const EntityItemPointer& entity) { EntitySimulation::processChangedEntity(entity); // queue incoming changes: from external sources (script, EntityServer, etc) to physics engine EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); uint8_t region = _space->getRegion(entity->getSpaceIndex()); bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); bool canBeKinematic = region <= workload::Region::R3; if (motionState) { if (!shouldBePhysical) { if (motionState->isLocallyOwned()) { // zero velocities by first deactivating the RigidBody btRigidBody* body = motionState->getRigidBody(); if (body) { body->forceActivationState(ISLAND_SLEEPING); } // send packet to remove ownership // NOTE: this packet will NOT be resent if lost, but the good news is: // the entity-server will eventually clear velocity and ownership for timeout motionState->sendUpdate(_entityPacketSender, _physicsEngine->getNumSubsteps()); } // remove from the physical simulation _incomingChanges.remove(motionState); removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } } else { _incomingChanges.insert(motionState); } motionState->setRegion(region); } else if (shouldBePhysical) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? _entitiesToAddToPhysics.insert(entity); SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr != _simpleKinematicEntities.end()) { _simpleKinematicEntities.erase(itr); } } else if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } else { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr != _simpleKinematicEntities.end()) { _simpleKinematicEntities.erase(itr); } }} void PhysicalEntitySimulation::processDeadEntities() { // Note: this override is a complete rewite of the base class's method because we cannot assume all entities // are domain entities, and the consequence of trying to delete a domain-entity in this case is very different. if (_deadEntitiesToRemoveFromTree.empty()) { return; } PROFILE_RANGE(simulation_physics, "Deletes"); std::vector<EntityItemPointer> entitiesToDeleteImmediately; entitiesToDeleteImmediately.reserve(_deadEntitiesToRemoveFromTree.size()); QUuid sessionID = Physics::getSessionUUID(); QMutexLocker lock(&_mutex); for (auto entity : _deadEntitiesToRemoveFromTree) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { _entitiesToRemoveFromPhysics.insert(entity); } if (entity->isDomainEntity() && !getEntityTree()->isServerlessMode()) { // interface-client can't delete domainEntities outright, they must roundtrip through the entity-server _entityPacketSender->queueEraseEntityMessage(entity->getID()); } else { entitiesToDeleteImmediately.push_back(entity); entity->collectChildrenForDelete(entitiesToDeleteImmediately, sessionID); } } _deadEntitiesToRemoveFromTree.clear(); if (!entitiesToDeleteImmediately.empty()) { getEntityTree()->deleteEntitiesByPointer(entitiesToDeleteImmediately); }} void PhysicalEntitySimulation::clearEntities() { // TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... QMutexLocker lock(&_mutex); // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); clearOwnershipData(); // delete the MotionStates for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr)); assert(motionState); EntityItemPointer entity = motionState->getEntity(); // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo // until then we must do it here delete motionState; } _physicalObjects.clear(); // clear all other lists specific to this derived class _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); _entitiesToDeleteLater.clear(); EntitySimulation::clearEntities();} void PhysicalEntitySimulation::queueEraseDomainEntity(const QUuid& id) const { if (_entityPacketSender) { _entityPacketSender->queueEraseEntityMessage(id); }} // virtualvoid PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { // DANGER! this can be called on any thread // do no dirty deeds here --> assemble list for later assert(entity); assert(entity->isDead()); QMutexLocker lock(&_mutex); _entitiesToDeleteLater.push_back(entity);} void PhysicalEntitySimulation::removeDeadEntities() { // DANGER! only ever call this on the main thread QMutexLocker lock(&_mutex); for (auto& entity : _entitiesToDeleteLater) { entity->clearActions(getThisPointer()); EntitySimulation::prepareEntityForDelete(entity); } _entitiesToDeleteLater.clear();}// end EntitySimulation overrides void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { // this lambda for when we decide to actually build the motionState auto buildMotionState = [&](btCollisionShape* shape, EntityItemPointer entity) { EntityMotionState* motionState = new EntityMotionState(shape, entity); entity->setPhysicsInfo(static_cast<void*>(motionState)); motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); _physicalObjects.insert(motionState); _incomingChanges.insert(motionState); }; uint32_t deliveryCount = ObjectMotionState::getShapeManager()->getWorkDeliveryCount(); if (deliveryCount != _lastWorkDeliveryCount) { // new off-thread shapes have arrived --> find adds whose shapes have arrived _lastWorkDeliveryCount = deliveryCount; ShapeRequests::iterator requestItr = _shapeRequests.begin(); while (requestItr != _shapeRequests.end()) { EntityItemPointer entity = requestItr->entity; EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (!motionState) { // this is an ADD because motionState doesn't exist yet btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShapeByKey(requestItr->shapeHash)); if (shape) { // shape is ready at last! // but the entity's physics desired physics status may have changed since last requested if (!entity->shouldBePhysical()) { requestItr = _shapeRequests.erase(requestItr); continue; } // rebuild the ShapeInfo to verify hash because entity's desired shape may have changed // TODO? is there a better way to do this? ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); if (shapeInfo.getType() == SHAPE_TYPE_NONE) { requestItr = _shapeRequests.erase(requestItr); } else if (shapeInfo.getHash() != requestItr->shapeHash) { // bummer, the hashes are different and we no longer want the shape we've received ObjectMotionState::getShapeManager()->releaseShape(shape); // try again shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { buildMotionState(shape, entity); requestItr = _shapeRequests.erase(requestItr); } else { requestItr->shapeHash = shapeInfo.getHash(); ++requestItr; } } else { buildMotionState(shape, entity); requestItr = _shapeRequests.erase(requestItr); } } else { // shape not ready ++requestItr; } } else { // this is a CHANGE because motionState already exists if (ObjectMotionState::getShapeManager()->hasShapeWithKey(requestItr->shapeHash)) { entity->markDirtyFlags(Simulation::DIRTY_SHAPE); _incomingChanges.insert(motionState); requestItr = _shapeRequests.erase(requestItr); } else { // shape not ready ++requestItr; } } } } SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); while (entityItr != _entitiesToAddToPhysics.end()) { EntityItemPointer entity = (*entityItr); if (entity->isDead()) { prepareEntityForDelete(entity); entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (entity->getPhysicsInfo()) { entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (!entity->shouldBePhysical()) { // this entity should no longer be on _entitiesToAddToPhysics if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } uint8_t region = _space->getRegion(entity->getSpaceIndex()); if (region == workload::Region::UNKNOWN) { // the workload hasn't categorized it yet --> skip for later ++entityItr; continue; } if (region > workload::Region::R2) { // not in physical zone --> remove from list entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (entity->isReadyToComputeShape()) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { // not waiting for a shape (yet) ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { buildMotionState(shape, entity); } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); _shapeRequests.insert(shapeRequest); } else { // failed to build shape --> will not be added } } entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { // skip for later ++entityItr; } }} void PhysicalEntitySimulation::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { QMutexLocker lock(&_mutex); // entities being removed for (auto entity : _entitiesToRemoveFromPhysics) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { transaction.objectsToRemove.push_back(motionState); _incomingChanges.remove(motionState); } if (_shapeRequests.size() > 0) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr != _shapeRequests.end()) { _shapeRequests.erase(requestItr); } } } _entitiesToRemoveFromPhysics.clear(); // entities to add buildMotionStatesForEntitiesThatNeedThem(); // motionStates with changed entities: delete, add, or change for (auto& object : _incomingChanges) { uint32_t unhandledFlags = object->getIncomingDirtyFlags(); uint32_t handledFlags = EASY_DIRTY_PHYSICS_FLAGS; bool isInPhysicsSimulation = object->isInPhysicsSimulation(); bool shouldBeInPhysicsSimulation = object->shouldBeInPhysicsSimulation(); if (!shouldBeInPhysicsSimulation && isInPhysicsSimulation) { transaction.objectsToRemove.push_back(object); continue; } bool needsNewShape = object->needsNewShape() && object->_entity->isReadyToComputeShape(); if (needsNewShape) { ShapeType shapeType = object->getShapeType(); if (shapeType == SHAPE_TYPE_STATIC_MESH) { ShapeRequest shapeRequest(object->_entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { ShapeInfo shapeInfo; object->_entity->computeShapeInfo(shapeInfo); uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { object->setShape(shape); handledFlags |= Simulation::DIRTY_SHAPE; needsNewShape = false; } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); _shapeRequests.insert(shapeRequest); } else { // failed to build shape --> will not be added/updated handledFlags |= Simulation::DIRTY_SHAPE; } } else { // continue waiting for shape request } } else { ShapeInfo shapeInfo; object->_entity->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { object->setShape(shape); handledFlags |= Simulation::DIRTY_SHAPE; needsNewShape = false; } else { // failed to build shape --> will not be added } } } if (!isInPhysicsSimulation) { if (needsNewShape) { // skip it continue; } else { transaction.objectsToAdd.push_back(object); handledFlags = DIRTY_PHYSICS_FLAGS; unhandledFlags = 0; } } if (unhandledFlags & EASY_DIRTY_PHYSICS_FLAGS) { object->handleEasyChanges(unhandledFlags); } if (unhandledFlags & (Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | (handledFlags & Simulation::DIRTY_SHAPE))) { transaction.objectsToReinsert.push_back(object); handledFlags |= HARD_DIRTY_PHYSICS_FLAGS; } else if (unhandledFlags & Simulation::DIRTY_PHYSICS_ACTIVATION && object->getRigidBody()->isStaticObject()) { transaction.activeStaticObjects.push_back(object); } object->clearIncomingDirtyFlags(handledFlags); } _incomingChanges.clear();} void PhysicalEntitySimulation::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) { // things on objectsToRemove are ready for delete for (auto object : transaction.objectsToRemove) { EntityMotionState* entityState = static_cast<EntityMotionState*>(object); removeOwnershipData(entityState); _physicalObjects.remove(object); delete object; } transaction.clear();Lines 1–500 of 703. Load more
// // PhysicalEntitySimulation.cpp // libraries/physics/src // // Created by Andrew Meadows 2015.04.27 // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "PhysicalEntitySimulation.h" #include <Profile.h> #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" PhysicalEntitySimulation::PhysicalEntitySimulation() { } PhysicalEntitySimulation::~PhysicalEntitySimulation() { } void PhysicalEntitySimulation::init( EntityTreePointer tree, PhysicsEnginePointer physicsEngine, EntityEditPacketSender* packetSender) { assert(tree); setEntityTree(tree); assert(physicsEngine); _physicsEngine = physicsEngine; assert(packetSender); _entityPacketSender = packetSender; } // begin EntitySimulation overrides void PhysicalEntitySimulation::addEntityToInternalLists(EntityItemPointer entity) { EntitySimulation::addEntityToInternalLists(entity); entity->deserializeActions(); // TODO: do this elsewhere uint8_t region = _space->getRegion(entity->getSpaceIndex()); bool maybeShouldBePhysical = (region < workload::Region::R3 || region == workload::Region::UNKNOWN) && entity->shouldBePhysical(); bool canBeKinematic = region <= workload::Region::R3; if (maybeShouldBePhysical) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { motionState->setRegion(region); } else { _entitiesToAddToPhysics.insert(entity); } } else if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } } void PhysicalEntitySimulation::removeEntityFromInternalLists(EntityItemPointer entity) { _entitiesToAddToPhysics.remove(entity); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); } if (entity->isDead() && entity->getElement()) { _deadEntitiesToRemoveFromTree.insert(entity); } if (entity->isAvatarEntity()) { _deadAvatarEntities.insert(entity); } EntitySimulation::removeEntityFromInternalLists(entity); } void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { assert(motionState); if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { for (uint32_t i = 0; i < _owned.size(); ++i) { if (_owned[i] == motionState) { _owned[i]->clearOwnershipState(); _owned.remove(i); } } } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { for (uint32_t i = 0; i < _bids.size(); ++i) { if (_bids[i] == motionState) { _bids[i]->clearOwnershipState(); _bids.remove(i); } } } } void PhysicalEntitySimulation::clearOwnershipData() { for (uint32_t i = 0; i < _owned.size(); ++i) { _owned[i]->clearOwnershipState(); } _owned.clear(); for (uint32_t i = 0; i < _bids.size(); ++i) { _bids[i]->clearOwnershipState(); } _bids.clear(); } void PhysicalEntitySimulation::takeDeadAvatarEntities(SetOfEntities& deadEntities) { _deadAvatarEntities.swap(deadEntities); _deadAvatarEntities.clear(); } void PhysicalEntitySimulation::processChangedEntity(const EntityItemPointer& entity) { EntitySimulation::processChangedEntity(entity); // queue incoming changes: from external sources (script, EntityServer, etc) to physics engine EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); uint8_t region = _space->getRegion(entity->getSpaceIndex()); bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); bool canBeKinematic = region <= workload::Region::R3; if (motionState) { if (!shouldBePhysical) { if (motionState->isLocallyOwned()) { // zero velocities by first deactivating the RigidBody btRigidBody* body = motionState->getRigidBody(); if (body) { body->forceActivationState(ISLAND_SLEEPING); } // send packet to remove ownership // NOTE: this packet will NOT be resent if lost, but the good news is: // the entity-server will eventually clear velocity and ownership for timeout motionState->sendUpdate(_entityPacketSender, _physicsEngine->getNumSubsteps()); } // remove from the physical simulation _incomingChanges.remove(motionState); removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } } else { _incomingChanges.insert(motionState); } motionState->setRegion(region); } else if (shouldBePhysical) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? _entitiesToAddToPhysics.insert(entity); SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr != _simpleKinematicEntities.end()) { _simpleKinematicEntities.erase(itr); } } else if (canBeKinematic && entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } else { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr != _simpleKinematicEntities.end()) { _simpleKinematicEntities.erase(itr); } } } void PhysicalEntitySimulation::processDeadEntities() { // Note: this override is a complete rewite of the base class's method because we cannot assume all entities // are domain entities, and the consequence of trying to delete a domain-entity in this case is very different. if (_deadEntitiesToRemoveFromTree.empty()) { return; } PROFILE_RANGE(simulation_physics, "Deletes"); std::vector<EntityItemPointer> entitiesToDeleteImmediately; entitiesToDeleteImmediately.reserve(_deadEntitiesToRemoveFromTree.size()); QUuid sessionID = Physics::getSessionUUID(); QMutexLocker lock(&_mutex); for (auto entity : _deadEntitiesToRemoveFromTree) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { _entitiesToRemoveFromPhysics.insert(entity); } if (entity->isDomainEntity() && !getEntityTree()->isServerlessMode()) { // interface-client can't delete domainEntities outright, they must roundtrip through the entity-server _entityPacketSender->queueEraseEntityMessage(entity->getID()); } else { entitiesToDeleteImmediately.push_back(entity); entity->collectChildrenForDelete(entitiesToDeleteImmediately, sessionID); } } _deadEntitiesToRemoveFromTree.clear(); if (!entitiesToDeleteImmediately.empty()) { getEntityTree()->deleteEntitiesByPointer(entitiesToDeleteImmediately); } } void PhysicalEntitySimulation::clearEntities() { // TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... QMutexLocker lock(&_mutex); // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); clearOwnershipData(); // delete the MotionStates for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr)); assert(motionState); EntityItemPointer entity = motionState->getEntity(); // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo // until then we must do it here delete motionState; } _physicalObjects.clear(); // clear all other lists specific to this derived class _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); _entitiesToDeleteLater.clear(); EntitySimulation::clearEntities(); } void PhysicalEntitySimulation::queueEraseDomainEntity(const QUuid& id) const { if (_entityPacketSender) { _entityPacketSender->queueEraseEntityMessage(id); } } // virtual void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { // DANGER! this can be called on any thread // do no dirty deeds here --> assemble list for later assert(entity); assert(entity->isDead()); QMutexLocker lock(&_mutex); _entitiesToDeleteLater.push_back(entity); } void PhysicalEntitySimulation::removeDeadEntities() { // DANGER! only ever call this on the main thread QMutexLocker lock(&_mutex); for (auto& entity : _entitiesToDeleteLater) { entity->clearActions(getThisPointer()); EntitySimulation::prepareEntityForDelete(entity); } _entitiesToDeleteLater.clear(); } // end EntitySimulation overrides void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { // this lambda for when we decide to actually build the motionState auto buildMotionState = [&](btCollisionShape* shape, EntityItemPointer entity) { EntityMotionState* motionState = new EntityMotionState(shape, entity); entity->setPhysicsInfo(static_cast<void*>(motionState)); motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); _physicalObjects.insert(motionState); _incomingChanges.insert(motionState); }; uint32_t deliveryCount = ObjectMotionState::getShapeManager()->getWorkDeliveryCount(); if (deliveryCount != _lastWorkDeliveryCount) { // new off-thread shapes have arrived --> find adds whose shapes have arrived _lastWorkDeliveryCount = deliveryCount; ShapeRequests::iterator requestItr = _shapeRequests.begin(); while (requestItr != _shapeRequests.end()) { EntityItemPointer entity = requestItr->entity; EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (!motionState) { // this is an ADD because motionState doesn't exist yet btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShapeByKey(requestItr->shapeHash)); if (shape) { // shape is ready at last! // but the entity's physics desired physics status may have changed since last requested if (!entity->shouldBePhysical()) { requestItr = _shapeRequests.erase(requestItr); continue; } // rebuild the ShapeInfo to verify hash because entity's desired shape may have changed // TODO? is there a better way to do this? ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); if (shapeInfo.getType() == SHAPE_TYPE_NONE) { requestItr = _shapeRequests.erase(requestItr); } else if (shapeInfo.getHash() != requestItr->shapeHash) { // bummer, the hashes are different and we no longer want the shape we've received ObjectMotionState::getShapeManager()->releaseShape(shape); // try again shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { buildMotionState(shape, entity); requestItr = _shapeRequests.erase(requestItr); } else { requestItr->shapeHash = shapeInfo.getHash(); ++requestItr; } } else { buildMotionState(shape, entity); requestItr = _shapeRequests.erase(requestItr); } } else { // shape not ready ++requestItr; } } else { // this is a CHANGE because motionState already exists if (ObjectMotionState::getShapeManager()->hasShapeWithKey(requestItr->shapeHash)) { entity->markDirtyFlags(Simulation::DIRTY_SHAPE); _incomingChanges.insert(motionState); requestItr = _shapeRequests.erase(requestItr); } else { // shape not ready ++requestItr; } } } } SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); while (entityItr != _entitiesToAddToPhysics.end()) { EntityItemPointer entity = (*entityItr); if (entity->isDead()) { prepareEntityForDelete(entity); entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (entity->getPhysicsInfo()) { entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (!entity->shouldBePhysical()) { // this entity should no longer be on _entitiesToAddToPhysics if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } uint8_t region = _space->getRegion(entity->getSpaceIndex()); if (region == workload::Region::UNKNOWN) { // the workload hasn't categorized it yet --> skip for later ++entityItr; continue; } if (region > workload::Region::R2) { // not in physical zone --> remove from list entityItr = _entitiesToAddToPhysics.erase(entityItr); continue; } if (entity->isReadyToComputeShape()) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { // not waiting for a shape (yet) ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { buildMotionState(shape, entity); } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); _shapeRequests.insert(shapeRequest); } else { // failed to build shape --> will not be added } } entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { // skip for later ++entityItr; } } } void PhysicalEntitySimulation::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { QMutexLocker lock(&_mutex); // entities being removed for (auto entity : _entitiesToRemoveFromPhysics) { EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); if (motionState) { transaction.objectsToRemove.push_back(motionState); _incomingChanges.remove(motionState); } if (_shapeRequests.size() > 0) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr != _shapeRequests.end()) { _shapeRequests.erase(requestItr); } } } _entitiesToRemoveFromPhysics.clear(); // entities to add buildMotionStatesForEntitiesThatNeedThem(); // motionStates with changed entities: delete, add, or change for (auto& object : _incomingChanges) { uint32_t unhandledFlags = object->getIncomingDirtyFlags(); uint32_t handledFlags = EASY_DIRTY_PHYSICS_FLAGS; bool isInPhysicsSimulation = object->isInPhysicsSimulation(); bool shouldBeInPhysicsSimulation = object->shouldBeInPhysicsSimulation(); if (!shouldBeInPhysicsSimulation && isInPhysicsSimulation) { transaction.objectsToRemove.push_back(object); continue; } bool needsNewShape = object->needsNewShape() && object->_entity->isReadyToComputeShape(); if (needsNewShape) { ShapeType shapeType = object->getShapeType(); if (shapeType == SHAPE_TYPE_STATIC_MESH) { ShapeRequest shapeRequest(object->_entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { ShapeInfo shapeInfo; object->_entity->computeShapeInfo(shapeInfo); uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { object->setShape(shape); handledFlags |= Simulation::DIRTY_SHAPE; needsNewShape = false; } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); _shapeRequests.insert(shapeRequest); } else { // failed to build shape --> will not be added/updated handledFlags |= Simulation::DIRTY_SHAPE; } } else { // continue waiting for shape request } } else { ShapeInfo shapeInfo; object->_entity->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { object->setShape(shape); handledFlags |= Simulation::DIRTY_SHAPE; needsNewShape = false; } else { // failed to build shape --> will not be added } } } if (!isInPhysicsSimulation) { if (needsNewShape) { // skip it continue; } else { transaction.objectsToAdd.push_back(object); handledFlags = DIRTY_PHYSICS_FLAGS; unhandledFlags = 0; } } if (unhandledFlags & EASY_DIRTY_PHYSICS_FLAGS) { object->handleEasyChanges(unhandledFlags); } if (unhandledFlags & (Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP | (handledFlags & Simulation::DIRTY_SHAPE))) { transaction.objectsToReinsert.push_back(object); handledFlags |= HARD_DIRTY_PHYSICS_FLAGS; } else if (unhandledFlags & Simulation::DIRTY_PHYSICS_ACTIVATION && object->getRigidBody()->isStaticObject()) { transaction.activeStaticObjects.push_back(object); } object->clearIncomingDirtyFlags(handledFlags); } _incomingChanges.clear(); } void PhysicalEntitySimulation::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) { // things on objectsToRemove are ready for delete for (auto object : transaction.objectsToRemove) { EntityMotionState* entityState = static_cast<EntityMotionState*>(object); removeOwnershipData(entityState); _physicalObjects.remove(object); delete object; } transaction.clear();