示例#1
0
 // Constructor
 public SyncedProperty(SyncableProperties.Type property, Object initValue, long initTS, string syncID)
 {
     Property = property;
     LastUpdateValue = initValue;
     LastUpdateTimeStamp = initTS;
     LastUpdateSyncID = syncID;
 }
示例#2
0
        private Object GetPropertyValue(SceneObjectPart part, SyncableProperties.Type property)
        {
            switch (property)
            {
                ///////////////////////
                //SOP properties
                ///////////////////////
                case SyncableProperties.Type.AggregateScriptEvents:
                    return (int)part.AggregateScriptEvents;
                case SyncableProperties.Type.AllowedDrop:
                    return part.AllowedDrop;
                case SyncableProperties.Type.AngularVelocity:
                    return part.AngularVelocity;
                case SyncableProperties.Type.AttachedAvatar:
                    return part.ParentGroup.AttachedAvatar;
                case SyncableProperties.Type.AttachedPos:
                    return part.AttachedPos;
                case SyncableProperties.Type.AttachmentPoint:
                    return part.ParentGroup.AttachmentPoint;
                case SyncableProperties.Type.BaseMask:
                    return part.BaseMask;
                case SyncableProperties.Type.Category:
                    return part.Category;
                case SyncableProperties.Type.ClickAction:
                    return part.ClickAction;
                case SyncableProperties.Type.CollisionSound:
                    return part.CollisionSound;
                case SyncableProperties.Type.CollisionSoundVolume:
                    return part.CollisionSoundVolume;
                case SyncableProperties.Type.Color:
                    return PropertySerializer.SerializeColor(part.Color);
                case SyncableProperties.Type.CreationDate:
                    return part.CreationDate;
                case SyncableProperties.Type.CreatorData:
                    return part.CreatorData;
                case SyncableProperties.Type.CreatorID:
                    return part.CreatorID;
                case SyncableProperties.Type.Description:
                    return part.Description;
                case SyncableProperties.Type.EveryoneMask:
                    return part.EveryoneMask;
                case SyncableProperties.Type.Flags:
                    return (int)part.Flags;
                case SyncableProperties.Type.FolderID:
                    return part.FolderID;
                //Skip SyncableProperties.FullUpdate, which should be handled seperatedly
                case SyncableProperties.Type.GroupID:
                    return part.GroupID;
                case SyncableProperties.Type.GroupMask:
                    return part.GroupMask;
                case SyncableProperties.Type.GroupPosition:
                    return part.GroupPosition;
                case SyncableProperties.Type.InventorySerial:
                    return part.InventorySerial;
                case SyncableProperties.Type.IsAttachment:
                    return part.ParentGroup.IsAttachment;
                case SyncableProperties.Type.LastOwnerID:
                    return part.LastOwnerID;
                case SyncableProperties.Type.LinkNum:
                    return part.LinkNum;
                case SyncableProperties.Type.LocalId:
                    return part.LocalId;
                case SyncableProperties.Type.LocalFlags:
                    return (int)part.LocalFlags;
                case SyncableProperties.Type.Material:
                    return part.Material;
                case SyncableProperties.Type.MediaUrl:
                    return part.MediaUrl;
                case SyncableProperties.Type.Name:
                    return part.Name;
                case SyncableProperties.Type.NextOwnerMask:
                    return part.NextOwnerMask;
                case SyncableProperties.Type.ObjectSaleType:
                    return part.ObjectSaleType;
                case SyncableProperties.Type.OffsetPosition:
                    return part.OffsetPosition;
                case SyncableProperties.Type.OwnerID:
                    return part.OwnerID;
                case SyncableProperties.Type.OwnerMask:
                    return part.OwnerMask;
                case SyncableProperties.Type.OwnershipCost:
                    return part.OwnershipCost;
                case SyncableProperties.Type.ParticleSystem:
                    //byte[], return a cloned copy
                    return part.ParticleSystem;//.Clone()
                case SyncableProperties.Type.PassTouches:
                    return part.PassTouches;
                case SyncableProperties.Type.RotationOffset:
                    return part.RotationOffset;
                case SyncableProperties.Type.SalePrice:
                    return part.SalePrice;
                case SyncableProperties.Type.Scale:
                    return part.Scale;
                case SyncableProperties.Type.ScriptAccessPin:
                    return part.ScriptAccessPin;
                case SyncableProperties.Type.Shape:
                    return PropertySerializer.SerializeShape(part.Shape);
                case SyncableProperties.Type.SitName:
                    return part.SitName;
                case SyncableProperties.Type.SitTargetOrientation:
                    return part.SitTargetOrientation;
                case SyncableProperties.Type.SitTargetOrientationLL:
                    return part.SitTargetOrientationLL;
                case SyncableProperties.Type.SitTargetPosition:
                    return part.SitTargetPosition;
                case SyncableProperties.Type.SitTargetPositionLL:
                    return part.SitTargetPositionLL;
                case SyncableProperties.Type.SOP_Acceleration:
                    return part.Acceleration;
                case SyncableProperties.Type.Sound:
                    return part.Sound;
                case SyncableProperties.Type.TaskInventory:
                    return PropertySerializer.SerializeTaskInventory(part.TaskInventory, Scene);
                case SyncableProperties.Type.Text:
                    return part.Text;
                case SyncableProperties.Type.TextureAnimation:
                    return part.TextureAnimation;//.Clone();
                case SyncableProperties.Type.TouchName:
                    return part.TouchName;
                /*
                case SyncableProperties.Type.UpdateFlag:
                    return part.UpdateFlag;
                */
                case SyncableProperties.Type.Velocity:
                    return part.Velocity;
                case SyncableProperties.Type.VolumeDetectActive:
                    return part.VolumeDetectActive;

                ///////////////////////
                //PhysActor properties
                ///////////////////////
                case SyncableProperties.Type.Buoyancy:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Buoyancy;
                case SyncableProperties.Type.Flying:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Flying;
                case SyncableProperties.Type.Force:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Force;
                case SyncableProperties.Type.IsColliding:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.IsColliding;
                case SyncableProperties.Type.CollidingGround:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.CollidingGround;
                case SyncableProperties.Type.Kinematic:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Kinematic;
                case SyncableProperties.Type.Orientation:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Orientation;
                case SyncableProperties.Type.PA_Acceleration:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Acceleration;
                case SyncableProperties.Type.PA_Velocity:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Velocity;
                case SyncableProperties.Type.PA_TargetVelocity:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.TargetVelocity;
                case SyncableProperties.Type.Position:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Position;
                case SyncableProperties.Type.RotationalVelocity:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.RotationalVelocity;
                case SyncableProperties.Type.Size:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Size;
                case SyncableProperties.Type.Torque:
                    if (part.PhysActor == null)
                        return null;
                    return part.PhysActor.Torque;

                ///////////////////////
                //SOG properties
                ///////////////////////
                case SyncableProperties.Type.AbsolutePosition:
                    return part.ParentGroup.AbsolutePosition;
                case SyncableProperties.Type.IsSelected:
                    return part.ParentGroup.IsSelected;
            }

            //DebugLog.ErrorFormat("{0}: GetPropertyValue could not get property {1} from {2}", LogHeader, property.ToString(), part.UUID);
            return null;
        }
示例#3
0
        /// <summary>
        /// Compare the value (not "reference") of the given property. 
        /// Assumption: the caller has already checked if PhysActor exists
        /// if there are physics properties updated.
        /// If the value maintained here is different from that in SOP data,
        /// synchronize the two: 
        /// (1) if the value here has a timestamp newer than lastUpdateByLocalTS 
        /// (e.g. due to clock drifts among different sync nodes, a remote
        /// write might have a newer timestamp than the local write), 
        /// overwrite the SOP's property with the value here (effectively 
        /// disvalidate the local write operation that just happened). 
        /// (2) otherwise, copy SOP's data and update timestamp and syncID 
        /// as indicated by "lastUpdateByLocalTS" and "syncID".
        /// </summary>
        /// <param name="part"></param>
        /// <param name="property"></param>
        /// <param name="lastUpdateByLocalTS"></param>
        /// <param name="syncID"></param>
        /// <returns>Return true if the property's value maintained in this SyncInfoPrim is replaced by SOP's data.</returns>
        private bool CompareValue_UpdateByLocal(SceneObjectPart part, SyncableProperties.Type property, long lastUpdateByLocalTS, string syncID, out bool resetSceneValue)
        {
            resetSceneValue = false;

            //DebugLog.WarnFormat("[SYNC INFO PRIM] CompareValue_UpdateByLocal: Updating property {0} on part {1}", property.ToString(), part.UUID);

            SyncedProperty syncedProperty;
            CurrentlySyncedProperties.TryGetValue(property, out syncedProperty);

            // If the property does not exist yet then add it.
            if (syncedProperty == null)
            {
                Object initValue = GetPropertyValue(part, property);
                SyncedProperty syncInfo = new SyncedProperty(property, initValue, lastUpdateByLocalTS, syncID);
                CurrentlySyncedProperties.Add(property, syncInfo);
                return true;
            }

            // First, check if the value maintained here is different from that in SOP's.
            // If different, next check if the timestamp in SyncInfo is newer than lastUpdateByLocalTS;
            // if so (although ideally should not happen, but due to things likc clock not so perfectly
            // sync'ed, it might happen), overwrite SOP's value with what's maintained
            // in SyncInfo; otherwise, copy SOP's data to SyncInfo.

            switch (property)
            {
                case SyncableProperties.Type.GroupPosition:
                    return CompareAndUpdateSOPGroupPositionByLocal(part, lastUpdateByLocalTS, syncID);

                default:
                    Object value = GetPropertyValue(part, property);

                    // If both null, no update needed
                    if (syncedProperty.LastUpdateValue == null && value == null)
                        return false;

                    // If one is null and the other is not, or if they are not equal, the property was changed.
                    if ((value == null && syncedProperty.LastUpdateValue != null) ||
                        (value != null && syncedProperty.LastUpdateValue == null) ||
                        (!value.Equals(syncedProperty.LastUpdateValue)))
                    {
                        if (value != null)
                        {
                            switch (property)
                            {
                                case SyncableProperties.Type.Velocity:
                                case SyncableProperties.Type.PA_Velocity:
                                case SyncableProperties.Type.PA_TargetVelocity:
                                case SyncableProperties.Type.RotationalVelocity:
                                case SyncableProperties.Type.AngularVelocity:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        // If velocity difference is small but not zero, don't update
                                        if (partVal.ApproxEquals(lastVal, VELOCITY_TOLERANCE) && !partVal.Equals(Vector3.Zero))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.RotationOffset:
                                case SyncableProperties.Type.Orientation:
                                    {
                                        Quaternion partVal = (Quaternion)value;
                                        Quaternion lastVal = (Quaternion)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, ROTATION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.OffsetPosition:
                                case SyncableProperties.Type.AbsolutePosition:
                                case SyncableProperties.Type.Position:
                                case SyncableProperties.Type.GroupPosition:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, POSITION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                            }
                        }
                        if (property == SyncableProperties.Type.Shape)
                        {
                            //DebugLog.WarnFormat("[SYNC INFO PRIM]: SHAPES DIFFER {0} {1}", (string)value, (string)syncedProperty.LastUpdateValue);
                        }
                        // DebugLog.WarnFormat("[SYNC INFO PRIM] CompareValue_UpdateByLocal (property={0}): value != syncedProperty.LastUpdateValue", property.ToString());
                        if (lastUpdateByLocalTS >= syncedProperty.LastUpdateTimeStamp)
                        {
                            // DebugLog.WarnFormat("[SYNC INFO PRIM] CompareValue_UpdateByLocal (property={0}): TS >= lastTS (updating SyncInfo)", property.ToString());
                            syncedProperty.UpdateSyncInfoByLocal(lastUpdateByLocalTS, syncID, value);

                            // Updating either absolute position or position also requires checking for updates to group position
                            if (property == SyncableProperties.Type.AbsolutePosition || property == SyncableProperties.Type.Position)
                            {
                                CompareValue_UpdateByLocal(part, SyncableProperties.Type.GroupPosition, lastUpdateByLocalTS, syncID, out resetSceneValue);
                                resetSceneValue = false;
                            }

                            return true;
                        }
                        // DebugLog.WarnFormat("[SYNC INFO PRIM] CompareValue_UpdateByLocal (property={0}): TS < lastTS (updating SOP)", property.ToString());

                        //We'll reset the property value outside of CompareValue_UpdateByLocal
                        //SetPropertyValue(property);
                        resetSceneValue = true;
                    }
                    break;
            }
            return false;
        }
示例#4
0
 public override Object GetPropertyValue(SyncableProperties.Type property)
 {
     return GetPropertyValue((SceneObjectPart)SceneThing, property);
 }
示例#5
0
 public abstract Object GetPropertyValue(SyncableProperties.Type property);
        private void SetPropertyValue(ScenePresence sp, SyncableProperties.Type property, SyncedProperty pSyncInfo)
        {
            if (sp == null || pSyncInfo == null) return;

            Object pValue = pSyncInfo.LastUpdateValue;
            switch (property)
            {
                case SyncableProperties.Type.LocalId:
                    sp.LocalId = (uint)pValue;
                    break;
                case SyncableProperties.Type.AbsolutePosition:
                    sp.AbsolutePosition = (Vector3)pValue;
                    break;
                case SyncableProperties.Type.AgentCircuitData:
                    DebugLog.WarnFormat("{0}: Received updated AgentCircuitData. Not implemented", LogHeader);
                    break;
                case SyncableProperties.Type.ParentId:
                    uint localID = (uint)pValue;
                    if (localID == 0)
                    {
                        // DebugLog.DebugFormat("{0}: SetPropertyValue:ParentID. Standup. Input={1}", LogHeader, localID); // DEBUG DEBUG
                        sp.StandUp();
                    }
                    else
                    {
                        SceneObjectPart parentPart = Scene.GetSceneObjectPart(localID);
                        if (parentPart != null) // TODO ??
                        {
                            sp.HandleAgentRequestSit(sp.ControllingClient, sp.ControllingClient.AgentId, parentPart.UUID, Vector3.Zero);
                            // DebugLog.DebugFormat("{0}: SetPropertyValue:ParentID. SitRequest. Input={1},sp={2},newParentID={3}",
                            //                 LogHeader, localID, (string)(sp == null ? "NULL" : sp.Name), sp.ParentID); // DEBUG DEBUG
                        }
                    }
                    //sp.ParentID = (uint)pValue;
                    break;
                case SyncableProperties.Type.AgentControlFlags:
                    sp.AgentControlFlags = (uint)pValue;
                    break;
                case SyncableProperties.Type.AllowMovement:
                    sp.AllowMovement = (bool)pValue;
                    break;
                case SyncableProperties.Type.AvatarAppearance:
                    sp.Appearance.Unpack((OSDMap)pValue);
                    sp.SendAppearanceToAllOtherAgents();
                    DebugLog.DebugFormat("{0} Received updated AvatarAppearance for uuid {1}.", LogHeader, sp.UUID);
                    break;
                case SyncableProperties.Type.Animations:
                    UpdateAvatarAnimations(sp, (OSDArray)pValue);
                    break;
                case SyncableProperties.Type.Rotation:
                    sp.Rotation = (Quaternion)pValue;
                    break;
                case SyncableProperties.Type.PA_Velocity:
                    if (sp.PhysicsActor != null)
                        sp.PhysicsActor.Velocity = (Vector3)pValue;
                    break;
                case SyncableProperties.Type.RealRegion:
                    ////// NOP //////
                    break;
                case SyncableProperties.Type.PA_TargetVelocity:
                    if(sp.PhysicsActor != null)
                        sp.PhysicsActor.TargetVelocity = (Vector3)pValue;
                     break;
                case SyncableProperties.Type.Flying:
                    sp.Flying = (bool)pValue;
                    break;
                case SyncableProperties.Type.PresenceType:
                    DebugLog.WarnFormat("{0} Received updated PresenceType for uuid {1}. Not implemented", LogHeader, sp.UUID);
                    break;
                case SyncableProperties.Type.IsColliding:
                    if(sp.PhysicsActor != null)
                        sp.IsColliding = (bool)pValue;
                    break;
            }

            // When presence values are changed, we tell the simulator with an event
            GenerateAgentUpdated(sp);
        }
        // Gets the value out of the SP in local scene and returns it as an object
        private Object GetPropertyValue(ScenePresence sp, SyncableProperties.Type property)
        {
            if (sp == null)
                return null;

            switch (property)
            {
                case SyncableProperties.Type.LocalId:
                    return sp.LocalId;
                case SyncableProperties.Type.AbsolutePosition:
                    return sp.AbsolutePosition;
                case SyncableProperties.Type.AgentCircuitData:
                    return Scene.AuthenticateHandler.GetAgentCircuitData(sp.ControllingClient.CircuitCode).PackAgentCircuitData();
                case SyncableProperties.Type.ParentId:
                    return sp.ParentID;
                case SyncableProperties.Type.AgentControlFlags:
                    return sp.AgentControlFlags;
                case SyncableProperties.Type.AllowMovement:
                    return sp.AllowMovement;
                case SyncableProperties.Type.Animations:
                    return sp.Animator.Animations.ToOSDArray();
                case SyncableProperties.Type.AvatarAppearance:
                    return sp.Appearance.Pack();
                case SyncableProperties.Type.Rotation:
                    return sp.Rotation;
                case SyncableProperties.Type.PA_Velocity:
                    if (sp.PhysicsActor == null)
                        return Vector3.Zero;
                    return sp.PhysicsActor.Velocity;
                case SyncableProperties.Type.RealRegion:
                    // Always just the local scene name the avatar is in when requested locally.
                    return sp.Scene.Name;
                case SyncableProperties.Type.PA_TargetVelocity:
                    if (sp.PhysicsActor == null)
                        return Vector3.Zero;
                    return sp.PhysicsActor.TargetVelocity;
                case SyncableProperties.Type.Flying:
                    return sp.Flying;
                case SyncableProperties.Type.PresenceType:
                    return (int)sp.PresenceType;
                case SyncableProperties.Type.IsColliding:
                    return sp.IsColliding;
            }

            //DebugLog.ErrorFormat("{0}: GetPropertyValue could not get property {1} from {2}", LogHeader, property.ToString(), sp.UUID);
            return null;
        }
        /// <summary>
        /// Compare the value (not "reference") of the given property. 
        /// Assumption: the caller has already checked if PhysActor exists
        /// if there are physics properties updated.
        /// If the value maintained here is different from that in SP data,
        /// synchronize the two: 
        /// (1) if the cached value has a timestamp newer than lastUpdateByLocalTS 
        /// overwrite the SP's property with the cached value (effectively 
        /// undoing the local write operation that just happened). 
        /// (2) otherwise, copy SP's data and update timestamp and syncID 
        /// as indicated by "lastUpdateByLocalTS" and "syncID".
        /// </summary>
        /// <param name="sp"></param>
        /// <param name="property"></param>
        /// <param name="lastUpdateByLocalTS"></param>
        /// <param name="syncID"></param>
        /// <returns>Return true if the property's value maintained in this 
        /// RegionSyncModule is replaced by SP's data.</returns>
        private bool CompareValue_UpdateByLocal(ScenePresence sp, SyncableProperties.Type property, long lastUpdateByLocalTS, string syncID)
        {
            //DebugLog.WarnFormat("[SYNC INFO PRESENCE] CompareValue_UpdateByLocal: Updating property {0} on sp {1}", property.ToString(), sp.UUID);
            // Check to see if this property is in the sync cache for this object.
            // If not, add it and initialize value to value in ScenePresence.
            bool ret = false;

            SyncedProperty syncedProperty;
            CurrentlySyncedProperties.TryGetValue(property, out syncedProperty);

            // If synced property is not in cache, add it now
            if (syncedProperty == null)
            {
                Object initValue = GetPropertyValue(sp, property);
                if (initValue != null)
                {
                    CurrentlySyncedProperties.Add(property, new SyncedProperty(property, initValue, lastUpdateByLocalTS, syncID));
                    ret = true;
                }
                return ret;
            }

            // First, check if the value maintained here is different from that in SP's.
            // If different, next check if the timestamp in SyncInfo is newer than lastUpdateByLocalTS;
            // if so (although ideally should not happen, but due to things likc clock not so perfectly
            // sync'ed, it might happen), overwrite SP's value with what's maintained
            // in SyncInfo; otherwise, copy SP's data to SyncInfo.
            Object value = GetPropertyValue(sp, property);

            // If both null, no update needed
            if (syncedProperty.LastUpdateValue == null && value == null)
                return false;

            switch (property)
            {
                default:
                    // If one is null and the other is not, or if the references are different, the property was changed.
                    // This will perform a value comparison for strings in C#. We could use String.Clone instead for string properties.
                    if ((value == null && syncedProperty.LastUpdateValue != null) ||
                        (value != null && syncedProperty.LastUpdateValue == null) ||
                        (!value.Equals(syncedProperty.LastUpdateValue)))
                    {
                        if (value != null)
                        {
                            // Some values, even if they are not 'equal', might be close enough to be equal.
                            // Note that the 'Equals()' above will most always return 'false' for lists and OSDMaps
                            //     since they are probably not the same object.
                            // Returning a 'false' here means the values don't need any updating (they are equal enough).
                            switch (property)
                            {
                                case SyncableProperties.Type.AvatarAppearance:
                                    String stringValue = OSDParser.SerializeJsonString((OSDMap)value);
                                    String lastStringValue = OSDParser.SerializeJsonString((OSDMap)syncedProperty.LastUpdateValue);
                                    if (stringValue == lastStringValue)
                                        return false;
                                    break;
                                case SyncableProperties.Type.Animations:
                                    if (syncedProperty.LastUpdateValue != null)
                                    {
                                        AnimationSet lastAnimations = new AnimationSet((OSDArray)syncedProperty.LastUpdateValue);

                                        // Get the home region for this presence (the client manager the presence is connected to).
                                        string cachedRealRegionName = (string)(CurrentlySyncedProperties[SyncableProperties.Type.RealRegion].LastUpdateValue);
                                        if (cachedRealRegionName != Scene.Name && sp.Animator.Animations.ToArray().Length == 0)
                                        {
                                            // If this is not the originating region for this presence and there is no additional
                                            //   animations being added, this simulator does not change the animation.
                                            // THIS IS A HORRIBLE KLUDGE. FIGURE OUT THE REAL SOLUTION!!
                                            // The problem is that animations are changed by every simulator (setting default
                                            //   sit and stand when parentID changes) and the updates conflict/override the real
                                            //   settings (like a scripted sit animation).
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Not home sim or no anim change. spID={1}, homeSim={2}, thisSim={3}, anims={4}",
                                            //                     LogHeader, sp.LocalId, cachedRealRegionName, Scene.Name, sp.Animator.Animations.ToArray().Length); // DEBUG DEBUG

                                            return false;
                                        }

                                        if (lastAnimations.Equals(sp.Animator.Animations))
                                        {
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Equal anims. spID={1}, sp.Anim={2}, lastAnim={3}",
                                            //                     LogHeader, sp.LocalId, sp.Animator.Animations, lastAnimations); // DEBUG DEBUG
                                            return false;
                                        }
                                        else
                                        {
                                            // If locally storing a new value of the animation, don't check for the time.
                                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal. Not equal anims. spID={1}, sp.Anim={2}, lastAnim={3}",
                                            //                     LogHeader, sp.LocalId, sp.Animator.Animations, lastAnimations); // DEBUG DEBUG
                                            syncedProperty.UpdateSyncInfoByLocal(lastUpdateByLocalTS, syncID, value);
                                            return true;
                                        }
                                    }
                                    break;
                                case SyncableProperties.Type.Velocity:
                                case SyncableProperties.Type.PA_Velocity:
                                case SyncableProperties.Type.PA_TargetVelocity:
                                case SyncableProperties.Type.RotationalVelocity:
                                case SyncableProperties.Type.AngularVelocity:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        // If velocity difference is small but not zero, don't update
                                        if (partVal.ApproxEquals(lastVal, VELOCITY_TOLERANCE) && !partVal.Equals(Vector3.Zero))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.RotationOffset:
                                case SyncableProperties.Type.Orientation:
                                    {
                                        Quaternion partVal = (Quaternion)value;
                                        Quaternion lastVal = (Quaternion)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, ROTATION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                                case SyncableProperties.Type.OffsetPosition:
                                case SyncableProperties.Type.AbsolutePosition:
                                case SyncableProperties.Type.Position:
                                case SyncableProperties.Type.GroupPosition:
                                    {
                                        Vector3 partVal = (Vector3)value;
                                        Vector3 lastVal = (Vector3)syncedProperty.LastUpdateValue;
                                        if (partVal.ApproxEquals(lastVal, POSITION_TOLERANCE))
                                            return false;
                                        break;
                                    }
                            }
                        }

                        // If we get here, the values are not equal and we need to update the cached value if the
                        //     new value is timestamp newer.
                        if (lastUpdateByLocalTS >= syncedProperty.LastUpdateTimeStamp)
                        {
                            // DebugLog.DebugFormat("{0} CompareValue_UpdateByLocal (property={1}): TS >= lastTS (updating SyncInfo)", LogHeader, property);
                            syncedProperty.UpdateSyncInfoByLocal(lastUpdateByLocalTS, syncID, value);
                            return true;
                        }
                    }
                    break;
            }
            return false;
        }