Merge pull request #5976 from sethalves/fix-action-deletes

Fix action deletion/addition when more than one interface are involved
This commit is contained in:
Andrew Meadows 2015-10-05 12:31:01 -07:00
commit 1ead20683c
6 changed files with 94 additions and 53 deletions

View file

@ -87,15 +87,18 @@ function getTag() {
}
function entityIsGrabbedByOther(entityID) {
// by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*.
var actionIDs = Entities.getActionIDs(entityID);
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
var actionID = actionIDs[actionIndex];
var actionArguments = Entities.getActionArguments(entityID, actionID);
var tag = actionArguments["tag"];
if (tag == getTag()) {
// we see a grab-*uuid* shaped tag, but it's our tag, so that's okay.
continue;
}
if (tag.slice(0, 5) == "grab-") {
// we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it.
return true;
}
}
@ -164,6 +167,12 @@ function MyController(hand, triggerAction) {
}
};
this.setState = function(newState) {
// print("STATE: " + this.state + " --> " + newState);
this.state = newState;
}
this.lineOn = function(closePoint, farPoint, color) {
// draw a line
if (this.pointer === null) {
@ -217,14 +226,14 @@ function MyController(hand, triggerAction) {
this.off = function() {
if (this.triggerSmoothedSqueezed()) {
this.state = STATE_SEARCHING;
this.setState(STATE_SEARCHING);
return;
}
}
this.search = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
@ -235,6 +244,8 @@ function MyController(hand, triggerAction) {
direction: Quat.getUp(this.getHandRotation())
};
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
var defaultGrabbableData = {
grabbable: true
};
@ -250,20 +261,20 @@ function MyController(hand, triggerAction) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, defaultGrabbableData);
if (grabbableData.grabbable === false) {
this.grabbedEntity = null;
return;
}
if (intersectionDistance < NEAR_PICK_MAX_DISTANCE) {
// the hand is very close to the intersected object. go into close-grabbing mode.
this.state = STATE_NEAR_GRABBING;
this.setState(STATE_NEAR_GRABBING);
} else {
// don't allow two people to distance grab the same object
if (entityIsGrabbedByOther(intersection.entityID)) {
// don't allow two people to distance grab the same object
return;
this.grabbedEntity = null;
} else {
// the hand is far from the intersected object. go into distance-holding mode
this.setState(STATE_DISTANCE_HOLDING);
}
// the hand is far from the intersected object. go into distance-holding mode
this.state = STATE_DISTANCE_HOLDING;
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
}
} else {
// forward ray test failed, try sphere test.
@ -287,15 +298,14 @@ function MyController(hand, triggerAction) {
}
}
if (this.grabbedEntity === null) {
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
// this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
} else if (props.locked === 0 && props.collisionsWillMove === 1) {
this.state = STATE_NEAR_GRABBING;
this.setState(STATE_NEAR_GRABBING);
} else if (props.collisionsWillMove === 0) {
// We have grabbed a non-physical object, so we want to trigger a non-colliding event as opposed to a grab event
this.state = STATE_NEAR_GRABBING_NON_COLLIDING;
this.setState(STATE_NEAR_GRABBING_NON_COLLIDING);
}
}
};
this.distanceHolding = function() {
@ -325,7 +335,7 @@ function MyController(hand, triggerAction) {
}
if (this.actionID !== null) {
this.state = STATE_CONTINUE_DISTANCE_HOLDING;
this.setState(STATE_CONTINUE_DISTANCE_HOLDING);
this.activateEntity(this.grabbedEntity);
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
@ -342,7 +352,7 @@ function MyController(hand, triggerAction) {
this.continueDistanceHolding = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
@ -400,7 +410,7 @@ function MyController(hand, triggerAction) {
var now = Date.now();
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
this.computeReleaseVelocity(deltaPosition, deltaTime, false);
this.currentObjectPosition = newObjectPosition;
this.currentObjectTime = now;
@ -423,7 +433,7 @@ function MyController(hand, triggerAction) {
this.nearGrabbing = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
@ -454,7 +464,7 @@ function MyController(hand, triggerAction) {
if (this.actionID === NULL_ACTION_ID) {
this.actionID = null;
} else {
this.state = STATE_CONTINUE_NEAR_GRABBING;
this.setState(STATE_CONTINUE_NEAR_GRABBING);
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else {
@ -471,7 +481,7 @@ function MyController(hand, triggerAction) {
this.continueNearGrabbing = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
@ -498,7 +508,7 @@ function MyController(hand, triggerAction) {
this.nearGrabbingNonColliding = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
if (this.hand === RIGHT_HAND) {
@ -507,12 +517,12 @@ function MyController(hand, triggerAction) {
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
}
Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding");
this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING;
this.setState(STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING);
};
this.continueNearGrabbingNonColliding = function() {
if (this.triggerSmoothedReleased()) {
this.state = STATE_RELEASE;
this.setState(STATE_RELEASE);
return;
}
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrabbingNonColliding");
@ -623,7 +633,7 @@ function MyController(hand, triggerAction) {
this.grabbedVelocity = ZERO_VEC;
this.grabbedEntity = null;
this.actionID = null;
this.state = STATE_OFF;
this.setState(STATE_OFF);
};
this.cleanup = function() {

View file

@ -45,26 +45,25 @@ var IDENTITY_QUAT = {
z: 0,
w: 0
};
var ACTION_LIFETIME = 120; // 2 minutes
var ACTION_LIFETIME = 10; // seconds
function getTag() {
return "grab-" + MyAvatar.sessionUUID;
}
function entityIsGrabbedByOther(entityID) {
// by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*.
var actionIDs = Entities.getActionIDs(entityID);
var actionIndex;
var actionID;
var actionArguments;
var tag;
for (actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
actionID = actionIDs[actionIndex];
actionArguments = Entities.getActionArguments(entityID, actionID);
tag = actionArguments.tag;
if (tag === getTag()) {
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
var actionID = actionIDs[actionIndex];
var actionArguments = Entities.getActionArguments(entityID, actionID);
var tag = actionArguments["tag"];
if (tag == getTag()) {
// we see a grab-*uuid* shaped tag, but it's our tag, so that's okay.
continue;
}
if (tag.slice(0, 5) === "grab-") {
if (tag.slice(0, 5) == "grab-") {
// we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it.
return true;
}
}
@ -530,4 +529,4 @@ Controller.mousePressEvent.connect(pressEvent);
Controller.mouseMoveEvent.connect(moveEvent);
Controller.mouseReleaseEvent.connect(releaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);

View file

@ -65,9 +65,9 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt
if (action) {
action->deserialize(data);
}
if (action->lifetimeIsOver()) {
return nullptr;
if (action->lifetimeIsOver()) {
return nullptr;
}
}
return action;

View file

@ -48,6 +48,8 @@ public:
virtual bool lifetimeIsOver() { return false; }
bool locallyAddedButNotYetReceived = false;
protected:
virtual glm::vec3 getPosition() = 0;
virtual void setPosition(glm::vec3 position) = 0;

View file

@ -34,6 +34,7 @@
bool EntityItem::_sendPhysicsUpdates = true;
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
EntityItem::EntityItem(const EntityItemID& entityItemID) :
_type(EntityTypes::Unknown),
@ -1505,6 +1506,8 @@ bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer act
result = addActionInternal(simulation, action);
if (!result) {
removeActionInternal(action->getID());
} else {
action->locallyAddedButNotYetReceived = true;
}
});
@ -1525,7 +1528,8 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi
simulation->addAction(action);
bool success;
QByteArray newDataCache = serializeActions(success);
QByteArray newDataCache;
serializeActions(success, newDataCache);
if (success) {
_allActionsDataCache = newDataCache;
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
@ -1546,7 +1550,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI
success = action->updateArguments(arguments);
if (success) {
_allActionsDataCache = serializeActions(success);
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
} else {
qDebug() << "EntityItem::updateAction failed";
@ -1566,6 +1570,7 @@ bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionI
bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* simulation) {
assertWriteLocked();
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
if (_objectActions.contains(actionID)) {
if (!simulation) {
EntityTreePointer entityTree = _element ? _element->getTree() : nullptr;
@ -1581,7 +1586,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s
}
bool success = true;
_allActionsDataCache = serializeActions(success);
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
return success;
}
@ -1618,6 +1623,8 @@ void EntityItem::deserializeActions() {
void EntityItem::deserializeActionsInternal() {
assertWriteLocked();
quint64 now = usecTimestampNow();
if (!_element) {
return;
}
@ -1643,6 +1650,10 @@ void EntityItem::deserializeActionsInternal() {
QUuid actionID;
serializedActionStream >> actionType;
serializedActionStream >> actionID;
if (_previouslyDeletedActions.contains(actionID)) {
continue;
}
updated << actionID;
if (_objectActions.contains(actionID)) {
@ -1650,14 +1661,14 @@ void EntityItem::deserializeActionsInternal() {
// TODO: make sure types match? there isn't currently a way to
// change the type of an existing action.
action->deserialize(serializedAction);
action->locallyAddedButNotYetReceived = false;
} else {
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
// EntityItemPointer entity = entityTree->findEntityByEntityItemID(_id, false);
EntityItemPointer entity = shared_from_this();
EntityActionPointer action = actionFactory->factoryBA(entity, serializedAction);
if (action) {
entity->addActionInternal(simulation, action);
action->locallyAddedButNotYetReceived = false;
}
}
}
@ -1667,11 +1678,27 @@ void EntityItem::deserializeActionsInternal() {
while (i != _objectActions.end()) {
QUuid id = i.key();
if (!updated.contains(id)) {
_actionsToRemove << id;
EntityActionPointer action = i.value();
// if we've just added this action, don't remove it due to lack of mention in an incoming packet.
if (! action->locallyAddedButNotYetReceived) {
_actionsToRemove << id;
_previouslyDeletedActions.insert(id, now);
}
}
i++;
}
// trim down _previouslyDeletedActions
QMutableHashIterator<QUuid, quint64> _previouslyDeletedIter(_previouslyDeletedActions);
while (_previouslyDeletedIter.hasNext()) {
_previouslyDeletedIter.next();
if (now - _previouslyDeletedIter.value() > _rememberDeletedActionTime) {
_previouslyDeletedActions.remove(_previouslyDeletedIter.key());
}
}
_actionDataDirty = true;
return;
}
@ -1697,13 +1724,13 @@ void EntityItem::setActionDataInternal(QByteArray actionData) {
deserializeActionsInternal();
}
QByteArray EntityItem::serializeActions(bool& success) const {
void EntityItem::serializeActions(bool& success, QByteArray& result) const {
assertLocked();
QByteArray result;
if (_objectActions.size() == 0) {
success = true;
return QByteArray();
result.clear();
return;
}
QVector<QByteArray> serializedActions;
@ -1721,21 +1748,20 @@ QByteArray EntityItem::serializeActions(bool& success) const {
if (result.size() >= _maxActionsDataSize) {
success = false;
return result;
return;
}
success = true;
return result;
return;
}
const QByteArray EntityItem::getActionDataInternal() const {
if (_actionDataDirty) {
bool success;
QByteArray newDataCache = serializeActions(success);
serializeActions(success, _allActionsDataCache);
if (success) {
_allActionsDataCache = newDataCache;
_actionDataDirty = false;
}
_actionDataDirty = false;
}
return _allActionsDataCache;
}

View file

@ -508,17 +508,21 @@ protected:
bool addActionInternal(EntitySimulation* simulation, EntityActionPointer action);
bool removeActionInternal(const QUuid& actionID, EntitySimulation* simulation = nullptr);
void deserializeActionsInternal();
QByteArray serializeActions(bool& success) const;
void serializeActions(bool& success, QByteArray& result) const;
QHash<QUuid, EntityActionPointer> _objectActions;
static int _maxActionsDataSize;
mutable QByteArray _allActionsDataCache;
// when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is
// ready. This means we can't find our EntityItemPointer or add the action to the simulation. These
// are used to keep track of and work around this situation.
void checkWaitingToRemove(EntitySimulation* simulation = nullptr);
mutable QSet<QUuid> _actionsToRemove;
mutable bool _actionDataDirty = false;
// _previouslyDeletedActions is used to avoid an action being re-added due to server round-trip lag
static quint64 _rememberDeletedActionTime;
mutable QHash<QUuid, quint64> _previouslyDeletedActions;
};
#endif // hifi_EntityItem_h