QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties properties) { EntityItemID entityID(id); // If we have a local entity tree set, then also update it. if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } 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 properties.setType(entity->getType()); bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; if (hasPhysicsChanges) { auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); if (entity->getSimulatorID() == myNodeID) { // we think we already own the simulation, so make sure to send ALL TerseUpdate properties if (hasTerseUpdateChanges) { entity->getAllTerseUpdateProperties(properties); } // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's // simulation. if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->flagForOwnership(); } } entity->setLastBroadcast(usecTimestampNow()); } }); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; }
void EntityScriptingInterface::deleteEntity(QUuid id) { EntityItemID entityID(id); bool shouldDelete = true; // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { auto dimensions = entity->getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); auto velocity = entity->getVelocity().length(); float cost = calculateCost(density * volume, velocity, 0); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { shouldDelete = false; return; } else { //debit the avatar energy and continue emit debitEnergySource(cost); } if (entity->getLocked()) { shouldDelete = false; } else { _entityTree->deleteEntity(entityID); } } }); } // if at this point, we know the id, and we should still delete the entity, send the update to the entity server if (shouldDelete) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } }
void EntityScriptingInterface::deleteEntity(QUuid id) { EntityItemID entityID(id); bool shouldDelete = true; // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { if (entity->getLocked()) { shouldDelete = false; } else { _entityTree->deleteEntity(entityID); } } }); } // if at this point, we know the id, and we should still delete the entity, send the update to the entity server if (shouldDelete) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } }
//------------------------------------------------------------------------------------- thread::TPTask::TPTaskState DataDownload::presentMainThread() { if(error_) { ERROR_MSG(fmt::format("DataDownload::presentMainThread: proxy({}), downloadID({}), type({}), thread error.\n", entityID(), id(), type())); return thread::TPTask::TPTASK_STATE_COMPLETED; } uint32 datasize = GAME_PACKET_MAX_SIZE_TCP - sizeof(int16) - sizeof(uint32); if(remainSent_ > 0 && currSent_ < remainSent_) { Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject(); if(!sentStart_) { pBundle->newMessage(ClientInterface::onStreamDataStarted); (*pBundle) << this->id(); (*pBundle) << totalBytes_; (*pBundle) << descr_; (*pBundle) << type(); sentStart_ = true; if(!send(ClientInterface::onStreamDataStarted, pBundle)) { DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({}), downloadID({}), type({}), thread exit.\n", entityID(), id(), type())); return thread::TPTask::TPTASK_STATE_COMPLETED; } return thread::TPTask::TPTASK_STATE_CONTINUE_MAINTHREAD; } pBundle->newMessage(ClientInterface::onStreamDataRecv); (*pBundle) << id(); if(remainSent_ - currSent_ > datasize) { (*pBundle) << datasize; (*pBundle).append(getOutStream() + currSent_, datasize); currSent_ += datasize; totalSentBytes_ += datasize; if(!send(ClientInterface::onStreamDataRecv, pBundle)) { DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({}), downloadID({}), type({}), thread exit.\n", entityID(), id(), type())); error_ = true; return thread::TPTask::TPTASK_STATE_COMPLETED; } } else { datasize = remainSent_ - currSent_; (*pBundle) << datasize; (*pBundle).append(getOutStream() + currSent_, datasize); if(!send(ClientInterface::onStreamDataRecv, pBundle)) { DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({}), downloadID({}), type({}), thread exit.\n", entityID(), id(), type())); error_ = true; return thread::TPTask::TPTASK_STATE_COMPLETED; } totalSentBytes_ += datasize; currSent_ = remainSent_; } } if(totalSentBytes_ == totalBytes_) { DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({0}), downloadID({1}), type({6}), sentBytes={5},{2}/{3} ({4:.2f}%).\n", entityID(), id(), totalSentBytes_, this->totalBytes(), 100.0f, datasize, type())); pDataDownloads_->onDownloadCompleted(this); Network::Bundle* pBundle = Network::Bundle::ObjPool().createObject(); pBundle->newMessage(ClientInterface::onStreamDataCompleted); (*pBundle) << this->id(); send(ClientInterface::onStreamDataCompleted, pBundle); return thread::TPTask::TPTASK_STATE_COMPLETED; } DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({0}), downloadID({1}), type({6}), sentBytes={5},{2}/{3} ({4:.2f}%).\n", entityID(), id(), totalSentBytes_, this->totalBytes(), (((float)totalSentBytes_ / (float)this->totalBytes()) * 100.0f), datasize, type())); if(currSent_ == remainSent_) { DEBUG_MSG(fmt::format("DataDownload::presentMainThread: proxy({}), downloadID({}), type({}), thread-continue.\n", entityID(), id(), type())); return thread::TPTask::TPTASK_STATE_CONTINUE_CHILDTHREAD; } return thread::TPTask::TPTASK_STATE_CONTINUE_MAINTHREAD; }
QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { EntityItemProperties properties = scriptSideProperties; auto dimensions = properties.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = properties.getDensity(); auto newVelocity = properties.getVelocity().length(); float oldVelocity = { 0.0f }; EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); //if there is no local entity entity tree, no existing velocity, use 0. float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { return QUuid(); } else { //debit the avatar energy and continue emit debitEnergySource(cost); } return id; } // If we have a local entity tree set, then also update it. bool updatedEntity = false; _entityTree->withWriteLock([&] { if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { return; } //existing entity, retrieve old velocity for check down below oldVelocity = entity->getVelocity().length(); if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } if (!scriptSideProperties.parentJointIndexChanged()) { properties.setParentJointIndex(entity->getParentJointIndex()); } if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { properties.setPosition(entity->getPosition()); } if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { properties.setRotation(entity->getOrientation()); } } properties = convertLocationFromScriptSemantics(properties); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; if (cost > _currentAvatarEnergy) { updatedEntity = false; } else { //debit the avatar energy and continue updatedEntity = _entityTree->updateEntity(entityID, properties); if (updatedEntity) { emit debitEnergySource(cost); } } }); 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 properties.setType(entity->getType()); bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; if (_bidOnSimulationOwnership && hasPhysicsChanges) { auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); if (entity->getSimulatorID() == myNodeID) { // we think we already own the simulation, so make sure to send ALL TerseUpdate properties if (hasTerseUpdateChanges) { entity->getAllTerseUpdateProperties(properties); } // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical double-update) that we see during the "poke rolling // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's // simulation. if (entity->getSimulationPriority() < SCRIPT_POKE_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->pokeSimulationOwnership(); } } if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) { properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { if (descendant->computePuffedQueryAACube()) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(usecTimestampNow()); } } }); } }); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; }
void EntityTests::entityTreeTests(bool verbose) { bool extraVerbose = false; int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "EntityTests::entityTreeTests()"; // Tree, id, and entity properties used in many tests below... EntityTree tree; QUuid id = QUuid::createUuid(); EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs EntityItemProperties properties; float oneMeter = 1.0f; //float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE; glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE; { testsTaken++; QString testName = "add entity to tree and search"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } properties.setPosition(positionAtCenterInMeters); // TODO: Fix these unit tests. //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); tree.addEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs { testsTaken++; QString testName = "change position of entity in tree"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionNearOriginInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; QString testName = "change position of entity in tree back to center"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionAtCenterInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findClosestEntity() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units quint64 start = usecTimestampNow(); const EntityItem* foundEntityByRadius = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; } bool passed = foundEntityByRadius; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findEntityByID() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } quint64 start = usecTimestampNow(); const EntityItem* foundEntityByID = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { // TODO: does this need to be updated?? foundEntityByID = tree.findEntityByEntityItemID(entityID); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByID=" << foundEntityByID; } bool passed = foundEntityByID; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { // seed the random number generator so that our tests are reproducible srand(0xFEEDBEEF); testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - add entity to tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedAdd = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs float randomX = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomY = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomZ = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); glm::vec3 randomPositionInMeters(randomX,randomY,randomZ); glm::vec3 randomPositionInTreeUnits = randomPositionInMeters / (float)TREE_SCALE; properties.setPosition(randomPositionInMeters); // TODO: fix these unit tests //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); if (extraVerbose) { qDebug() << "iteration:" << i << "ading entity at x/y/z=" << randomX << "," << randomY << "," << randomZ; qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startAdd = usecTimestampNow(); tree.addEntity(entityID, properties); quint64 endAdd = usecTimestampNow(); totalElapsedAdd += (endAdd - startAdd); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(randomPositionInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); bool elementIsBestFit = containingElement->bestFitEntityBounds(foundEntityByID); if (extraVerbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); qDebug() << "elementIsBestFit=" << elementIsBestFit; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID) && elementIsBestFit; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByRadius=" << foundEntityByRadius << "foundEntityByID=" << foundEntityByID << "x/y/z=" << randomX << "," << randomY << "," << randomZ << "elementIsBestFit=" << elementIsBestFit; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsAdd = (float)(totalElapsedAdd) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Add=" << elapsedInMSecsAdd << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - delete entity from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntity(entityID); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 100; const int ENTITIES_PER_ITERATION = 10; QString testName = "Performance - delete " + QString::number(ENTITIES_PER_ITERATION) + " entities from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QSet<EntityItemID> entitiesToDelete; for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entitiesToDelete << entityID; } if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntities(entitiesToDelete); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == (TEST_ITERATIONS * ENTITIES_PER_ITERATION); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken; if (verbose) { qDebug() << "******************************************************************************************"; } }