From 384895cbdd7961c9a6a95e8159eb9868cd170041 Mon Sep 17 00:00:00 2001 From: "Huaiyu (Kitty) Liu" Date: Tue, 28 Dec 2010 12:09:17 -0800 Subject: [PATCH] Added synchronization functions to RegionSyncModule, Scene, SceneGraph, SceneObjectGroup and SceneObjectPart: examples: HandleAddOrUpdateObjectBySynchronization QueueSceneObjectPartForUpdate SceneObjectGroup.UpdateObjectAllProperties SceneObjectPart.UpdateAllProperties Now script engine and Scene can sync on script updating or Scene editing objects. --- .../SymmetricSync/RegionSyncModule.cs | 212 ++++++++++++++++-- .../SymmetricSync/SymmetricSyncMessage.cs | 65 ++---- .../SymmetricSync/SyncConnector.cs | 19 ++ OpenSim/Region/Framework/Scenes/Scene.cs | 45 ++-- OpenSim/Region/Framework/Scenes/SceneGraph.cs | 99 +++++++- .../Framework/Scenes/SceneObjectGroup.cs | 135 +++++++++++ .../Framework/Scenes/SceneObjectPart.cs | 154 +++++++++++++ 7 files changed, 643 insertions(+), 86 deletions(-) diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs index 2f77ac06ad..ce8b258422 100755 --- a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs @@ -170,32 +170,130 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule // Lock is used to synchronize access to the update status and update queues private object m_updateSceneObjectPartLock = new object(); private Dictionary m_primUpdates = new Dictionary(); - private object m_updatePresenceLock = new object(); + private object m_updateScenePresenceLock = new object(); private Dictionary m_presenceUpdates = new Dictionary(); + private int m_sendingUpdates; public void QueueSceneObjectPartForUpdate(SceneObjectPart part) { - lock (m_updateSceneObjectPartLock) + //if the last update of the prim is caused by this actor itself, or if the actor is a relay node, then enqueue the update + if (part.LastUpdateActorID.Equals(m_actorID) || m_isSyncRelay) { - m_primUpdates[part.UUID] = part.ParentGroup; + lock (m_updateSceneObjectPartLock) + { + m_primUpdates[part.UUID] = part.ParentGroup; + } } } public void QueueScenePresenceForTerseUpdate(ScenePresence presence) { - lock (m_updateSceneObjectPartLock) + lock (m_updateScenePresenceLock) { m_presenceUpdates[presence.UUID] = presence; } } - public void SendObjectUpdates(List sog) + public void SendSceneUpdates() { + // Existing value of 1 indicates that updates are currently being sent so skip updates this pass + if (Interlocked.Exchange(ref m_sendingUpdates, 1) == 1) + { + m_log.WarnFormat("[REGION SYNC SERVER MODULE] SendUpdates(): An update thread is already running."); + return; + } + List primUpdates; + List presenceUpdates; + + lock (m_updateSceneObjectPartLock) + { + primUpdates = new List(m_primUpdates.Values); + //presenceUpdates = new List(m_presenceUpdates.Values); + m_primUpdates.Clear(); + //m_presenceUpdates.Clear(); + } + + lock (m_updateScenePresenceLock) + { + presenceUpdates = new List(m_presenceUpdates.Values); + m_presenceUpdates.Clear(); + } + + // This could be another thread for sending outgoing messages or just have the Queue functions + // create and queue the messages directly into the outgoing server thread. + System.Threading.ThreadPool.QueueUserWorkItem(delegate + { + // Dan's note: Sending the message when it's first queued would yield lower latency but much higher load on the simulator + // as parts may be updated many many times very quickly. Need to implement a higher resolution send in heartbeat + foreach (SceneObjectGroup sog in primUpdates) + { + //If this is a relay node, or at least one part of the object has the last update caused by this actor, then send the update + if (m_isSyncRelay || CheckObjectForSendingUpdate(sog)) + { + //send + List syncConnectors = GetSyncConnectorsForObjectUpdates(sog); + + foreach (SyncConnector connector in syncConnectors) + { + string sogxml = SceneObjectSerializer.ToXml2Format(sog); + SymmetricSyncMessage syncMsg = new SymmetricSyncMessage(SymmetricSyncMessage.MsgType.UpdatedObject, sogxml); + connector.EnqueueOutgoingUpdate(sog.UUID, syncMsg.ToBytes()); + } + + } + } + /* + foreach (ScenePresence presence in presenceUpdates) + { + try + { + if (!presence.IsDeleted) + { + + OSDMap data = new OSDMap(10); + data["id"] = OSD.FromUUID(presence.UUID); + // Do not include offset for appearance height. That will be handled by RegionSyncClient before sending to viewers + if(presence.AbsolutePosition.IsFinite()) + data["pos"] = OSD.FromVector3(presence.AbsolutePosition); + else + data["pos"] = OSD.FromVector3(Vector3.Zero); + if(presence.Velocity.IsFinite()) + data["vel"] = OSD.FromVector3(presence.Velocity); + else + data["vel"] = OSD.FromVector3(Vector3.Zero); + data["rot"] = OSD.FromQuaternion(presence.Rotation); + data["fly"] = OSD.FromBoolean(presence.Flying); + data["flags"] = OSD.FromUInteger((uint)presence.AgentControlFlags); + data["anim"] = OSD.FromString(presence.Animator.CurrentMovementAnimation); + // needed for a full update + if (presence.ParentID != presence.lastSentParentID) + { + data["coll"] = OSD.FromVector4(presence.CollisionPlane); + data["off"] = OSD.FromVector3(presence.OffsetPosition); + data["pID"] = OSD.FromUInteger(presence.ParentID); + presence.lastSentParentID = presence.ParentID; + } + + RegionSyncMessage rsm = new RegionSyncMessage(RegionSyncMessage.MsgType.UpdatedAvatar, OSDParser.SerializeJsonString(data)); + m_server.EnqueuePresenceUpdate(presence.UUID, rsm.ToBytes()); + + + } + } + catch (Exception e) + { + m_log.ErrorFormat("[REGION SYNC SERVER MODULE] Caught exception sending presence updates for {0}: {1}", presence.Name, e.Message); + } + } + * */ + + // Indicate that the current batch of updates has been completed + Interlocked.Exchange(ref m_sendingUpdates, 0); + }); } - #endregion //IRegionSyncModule #region ICommandableModule Members @@ -296,6 +394,60 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule m_log.Warn("[REGION SYNC MODULE]: StatsTimerElapsed -- NOT yet implemented."); } + /// + /// Check if we need to send out an update message for the given object. + /// + /// + /// + private bool CheckObjectForSendingUpdate(SceneObjectGroup sog) + { + if (sog == null || sog.IsDeleted) + return false; + + //If any part in the object has the last update caused by this actor itself, then send the update + foreach (SceneObjectPart part in sog.Parts) + { + if (part.LastUpdateActorID.Equals(m_actorID)) + { + return true; + } + } + + return false; + } + + /// + /// Get the set of SyncConnectors to send updates of the given object. + /// + /// + /// + private List GetSyncConnectorsForObjectUpdates(SceneObjectGroup sog) + { + List syncConnectors = new List(); + if (m_isSyncRelay) + { + //This is a relay node in the synchronization overlay, forward it to all connectors. + //Note LastUpdateTimeStamp and LastUpdateActorID is one per SceneObjectPart, not one per SceneObjectGroup, + //hence an actor sending in an update on one SceneObjectPart of a SceneObjectGroup may need to know updates + //in other parts as well, so we are sending to all connectors. + ForEachSyncConnector(delegate(SyncConnector connector) + { + syncConnectors.Add(connector); + }); + } + else + { + //This is a end node in the synchronization overlay (e.g. a non ScenePersistence actor). Get the right set of synconnectors. + //This may go more complex when an actor connects to several ScenePersistence actors. + ForEachSyncConnector(delegate(SyncConnector connector) + { + syncConnectors.Add(connector); + }); + } + + return syncConnectors; + } + //NOTE: We proably don't need to do this, and there might not be a need for OnPostSceneCreation event to let RegionSyncModule // and ActorSyncModules to gain some access to each other. We'll keep it here for a while, until we are sure it's not // needed. @@ -526,7 +678,7 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule List closed = null; foreach (SyncConnector syncConnector in m_syncConnectors) { - // If connected, send the message. + // If connected, apply the action if (syncConnector.Connected) { action(syncConnector); @@ -584,22 +736,50 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule return; } case SymmetricSyncMessage.MsgType.NewObject: + case SymmetricSyncMessage.MsgType.UpdatedObject: { - string sogxml = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); - - //m_log.Debug(LogHeader + ": " + sogxml); - - SceneObjectGroup sog = SceneObjectSerializer.FromXml2Format(sogxml); - - //HandleAddOrUpdateObjectInLocalScene(sog, true, true); - HandleAddNewObject(sog); + HandleAddOrUpdateObjectBySynchronization(msg); + //HandleAddNewObject(sog); + return; } - return; default: return; } } + public void HandleAddOrUpdateObjectBySynchronization(SymmetricSyncMessage msg) + { + string sogxml = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + SceneObjectGroup sog = SceneObjectSerializer.FromXml2Format(sogxml); + + if (sog.IsDeleted) + { + SymmetricSyncMessage.HandleTrivial(LogHeader, msg, String.Format("Ignoring update on deleted object, UUID: {0}.", sog.UUID)); + return; + } + else + { + Scene.ObjectUpdateResult updateResult = m_scene.AddOrUpdateObjectBySynchronization(sog); + + //if (added) + switch (updateResult) + { + case Scene.ObjectUpdateResult.New: + m_log.DebugFormat("[{0} Object \"{1}\" ({1}) ({2}) added.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + break; + case Scene.ObjectUpdateResult.Updated: + m_log.DebugFormat("[{0} Object \"{1}\" ({1}) ({2}) updated.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + break; + case Scene.ObjectUpdateResult.Error: + m_log.WarnFormat("[{0} Object \"{1}\" ({1}) ({2}) -- add or update ERROR.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + break; + case Scene.ObjectUpdateResult.Unchanged: + m_log.DebugFormat("[{0} Object \"{1}\" ({1}) ({2}) unchanged after receiving an update.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + break; + } + } + } + private void HandleAddNewObject(SceneObjectGroup sog) { //RegionSyncModule only add object to SceneGraph. Any actor specific actions will be implemented diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs index 9eda4d40fb..c4a1d79c74 100755 --- a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs @@ -21,61 +21,22 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule public enum MsgType { Null, - //ConnectSyncClient, - //DisconnectSyncClient, - // CM -> SIM(Scene) - ActorConnect, - AgentAdd, - AgentUpdate, - AgentRemove, - AgentRequestSit, - AgentSit, - GrabObject, - GrabUpdate, - DeGrabObject, - StartAnim, - StopAnim, + // Actor -> SIM(Scene) GetTerrain, GetObjects, - SubscribeObjects, - GetAvatars, - SubscribeAvatars, - ChatFromClient, - AvatarTeleportOut, // An LLClientView (real client) was converted to a RegionSyncAvatar - AvatarTeleportIn, // A RegionSyncAvatar was converted to an LLClientView (real client) + // SIM -> CM Terrain, NewObject, // objects UpdatedObject, // objects RemovedObject, // objects - NewAvatar, // avatars - UpdatedAvatar, // avatars - AnimateAvatar, - AvatarAppearance, - RemovedAvatar, // avatars - BalanceClientLoad, // Tells CM a client load target and a place to teleport the extras - ChatFromSim, - SitResponse, - SendAnimations, - // BIDIR - EchoRequest, - EchoResponse, - RegionName, - RegionStatus, - //Added by KittyL - // Actor -> Scene - // ActorType, //to register the type (e.g. Client Manager or Script Engine) with Scene when sync channel is initialized - //SetObjectProperty, - // ActorStop, - ResetScene, - OnRezScript, - OnScriptReset, - OnUpdateScript, - //QuarkSubscription, - // Scene -> Script Engine - //NewObjectWithScript, - //SceneLocation, + // BIDIR + //EchoRequest, + //EchoResponse, + RegionName, + //RegionStatus, + ActorID, } #endregion @@ -164,27 +125,27 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule #endregion - public static void HandleSuccess(string header, RegionSyncMessage msg, string message) + public static void HandleSuccess(string header, SymmetricSyncMessage msg, string message) { m_log.WarnFormat("{0} Handled {1}: {2}", header, msg.ToString(), message); } - public static void HandleTrivial(string header, RegionSyncMessage msg, string message) + public static void HandleTrivial(string header, SymmetricSyncMessage msg, string message) { m_log.WarnFormat("{0} Issue handling {1}: {2}", header, msg.ToString(), message); } - public static void HandleWarning(string header, RegionSyncMessage msg, string message) + public static void HandleWarning(string header, SymmetricSyncMessage msg, string message) { m_log.WarnFormat("{0} Warning handling {1}: {2}", header, msg.ToString(), message); } - public static void HandleError(string header, RegionSyncMessage msg, string message) + public static void HandleError(string header, SymmetricSyncMessage msg, string message) { m_log.WarnFormat("{0} Error handling {1}: {2}", header, msg.ToString(), message); } - public static bool HandlerDebug(string header, RegionSyncMessage msg, string message) + public static bool HandlerDebug(string header, SymmetricSyncMessage msg, string message) { m_log.WarnFormat("{0} DBG ({1}): {2}", header, msg.ToString(), message); return true; diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs index cf2f0654e6..6f2e915b36 100755 --- a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs @@ -45,6 +45,14 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule get { return m_connectorNum; } } + //the actorID of the other end of the connection + private string m_syncOtherSideActorID; + public string OtherSideActorID + { + get { return m_syncOtherSideActorID; } + set { m_syncOtherSideActorID = value; } + } + //The region name of the other side of the connection private string m_syncOtherSideRegionName=""; public string OtherSideRegionName @@ -267,6 +275,17 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule m_log.DebugFormat("Syncing to region \"{0}\"", m_syncOtherSideRegionName); return; } + case SymmetricSyncMessage.MsgType.ActorID: + { + m_syncOtherSideActorID = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + if (m_regionSyncModule.IsSyncRelay) + { + SymmetricSyncMessage outMsg = new SymmetricSyncMessage(SymmetricSyncMessage.MsgType.ActorID, m_regionSyncModule.ActorID); + Send(outMsg); + } + m_log.DebugFormat("Syncing to actor \"{0}\"", m_syncOtherSideActorID); + return; + } default: break; } diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 7ca1980976..18ae8d0768 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -598,6 +598,29 @@ namespace OpenSim.Region.Framework.Scenes } + //This enumeration would help to identify if after a NewObject/UpdatedObject message is received, + //the object is a new object and hence added to the scene graph, or it an object with some properties + //just updated, or the copy of the object in the UpdatedObject message is the same with local copy + //(before we add time-stamp to identify updates from different actors/scene, it could be possible the same + //update be forwarded, say from script engine to scene, and then back to script engine. + public enum ObjectUpdateResult + { + New, //the New/UpdatedObject message ends up adding a new object to local scene graph + Updated, //the object has some property updated after processing the New/UpdatedObject + Unchanged, //no property of the object has been changed after processing the New/UpdatedObject + //(it probably is the same update this end has sent out before + Error //Errors happen during processing the message, e.g. the entity with the given UUID is not of type SceneObjectGroup + } + + //This function should only be called by an actor who's local Scene is just a cache of the authorative Scene. + //If the object already exists, use the new copy to replace it. + //Return true if added, false if just updated + public ObjectUpdateResult AddOrUpdateObjectBySynchronization(SceneObjectGroup sog) + { + return m_sceneGraph.AddOrUpdateObjectBySynchronization(sog); + } + + #endregion //SYMMETRIC SYNC public ICapabilitiesModule CapsModule @@ -1484,24 +1507,14 @@ namespace OpenSim.Region.Framework.Scenes m_regionSyncServerModule.SendUpdates(); } - /* - // The authoritative sim should not try to send coarse locations - // Leave this up to the client managers - if (!IsSyncedServer()) + //SYMMETRIC SYNC + + //NOTE: If it is configured as symmetric sync in opensim.ini, the above IsSyncedServer() or IsSyncedClient() should all return false + if (RegionSyncModule != null) { - if (m_frame % m_update_coarse_locations == 0) - { - List coarseLocations; - List avatarUUIDs; - SceneGraph.GetCoarseLocations(out coarseLocations, out avatarUUIDs, 60); - // Send coarse locations to clients - ForEachScenePresence(delegate(ScenePresence presence) - { - presence.SendCoarseLocations(coarseLocations, avatarUUIDs); - }); - } + RegionSyncModule.SendSceneUpdates(); } - * */ + //end of SYMMETRIC SYNC int tmpPhysicsMS2 = Util.EnvironmentTickCount(); // Do not simulate physics locally if this is a synced client diff --git a/OpenSim/Region/Framework/Scenes/SceneGraph.cs b/OpenSim/Region/Framework/Scenes/SceneGraph.cs index c91f205ee3..bb6be976a6 100644 --- a/OpenSim/Region/Framework/Scenes/SceneGraph.cs +++ b/OpenSim/Region/Framework/Scenes/SceneGraph.cs @@ -1920,10 +1920,105 @@ namespace OpenSim.Region.Framework.Scenes return true; } - + } - #endregion + #endregion // REGION SYNC + #region SYMMETRIC SYNC + + public Scene.ObjectUpdateResult AddOrUpdateObjectBySynchronization(SceneObjectGroup updatedSog) + { + UUID sogID = updatedSog.UUID; + + if (Entities.ContainsKey(sogID)) + { + //update the object + EntityBase entity = Entities[sogID]; + if (entity is SceneObjectGroup) + { + SceneObjectGroup localSog = (SceneObjectGroup)entity; + Scene.ObjectUpdateResult updateResult = localSog.UpdateObjectAllProperties(updatedSog); + return updateResult; + } + else + { + m_log.Warn("Entity with " + sogID + " is not of type SceneObjectGroup"); + //return false; + return Scene.ObjectUpdateResult.Error; + } + } + else + { + AddSceneObjectByStateSynch(updatedSog); + return Scene.ObjectUpdateResult.New; + } + } + + //This is an object added due to receiving a state synchronization message from Scene or an actor. Do similar things as the original AddSceneObject(), + //but call ScheduleGroupForFullUpdate_TimeStampUnchanged() instead, so as not to modify the timestamp or actorID, since the object was not created + //locally. + protected bool AddSceneObjectByStateSynch(SceneObjectGroup sceneObject) + { + if (sceneObject == null || sceneObject.RootPart == null || sceneObject.RootPart.UUID == UUID.Zero) + return false; + + if (Entities.ContainsKey(sceneObject.UUID)) + return false; + + SceneObjectPart[] children = sceneObject.Parts; + + // Clamp child prim sizes and add child prims to the m_numPrim count + if (m_parentScene.m_clampPrimSize) + { + foreach (SceneObjectPart part in children) + { + Vector3 scale = part.Shape.Scale; + + if (scale.X > m_parentScene.m_maxNonphys) + scale.X = m_parentScene.m_maxNonphys; + if (scale.Y > m_parentScene.m_maxNonphys) + scale.Y = m_parentScene.m_maxNonphys; + if (scale.Z > m_parentScene.m_maxNonphys) + scale.Z = m_parentScene.m_maxNonphys; + + part.Shape.Scale = scale; + } + } + m_numPrim += children.Length; + + sceneObject.AttachToScene(m_parentScene); + + //SYMMETRIC SYNC, + sceneObject.ScheduleGroupForFullUpdate_SyncInfoUnchanged(); + //end of SYMMETRIC SYNC, + + Entities.Add(sceneObject); + + //ScenePersistenceSyncModule will attach the object to backup when it catches the OnObjectCreate event. + //if (attachToBackup) + // sceneObject.AttachToBackup(); + + if (OnObjectCreate != null) + OnObjectCreate(sceneObject); + + lock (SceneObjectGroupsByFullID) + { + SceneObjectGroupsByFullID[sceneObject.UUID] = sceneObject; + foreach (SceneObjectPart part in children) + SceneObjectGroupsByFullID[part.UUID] = sceneObject; + } + + lock (SceneObjectGroupsByLocalID) + { + SceneObjectGroupsByLocalID[sceneObject.LocalId] = sceneObject; + foreach (SceneObjectPart part in children) + SceneObjectGroupsByLocalID[part.LocalId] = sceneObject; + } + + return true; + } + + #endregion //SYMMETRIC SYNC } } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 54297b1c78..8bc467c3bd 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -3459,6 +3459,141 @@ namespace OpenSim.Region.Framework.Scenes #region SYMMETRIC SYNC + //update the existing copy of the object with updated properties in 'updatedSog' + //NOTE: updates on script content are handled seperately (e.g. user edited the script and saved it) -- SESyncServerOnUpdateScript(), a handler of EventManager.OnUpdateScript + //public void UpdateObjectProperties(SceneObjectGroup updatedSog) + + /// + /// Update the existing copy of the object with updated properties in 'updatedSog'. For now we update + /// all properties. Later on this should be edited to allow only updating a bucket of properties. + /// + /// + /// + public Scene.ObjectUpdateResult UpdateObjectAllProperties(SceneObjectGroup updatedSog) + { + if (!this.GroupID.Equals(updatedSog.GroupID)) + return Scene.ObjectUpdateResult.Error; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + //NOTE!!! + //We do not want to simply call SceneObjectGroup.Copy here to clone the object: + //the prims (SceneObjectParts) in updatedSog are different instances than those in the local copy, + //and we want to preserve the references to the prims in this local copy, especially for scripts + //of each prim, where the scripts have references to the local copy. If the local copy is replaced, + //the prims (parts) will be replaces and we need to update all the references that were pointing to + //the previous prims. + //////////////////////////////////////////////////////////////////////////////////////////////////// + + Scene.ObjectUpdateResult groupUpdateResult = Scene.ObjectUpdateResult.Unchanged; + Dictionary updatedParts = new Dictionary(); + bool partsRemoved = false; //has any old part been removed? + bool rootPartChanged = false; //has the rootpart be changed to a different prim? + + lock (m_parts) + { + //update rootpart, if changed + if (m_rootPart.UUID != updatedSog.RootPart.UUID) + { + m_rootPart = updatedSog.RootPart; + rootPartChanged = true; + } + + //foreach (KeyValuePair pair in updatedSog.Parts) + foreach (SceneObjectPart updatedPart in updatedSog.Parts) + { + UUID partUUID = updatedPart.UUID; + Scene.ObjectUpdateResult partUpdateResult = Scene.ObjectUpdateResult.Unchanged; + if (HasChildPrim(partUUID)) + { + //update the existing part + SceneObjectPart oldPart = GetChildPart(partUUID); + partUpdateResult = oldPart.UpdateAllProperties(updatedPart); + updatedParts.Add(partUUID, updatedPart); + } + else + { + //a new part + //m_parts.Add(partUUID, updatedPart); + AddPart(updatedPart); + partUpdateResult = Scene.ObjectUpdateResult.New; + } + + if (partUpdateResult != Scene.ObjectUpdateResult.Unchanged) + { + if (partUpdateResult == Scene.ObjectUpdateResult.New) + groupUpdateResult = Scene.ObjectUpdateResult.Updated; + else + groupUpdateResult = partUpdateResult; //Error or Updated + } + } + + //For any parts that are not in the updatesParts (the old parts that are still in updatedSog), delete them. + foreach (SceneObjectPart oldPart in this.Parts) + { + if (!updatedParts.ContainsKey(oldPart.UUID)) + { + m_parts.Remove(oldPart.UUID); + partsRemoved = true; + } + } + + //Update the rootpart's ID in each non root parts + if (rootPartChanged) + { + UpdateParentIDs(); + } + } + + if (partsRemoved) + { + groupUpdateResult = Scene.ObjectUpdateResult.Updated; + } + + /* + //update the authoritative scene that this object is located, which is identified by (LocX, LocY) + if (this.m_locX != updatedSog.LocX) + { + this.m_locX = updatedSog.LocX; + groupUpdateResult = Scene.ObjectUpdateResult.Updated; + } + if (this.m_locY != updatedSog.LocY) + { + this.m_locY = updatedSog.LocY; + groupUpdateResult = Scene.ObjectUpdateResult.Updated; + } + * */ + + //Schedule updates to be sent out, if the local copy has just been updated + //(1) if we are debugging the actor with a viewer attaching to it, + //we need to schedule updates to be sent to the viewer. + //(2) or if we are a relaying node to relay updates, we need to forward the updates. + //NOTE: LastUpdateTimeStamp and LastUpdateActorID should be kept the same as in the received copy of the object. + if (groupUpdateResult == Scene.ObjectUpdateResult.Updated) + { + ScheduleGroupForFullUpdate_SyncInfoUnchanged(); + } + + return groupUpdateResult; + } + + public void ScheduleGroupForFullUpdate_SyncInfoUnchanged() + { + if (IsAttachment) + m_log.DebugFormat("[SOG]: Scheduling full update for {0} {1}", Name, LocalId); + + checkAtTargets(); + RootPart.ScheduleFullUpdate_SyncInfoUnchanged(); + + lock (m_parts) + { + foreach (SceneObjectPart part in this.Parts) + { + if (part != RootPart) + part.ScheduleFullUpdate_SyncInfoUnchanged(); + } + } + } + #endregion } } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 6799bc5bf8..4e5bcee7c0 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -3147,6 +3147,8 @@ namespace OpenSim.Region.Framework.Scenes //SYMMETRIC SYNC + m_parentGroup.Scene.RegionSyncModule.QueueSceneObjectPartForUpdate(this); + //end of SYMMETRIC SYNC } @@ -4962,6 +4964,158 @@ namespace OpenSim.Region.Framework.Scenes } } + //!!!!!! -- TODO: + //!!!!!! -- We should call UpdateXXX functions to update each property, cause some of such updates involves sanity checking. + public Scene.ObjectUpdateResult UpdateAllProperties(SceneObjectPart updatedPart) + { + //////////////////////////////////////////////////////////////////////////////////////////////////// + //NOTE!!!: So far this function is written with Script Engine updating local Scene cache in mind. + //////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////Assumptions: //////////////////// + //(1) prim's UUID and LocalID shall not change (UUID is the unique identifies, LocalID is used to refer to the prim by, say scripts) + //(2) RegionHandle won't be updated -- each copy of Scene is hosted on a region with different region handle + //(3) ParentID won't be updated -- if the rootpart of the SceneObjectGroup changed, that will be updated in SceneObjectGroup.UpdateObjectProperties + + ////////////////////Furture enhancements://////////////////// + //For now, we only update the set of properties that are included in serialization. + //See SceneObjectSerializer for the properties that are included in a serialized SceneObjectPart. + //Later on, we may implement update functions that allow updating certain properties or certain buckets of properties. + + if (updatedPart == null) + return Scene.ObjectUpdateResult.Error; + + if (m_lastUpdateTimeStamp > updatedPart.LastUpdateTimeStamp) + { + //Our timestamp is more update to date, keep our values of the properties. Do not update anything. + return Scene.ObjectUpdateResult.Unchanged; + } + + if (m_lastUpdateTimeStamp == updatedPart.LastUpdateTimeStamp) + { + //if (m_parentGroup.Scene.GetActorID() != updatedPart.LastUpdatedByActorID) + if (m_lastUpdateByActorID != updatedPart.LastUpdateActorID) + { + m_log.Warn("Different actors modified SceneObjetPart " + UUID + " with the same TimeStamp, CONFLICT RESOLUTION TO BE IMPLEMENTED!!!!"); + return Scene.ObjectUpdateResult.Unchanged; + } + + //My own update was relayed back. Don't relay it. + return Scene.ObjectUpdateResult.Unchanged; + } + + //Otherwise, our timestamp is less up to date, update the prim with the received copy + + Scene.ObjectUpdateResult partUpdateResult = Scene.ObjectUpdateResult.Updated; + + //See SceneObjectSerializer for the properties that are included in a serialized SceneObjectPart. + this.AllowedDrop = updatedPart.AllowedDrop; + this.CreatorID = updatedPart.CreatorID; + this.CreatorData = updatedPart.CreatorData; + this.FolderID = updatedPart.FolderID; + this.InventorySerial = updatedPart.InventorySerial; + this.TaskInventory = updatedPart.TaskInventory; + //Following two properties, UUID and LocalId, shall not be updated. + //this.UUID + //this.LocalId + this.Name = updatedPart.Name; + this.Material = updatedPart.Material; + this.PassTouches = updatedPart.PassTouches; + //RegionHandle shall not be copied, since updatedSog is sent by a different actor, which has a different local region + //this.RegionHandle + this.ScriptAccessPin = updatedPart.ScriptAccessPin; + this.GroupPosition = updatedPart.GroupPosition; + this.OffsetPosition = updatedPart.OffsetPosition; + this.RotationOffset = updatedPart.RotationOffset; + this.Velocity = updatedPart.Velocity; + this.AngularVelocity = updatedPart.AngularVelocity; + this.Acceleration = updatedPart.Acceleration; + this.Description = updatedPart.Description; + this.Color = updatedPart.Color; + this.Text = updatedPart.Text; + this.SitName = updatedPart.SitName; + this.TouchName = updatedPart.TouchName; + this.LinkNum = updatedPart.LinkNum; + this.ClickAction = updatedPart.ClickAction; + this.Shape = updatedPart.Shape; + this.Scale = updatedPart.Scale; + this.UpdateFlag = updatedPart.UpdateFlag; + this.SitTargetOrientation = updatedPart.SitTargetOrientation; + this.SitTargetPosition = updatedPart.SitTargetPosition; + this.SitTargetPositionLL = updatedPart.SitTargetPositionLL; + this.SitTargetOrientationLL = updatedPart.SitTargetOrientationLL; + //ParentID should still point to the rootpart in the local sog, do not update. If the root part changed, we will update it in SceneObjectGroup.UpdateObjectProperties() + //this.ParentID; + this.CreationDate = updatedPart.CreationDate; + this.Category = updatedPart.Category; + this.SalePrice = updatedPart.SalePrice; + this.ObjectSaleType = updatedPart.ObjectSaleType; + this.OwnershipCost = updatedPart.OwnershipCost; + this.GroupID = updatedPart.GroupID; + this.OwnerID = updatedPart.OwnerID; + this.LastOwnerID = updatedPart.LastOwnerID; + this.BaseMask = updatedPart.BaseMask; + this.OwnerMask = updatedPart.OwnerMask; + this.GroupMask = updatedPart.GroupMask; + this.EveryoneMask = updatedPart.EveryoneMask; + this.NextOwnerMask = updatedPart.NextOwnerMask; + this.Flags = updatedPart.Flags; + this.CollisionSound = updatedPart.CollisionSound; + this.CollisionSoundVolume = updatedPart.CollisionSoundVolume; + this.MediaUrl = updatedPart.MediaUrl; + this.TextureAnimation = updatedPart.TextureAnimation; + this.ParticleSystem = updatedPart.ParticleSystem; + + //Update the timestamp and LastUpdatedByActorID first. + this.m_lastUpdateByActorID = updatedPart.LastUpdateActorID; + this.m_lastUpdateTimeStamp = updatedPart.LastUpdateTimeStamp; + + + /* + this.m_inventory.Items = (TaskInventoryDictionary)updatedPart.m_inventory.Items.Clone(); + //update shape information, for now, only update fileds in Shape whose set functions are defined in PrimitiveBaseShape + this.Shape = updatedPart.Shape.Copy(); + this.Shape.TextureEntry = updatedPart.Shape.TextureEntry; + * */ + + return partUpdateResult; + } + + /// + /// Schedules this prim for a full update, without changing the timestamp or actorID (info on when and who modified any property). + /// NOTE: this is the same as the original SceneObjectPart.ScheduleFullUpdate(). + /// + public void ScheduleFullUpdate_SyncInfoUnchanged() + { + // m_log.DebugFormat("[SCENE OBJECT PART]: Scheduling full update for {0} {1}", Name, LocalId); + + if (m_parentGroup != null) + { + m_parentGroup.QueueForUpdateCheck(); + } + + int timeNow = Util.UnixTimeSinceEpoch(); + + // If multiple updates are scheduled on the same second, we still need to perform all of them + // So we'll force the issue by bumping up the timestamp so that later processing sees these need + // to be performed. + if (timeNow <= TimeStampFull) + { + TimeStampFull += 1; + } + else + { + TimeStampFull = (uint)timeNow; + } + + m_updateFlag = 2; + + // m_log.DebugFormat( + // "[SCENE OBJECT PART]: Scheduling full update for {0}, {1} at {2}", + // UUID, Name, TimeStampFull); + + } + #endregion }