first cut at properly working lifetime

This commit is contained in:
ZappoMan 2014-08-28 13:46:54 -07:00
parent 2f90df04ee
commit 76c77d6994
16 changed files with 359 additions and 76 deletions

View file

@ -1200,17 +1200,23 @@ function Tooltip() {
text += "Roll: " + angles.z.toFixed(this.decimals) + "\n"
text += "Scale: " + 2 * properties.radius.toFixed(this.decimals) + "\n"
text += "ID: " + properties.id + "\n"
text += "Model URL: " + properties.modelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints && properties.sittingPoints.length > 0) {
text += properties.sittingPoints.length + " Sitting points: "
for (var i = 0; i < properties.sittingPoints.length; ++i) {
text += properties.sittingPoints[i].name + " "
if (properties.type == "Model") {
text += "Model URL: " + properties.modelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints && properties.sittingPoints.length > 0) {
text += properties.sittingPoints.length + " Sitting points: "
for (var i = 0; i < properties.sittingPoints.length; ++i) {
text += properties.sittingPoints[i].name + " "
}
} else {
text += "No sitting points" + "\n"
}
} else {
text += "No sitting points"
}
if (properties.lifetime > -1) {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
Overlays.editOverlay(this.textOverlay, { text: text });
@ -1684,6 +1690,8 @@ function handeMenuEvent(menuItem){
array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
if (properties.type == "Box") {
array.push({ label: "Red:", value: properties.color.red });
@ -1718,6 +1726,7 @@ function handeMenuEvent(menuItem){
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
properties.lifetime = array[index++].value; // give ourselves that many more seconds
if (properties.type == "Box") {
properties.color.red = array[index++].value;

View file

@ -29,7 +29,9 @@ EntityItem* RenderableModelEntityItem::factory(const EntityItemID& entityID, con
}
RenderableModelEntityItem::~RenderableModelEntityItem() {
delete _model;
// TODO: we can't just delete this because we may be on the wrong thread.
//delete _model;
_model = NULL;
};

View file

@ -47,6 +47,8 @@ void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEnt
_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);
}
}
}
@ -94,15 +96,11 @@ bool DeleteEntityOperator::PreRecursion(OctreeElement* element) {
// If this is the element we're looking for, then ask it to remove the old entity
// and we can stop searching.
if (entityTreeElement == details.containingElement) {
EntityTreeElement* containingElement = _tree->getContainingElement(details.entity->getEntityItemID());
// This is a good place to delete it!!!
EntityItemID entityItemID = details.entity->getEntityItemID();
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID);
entityTreeElement->removeEntityItem(theEntity);
_tree->setContainingElement(entityItemID, NULL);
delete theEntity; // now actually delete it!
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity
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++;
}
}

View file

@ -44,6 +44,7 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
//uint64_t now = usecTimestampNow();
_lastEdited = 0;
_lastUpdated = 0;
_created = 0; // TODO: when do we actually want to make this "now"
_position = glm::vec3(0,0,0);
_radius = 0;
@ -61,6 +62,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert
_type = EntityTypes::Unknown;
_lastEdited = 0;
_lastUpdated = 0;
_created = properties.getCreated();
initFromEntityItemID(entityItemID);
setProperties(properties, true); // force copy
}
@ -120,6 +122,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
bool successIDFits = false;
bool successTypeFits = false;
bool successCreatedFits = false;
bool successLastEditedFits = false;
bool successLastUpdatedFits = false;
bool successPropertyFlagsFits = false;
@ -132,8 +135,22 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
if (successIDFits) {
successTypeFits = packetData->appendValue(encodedType);
}
if (successTypeFits) {
successCreatedFits = packetData->appendValue(_created);
qDebug() << "EntityItem::appendEntityData()...";
qDebug() << " entityID=" << getEntityItemID();
if (_created == USE_EXISTING_CREATED_TIME) {
qDebug() << " _created = USE_EXISTING_CREATED_TIME";
} else if (_created == UNKNOWN_CREATED_TIME) {
qDebug() << " _created = UNKNOWN_CREATED_TIME";
} else {
qDebug() << " _created=" << _created;
qDebug() << " getAge()=" << getAge() << " - " << formatSecondsElapsed(getAge());
}
}
if (successCreatedFits) {
successLastEditedFits = packetData->appendValue(lastEdited);
}
if (successLastEditedFits) {
@ -147,7 +164,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags);
}
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData->getUncompressedByteOffset();
@ -426,9 +443,35 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
_type = (EntityTypes::EntityType)type;
bool overwriteLocalData = true; // assume the new content overwrites our local data
// _created
quint64 createdFromBuffer = 0;
memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer));
dataAt += sizeof(createdFromBuffer);
bytesRead += sizeof(createdFromBuffer);
createdFromBuffer -= clockSkew;
_created = createdFromBuffer; // TODO: do we ever want to discard this???
qDebug() << "EntityItem::readEntityDataFromBuffer()...";
qDebug() << " entityID=" << getEntityItemID();
if (_created == USE_EXISTING_CREATED_TIME) {
qDebug() << " _created = USE_EXISTING_CREATED_TIME";
} else if (_created == UNKNOWN_CREATED_TIME) {
qDebug() << " _created = UNKNOWN_CREATED_TIME";
} else {
qDebug() << " _created=" << _created;
qDebug() << " getAge()=" << getAge() << " - " << formatSecondsElapsed(getAge());
}
/*
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading entity " << getEntityItemID() << " from buffer, _created =" << _created
<< " age=" << getAge() << "seconds - " << ageAsString;
*/
quint64 lastEditedFromBuffer = 0;
// TODO: we could make this encoded as a delta from _created
// _lastEdited
memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer));
dataAt += sizeof(lastEditedFromBuffer);
@ -621,7 +664,6 @@ bool EntityItem::isRestingOnSurface() const {
&& _gravity.y < 0.0f;
}
void EntityItem::update(const quint64& updateTime) {
bool wantDebug = false;
float timeElapsed = (float)(updateTime - _lastUpdated) / (float)(USECS_PER_SECOND);
@ -631,13 +673,6 @@ void EntityItem::update(const quint64& updateTime) {
qDebug() << "********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated;
}
if (isMortal()) {
if (getAge() > getLifetime()) {
qDebug() << "Lifetime has expired... WHAT TO DO??? getAge()=" << getAge() << "getLifetime()=" << getLifetime();
//setShouldDie(true); // TODO get rid of this stuff!!
}
}
if (hasVelocity() || hasGravity()) {
glm::vec3 position = getPosition();
glm::vec3 velocity = getVelocity();
@ -706,7 +741,7 @@ void EntityItem::update(const quint64& updateTime) {
}
}
EntityItem::SimuationState EntityItem::getSimulationState() const {
EntityItem::SimulationState EntityItem::getSimulationState() const {
bool wantDebug = false;
if (wantDebug) {
@ -725,9 +760,9 @@ EntityItem::SimuationState EntityItem::getSimulationState() const {
}
if (isMortal()) {
if (wantDebug) {
qDebug() << " return EntityItem::Changing;";
qDebug() << " return EntityItem::Mortal;";
}
return EntityItem::Changing;
return EntityItem::Mortal;
}
if (wantDebug) {
qDebug() << " return EntityItem::Static;";
@ -735,6 +770,18 @@ EntityItem::SimuationState EntityItem::getSimulationState() const {
return EntityItem::Static;
}
bool EntityItem::lifetimeHasExpired() const {
bool wantDebug = false;
if (wantDebug) {
qDebug() << "EntityItem::lifetimeHasExpired()...";
qDebug() << " isMortal()=" << isMortal();
qDebug() << " getAge()=" << getAge();
qDebug() << " getLifetime()=" << getLifetime();
}
return isMortal() && (getAge() > getLifetime());
}
void EntityItem::copyChangedProperties(const EntityItem& other) {
*this = other;
}
@ -775,6 +822,16 @@ EntityItemProperties EntityItem::getProperties() const {
bool EntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = false;
// handle the setting of created timestamps for the basic new entity case
if (forceCopy) {
if (properties.getCreated() == UNKNOWN_CREATED_TIME) {
_created = usecTimestampNow();
} else if (properties.getCreated() != USE_EXISTING_CREATED_TIME) {
_created = properties.getCreated();
}
}
if (properties._positionChanged || forceCopy) {
setPosition(properties._position / (float) TREE_SCALE);
somethingChanged = true;
@ -814,6 +871,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
somethingChanged = true;
}
if (properties._lifetimeChanged || forceCopy) {
qDebug() << "setLifetime().... properties._lifetime=" << properties._lifetime;
setLifetime(properties._lifetime);
somethingChanged = true;
}

View file

@ -101,13 +101,14 @@ public:
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew);
virtual void update(const quint64& now);
typedef enum SimuationState_t {
typedef enum SimulationState_t {
Static,
Mortal,
Changing,
Moving
} SimuationState;
} SimulationState;
virtual SimuationState getSimulationState() const;
virtual SimulationState getSimulationState() const;
virtual void debugDump() const;
// similar to assignment/copy, but it handles keeping lifetime accurate
@ -167,6 +168,7 @@ public:
/// age of this entity in seconds
float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; }
bool lifetimeHasExpired() const;
// position, size, and bounds related helpers
float getSize() const { return _radius * 2.0f; } /// get maximum dimension in domain scale units (0.0 - 1.0)

View file

@ -24,6 +24,7 @@ EntityItemProperties::EntityItemProperties() :
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
_lastEdited(0),
_created(UNKNOWN_CREATED_TIME),
_type(EntityTypes::Unknown),
_position(0),
@ -167,6 +168,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
properties.setProperty("gravity", gravity);
properties.setProperty("damping", _damping);
properties.setProperty("lifetime", _lifetime);
properties.setProperty("age", getAge()); // gettable, but not settable
properties.setProperty("ageAsText", formatSecondsElapsed(getAge())); // gettable, but not settable
properties.setProperty("script", _script);
QScriptValue color = xColorToScriptValue(engine, _color);
@ -496,15 +499,18 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
// timestamp for clock skew
quint64 lastEdited = properties.getLastEdited();
bool successLastEditedFits = packetData.appendValue(lastEdited);
bool successIDFits = packetData.appendValue(encodedID);
if (isNewEntityItem && successIDFits) {
successIDFits = packetData.appendValue(encodedToken);
}
bool successTypeFits = packetData.appendValue(encodedType);
// NOTE: We intentionally do not send "created" times in edit messages. This is because:
// 1) if the edit is to an existing entity, the created time can not be changed
// 2) if the edit is to a new entity, the created time is the last edited time
// TODO: Should we get rid of this in this in edit packets, since this has to always be 0?
bool successLastUpdatedFits = packetData.appendValue(encodedUpdateDelta);
@ -514,7 +520,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
bool successPropertyFlagsFits = packetData.appendValue(encodedPropertyFlags);
int propertyCount = 0;
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
&& successLastUpdatedFits && successPropertyFlagsFits;
int startOfEntityItemData = packetData.getUncompressedByteOffset();
@ -883,6 +889,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
processedBytes += sizeof(lastEdited);
properties.setLastEdited(lastEdited);
// NOTE: We intentionally do not send "created" times in edit messages. This is because:
// 1) if the edit is to an existing entity, the created time can not be changed
// 2) if the edit is to a new entity, the created time is the last edited time
// encoded id
QByteArray encodedID((const char*)dataAt, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid editID = QUuid::fromRfc4122(encodedID);
@ -917,11 +927,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
valid = true;
// created time is lastEdited time
properties.setCreated(lastEdited);
} else {
entityID.id = editID;
entityID.creatorTokenID = UNKNOWN_ENTITY_TOKEN;
entityID.isKnownID = true;
valid = true;
// created time is lastEdited time
properties.setCreated(USE_EXISTING_CREATED_TIME);
}
// Entity Type...
@ -932,7 +947,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
encodedType = typeCoder; // determine true bytesToRead
dataAt += encodedType.size();
processedBytes += encodedType.size();
// Update Delta - when was this item updated relative to last edit... this really should be 0
// TODO: Should we get rid of this in this in edit packets, since this has to always be 0?
// TODO: do properties need to handle lastupdated???

View file

@ -39,6 +39,9 @@ const glm::quat ENTITY_DEFAULT_ROTATION;
const QString ENTITY_DEFAULT_ANIMATION_URL("");
const float ENTITY_DEFAULT_ANIMATION_FPS = 30.0f;
const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1);
const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2);
// PropertyFlags support
enum EntityPropertyList {
PROP_PAGED_PROPERTY,
@ -138,6 +141,11 @@ public:
float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity
void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } /// set the lifetime in seconds for the entity
float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; }
quint64 getCreated() const { return _created; }
void setCreated(quint64 usecTime) { _created = usecTime; }
bool hasCreatedTime() const { return (_created != UNKNOWN_CREATED_TIME); }
// NOTE: how do we handle _defaultSettings???
bool containsBoundsProperties() const { return (_positionChanged || _radiusChanged); }
@ -196,11 +204,12 @@ public:
void markAllChanged();
private:
void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; }
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
QUuid _id;
bool _idSet;
quint64 _lastEdited;
quint64 _created;
EntityTypes::EntityType _type;
glm::vec3 _position;

View file

@ -102,13 +102,13 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
}
// check to see if we need to simulate this entity...
EntityItem::SimuationState oldState = existingEntity->getSimulationState();
EntityItem::SimulationState oldState = existingEntity->getSimulationState();
UpdateEntityOperator theOperator(this, containingElement, existingEntity, properties);
recurseTreeWithOperator(&theOperator);
_isDirty = true;
EntityItem::SimuationState newState = existingEntity->getSimulationState();
EntityItem::SimulationState newState = existingEntity->getSimulationState();
changeEntityState(existingEntity, oldState, newState);
containingElement = getContainingElement(entityID);
@ -196,7 +196,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
DeleteEntityOperator theOperator(this);
foreach(const EntityItemID& entityID, entityIDs) {
// First, look for the existing entity in the tree..
// tell our delete operator about this entityID
theOperator.addEntityIDToDeleteList(entityID);
}
@ -215,6 +215,32 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
}
}
void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) {
EntityItem* theEntity = findEntityByEntityItemID(entityID);
if (theEntity) {
// make sure to remove it from any of our simulation lists
EntityItem::SimulationState theState = theEntity->getSimulationState();
switch (theState) {
case EntityItem::Changing:
_changingEntities.removeAll(theEntity);
break;
case EntityItem::Moving:
_movingEntities.removeAll(theEntity);
break;
case EntityItem::Mortal:
_mortalEntities.removeAll(theEntity);
break;
default:
break;
}
}
}
/// This method is used to find and fix entity IDs that are shifting from creator token based to known ID based entity IDs.
/// This should only be used on a client side (viewing) tree. The typical usage is that a local editor has been creating
/// entities in the local tree, those entities have creatorToken based entity IDs. But those entity edits are also sent up to
@ -521,7 +547,6 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
EntityItemID entityItemID;
EntityItemProperties properties;
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength,
processedBytes, entityItemID, properties);
@ -560,7 +585,6 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
qDebug() << "EntityTree::processEditPacketData() ... "
"AFTER assignEntityID()... entityItemID=" << entityItemID;
}
EntityItem* newEntity = addEntity(entityItemID, properties);
if (newEntity) {
notifyNewlyCreatedEntity(*newEntity, senderNode);
@ -605,7 +629,8 @@ void EntityTree::removeNewlyCreatedHook(NewlyCreatedEntityHook* hook) {
}
void EntityTree::changeEntityState(EntityItem* const entity, EntityItem::SimuationState oldState, EntityItem::SimuationState newState) {
void EntityTree::changeEntityState(EntityItem* const entity,
EntityItem::SimulationState oldState, EntityItem::SimulationState newState) {
bool wantDebug = false;
@ -617,6 +642,7 @@ void EntityTree::changeEntityState(EntityItem* const entity, EntityItem::Simuati
qDebug() << "EntityTree::changeEntityState() BEFORE....";
qDebug() << " _changingEntities:" << _changingEntities;
qDebug() << " _movingEntities:" << _movingEntities;
qDebug() << " _mortalEntities:" << _mortalEntities;
}
// TODO: can we short circuit this if the state isn't changing?
@ -628,6 +654,11 @@ void EntityTree::changeEntityState(EntityItem* const entity, EntityItem::Simuati
case EntityItem::Moving:
_movingEntities.removeAll(entity);
break;
case EntityItem::Mortal:
_mortalEntities.removeAll(entity);
break;
default:
break;
}
@ -641,6 +672,11 @@ void EntityTree::changeEntityState(EntityItem* const entity, EntityItem::Simuati
case EntityItem::Moving:
_movingEntities.push_back(entity);
break;
case EntityItem::Mortal:
_mortalEntities.push_back(entity);
break;
default:
break;
}
@ -650,14 +686,12 @@ void EntityTree::changeEntityState(EntityItem* const entity, EntityItem::Simuati
qDebug() << "EntityTree::changeEntityState() AFTER....";
qDebug() << " _changingEntities:" << _changingEntities;
qDebug() << " _movingEntities:" << _movingEntities;
qDebug() << " _mortalEntities:" << _mortalEntities;
}
}
void EntityTree::update() {
bool wantDebug = false;
// 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
@ -665,51 +699,147 @@ void EntityTree::update() {
lockForWrite();
quint64 now = usecTimestampNow();
QSet<EntityItemID> entitiesToDelete;
updateChangingEntities(now, entitiesToDelete);
updateMovingEntities(now, entitiesToDelete);
updateMortalEntities(now, entitiesToDelete);
deleteEntities(entitiesToDelete);
unlock();
}
void EntityTree::updateChangingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
QSet<EntityItem*> entitiesBecomingStatic;
QSet<EntityItem*> entitiesBecomingMortal;
QSet<EntityItem*> entitiesBecomingMoving;
// TODO: switch these to iterators so we can remove items that get deleted
for (int i = 0; i < _changingEntities.size(); i++) {
EntityItem* thisEntity = _changingEntities[i];
thisEntity->update(now);
// 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 {
// check to see if this entity is no longer moving
EntityItem::SimulationState newState = thisEntity->getSimulationState();
if (newState == EntityItem::Static) {
entitiesBecomingStatic << thisEntity;
} else if (newState == EntityItem::Mortal) {
entitiesBecomingMortal << thisEntity;
} else if (newState == EntityItem::Moving) {
entitiesBecomingMoving << thisEntity;
}
}
}
// change state for any entities that were changing but are now either static, mortal, or moving
foreach(EntityItem* entity, entitiesBecomingStatic) {
changeEntityState(entity, EntityItem::Changing, EntityItem::Static);
}
foreach(EntityItem* entity, entitiesBecomingMortal) {
changeEntityState(entity, EntityItem::Changing, EntityItem::Mortal);
}
foreach(EntityItem* entity, entitiesBecomingMoving) {
changeEntityState(entity, EntityItem::Changing, EntityItem::Moving);
}
}
void EntityTree::updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
bool wantDebug = false;
MovingEntitiesOperator moveOperator(this);
QSet<EntityItem*> entitiesBecomingStatic;
QSet<EntityItem*> entitiesBecomingMortal;
QSet<EntityItem*> entitiesBecomingChanging;
// 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];
AACube oldCube = thisEntity->getAACube();
thisEntity->update(now);
AACube newCube = thisEntity->getAACube();
if (wantDebug) {
qDebug() << "EntityTree::update() thisEntity=" << thisEntity;
qDebug() << " oldCube=" << oldCube;
qDebug() << " newCube=" << newCube;
}
moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube);
// check to see if this entity is no longer moving
EntityItem::SimuationState newState = thisEntity->getSimulationState();
if (newState == EntityItem::Changing) {
entitiesBecomingChanging << thisEntity;
} else if (newState == EntityItem::Static) {
// 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;
}
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);
// for any and all entities that were moving but are now either static or changing
// change their state accordingly
// 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);
}
}
unlock();
void EntityTree::updateMortalEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
QSet<EntityItem*> entitiesBecomingStatic;
QSet<EntityItem*> entitiesBecomingChanging;
QSet<EntityItem*> entitiesBecomingMoving;
// TODO: switch these to iterators so we can remove items that get deleted
for (int i = 0; i < _mortalEntities.size(); i++) {
EntityItem* thisEntity = _mortalEntities[i];
thisEntity->update(now);
// 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 {
// check to see if this entity is no longer moving
EntityItem::SimulationState newState = thisEntity->getSimulationState();
if (newState == EntityItem::Static) {
entitiesBecomingStatic << thisEntity;
} else if (newState == EntityItem::Changing) {
entitiesBecomingChanging << thisEntity;
} else if (newState == EntityItem::Moving) {
entitiesBecomingMoving << thisEntity;
}
}
}
// change state for any entities that were mortal but are now either static, changing, or moving
foreach(EntityItem* entity, entitiesBecomingStatic) {
changeEntityState(entity, EntityItem::Mortal, EntityItem::Static);
}
foreach(EntityItem* entity, entitiesBecomingChanging) {
changeEntityState(entity, EntityItem::Mortal, EntityItem::Changing);
}
foreach(EntityItem* entity, entitiesBecomingMoving) {
changeEntityState(entity, EntityItem::Mortal, EntityItem::Moving);
}
}

View file

@ -75,6 +75,7 @@ public:
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
void deleteEntity(const EntityItemID& entityID);
void deleteEntities(QSet<EntityItemID> entityIDs);
void removeEntityFromSimulationLists(const EntityItemID& entityID);
const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius);
EntityItem* findEntityByID(const QUuid& id);
@ -123,12 +124,17 @@ public:
void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z);
void changeEntityState(EntityItem* const entity, EntityItem::SimuationState oldState, EntityItem::SimuationState newState);
void changeEntityState(EntityItem* const entity,
EntityItem::SimulationState oldState, EntityItem::SimulationState newState);
void trackDeletedEntity(const EntityItemID& entityID);
private:
void updateChangingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
void updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
void updateMortalEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);
@ -147,6 +153,7 @@ private:
QList<EntityItem*> _movingEntities; // entities that are moving as part of update
QList<EntityItem*> _changingEntities; // entities that are changing (like animating), but not moving
QList<EntityItem*> _mortalEntities; // entities that are mortal (have lifetime), but not moving or changing
};
#endif // hifi_EntityTree_h

View file

@ -663,9 +663,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (entityItem) {
bool bestFitBefore = bestFitEntityBounds(entityItem);
EntityTreeElement* currentContainingElement = _myTree->getContainingElement(entityItemID);
EntityItem::SimuationState oldState = entityItem->getSimulationState();
EntityItem::SimulationState oldState = entityItem->getSimulationState();
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
EntityItem::SimuationState newState = entityItem->getSimulationState();
EntityItem::SimulationState newState = entityItem->getSimulationState();
_myTree->changeEntityState(entityItem, oldState, newState);
bool bestFitAfter = bestFitEntityBounds(entityItem);

View file

@ -84,7 +84,15 @@ EntityItem* EntityTypes::constructEntityItem(EntityType entityType, const Entity
factory = _factories[entityType];
}
if (factory) {
newEntityItem = factory(entityID, properties);
// NOTE: if someone attempts to create an entity with properties that do not include a proper "created" time
// then set the created time to now
if (!properties.hasCreatedTime()) {
EntityItemProperties mutableProperties = properties;
mutableProperties.setCreated(usecTimestampNow());
newEntityItem = factory(entityID, mutableProperties);
} else {
newEntityItem = factory(entityID, properties);
}
}
return newEntityItem;
}
@ -139,6 +147,7 @@ EntityItem* EntityTypes::constructEntityItem(const unsigned char* data, int byte
EntityItemID tempEntityID(actualID);
EntityItemProperties tempProperties;
tempProperties.setCreated(usecTimestampNow()); // this is temporary...
if (wantDebug) {
qDebug() << "EntityTypes::constructEntityItem(data, bytesToRead).... NEW BITSTREAM!!! entityType=" << entityType;

View file

@ -232,6 +232,11 @@ int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* dat
dataAt += sizeof(_lastEdited);
bytesRead += sizeof(_lastEdited);
_lastEdited -= clockSkew;
_created = _lastEdited; // NOTE: old models didn't have age or created time, assume their last edit was a create
QString ageAsString = formatSecondsElapsed(getAge());
qDebug() << "Loading old model file, _created = _lastEdited =" << _created
<< " age=" << getAge() << "seconds - " << ageAsString;
// radius
memcpy(&_radius, dataAt, sizeof(_radius));
@ -532,8 +537,8 @@ bool ModelEntityItem::isAnimatingSomething() const {
!getAnimationURL().isEmpty();
}
EntityItem::SimuationState ModelEntityItem::getSimulationState() const {
EntityItem::SimuationState baseClassState = EntityItem::getSimulationState();
EntityItem::SimulationState ModelEntityItem::getSimulationState() const {
EntityItem::SimulationState baseClassState = EntityItem::getSimulationState();
// if the base class is static, then consider our animation state, and upgrade to changing if
// we are animating. If the base class has a higher simulation state than static, then

View file

@ -44,7 +44,7 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
virtual void update(const quint64& now);
virtual SimuationState getSimulationState() const;
virtual SimulationState getSimulationState() const;
virtual void debugDump() const;

View file

@ -1,7 +1,25 @@
// REQUIRED:
10) Lifetime??
1) Lifetime, Age, Created
* support created timestamp in all entities
* handle old model files with no created time stamp
* add age, ageAsText properties for scripting
* make sure that entities that expire are correctly deleted safely
* make sure that deleting a mortal but not yet dead entity works
* make sure that newly "viewed" entities are correctly added to our simulation lists: moral, changing, moving
2) Crash in delete model - deleting the geometry...
I think this is a cross threading issue... delete Model needs to be on same thread that created it.
-- change Model* RenderableModelEntityItem::getModel(); to call a new method on EntityTreeRenderer::newModel();
-- change RenderableModelEntityItem::~RenderableModelEntityItem() to call a new method on EntityTreeRenderer::deleteModel(Model*);
3) Look into why non-changed octree cells are being resent when editing an entity
7) Test file save load for case where two siblings have more than MTU amount of data. I wonder if the fact that file save

View file

@ -571,3 +571,23 @@ QString formatUsecTime(float usecs, int prec) {
}
return result;
}
QString formatSecondsElapsed(float seconds) {
QString result;
const float SECONDS_IN_DAY = 60.0f * 60.0f * 24.0f;
if (seconds > SECONDS_IN_DAY) {
float days = floor(seconds / SECONDS_IN_DAY);
float rest = seconds - (days * SECONDS_IN_DAY);
result = QString::number((int)days);
if (days > 1.0f) {
result += " days ";
} else {
result += " day ";
}
result += QDateTime::fromTime_t(rest).toUTC().toString("h 'hours' m 'minutes' s 'seconds'");
} else {
result = QDateTime::fromTime_t(seconds).toUTC().toString("h 'hours' m 'minutes' s 'seconds'");
}
return result;
}

View file

@ -121,6 +121,7 @@ bool isBetween(int64_t value, int64_t max, int64_t min);
bool isNaN(float value);
QString formatUsecTime(float usecs, int prec = 3);
QString formatSecondsElapsed(float seconds);
#endif // hifi_SharedUtil_h