diff --git a/OpenSim/Region/Application/OpenSimBase.cs b/OpenSim/Region/Application/OpenSimBase.cs index 1652b82542..6db3367673 100644 --- a/OpenSim/Region/Application/OpenSimBase.cs +++ b/OpenSim/Region/Application/OpenSimBase.cs @@ -411,6 +411,12 @@ namespace OpenSim scene.StartTimer(); + //SYMMETRIC SYNC + //For INonSharedRegionModule, there is no more PostInitialise(). So we trigger OnPostSceneCreation event here + //to let the Sync modules to do their work once all modules are loaded and scene has interfaces to all of them. + scene.EventManager.TriggerOnPostSceneCreation(scene); + //end of SYMMETRIC SYNC + return clientServer; } diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncServerModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncServerModule.cs index 90b768c14b..318da00581 100644 --- a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncServerModule.cs +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncServerModule.cs @@ -75,7 +75,8 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule } // If syncConfig does not indicate "server", do not start up server mode - string mode = syncConfig.GetString("Mode", "server").ToLower(); + //string mode = syncConfig.GetString("Mode", "server").ToLower(); + string mode = syncConfig.GetString("Mode", "").ToLower(); if(mode != "server") { scene.RegionSyncEnabled = false; diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ClientManagerSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ClientManagerSyncModule.cs new file mode 100755 index 0000000000..ea6d8bb3e2 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ClientManagerSyncModule.cs @@ -0,0 +1,169 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using log4net; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")] + public class ClientManagerSyncModule : INonSharedRegionModule, IDSGActorSyncModule + { + #region INonSharedRegionModule + + public void Initialise(IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + IConfig syncConfig = config.Configs["RegionSyncModule"]; + m_active = false; + if (syncConfig == null) + { + m_log.Warn(LogHeader + " No RegionSyncModule config section found. Shutting down."); + return; + } + else if (!syncConfig.GetBoolean("Enabled", false)) + { + m_log.Warn(LogHeader + " RegionSyncModule is not enabled. Shutting down."); + return; + } + + string actorType = syncConfig.GetString("DSGActorType", "").ToLower(); + + //Read in configuration, if the local actor is configured to be a client manager, load this module. + if (!actorType.Equals("client_manager")) + { + m_log.Warn(LogHeader + ": not configured as Scene Persistence Actor. Shut down."); + return; + } + + m_actorID = syncConfig.GetString("ActorID", ""); + if (m_actorID.Equals("")) + { + m_log.Warn(LogHeader + ": ActorID not specified in config file. Shutting down."); + return; + } + + m_active = true; + + m_log.Warn(LogHeader + " Initialised"); + + } + + //Called after Initialise() + public void AddRegion(Scene scene) + { + if (!m_active) + return; + + //connect with scene + m_scene = scene; + + //register the module with SceneGraph. If needed, SceneGraph checks the module's ActorType to know what type of module it is. + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + //m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + //InstallInterfaces(); + + //Register for the OnPostSceneCreation event + //m_scene.EventManager.OnPostSceneCreation += OnPostSceneCreation; + + //Register for Scene/SceneGraph events + m_scene.SceneGraph.OnObjectCreate += new ObjectCreateDelegate(ClientManager_OnObjectCreate); + m_scene.EventManager.OnSymmetricSyncStop += ClientManager_OnSymmetricSyncStop; + } + + //Called after AddRegion() has been called for all region modules of the scene. + //NOTE::However, at this point, Scene may not have requested all the needed region module interfaces yet. + public void RegionLoaded(Scene scene) + { + if (!m_active) + return; + + } + + public void RemoveRegion(Scene scene) + { + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Close() + { + m_scene = null; + } + + public string Name + { + get { return "ClientManagerSyncModule"; } + } + + #endregion //INonSharedRegionModule + + + #region IDSGActorSyncModule members and functions + + private DSGActorTypes m_actorType = DSGActorTypes.ClientManager; + public DSGActorTypes ActorType + { + get { return m_actorType; } + } + + private string m_actorID; + public string ActorID + { + get { return m_actorID; } + } + + #endregion //IDSGActorSyncModule + + #region ClientManagerSyncModule memebers and functions + private ILog m_log; + private bool m_active = false; + public bool Active + { + get { return m_active; } + } + + private Scene m_scene; + + private string LogHeader = "[ClientManagerSyncModule]"; + + /// + /// Script Engine's action upon an object is added to the local scene + /// + private void ClientManager_OnObjectCreate(EntityBase entity) + { + if (entity is SceneObjectGroup) + { + } + } + + public void ClientManager_OnSymmetricSyncStop() + { + //remove all objects + m_scene.DeleteAllSceneObjects(); + } + + #endregion //ScriptEngineSyncModule + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/PhysicsEngineSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/PhysicsEngineSyncModule.cs new file mode 100755 index 0000000000..e0b8c73982 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/PhysicsEngineSyncModule.cs @@ -0,0 +1,169 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using log4net; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")] + public class PhysicsEngineSyncModule : INonSharedRegionModule, IDSGActorSyncModule + { + #region INonSharedRegionModule + + public void Initialise(IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + IConfig syncConfig = config.Configs["RegionSyncModule"]; + m_active = false; + if (syncConfig == null) + { + m_log.Warn(LogHeader + " No RegionSyncModule config section found. Shutting down."); + return; + } + else if (!syncConfig.GetBoolean("Enabled", false)) + { + m_log.Warn(LogHeader + " RegionSyncModule is not enabled. Shutting down."); + return; + } + + string actorType = syncConfig.GetString("DSGActorType", "").ToLower(); + + //Read in configuration, if the local actor is configured to be a client manager, load this module. + if (!actorType.Equals("physics_engine")) + { + m_log.Warn(LogHeader + ": not configured as Scene Persistence Actor. Shut down."); + return; + } + + m_actorID = syncConfig.GetString("ActorID", ""); + if (m_actorID.Equals("")) + { + m_log.Warn(LogHeader + ": ActorID not specified in config file. Shutting down."); + return; + } + + m_active = true; + + m_log.Warn(LogHeader + " Initialised"); + + } + + //Called after Initialise() + public void AddRegion(Scene scene) + { + if (!m_active) + return; + + //connect with scene + m_scene = scene; + + //register the module with SceneGraph. If needed, SceneGraph checks the module's ActorType to know what type of module it is. + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + //m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + //InstallInterfaces(); + + //Register for the OnPostSceneCreation event + //m_scene.EventManager.OnPostSceneCreation += OnPostSceneCreation; + + //Register for Scene/SceneGraph events + m_scene.SceneGraph.OnObjectCreate += new ObjectCreateDelegate(PhysicsEngine_OnObjectCreate); + m_scene.EventManager.OnSymmetricSyncStop += PhysicsEngine_OnSymmetricSyncStop; + } + + //Called after AddRegion() has been called for all region modules of the scene. + //NOTE::However, at this point, Scene may not have requested all the needed region module interfaces yet. + public void RegionLoaded(Scene scene) + { + if (!m_active) + return; + + } + + public void RemoveRegion(Scene scene) + { + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Close() + { + m_scene = null; + } + + public string Name + { + get { return "PhysicsEngineSyncModule"; } + } + + #endregion //INonSharedRegionModule + + + #region IDSGActorSyncModule members and functions + + private DSGActorTypes m_actorType = DSGActorTypes.PhysicsEngine; + public DSGActorTypes ActorType + { + get { return m_actorType; } + } + + private string m_actorID; + public string ActorID + { + get { return m_actorID; } + } + + #endregion //IDSGActorSyncModule + + #region PhysicsEngineSyncModule memebers and functions + private ILog m_log; + private bool m_active = false; + public bool Active + { + get { return m_active; } + } + + private Scene m_scene; + + private string LogHeader = "[PhysicsEngineSyncModule]"; + + /// + /// Script Engine's action upon an object is added to the local scene + /// + private void PhysicsEngine_OnObjectCreate(EntityBase entity) + { + if (entity is SceneObjectGroup) + { + } + } + + public void PhysicsEngine_OnSymmetricSyncStop() + { + //remove all objects + m_scene.DeleteAllSceneObjects(); + } + + #endregion //ScriptEngineSyncModule + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs new file mode 100755 index 0000000000..3b9ca5f3eb --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/RegionSyncModule.cs @@ -0,0 +1,1026 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using log4net; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Text; + +using Mono.Addins; +using OpenMetaverse.StructuredData; + +///////////////////////////////////////////////////////////////////////////////////////////// +//KittyL: created 12/17/2010, to start DSG Symmetric Synch implementation +///////////////////////////////////////////////////////////////////////////////////////////// +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")] + public class RegionSyncModule : INonSharedRegionModule, IRegionSyncModule, ICommandableModule + //public class RegionSyncModule : IRegionModule, IRegionSyncModule, ICommandableModule + { + #region INonSharedRegionModule + + public void Initialise(IConfigSource config) + //public void Initialise(Scene scene, IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + m_sysConfig = config.Configs["RegionSyncModule"]; + m_active = false; + if (m_sysConfig == null) + { + m_log.Warn("[REGION SYNC MODULE] No RegionSyncModule config section found. Shutting down."); + return; + } + else if (!m_sysConfig.GetBoolean("Enabled", false)) + { + m_log.Warn("[REGION SYNC MODULE] RegionSyncModule is not enabled. Shutting down."); + return; + } + + m_actorID = m_sysConfig.GetString("ActorID", ""); + if (m_actorID == "") + { + m_log.Error("ActorID not defined in [RegionSyncModule] section in config file. Shutting down."); + return; + } + + m_isSyncRelay = m_sysConfig.GetBoolean("IsSyncRelay", false); + m_isSyncListenerLocal = m_sysConfig.GetBoolean("IsSyncListenerLocal", false); + + m_active = true; + + m_log.Warn("[REGION SYNC MODULE] Initialised for actor "+ m_actorID); + + //The ActorType configuration will be read in and process by an ActorSyncModule, not here. + } + + //Called after Initialise() + public void AddRegion(Scene scene) + { + m_log.Warn(LogHeader + " AddRegion() called"); + + if (!m_active) + return; + + //connect with scene + m_scene = scene; + + //register the module + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + InstallInterfaces(); + + //Register for the OnPostSceneCreation event + m_scene.EventManager.OnPostSceneCreation += OnPostSceneCreation; + m_scene.EventManager.OnObjectBeingRemovedFromScene += new EventManager.ObjectBeingRemovedFromScene(RegionSyncModule_OnObjectBeingRemovedFromScene); + } + + //Called after AddRegion() has been called for all region modules of the scene + public void RegionLoaded(Scene scene) + { + m_log.Warn(LogHeader + " RegionLoaded() called"); + + //If this one is configured to start a listener so that other actors can connect to form a overlay, start the listener. + //For now, we use start topology, and ScenePersistence actor is always the one to start the listener. + if (m_isSyncListenerLocal) + { + StartLocalSyncListener(); + } + else + { + //Start connecting to the remote listener. TO BE IMPLEMENTED. + //For now, the connection will be started by manually typing in "sync start". + } + + } + + public void RemoveRegion(Scene scene) + { + } + + public void Close() + { + m_scene = null; + } + + public string Name + { + get { return "RegionSyncModule"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + #endregion //INonSharedRegionModule + + #region IRegionSyncModule + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Synchronization related members and functions, exposed through IRegionSyncModule interface + /////////////////////////////////////////////////////////////////////////////////////////////////// + + private DSGActorTypes m_actorType = DSGActorTypes.Unknown; + /// + /// The type of the actor running locally. This value will be set by an ActorSyncModule, so that + /// no hard code needed in RegionSyncModule to recoganize the actor's type, thus make it easier + /// to add new ActorSyncModules w/o chaning the code in RegionSyncModule. + /// + public DSGActorTypes DSGActorType + { + get { return m_actorType; } + set { m_actorType = value; } + } + + private string m_actorID; + public string ActorID + { + get { return m_actorID; } + } + + private bool m_active = false; + public bool Active + { + get { return m_active; } + } + + private bool m_isSyncRelay = false; + public bool IsSyncRelay + { + get { return m_isSyncRelay; } + } + + private RegionSyncListener m_localSyncListener = null; + + // 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_updateScenePresenceLock = new object(); + private Dictionary m_presenceUpdates = new Dictionary(); + private int m_sendingUpdates; + + public void QueueSceneObjectPartForUpdate(SceneObjectPart part) + { + //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) + { + lock (m_updateSceneObjectPartLock) + { + m_primUpdates[part.UUID] = part.ParentGroup; + } + } + } + + public void QueueScenePresenceForTerseUpdate(ScenePresence presence) + { + lock (m_updateScenePresenceLock) + { + m_presenceUpdates[presence.UUID] = presence; + } + } + + 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 || (!sog.IsDeleted && CheckObjectForSendingUpdate(sog))) + { + //send + string sogxml = SceneObjectSerializer.ToXml2Format(sog); + SymmetricSyncMessage syncMsg = new SymmetricSyncMessage(SymmetricSyncMessage.MsgType.UpdatedObject, sogxml); + SendObjectUpdateToRelevantSyncConnectors(sog, syncMsg); + } + } + /* + 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); + }); + } + + public void SendTerrainUpdates(string lastUpdateActorID) + { + if(m_isSyncRelay || m_actorID.Equals(lastUpdateActorID)) + { + //m_scene.Heightmap should have been updated already by the caller, send it out + //SendSyncMessage(SymmetricSyncMessage.MsgType.Terrain, m_scene.Heightmap.SaveToXmlString()); + SendTerrainUpdateMessage(); + } + } + + #endregion //IRegionSyncModule + + #region ICommandableModule Members + private readonly Commander m_commander = new Commander("sync"); + public ICommander CommandInterface + { + get { return m_commander; } + } + #endregion + + #region Console Command Interface + private void InstallInterfaces() + { + Command cmdSyncStart = new Command("start", CommandIntentions.COMMAND_HAZARDOUS, SyncStart, "Begins synchronization with RegionSyncServer."); + //cmdSyncStart.AddArgument("server_address", "The IP address of the server to synchronize with", "String"); + //cmdSyncStart.AddArgument("server_port", "The port of the server to synchronize with", "Integer"); + + Command cmdSyncStop = new Command("stop", CommandIntentions.COMMAND_HAZARDOUS, SyncStop, "Stops synchronization with RegionSyncServer."); + //cmdSyncStop.AddArgument("server_address", "The IP address of the server to synchronize with", "String"); + //cmdSyncStop.AddArgument("server_port", "The port of the server to synchronize with", "Integer"); + + Command cmdSyncStatus = new Command("status", CommandIntentions.COMMAND_HAZARDOUS, SyncStatus, "Displays synchronization status."); + + m_commander.RegisterCommand("start", cmdSyncStart); + m_commander.RegisterCommand("stop", cmdSyncStop); + m_commander.RegisterCommand("status", cmdSyncStatus); + + lock (m_scene) + { + // Add this to our scene so scripts can call these functions + m_scene.RegisterModuleCommander(m_commander); + } + } + + + /// + /// Processes commandline input. Do not call directly. + /// + /// Commandline arguments + private void EventManager_OnPluginConsole(string[] args) + { + if (args[0] == "sync") + { + if (args.Length == 1) + { + m_commander.ProcessConsoleCommand("help", new string[0]); + return; + } + + string[] tmpArgs = new string[args.Length - 2]; + int i; + for (i = 2; i < args.Length; i++) + tmpArgs[i - 2] = args[i]; + + m_commander.ProcessConsoleCommand(args[1], tmpArgs); + } + } + + + #endregion Console Command Interface + + #region RegionSyncModule members and functions + + ///////////////////////////////////////////////////////////////////////////////////////// + // Synchronization related functions, NOT exposed through IRegionSyncModule interface + ///////////////////////////////////////////////////////////////////////////////////////// + + private ILog m_log; + //private bool m_active = true; + + private bool m_isSyncListenerLocal = false; + //private RegionSyncListenerInfo m_localSyncListenerInfo + + private HashSet m_remoteSyncListeners; + + private int m_syncConnectorNum = 0; + + private Scene m_scene; + public Scene LocalScene + { + get { return m_scene; } + } + + private IConfig m_sysConfig = null; + private string LogHeader = "[REGION SYNC MODULE]"; + + //The list of SyncConnectors. ScenePersistence could have multiple SyncConnectors, each connecting to a differerent actor. + //An actor could have several SyncConnectors as well, each connecting to a ScenePersistence that hosts a portion of the objects/avatars + //the actor operates on. + private HashSet m_syncConnectors= new HashSet(); + private object m_syncConnectorsLock = new object(); + + //Timers for periodically status report has not been implemented yet. + private System.Timers.Timer m_statsTimer = new System.Timers.Timer(1000); + private void StatsTimerElapsed(object source, System.Timers.ElapsedEventArgs e) + { + //TO BE IMPLEMENTED + m_log.Warn("[REGION SYNC MODULE]: StatsTimerElapsed -- NOT yet implemented."); + } + + private void SendObjectUpdateToRelevantSyncConnectors(SceneObjectGroup sog, SymmetricSyncMessage syncMsg) + { + 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()); + } + } + + /// + /// Check if we need to send out an update message for the given object. + /// + /// + /// + private bool CheckObjectForSendingUpdate(SceneObjectGroup sog) + { + //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. + // Now the communication between RegionSyncModule and ActorSyncModules are through SceneGraph or Scene.EventManager events. + public void OnPostSceneCreation(Scene createdScene) + { + //If this is the local scene the actor is working on, find out the actor type. + if (createdScene.RegionInfo.RegionName == m_scene.RegionInfo.RegionName) + { + if(m_scene.ActorSyncModule == null){ + m_log.Error(LogHeader + "interface Scene.ActorSyncModule has not been set yet"); + return; + } + m_actorType = m_scene.ActorSyncModule.ActorType; + } + } + + private void StartLocalSyncListener() + { + RegionSyncListenerInfo localSyncListenerInfo = GetLocalSyncListenerInfo(); + m_localSyncListener = new RegionSyncListener(localSyncListenerInfo, this); + m_localSyncListener.Start(); + + //STATS TIMER: TO BE IMPLEMENTED + //m_statsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimerElapsed); + //m_statsTimer.Start(); + } + + //Get the information for local IP:Port for listening incoming connection requests. + //For now, we use configuration to access the information. Might be replaced by some Grid Service later on. + private RegionSyncListenerInfo GetLocalSyncListenerInfo() + { + string addr = m_sysConfig.GetString("SyncListenerIPAddress", "127.0.0.1"); + int port = m_sysConfig.GetInt("SyncListenerPort", 13000); + RegionSyncListenerInfo info = new RegionSyncListenerInfo(addr, port); + + return info; + } + + //Get the information for remote [IP:Port] to connect to for synchronization purpose. + //For example, an actor may need to connect to several ScenePersistence's if the objects it operates are hosted collectively + //by these ScenePersistence. + //For now, we use configuration to access the information. Might be replaced by some Grid Service later on. + //And for now, we assume there is only 1 remote listener to connect to. + private void GetRemoteSyncListenerInfo() + { + //For now, we assume there is only one remote listener to connect to. Later on, + //we may need to modify the code to read in multiple listeners. + string addr = m_sysConfig.GetString("SyncListenerIPAddress", "127.0.0.1"); + int port = m_sysConfig.GetInt("SyncListenerPort", 13000); + RegionSyncListenerInfo info = new RegionSyncListenerInfo(addr, port); + + m_remoteSyncListeners = new HashSet(); + + m_remoteSyncListeners.Add(info); + } + + //Start SyncListener if a listener is supposed to run on this actor; Otherwise, initiate connections to remote listeners. + private void SyncStart(Object[] args) + { + if (m_actorType == DSGActorTypes.Unknown) + { + m_log.Error(LogHeader + ": SyncStart -- ActorType not set yet. Either it's not defined in config file (DSGActorType), or the ActorSyncModule (ScenePersistenceSyncModule, ScriptEngineSyncModule etc) has not defined it."); + return; + } + + if (m_isSyncListenerLocal) + { + if (m_localSyncListener.IsListening) + { + m_log.Warn("[REGION SYNC MODULE]: RegionSyncListener is local, already started"); + } + else + { + StartLocalSyncListener(); + } + } + else + { + if (m_remoteSyncListeners == null) + { + GetRemoteSyncListenerInfo(); + } + StartSyncConnections(); + DoInitialSync(); + } + } + + private void SyncStop(Object[] args) + { + if (m_isSyncListenerLocal) + { + if (m_localSyncListener.IsListening) + { + m_localSyncListener.Shutdown(); + } + } + else + { + //Shutdown all sync connectors + StopAllSyncConnectors(); + } + + //Trigger SyncStop event, ActorSyncModules can then take actor specific action if needed. + //For instance, script engine will save script states + //save script state and stop script instances + m_scene.EventManager.TriggerOnSymmetricSyncStop(); + + } + + private void SyncStatus(Object[] args) + { + //TO BE IMPLEMENTED + m_log.Warn("[REGION SYNC MODULE]: SyncStatus() TO BE IMPLEMENTED !!!"); + } + + //Start connections to each remote listener. + //For now, there is only one remote listener. + private void StartSyncConnections() + { + foreach (RegionSyncListenerInfo remoteListener in m_remoteSyncListeners) + { + SyncConnector syncConnector = new SyncConnector(m_syncConnectorNum++, remoteListener, this); + if (syncConnector.Connect()) + { + syncConnector.StartCommThreads(); + AddSyncConnector(syncConnector); + } + } + } + + //To be called when a SyncConnector needs to be created by that the local listener receives a connection request + public void AddNewSyncConnector(TcpClient tcpclient) + { + //Create a SynConnector due to an incoming request, and starts its communication threads + SyncConnector syncConnector = new SyncConnector(m_syncConnectorNum++, tcpclient, this); + syncConnector.StartCommThreads(); + AddSyncConnector(syncConnector); + } + + public void AddSyncConnector(SyncConnector syncConnector) + { + lock (m_syncConnectorsLock) + { + // Create a new list while modifying the list: An optimization for frequent reads and occasional writes. + // Anyone holding the previous version of the list can keep using it since + // they will not hold it for long and get a new copy next time they need to iterate + + HashSet currentlist = m_syncConnectors; + HashSet newlist = new HashSet(currentlist); + newlist.Add(syncConnector); + + m_syncConnectors = newlist; + } + + m_log.Debug("[REGION SYNC MODULE]: new connector " + syncConnector.ConnectorNum); + } + + public void RemoveSyncConnector(SyncConnector syncConnector) + { + lock (m_syncConnectorsLock) + { + // Create a new list while modifying the list: An optimization for frequent reads and occasional writes. + // Anyone holding the previous version of the list can keep using it since + // they will not hold it for long and get a new copy next time they need to iterate + + HashSet currentlist = m_syncConnectors; + HashSet newlist = new HashSet(currentlist); + newlist.Remove(syncConnector); + + m_syncConnectors = newlist; + } + } + + public void StopAllSyncConnectors() + { + lock (m_syncConnectorsLock) + { + foreach (SyncConnector syncConnector in m_syncConnectors) + { + syncConnector.Shutdown(); + } + + m_syncConnectors.Clear(); + } + } + + private void DoInitialSync() + { + m_scene.DeleteAllSceneObjects(); + + SendSyncMessage(SymmetricSyncMessage.MsgType.RegionName, m_scene.RegionInfo.RegionName); + m_log.WarnFormat("Sending region name: \"{0}\"", m_scene.RegionInfo.RegionName); + + SendSyncMessage(SymmetricSyncMessage.MsgType.GetTerrain); + SendSyncMessage(SymmetricSyncMessage.MsgType.GetObjects); + //Send(new RegionSyncMessage(RegionSyncMessage.MsgType.GetAvatars)); + + //We'll deal with Event a bit later + + // Register for events which will be forwarded to authoritative scene + // m_scene.EventManager.OnNewClient += EventManager_OnNewClient; + //m_scene.EventManager.OnMakeRootAgent += EventManager_OnMakeRootAgent; + //m_scene.EventManager.OnMakeChildAgent += EventManager_OnMakeChildAgent; + //m_scene.EventManager.OnClientClosed += new EventManager.ClientClosed(RemoveLocalClient); + } + + /// + /// This function will enqueue a message for each SyncConnector in the connector's outgoing queue. + /// Each SyncConnector has a SendLoop thread to send the messages in its outgoing queue. + /// + /// + /// + private void SendSyncMessage(SymmetricSyncMessage.MsgType msgType, string data) + { + //See RegionSyncClientView for initial implementation by Dan Lake + + SymmetricSyncMessage msg = new SymmetricSyncMessage(msgType, data); + ForEachSyncConnector(delegate(SyncConnector syncConnector) + { + syncConnector.Send(msg); + }); + } + + private void SendSyncMessage(SymmetricSyncMessage.MsgType msgType) + { + //See RegionSyncClientView for initial implementation by Dan Lake + + SendSyncMessage(msgType, ""); + } + + public void ForEachSyncConnector(Action action) + { + List closed = null; + foreach (SyncConnector syncConnector in m_syncConnectors) + { + // If connected, apply the action + if (syncConnector.Connected) + { + action(syncConnector); + } + // Else, remove the SyncConnector from the list + else + { + if (closed == null) + closed = new List(); + closed.Add(syncConnector); + } + } + + if (closed != null) + { + foreach (SyncConnector connector in closed) + { + RemoveSyncConnector(connector); + } + } + } + + + + /// + /// The handler for processing incoming sync messages. + /// + /// + public void HandleIncomingMessage(SymmetricSyncMessage msg) + { + switch (msg.Type) + { + case SymmetricSyncMessage.MsgType.GetTerrain: + { + //SendSyncMessage(SymmetricSyncMessage.MsgType.Terrain, m_scene.Heightmap.SaveToXmlString()); + SendTerrainUpdateMessage(); + return; + } + case SymmetricSyncMessage.MsgType.Terrain: + { + /* + m_scene.Heightmap.LoadFromXmlString(Encoding.ASCII.GetString(msg.Data, 0, msg.Length)); + //Inform the terrain module that terrain has been updated + m_scene.RequestModuleInterface().TaintTerrain(); + m_log.Debug(LogHeader+": Synchronized terrain"); + * */ + HandleTerrainUpdateMessage(msg); + return; + } + case SymmetricSyncMessage.MsgType.GetObjects: + { + EntityBase[] entities = m_scene.GetEntities(); + foreach (EntityBase e in entities) + { + if (e is SceneObjectGroup) + { + string sogxml = SceneObjectSerializer.ToXml2Format((SceneObjectGroup)e); + SendSyncMessage(SymmetricSyncMessage.MsgType.NewObject, sogxml); + + //m_log.Debug(LogHeader + ": " + sogxml); + } + } + return; + } + case SymmetricSyncMessage.MsgType.NewObject: + case SymmetricSyncMessage.MsgType.UpdatedObject: + { + HandleAddOrUpdateObjectBySynchronization(msg); + //HandleAddNewObject(sog); + return; + } + case SymmetricSyncMessage.MsgType.RemovedObject: + { + HandleRemovedObject(msg); + return; + } + default: + return; + } + } + + private void HandleTerrainUpdateMessage(SymmetricSyncMessage msg) + { + // Get the data from message and error check + OSDMap data = DeserializeMessage(msg); + + if (data == null) + { + SymmetricSyncMessage.HandleError(LogHeader, msg, "Could not deserialize JSON data."); + return; + } + + string msgData = data["terrain"].AsString(); + long lastUpdateTimeStamp = data["actorID"].AsLong(); + string lastUpdateActorID = data["timeStamp"].AsString(); + + //set new terrain + m_scene.Heightmap.LoadFromXmlString(msgData); + m_scene.RequestModuleInterface().TaintTerrianBySynchronization(lastUpdateTimeStamp, lastUpdateActorID); ; + m_log.Debug(LogHeader + ": Synchronized terrain"); + } + + private 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 SendTerrainUpdateMessage() + { + string msgData = m_scene.Heightmap.SaveToXmlString(); + long lastUpdateTimeStamp; + string lastUpdateActorID; + m_scene.RequestModuleInterface().GetSyncInfo(out lastUpdateTimeStamp, out lastUpdateActorID); + + OSDMap data = new OSDMap(3); + data["terrain"] = OSD.FromString(msgData); + data["actorID"] = OSD.FromString(lastUpdateActorID); + data["timeStamp"] = OSD.FromLong(lastUpdateTimeStamp); + + SendSyncMessage(SymmetricSyncMessage.MsgType.Terrain, OSDParser.SerializeJsonString(data)); + } + + private void HandleRemovedObject(SymmetricSyncMessage msg) + { + // Get the data from message and error check + OSDMap data = DeserializeMessage(msg); + + if (data == null) + { + + SymmetricSyncMessage.HandleError(LogHeader, msg, "Could not deserialize JSON data."); + return; + } + + // Get the parameters from data + //ulong regionHandle = data["regionHandle"].AsULong(); + //uint localID = data["UUID"].AsUInteger(); + UUID sogUUID = data["UUID"].AsUUID(); + + SceneObjectGroup sog = m_scene.SceneGraph.GetGroupByPrim(sogUUID); + if (sog != null) + { + m_scene.DeleteSceneObjectBySynchronization(sog); + } + } + + HashSet exceptions = new HashSet(); + private OSDMap DeserializeMessage(SymmetricSyncMessage msg) + { + OSDMap data = null; + try + { + data = OSDParser.DeserializeJson(Encoding.ASCII.GetString(msg.Data, 0, msg.Length)) as OSDMap; + } + catch (Exception e) + { + lock (exceptions) + // If this is a new message, then print the underlying data that caused it + if (!exceptions.Contains(e.Message)) + m_log.Error(LogHeader + " " + Encoding.ASCII.GetString(msg.Data, 0, msg.Length)); + data = null; + } + return data; + } + + private void HandleAddNewObject(SceneObjectGroup sog) + { + //RegionSyncModule only add object to SceneGraph. Any actor specific actions will be implemented + //by each ActorSyncModule, which would be triggered its subcription to event SceneGraph.OnObjectCreate. + bool attachToBackup = false; + + if (m_scene.AddNewSceneObject(sog, attachToBackup)) + { + m_log.Debug(LogHeader + ": added obj " + sog.UUID); + } + } + + + /// + /// Send a sync message to remove the given objects in all connected actors, if this is a relay node. + /// UUID is used for identified a removed object. + /// + /// + private void RegionSyncModule_OnObjectBeingRemovedFromScene(SceneObjectGroup sog) + { + //m_log.DebugFormat("RegionSyncModule_OnObjectBeingRemovedFromScene called at time {0}:{1}:{2}", DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond); + + //Only send the message out if this is a relay node for sync messages, or this actor caused deleting the object + if (m_isSyncRelay || CheckObjectForSendingUpdate(sog)) + { + OSDMap data = new OSDMap(1); + //data["regionHandle"] = OSD.FromULong(regionHandle); + //data["localID"] = OSD.FromUInteger(sog.LocalId); + data["UUID"] = OSD.FromUUID(sog.UUID); + + SymmetricSyncMessage rsm = new SymmetricSyncMessage(SymmetricSyncMessage.MsgType.RemovedObject, OSDParser.SerializeJsonString(data)); + SendObjectUpdateToRelevantSyncConnectors(sog, rsm); + } + + + } + + #endregion //RegionSyncModule members and functions + + } + + public class RegionSyncListenerInfo + { + public IPAddress Addr; + public int Port; + + //TO ADD: reference to RegionInfo that describes the shape/size of the space that the listener is associated with + + public RegionSyncListenerInfo(string addr, int port) + { + Addr = IPAddress.Parse(addr); + Port = port; + } + } + + public class RegionSyncListener + { + private RegionSyncListenerInfo m_listenerInfo; + private RegionSyncModule m_regionSyncModule; + private ILog m_log; + private string LogHeader = "[RegionSyncListener]"; + + // The listener and the thread which listens for sync connection requests + private TcpListener m_listener; + private Thread m_listenerThread; + + private bool m_isListening = false; + public bool IsListening + { + get { return m_isListening; } + } + + public RegionSyncListener(RegionSyncListenerInfo listenerInfo, RegionSyncModule regionSyncModule) + { + m_listenerInfo = listenerInfo; + m_regionSyncModule = regionSyncModule; + + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + } + + // Start the listener + public void Start() + { + m_listenerThread = new Thread(new ThreadStart(Listen)); + m_listenerThread.Name = "RegionSyncListener"; + m_log.WarnFormat(LogHeader+" Starting {0} thread", m_listenerThread.Name); + m_listenerThread.Start(); + m_isListening = true; + //m_log.Warn("[REGION SYNC SERVER] Started"); + } + + // Stop the server and disconnect all RegionSyncClients + public void Shutdown() + { + // Stop the listener and listening thread so no new clients are accepted + m_listener.Stop(); + + //Aborting the listener thread probably is not the best way to shutdown, but let's worry about that later. + m_listenerThread.Abort(); + m_listenerThread = null; + m_isListening = false; + } + + // Listen for connections from a new SyncConnector + // When connected, start the ReceiveLoop for the new client + private void Listen() + { + m_listener = new TcpListener(m_listenerInfo.Addr, m_listenerInfo.Port); + + try + { + // Start listening for clients + m_listener.Start(); + while (true) + { + // *** Move/Add TRY/CATCH to here, but we don't want to spin loop on the same error + m_log.WarnFormat("[REGION SYNC SERVER] Listening for new connections on {0}:{1}...", m_listenerInfo.Addr.ToString(), m_listenerInfo.Port.ToString()); + TcpClient tcpclient = m_listener.AcceptTcpClient(); + + //Create a SynConnector and starts it communication threads + m_regionSyncModule.AddNewSyncConnector(tcpclient); + } + } + catch (SocketException e) + { + m_log.WarnFormat("[REGION SYNC SERVER] [Listen] SocketException: {0}", e); + } + } + + } + +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScenePersistenceSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScenePersistenceSyncModule.cs new file mode 100755 index 0000000000..f922b66980 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScenePersistenceSyncModule.cs @@ -0,0 +1,174 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using log4net; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")] + public class ScenePersistenceSyncModule : INonSharedRegionModule, IDSGActorSyncModule + { + #region INonSharedRegionModule + + public void Initialise(IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + IConfig syncConfig = config.Configs["RegionSyncModule"]; + m_active = false; + if (syncConfig == null) + { + m_log.Warn(LogHeader + " No RegionSyncModule config section found. Shutting down."); + return; + } + else if (!syncConfig.GetBoolean("Enabled", false)) + { + m_log.Warn(LogHeader + " RegionSyncModule is not enabled. Shutting down."); + return; + } + + string actorType = syncConfig.GetString("DSGActorType", "").ToLower(); + if (!actorType.Equals("scene_persistence")) + { + m_log.Warn(LogHeader + ": not configured as Scene Persistence Actor. Shutting down."); + return; + } + + m_actorID = syncConfig.GetString("ActorID", ""); + if (m_actorID.Equals("")) + { + m_log.Warn(LogHeader + ": ActorID not specified in config file. Shutting down."); + return; + } + + m_active = true; + + m_log.Warn(LogHeader+" Initialised"); + + } + + //Called after Initialise() + public void AddRegion(Scene scene) + { + if (!m_active) + return; + m_log.Warn(LogHeader + " AddRegion() called"); + //connect with scene + m_scene = scene; + + //register the module with SceneGraph. If needed, SceneGraph checks the module's ActorType to know what type of module it is. + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + //m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + //InstallInterfaces(); + + //Register for the OnPostSceneCreation event + //m_scene.EventManager.OnPostSceneCreation += OnPostSceneCreation; + + //Register for Scene/SceneGraph events + m_scene.SceneGraph.OnObjectCreate += new ObjectCreateDelegate(ScenePersistence_OnObjectCreate); + } + + //Called after AddRegion() has been called for all region modules of the scene. + //NOTE::However, at this point, Scene may not have requested all the needed region module interfaces yet. + // So to try to access other region modules in RegionLoaded, e.g. RegionSyncModule, is not a good idea. + public void RegionLoaded(Scene scene) + { + if (!m_active) + return; + m_log.Warn(LogHeader + " RegionLoaded() called"); + + } + + public void RemoveRegion(Scene scene) + { + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Close() + { + m_scene = null; + } + + public string Name + { + get { return "ScenePersistenceSyncModule"; } + } + + #endregion //INonSharedRegionModule + + #region IDSGActorSyncModule members and functions + + private DSGActorTypes m_actorType = DSGActorTypes.ScenePersistence; + public DSGActorTypes ActorType + { + get { return m_actorType; } + } + + private string m_actorID; + public string ActorID + { + get { return m_actorID; } + } + + #endregion //IDSGActorSyncModule + + #region ScenePersistenceSyncModule memebers and functions + private ILog m_log; + private bool m_active = false; + public bool Active + { + get { return m_active; } + } + + private Scene m_scene; + + private string LogHeader = "[ScenePersistenceSyncModule]"; + + public void OnPostSceneCreation(Scene createdScene) + { + //If this is the local scene the actor is working on, do something + if (createdScene == m_scene) + { + } + } + + /// + /// ScenePersistence's actions upon an object is added to the local scene. + /// + private void ScenePersistence_OnObjectCreate(EntityBase entity) + { + if (entity is SceneObjectGroup) + { + m_log.Warn(LogHeader + ": link to backup for " + entity.UUID); + SceneObjectGroup sog = (SceneObjectGroup)entity; + sog.AttachToBackup(); + } + } + + #endregion //ScenePersistenceSyncModule + } + + + +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScriptEngineSyncModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScriptEngineSyncModule.cs new file mode 100755 index 0000000000..6c24792216 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/ScriptEngineSyncModule.cs @@ -0,0 +1,184 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using log4net; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Mono.Addins; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AttachmentsModule")] + public class ScriptEngineSyncModule : INonSharedRegionModule, IDSGActorSyncModule + { + #region INonSharedRegionModule + + public void Initialise(IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + IConfig syncConfig = config.Configs["RegionSyncModule"]; + m_active = false; + if (syncConfig == null) + { + m_log.Warn(LogHeader + " No RegionSyncModule config section found. Shutting down."); + return; + } + else if (!syncConfig.GetBoolean("Enabled", false)) + { + m_log.Warn(LogHeader + " RegionSyncModule is not enabled. Shutting down."); + return; + } + + string actorType = syncConfig.GetString("DSGActorType", "").ToLower(); + if (!actorType.Equals("script_engine")) + { + m_log.Warn(LogHeader + ": not configured as Scene Persistence Actor. Shut down."); + return; + } + + m_actorID = syncConfig.GetString("ActorID", ""); + if (m_actorID.Equals("")) + { + m_log.Warn(LogHeader + ": ActorID not specified in config file. Shutting down."); + return; + } + + m_active = true; + + m_log.Warn(LogHeader + " Initialised"); + + } + + //Called after Initialise() + public void AddRegion(Scene scene) + { + if (!m_active) + return; + m_log.Warn(LogHeader + " AddRegion() called"); + //connect with scene + m_scene = scene; + + //register the module with SceneGraph. If needed, SceneGraph checks the module's ActorType to know what type of module it is. + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + //m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + //InstallInterfaces(); + + //Register for the OnPostSceneCreation event + //m_scene.EventManager.OnPostSceneCreation += OnPostSceneCreation; + + //Register for Scene/SceneGraph events + m_scene.SceneGraph.OnObjectCreate += new ObjectCreateDelegate(ScriptEngine_OnObjectCreate); + m_scene.EventManager.OnSymmetricSyncStop += ScriptEngine_OnSymmetricSyncStop; + } + + //Called after AddRegion() has been called for all region modules of the scene. + //NOTE::However, at this point, Scene may not have requested all the needed region module interfaces yet. + public void RegionLoaded(Scene scene) + { + if (!m_active) + return; + + } + + public void RemoveRegion(Scene scene) + { + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Close() + { + m_scene = null; + } + + public string Name + { + get { return "ScriptEngineSyncModule"; } + } + + #endregion //INonSharedRegionModule + + #region IDSGActorSyncModule members and functions + + private DSGActorTypes m_actorType = DSGActorTypes.ScriptEngine; + public DSGActorTypes ActorType + { + get { return m_actorType; } + } + + private string m_actorID; + public string ActorID + { + get { return m_actorID; } + } + + #endregion //IDSGActorSyncModule + + + #region ScriptEngineSyncModule memebers and functions + private ILog m_log; + private bool m_active = false; + public bool Active + { + get { return m_active; } + } + + private Scene m_scene; + + private string LogHeader = "[ScriptEngineSyncModule]"; + + public void OnPostSceneCreation(Scene createdScene) + { + //If this is the local scene the actor is working on, do something + if (createdScene == m_scene) + { + } + } + + /// + /// Script Engine's action upon an object is added to the local scene + /// + private void ScriptEngine_OnObjectCreate(EntityBase entity) + { + if (entity is SceneObjectGroup) + { + m_log.Warn(LogHeader + ": start script for obj " + entity.UUID); + SceneObjectGroup sog = (SceneObjectGroup)entity; + sog.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, 0); + sog.ResumeScripts(); + } + } + + public void ScriptEngine_OnSymmetricSyncStop() + { + //Inform script engine to save script states and stop scripts + m_scene.EventManager.TriggerScriptEngineSyncStop(); + //remove all objects + m_scene.DeleteAllSceneObjects(); + } + + #endregion //ScriptEngineSyncModule + + } + + +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs new file mode 100755 index 0000000000..b4b960ace1 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SymmetricSyncMessage.cs @@ -0,0 +1,153 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.IO; +using OpenMetaverse; +using log4net; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + //Initial code in SymmetricSyncMessage copied from RegionSyncMessage. + + /// + /// Types of symmetric sync messages among actors. + /// NOTE:: To enable message subscription, we may need to move the definition of MsgType to, say IRegionSyncModule, so that it can be exposed to other region modules. + /// + public class SymmetricSyncMessage + { + #region MsgType Enum + public enum MsgType + { + Null, + // Actor -> SIM(Scene) + GetTerrain, + GetObjects, + + // SIM -> CM + Terrain, + NewObject, // objects + UpdatedObject, // objects + RemovedObject, // objects + // BIDIR + //EchoRequest, + //EchoResponse, + RegionName, + //RegionStatus, + ActorID, + } + #endregion + + #region Member Data + private MsgType m_type; + private byte[] m_data; + static ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + #region Constructors + public SymmetricSyncMessage(MsgType type, byte[] data) + { + m_type = type; + m_data = data; + } + + public SymmetricSyncMessage(MsgType type, string msg) + { + m_type = type; + m_data = System.Text.Encoding.ASCII.GetBytes(msg); + } + + public SymmetricSyncMessage(MsgType type) + { + m_type = type; + m_data = new byte[0]; + } + + public SymmetricSyncMessage(Stream stream) + { + //ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + //try + { + m_type = (MsgType)Utils.BytesToInt(GetBytesFromStream(stream, 4)); + int length = Utils.BytesToInt(GetBytesFromStream(stream, 4)); + m_data = GetBytesFromStream(stream, length); + //log.WarnFormat("RegionSyncMessage Constructed {0} ({1} bytes)", m_type.ToString(), length); + } + } + + private byte[] GetBytesFromStream(Stream stream, int count) + { + // Loop to receive the message length + byte[] ret = new byte[count]; + int i = 0; + while (i < count) + { + i += stream.Read(ret, i, count - i); + } + return ret; + } + + #endregion + + #region Accessors + public MsgType Type + { + get { return m_type; } + } + + public int Length + { + get { return m_data.Length; } + } + + public byte[] Data + { + get { return m_data; } + } + #endregion + + #region Conversions + public byte[] ToBytes() + { + byte[] buf = new byte[m_data.Length + 8]; + Utils.IntToBytes((int)m_type, buf, 0); + Utils.IntToBytes(m_data.Length, buf, 4); + Array.Copy(m_data, 0, buf, 8, m_data.Length); + return buf; + } + + public override string ToString() + { + return String.Format("{0} ({1} bytes)", m_type.ToString(), m_data.Length.ToString()); + } + #endregion + + + 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, SymmetricSyncMessage msg, string message) + { + m_log.WarnFormat("{0} Issue handling {1}: {2}", header, msg.ToString(), 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, SymmetricSyncMessage msg, string message) + { + m_log.WarnFormat("{0} Error handling {1}: {2}", header, msg.ToString(), message); + } + + public static bool HandlerDebug(string header, SymmetricSyncMessage msg, string message) + { + m_log.WarnFormat("{0} DBG ({1}): {2}", header, msg.ToString(), message); + return true; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs new file mode 100755 index 0000000000..6f2e915b36 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SymmetricSync/SyncConnector.cs @@ -0,0 +1,298 @@ +/* + * Copyright (c) Contributors: TO BE FILLED + */ + +using System; +using System.IO; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Threading; +using System.Text; +using log4net; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + // For implementations, a lot was copied from RegionSyncClientView, especially the SendLoop/ReceiveLoop. + public class SyncConnector + { + private TcpClient m_tcpConnection = null; + private RegionSyncListenerInfo m_remoteListenerInfo = null; + private Thread m_rcvLoop; + private Thread m_send_loop; + + private string LogHeader = "[SYNC CONNECTOR]"; + // The logfile + private ILog m_log; + + //members for in/out messages queueing + object stats = new object(); + private long queuedUpdates=0; + private long dequeuedUpdates=0; + private long msgsIn=0; + private long msgsOut=0; + private long bytesIn=0; + private long bytesOut=0; + private int msgCount = 0; + // A queue for outgoing traffic. + private BlockingUpdateQueue m_outQ = new BlockingUpdateQueue(); + + private RegionSyncModule m_regionSyncModule = null; + + private int m_connectorNum; + public int ConnectorNum + { + 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 + { + get { return m_syncOtherSideRegionName; } + } + + // Check if the client is connected + public bool Connected + { get { return (m_tcpConnection !=null && m_tcpConnection.Connected); } } + + public string Description + { + get + { + if (m_syncOtherSideRegionName == null) + return String.Format("SyncConnector #{0}", m_connectorNum); + return String.Format("SyncConnector #{0} ({1:10})", m_connectorNum, m_syncOtherSideRegionName); + } + } + + /// + /// The constructor that will be called when a SyncConnector is created passively: a remote SyncConnector has initiated the connection. + /// + /// + /// + public SyncConnector(int connectorNum, TcpClient tcpclient, RegionSyncModule syncModule) + { + m_tcpConnection = tcpclient; + m_connectorNum = connectorNum; + m_regionSyncModule = syncModule; + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + } + + /// + /// The constructor that will be called when a SyncConnector is created actively: it is created to send connection request to a remote listener + /// + /// + /// + public SyncConnector(int connectorNum, RegionSyncListenerInfo listenerInfo, RegionSyncModule syncModule) + { + m_remoteListenerInfo = listenerInfo; + m_connectorNum = connectorNum; + m_regionSyncModule = syncModule; + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + } + + //Connect to the remote listener + public bool Connect() + { + m_tcpConnection = new TcpClient(); + try + { + m_tcpConnection.Connect(m_remoteListenerInfo.Addr, m_remoteListenerInfo.Port); + } + catch (Exception e) + { + m_log.WarnFormat("{0} [Start] Could not connect to RegionSyncServer at {1}:{2}", LogHeader, m_remoteListenerInfo.Addr, m_remoteListenerInfo.Port); + m_log.Warn(e.Message); + return false; + } + return true; + } + + /// + /// Start both the send and receive threads + /// + public void StartCommThreads() + { + // Create a thread for the receive loop + m_rcvLoop = new Thread(new ThreadStart(ReceiveLoop)); + m_rcvLoop.Name = Description + " (ReceiveLoop)"; + m_log.WarnFormat("{0} Starting {1} thread", Description, m_rcvLoop.Name); + m_rcvLoop.Start(); + + // Create a thread for the send loop + m_send_loop = new Thread(new ThreadStart(delegate() { SendLoop(); })); + m_send_loop.Name = Description + " (SendLoop)"; + m_log.WarnFormat("{0} Starting {1} thread", Description, m_send_loop.Name); + m_send_loop.Start(); + } + + public void Shutdown() + { + m_log.Warn(LogHeader + " shutdown connection"); + // Abort receive and send loop + m_rcvLoop.Abort(); + m_send_loop.Abort(); + + // Close the connection + m_tcpConnection.Client.Close(); + m_tcpConnection.Close(); + } + + /////////////////////////////////////////////////////////// + // Sending messages out to the other side of the connection + /////////////////////////////////////////////////////////// + // Send messages from the update Q as fast as we can DeQueue them + // *** This is the main send loop thread for each connected client + private void SendLoop() + { + try + { + while (true) + { + // Dequeue is thread safe + byte[] update = m_outQ.Dequeue(); + lock (stats) + dequeuedUpdates++; + Send(update); + } + } + catch (Exception e) + { + m_log.ErrorFormat("{0} has disconnected: {1} (SendLoop)", Description, e.Message); + } + Shutdown(); + } + + /// + /// Enqueue update of an object/avatar into the outgoing queue, and return right away + /// + /// UUID of the object/avatar + /// the update infomation in byte format + public void EnqueueOutgoingUpdate(UUID id, byte[] update) + { + lock (stats) + queuedUpdates++; + // Enqueue is thread safe + m_outQ.Enqueue(id, update); + } + + //Send out a messge directly. This should only by called for short messages that are not sent frequently. + //Don't call this function for sending out updates. Call EnqueueOutgoingUpdate instead + public void Send(SymmetricSyncMessage msg) + { + Send(msg.ToBytes()); + } + + private void Send(byte[] data) + { + if (m_tcpConnection.Connected) + { + try + { + lock (stats) + { + msgsOut++; + bytesOut += data.Length; + } + m_tcpConnection.GetStream().BeginWrite(data, 0, data.Length, ar => + { + if (m_tcpConnection.Connected) + { + try + { + m_tcpConnection.GetStream().EndWrite(ar); + } + catch (Exception) + { } + } + }, null); + } + catch (IOException) + { + m_log.WarnFormat("{0}:{1} has disconnected.", Description, m_connectorNum); + } + } + } + + /////////////////////////////////////////////////////////// + // Receiving messages from the other side ofthe connection + /////////////////////////////////////////////////////////// + private void ReceiveLoop() + { + m_log.WarnFormat("{0} Thread running: {1}", LogHeader, m_rcvLoop.Name); + while (true && m_tcpConnection.Connected) + { + SymmetricSyncMessage msg; + // Try to get the message from the network stream + try + { + msg = new SymmetricSyncMessage(m_tcpConnection.GetStream()); + //m_log.WarnFormat("{0} Received: {1}", LogHeader, msg.ToString()); + } + // If there is a problem reading from the client, shut 'er down. + catch + { + //ShutdownClient(); + m_log.WarnFormat("{0}:{1} has disconnected.", Description, m_connectorNum); + Shutdown(); + return; + } + // Try handling the message + try + { + HandleMessage(msg); + } + catch (Exception e) + { + m_log.WarnFormat("{0} Encountered an exception: {1} (MSGTYPE = {2})", Description, e.Message, msg.ToString()); + } + } + } + + private void HandleMessage(SymmetricSyncMessage msg) + { + + msgCount++; + switch (msg.Type) + { + case SymmetricSyncMessage.MsgType.RegionName: + { + m_syncOtherSideRegionName = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + if (m_regionSyncModule.IsSyncRelay) + { + SymmetricSyncMessage outMsg = new SymmetricSyncMessage(SymmetricSyncMessage.MsgType.RegionName, m_regionSyncModule.LocalScene.RegionInfo.RegionName); + Send(outMsg); + } + 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; + } + + //For any other messages, we simply deliver the message to RegionSyncModule for now. + //Later on, we may deliver messages to different modules, say sync message to RegionSyncModule and event message to ActorSyncModule. + m_regionSyncModule.HandleIncomingMessage(msg); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index 25d73c2c0f..276fcfe9a2 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -589,6 +589,56 @@ namespace OpenSim.Region.CoreModules.World.Terrain client.OnUnackedTerrain += client_OnUnackedTerrain; } + //SYMMETRIC SYNC + private long m_lastUpdateTimeStamp = DateTime.Now.Ticks; + public long LastUpdateTimeStamp + { + get { return m_lastUpdateTimeStamp; } + set { m_lastUpdateTimeStamp = value; } + } + + private string m_lastUpdateActorID; + public string LastUpdateActorID + { + get { return m_lastUpdateActorID; } + set { m_lastUpdateActorID = value; } + } + + private void SyncInfoUpdate(long timeStamp, string actorID) + { + m_lastUpdateTimeStamp = timeStamp; + m_lastUpdateActorID = actorID; + } + + /* + public void CheckForTerrainUpdatesBySynchronization(long timeStamp, string actorID) + { + SyncInfoUpdate(timeStamp, actorID); + CheckForTerrainUpdates(false); + } + * */ + + public void TaintTerrianBySynchronization(long timeStamp, string actorID) + { + SyncInfoUpdate(timeStamp, actorID); + CheckForTerrainUpdates(false, timeStamp, actorID); + } + + public bool TerrianModifiedLocally(string localActorID) + { + if (localActorID == m_lastUpdateActorID) + return true; + return false; + } + + public void GetSyncInfo(out long lastUpdateTimeStamp, out string lastUpdateActorID) + { + lastUpdateTimeStamp = m_lastUpdateTimeStamp; + lastUpdateActorID = m_lastUpdateActorID; + } + + //end of SYMMETRIC SYNC + /// /// Checks to see if the terrain has been modified since last check /// but won't attempt to limit those changes to the limits specified in the estate settings @@ -596,7 +646,21 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// private void CheckForTerrainUpdates() { - CheckForTerrainUpdates(false); + //SYMMETRIC SYNC + + //Assumption: Thus function is only called when the terrain is updated by the local actor. + // Updating terrain during receiving sync messages from another actor will call CheckForTerrainUpdates. + + //Update the timestamp to the current time tick, and set the LastUpdateActorID to be self + long currentTimeTick = DateTime.Now.Ticks; + string localActorID = m_scene.GetSyncActorID(); + SyncInfoUpdate(currentTimeTick, localActorID); + //Check if the terrain has been modified and send out sync message if modified. + CheckForTerrainUpdates(false, currentTimeTick, localActorID); + + //end of SYMMETRIC SYNC + + //CheckForTerrainUpdates(false); } /// @@ -607,7 +671,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces /// should height map deltas be limited to the estate settings limits /// - private void CheckForTerrainUpdates(bool respectEstateSettings) + //private void CheckForTerrainUpdates(bool respectEstateSettings) + //SYMMETRIC SYNC: Change the interface, to input the right sync information for the most recent update + private void CheckForTerrainUpdates(bool respectEstateSettings, long lastUpdateTimeStamp, string lastUpdateActorID) + //end of SYMMETRIC SYNC { bool shouldTaint = false; float[] serialised = m_channel.GetFloatsSerialised(); @@ -636,6 +703,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (shouldTaint) { m_tainted = true; + //SYMMETRIC SYNC + //Terrain has been modified, send out sync message if needed + if (m_scene.RegionSyncModule != null) + { + m_scene.RegionSyncModule.SendTerrainUpdates(m_lastUpdateActorID); + } + //end of SYMMETRIC SYNC } } @@ -748,7 +822,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_painteffects[(StandardTerrainEffects) action].PaintEffect( m_channel, allowMask, west, south, height, size, seconds); - CheckForTerrainUpdates(!god); //revert changes outside estate limits + //CheckForTerrainUpdates(!god); //revert changes outside estate limits + //SYMMETRIC SYNC + CheckForTerrainUpdates(!god, DateTime.Now.Ticks, m_scene.GetSyncActorID()); + //end of SYMMETRIC SYNC } } else @@ -789,7 +866,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_floodeffects[(StandardTerrainEffects) action].FloodEffect( m_channel, fillArea, size); - CheckForTerrainUpdates(!god); //revert changes outside estate limits + //CheckForTerrainUpdates(!god); //revert changes outside estate limits + //SYMMETRIC SYNC + CheckForTerrainUpdates(!god, DateTime.Now.Ticks, m_scene.GetSyncActorID()); + //end of SYMMETRIC SYNC } } else diff --git a/OpenSim/Region/Framework/Interfaces/ITerrainModule.cs b/OpenSim/Region/Framework/Interfaces/ITerrainModule.cs index 5947afb927..58e7022cee 100644 --- a/OpenSim/Region/Framework/Interfaces/ITerrainModule.cs +++ b/OpenSim/Region/Framework/Interfaces/ITerrainModule.cs @@ -64,5 +64,21 @@ namespace OpenSim.Region.Framework.Interfaces void InstallPlugin(string name, ITerrainEffect plug); void UndoTerrain(ITerrainChannel channel); + + //SYMMETRIC SYNC + void TaintTerrianBySynchronization(long timeStamp, string actorID); + /// + /// Return true if the most recent update on terrain is done locally (i.e. not by receiving a terrain-sync message). + /// + /// + /// + bool TerrianModifiedLocally(string localActorID); + /// + /// Obtain the timestemp and actorID information for the most recent update on terrain. + /// + /// + /// + void GetSyncInfo(out long lastUpdateTimeStamp, out string lastUpdateActorID); + //end of SYMMETRIC SYNC } } diff --git a/OpenSim/Region/Framework/Scenes/EventManager.cs b/OpenSim/Region/Framework/Scenes/EventManager.cs index a6ae574781..7ba4f39d97 100644 --- a/OpenSim/Region/Framework/Scenes/EventManager.cs +++ b/OpenSim/Region/Framework/Scenes/EventManager.cs @@ -376,7 +376,6 @@ namespace OpenSim.Region.Framework.Scenes public delegate void RegionUp(GridRegion region); public event RegionUp OnRegionUp; - public class MoneyTransferArgs : EventArgs { public UUID sender; @@ -2184,7 +2183,7 @@ namespace OpenSim.Region.Framework.Scenes catch (Exception e) { m_log.ErrorFormat( - "[EVENT MANAGER]: Delegate for TriggerOnSceneObjectLoaded failed - continuing. {0} {1}", + "[EVENT MANAGER]: Delegate for TriggerScriptEngineSyncStop failed - continuing. {0} {1}", e.Message, e.StackTrace); } } @@ -2243,5 +2242,55 @@ namespace OpenSim.Region.Framework.Scenes } } #endregion + + //SYMMETRIC SYNC + public event PostSceneCreation OnPostSceneCreation; + public delegate void PostSceneCreation(Scene createdScene); + + public void TriggerOnPostSceneCreation(Scene createdScene) + { + PostSceneCreation handler = OnPostSceneCreation; + if (handler != null) + { + foreach (PostSceneCreation d in handler.GetInvocationList()) + { + try + { + d(createdScene); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnPostSceneCreation failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + + public delegate void SymmetricSyncStop(); + public event SymmetricSyncStop OnSymmetricSyncStop; + public void TriggerOnSymmetricSyncStop() + { + SymmetricSyncStop handlerSymmetricSyncStop = OnSymmetricSyncStop; + if (handlerSymmetricSyncStop != null) + { + foreach (SymmetricSyncStop d in handlerSymmetricSyncStop.GetInvocationList()) + { + try + { + d(); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[EVENT MANAGER]: Delegate for TriggerOnSymmetricSyncStop failed - continuing. {0} {1}", + e.Message, e.StackTrace); + } + } + } + } + + //end of SYMMETRIC SYNC } } diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 41b9f2e5dc..95ecfb3d5f 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -408,7 +408,10 @@ namespace OpenSim.Region.Framework.Scenes { get { return m_AvatarFactory; } } - #region REGION SYNC + + + #region REGION SYNC -- Asymmetric sync, old style, depreciated --------- + protected IRegionSyncServerModule m_regionSyncServerModule; protected IRegionSyncClientModule m_regionSyncClientModule; @@ -576,6 +579,100 @@ namespace OpenSim.Region.Framework.Scenes // } #endregion + + + #region SYMMETRIC SYNC + /////////////////////////////////////////////////////////////////////////////////////////////// + //KittyL: 12/23/2010. SYMMETRIC SYNC: Implementation for the symmetric synchronization model. + /////////////////////////////////////////////////////////////////////////////////////////////// + + private IRegionSyncModule m_regionSyncModule = null; + public IRegionSyncModule RegionSyncModule + { + get { return m_regionSyncModule; } + //set { m_regionSyncModule = value; } + } + + private IDSGActorSyncModule m_DSGActorSyncModule = null; + public IDSGActorSyncModule ActorSyncModule + { + get { return m_DSGActorSyncModule; } + + } + + //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 + } + + public string GetSyncActorID() + { + if (m_DSGActorSyncModule != null) + { + return m_DSGActorSyncModule.ActorID; + } + return ""; + } + + //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); + } + + //Similar to DeleteSceneObject, except that this does not change LastUpdateActorID and LastUpdateTimeStamp + public void DeleteSceneObjectBySynchronization(SceneObjectGroup group) + { + // m_log.DebugFormat("[SCENE]: Deleting scene object {0} {1}", group.Name, group.UUID); + + //SceneObjectPart rootPart = group.GetChildPart(group.UUID); + + // Serialise calls to RemoveScriptInstances to avoid + // deadlocking on m_parts inside SceneObjectGroup + lock (m_deleting_scene_object) + { + group.RemoveScriptInstances(true); + } + + SceneObjectPart[] partList = group.Parts; + + foreach (SceneObjectPart part in partList) + { + if (part.IsJoint() && ((part.Flags & PrimFlags.Physics) != 0)) + { + PhysicsScene.RequestJointDeletion(part.Name); // FIXME: what if the name changed? + } + else if (part.PhysActor != null) + { + PhysicsScene.RemovePrim(part.PhysActor); + part.PhysActor = null; + } + } + + if (UnlinkSceneObject(group, false)) + { + EventManager.TriggerObjectBeingRemovedFromScene(group); + EventManager.TriggerParcelPrimCountTainted(); + } + + bool silent = false; //do not suppress broadcasting changes to other clients, for debugging with viewers + group.DeleteGroupFromScene(silent); + + } + + #endregion //SYMMETRIC SYNC + public ICapabilitiesModule CapsModule { get { return m_capsModule; } @@ -1265,6 +1362,16 @@ namespace OpenSim.Region.Framework.Scenes RegionSyncClientModule = RequestModuleInterface(); ScriptEngineToSceneConnectorModule = RequestModuleInterface(); + ////////////////////////////////////////////////////////////////////// + //SYMMETRIC SYNC (KittyL: started 12/23/2010) + ////////////////////////////////////////////////////////////////////// + m_regionSyncModule = RequestModuleInterface(); + m_DSGActorSyncModule = RequestModuleInterface(); + + ////////////////////////////////////////////////////////////////////// + //end of SYMMETRIC SYNC + ////////////////////////////////////////////////////////////////////// + // Shoving this in here for now, because we have the needed // interfaces at this point // @@ -1416,7 +1523,7 @@ namespace OpenSim.Region.Framework.Scenes // If it's a client manager, just send prim updates // This will get fixed later to only send to locally logged in presences rather than all presences // but requires pulling apart the concept of a client from the concept of a presence/avatar - if (IsSyncedClient() || !RegionSyncEnabled) + if (IsSyncedClient()) { ForEachScenePresence(delegate(ScenePresence sp) { sp.SendPrimUpdates(); }); @@ -1450,24 +1557,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 @@ -2317,6 +2414,15 @@ namespace OpenSim.Region.Framework.Scenes group.DeleteGroupFromScene(silent); // m_log.DebugFormat("[SCENE]: Exit DeleteSceneObject() for {0} {1}", group.Name, group.UUID); + + //SYMMETRIC SYNC + //Set the ActorID and TimeStamp info for this latest update + foreach (SceneObjectPart part in group.Parts) + { + part.SyncInfoUpdate(); + } + //end of SYMMETRIC SYNC + } /// diff --git a/OpenSim/Region/Framework/Scenes/SceneGraph.cs b/OpenSim/Region/Framework/Scenes/SceneGraph.cs index 3261b58804..c885fc4e7e 100644 --- a/OpenSim/Region/Framework/Scenes/SceneGraph.cs +++ b/OpenSim/Region/Framework/Scenes/SceneGraph.cs @@ -373,24 +373,14 @@ namespace OpenSim.Region.Framework.Scenes sceneObject.AttachToScene(m_parentScene); - //KittyL: edited to support script engine actor - //if (sendClientUpdates) - // sceneObject.ScheduleGroupForFullUpdate(); if (sendClientUpdates) - { sceneObject.ScheduleGroupForFullUpdate(); - } - + Entities.Add(sceneObject); - //KittyL: edited to support script engine actor - //if (attachToBackup) - // sceneObject.AttachToBackup(); - if (attachToBackup && m_parentScene.IsAuthoritativeScene()) - { + if (attachToBackup) sceneObject.AttachToBackup(); - } - + if (OnObjectCreate != null) OnObjectCreate(sceneObject); @@ -465,7 +455,10 @@ namespace OpenSim.Region.Framework.Scenes protected internal void AddToUpdateList(SceneObjectGroup obj) { lock (m_updateList) + { m_updateList[obj.UUID] = obj; + m_log.Debug("added " + obj.UUID + " to m_updateList"); + } } /// @@ -484,6 +477,13 @@ namespace OpenSim.Region.Framework.Scenes lock (m_updateList) { updates = new List(m_updateList.Values); + + if (updates.Count > 0) + { + m_log.Debug("SceneGraph: " + updates.Count + " objects to send updates for"); + } + + m_updateList.Clear(); } @@ -1925,10 +1925,106 @@ 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 + { + m_log.Debug("AddSceneObjectByStateSynch to be called"); + 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 6879cf7335..8bc467c3bd 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -3456,5 +3456,144 @@ namespace OpenSim.Region.Framework.Scenes this.m_locY = updatedSog.LocY; } #endregion + + #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 71604eb86a..29b6b7ebac 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -2892,6 +2892,13 @@ namespace OpenSim.Region.Framework.Scenes // m_log.DebugFormat( // "[SCENE OBJECT PART]: Scheduling full update for {0}, {1} at {2}", // UUID, Name, TimeStampFull); + + //SYMMETRIC SYNC + + //update information (timestamp, actorID, etc) needed for synchronization across copies of Scene + SyncInfoUpdate(); + + //end of SYMMETRIC SYNC } /// @@ -2913,6 +2920,13 @@ namespace OpenSim.Region.Framework.Scenes // m_log.DebugFormat( // "[SCENE OBJECT PART]: Scheduling terse update for {0}, {1} at {2}", // UUID, Name, TimeStampTerse); + + //SYMMETRIC SYNC + + //update information (timestamp, actorID, etc) needed for synchronization across copies of Scene + SyncInfoUpdate(); + + //end of SYMMETRIC SYNC } } @@ -3130,6 +3144,12 @@ namespace OpenSim.Region.Framework.Scenes } } ClearUpdateSchedule(); + + //SYMMETRIC SYNC + + m_parentGroup.Scene.RegionSyncModule.QueueSceneObjectPartForUpdate(this); + + //end of SYMMETRIC SYNC } /// @@ -4888,5 +4908,208 @@ namespace OpenSim.Region.Framework.Scenes #endregion + #region SYMMETRIC SYNC + + //Time stamp for the most recent update on this prim. We only have one time-stamp per prim for now. + //The goal is to evetually have time-stamp per property bucket for each prim. + private long m_lastUpdateTimeStamp = DateTime.Now.Ticks; + public long LastUpdateTimeStamp + { + get { return m_lastUpdateTimeStamp; } + set { m_lastUpdateTimeStamp = value; } + } + + //The ID the identifies which actor has caused the most recent update to the prim. + //We use type "string" for the ID only to make it human-readable. + private string m_lastUpdateActorID; + public string LastUpdateActorID + { + get { return m_lastUpdateActorID; } + set { m_lastUpdateActorID = value; } + } + + public void UpdateTimestamp() + { + m_lastUpdateTimeStamp = DateTime.Now.Ticks; + } + + public void SetLastUpdateActorID() + { + if (m_parentGroup != null) + { + m_lastUpdateActorID = m_parentGroup.Scene.ActorSyncModule.ActorID; + } + else + { + m_log.Error("Prim " + UUID + " is not in a SceneObjectGroup yet"); + } + } + + public void SyncInfoUpdate() + { + //Trick: calling UpdateTimestamp here makes sure that when an object was received and de-serialized, before + // its parts are linked together, neither TimeStamp or ActorID will be modified. This is because during de-serialization, + // ScheduleFullUpdate() is called when m_parentGroup == null + if (m_parentGroup != null) + { + UpdateTimestamp(); + m_lastUpdateActorID = m_parentGroup.Scene.ActorSyncModule.ActorID; + } + } + + //!!!!!! -- 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_lastUpdateActorID != 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_lastUpdateActorID = 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]: ScheduleFullUpdate_SyncInfoUnchanged 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 + } } diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs index 605521a0fa..efadf8dedf 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs @@ -215,6 +215,10 @@ namespace OpenSim.Region.Framework.Scenes.Serialization sceneObject.AddPart(part); + //SYMMETRIC SYNC + //KittyL: 12/27/2010, added ActorID for symmetric synch model + part.SetLastUpdateActorID(); + // SceneObjectGroup.AddPart() tries to be smart and automatically set the LinkNum. // We override that here if (originalLinkNum != 0) @@ -324,6 +328,12 @@ namespace OpenSim.Region.Framework.Scenes.Serialization m_SOPXmlProcessors.Add("MediaUrl", ProcessMediaUrl); m_SOPXmlProcessors.Add("TextureAnimation", ProcessTextureAnimation); m_SOPXmlProcessors.Add("ParticleSystem", ProcessParticleSystem); + + //SYMMETRIC SYNC + m_SOPXmlProcessors.Add("LastUpdateTimeStamp", ProcessUpdateTimeStamp); + m_SOPXmlProcessors.Add("LastUpdateActorID", ProcessLastUpdateActorID); + //end of SYMMETRIC SYNC + #endregion #region TaskInventoryXmlProcessors initialization @@ -681,6 +691,19 @@ namespace OpenSim.Region.Framework.Scenes.Serialization { obj.ParticleSystem = Convert.FromBase64String(reader.ReadElementContentAsString("ParticleSystem", String.Empty)); } + + //SYMMETRIC SYNC + private static void ProcessUpdateTimeStamp(SceneObjectPart obj, XmlTextReader reader) + { + obj.LastUpdateTimeStamp = reader.ReadElementContentAsLong("LastUpdateTimeStamp", string.Empty); + } + + private static void ProcessLastUpdateActorID(SceneObjectPart obj, XmlTextReader reader) + { + obj.LastUpdateActorID = reader.ReadElementContentAsString("LastUpdateActorID", string.Empty); + } + //end of SYMMETRIC SYNC + #endregion #region TaskInventoryXmlProcessors @@ -1057,7 +1080,7 @@ namespace OpenSim.Region.Framework.Scenes.Serialization ////////// Write ///////// - public static void SOGToXml2(XmlTextWriter writer, SceneObjectGroup sog, Dictionaryoptions) + public static void SOGToXml2(XmlTextWriter writer, SceneObjectGroup sog, Dictionary options) { writer.WriteStartElement(String.Empty, "SceneObjectGroup", String.Empty); SOPToXml2(writer, sog.RootPart, options); @@ -1159,6 +1182,11 @@ namespace OpenSim.Region.Framework.Scenes.Serialization WriteBytes(writer, "TextureAnimation", sop.TextureAnimation); WriteBytes(writer, "ParticleSystem", sop.ParticleSystem); + //SYMMETRIC SYNC + writer.WriteElementString("LastUpdateTimeStamp", sop.LastUpdateTimeStamp.ToString()); + writer.WriteElementString("LastUpdateActorID", sop.LastUpdateActorID); + //end of SYMMETRIC SYNC + writer.WriteEndElement(); } diff --git a/bin/config-include/SimianGrid.ini b/bin/config-include/SimianGrid.ini index 292385b255..6f1ffc3018 100644 --- a/bin/config-include/SimianGrid.ini +++ b/bin/config-include/SimianGrid.ini @@ -65,7 +65,8 @@ AssetLoaderArgs = "assets/AssetSets.xml" [Groups] - Enabled = true + ;Enabled = true + Enabled = false Module = GroupsModule DebugEnabled = false NoticesEnabled = true