/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyrightD * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Reflection; using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; 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.IO; using System.Net; using System.Net.Sockets; using System.Threading; namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule { public class RegionSyncServerModule : IRegionModule, IRegionSyncServerModule, ICommandableModule { private static int DefaultPort = 13000; public static string ActorID = "XX"; private static int PortUnknown = -1; private static string IPAddrUnknown = ""; #region IRegionModule Members public void Initialise(Scene scene, IConfigSource config) { m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // If no syncConfig, do not start up server mode IConfig syncConfig = config.Configs["RegionSyncModule"]; if (syncConfig == null) { scene.RegionSyncEnabled = false; m_active = false; m_log.Warn("[REGION SYNC SERVER MODULE] No RegionSyncModule config section found. Shutting down."); return; } // If syncConfig does not indicate "enabled", do not start up server mode bool enabled = syncConfig.GetBoolean("Enabled", true); if(!enabled) { scene.RegionSyncEnabled = false; m_active = false; m_log.Warn("[REGION SYNC SERVER MODULE] RegionSyncModule is not enabled. Shutting down."); return; } // get identifying actor ID whether in client or server mode ActorID = syncConfig.GetString("ActorID", "ZZ"); // If syncConfig does not indicate "server", do not start up server mode //string mode = syncConfig.GetString("Mode", "server").ToLower(); string mode = syncConfig.GetString("Mode", "").ToLower(); if(mode != "server") { //scene.RegionSyncEnabled = false; m_active = false; m_log.WarnFormat("[REGION SYNC SERVER MODULE] RegionSyncModule is in {0} mode. Shutting down.", mode); return; } // Enable region sync in server mode on the scene and module scene.RegionSyncEnabled = true; scene.RegionSyncMode = mode; m_active = true; // Init the sync statistics log file string syncstats = "syncstats" + "_" + scene.RegionInfo.RegionName + ".txt"; m_statsWriter = File.AppendText(syncstats); //Get sync server info for Client Manager actors //string serverAddr = scene.RegionInfo.RegionName + "_ServerIPAddress"; //m_serveraddr = syncConfig.GetString(serverAddr, IPAddrUnknown); //string serverPort = scene.RegionInfo.RegionName + "_ServerPort"; //m_serverport = syncConfig.GetInt(serverPort, PortUnknown); // Client manager load balancing m_maxClientsPerManager = syncConfig.GetInt("MaxClientsPerManager", 100); DefaultPort++; //Get sync server info for Script Engine actors string seServerAddr = scene.RegionInfo.RegionName + "_SceneToSESyncServerIP"; m_seSyncServeraddr = syncConfig.GetString(seServerAddr, IPAddrUnknown); string seServerPort = scene.RegionInfo.RegionName + "_SceneToSESyncServerPort"; m_seSyncServerport = syncConfig.GetInt(seServerPort, PortUnknown); DefaultPort++; //Get sync server info for Physics Engine actors string peServerAddr = scene.RegionInfo.RegionName + "_SceneToPESyncServerIP"; m_peSyncServeraddr = syncConfig.GetString(peServerAddr, "127.0.0.1"); string peServerPort = scene.RegionInfo.RegionName + "_SceneToPESyncServerPort"; m_peSyncServerport = syncConfig.GetInt(peServerPort, DefaultPort); DefaultPort++; m_scene = scene; m_scene.RegisterModuleInterface(this); // Setup the command line interface m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; InstallInterfaces(); //m_log.Warn("[REGION SYNC SERVER MODULE] Initialised"); } public void PostInitialise() { if (!m_active) return; //m_scene.EventManager.OnObjectBeingRemovedFromScene += new EventManager.ObjectBeingRemovedFromScene(EventManager_OnObjectBeingRemovedFromScene); //m_scene.EventManager.OnAvatarEnteringNewParcel += new EventManager.AvatarEnteringNewParcel(EventManager_OnAvatarEnteringNewParcel); //m_scene.EventManager.OnClientMovement += new EventManager.ClientMovement(EventManager_OnClientMovement); //m_scene.EventManager.OnLandObjectAdded += new EventManager.LandObjectAdded(EventManager_OnLandObjectAdded); //m_scene.EventManager.OnLandObjectRemoved += new EventManager.LandObjectRemoved(EventManager_OnLandObjectRemoved); m_scene.EventManager.OnNewClient += new EventManager.OnNewClientDelegate(EventManager_OnNewClient); //m_scene.EventManager.OnNewPresence += new EventManager.OnNewPresenceDelegate(EventManager_OnNewPresence); m_scene.EventManager.OnRemovePresence += new EventManager.OnRemovePresenceDelegate(EventManager_OnRemovePresence); // Start the server and listen for RegionSyncClients m_serveraddr = m_scene.RegionInfo.AvatarSyncServerAddress; m_serverport = m_scene.RegionInfo.AvatarSyncServerPort; m_log.Debug("[REGION SYNC SERVER MODULE] to start server on " + m_serveraddr + ":" + m_serverport); if (!m_serveraddr.Equals(IPAddrUnknown) && m_serverport != PortUnknown) { m_log.Warn("[REGION SYNC SERVER MODULE] Starting RegionSyncServer"); m_server = new RegionSyncServer(m_scene, m_serveraddr, m_serverport, m_maxClientsPerManager); m_server.Start(); m_statsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimerElapsed); m_statsTimer.Start(); } m_peSyncServeraddr = m_scene.RegionInfo.PhysicsSyncServerAddress; m_peSyncServerport = m_scene.RegionInfo.PhysicsSyncServerPort; if (!m_peSyncServeraddr.Equals(IPAddrUnknown) && m_peSyncServerport != PortUnknown) { m_log.Warn("[REGION SYNC SERVER MODULE] Starting SceneToPhysEngineSyncServer"); //Start the sync server for physics engines m_sceneToPESyncServer = new SceneToPhysEngineSyncServer(m_scene, m_peSyncServeraddr, m_peSyncServerport); m_sceneToPESyncServer.Start(); } //m_log.Warn("[REGION SYNC SERVER MODULE] Post-Initialised"); } private void StatsTimerElapsed(object source, System.Timers.ElapsedEventArgs e) { if (Synced) m_server.ReportStats(m_statsWriter); } void IRegionModule.Close() { m_scene = null; } public string Name { get { return "RegionSyncModule"; } } public bool IsSharedModule { get { return false; } } //KittyL added //Later, should make quarkIDs the argument to the function call //public void SendResetScene() //{ // m_server.Broadcast(new RegionSyncMessage(RegionSyncMessage.MsgType.ResetScene, "reset")); //} #endregion #region ICommandableModule Members private readonly Commander m_commander = new Commander("sync"); public ICommander CommandInterface { get { return m_commander; } } #endregion #region IRegionSyncServerModule members // Lock is used to synchronize access to the update status and both update queues private object m_updateLock = new object(); private int m_sendingUpdates; //private Dictionary m_primUpdates = new Dictionary(); private Dictionary m_presenceUpdates = new Dictionary(); private System.Timers.Timer m_statsTimer = new System.Timers.Timer(1000); //private TextWriter m_statsWriter = File.AppendText("syncstats.txt"); private TextWriter m_statsWriter; public void QueuePresenceForTerseUpdate(ScenePresence presence) { if (!Active || !Synced) return; lock (m_updateLock) { m_presenceUpdates[presence.UUID] = presence; } //m_log.DebugFormat("[REGION SYNC SERVER MODULE] QueuePresenceForUpdate: {0}", presence.UUID.ToString()); } /// /// Send a teleport message out. This should only be called when teleporting within the same region. /// /// public void SendTeleportUpdate(ScenePresence presence) { if (!Active || !Synced) return; System.Threading.ThreadPool.QueueUserWorkItem(delegate { 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); if (System.Single.IsNaN(presence.Rotation.X)) data["rot"] = OSD.FromQuaternion(Quaternion.Identity); else 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.AvatarTeleportSameRegion, OSDParser.SerializeJsonString(data)); m_server.Broadcast(rsm); }); } public void SendUpdates() { if (!Active || !Synced) return; // 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.DebugFormat("[REGION SYNC SERVER MODULE] SendUpdates(): An update thread is already running."); return; } List primUpdates; List presenceUpdates; lock (m_updateLock) { //primUpdates = new List(m_primUpdates.Values); presenceUpdates = new List(m_presenceUpdates.Values); //m_primUpdates.Clear(); 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 { // 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 (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); if(System.Single.IsNaN(presence.Rotation.X)) data["rot"] = OSD.FromQuaternion(Quaternion.Identity); else 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); }); } private Dictionary m_appearanceTimers = new Dictionary(); public void SendAppearance(UUID agentID) { OSDMap data = new OSDMap(1); data["id"] = OSDUUID.FromUUID(agentID); m_server.Broadcast(new RegionSyncMessage(RegionSyncMessage.MsgType.AvatarAppearance, OSDParser.SerializeJsonString(data))); } public void SendAnimations(UUID agentID, UUID[] animations, int[] seqs, UUID sourceAgentId, UUID[] objectIDs) { OSDMap data = new OSDMap(); data["agentID"] = OSD.FromUUID(agentID); OSDArray animatA = new OSDArray(); foreach (UUID uu in animations) animatA.Add(OSD.FromUUID(uu)); data["animations"] = animatA; OSDArray seqsA = new OSDArray(); foreach (int ss in seqs) seqsA.Add(OSD.FromInteger(ss)); data["seqs"] = seqsA; data["sourceAgentID"] = OSD.FromUUID(sourceAgentId); OSDArray obIDA = new OSDArray(); foreach (UUID ii in objectIDs) obIDA.Add(OSD.FromUUID(ii)); data["objectIDs"] = obIDA; // m_log.DebugFormat("[REGION SYNC SERVER MODULE] Broadcast animations for {0}", agentID.ToString()); RegionSyncMessage rsm = new RegionSyncMessage(RegionSyncMessage.MsgType.SendAnimations, OSDParser.SerializeJsonString(data)); m_server.Broadcast(rsm); // m_clientView.Send(rsm); } public bool Active { get { return m_active; } } // Check if the sync server module is connected to any clients (KittyL: edited for testing if connected to any actors) public bool Synced { get { if (m_server == null || !m_server.Synced) //if((m_server == null || !m_server.Synced) && (m_sceneToSESyncServer==null || !m_sceneToSESyncServer.Synced)) return false; return true; } } /* public void SendLoadWorldMap(ITerrainChannel heightMap) { RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.Terrain, m_scene.Heightmap.SaveToXmlString()); if(m_server!=null) m_server.Broadcast(msg); } * */ #endregion #region RegionSyncServerModule members private bool m_active = true; private string m_serveraddr; private int m_serverport; private int m_maxClientsPerManager; private Scene m_scene; //private IClientAPI m_clientAggregator; private ILog m_log; //private int m_moveCounter = 0; private RegionSyncServer m_server = null; //Sync-server for script engines private string m_seSyncServeraddr; private int m_seSyncServerport; //private SceneToScriptEngineSyncServer m_sceneToSESyncServer = null; //Sync-server for physics engines private string m_peSyncServeraddr; private int m_peSyncServerport; private SceneToPhysEngineSyncServer m_sceneToPESyncServer = null; //a boolean variable to indicate in symmetric sync is configured //private bool m_symsync = false; #endregion #region Event Handlers // A ficticious event public void Scene_AddNewPrim(SceneObjectGroup sog) { if (!Synced) return; } private void EventManager_OnNewPresence(ScenePresence presence) { if (!Synced) return; m_log.WarnFormat("[REGION SYNC SERVER MODULE] (OneNewPresence) \"{0}\"", presence.Name); } private void EventManager_OnNewClient(IClientAPI client) { if (!Synced) return; m_log.WarnFormat("[REGION SYNC SERVER MODULE] Agent \"{0}\" {1} has joined the scene", client.FirstName + " " + client.LastName, client.AgentId.ToString()); // Let the client managers know that a new agent has connected OSDMap data = new OSDMap(1); data["agentID"] = OSD.FromUUID(client.AgentId); data["localID"] = OSD.FromUInteger(m_scene.GetScenePresence(client.AgentId).LocalId); data["first"] = OSD.FromString(client.FirstName); data["last"] = OSD.FromString(client.LastName); data["startPos"] = OSD.FromVector3(client.StartPos); m_server.Broadcast(new RegionSyncMessage(RegionSyncMessage.MsgType.NewAvatar, OSDParser.SerializeJsonString(data))); } private void EventManager_OnRemovePresence(UUID agentID) { if (!Synced) return; /* ScenePresence avatar; if (m_scene.TryGetScenePresence(agentID, out avatar)) { m_log.WarnFormat("[REGION SYNC SERVER MODULE] Avatar \"{0}\" {1} {2} has left the scene", avatar.Firstname + " " + avatar.Lastname, agentID.ToString(), avatar.UUID.ToString()); } else { m_log.WarnFormat("[REGION SYNC SERVER MODULE] Avatar \"unknown\" has left the scene"); } * */ OSDMap data = new OSDMap(); data["agentID"] = OSD.FromUUID(agentID); m_server.Broadcast(new RegionSyncMessage(RegionSyncMessage.MsgType.RemovedAvatar, OSDParser.SerializeJsonString(data))); } #endregion #region Console Command Interface private void InstallInterfaces() { Command cmdSyncStatus = new Command("status", CommandIntentions.COMMAND_HAZARDOUS, SyncStatus, "Reports current status of the RegionSyncServer."); Command cmdBalanceClients = new Command("balance", CommandIntentions.COMMAND_HAZARDOUS, BalanceClients, "Balance client load across available client managers."); m_commander.RegisterCommand("status", cmdSyncStatus); m_commander.RegisterCommand("balance", cmdBalanceClients); 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); } } private void SyncStatus(Object[] args) { if (Synced) m_server.ReportStatus(); else m_log.Error("No RegionSyncClients connected"); } private void BalanceClients(Object[] args) { if (Synced) m_server.BalanceClients(); else m_log.Error("No RegionSyncClients connected"); } #endregion } }