From 2fab662e8c09f4e6bf40e77040e7a7bd70e230c3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 29 Aug 2014 17:48:14 -0700 Subject: [PATCH] fixed some problems in UpdateEntityOperator in case where original containing element isn't best fit --- interface/src/entities/EntityTreeRenderer.cpp | 29 ++- .../entities/src/DeleteEntityOperator.cpp | 117 ++++++++++- libraries/entities/src/DeleteEntityOperator.h | 2 + libraries/entities/src/EntityTree.cpp | 118 ++++++----- libraries/entities/src/EntityTreeElement.cpp | 26 +-- libraries/entities/src/EntityTreeElement.h | 2 +- .../entities/src/MovingEntitiesOperator.cpp | 198 ++++++++++++++++-- .../entities/src/MovingEntitiesOperator.h | 4 + .../entities/src/UpdateEntityOperator.cpp | 130 +++++++++--- libraries/entities/src/UpdateEntityOperator.h | 2 + libraries/entities/src/todo.txt | 28 ++- 11 files changed, 532 insertions(+), 124 deletions(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 57905072e6..d384f03727 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -66,8 +66,21 @@ void EntityTreeRenderer::update() { } void EntityTreeRenderer::render(RenderMode renderMode) { + + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "------------ EntityTreeRenderer::render() -- BEFORE WE BEGIN: the tree[" << _tree << "] -------------"; + static_cast(_tree)->dumpTree(); + qDebug() << "------------ EntityTreeRenderer::render() -- END the tree-------------"; + } + OctreeRenderer::render(renderMode); deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup + + if (wantDebug) { + qDebug() << "------------ DONE EntityTreeRenderer::render() -------------"; + } } const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(const EntityItem* entityItem) { @@ -98,6 +111,14 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI } void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "EntityTreeRenderer::renderElement()..."; + qDebug() << " element=" << element; + qDebug() << " element->getAACube()=" << element->getAACube(); + } + //PerformanceTimer perfTimer("renderElement"); args->_elementsTouched++; // actually render it here... @@ -180,8 +201,6 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) for (uint16_t i = 0; i < numberOfEntities; i++) { EntityItem* entityItem = entityItems[i]; - bool wantDebug = false; - if (wantDebug) { bool isBestFit = entityTreeElement->bestFitEntityBounds(entityItem); qDebug() << "EntityTreeRenderer::renderElement() " @@ -202,6 +221,12 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) if (entityItem->getGlowLevel() > 0.0f) { glower = new Glower(entityItem->getGlowLevel()); } + if (wantDebug) { + qDebug() << " EntityTreeRenderer about to render entity..."; + qDebug() << " element=" << element; + qDebug() << " entityItem=" << entityItem; + qDebug() << " entityItem->getEntityItemID()=" << entityItem->getEntityItemID(); + } entityItem->render(args); if (glower) { delete glower; diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index 7b6835f017..46d7d7eecb 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -21,36 +21,95 @@ DeleteEntityOperator::DeleteEntityOperator(EntityTree* tree, const EntityItemID& _foundCount(0), _lookingCount(0) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator(EntityTree* tree, const EntityItemID& searchEntityID).... "; + qDebug() << " tree=" << tree; + qDebug() << " _tree=" << tree; + qDebug() << "------------ DeleteEntityOperator -- BEFORE WE BEGIN: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ DeleteEntityOperator -- END the tree-------------"; + } addEntityIDToDeleteList(searchEntityID); } +DeleteEntityOperator::~DeleteEntityOperator() { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "~DeleteEntityOperator(EntityTree* tree, const EntityItemID& searchEntityID).... "; + qDebug() << "------------ ~DeleteEntityOperator -- AFTER WE'RE DONE: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ ~DeleteEntityOperator -- END the tree-------------"; + } +} + DeleteEntityOperator::DeleteEntityOperator(EntityTree* tree) : _tree(tree), _changeTime(usecTimestampNow()), _foundCount(0), _lookingCount(0) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator(EntityTree* tree).... "; + qDebug() << " tree=" << tree; + qDebug() << " _tree=" << _tree; + qDebug() << "------------ DeleteEntityOperator -- BEFORE WE BEGIN: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ DeleteEntityOperator -- END the tree-------------"; + } } void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEntityID) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEntityID).... "; + qDebug() << " _tree=" << _tree; + qDebug() << " searchEntityID=" << searchEntityID; + } + // check our tree, to determine if this entity is known EntityToDeleteDetails details; details.containingElement = _tree->getContainingElement(searchEntityID); + if (wantDebug) { + qDebug() << " details.containingElement=" << details.containingElement; + } + if (details.containingElement) { details.entity = details.containingElement->getEntityWithEntityItemID(searchEntityID); + + if (wantDebug) { + qDebug() << " details.entity=" << details.entity; + } + if (!details.entity) { //assert(false); qDebug() << "that's UNEXPECTED, we got a _containingElement, but couldn't find the oldEntity!"; } else { details.cube = details.containingElement->getAACube(); + + if (wantDebug) { + qDebug() << " details.cube=" << details.cube; + } + _entitiesToDelete << details; _lookingCount++; + + _tree->trackDeletedEntity(searchEntityID); // before deleting any entity make sure to remove it from our Mortal, Changing, and Moving lists _tree->removeEntityFromSimulationLists(searchEntityID); } } + + if (wantDebug) { + qDebug() << " _entitiesToDelete.size():" << _entitiesToDelete.size(); + } } @@ -73,6 +132,29 @@ bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(OctreeElement* el } bool DeleteEntityOperator::PreRecursion(OctreeElement* element) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator::PreRecursion().... "; + qDebug() << " element=" << element; + qDebug() << " element.AACube=" << element->getAACube(); + qDebug() << " _lookingCount=" << _lookingCount; + qDebug() << " _foundCount=" << _foundCount; + + qDebug() << " --------- list of deleting entities -----------"; + foreach(const EntityToDeleteDetails& details, _entitiesToDelete) { + + qDebug() << " ENTITY TO DELETE"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " cube=" << details.cube; + qDebug() << " containingElement=" << details.containingElement; + qDebug() << " containingElement->getAACube()=" << details.containingElement->getAACube(); + } + qDebug() << " --------- list of deleting entities -----------"; + } + + EntityTreeElement* entityTreeElement = static_cast(element); // In Pre-recursion, we're generally deciding whether or not we want to recurse this @@ -98,10 +180,20 @@ bool DeleteEntityOperator::PreRecursion(OctreeElement* element) { if (entityTreeElement == details.containingElement) { EntityItemID entityItemID = details.entity->getEntityItemID(); EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity - entityTreeElement->removeEntityItem(theEntity); // remove it from the element + bool removed = entityTreeElement->removeEntityItem(theEntity); // remove it from the element _tree->setContainingElement(entityItemID, NULL); // update or id to element lookup delete theEntity; // now actually delete the entity! _foundCount++; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator::PreRecursion().... deleting entity:" << entityItemID; + qDebug() << " details.entity=" << details.entity; + qDebug() << " theEntity=" << theEntity; + qDebug() << " called entityTreeElement->removeEntityItem(theEntity)"; + qDebug() << " removed=" << removed; + qDebug() << " called _tree->setContainingElement(entityItemID, NULL)"; + qDebug() << " called delete theEntity"; + } } } @@ -113,6 +205,27 @@ bool DeleteEntityOperator::PreRecursion(OctreeElement* element) { } bool DeleteEntityOperator::PostRecursion(OctreeElement* element) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "DeleteEntityOperator::PostRecursion().... "; + qDebug() << " element=" << element; + qDebug() << " element.AACube=" << element->getAACube(); + qDebug() << " _lookingCount=" << _lookingCount; + qDebug() << " _foundCount=" << _foundCount; + + qDebug() << " --------- list of deleting entities -----------"; + foreach(const EntityToDeleteDetails& details, _entitiesToDelete) { + + qDebug() << " DELETING ENTITY"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " cube=" << details.cube; + qDebug() << " containingElement=" << details.containingElement; + } + qDebug() << " --------- list of deleting entities -----------"; + } + // Post-recursion is the unwinding process. For this operation, while we // unwind we want to mark the path as being dirty if we changed it below. // We might have two paths, one for the old entity and one for the new entity. @@ -124,9 +237,9 @@ bool DeleteEntityOperator::PostRecursion(OctreeElement* element) { element->markWithChangedTime(); } + EntityTreeElement* entityTreeElement = static_cast(element); bool somethingPruned = entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves - bool wantDebug = false; if (somethingPruned && wantDebug) { qDebug() << "DeleteEntityOperator::PostRecursion() something pruned!!!"; } diff --git a/libraries/entities/src/DeleteEntityOperator.h b/libraries/entities/src/DeleteEntityOperator.h index 6765256aab..a85ce1a25f 100644 --- a/libraries/entities/src/DeleteEntityOperator.h +++ b/libraries/entities/src/DeleteEntityOperator.h @@ -31,6 +31,8 @@ class DeleteEntityOperator : public RecurseOctreeOperator { public: DeleteEntityOperator(EntityTree* tree); DeleteEntityOperator(EntityTree* tree, const EntityItemID& searchEntityID); + ~DeleteEntityOperator(); + void addEntityIDToDeleteList(const EntityItemID& searchEntityID); virtual bool PreRecursion(OctreeElement* element); virtual bool PostRecursion(OctreeElement* element); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 45767d9f1a..b719d0fd57 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -694,16 +694,23 @@ void EntityTree::changeEntityState(EntityItem* const entity, void EntityTree::update() { // our new strategy should be to segregate entities into three classes: // 1) stationary things that are not changing - most models - // 2) stationary things that are animating - they can be touched linearly and they don't change the tree + // 2) mortal things - these are stationary but have a lifetime - then need to be checked, + // can be touched linearly, and won't change the tree + // 2) changing things - like things animating they can be touched linearly and they don't change the tree // 3) moving things - these need to scan the tree and update accordingly - + // finally - all things that need to be deleted, can be handled on a single delete pass. + // + // TODO: theoretically we could combine the move and delete tree passes... lockForWrite(); quint64 now = usecTimestampNow(); QSet entitiesToDelete; updateChangingEntities(now, entitiesToDelete); updateMovingEntities(now, entitiesToDelete); updateMortalEntities(now, entitiesToDelete); - deleteEntities(entitiesToDelete); + + if (entitiesToDelete.size() > 0) { + deleteEntities(entitiesToDelete); + } unlock(); } @@ -749,66 +756,76 @@ void EntityTree::updateChangingEntities(quint64 now, QSet& entitie void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesToDelete) { bool wantDebug = false; - MovingEntitiesOperator moveOperator(this); - QSet entitiesBecomingStatic; - QSet entitiesBecomingMortal; - QSet entitiesBecomingChanging; + if (_movingEntities.size() > 0) { + MovingEntitiesOperator moveOperator(this); - // TODO: switch these to iterators so we can remove items that get deleted - for (int i = 0; i < _movingEntities.size(); i++) { - EntityItem* thisEntity = _movingEntities[i]; + QSet entitiesBecomingStatic; + QSet entitiesBecomingMortal; + QSet entitiesBecomingChanging; - // always check to see if the lifetime has expired, for immortal entities this is always false - if (thisEntity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); - entitiesToDelete << thisEntity->getEntityItemID(); - entitiesBecomingStatic << thisEntity; - } else { - AACube oldCube = thisEntity->getAACube(); - thisEntity->update(now); - AACube newCube = thisEntity->getAACube(); - - // check to see if this movement has sent the entity outside of the domain. - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); - if (!domainBounds.touches(newCube)) { - if (wantDebug) { - qDebug() << "The entity " << thisEntity->getEntityItemID() << " moved outside of the domain. Delete it."; - } + // TODO: switch these to iterators so we can remove items that get deleted + for (int i = 0; i < _movingEntities.size(); i++) { + EntityItem* thisEntity = _movingEntities[i]; + + // always check to see if the lifetime has expired, for immortal entities this is always false + if (thisEntity->lifetimeHasExpired()) { + qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); entitiesToDelete << thisEntity->getEntityItemID(); entitiesBecomingStatic << thisEntity; } else { + AACube oldCube = thisEntity->getAACube(); + thisEntity->update(now); + AACube newCube = thisEntity->getAACube(); if (wantDebug) { - qDebug() << "EntityTree::update() thisEntity=" << thisEntity; - qDebug() << " oldCube=" << oldCube; - qDebug() << " newCube=" << newCube; + qDebug() << "MOVING entity " << thisEntity->getEntityItemID(); + qDebug() << " oldCube:" << oldCube; + qDebug() << " newCube:" << newCube; + } + + // check to see if this movement has sent the entity outside of the domain. + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + if (!domainBounds.touches(newCube)) { + if (wantDebug) { + qDebug() << "The entity moved outside of the domain. Delete it."; + qDebug() << " entity=" << thisEntity; + qDebug() << " entityID=" << thisEntity->getEntityItemID(); } - - moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube); - - // check to see if this entity is no longer moving - EntityItem::SimulationState newState = thisEntity->getSimulationState(); - if (newState == EntityItem::Changing) { - entitiesBecomingChanging << thisEntity; - } else if (newState == EntityItem::Mortal) { - entitiesBecomingMortal << thisEntity; - } else if (newState == EntityItem::Static) { + entitiesToDelete << thisEntity->getEntityItemID(); entitiesBecomingStatic << thisEntity; + } else { + if (wantDebug) { + qDebug() << " ACTUALLY MOVING IT"; + } + + moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube); + + // check to see if this entity is no longer moving + EntityItem::SimulationState newState = thisEntity->getSimulationState(); + if (newState == EntityItem::Changing) { + entitiesBecomingChanging << thisEntity; + } else if (newState == EntityItem::Mortal) { + entitiesBecomingMortal << thisEntity; + } else if (newState == EntityItem::Static) { + entitiesBecomingStatic << thisEntity; + } } } } - } - recurseTreeWithOperator(&moveOperator); + if (moveOperator.hasMovingEntities()) { + recurseTreeWithOperator(&moveOperator); + } - // change state for any entities that were moving but are now either static, mortal, or changing - foreach(EntityItem* entity, entitiesBecomingStatic) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Static); - } - foreach(EntityItem* entity, entitiesBecomingMortal) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Mortal); - } - foreach(EntityItem* entity, entitiesBecomingChanging) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Changing); + // change state for any entities that were moving but are now either static, mortal, or changing + foreach(EntityItem* entity, entitiesBecomingStatic) { + changeEntityState(entity, EntityItem::Moving, EntityItem::Static); + } + foreach(EntityItem* entity, entitiesBecomingMortal) { + changeEntityState(entity, EntityItem::Moving, EntityItem::Mortal); + } + foreach(EntityItem* entity, entitiesBecomingChanging) { + changeEntityState(entity, EntityItem::Moving, EntityItem::Changing); + } } } @@ -1196,6 +1213,7 @@ public: bool DebugOperator::PreRecursion(OctreeElement* element) { EntityTreeElement* entityTreeElement = static_cast(element); + qDebug() << "EntityTreeElement [" << entityTreeElement << "]"; entityTreeElement->debugDump(); return true; } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index d66716c678..2c4e861c73 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -54,8 +54,6 @@ EntityTreeElement* EntityTreeElement::addChildAtIndex(int index) { OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { - - bool wantDebug = false; if (wantDebug) { qDebug() << "EntityTreeElement::appendElementData()"; @@ -600,17 +598,8 @@ bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { return foundEntity; } -bool EntityTreeElement::removeEntityItem(const EntityItem* entity) { - bool foundEntity = false; - uint16_t numberOfEntities = _entityItems->size(); - for (uint16_t i = 0; i < numberOfEntities; i++) { - if ((*_entityItems)[i] == entity) { - foundEntity = true; - _entityItems->removeAt(i); - break; - } - } - return foundEntity; +bool EntityTreeElement::removeEntityItem(EntityItem* entity) { + return _entityItems->removeAll(entity) > 0; } @@ -666,7 +655,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int EntityItem::SimulationState oldState = entityItem->getSimulationState(); bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); EntityItem::SimulationState newState = entityItem->getSimulationState(); - _myTree->changeEntityState(entityItem, oldState, newState); + if (oldState != newState) { + _myTree->changeEntityState(entityItem, oldState, newState); + } bool bestFitAfter = bestFitEntityBounds(entityItem); if (bestFitBefore != bestFitAfter) { @@ -675,19 +666,20 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int // This is the case where the entity existed, and is in some element in our tree... if (currentContainingElement != this) { currentContainingElement->removeEntityItem(entityItem); - this->addEntityItem(entityItem); + addEntityItem(entityItem); _myTree->setContainingElement(entityItemID, this); } } } } else { entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); - if (entityItem) { bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); addEntityItem(entityItem); // add this new entity to this elements entities - _myTree->setContainingElement(entityItem->getEntityItemID(), this); + _myTree->setContainingElement(entityItemID, this); newEntity = true; + EntityItem::SimulationState newState = entityItem->getSimulationState(); + _myTree->changeEntityState(entityItem, EntityItem::Static, newState); } } // Move the buffer forward to read more entities diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index f64b972ae1..2e9697abc7 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -143,7 +143,7 @@ public: void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityWithEntityItemID(const EntityItemID& id); - bool removeEntityItem(const EntityItem* entity); + bool removeEntityItem(EntityItem* entity); bool containsEntityBounds(const EntityItem* entity) const; bool bestFitEntityBounds(const EntityItem* entity) const; diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index 823c3099d3..bd78005de9 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -22,8 +22,31 @@ MovingEntitiesOperator::MovingEntitiesOperator(EntityTree* tree) : _foundNewCount(0), _lookingCount(0) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "MovingEntitiesOperator..... *********** start here!"; + qDebug() << " tree=" << tree; + qDebug() << " _tree=" << _tree; + + qDebug() << "------------ MovingEntitiesOperator -- BEFORE WE BEGIN: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ MovingEntitiesOperator -- END the tree-------------"; + } } +MovingEntitiesOperator::~MovingEntitiesOperator() { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "~MovingEntitiesOperator().... "; + qDebug() << "------------ ~MovingEntitiesOperator -- AFTER WE'RE DONE: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ ~MovingEntitiesOperator -- END the tree-------------"; + } +} + + void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube) { // check our tree, to determine if this entity is known EntityToMoveDetails details; @@ -33,6 +56,7 @@ void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACub details.newFound = false; details.oldCube = oldCube; details.newCube = newCube; + details.newBox = newCube.clamp(0.0f, 1.0f); _entitiesToMove << details; _lookingCount++; } @@ -56,6 +80,32 @@ bool MovingEntitiesOperator::shouldRecurseSubTree(OctreeElement* element) { } bool MovingEntitiesOperator::PreRecursion(OctreeElement* element) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "MovingEntitiesOperator::PreRecursion().... "; + qDebug() << " element=" << element; + qDebug() << " element.AACube=" << element->getAACube(); + qDebug() << " _lookingCount=" << _lookingCount; + qDebug() << " _foundNewCount=" << _foundNewCount; + qDebug() << " _foundOldCount=" << _foundOldCount; + + qDebug() << " --------- list of moving entities -----------"; + foreach(const EntityToMoveDetails& details, _entitiesToMove) { + + qDebug() << " MOVING ENTITY"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " oldCube=" << details.oldCube; + qDebug() << " newCube=" << details.newCube; + qDebug() << " newBox=" << details.newBox; + qDebug() << " oldContainingElement=" << details.oldContainingElement; + qDebug() << " oldFound=" << details.oldFound; + qDebug() << " newFound=" << details.newFound; + } + qDebug() << " --------- list of moving entities -----------"; + } + EntityTreeElement* entityTreeElement = static_cast(element); // In Pre-recursion, we're generally deciding whether or not we want to recurse this @@ -68,34 +118,97 @@ bool MovingEntitiesOperator::PreRecursion(OctreeElement* element) { // and the new entity. bool keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount); - + + if (wantDebug) { + qDebug() << " keepSearching=" << keepSearching; + qDebug() << " shouldRecurseSubTree(element)=" << shouldRecurseSubTree(element); + } + // If we haven't yet found all the entities, and this sub tree contains at least one of our // entities, then we need to keep searching. if (keepSearching && shouldRecurseSubTree(element)) { + if (wantDebug) { + qDebug() << " PROCESSING MOVE ON THIS ELEMENT"; + } + // check against each of our search entities + + qDebug() << " --------- PROCESSING list of moving entities -----------"; foreach(const EntityToMoveDetails& details, _entitiesToMove) { + qDebug() << " PROCESSING --- MOVING ENTITY"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " oldCube=" << details.oldCube; + qDebug() << " newCube=" << details.newCube; + qDebug() << " newBox=" << details.newBox; + qDebug() << " oldContainingElement=" << details.oldContainingElement; + qDebug() << " oldFound=" << details.oldFound; + qDebug() << " newFound=" << details.newFound; + + if (!details.oldFound) { + qDebug() << " THIS ENTITY'S OLD LOCATION HAS NOT BEEN FOUND... "; + } + + if (entityTreeElement == details.oldContainingElement) { + qDebug() << " THIS ELEMENT IS THE ENTITY'S OLD LOCATION... "; + } + // If this is one of the old elements we're looking for, then ask it to remove the old entity if (!details.oldFound && entityTreeElement == details.oldContainingElement) { - EntityItemID entityItemID = details.entity->getEntityItemID(); - entityTreeElement->removeEntityWithEntityItemID(entityItemID); - //qDebug() << "removing entityItem from element... entityItemID=" << entityItemID << "entityTreeElement=" << entityTreeElement; + qDebug() << " PROCESSING REMOVE ENTITY FROM OLD ELEMENT <<<<<<<<<<<<<"; + + entityTreeElement->removeEntityItem(details.entity); + qDebug() << "removing entityItem from element... "; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " entityTreeElement=" << entityTreeElement; + qDebug() << " element=" << element; + qDebug() << " element->getAACube()=" << element->getAACube(); + _foundOldCount++; //details.oldFound = true; // TODO: would be nice to add this optimization } // If this element is the best fit for the new bounds of this entity then add the entity to the element + bool bestFitCube = entityTreeElement->bestFitBounds(details.newCube); + bool bestFitBox = entityTreeElement->bestFitBounds(details.newBox); + qDebug() << " bestFitCube=" << bestFitCube; + qDebug() << " bestFitBox=" << bestFitBox; + if (bestFitCube != bestFitBox) { + qDebug() << " WHOA!!!! bestFitCube != bestFitBox!!!!"; + } + + if (!details.newFound) { + qDebug() << " THIS ENTITY'S NEW LOCATION HAS NOT BEEN FOUND... "; + } + + if (entityTreeElement->bestFitBounds(details.newCube)) { + qDebug() << " THIS ELEMENT IS THE ENTITY'S BEST FIT NEW LOCATION... "; + } + if (!details.newFound && entityTreeElement->bestFitBounds(details.newCube)) { + + qDebug() << " PROCESSING ADD ENTITY TO NEW ELEMENT <<<<<<<<<<<<<"; + + EntityItemID entityItemID = details.entity->getEntityItemID(); + qDebug() << "adding entityItem to element..."; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << entityItemID; + qDebug() << " entityTreeElement=" << entityTreeElement; + qDebug() << " element=" << element; + qDebug() << " element->getAACube()=" << element->getAACube(); entityTreeElement->addEntityItem(details.entity); _tree->setContainingElement(entityItemID, entityTreeElement); - //qDebug() << "adding entityItem to element... entityItemID=" << entityItemID << "entityTreeElement=" << entityTreeElement; + _foundNewCount++; //details.newFound = true; // TODO: would be nice to add this optimization } } + qDebug() << " --------- DONE PROCESSING list of moving entities -----------"; // if we haven't found all of our search for entities, then keep looking keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount); @@ -105,6 +218,33 @@ bool MovingEntitiesOperator::PreRecursion(OctreeElement* element) { } bool MovingEntitiesOperator::PostRecursion(OctreeElement* element) { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "MovingEntitiesOperator::PostRecursion().... "; + qDebug() << " element=" << element; + qDebug() << " element.AACube=" << element->getAACube(); + qDebug() << " _lookingCount=" << _lookingCount; + qDebug() << " _foundNewCount=" << _foundNewCount; + qDebug() << " _foundOldCount=" << _foundOldCount; + + qDebug() << " --------- list of moving entities -----------"; + foreach(const EntityToMoveDetails& details, _entitiesToMove) { + + qDebug() << " MOVING ENTITY"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " oldCube=" << details.oldCube; + qDebug() << " newCube=" << details.newCube; + qDebug() << " newBox=" << details.newBox; + qDebug() << " oldContainingElement=" << details.oldContainingElement; + qDebug() << " oldFound=" << details.oldFound; + qDebug() << " newFound=" << details.newFound; + } + qDebug() << " --------- list of moving entities -----------"; + + } + // Post-recursion is the unwinding process. For this operation, while we // unwind we want to mark the path as being dirty if we changed it below. // We might have two paths, one for the old entity and one for the new entity. @@ -118,7 +258,6 @@ bool MovingEntitiesOperator::PostRecursion(OctreeElement* element) { EntityTreeElement* entityTreeElement = static_cast(element); bool somethingPruned = entityTreeElement->pruneChildren(); // take this opportunity to prune any empty leaves - bool wantDebug = false; if (somethingPruned && wantDebug) { qDebug() << "MovingEntitiesOperator::PostRecursion() something pruned!!!"; } @@ -131,8 +270,27 @@ OctreeElement* MovingEntitiesOperator::PossiblyCreateChildAt(OctreeElement* elem if (wantDebug) { qDebug() << "MovingEntitiesOperator::PossiblyCreateChildAt().... "; - qDebug() << " _foundNewCount=" << _foundNewCount; + qDebug() << " element=" << element; + qDebug() << " element.AACube=" << element->getAACube(); + qDebug() << " childIndex=" << childIndex; qDebug() << " _lookingCount=" << _lookingCount; + qDebug() << " _foundNewCount=" << _foundNewCount; + qDebug() << " _foundOldCount=" << _foundOldCount; + + qDebug() << " --------- list of moving entities -----------"; + foreach(const EntityToMoveDetails& details, _entitiesToMove) { + + qDebug() << " MOVING ENTITY"; + qDebug() << " entity=" << details.entity; + qDebug() << " entityItemID=" << details.entity->getEntityItemID(); + qDebug() << " oldCube=" << details.oldCube; + qDebug() << " newCube=" << details.newCube; + qDebug() << " newBox=" << details.newBox; + qDebug() << " oldContainingElement=" << details.oldContainingElement; + qDebug() << " oldFound=" << details.oldFound; + qDebug() << " newFound=" << details.newFound; + } + qDebug() << " --------- list of moving entities -----------"; } // If we're getting called, it's because there was no child element at this index while recursing. @@ -145,25 +303,37 @@ OctreeElement* MovingEntitiesOperator::PossiblyCreateChildAt(OctreeElement* elem foreach(const EntityToMoveDetails& details, _entitiesToMove) { EntityTreeElement* entityTreeElement = static_cast(element); - bool thisElementIsBestFit = entityTreeElement->bestFitBounds(details.newCube); if (wantDebug) { - qDebug() << " thisElementIsBestFit=" << thisElementIsBestFit; + bool thisElementIsBestFitCube = entityTreeElement->bestFitBounds(details.newCube); + bool thisElementIsBestFitBox = entityTreeElement->bestFitBounds(details.newBox); + qDebug() << " thisElementIsBestFitCube=" << thisElementIsBestFitCube; + qDebug() << " thisElementIsBestFitBox=" << thisElementIsBestFitBox; qDebug() << " details.newCube=" << details.newCube; + qDebug() << " details.newBox=" << details.newBox; + qDebug() << " element=" << element; qDebug() << " element->getAACube()=" << element->getAACube(); } // if the scale of our desired cube is smaller than our children, then consider making a child - if (details.newCube.getScale() <= childElementScale) { - //qDebug() << " calling element->getMyChildContaining(details.newCube); ---------"; - int indexOfChildContainingNewEntity = element->getMyChildContaining(details.newCube); - //qDebug() << " called element->getMyChildContaining(details.newCube); ---------"; + if (details.newBox.getLargestDimension() <= childElementScale) { + + int indexOfChildContainingNewEntity = element->getMyChildContaining(details.newBox); + if (wantDebug) { + qDebug() << " called element->getMyChildContaining(details.newBox); ---------"; + qDebug() << " childIndex=" << childIndex; + qDebug() << " indexOfChildContainingNewEntity=" << indexOfChildContainingNewEntity; + } // If the childIndex we were asked if we wanted to create contains this newCube, // then we will create this branch and continue. We can exit this loop immediately // because if we need this branch for any one entity then it doesn't matter if it's // needed for more entities. if (childIndex == indexOfChildContainingNewEntity) { - return element->addChildAtIndex(childIndex); + OctreeElement* newChild = element->addChildAtIndex(childIndex); + if (wantDebug) { + qDebug() << " CREATING NEW CHILD --- childIndex=" << childIndex << "newChild=" << newChild; + } + return newChild; } } } diff --git a/libraries/entities/src/MovingEntitiesOperator.h b/libraries/entities/src/MovingEntitiesOperator.h index cafcc9b0d8..09e349392c 100644 --- a/libraries/entities/src/MovingEntitiesOperator.h +++ b/libraries/entities/src/MovingEntitiesOperator.h @@ -17,6 +17,7 @@ public: EntityItem* entity; AACube oldCube; AACube newCube; + AABox newBox; EntityTreeElement* oldContainingElement; bool oldFound; bool newFound; @@ -33,10 +34,13 @@ inline bool operator==(const EntityToMoveDetails& a, const EntityToMoveDetails& class MovingEntitiesOperator : public RecurseOctreeOperator { public: MovingEntitiesOperator(EntityTree* tree); + ~MovingEntitiesOperator(); + void addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube); virtual bool PreRecursion(OctreeElement* element); virtual bool PostRecursion(OctreeElement* element); virtual OctreeElement* PossiblyCreateChildAt(OctreeElement* element, int childIndex); + bool hasMovingEntities() const { return _entitiesToMove.size() > 0; } private: EntityTree* _tree; QSet _entitiesToMove; diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index e4b3ed3cdc..377e1b8566 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -28,6 +28,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, _foundOld(false), _foundNew(false), _removeOld(false), + _dontMove(false), // assume we'll be moving _changeTime(usecTimestampNow()), _oldEntityCube(), _newEntityCube() @@ -42,6 +43,10 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, if (wantDebug) { qDebug() << "UpdateEntityOperator...."; + qDebug() << " _existingEntity=" << _existingEntity; + qDebug() << " _entityItemID=" << _entityItemID; + qDebug() << " _containingElement=" << _containingElement; + qDebug() << " _containingElement->getAACube()=" << _containingElement->getAACube(); qDebug() << " _oldEntityCube=" << _oldEntityCube; qDebug() << " _oldEntityBox=" << _oldEntityBox; qDebug() << " _existingEntity->getPosition()=" << _existingEntity->getPosition() * (float)TREE_SCALE << "in meters"; @@ -64,16 +69,30 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, // If our new properties don't have bounds details (no change to position, etc) or if this containing element would // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update - bool oldElementBestFit = _containingElement->bestFitBounds(_properties); - + + // if we don't have bounds properties, then use our old clamped box to determine best fit + if (!_properties.containsBoundsProperties()) { + oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); + } + if (wantDebug) { + qDebug() << " _properties.containsBoundsProperties()=" << _properties.containsBoundsProperties(); qDebug() << " oldElementBestFit=" << oldElementBestFit; } - if (!_properties.containsBoundsProperties() || oldElementBestFit) { + // For some reason we've seen a case where the original containing element isn't a best fit for the old properties + // in this case we want to move it, even if the properties haven't changed. + if (!_properties.containsBoundsProperties() && !oldElementBestFit) { + _newEntityCube = _oldEntityCube; + _removeOld = true; // our properties are going to move us, so remember this for later processing + if (wantDebug) { + qDebug() << " old element is NOT the best element even though props not changing <<<<<"; + } + } else if (!_properties.containsBoundsProperties() || oldElementBestFit) { _foundOld = true; _newEntityCube = _oldEntityCube; + _dontMove = true; if (wantDebug) { qDebug() << " old element is best element <<<<<"; } @@ -94,12 +113,37 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, qDebug() << " _newEntityCube=" << _newEntityCube; qDebug() << " _newEntityBox=" << _newEntityBox; qDebug() << " _removeOld=" << _removeOld; + qDebug() << " _containingElement->bestFitBounds(_properties)=" << _containingElement->bestFitBounds(_properties); + qDebug() << " _containingElement->bestFitBounds(_oldEntityCube)=" << _containingElement->bestFitBounds(_oldEntityCube); + qDebug() << " _containingElement->bestFitBounds(_oldEntityBox)=" << _containingElement->bestFitBounds(_oldEntityBox); + qDebug() << " _containingElement->bestFitBounds(_newEntityCube)=" << _containingElement->bestFitBounds(_newEntityCube); + qDebug() << " _containingElement->bestFitBounds(_newEntityBox)=" << _containingElement->bestFitBounds(_newEntityBox); + + qDebug() << "------------ UpdateEntityOperator -- BEFORE WE BEGIN: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ UpdateEntityOperator -- END the tree-------------"; } } + +UpdateEntityOperator::~UpdateEntityOperator() { + bool wantDebug = false; + + if (wantDebug) { + qDebug() << "~UpdateEntityOperator().... "; + qDebug() << "------------ ~UpdateEntityOperator -- AFTER WE'RE DONE: the tree[" << _tree << "] -------------"; + _tree->dumpTree(); + qDebug() << "------------ ~UpdateEntityOperator -- END the tree-------------"; + } +} + + // does this entity tree element contain the old entity bool UpdateEntityOperator::subTreeContainsOldEntity(OctreeElement* element) { - bool elementContainsOldBox = element->getAACube().contains(_oldEntityBox); + + // We've found cases where the old entity might be placed in an element that is not actually the best fit + // so when we're searching the tree for the old element, we use the known cube for the known containing element + bool elementContainsOldBox = element->getAACube().contains(_containingElementCube); /* bool elementContainsOldCube = element->getAACube().contains(_oldEntityCube); @@ -177,6 +221,11 @@ bool UpdateEntityOperator::PreRecursion(OctreeElement* element) { if (wantDebug) { qDebug() << " OLD BRANCH...."; + qDebug() << " _dontMove=" << _dontMove; + qDebug() << " _removeOld=" << _removeOld; + qDebug() << " entityTreeElement == _containingElement=" << (entityTreeElement == _containingElement); + qDebug() << " entityTreeElement->bestFitBounds(_newEntityBox)=" << entityTreeElement->bestFitBounds(_newEntityBox); + qDebug() << " _containingElement->bestFitBounds(_newEntityBox)=" << _containingElement->bestFitBounds(_newEntityBox); } @@ -223,6 +272,11 @@ bool UpdateEntityOperator::PreRecursion(OctreeElement* element) { if (wantDebug) { qDebug() << " NEW BRANCH...."; + qDebug() << " _dontMove=" << _dontMove; + qDebug() << " _removeOld=" << _removeOld; + qDebug() << " entityTreeElement == _containingElement=" << (entityTreeElement == _containingElement); + qDebug() << " entityTreeElement->bestFitBounds(_newEntityBox)=" << entityTreeElement->bestFitBounds(_newEntityBox); + qDebug() << " _containingElement->bestFitBounds(_newEntityBox)=" << _containingElement->bestFitBounds(_newEntityBox); } @@ -231,9 +285,13 @@ bool UpdateEntityOperator::PreRecursion(OctreeElement* element) { if (wantDebug) { qDebug() << " NEW BRANCH.... BEST FIT"; + qDebug() << " _dontMove=" << _dontMove; + qDebug() << " _removeOld=" << _removeOld; + qDebug() << " entityTreeElement == _containingElement=" << (entityTreeElement == _containingElement); } // if we are the existing containing element, then we can just do the update of the entity properties + if (entityTreeElement == _containingElement) { assert(!_removeOld); // We shouldn't be in a remove old case and also be the new best fit @@ -246,6 +304,15 @@ bool UpdateEntityOperator::PreRecursion(OctreeElement* element) { } else { // otherwise, this is an add case. + + if (wantDebug) { + qDebug() << "UpdateEntityOperator()... UPDATING EXISTING ENTITY -- PLACE IN NEW ELEMENT"; + qDebug() << " element=" << entityTreeElement; + qDebug() << " element cube=" << entityTreeElement->getAACube(); + qDebug() << " existingEntity=" << _existingEntity; + qDebug() << " entityItemID=" << _entityItemID; + } + entityTreeElement->addEntityItem(_existingEntity); _existingEntity->setProperties(_properties); // still need to update the properties! _tree->setContainingElement(_entityItemID, entityTreeElement); @@ -324,37 +391,38 @@ bool UpdateEntityOperator::PostRecursion(OctreeElement* element) { } OctreeElement* UpdateEntityOperator::PossiblyCreateChildAt(OctreeElement* element, int childIndex) { - - bool wantDebug = false; - if (wantDebug) { - qDebug() << "UpdateEntityOperator::PossiblyCreateChildAt()..."; - qDebug() << " element=" << element; - qDebug() << " element->getAACube()=" << element->getAACube(); - qDebug() << " childIndex=" << childIndex; - - bool subtreeContainsOld = subTreeContainsOldEntity(element); - bool subtreeContainsNew = subTreeContainsNewEntity(element); - - qDebug() << " subtreeContainsOld=" << subtreeContainsOld; - qDebug() << " subtreeContainsNew=" << subtreeContainsNew; - - qDebug() << " _foundOld=" << _foundOld; - qDebug() << " _foundNew=" << _foundNew; - qDebug() << " _removeOld=" << _removeOld; - - qDebug() << " _containingElement=" << _containingElement; - qDebug() << " _containingElementCube=" << _containingElementCube; - - qDebug() << " _properties.getPosition()=" << _properties.getPosition() << "in meters"; - qDebug() << " _properties.getRadius()=" << _properties.getRadius() << "in meters"; - qDebug() << " _newEntityCube=" << _newEntityCube; - qDebug() << " _newEntityBox=" << _newEntityBox; - } - // If we're getting called, it's because there was no child element at this index while recursing. // We only care if this happens while still searching for the new entity location. // Check to see if if (!_foundNew) { + + bool wantDebug = false; + if (wantDebug) { + qDebug() << "UpdateEntityOperator::PossiblyCreateChildAt()..."; + qDebug() << " element=" << element; + qDebug() << " element->getAACube()=" << element->getAACube(); + qDebug() << " childIndex=" << childIndex; + + bool subtreeContainsOld = subTreeContainsOldEntity(element); + bool subtreeContainsNew = subTreeContainsNewEntity(element); + + qDebug() << " subtreeContainsOld=" << subtreeContainsOld; + qDebug() << " subtreeContainsNew=" << subtreeContainsNew; + + qDebug() << " _foundOld=" << _foundOld; + qDebug() << " _foundNew=" << _foundNew; + qDebug() << " _removeOld=" << _removeOld; + + qDebug() << " _containingElement=" << _containingElement; + qDebug() << " _containingElementCube=" << _containingElementCube; + + qDebug() << " _properties.getPosition()=" << _properties.getPosition() << "in meters"; + qDebug() << " _properties.getRadius()=" << _properties.getRadius() << "in meters"; + qDebug() << " _newEntityCube=" << _newEntityCube; + qDebug() << " _newEntityBox=" << _newEntityBox; + } + + float childElementScale = element->getScale() / 2.0f; // all of our children will be half our scale // Note: because the entity's bounds might have been clamped to the domain. We want to check if the diff --git a/libraries/entities/src/UpdateEntityOperator.h b/libraries/entities/src/UpdateEntityOperator.h index 2d4cc09b77..56e418a6d6 100644 --- a/libraries/entities/src/UpdateEntityOperator.h +++ b/libraries/entities/src/UpdateEntityOperator.h @@ -16,6 +16,7 @@ class UpdateEntityOperator : public RecurseOctreeOperator { public: UpdateEntityOperator(EntityTree* tree, EntityTreeElement* containingElement, EntityItem* existingEntity, const EntityItemProperties& properties); + ~UpdateEntityOperator(); virtual bool PreRecursion(OctreeElement* element); virtual bool PostRecursion(OctreeElement* element); @@ -30,6 +31,7 @@ private: bool _foundOld; bool _foundNew; bool _removeOld; + bool _dontMove; quint64 _changeTime; AACube _oldEntityCube; diff --git a/libraries/entities/src/todo.txt b/libraries/entities/src/todo.txt index 82d26ecdb1..9e7cd36052 100644 --- a/libraries/entities/src/todo.txt +++ b/libraries/entities/src/todo.txt @@ -1,7 +1,5 @@ // REQUIRED: - 1) crash on rendering of element that flies off the domain??? - 2) Test file save load for case where two siblings have more than MTU amount of data. I wonder if the fact that file save doesn't include the extra exists bits will break something. @@ -9,15 +7,12 @@ 4) test animation again... - 5) PROP_VISIBLE - - 6) PROP_SCRIPT - 7) some jutter with moving entities -- I think this might only happen with lots of models in an element or in view this may be related to issue 13 below - 8) Look into why non-changed octree cells are being resent when editing an entity + 8) Look into why non-changed octree cells are being resent when editing an entity -- + this is probably because we're marking the trees as dirty -- but we probably can not send entitys that haven't changed 9) Verify pruning logic... The old code used the update loop to handle pruning elements from the tree - do we need this? @@ -27,11 +22,23 @@ 11) quickly do some edits... then change domains... watch the entities continue to exist in new domain and move around. -- verify this happens in old code, if so... move to "nice to have" + 12) Double check operators for these problems: + MovingEntitiesOperator has these issues: + - does the same pruning/reallocating issue as the old UpdateEntityOperator + - doesn't used clamped boxes for best fit tests... so could be problematic // NICE TO HAVE: + + why is _entityItems a pointer? why not just make it a member of EntityTreeElement.... + 1) EnterEntity/LeaveEntity JS messages + 2) PROP_VISIBLE + + 3) PROP_SCRIPT + + 2) update and verify all particle examples to use new entity features 3) implement support for requestedProperties in appendEntityData() that only include CHANGED properties for the viewer... @@ -200,6 +207,13 @@ // -- 3) the containing element got pruned // -- 4) the NEW branch is traversed and PossiblyCreateChildAt creates a new element -- THAT HAS SAME POINTER AS CONTAINING ELEMENT!!!! // -- 5) the logic for recursedelement == _containingElement gets confused +// SOLVED -- 47) crash on rendering of element that flies off the domain +// -- root cause was an UpdateEntityOperator that left two copies of the entity in the tree +// this was caused by the original element that the entity was in (according to the server) +// was not actually the best fit. it's not clear how this happened, but we can protect +// against it in the update operator if it does happen again. This made update operator more robust. +// SOLVED -- 48) server and client definitley not adding entities to proper update lists on load... only on change... + ---------------- properties ----------------- Base properties...