diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncClientModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncClientModule.cs index d6bc1f97b9..2b963836f1 100644 --- a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncClientModule.cs +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncClientModule.cs @@ -47,25 +47,37 @@ namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule public void Initialise(Scene scene, IConfigSource config) { + /* This config parsing pattern should be used in each of these files: + OpenSim.cs + RegionSyncServerModule.cs + RegionSyncClientModule.cs + ScriptEngineToSceneConnectorModule.cs + */ m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + IConfig syncConfig = config.Configs["RegionSyncModule"]; - if (syncConfig == null || syncConfig.GetString("Mode", "client").ToLower() != "client") + m_active = false; + if (syncConfig == null) + m_log.Warn("[REGION SYNC CLIENT MODULE] No RegionSyncModule config section found. Shutting down."); + else if (!syncConfig.GetBoolean("Enabled", true)) + m_log.Warn("[REGION SYNC CLIENT MODULE] RegionSyncModule is not enabled. Shutting down."); + else if (!syncConfig.GetString("Mode", "client").ToLower().Equals("client")) + m_log.WarnFormat("[REGION SYNC CLIENT MODULE] RegionSyncModule is not in client mode. Shutting down."); + else { - m_active = false; - m_log.Warn("[REGION SYNC CLIENT MODULE] Not in client mode. Shutting down."); - return; + m_scene = scene; + m_active = true; + m_scene.RegionSyncEnabled = true; + m_scene.RegionSyncMode = "client"; + m_serveraddr = syncConfig.GetString("ServerIPAddress", "127.0.0.1"); + m_serverport = syncConfig.GetInt("ServerPort", 13000); + m_scene.RegisterModuleInterface(this); + + // Setup the command line interface + m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + InstallInterfaces(); + m_log.Warn("[REGION SYNC CLIENT MODULE] Initialised"); } - m_serveraddr = syncConfig.GetString("ServerIPAddress", "127.0.0.1"); - m_serverport = syncConfig.GetInt("ServerPort", 13000); - m_scene = scene; - m_scene.RegisterModuleInterface(this); - - // Setup the command line interface - m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; - InstallInterfaces(); - - m_log.Warn("[REGION SYNC CLIENT MODULE] Initialised"); } public void PostInitialise() diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncUtil.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncUtil.cs new file mode 100644 index 0000000000..4eb51e0678 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/RegionSyncUtil.cs @@ -0,0 +1,285 @@ +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using OpenMetaverse.StructuredData; +using log4net; + +using OpenSim.Framework; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + class RegionSyncUtil + { + // The logfile + private static ILog m_log; + + //HashSet exceptions = new HashSet(); + public static OSDMap DeserializeMessage(RegionSyncMessage msg, string logHeader) + { + OSDMap data = null; + try + { + data = OSDParser.DeserializeJson(Encoding.ASCII.GetString(msg.Data, 0, msg.Length)) as OSDMap; + } + catch (Exception e) + { + m_log.Error(logHeader + " " + Encoding.ASCII.GetString(msg.Data, 0, msg.Length)); + data = null; + } + return data; + } + + #region Quark operations + + //Convert a list of quarks, each identified by "x_y", where (x,y) are the offset position (in a 256x256 scene) of the left-bottom corner, to a single string + public static string QuarkStringListToString(List quarks) + { + string quarkString = ""; + foreach (string quark in quarks) + { + quarkString += quark + ","; + } + //trim the last ',' + char[] trimChar = {','}; + return quarkString.TrimEnd(trimChar); + //return quarkString; + } + + //Convert a list of quarks, each identified by a QuarkInfo data structure, to a single string + public static string QuarkInfoToString(List quarks) + { + string quarkString = ""; + foreach (QuarkInfo quark in quarks) + { + quarkString += quark.QuarkStringRepresentation + ","; + } + //trim the last ',' + char[] trimChar = { ',' }; + return quarkString.TrimEnd(trimChar); + //return quarkString; + } + + public static List QuarkStringToStringList(string quarkString) + { + string[] data = quarkString.Split(new char[] { ',' }); + List quarkList = new List(data); + + return quarkList; + } + + //public static List GetQuarkInfoList(List quarkStringList, int quarkSizeX, int quarkSizeY) + public static List GetQuarkInfoList(List quarkStringList) + { + List quarkInfoList = new List(); + foreach (string quarkString in quarkStringList) + { + QuarkInfo quark = new QuarkInfo(quarkString); + quarkInfoList.Add(quark); + } + return quarkInfoList; + } + + //public static List GetAllQuarksInScene(int QuarkInfo.SizeX, int QuarkInfo.SizeY) + public static List GetAllQuarksInScene() + { + List quarkList = new List(); + int xSlots =(int) Constants.RegionSize / QuarkInfo.SizeX; + int ySlots = (int) Constants.RegionSize / QuarkInfo.SizeY; + + for (int i = 0; i < xSlots; i++) + { + int posX = i * QuarkInfo.SizeX; + for (int j = 0; j < ySlots; j++) + { + int posY = j * QuarkInfo.SizeY; + QuarkInfo quark = new QuarkInfo(posX, posY); + quarkList.Add(quark); + } + } + return quarkList; + } + + //public static List GetAllQuarkStringInScene(int QuarkInfo.SizeX, int QuarkInfo.SizeY) + public static List GetAllQuarkStringInScene() + { + List quarkStringList = new List(); + int xSlots = (int)Constants.RegionSize / QuarkInfo.SizeX; + int ySlots = (int)Constants.RegionSize / QuarkInfo.SizeY; + + for (int i = 0; i < xSlots; i++) + { + int posX = i * QuarkInfo.SizeX; + for (int j = 0; j < ySlots; j++) + { + int posY = j * QuarkInfo.SizeY; + string quarkString = ""; + quarkString += posX + "_" + posY; + quarkStringList.Add(quarkString); + } + } + return quarkStringList; + } + + //public static string GetQuarkIDByPosition(Vector3 pos, int QuarkInfo.SizeX, int QuarkInfo.SizeY) + public static string GetQuarkIDByPosition(Vector3 pos) + { + float x, y; + if (pos.X < 0) + { + x = 0; + } + else if (pos.X >= Constants.RegionSize) + { + x = (int)Constants.RegionSize - 1; + } + else + x = pos.X; + + if (pos.Y < 0) + { + y = 0; + } + else if (pos.Y >= Constants.RegionSize) + { + y = (int)Constants.RegionSize - 1; + } + else + y = pos.Y; + + int xRange = (int) x / QuarkInfo.SizeX; + int yRange = (int) y / QuarkInfo.SizeY; + + int quarkPosX = xRange * QuarkInfo.SizeX; + int quarkPosY = yRange * QuarkInfo.SizeY; + + string qID = quarkPosX + "_" + quarkPosX; + return qID; + } + + //public static List GetQuarkSubscriptions(int QuarkInfo.SizeX, int QuarkInfo.SizeY, int xmin, int ymin, int xmax, int ymax) + public static List GetQuarkSubscriptions(int xmin, int ymin, int xmax, int ymax) + { + List quarkList = new List(); + int xStart = (int)xmin / QuarkInfo.SizeX; + int yStart = (int)ymin / QuarkInfo.SizeY; + int xEnd = (int)xmax / QuarkInfo.SizeX; + int yEnd = (int)ymax / QuarkInfo.SizeY; + + for (int i = xStart; i < xEnd; i++) + { + int posX = i * QuarkInfo.SizeX; + for (int j = yStart; j < yEnd; j++) + { + int posY = j * QuarkInfo.SizeY; + QuarkInfo quark = new QuarkInfo(posX, posY); + quarkList.Add(quark); + } + } + return quarkList; + } + + public static int[] GetCornerCoordinates(string space) + { + if (space == "") return null; + string[] corners = space.Split(new char[] { ',' }); + string leftBottom = corners[0]; + string rightTop = corners[1]; + string[] coordinates = leftBottom.Split(new char[] { '_' }); + + int [] results = new int[4]; + results[0] = Convert.ToInt32(coordinates[0]); + results[1] = Convert.ToInt32(coordinates[1]); + coordinates = rightTop.Split(new char[] { '_' }); + results[2] = Convert.ToInt32(coordinates[0]); + results[3] = Convert.ToInt32(coordinates[1]); + return results; + } + + public static string GetSpaceStringRepresentationByCorners(int minX, int minY, int maxX, int maxY) + { + string spaceString = minX + "_" + minY+","+maxX+"_"+maxY; + return spaceString; + } + + public static Dictionary BinarySpaceParition(int minX, int minY, int maxX, int maxY) + { + int xLen = maxX - minX; + int yLen = maxY - minY; + + //We always return the half space that share the same right-top corner, (maxX, maxY), with the original space. + int upperPlainStartX, upperPlainStartY; + int upperPlainEndX = maxX, upperPlainEndY = maxY; + + bool partitionOnX; + if (xLen >= yLen) + { + //partition along x axis + partitionOnX = true; + int partXLen = xLen / 2; + upperPlainStartX = minX + partXLen; + upperPlainStartY = minY; + } + else + { + //partition along y axis + partitionOnX = false; + int partYLen = yLen / 2; + upperPlainStartY = minY + partYLen; + upperPlainStartX = minX; + } + Dictionary partitionedSpace = new Dictionary(); + + //upper plain refers to the right (if partition along x) or top (if partition along y) part + int[] upperPlain = new int[4]; + upperPlain[0] = upperPlainStartX; + upperPlain[1] = upperPlainStartY; + upperPlain[2] = upperPlainEndX; + upperPlain[3] = upperPlainEndY; + + int[] lowerPlain = new int[4]; + if (partitionOnX) + { + lowerPlain[0] = minX; + lowerPlain[1] = minY; + lowerPlain[2] = upperPlainStartX; + lowerPlain[3] = maxY; + } + else + { + lowerPlain[0] = minX; + lowerPlain[1] = minY; + lowerPlain[2] = maxX; + lowerPlain[3] = upperPlainStartY; + } + + partitionedSpace.Add("lower", lowerPlain); + partitionedSpace.Add("upper", upperPlain); + + return partitionedSpace; + } + + public static int[] RemoveSpace(int originMinX, int originMinY, int originMaxX, int originMaxY, int toRemoveMinX, int toRemoveMinY, int toRemoveMaxX, int toRemoveMaxY) + { + int[] remainingBox = new int[4]; + remainingBox[0] = originMinX; + remainingBox[1] = originMinY; + if (originMinX == toRemoveMinX) + { + //partitioned along Y + remainingBox[2] = originMaxX; + remainingBox[3] = toRemoveMinY; + } + else + { + //partitioned along X + remainingBox[2] = toRemoveMinX; + remainingBox[3] = originMaxY; + } + return remainingBox; + } + + #endregion Quark operations + } +} diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineConnector.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineConnector.cs new file mode 100644 index 0000000000..6e9e174eb4 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineConnector.cs @@ -0,0 +1,640 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Region.Framework.Interfaces; +using log4net; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + + + + //KittyL: NOTE -- We need to define an interface for all actors to connect into the Scene, + // e.g. IActorConnector, that runs on the Scene side, processes messages from actors, + // and apply Scene/Object operations. + + // The SceneToScriptEngineConnector acts as a thread on the RegionSyncServer to handle incoming + // messages from ScriptEngineToSceneConnectors that run on Script Engines. It connects the + // authoratative Scene with remote script engines. + public class SceneToScriptEngineConnector + { + #region SceneToScriptEngineConnector members + + object stats = new object(); + private DateTime lastStatTime; + private long msgsIn; + private long msgsOut; + private long bytesIn; + private long bytesOut; + private long pollBlocks; + private int lastTotalCount; + private int lastLocalCount; + private int lastRemoteCount; + + private int msgCount = 0; + + // The TcpClient this view uses to communicate with its RegionSyncClient + private TcpClient m_tcpclient; + // Set the addr and port for TcpListener + private IPAddress m_addr; + private Int32 m_port; + private int m_connection_number; + private Scene m_scene; + + object m_syncRoot = new object(); + Dictionary m_syncedAvatars = new Dictionary(); + + // A queue for incoming and outgoing traffic + private OpenMetaverse.BlockingQueue inbox = new OpenMetaverse.BlockingQueue(); + private OpenMetaverse.BlockingQueue outbox = new OpenMetaverse.BlockingQueue(); + + private ILog m_log; + + private Thread m_receive_loop; + private string m_regionName; + + private SceneToScriptEngineSyncServer m_syncServer = null; + + // A string of the format [REGION SYNC SCRIPT API (regionname)] for use in log headers + private string LogHeader + { + get + { + if (m_regionName == null) + return String.Format("[SceneToScriptEngineConnector #{0}]", m_connection_number); + return String.Format("[SceneToScriptEngineConnector #{0} ({1:10})]", m_connection_number, m_regionName); + } + } + + // A string of the format "RegionSyncClientView #X" for use in describing the object itself + public string Description + { + get + { + if (m_regionName == null) + return String.Format("RegionSyncScriptAPI #{0}", m_connection_number); + return String.Format("RegionSyncScriptAPI #{0} ({1:10})", m_connection_number, m_regionName); + } + } + + public int ConnectionNum + { + get { return m_connection_number; } + } + + public string GetStats() + { + int syncedAvCount; + string ret; + //lock (m_syncRoot) + // syncedAvCount = m_syncedAvatars.Count; + lock (stats) + { + double secondsSinceLastStats = DateTime.Now.Subtract(lastStatTime).TotalSeconds; + lastStatTime = DateTime.Now; + + ret = String.Format("[{0,4}/{1,4}], [{2,4}/{3,4}], [{4,4}/{5,4}], [{6,4} ({7,4})], [{8,8} ({9,8:00.00})], [{10,4} ({11,4})], [{12,8} ({13,8:00.00})], [{14,8} ({15,4}]", + //lastTotalCount, totalAvCount, // TOTAL AVATARS + //lastLocalCount, syncedAvCount, // LOCAL TO THIS CLIENT VIEW + //lastRemoteCount, totalAvCount - syncedAvCount, // REMOTE (SHOULD = TOTAL - LOCAL) + msgsIn, (int)(msgsIn / secondsSinceLastStats), + bytesIn, 8 * (bytesIn / secondsSinceLastStats / 1000000), // IN + msgsOut, (int)(msgsOut / secondsSinceLastStats), + bytesOut, 8 * (bytesOut / secondsSinceLastStats / 1000000), // OUT + pollBlocks, (int)(pollBlocks / secondsSinceLastStats)); // NUMBER OF TIMES WE BLOCKED WRITING TO SOCKET + + msgsIn = msgsOut = bytesIn = bytesOut = pollBlocks = 0; + } + return ret; + } + + // Check if the client is connected + public bool Connected + { get { return m_tcpclient.Connected; } } + + //private int QuarkInfo.SizeX; + //private int QuarkInfo.SizeY; + //private List m_quarkSubscriptions; + Dictionary m_quarkSubscriptions; + public Dictionary QuarkSubscriptionList + { + get { return m_quarkSubscriptions; } + } + + + + #endregion + + + + // Constructor + public SceneToScriptEngineConnector(int num, Scene scene, TcpClient client, SceneToScriptEngineSyncServer syncServer) + { + m_connection_number = num; + m_scene = scene; + m_tcpclient = client; + m_addr = ((IPEndPoint)client.Client.RemoteEndPoint).Address; + m_port = ((IPEndPoint)client.Client.RemoteEndPoint).Port; + m_syncServer = syncServer; + + //QuarkInfo.SizeX = quarkSizeX; + //QuarkInfo.SizeY = quarkSizeY; + + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + //m_log.WarnFormat("{0} Constructed", LogHeader); + + //Register for events from Scene.EventManager + //m_scene.EventManager.OnRezScript += SEConnectorOnRezScript; + //m_scene.EventManager.OnScriptReset += SEConnectorOnScriptReset; + //m_scene.EventManager.OnUpdateScript += SEConnectorOnUpdateScript; + // Create a thread for the receive loop + m_receive_loop = new Thread(new ThreadStart(delegate() { ReceiveLoop(); })); + m_receive_loop.Name = Description; + //m_log.WarnFormat("{0} Started thread: {1}", LogHeader, m_receive_loop.Name); + m_receive_loop.Start(); + + //tell the remote script engine about the locX, locY of this authoritative scene + SendSceneLoc(); + } + + // Stop the listening thread, disconnecting the RegionSyncScriptEngine + public void Shutdown() + { + m_syncServer.RemoveSyncedScriptEngine(this); + // m_scene.EventManager.OnChatFromClient -= EventManager_OnChatFromClient; + // Abort ReceiveLoop Thread, close Socket and TcpClient + m_receive_loop.Abort(); + m_tcpclient.Client.Close(); + m_tcpclient.Close(); + + //m_scene.EventManager.OnRezScript -= SEConnectorOnRezScript; + //m_scene.EventManager.OnScriptReset -= SEConnectorOnScriptReset; + //m_scene.EventManager.OnUpdateScript -= SEConnectorOnUpdateScript; + } + + #region Send/Receive messages to/from the remote Script Engine + + // Listen for messages from a RegionSyncClient + // *** This is the main thread loop for each connected client + private void ReceiveLoop() + { + //m_scene.EventManager.OnChatFromClient += new EventManager.ChatFromClientEvent(EventManager_OnChatFromClient); + + // Reset stats and time + lastStatTime = DateTime.Now; + msgsIn = msgsOut = bytesIn = bytesOut = 0; + + try + { + while (true) + { + RegionSyncMessage msg = GetMessage(); + lock (stats) + { + msgsIn++; + bytesIn += msg.Length; + } + lock (m_syncRoot) + HandleMessage(msg); + } + } + catch (Exception e) + { + m_log.WarnFormat("{0}: has disconnected: {1}", LogHeader, e.Message); + } + Shutdown(); + } + + // Get a message from the RegionSyncClient + private RegionSyncMessage GetMessage() + { + // Get a RegionSyncMessager from the incoming stream + RegionSyncMessage msg = new RegionSyncMessage(m_tcpclient.GetStream()); + //m_log.WarnFormat("{0} Received {1}", LogHeader, msg.ToString()); + return msg; + } + + // Handle an incoming message + // *** Perhaps this should not be synchronous with the receive + // We could handle messages from an incoming Queue + private void HandleMessage(RegionSyncMessage msg) + { + msgCount++; + //string handlerMessage = ""; + switch (msg.Type) + { + case RegionSyncMessage.MsgType.QuarkSubscription: + HandleQuarkSubscription(msg); + return; + case RegionSyncMessage.MsgType.RegionName: + { + m_regionName = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.RegionName, m_scene.RegionInfo.RegionName)); + RegionSyncMessage.HandleSuccess(LogHeader, msg, String.Format("Syncing to region \"{0}\"", m_regionName)); + return; + } + case RegionSyncMessage.MsgType.GetTerrain: + { + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.Terrain, m_scene.Heightmap.SaveToXmlString())); + RegionSyncMessage.HandleSuccess(LogHeader, msg, "Terrain sent"); + return; + } + case RegionSyncMessage.MsgType.GetObjects: + { + //List entities = m_scene.GetEntities(); + + //This should be a function of Scene, but since we don't have the quark concept in Scene yet, + //for now we implement it here. + List objectsInSpace = GetObjectsInGivenSpace(m_scene, m_quarkSubscriptions); + foreach (SceneObjectGroup sog in objectsInSpace) + { + Send(PrepareObjectUpdateMessage(RegionSyncMessage.MsgType.NewObject, sog)); + } + RegionSyncMessage.HandleSuccess(LogHeader, msg, "Sent all scene objects"); + return; + } + case RegionSyncMessage.MsgType.SetObjectProperty: + { + OSDMap data = RegionSyncUtil.DeserializeMessage(msg, LogHeader); + if (!data.ContainsKey("UUID") || !data.ContainsKey("name")) + { + m_log.WarnFormat("{0} Parameters missing in SetObjectProperty request, need \"UUID\", \"name\" (property-name), and \"valParams\" (property-value)", LogHeader); + return; + } + UUID objID = data["UUID"].AsUUID(); + string pName = data["name"].AsString(); + //valParams + SetObjectProperty(objID, pName, data); + } + return; + case RegionSyncMessage.MsgType.ActorStop: + { + Shutdown(); + } + return; + case RegionSyncMessage.MsgType.LoadBalanceRequest: + { + m_syncServer.HandleLoadBalanceRequest(this); + return; + } + + case RegionSyncMessage.MsgType.ActorStatus: + { + string status = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + ActorStatus actorStatus = (ActorStatus)Convert.ToInt32(status); + if (actorStatus == ActorStatus.Sync) + { + m_log.Debug(LogHeader + ": received ActorStatus " + actorStatus.ToString()); + m_syncServer.AddSyncedScriptEngine(this); + } + else + { + m_log.Warn(LogHeader + ": not supposed to received RegionSyncMessage.MsgType.ActorStatus==" + status.ToString()); + } + return; + } + + default: + { + m_log.WarnFormat("{0} Unable to handle unsupported message type", LogHeader); + return; + } + } + } + + //For simplicity, we assume the subscription sent by ScriptEngine is legistimate (no overlapping with other script engines, etc) + private void HandleQuarkSubscription(RegionSyncMessage msg) + { + string quarkString = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + m_log.Debug(LogHeader + ": received quark-string: " + quarkString); + + List quarkStringList = RegionSyncUtil.QuarkStringToStringList(quarkString); + //m_quarkSubscriptions = RegionSyncUtil.GetQuarkInfoList(quarkStringList, QuarkInfo.SizeX, QuarkInfo.SizeY); + List quarkList = RegionSyncUtil.GetQuarkInfoList(quarkStringList); + m_syncServer.RegisterQuarkSubscription(quarkList, this); + + m_quarkSubscriptions = new Dictionary(); + foreach (QuarkInfo quark in quarkList) + { + m_quarkSubscriptions.Add(quark.QuarkStringRepresentation, quark); + } + } + + private RegionSyncMessage PrepareObjectUpdateMessage(RegionSyncMessage.MsgType msgType, SceneObjectGroup sog) + { + OSDMap data = new OSDMap(3); + data["locX"] = OSD.FromUInteger(m_scene.RegionInfo.RegionLocX); + data["locY"] = OSD.FromUInteger(m_scene.RegionInfo.RegionLocY); + string sogxml = SceneObjectSerializer.ToXml2Format(sog); + data["sogXml"] = OSD.FromString(sogxml); + + RegionSyncMessage rsm = new RegionSyncMessage(msgType, OSDParser.SerializeJsonString(data)); + return rsm; + } + + private void SendSceneLoc() + { + uint locX = m_scene.RegionInfo.RegionLocX; + uint locY = m_scene.RegionInfo.RegionLocY; + + OSDMap data = new OSDMap(2); + data["locX"] = OSD.FromUInteger(locX); + data["locY"] = OSD.FromUInteger(locY); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.SceneLocation, OSDParser.SerializeJsonString(data))); + } + + public void Send(RegionSyncMessage msg) + { + if (msg.Type == RegionSyncMessage.MsgType.AvatarAppearance) + m_log.WarnFormat("{0} Sending AvatarAppearance to client manager", LogHeader); + + Send(msg.ToBytes()); + } + + private void Send(byte[] data) + { + if (m_tcpclient.Connected) + { + try + { + lock (stats) + { + msgsOut++; + bytesOut += data.Length; + } + m_tcpclient.GetStream().BeginWrite(data, 0, data.Length, ar => + { + if (m_tcpclient.Connected) + { + try + { + m_tcpclient.GetStream().EndWrite(ar); + } + catch (Exception) + { } + } + }, null); + } + catch (IOException) + { + m_log.WarnFormat("{0} Script Engine has disconnected.", LogHeader); + } + } + } + + public void SendObjectUpdate(RegionSyncMessage.MsgType msgType, SceneObjectGroup sog) + { + Send(PrepareObjectUpdateMessage(msgType, sog)); + } + + #endregion Send/Receive messages to/from the remote Script Engine + + #region stub functions for remote actors to set object properties + + //NOTE: the current implementation of setting various object properties is mimicking the way script engine sets object properties + // (i.e. as implemented in LSL_Api.cs. But the function calls are meant to be generic for all actors. We may need to change + // the implementation details later to accomdate more actors, and move it to the Scene implementation. + + //TODO: These functions should eventually be part of DSG interface, rather than part of SceneToScriptEngineConnector. + + private void SetObjectProperty(UUID primID, string pName, OSDMap valParams) + { + //m_log.Debug(LogHeader + " received SetObjectProperty request, " + primID + ", " + pName); + switch (pName) + { + case "object_rez": + //rez a new object, rather than update an existing object's property + RezObjectOnScene(primID, valParams); + break; + case "color": + SetPrimColor(primID, valParams); + break; + case "pos": + SetPrimPos(primID, valParams); + break; + default: + break; + } + } + + //Triggered when an object is rez'ed by script. Followed the implementation in LSL_Api.llRezAtRoot(). + //TODO: the real rez object part should be executed by Scene class, not here + private void RezObjectOnScene(UUID primID, OSDMap data) + { + if(data["inventory"] == null || data["pos"]==null || data["vel"] == null || data["rot"]==null || data["param"]==null){ + m_log.Warn(LogHeader + ": not enough parameters for setting color on " + primID); + return; + } + string inventory = data["inventory"].AsString(); + Vector3 pos = data["pos"].AsVector3(); + Vector3 vel = data["vel"].AsVector3(); + Quaternion rot = data["rot"].AsQuaternion(); + int param = data["param"].AsInteger(); + SceneObjectPart primToFetchObject = m_scene.GetSceneObjectPart(primID); + + if (primToFetchObject == null) + return; + + TaskInventoryDictionary partInventory = (TaskInventoryDictionary)primToFetchObject.TaskInventory.Clone(); + + foreach (KeyValuePair inv in partInventory) + { + if (inv.Value.Name == inventory) + { + // make sure we're an object. + if (inv.Value.InvType != (int)InventoryType.Object) + { + //llSay(0, "Unable to create requested object. Object is missing from database."); + m_log.Warn(LogHeader + ": Unable to create requested object. Object is missing from database."); + return; + } + + Vector3 llpos = pos; + Vector3 llvel = vel; + + // need the magnitude later + float velmag = (float)Util.GetMagnitude(llvel); + + SceneObjectGroup new_group = m_scene.RezObject(primToFetchObject, inv.Value, llpos, rot, llvel, param); + + // If either of these are null, then there was an unknown error. + if (new_group == null) + continue; + if (new_group.RootPart == null) + continue; + + // objects rezzed with this method are die_at_edge by default. + new_group.RootPart.SetDieAtEdge(true); + + /* + m_ScriptEngine.PostObjectEvent(m_host.LocalId, new EventParams( + "object_rez", new Object[] { + new LSL_String( + new_group.RootPart.UUID.ToString()) }, + new DetectParams[0])); + * */ + //NOTE: should replace the above code with the following and implement TriggerRezObject. However, since + // nothing is really doen with "object_rez" in XEngine, we ignore posting the event for now. + //m_scene.EventManager.TriggerRezObject(); + + float groupmass = new_group.GetMass(); + + if (new_group.RootPart.PhysActor != null && new_group.RootPart.PhysActor.IsPhysical && llvel != Vector3.Zero) + { + //Recoil. + //llApplyImpulse(new LSL_Vector(llvel.X * groupmass, llvel.Y * groupmass, llvel.Z * groupmass), 0); + //copy the implementation of llApplyImpulse here. + Vector3 v = new Vector3(llvel.X * groupmass, llvel.Y * groupmass, llvel.Z * groupmass); + if (v.Length() > 20000.0f) + { + v.Normalize(); + v = v * 20000.0f; + } + new_group.RootPart.ApplyImpulse(v, false); + } + return; + } + } + + } + + private void SetPrimColor(UUID primID, OSDMap data) + { + if (!data.ContainsKey("color") || !data.ContainsKey("face")) + { + m_log.Warn(LogHeader + ": not enough parameters for setting color on " + primID); + return; + } + SceneObjectPart primToUpdate = m_scene.GetSceneObjectPart(primID); + if (primToUpdate == null) + return; + Vector3 color = data["color"].AsVector3(); + int face = data["face"].AsInteger(); + + //m_log.DebugFormat("{0}: Set Color request, to set ({1}) on face {2}", LogHeader, color.ToString(), face); + + primToUpdate.SetFaceColor(color, face); + } + + //Whichever actor that triggers this function call shall have already checked the legitimacy of the position. + private void SetPrimPos(UUID primID, OSDMap data) + { + if (!data.ContainsKey("pos")) + { + m_log.Warn(LogHeader + ": not enough parameters for setting pos on " + primID); + return; + } + Vector3 pos = data["pos"].AsVector3(); + SceneObjectPart primToUpdate = m_scene.GetSceneObjectPart(primID); + + if (primToUpdate == null) + return; + + SceneObjectGroup parent = primToUpdate.ParentGroup; + + if (parent.RootPart == primToUpdate) + { + //the prim is the root-part of the object group, set the position of the whole group + parent.UpdateGroupPosition(pos); + } + else + { + //the prim is not the root-part, set the offset position + primToUpdate.OffsetPosition = pos; + parent.HasGroupChanged = true; + parent.ScheduleGroupForTerseUpdate(); + } + } + + #endregion + + #region Event Handlers + //calling by SceneToSESyncServer + public void SEConnectorOnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) + { + m_log.Debug(LogHeader + ": Caught event OnRezScript, send message to Script Engine"); + + OSDMap data = new OSDMap(7); + data["localID"] = OSD.FromUInteger(localID); + data["itemID"] = OSD.FromUUID(itemID); + data["script"] = OSD.FromString(script); + data["startParam"] = OSD.FromInteger(startParam); + data["postOnRez"] = OSD.FromBoolean(postOnRez); + data["engine"] = OSD.FromString(engine); + data["stateSource"] = OSD.FromInteger(stateSource); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.OnRezScript, OSDParser.SerializeJsonString(data))); + } + + public void SEConnectorOnScriptReset(uint localID, UUID itemID) + { + m_log.Debug(LogHeader + ": Caught event OnScriptReset, send message to Script Engine"); + + OSDMap data = new OSDMap(2); + data["localID"] = OSD.FromUInteger(localID); + data["itemID"] = OSD.FromUUID(itemID); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.OnScriptReset, OSDParser.SerializeJsonString(data))); + } + + public void SEConnectorOnUpdateScript(UUID agentID, UUID itemId, UUID primId, bool isScriptRunning, UUID newAssetID) + { + m_log.Debug(LogHeader + ": Caught event OnUpdateScript, send message to Script Engine"); + + OSDMap data = new OSDMap(5); + data["agentID"] = OSD.FromUUID(agentID); + data["itemID"] = OSD.FromUUID(itemId); + data["primID"] = OSD.FromUUID(primId); + data["running"] = OSD.FromBoolean(isScriptRunning); + data["assetID"] = OSD.FromUUID(newAssetID); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.OnUpdateScript, OSDParser.SerializeJsonString(data))); + } + + #endregion Event Handlers + + #region Spacial query functions (should be eventually implemented within Scene) + + //This should be a function of Scene, but since we don't have the quark concept in Scene yet, + //for now we implement it here. + //Ideally, for quark based space representation, the Scene has a list of quarks, and each quark points + //to a list of objects within that quark. Then it's much easier to return the right set of objects within + //a certain space. (Or use DB that supports spatial queries.) + List GetObjectsInGivenSpace(Scene scene, Dictionary quarkSubscriptions) + { + List entities = m_scene.GetEntities(); + List sogList = new List(); + foreach (EntityBase e in entities) + { + if (e is SceneObjectGroup) + { + SceneObjectGroup sog = (SceneObjectGroup)e; + string quarkID = RegionSyncUtil.GetQuarkIDByPosition(sog.AbsolutePosition); + if (m_quarkSubscriptions.ContainsKey(quarkID)) + { + sogList.Add(sog); + } + } + } + + return sogList; + } + + #endregion + + #region Load balancing functions + public void SendLoadBalanceRejection(string response) + { + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.LoadBalanceRejection, response); + Send(msg); + } + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineSyncServer.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineSyncServer.cs new file mode 100644 index 0000000000..4627038900 --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/SceneToScriptEngineSyncServer.cs @@ -0,0 +1,605 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using log4net; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Region.Framework.Scenes.Serialization; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + public class QuarkInfo + { + public static int SizeX = -1; //the length along X axis, should be same for all quarks, need to be set by either the SyncServer on Scene, or by an ActorToSceneConnectorModule. + public static int SizeY = -1; //the length along Y axis, should be same for all quarks + + public int PosX = 0; //the offset position of the left-bottom corner (0~255) + public int PosY = 0; + + private ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private int maxX, maxY; + + //this field is only meaning for the QuarkInfo records on the Scene side + private SceneToScriptEngineConnector m_seConnector=null; + public SceneToScriptEngineConnector SEConnector + { + get { return m_seConnector; } + set { m_seConnector = value; } + } + + private string m_quarkString = ""; + public string QuarkStringRepresentation + { + get { return m_quarkString;} + } + + //public QuarkInfo(int x, int y, int sizeX, int sizeY) + public QuarkInfo(int x, int y) + { + PosX = x; + PosY = y; + //SizeX = sizeX; + //SizeY = sizeY; + maxX = PosX + SizeX; + maxY = PosY + SizeY; + m_quarkString = PosX + "_" + PosY; + } + + //public QuarkInfo(string xy, int sizeX, int sizeY) + public QuarkInfo(string xy) + { + string[] coordinate = xy.Split(new char[] { '_' }); + if (coordinate.Length < 2) + { + m_log.Warn("[QUARK INFO] QuarkInfo Constructor: missing x or y value, format expected x_y: " + xy); + return; + } + PosX = Convert.ToInt32(coordinate[0]); + PosY = Convert.ToInt32(coordinate[1]); + //SizeX = sizeX; + //SizeY = sizeY; + maxX = PosX + SizeX; + maxY = PosY + SizeY; + m_quarkString = PosX + "_" + PosY; + } + + //public void SetPartitionedSceneInfo(int x, int y, int sizeX, int sizeY) + public void SetPartitionedSceneInfo(int x, int y) + { + PosX = x; + PosY = y; + //SizeX = sizeX; + //SizeY = sizeY; + maxX = PosX + SizeX; + maxY = PosY + SizeY; + m_quarkString = PosX + "_" + PosY; + } + + public bool IsPositionInQuarkSpace(Vector3 pos) + { + if (pos.X >= PosX && pos.X <= maxX && pos.Y >= PosY && pos.Y <= maxY) + return true; + else + return false; + } + + } + + //Information of a registered idle script engine. + //Note, this is a temporary solution to inlcude idle script engines here. + //In the future, there might be a independent load balaner that keeps track + //of available idle hardware. + public class IdleScriptEngineInfo + { + public TcpClient TClient; + //public IPAddress ScriptEngineIPAddr; + //public int ScriptEnginePort; + public string ID; + + //Will be used to store the overloaded SE that has send LB request and paired with this idle SE + public SceneToScriptEngineConnector AwaitOverloadedSE=null; + + public IdleScriptEngineInfo(TcpClient tclient) + { + if(tclient==null) return; + TClient = tclient; + IPAddress ipAddr = ((IPEndPoint)tclient.Client.RemoteEndPoint).Address; + int port = ((IPEndPoint)tclient.Client.RemoteEndPoint).Port; + ID = ipAddr.ToString()+":"+port; + } + } + + //Here is the per actor type listening server for Script Engines. + public class SceneToScriptEngineSyncServer + { + #region SceneToScriptEngineSyncServer members + // Set the addr and port for TcpListener + private IPAddress m_addr; + private Int32 m_port; + + private int seCounter; + + // The local scene. + private Scene m_scene; + + private ILog m_log; + + // The listener and the thread which listens for connections from client managers + private TcpListener m_listener; + private Thread m_listenerThread; + + private object m_scriptEngineConnector_lock = new object(); + //private Dictionary m_scriptEngineConnectors = new Dictionary(); + private List m_scriptEngineConnectors = new List(); + + //list of idle script engines that have registered. + private List m_idleScriptEngineList = new List(); + + //List of all quarks, each using the concatenation of x,y values of its left-bottom corners, where the x,y values are the offset + //position in the scene. + private Dictionary m_quarksInScene = new Dictionary(); + + private string LogHeader = "[SCENE TO SCRIPT ENGINE SYNC SERVER]"; + + //Quark related info + //private int QuarkInfo.SizeX; + //private int QuarkInfo.SizeY; + + // Check if any of the client views are in a connected state + public bool Synced + { + get + { + return (m_scriptEngineConnectors.Count > 0); + } + } + + + + #endregion + + // Constructor + public SceneToScriptEngineSyncServer(Scene scene, string addr, int port) + { + if (QuarkInfo.SizeX == -1 || QuarkInfo.SizeY == -1) + { + m_log.Error(LogHeader + " QuarkInfo.SizeX or QuarkInfo.SizeY has not been configured yet."); + Environment.Exit(0); ; + } + + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + //m_log.Warn(LogHeader + "Constructed"); + m_scene = scene; + m_addr = IPAddress.Parse(addr); + m_port = port; + + InitQuarksInScene(); + SubscribeToEvents(); + } + + + private void SubscribeToEvents() + { + m_scene.EventManager.OnRezScript += SESyncServerOnRezScript; + m_scene.EventManager.OnScriptReset += SESyncServerOnScriptReset; + m_scene.EventManager.OnUpdateScript += SESyncServerOnUpdateScript; + } + + private void UnSubscribeToEvents() + { + m_scene.EventManager.OnRezScript -= SESyncServerOnRezScript; + m_scene.EventManager.OnScriptReset -= SESyncServerOnScriptReset; + m_scene.EventManager.OnUpdateScript -= SESyncServerOnUpdateScript; + } + + // Start the server + public void Start() + { + m_listenerThread = new Thread(new ThreadStart(Listen)); + m_listenerThread.Name = "SceneToScriptEngineSyncServer Listener"; + m_log.WarnFormat(LogHeader + ": Starting {0} thread", m_listenerThread.Name); + m_listenerThread.Start(); + //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(); + m_listenerThread.Abort(); + m_listenerThread = null; + + // Stop all existing SceneTOSEConnectors + //TO FINISH + foreach (SceneToScriptEngineConnector seConnector in m_scriptEngineConnectors) + { + seConnector.Shutdown(); + } + m_scriptEngineConnectors.Clear(); + + UnSubscribeToEvents(); + } + + private void InitQuarksInScene() + { + List quarkList = RegionSyncUtil.GetAllQuarksInScene(); + foreach (QuarkInfo quark in quarkList) + { + m_quarksInScene.Add(quark.QuarkStringRepresentation, quark); + } + } + + public void RegisterQuarkSubscription(List quarkSubscriptions, SceneToScriptEngineConnector seConnector) + { + foreach (QuarkInfo quark in quarkSubscriptions) + { + string quarkID = quark.QuarkStringRepresentation; + m_quarksInScene[quarkID].SEConnector = seConnector; + m_log.Debug(LogHeader + ": " + quarkID + " subscribed by "+seConnector.Description); + } + } + + // Add a connector to a script engine + public void AddSyncedScriptEngine(SceneToScriptEngineConnector seConnector) + { + lock (m_scriptEngineConnector_lock) + { + //Dictionary currentlist = m_scriptEngineConnectors; + //Dictionary newlist = new Dictionary(currentlist); + m_scriptEngineConnectors.Add(seConnector); + // Threads 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 + //m_scriptEngineConnectors = newlist; + } + } + + // Remove the client view from the list and decrement synced client counter + public void RemoveSyncedScriptEngine(SceneToScriptEngineConnector seConnector) + { + lock (m_scriptEngineConnector_lock) + { + //Dictionary currentlist = m_scriptEngineConnectors; + //Dictionary newlist = new Dictionary(currentlist); + m_scriptEngineConnectors.Remove(seConnector); + // Threads 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 + //m_scriptEngineConnectors = newlist; + } + } + + // Listen for connections from a new RegionSyncClient + // When connected, start the ReceiveLoop for the new client + private void Listen() + { + m_listener = new TcpListener(m_addr, m_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(LogHeader + ": Listening for new connections on port {0}...", m_port.ToString()); + TcpClient tcpclient = m_listener.AcceptTcpClient(); + IPAddress addr = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Address; + int port = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Port; + + ActorStatus actorStatus = GetActorStatus(tcpclient); + + switch (actorStatus) + { + case ActorStatus.Sync: + // Add the SceneToScriptEngineConnector to the list + SceneToScriptEngineConnector sceneToSEConnector = new SceneToScriptEngineConnector(++seCounter, m_scene, tcpclient, this); + AddSyncedScriptEngine(sceneToSEConnector); + break; + case ActorStatus.Idle: + IdleScriptEngineInfo idleSE = new IdleScriptEngineInfo(tcpclient); + m_log.Debug(": adding an idle SE ("+addr+","+port+")"); + m_idleScriptEngineList.Add(idleSE); + break; + default: + break; + } + + } + } + catch (SocketException e) + { + m_log.WarnFormat(LogHeader + " [Listen] SocketException: {0}", e); + } + } + + /* + public void RegisterSyncedScriptEngine(SceneToScriptEngineConnector sceneToSEConnector) + { + //first, remove it from the idle list + m_idleScriptEngineList.Remove(sceneToSEConnector); + + //now, added to the synced SE list + AddSyncedScriptEngine(sceneToSEConnector); + } + * */ + + + // Broadcast a message to all connected RegionSyncClients + public void SendToAllConnectedSE(RegionSyncMessage msg) + { + if (m_scriptEngineConnectors.Count > 0) + { + m_log.Debug(LogHeader + ": region " + m_scene.RegionInfo.RegionName + " Broadcast to ScriptEngine, msg " + msg.Type); + foreach (SceneToScriptEngineConnector seConnector in m_scriptEngineConnectors) + { + seConnector.Send(msg); + } + } + + } + + //TO FINISH: Find the right SceneToSEConnector to forward the message + public void SendToSE(RegionSyncMessage.MsgType msgType, SceneObjectGroup sog) + { + SceneToScriptEngineConnector seConnector = GetSceneToSEConnector(sog); + if (seConnector != null) + { + seConnector.SendObjectUpdate(msgType, sog); + } + else + { + m_log.Warn(LogHeader + sog.AbsolutePosition.ToString() + " not covered by any script engine"); + } + } + + //This is to send a message, rsm, to script engine, and the message is about object SOG. E.g. RemovedObject + public void SendToSE(RegionSyncMessage rsm, SceneObjectGroup sog) + { + SceneToScriptEngineConnector seConnector = GetSceneToSEConnector(sog); + if (seConnector != null) + { + seConnector.Send(rsm); + } + else + { + m_log.Warn(LogHeader + sog.AbsolutePosition.ToString() + " not covered by any script engine"); + } + } + + + private SceneToScriptEngineConnector GetSceneToSEConnector(SceneObjectGroup sog) + { + if (sog==null) + { + return m_scriptEngineConnectors[0]; + } + else + { + //Find the right SceneToSEConnector by the object's position + //TO FINISH: Map the object to a quark first, then map the quark to SceneToSEConnector + string quarkID = RegionSyncUtil.GetQuarkIDByPosition(sog.AbsolutePosition); + SceneToScriptEngineConnector seConnector = m_quarksInScene[quarkID].SEConnector; + return seConnector; + } + + } + + private ActorStatus GetActorStatus(TcpClient tcpclient) + { + m_log.Debug(LogHeader+ ": Get Actor status"); + + RegionSyncMessage msg = new RegionSyncMessage(tcpclient.GetStream()); + ActorStatus actorStatus; + switch (msg.Type) + { + case RegionSyncMessage.MsgType.ActorStatus: + { + string status = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + m_log.Debug(LogHeader + ": recv status: " + status); + actorStatus = (ActorStatus)Convert.ToInt32(status); + break; + } + default: + { + m_log.Error(LogHeader + ": Expect Message Type: ActorStatus"); + RegionSyncMessage.HandleError("[REGION SYNC SERVER]", msg, String.Format("{0} Expect Message Type: ActorType", "[REGION SYNC SERVER]")); + return ActorStatus.Null; + } + } + return actorStatus; + } + + + #region Event Handlers + + private void SESyncServerOnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) + { + SceneObjectGroup sog = m_scene.GetSceneObjectPart(localID).ParentGroup; + if (sog != null) + { + //First, figure out which script engine to forward the event + SceneToScriptEngineConnector seConnector = GetSceneToSEConnector(sog); + if (seConnector != null) + { + m_log.Debug(LogHeader + ": Caught event OnRezScript, send message to Script Engine " + seConnector.Description); + + seConnector.SEConnectorOnRezScript(localID, itemID, script, startParam, postOnRez, engine, stateSource); + } + } + } + + private void SESyncServerOnScriptReset(uint localID, UUID itemID) + { + + SceneObjectGroup sog = m_scene.GetSceneObjectPart(localID).ParentGroup; + if (sog != null) + { + //First, figure out which script engine to forward the event + SceneToScriptEngineConnector seConnector = GetSceneToSEConnector(sog); + if (seConnector != null) + { + m_log.Debug(LogHeader + ": Caught event OnScriptReset, send message to Script Engine " + seConnector.Description); + + seConnector.SEConnectorOnScriptReset(localID, itemID); + } + } + } + + private void SESyncServerOnUpdateScript(UUID agentID, UUID itemId, UUID primId, bool isScriptRunning, UUID newAssetID) + { + SceneObjectGroup sog = m_scene.GetSceneObjectPart(primId).ParentGroup; + if (sog != null) + { + //First, figure out which script engine to forward the event + SceneToScriptEngineConnector seConnector = GetSceneToSEConnector(sog); + if (seConnector != null) + { + m_log.Debug(LogHeader + ": Caught event OnScriptReset, send message to Script Engine " + seConnector.Description); + + seConnector.SEConnectorOnUpdateScript(agentID, itemId, primId, isScriptRunning, newAssetID); + } + } + } + + #endregion Event Handlers + + #region Load balancing members and functions + //keep track of idle script engines that are in the process of load balancing (they are off the idle list, but not a working script engine yet (not sync'ing with Scene yet)). + private Dictionary m_loadBalancingIdleSEs = new Dictionary(); + public void HandleLoadBalanceRequest(SceneToScriptEngineConnector seConnctor) + { + //Let's start a thread to do the job, so that we can return quickly and don't block on ReceiveLoop() + + Thread partitionThread = new Thread(new ParameterizedThreadStart(TriggerLoadBalanceProcess)); + partitionThread.Name = "TriggerLoadBalanceProcess"; + partitionThread.Start((object)seConnctor); + } + + public void TriggerLoadBalanceProcess(object arg) + { + SceneToScriptEngineConnector seConnctor = (SceneToScriptEngineConnector)arg; + IdleScriptEngineInfo idleScriptEngineInfo = GetIdleScriptEngineConnector(); + if (idleScriptEngineInfo != null) + { + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.LoadMigrationNotice); + Send(idleScriptEngineInfo.TClient, msg.ToBytes()); + m_log.Debug(LogHeader + ": HandleLoadBalanceRequest from " + seConnctor.Description + ", picked idle SE: " + idleScriptEngineInfo.ID); + + //keep track of which overload script engine is paired up with which idle script engine + idleScriptEngineInfo.AwaitOverloadedSE = seConnctor; + m_loadBalancingIdleSEs.Add(idleScriptEngineInfo.ID, idleScriptEngineInfo); + + m_log.Debug("ToSEConnector portal: local -" + + ((IPEndPoint)idleScriptEngineInfo.TClient.Client.LocalEndPoint).Address.ToString() + ":" + ((IPEndPoint)idleScriptEngineInfo.TClient.Client.LocalEndPoint).Port + + "; remote - " + ((IPEndPoint)idleScriptEngineInfo.TClient.Client.RemoteEndPoint).Address.ToString() + ":" + + ((IPEndPoint)idleScriptEngineInfo.TClient.Client.RemoteEndPoint).Port); + + //Now we expect the idle script engine to reply back + msg = new RegionSyncMessage(idleScriptEngineInfo.TClient.GetStream()); + if (msg.Type != RegionSyncMessage.MsgType.LoadMigrationListenerInitiated) + { + m_log.Warn(LogHeader + ": should receive a message of type LoadMigrationListenerInitiated, but received " + msg.Type.ToString()); + } + else + { + //Before the load is migrated from overloaded script engine to the idle engine, sync with the DB to update the state in DB + List entities = m_scene.GetEntities(); + foreach (EntityBase entity in entities) + { + if (!entity.IsDeleted && entity is SceneObjectGroup && ((SceneObjectGroup)entity).HasGroupChanged) + { + m_scene.ForceSceneObjectBackup((SceneObjectGroup)entity); + } + } + + OSDMap data = DeserializeMessage(msg); + if (!data.ContainsKey("ip") || !data.ContainsKey("port") ) + { + m_log.Warn(LogHeader + ": parameters missing in SceneLocation message from Scene, need to have ip, port"); + return; + } + //echo the information back to the overloaded script engine + seConnctor.Send(new RegionSyncMessage(RegionSyncMessage.MsgType.LoadBalanceResponse, OSDParser.SerializeJsonString(data))); + + m_log.Debug(LogHeader + " now remove script engine " + idleScriptEngineInfo.ID + " from idle SE list, and create SceneToScriptEngineConnector to it"); + //create a SceneToSEConnector for the idle script engine, who will be sync'ing with this SyncServer soon + SceneToScriptEngineConnector sceneToSEConnector = new SceneToScriptEngineConnector(++seCounter, m_scene, idleScriptEngineInfo.TClient, this); + //Now remove the script engine from the idle SE list + m_idleScriptEngineList.Remove(idleScriptEngineInfo); + //AddSyncedScriptEngine(sceneToSEConnector); + } + + } + else + { + seConnctor.SendLoadBalanceRejection("no idle script engines"); + } + } + + HashSet exceptions = new HashSet(); + private OSDMap DeserializeMessage(RegionSyncMessage 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 Send(TcpClient tcpclient, byte[] data) + { + if (tcpclient.Connected) + { + try + { + tcpclient.GetStream().BeginWrite(data, 0, data.Length, ar => + { + if (tcpclient.Connected) + { + try + { + tcpclient.GetStream().EndWrite(ar); + } + catch (Exception) + { } + } + }, null); + } + catch (IOException) + { + m_log.WarnFormat("{0} Script Engine has disconnected.", LogHeader); + } + } + } + + private IdleScriptEngineInfo GetIdleScriptEngineConnector() + { + if (m_idleScriptEngineList.Count == 0) + return null; + IdleScriptEngineInfo idleSEInfo = m_idleScriptEngineList[0]; + m_idleScriptEngineList.Remove(idleSEInfo); + return idleSEInfo; + } + + #endregion Load balancing functions + } +} diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnector.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnector.cs new file mode 100644 index 0000000000..6e48a82b7f --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnector.cs @@ -0,0 +1,1422 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes.Types; +using log4net; + +using Nini.Config; + +//SYNC_SE: Added by KittyL, 06/21/2010 +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + //The data structure that maintains the list of quarks a script engine subscribes to. + //It might be better to organize the quarks in a k-d tree structure, for easier + //partitioning of the quarks based on spatial information. + //But for now, we just assume the quarks each script engine operates on form a rectangle shape. + //So we just use xmin,ymin and xmax,ymax to identify the rectange; and use a List structure to + //store the quarks. + //Quark size is defined in QuarkInfo.SizeX and QuarkInfo.SizeY. + public class QuarkSubsriptionInfo + { + private List m_quarks; + private int m_minX; + private int m_minY; + private int m_maxX; + private int m_maxY; + //public static int QuarkSizeX; + //public static int QuarkSizeY; + + public int MinX + { + get { return m_minX; } + } + + public int MinY + { + get { return m_minY; } + } + + public int MaxX + { + get { return m_maxX; } + } + + public int MaxY + { + get { return m_maxY; } + } + + public QuarkSubsriptionInfo(int xmin, int ymin, int xmax, int ymax) + { + //QuarkInfo.SizeX = quarkSizeX; + //QuarkInfo.SizeY = quarkSizeY; + m_minX = xmin; + m_minY = ymin; + m_maxX = xmax; + m_maxY = ymax; + m_quarks = RegionSyncUtil.GetQuarkSubscriptions(xmin, ymin, xmax, ymax); + } + + public QuarkSubsriptionInfo(string space) + { + //QuarkInfo.SizeX = quarkSizeX; + //QuarkInfo.SizeY = quarkSizeY; + int[] coordinates = RegionSyncUtil.GetCornerCoordinates(space); + m_minX = coordinates[0]; + m_minY = coordinates[1]; + m_maxX = coordinates[2]; + m_maxY = coordinates[3]; + + m_quarks = RegionSyncUtil.GetQuarkSubscriptions(m_minX, m_minY, m_maxX, m_maxY); + } + + public List QuarkList + { + get { return m_quarks; } + } + + /// + /// Test if a given position is in the space of the quarks. + /// + /// + /// + public bool IsInSpace(Vector3 pos) + { + //For now, the test is simple: we just compare with the corners of the rectangle space. + //Need to be modified when we later support irregular quark unions. + if (pos.X < MinX || pos.X >= MaxX || pos.Y < MinY || pos.Y >= MaxY) + { + return false; + } + else + return true; + } + + public string StringRepresentation() + { + return RegionSyncUtil.QuarkInfoToString(m_quarks); + } + + public void RemoveQuarks(QuarkSubsriptionInfo remQuarks) + { + //First, remove the given quarks. + QuarkInfo toRemoveQuark; + foreach (QuarkInfo rQuark in remQuarks.QuarkList) + { + toRemoveQuark = null; + foreach (QuarkInfo originalQuark in m_quarks) + { + if (rQuark.QuarkStringRepresentation.Equals(originalQuark.QuarkStringRepresentation)) + { + toRemoveQuark = originalQuark; + break; + } + } + if (toRemoveQuark != null) + { + m_quarks.Remove(toRemoveQuark); + } + } + + //Second, adjust the space bounding box + //The removed space should share the same top-right corner with the original space (simple binary space partition) + int[] remainingBoundingBox = RegionSyncUtil.RemoveSpace(m_minX, m_minY, m_maxX, m_maxY, remQuarks.MinX, remQuarks.MinY, remQuarks.MaxX, remQuarks.MaxY); + m_minX = remainingBoundingBox[0]; + m_minY = remainingBoundingBox[1]; + m_maxX = remainingBoundingBox[2]; + m_maxY = remainingBoundingBox[3]; + } + } + + // The RegionSyncScriptEngine has a receive thread to process messages from the RegionSyncServer. + // It is the client side of the synchronization channel, and send to and receive updates from the + // Auth. Scene. The server side thread handling the sync channel is implemented in RegionSyncScriptAPI.cs. + // + // The current implementation is very similar to RegionSyncClient. + // TODO: eventually the same RegionSyncSceneAPI should handle all traffic from different actors, e.g. + // using a pub/sub model. + public class ScriptEngineToSceneConnector + { + #region ScriptEngineToSceneConnector members + + // Set the addr and port of RegionSyncServer + private IPAddress m_addr; + private string m_addrString; + private Int32 m_port; + + // A reference to the local scene + private Scene m_validLocalScene; + + // The avatars added to this client manager for clients on other client managers + object m_syncRoot = new object(); + + // The logfile + private ILog m_log; + + private string LogHeader = "[SCRIPT ENGINE TO SCENE CONNECTOR]"; + + // The listener and the thread which listens for connections from client managers + private Thread m_rcvLoop; + + // The client connection to the RegionSyncServer + private TcpClient m_client = new TcpClient(); + + private string m_authSceneName; + + //KittyL: Comment out m_statsTimer for now, will figure out whether we need it for ScriptEngine later + //private System.Timers.Timer m_statsTimer = new System.Timers.Timer(30000); + + // The queue of incoming messages which need handling + //private Queue m_inQ = new Queue(); + + //KittyL: added to identify different actors + private ActorType m_actorType = ActorType.ScriptEngine; + + private bool m_debugWithViewer = false; + + private QuarkSubsriptionInfo m_subscribedQuarks; + + + private IConfig m_sysConfig; + + //members for load balancing purpose + //private TcpClient m_loadMigrationSouceEnd = null; + private LoadMigrationEndPoint m_loadMigrationSouceEnd = null; + private Thread m_loadMigrationSrcRcvLoop; + private LoadMigrationListener m_loadMigrationListener = null; + + //List of queued messages, when the space that the updated object is located is being migrated + private List m_updateMsgQueue = new List(); + + #endregion + + + // Constructor + public ScriptEngineToSceneConnector(Scene validLocalScene, string addr, int port, bool debugWithViewer, string authSceneName, IConfig sysConfig) + { + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + m_validLocalScene = validLocalScene; + m_addr = IPAddress.Parse(addr); + m_addrString = addr; + m_port = port; + m_debugWithViewer = debugWithViewer; + m_authSceneName = authSceneName; + //m_statsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimerElapsed); + m_sysConfig = sysConfig; + + //assume we are connecting to the whole scene as one big quark + m_subscribedQuarks = new QuarkSubsriptionInfo(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize); + } + + /// + /// Create a ScriptEngineToSceneConnector based on the space it is supposed to subscribe (and operate) on. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public ScriptEngineToSceneConnector(Scene validLocalScene, string addr, int port, bool debugWithViewer, string authSceneName, + string subscriptionSpace, IConfig sysConfig) + { + if (QuarkInfo.SizeX == -1 || QuarkInfo.SizeY == -1) + { + m_log.Error("QuarkInfo.SizeX or QuarkInfo.SizeY has not been configured."); + Environment.Exit(0); + } + + m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + m_validLocalScene = validLocalScene; + m_addr = IPAddress.Parse(addr); + m_addrString = addr; + m_port = port; + m_debugWithViewer = debugWithViewer; + m_authSceneName = authSceneName; + //m_statsTimer.Elapsed += new System.Timers.ElapsedEventHandler(StatsTimerElapsed); + m_sysConfig = sysConfig; + + m_subscribedQuarks = new QuarkSubsriptionInfo(subscriptionSpace); + } + + public ScriptEngineToSceneConnectorModule GetSEToSceneConnectorMasterModule() + { + if (m_validLocalScene == null) + return null; + return (ScriptEngineToSceneConnectorModule)m_validLocalScene.ScriptEngineToSceneConnectorModule; + } + + public Scene GetValidLocalScene() + { + return m_validLocalScene; + } + + + private List GetQuarkStringList() + { + List quarkList = new List(); + foreach (QuarkInfo quark in m_subscribedQuarks.QuarkList) + { + quarkList.Add(quark.QuarkStringRepresentation); + } + return quarkList; + } + + + + /// + /// Get the reference to the local scene that is supposed to be mapped to the remote auth. scene. + /// + /// + /// + private Scene GetLocalScene(string authSceneName) + { + ScriptEngineToSceneConnectorModule connectorModule = GetSEToSceneConnectorMasterModule(); + return connectorModule.GetLocalScene(authSceneName); + } + + // Start the RegionSyncScriptEngine client thread + public bool Start() + { + if (EstablishConnection()) + { + StartStateSync(); + return true; + } + else + { + return false; + } + } + + private bool EstablishConnection() + { + if (m_client.Connected) + { + m_log.Warn(LogHeader + ": already connected"); + return false; + } + + try + { + m_client.Connect(m_addr, m_port); + } + catch (Exception e) + { + m_log.WarnFormat("{0} [Start] Could not connect to SceneToScriptEngineSyncServer at {1}:{2}", LogHeader, m_addr, m_port); + m_log.Warn(e.Message); + return false; + } + + m_log.WarnFormat("{0} Connected to SceneToScriptEngineSyncServer at {1}:{2}", LogHeader, m_addr, m_port); + + m_rcvLoop = new Thread(new ThreadStart(ReceiveLoop)); + m_rcvLoop.Name = "ScriptEngineToSceneConnector ReceiveLoop"; + m_log.WarnFormat("{0} Starting {1} thread", LogHeader, m_rcvLoop.Name); + m_rcvLoop.Start(); + return true; + } + + private void StartStateSync() + { + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.ActorStatus, Convert.ToString((int)ActorStatus.Sync)); + Send(msg); + SendQuarkSubscription(); + Thread.Sleep(100); + DoInitialSync(); + } + + + private void SendQuarkSubscription() + { + List quarkStringList = GetQuarkStringList(); + string quarkString = RegionSyncUtil.QuarkStringListToString(quarkStringList); + + m_log.Debug(LogHeader + ": subscribe to quarks: " + quarkString); + //Send(quarkString); + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.QuarkSubscription, quarkString); + Send(msg); + } + + public void SetQuarkSubscription(QuarkSubsriptionInfo quarks) + { + m_subscribedQuarks = quarks; + } + + public void RegisterIdle() + { + EstablishConnection(); + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.ActorStatus, Convert.ToString((int)ActorStatus.Idle)); + Send(msg); + } + + private void DoInitialSync() + { + m_validLocalScene.DeleteAllSceneObjects(); + //m_log.Debug(LogHeader + ": send actor type " + m_actorType); + //Send(new RegionSyncMessage(RegionSyncMessage.MsgType.ActorType, Convert.ToString((int)m_actorType))); + //KittyL??? Do we need to send in RegionName? + + //Send(new RegionSyncMessage(RegionSyncMessage.MsgType.RegionName, m_scene.RegionInfo.RegionName)); + //m_log.WarnFormat("Sending region name: \"{0}\"", m_scene.RegionInfo.RegionName); + + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.GetTerrain)); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.GetObjects)); + + // Register for events which will be forwarded to authoritative scene + // m_scene.EventManager.OnNewClient += EventManager_OnNewClient; + //m_scene.EventManager.OnClientClosed += new EventManager.ClientClosed(RemoveLocalClient); + } + + // Disconnect from the RegionSyncServer and close client thread + public void Stop() + { + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.ActorStop, "stop")); + // The remote scene will remove the SceneToScriptEngineConnector when we disconnect + m_rcvLoop.Abort(); + ShutdownClient(); + + //stop the migration connections + //ShutdownClient(m_loadMigrationSouceEnd); + if (m_loadMigrationListener != null) + m_loadMigrationListener.Shutdown(); + } + + public void ReportStatus() + { + m_log.WarnFormat("{0} Synchronized to RegionSyncServer at {1}:{2}", LogHeader, m_addr, m_port); + lock (m_syncRoot) + { + //TODO: should be reporting about the information of the objects/scripts + } + } + + private void ShutdownClient() + { + m_log.WarnFormat("{0} Disconnected from RegionSyncServer. Shutting down.", LogHeader); + + //TODO: remove the objects and scripts + //lock (m_syncRoot) + //{ + + //} + + if (m_client != null) + { + // Close the connection + m_client.Client.Close(); + m_client.Close(); + } + + } + + // Listen for messages from a RegionSyncServer + // *** This is the main thread loop for each connected client + private void ReceiveLoop() + { + m_log.WarnFormat("{0} Thread running: {1}", LogHeader, m_rcvLoop.Name); + while (true && m_client.Connected) + { + RegionSyncMessage msg; + // Try to get the message from the network stream + try + { + msg = new RegionSyncMessage(m_client.GetStream()); + //m_log.WarnFormat("{0} Received: {1}", LogHeader, msg.ToString()); + } + // If there is a problem reading from the client, shut 'er down. + catch + { + ShutdownClient(); + return; + } + // Try handling the message + try + { + //lock (m_syncRoot) -- KittyL: why do we need to lock here? We could lock inside HandleMessage if necessary, and lock on different objects for better performance + HandleMessage(msg); + } + catch (Exception e) + { + m_log.WarnFormat("{0} Encountered an exception: {1} (MSGTYPE = {2})", LogHeader, e.Message, msg.ToString()); + } + } + } + + #region SEND + //DSG-TODO: for Scene based DSG, Send() also needs to figure out which Scene to send to, e.g. needs a switching function based on object position + + // Send a message to a single connected RegionSyncServer + private void Send(string msg) + { + byte[] bmsg = System.Text.Encoding.ASCII.GetBytes(msg + System.Environment.NewLine); + Send(bmsg); + } + + private void Send(RegionSyncMessage msg) + { + Send(msg.ToBytes()); + //m_log.WarnFormat("{0} Sent {1}", LogHeader, msg.ToString()); + } + + private void Send(byte[] data) + { + if (m_client.Connected) + { + try + { + m_client.GetStream().Write(data, 0, data.Length); + } + // If there is a problem reading from the client, shut 'er down. + // *** Still need to alert the module that it's no longer connected! + catch + { + ShutdownClient(); + } + } + } + + /// + /// Send requests to update object properties in the remote authoratative Scene. + /// + /// UUID of the object + /// name of the property to be updated + /// parameters of the value of the property + /// + public void SendSetPrimProperties(UUID primID, string pName, object val) + { + OSDMap data = new OSDMap(); + data["UUID"] = OSD.FromUUID(primID); + data["name"] = OSD.FromString(pName); + object[] valParams = (object[])val; + //data["param"] = OSD.FromString(presence.ControllingClient.LastName); + Vector3 pos, vel; + switch (pName) + { + case "object_rez": + //this is to rez an object from the prim's inventory, rather than change the prim's property + if(valParams.Length<5){ + m_log.Warn(LogHeader+": values for object's "+pName+" property should include: inventory, pos, velocity, rotation, param"); + return; + } + string inventory = (string)valParams[0]; + pos = (Vector3)valParams[1]; + vel = (Vector3)valParams[2]; + Quaternion rot = (Quaternion)valParams[3]; + int param = (int)valParams[4]; + data["inventory"]=OSD.FromString(inventory); + data["pos"]=OSD.FromVector3(pos); + data["vel"] = OSD.FromVector3(vel); + data["rot"] = OSD.FromQuaternion(rot); + data["param"] = OSD.FromInteger(param); + break; + case "color": + if(valParams.Length<2){ + m_log.Warn(LogHeader+": values for object's "+pName+" property should include: color-x, color-y, color-z, face"); + return; + } + //double cx = (double)valParams[0]; + //double cy = (double)valParams[1]; + //double cz = (double)valParams[2]; + //Vector3 color = new Vector3((float)cx, (float)cy, (float)cz); + Vector3 color = (Vector3)valParams[0]; + data["color"] = OSD.FromVector3(color); + data["face"] = OSD.FromInteger((int)valParams[1]); + + //m_log.DebugFormat("{0}: to set color {1} on face {2} of prim {3}", LogHeader, color.ToString(), (int)valParams[1], primID); + + break; + case "pos": + if (valParams.Length < 1) + { + m_log.Warn(LogHeader + ": values for object's " + pName + " property should include: pos(vector)"); + return; + } + //double px = (double)valParams[0]; + //double py = (double)valParams[1]; + //double pz = (double)valParams[2]; + //Vector3 pos = new Vector3((float)px, (float)py, (float)pz); + pos = (Vector3)valParams[0]; + data["pos"] = OSD.FromVector3(pos); + + m_log.DebugFormat("{0}: to set pos {1} for prim {2}", LogHeader, pos.ToString(), primID); + break; + default: + // + break; + } + + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.SetObjectProperty, OSDParser.SerializeJsonString(data))); + } + #endregion SEND + + //KittyL: Has to define SendCoarseLocations() here, since it's defined in IRegionSyncClientModule. + // But should not do much as being ScriptEngine, not ClientManager + public void SendCoarseLocations() + { + } + + // Handle an incoming message + // Dan-TODO: This should not be synchronous with the receive! + // Instead, handle messages from an incoming Queue so server doesn't block sending + // + // KittyL: This is the function that ScriptEngine and ClientManager have the most different implementations + private void HandleMessage(RegionSyncMessage msg) + { + //TO FINISH: + + switch (msg.Type) + { + case RegionSyncMessage.MsgType.RegionName: + { + string authSceneName = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + if (authSceneName != m_authSceneName) + { + //This should not happen. If happens, check the configuration files (OpenSim.ini) on other sides. + m_log.Warn(": !!! Mismatch between configurations of authoritative scene. Script Engine's config: "+m_authSceneName+", Scene's config: "+authSceneName); + return; + } + RegionSyncMessage.HandleSuccess(LogHeader, msg, String.Format("Syncing to region \"{0}\"", m_authSceneName)); + return; + } + case RegionSyncMessage.MsgType.Terrain: + { + //We need to handle terrain differently as we handle objects: we really will set the HeightMap + //of each local scene that is the shadow copy of its auth. scene. + Scene localScene = GetLocalScene(m_authSceneName); + if (localScene == null) + { + m_log.Warn("no local Scene mapped to "+m_authSceneName); + return; + } + localScene.Heightmap.LoadFromXmlString(Encoding.ASCII.GetString(msg.Data, 0, msg.Length)); + RegionSyncMessage.HandleSuccess(LogHeader, msg, "Synchronized terrain"); + return; + } + case RegionSyncMessage.MsgType.NewObject: + case RegionSyncMessage.MsgType.UpdatedObject: + { + HandleAddOrUpdateObjectInLocalScene(msg); + return; + } + case RegionSyncMessage.MsgType.RemovedObject: + { + HandleRemoveObject(msg); + return; + } + case RegionSyncMessage.MsgType.ResetScene: + { + m_validLocalScene.DeleteAllSceneObjects(); + return; + } + case RegionSyncMessage.MsgType.OnRezScript: + { + HandleOnRezScript(msg); + return; + } + case RegionSyncMessage.MsgType.OnScriptReset: + { + HandleOnScriptReset(msg); + return; + } + case RegionSyncMessage.MsgType.OnUpdateScript: + { + HandleOnUpdateScript(msg); + return; + } + case RegionSyncMessage.MsgType.SceneLocation: + { + HandleSceneLocation(msg); + return; + } + case RegionSyncMessage.MsgType.LoadMigrationNotice: + { + HanldeLoadMigrationNotice(msg); + return; + } + case RegionSyncMessage.MsgType.LoadBalanceRejection: + { + HandleLoadBalanceRejection(msg); + return; + } + case RegionSyncMessage.MsgType.LoadBalanceResponse: + { + HandleLoadBalanceResponse(msg); + return; + } + default: + { + RegionSyncMessage.HandleError(LogHeader, msg, String.Format("{0} Unsupported message type: {1}", LogHeader, ((int)msg.Type).ToString())); + return; + } + } + + } + + #region Utility functions + + private OSDMap GetOSDMap(string strdata) + { + OSDMap args = null; + OSD buffer = OSDParser.DeserializeJson(strdata); + if (buffer.Type == OSDType.Map) + { + args = (OSDMap)buffer; + return args; + } + return null; + + } + + HashSet exceptions = new HashSet(); + private OSDMap DeserializeMessage(RegionSyncMessage 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; + } + + + + public string GetServerAddressAndPort() + { + return m_addr.ToString() + ":" + m_port.ToString(); + } + + #endregion Utility functions + + #region Handlers for Scene events + + private void HandleAddOrUpdateObjectInLocalScene(RegionSyncMessage msg) + { + OSDMap data = DeserializeMessage(msg); + /* + if (data["locX"] == null || data["locY"] == null || data["sogXml"] == null) + { + m_log.Warn(LogHeader + ": parameters missing in NewObject/UpdatedObject message, need to have locX, locY, sogXml"); + return; + } + * */ + uint locX = data["locX"].AsUInteger(); + uint locY = data["locY"].AsUInteger(); + string sogxml = data["sogXml"].AsString(); + SceneObjectGroup sog = SceneObjectSerializer.FromXml2Format(sogxml); + + if (sog.IsDeleted) + { + RegionSyncMessage.HandleTrivial(LogHeader, msg, String.Format("Ignoring update on deleted LocalId {0}.", sog.LocalId.ToString())); + return; + } + else + { + //We record the object's location (locX, locY), the coordinates of the left-bottom corner of the scene + sog.LocX = locX; + sog.LocY = locY; + bool added = m_validLocalScene.AddOrUpdateObjectInLocalScene(sog, m_debugWithViewer); + + if (added) + { + //m_log.DebugFormat("[{0} Object \"{1}\" ({1}) ({2}) added.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + //rez script + sog.CreateScriptInstances(0, false, m_validLocalScene.DefaultScriptEngine, 0); + sog.ResumeScripts(); + } + else + { + //m_log.DebugFormat("[{0} Object \"{1}\" ({1}) ({2}) updated.", LogHeader, sog.Name, sog.UUID.ToString(), sog.LocalId.ToString()); + } + } + } + + private void HandleRemoveObject(RegionSyncMessage msg) + { + // Get the data from message and error check + OSDMap data = DeserializeMessage(msg); + if (data == null) + { + RegionSyncMessage.HandleError(LogHeader, msg, "Could not deserialize JSON data."); + return; + } + + if (!data.ContainsKey("UUID")) + { + m_log.Warn(LogHeader + ": parameters missing in RemoveObject message from Scene, need to have object UUID"); + return; + } + + // Get the parameters from data + //ulong regionHandle = data["regionHandle"].AsULong(); + // UUID objID = data["sogUUID"].AsUUID(); + UUID primID = data["UUID"].AsUUID(); + + // Find the object in the scene + SceneObjectGroup sog = m_validLocalScene.SceneGraph.GetGroupByPrim(primID); + if (sog == null) + { + //RegionSyncMessage.HandleWarning(LogHeader, msg, String.Format("localID {0} not found.", localID.ToString())); + return; + } + + // Delete the object from the scene + m_validLocalScene.DeleteSceneObject(sog, false); + RegionSyncMessage.HandleSuccess(LogHeader, msg, String.Format("object {0} deleted.", primID.ToString())); + } + + private void HandleOnRezScript(RegionSyncMessage msg) + { + + OSDMap data = DeserializeMessage(msg); + /* + if (!data.ContainsKey("localID") || !data.ContainsKey("itemID") || !data.ContainsKey("script") || !data.ContainsKey("startParam") + || !data.ContainsKey("postOnRez") || !data.ContainsKey("engine") || !data.ContainsKey("stateSource")) + { + if (!data.ContainsKey("localID")) m_log.Debug("missing localID"); + if (!data.ContainsKey("itemID")) m_log.Debug("missing itemID"); + if (!data.ContainsKey("script")) m_log.Debug("missing script"); + if (!data.ContainsKey("startParam")) m_log.Debug("missing startParam"); + if (!data.ContainsKey("postOnRez")) m_log.Debug("missing postOnRez"); + if (!data.ContainsKey("engine")) m_log.Debug("missing engine"); + if (!data.ContainsKey("stateSource")) m_log.Debug("missing stateSource"); + + m_log.Warn(LogHeader+": parameters missing in OnRezScript message from Scene, need to have localID, itemID, script,startParam, postOnRez, engine, and stateSource"); + return; + } + * */ + + uint localID = data["localID"].AsUInteger(); + UUID itemID = data["itemID"].AsUUID(); + string script = data["script"].AsString(); + int startParam = data["startParam"].AsInteger(); + bool postOnRez = data["postOnRez"].AsBoolean(); + string engine = data["engine"].AsString(); + int stateSource = data["stateSource"].AsInteger(); + + m_log.Debug(LogHeader + ": TriggerRezScript"); + //Trigger OnRezScrip to start script engine's execution + m_validLocalScene.EventManager.TriggerRezScript(localID, itemID, script, startParam, postOnRez, engine, stateSource); + } + + private void HandleOnScriptReset(RegionSyncMessage msg) + { + OSDMap data = DeserializeMessage(msg); + /* + if (data["localID"] == null || data["itemID"] == null) + { + m_log.Warn(LogHeader + ": parameters missing in OnScriptReset message from Scene, need to have localID, itemID"); + return; + } + * */ + + uint localID = data["localID"].AsUInteger(); + UUID itemID = data["itemID"].AsUUID(); + m_log.Debug(LogHeader + ": TriggerScriptReset"); + //Trigger OnScriptReset to start script engine's execution + m_validLocalScene.EventManager.TriggerScriptReset(localID, itemID); + } + + private void HandleOnUpdateScript(RegionSyncMessage msg) + { + OSDMap data = DeserializeMessage(msg); + /* + if (!data.ContainsKey("agentID") == null || !data.ContainsKey("itemID") == null || !data.ContainsKey("primID") == null || !data.ContainsKey("running") == null) + { + m_log.Warn(LogHeader + ": parameters missing in OnUpdateScript message from Scene, need to have agentID, itemID, primID, isRunning, and assetID"); + return; + } + * */ + UUID agentID = data["agentID"].AsUUID(); + UUID itemID = data["itemID"].AsUUID(); + UUID primID = data["primID"].AsUUID(); + bool isScriptRunning = data["running"].AsBoolean(); + UUID newAssetID = data["assetID"].AsUUID(); + m_log.Debug(LogHeader + ": HandleOnUpdateScript"); + //m_scene.EventManager.TriggerUpdateTaskInventoryScriptAsset(agentID, itemID, primID, isScriptRunning); + ArrayList errors = m_validLocalScene.OnUpdateScript(agentID, itemID, primID, isScriptRunning, newAssetID); + + //now the script is re-rez'ed, return complication errors if there are + + } + + private void HandleSceneLocation(RegionSyncMessage msg) + { + OSDMap data = DeserializeMessage(msg); + /* + if (!data.ContainsKey("locX")|| !data.ContainsKey("locY")) + { + m_log.Warn(LogHeader + ": parameters missing in SceneLocation message from Scene, need to have locX, locY"); + return; + } + * */ + uint locX = data["locX"].AsUInteger(); + uint locY = data["locY"].AsUInteger(); + + ScriptEngineToSceneConnectorModule connectorModule = GetSEToSceneConnectorMasterModule(); + connectorModule.RecordSceneLocation(m_addrString, m_port, locX, locY); + } + + private void HandleLoadBalanceRejection(RegionSyncMessage msg) + { + string reason = Encoding.ASCII.GetString(msg.Data, 0, msg.Length); + m_log.Debug(LogHeader + ": Load balance request rejected: " + reason); + } + + private void HandleLoadBalanceResponse(RegionSyncMessage msg) + { + //Start a seperate thread to handle the load migration process + + Thread migrationThread = new Thread(new ParameterizedThreadStart(TriggerLoadMigrationProcess)); + migrationThread.Name = "TriggerLoadMigrationProcess"; + migrationThread.Start((object)msg); + } + + #endregion Handlers for events/updates from Scene + + #region Load migration functions: source side for migrating load away + + + public void SendLoadBalanceRequest() + { + //Send a request to Scene, which, is also a load balancing server + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.LoadBalanceRequest); + Send(msg); + } + + //Migration process handler. For now, we do binary space partitioning to balance load. + private void TriggerLoadMigrationProcess(object arg) + { + if (m_loadMigrationSouceEnd!=null) + { + m_log.Warn(LogHeader + ": already in load balancing process"); + return; + } + + RegionSyncMessage msg = (RegionSyncMessage)arg; + OSDMap data = DeserializeMessage(msg); + if (!data.ContainsKey("ip") || !data.ContainsKey("port")) + { + m_log.Warn(LogHeader + ": parameters missing in SceneLocation message from Scene, need to have ip, port"); + return; + } + + string idleSEAddr = data["ip"].AsString(); + int idleSEPort = data["port"].AsInteger(); + m_log.Debug(LogHeader + " receive LB response: idle SE listening at " + idleSEAddr + ":" + idleSEPort); + + Thread.Sleep(500); + //connect to the idle script engine's load migration listening port + TcpClient loadMigrationSrc = new TcpClient(); + try + { + m_log.Debug(LogHeader + ", connecting to " + idleSEAddr + "," + idleSEPort); + loadMigrationSrc.Connect(idleSEAddr, idleSEPort); + } + catch (Exception e) + { + m_log.WarnFormat("{0} [Start] In load migration. Could not connect to {1}:{2}", LogHeader, idleSEAddr, idleSEPort); + m_log.Warn(e.Message); + return; + } + m_log.WarnFormat("{0} Connected to Load migration destination at {1}:{2}", LogHeader, idleSEAddr, idleSEPort); + + //Get the quarks that will be migrated out + QuarkSubsriptionInfo migratingQuarks = PartitionLoad(m_subscribedQuarks); + + //Create an EndPoint to manage the migration process + m_loadMigrationSouceEnd = new LoadMigrationEndPoint(loadMigrationSrc, migratingQuarks, this, m_log); + + OSDMap outData = GetMigrationSpaceDescription(migratingQuarks); + msg = new RegionSyncMessage(RegionSyncMessage.MsgType.MigrationSpace, OSDParser.SerializeJsonString(outData)); + m_loadMigrationSouceEnd.Send(msg); + + } + + + //For now, we simply implement a simple binary space partitioning. + //Ideally, a k-d tree structure should be used and we can divide the quarks accordingly + /// + /// Partition the suscribed quarks, and return the quarks that should be migrated away. + /// + /// + /// + private QuarkSubsriptionInfo PartitionLoad(QuarkSubsriptionInfo quarkSubscription) + { + Dictionary partitionedPlains = RegionSyncUtil.BinarySpaceParition(quarkSubscription.MinX, quarkSubscription.MinY, quarkSubscription.MaxX, quarkSubscription.MaxY); + + int[] upperPlain = partitionedPlains["upper"]; + string partitionedSpace = RegionSyncUtil.GetSpaceStringRepresentationByCorners(upperPlain[0], upperPlain[1], upperPlain[2], upperPlain[3]); + QuarkSubsriptionInfo partitionedQuarks = new QuarkSubsriptionInfo(partitionedSpace); + + return partitionedQuarks; + } + + //For now, since all the quarks form a rectangle shape, we simple use the corners to represent the space. + /// + /// Return a OSDMap that describes the set of quarks that are being migrated. + /// + /// + /// + private OSDMap GetMigrationSpaceDescription(QuarkSubsriptionInfo quarks) + { + + OSDMap outData = new OSDMap(5); + + //send the parameters as uint, to avoid seemingly some bugs in SerializeJsonString that somehow does not serialize integer '0' correctly + outData["minX"] = OSD.FromUInteger((uint)quarks.MinX); + outData["minY"] = OSD.FromUInteger((uint)quarks.MinY); + outData["maxX"] = OSD.FromUInteger((uint)quarks.MaxX); + outData["maxY"] = OSD.FromUInteger((uint)quarks.MaxY); + + outData["regionName"] = OSD.FromString(m_authSceneName); + + return outData; + } + + public void RemoveQuarks(QuarkSubsriptionInfo removedQuarks) + { + //Remove the given quarks + m_subscribedQuarks.RemoveQuarks(removedQuarks); + + //Inform Scene with the updated quark subscription + SendQuarkSubscription(); + } + + #endregion Load migration functions: source side for migrating load away + + #region Load migration functions: load receiving side + + private void HanldeLoadMigrationNotice(RegionSyncMessage msg) + { + m_log.Debug(LogHeader + " received LoadMigrationNotice"); + + if (m_loadMigrationListener == null) + { + + //set up a listening thread for receiving load + string addr = m_sysConfig.GetString("LoadBalancingListenerAddr", "127.0.0.1"); + int port = m_sysConfig.GetInt("LoadBalancingListenerAddrPort", 14000); + m_loadMigrationListener = new LoadMigrationListener(addr, port, this, m_log); + m_loadMigrationListener.Start(); + + m_log.Debug("Scene's LB connector portal: local - " + + ((IPEndPoint)m_client.Client.LocalEndPoint).Address.ToString() + ":" + ((IPEndPoint)m_client.Client.LocalEndPoint).Port + + "; remote -" + + ((IPEndPoint)m_client.Client.RemoteEndPoint).Address.ToString() + ":"+((IPEndPoint)m_client.Client.RemoteEndPoint).Port); + //reply back with the ip:port + OSDMap data = new OSDMap(2); + data["ip"] = OSD.FromString(addr); + data["port"] = OSD.FromInteger(port); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.LoadMigrationListenerInitiated, OSDParser.SerializeJsonString(data))); + } + else + { + //otherwise, this script engine is already expecting incoming load, should reject the notice + //TO FINISH + } + + } + + public void LoadObjectsToValidLocalScene(string regionName) + { + + m_validLocalScene.LoadPrimsFromStorageInGivenSpace(regionName, m_subscribedQuarks.MinX, m_subscribedQuarks.MinY, m_subscribedQuarks.MaxX, m_subscribedQuarks.MaxY); + + //set the LocX,LocY information of each object's copy, so that later we can figure out which SEtoSceneConnector to forwards setproperty request + OpenSim.Services.Interfaces.GridRegion regionInfo = m_validLocalScene.GridService.GetRegionByName(UUID.Zero, regionName); + List entities = m_validLocalScene.GetEntities(); + foreach (EntityBase group in entities) + { + if (group is SceneObjectGroup) + { + SceneObjectGroup sog = (SceneObjectGroup)group; + //seems the returned regionInfo.RegionLocX is the absolute coordinates. need to convert it to the region loc values (e.g. 1000,1000) + sog.LocX = (uint)regionInfo.RegionLocX / Constants.RegionSize; + sog.LocY = (uint)regionInfo.RegionLocY / Constants.RegionSize; + } + } + } + + + public void StartSyncAfterLoadMigration() + { + //First, register new status with Scene (that it is sync'ing now, not idle anymore) + RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.ActorStatus, Convert.ToString((int)ActorStatus.Sync)); + Send(msg); + + //Second, modify the local record, from an idle connector to a busy sync'ing one + ScriptEngineToSceneConnectorModule connectorModule = GetSEToSceneConnectorMasterModule(); + connectorModule.RecordSyncStartAfterLoadMigration(this); + + //Next, send the quark list to Scene, and start syncing (download terrian, start script instances, etc) + SendQuarkSubscription(); + Thread.Sleep(100); + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.GetTerrain)); + Thread.Sleep(100); + + m_log.Debug(LogHeader + ": next start script instances"); + //m_validLocalScene.CreateScriptInstances(); + + List entities = m_validLocalScene.GetEntities(); + foreach (EntityBase group in entities) + { + if (group is SceneObjectGroup) + { + ((SceneObjectGroup)group).RootPart.ParentGroup.CreateScriptInstances(0, false, m_validLocalScene.DefaultScriptEngine, 1); + ((SceneObjectGroup)group).ResumeScripts(); + } + } + } + +#endregion Load migration functions: load receiving side + } + + + public class LoadMigrationListener + { + // The listener and the thread which listens for connections from overloaded script engines + private TcpListener m_listener; + private Thread m_listenerThread; + //private TcpClient m_tcpClient; + private LoadMigrationEndPoint m_loadMigrationReceiver = null; + private IPAddress m_addr; + private int m_port; + private ILog m_log; + private string LogHeader = "LoadMigrationListener"; + private ScriptEngineToSceneConnector m_seToSceneConnector; + + public LoadMigrationListener(string ip, int port, ScriptEngineToSceneConnector sceneConnector, ILog log) + { + m_addr = IPAddress.Parse(ip); + m_port = port; + m_log = log; + m_seToSceneConnector = sceneConnector; + + m_log.Debug(LogHeader + ": to create a new LoadMigrationListener on " + m_addr + "," + m_port); + } + + // Start the server + public void Start() + { + m_listenerThread = new Thread(new ThreadStart(Listen)); + m_listenerThread.Name = "LoadMigrationListener Listener"; + m_log.WarnFormat(LogHeader + ": Starting {0} thread", m_listenerThread.Name); + m_listenerThread.Start(); + //m_log.Warn("[REGION SYNC SERVER] Started"); + } + + private void Listen() + { + m_listener = new TcpListener(m_addr, m_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(LogHeader + ": Listening for new connections on ip {0} port {1}...", m_addr.ToString(), m_port.ToString()); + TcpClient tcpClient = m_listener.AcceptTcpClient(); + + IPAddress addr = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address; + int port = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port; + + m_log.Debug(LogHeader + ": accepted new client - " + addr.ToString() + ", " + port); + + m_loadMigrationReceiver = new LoadMigrationEndPoint(tcpClient, m_seToSceneConnector, m_log); + } + } + catch (SocketException e) + { + m_log.Warn(LogHeader + " [Listen] SocketException"); + } + } + + public void Shutdown() + { + m_listener.Stop(); + m_listenerThread.Abort(); + if(m_loadMigrationReceiver!=null) + m_loadMigrationReceiver.Shutdown(); + } + } + + //The tcp EndPoint of either end of load migration (source or receiving side). + public class LoadMigrationEndPoint + { + private Thread m_receive_loop; + private TcpClient m_tcpclient; + private ILog m_log; + private ScriptEngineToSceneConnector m_seToSceneConnector; + //private int m_minX; + //private int m_minY; + //private int m_maxX; + //private int m_maxY; + //private int QuarkInfo.SizeX; + //private int QuarkInfo.SizeY; + private QuarkSubsriptionInfo m_quarks; + private bool m_inScriptStateSync = false; + private Scene m_validLocalScene = null; + + public QuarkSubsriptionInfo QuarksInfo + { + get { return m_quarks; } + set { m_quarks = value; } + } + + public LoadMigrationEndPoint(TcpClient tcpClient, QuarkSubsriptionInfo quarks, ScriptEngineToSceneConnector sceneConnector, ILog log) + { + m_log = log; + m_tcpclient = tcpClient; + m_seToSceneConnector = sceneConnector; + m_quarks = quarks; + //QuarkInfo.SizeX = quarks.QuarkSizeX; + //QuarkInfo.SizeY = quarks.QuarkSizeY; + + m_receive_loop = new Thread(new ThreadStart(delegate() { ReceiveLoop(); })); + m_receive_loop.Name = "LoadMigrationReceiver"; + //m_log.WarnFormat("{0} Started thread: {1}", LogHeader, m_receive_loop.Name); + m_receive_loop.Start(); + + m_validLocalScene = sceneConnector.GetValidLocalScene(); + } + + public LoadMigrationEndPoint(TcpClient tcpClient, ScriptEngineToSceneConnector sceneConnector, ILog log) + { + m_log = log; + m_tcpclient = tcpClient; + m_seToSceneConnector = sceneConnector; + m_validLocalScene = sceneConnector.GetValidLocalScene(); + //QuarkInfo.SizeX = quarkSizeX; + //QuarkInfo.SizeY = quarkSizeY; + + m_receive_loop = new Thread(new ThreadStart(delegate() { ReceiveLoop(); })); + m_receive_loop.Name = "LoadMigrationReceiver"; + //m_log.WarnFormat("{0} Started thread: {1}", LogHeader, m_receive_loop.Name); + m_receive_loop.Start(); + } + + public void SetQuarksInfo (QuarkSubsriptionInfo quarks){ + m_quarks = quarks; + } + + private void ReceiveLoop() + { + try + { + while (true) + { + RegionSyncMessage msg = new RegionSyncMessage(m_tcpclient.GetStream()); + HandleMessage(msg); + } + } + catch (Exception e) + { + //m_log.WarnFormat("LoadMigrationEndPoint: has disconnected: {1}", e.Message); + m_log.Warn("LoadMigrationEndPoint: has disconnected:"); + } + Shutdown(); + } + + public void Shutdown() + { + // Abort ReceiveLoop Thread, close Socket and TcpClient + m_receive_loop.Abort(); + m_tcpclient.Client.Close(); + m_tcpclient.Close(); + } + + private void HandleMessage(RegionSyncMessage msg) + { + switch (msg.Type) + { + case RegionSyncMessage.MsgType.MigrationSpace: + HandleMigrationSpaceNotice(msg); + break; + case RegionSyncMessage.MsgType.ScriptStateSyncRequest: + HandleScriptStateSyncRequest(msg); + break; + case RegionSyncMessage.MsgType.ScriptStateSyncStart: + m_log.Debug("LoadMigration receiving end: received ScriptStateSyncStart"); + m_inScriptStateSync = true; + break; + case RegionSyncMessage.MsgType.ScriptStateSyncPerObject: + HandleScriptStateSyncPerObject(msg); + break; + case RegionSyncMessage.MsgType.ScriptStateSyncEnd: + HandleScriptStateSyncEnd(msg); + break; + + default: + break; + } + } + + private void HandleMigrationSpaceNotice(RegionSyncMessage msg) + { + //get the space description, + OSDMap data = DeserializeMessage(msg); + if (!data.ContainsKey("minX") || !data.ContainsKey("minY") || !data.ContainsKey("maxX") || !data.ContainsKey("maxY") || !data.ContainsKey("regionName")) + { + m_log.Warn("Load migration receiving end : parameters missing in MigratingSpace message from migration source, need to have (minX,minY),(maxX,maxY),regionName"); + return; + } + int minX = (int) data["minX"].AsUInteger(); + int minY = (int) data["minY"].AsUInteger(); + int maxX = (int) data["maxX"].AsUInteger(); + int maxY = (int) data["maxY"].AsUInteger(); + string regionName = data["regionName"].AsString(); + m_log.Debug("Load migration receiving end: space migrating in -- (" + minX + "," + minY + ")" + ", (" + maxX + "," + maxY + ")"); + + //set the quark subscription information + m_quarks = new QuarkSubsriptionInfo( minX, minY, maxX, maxY); + m_seToSceneConnector.SetQuarkSubscription(m_quarks); + + //load the objects into local scene + //TO FINISH: only load the objects with scripts, but not creating script instances yet + m_seToSceneConnector.LoadObjectsToValidLocalScene(regionName); + + //request script state from the migration source + //RegionSyncMessage msg = new RegionSyncMessage(RegionSyncMessage.MsgType.ScriptStateSyncRequest, OSDParser.SerializeJsonString(data)); + RegionSyncMessage outMsg = new RegionSyncMessage(RegionSyncMessage.MsgType.ScriptStateSyncRequest); + Send(outMsg); + } + + //Upon receiving a ScriptStateSyncRequest from the receiving side, the source needs to suspend script execution, save + //the script's state, and migrate the state to the receiving end. + private void HandleScriptStateSyncRequest(RegionSyncMessage msg) + { + List entities = m_validLocalScene.GetEntities(); + List sogInMigrationSpace = new List(); + + //Inform the receiving end the script state synchronization now starts. + m_inScriptStateSync = true; + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.ScriptStateSyncStart)); + + foreach (EntityBase e in entities) + { + if (e is SceneObjectGroup) + { + SceneObjectGroup sog = (SceneObjectGroup)e; + + //m_log.Debug("check object " + sog.UUID + ", at position " + sog.AbsolutePosition.ToString()); + + if (m_quarks.IsInSpace(sog.AbsolutePosition)) + { + //m_log.Debug("object " + sog.UUID + " in space of " + m_quarks.StringRepresentation()); + + sogInMigrationSpace.Add(sog); + sog.SuspendScripts(); + string scriptStateXml = sog.GetStateSnapshot(); + OSDMap data = new OSDMap(2); + data["UUID"] = OSD.FromUUID(sog.UUID); + data["state"] = OSD.FromString(scriptStateXml); + RegionSyncMessage stateMsg = new RegionSyncMessage(RegionSyncMessage.MsgType.ScriptStateSyncPerObject, OSDParser.SerializeJsonString(data)); + Send(stateMsg); + } + } + } + + //Inform the receiving end the script state synchronization now ends. + Send(new RegionSyncMessage(RegionSyncMessage.MsgType.ScriptStateSyncEnd)); + m_inScriptStateSync = false; + + //Next, remove the space, objects/scripts in the space, + foreach (SceneObjectGroup sog in sogInMigrationSpace) + { + m_validLocalScene.DeleteSceneObject(sog, true); + } + //udpate the quark subscriptions, and informs Scene about the quark subscription changes + m_seToSceneConnector.RemoveQuarks(m_quarks); + } + + private void HandleScriptStateSyncPerObject(RegionSyncMessage msg) + { + OSDMap data = DeserializeMessage(msg); + if (!data.ContainsKey("UUID") || !data.ContainsKey("state")) + { + m_log.Warn("LoadMigrationReceiver: ScriptStateSyncPerObject message missing parameters, need object's UUID and its script's state (string)"); + return; + } + + UUID objID = data["UUID"].AsUUID(); + string stateXml = data["state"].AsString(); + + SceneObjectGroup sog = m_validLocalScene.GetSceneObjectPart(objID).ParentGroup; + sog.SetState(stateXml, m_validLocalScene); + + //m_log.Debug("LoadMigration receiving end: set state for object " + objID + ", script state: " + stateXml); + } + + private void HandleScriptStateSyncEnd(RegionSyncMessage msg) + { + m_log.Debug("LoadMigration receiving end: received ScriptStateSyncEnd"); + m_inScriptStateSync = false; + + //Start sync'ing state with Scene: send quark subscription, get terrian, and start script execution + m_seToSceneConnector.StartSyncAfterLoadMigration(); + } + + private OSDMap DeserializeMessage(RegionSyncMessage msg) + { + OSDMap data = null; + try + { + data = OSDParser.DeserializeJson(Encoding.ASCII.GetString(msg.Data, 0, msg.Length)) as OSDMap; + } + catch (Exception e) + { + m_log.Error("LoadMigrationEndPoint " + Encoding.ASCII.GetString(msg.Data, 0, msg.Length)); + data = null; + } + return data; + } + + + public void Send(RegionSyncMessage msg) + { + byte[] data = msg.ToBytes(); + if (m_tcpclient.Connected) + { + try + { + m_tcpclient.GetStream().Write(data, 0, data.Length); + } + // If there is a problem reading from the client, shut 'er down. + // *** Still need to alert the module that it's no longer connected! + catch + { + Shutdown(); + } + } + } + + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnectorModule.cs b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnectorModule.cs new file mode 100644 index 0000000000..6eb2bfef3f --- /dev/null +++ b/OpenSim/Region/CoreModules/RegionSync/RegionSyncModule/ScriptEngineToSceneConnectorModule.cs @@ -0,0 +1,821 @@ +/* + * 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 copyright + * 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 OpenSim 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 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; + +namespace OpenSim.Region.CoreModules.RegionSync.RegionSyncModule +{ + //The information of the authoratative copy of a scene + public class AuthSceneInfo + { + public string Name; + public string Addr; + public int Port; + public int LocX=-1; + public int LocY=-1; + + public AuthSceneInfo(string name, string addr, int port) + { + Name = name; + Addr = addr; + Port = port; + } + + public AuthSceneInfo(string name, string addr, int port, int locX, int locY) + { + Name = name; + Addr = addr; + Port = port; + LocX = locX; + LocY = locY; + } + } + + //The connector that connects the local Scene (cache) and remote authoratative Scene + public class ScriptEngineToSceneConnectorModule : IRegionModule, IScriptEngineToSceneConnectorModule, ICommandableModule + { + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + m_active = false; //set to false unless this is the valid local scene + + //Read in configuration + IConfig syncConfig = config.Configs["RegionSyncModule"]; + if (syncConfig != null && syncConfig.GetString("Enabled", "").ToLower() == "true") + { + scene.RegionSyncEnabled = true; + } + else + { + scene.RegionSyncEnabled = false; + } + + m_regionSyncMode = syncConfig.GetString("Mode", "").ToLower(); + if (syncConfig == null || m_regionSyncMode != "script_engine") + { + m_log.Warn("[REGION SYNC SCRIPT ENGINE MODULE] Not in script_engine mode. Shutting down."); + return; + } + + //get the name of the valid region for script engine, i.e., that region that will holds all objects and scripts + //if not matching m_scene's name, simply return + string validLocalScene = syncConfig.GetString("ValidScriptEngineScene", ""); + if (!validLocalScene.Equals(scene.RegionInfo.RegionName)) + { + m_log.Warn("Not the valid local scene, shutting down"); + return; + } + m_active = true; + m_validLocalScene = validLocalScene; + + m_log.Debug("Init SEToSceneConnectorModule, for local scene " + scene.RegionInfo.RegionName); + + //get the number of regions this script engine subscribes + m_sceneNum = syncConfig.GetInt("SceneNumber", 1); + + //get the mapping of local scenes to auth. scenes + List authScenes = new List(); + for (int i = 0; i < m_sceneNum; i++) + { + string localScene = "LocalScene" + i; + string localSceneName = syncConfig.GetString(localScene, ""); + string masterScene = localScene + "Master"; + string masterSceneName = syncConfig.GetString(masterScene, ""); + + if (localSceneName.Equals("") || masterSceneName.Equals("")) + { + m_log.Warn(localScene + " or " + masterScene+ " has not been assigned a value in configuration. Shutting down."); + return; + } + + //m_localToAuthSceneMapping.Add(localSceneName, masterSceneName); + RecordLocalAuthSceneMappings(localSceneName, masterSceneName); + authScenes.Add(masterSceneName); + m_localScenesByName.Add(localSceneName, null); + } + + int defaultPort = 13000; + //get the addr:port info of the authoritative scenes + for (int i = 0; i < m_sceneNum; i++) + { + string authSceneName = authScenes[i]; + //string serverAddr = authSceneName + "_ServerIPAddress"; + //string serverPort = authSceneName + "_ServerPort"; + string serverAddr = authSceneName + "_SceneToSESyncServerIP"; + string addr = syncConfig.GetString(serverAddr, "127.0.0.1"); + string serverPort = authSceneName + "_SceneToSESyncServerPort"; + int port = syncConfig.GetInt(serverPort, defaultPort); + defaultPort++; + + AuthSceneInfo authSceneInfo = new AuthSceneInfo(authSceneName, addr, port); + m_authScenesInfoByName.Add(authSceneName, authSceneInfo); + } + + m_scene = scene; + m_scene.RegisterModuleInterface(this); + m_syncConfig = syncConfig; + m_debugWithViewer = syncConfig.GetBoolean("ScriptEngineDebugWithViewer", false); + + //read in the quark size information + //QuarkInfo.SizeX = syncConfig.GetInt("QuarkSizeX", (int)Constants.RegionSize); + //QuarkInfo.SizeY = syncConfig.GetInt("QuarkSizeY", (int)Constants.RegionSize); + QuarkInfo.SizeX = syncConfig.GetInt("QuarkSizeX", (int)Constants.RegionSize); + QuarkInfo.SizeY = syncConfig.GetInt("QuarkSizeY", (int)Constants.RegionSize); + + //m_quarkListString = syncConfig.GetString("InitQuarkSet", ""); //if not specified, dost not subscribe to any quark + //if (m_quarkListString.Equals("all")) + //{ + // m_quarkListString = RegionSyncUtil.QuarkStringListToString(RegionSyncUtil.GetAllQuarkStringInScene(QuarkInfo.SizeX, QuarkInfo.SizeY)); + //} + m_subscriptionSpaceString = syncConfig.GetString("InitSubscriptionSpace", "0_0,256_256"); + + + // Setup the command line interface + m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + InstallInterfaces(); + + m_log.Warn("[REGION SYNC SCRIPT ENGINE MODULE] Initialised"); + } + + public void PostInitialise() + { + if (!m_active) + return; + + //m_log.Warn("[REGION SYNC CLIENT MODULE] Post-Initialised"); + m_scene.EventManager.OnPopulateLocalSceneList += OnPopulateLocalSceneList; + } + + public void Close() + { + if (m_active) + { + m_scene.EventManager.OnPopulateLocalSceneList -= OnPopulateLocalSceneList; + } + m_scene = null; + m_active = false; + } + + public string Name + { + get { return "RegionSyncScriptEngineModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + #endregion + + #region ICommandableModule Members + private readonly Commander m_commander = new Commander("sync"); + public ICommander CommandInterface + { + get { return m_commander; } + } + #endregion + + #region IScriptEngineToSceneConnectorModule members + + + public bool Active + { + get { return m_active; } + } + + public bool Synced + { + get + { + lock(m_client_lock) + { + //return (m_scriptEngineToSceneConnector != null); + return (m_SEToSceneConnectors.Count > 0); + } + } + } + + + #endregion + + #region ScriptEngineToSceneConnectorModule members and functions + + private bool m_active = false; + private string m_serveraddr; + private int m_serverport; + private Scene m_scene; + private ILog m_log; + private Object m_client_lock = new Object(); + //private ScriptEngineToSceneConnector m_scriptEngineToSceneConnector = null; + private IConfig m_syncConfig = null; + private bool m_debugWithViewer = false; + private string m_regionSyncMode = ""; + + //Variables relavant for multi-scene subscription. + private int m_sceneNum = 0; + private string m_validLocalScene = ""; + private Dictionary m_localToAuthSceneMapping = new Dictionary(); //1-1 mapping from local shadow scene to authoratative scene + private Dictionary m_authToLocalSceneMapping = new Dictionary(); //1-1 mapping from authoratative scene to local shadow scene + private Dictionary m_localScenesByName = new Dictionary(); //name and references to local scenes + private Dictionary m_authScenesInfoByName = new Dictionary(); //info of each auth. scene's connector port, stored by each scene's name + private Dictionary m_SEToSceneConnectors = new Dictionary(); //connector for each auth. scene + private Dictionary m_authScenesInfoByLoc = new Dictionary(); //IP and port number of each auth. scene's connector port + private string LogHeader = "[ScriptEngineToSceneConnectorModule]"; + private ScriptEngineToSceneConnector m_idleSEToSceneConnector = null; + + //quark information + //private int QuarkInfo.SizeX; + //private int QuarkInfo.SizeY; + //private string m_quarkListString; + private string m_subscriptionSpaceString; + + public IConfig SyncConfig + { + get { return m_syncConfig; } + } + + public bool DebugWithViewer + { + get { return m_debugWithViewer; } + } + + //Record the locX and locY of one auth. scene (identified by addr:port) this ScriptEngine connects to + public void RecordSceneLocation(string addr, int port, uint locX, uint locY) + { + string loc = SceneLocToString(locX, locY); + if (m_authScenesInfoByLoc.ContainsKey(loc)) + { + m_log.Warn(": have already registered info for Scene at " + loc); + m_authScenesInfoByLoc.Remove(loc); + } + + foreach (KeyValuePair valPair in m_authScenesInfoByName) + { + AuthSceneInfo authSceneInfo = valPair.Value; + if (authSceneInfo.Addr == addr && authSceneInfo.Port == port) + { + authSceneInfo.LocX = (int)locX; + authSceneInfo.LocY = (int)locY; + m_authScenesInfoByLoc.Add(loc, authSceneInfo); + break; + } + } + } + + /// + /// Set the property of a prim located in the given scene (identified by locX, locY) + /// + /// + /// + /// + /// + /// + public void SendSetPrimProperties(uint locX, uint locY, UUID primID, string pName, object pValue) + { + if (!Active || !Synced) + return; + + ScriptEngineToSceneConnector connector = GetSEToSceneConnector(locX, locY); + connector.SendSetPrimProperties(primID, pName, pValue); + } + + public Scene GetLocalScene(string authSceneName) + { + if (!m_authToLocalSceneMapping.ContainsKey(authSceneName)) + { + m_log.Warn(LogHeader + ": no authoritative scene with name "+authSceneName+" recorded"); + return null; + } + string localSceneName = m_authToLocalSceneMapping[authSceneName]; + if (m_localScenesByName.ContainsKey(localSceneName)) + { + return m_localScenesByName[localSceneName]; + } + else + return null; + } + + private string SceneLocToString(uint locX, uint locY) + { + string loc = locX + "-" + locY; + return loc; + } + + //Get the right instance of ScriptEngineToSceneConnector, given the location of the authoritative scene + private ScriptEngineToSceneConnector GetSEToSceneConnector(uint locX, uint locY) + { + string loc = SceneLocToString(locX, locY); + if (!m_authScenesInfoByLoc.ContainsKey(loc)) + return null; + string authSceneName = m_authScenesInfoByLoc[loc].Name; + if (!m_SEToSceneConnectors.ContainsKey(authSceneName)) + { + return null; + } + return m_SEToSceneConnectors[authSceneName]; + } + + + private void RecordLocalAuthSceneMappings(string localSceneName, string authSceneName) + { + if (m_localToAuthSceneMapping.ContainsKey(localSceneName)) + { + m_log.Warn(LogHeader + ": already registered " + localSceneName+", authScene was recorded as "+ m_localToAuthSceneMapping[localSceneName]); + } + else + { + m_localToAuthSceneMapping.Add(localSceneName, authSceneName); + } + if (m_authToLocalSceneMapping.ContainsKey(authSceneName)) + { + m_log.Warn(LogHeader + ": already registered " + authSceneName + ", authScene was recorded as " + m_authToLocalSceneMapping[authSceneName]); + } + else + { + m_authToLocalSceneMapping.Add(authSceneName, localSceneName); + } + } + + //Get the name of the authoritative scene the given local scene maps to. Return null if not found. + private string GetAuthSceneName(string localSceneName) + { + if (m_localToAuthSceneMapping.ContainsKey(localSceneName)) + { + m_log.Warn(LogHeader + ": " + localSceneName + " not registered in m_localToAuthSceneMapping"); + return null; + } + return m_localToAuthSceneMapping[localSceneName]; + } + + //get the name of the local scene the given authoritative scene maps to. Return null if not found. + private string GetLocalSceneName(string authSceneName) + { + if (!m_authToLocalSceneMapping.ContainsKey(authSceneName)) + { + m_log.Warn(LogHeader + ": " + authSceneName + " not registered in m_authToLocalSceneMapping"); + return null; + } + return m_authToLocalSceneMapping[authSceneName]; + } + + #endregion + + #region Event Handlers + + public void OnPopulateLocalSceneList(List localScenes) + //public void OnPopulateLocalSceneList(List localScenes, string[] cmdparams) + { + if (!Active) + return; + + //populate the dictionary m_localScenes + foreach (Scene lScene in localScenes) + { + string name = lScene.RegionInfo.RegionName; + if(!m_localScenesByName.ContainsKey(name)){ + m_log.Warn(LogHeader+": has not reigstered a local scene named "+name); + continue; + } + m_localScenesByName[name] = lScene; + + //lScene.RegionSyncMode = m_regionSyncMode; + lScene.IsOutsideScenes = IsOutSideSceneSubscriptions; + } + + //test position conversion + /* + //Vector3 pos = new Vector3(290, 100, 10); + uint preLocX = Convert.ToUInt32(cmdparams[2]); + uint preLocY = Convert.ToUInt32(cmdparams[3]); + float posX = (float)Convert.ToDouble(cmdparams[4]); + float posY = (float)Convert.ToDouble(cmdparams[5]); + float posZ = (float)Convert.ToDouble(cmdparams[6]); + Vector3 pos = new Vector3(posX, posY, posZ); + uint locX, locY; + Vector3 newPos; + ConvertPosition(1000, 1000, pos, out locX, out locY, out newPos); + * */ + } + + #endregion + + private string GetAllSceneNames() + { + string scenes = ""; + foreach (KeyValuePair valPair in m_localScenesByName) + { + Scene lScene = valPair.Value; + string authScene = m_localToAuthSceneMapping[lScene.RegionInfo.RegionName]; + scenes += authScene + ","; + + } + return scenes; + } + + //public bool IsOutSideSceneSubscriptions(Scene currentScene, Vector3 pos) + public bool IsOutSideSceneSubscriptions(uint locX, uint locY, Vector3 pos) + { + string sceneNames = GetAllSceneNames(); + m_log.Debug(LogHeader + ": IsOutSideSceneSubscriptions called. Conceptually, we are checking inside scene-subscriptions: " + sceneNames); + + //First, convert the position to a scene s.t. the attempting position is contained withing that scene + uint curLocX, curLocY; + Vector3 curPos; + bool converted = ConvertPosition(locX, locY, pos, out curLocX, out curLocY, out curPos); + + if (!converted) + { + m_log.Warn("("+locX+","+locY+","+pos+")"+" converts to scenes with negative coordinates."); + return false; + } + //See of the quark identified by (curLocX,curLocY) is one we subscribed to + string sceneLoc = SceneLocToString(curLocX, curLocY); + if (m_authScenesInfoByLoc.ContainsKey(sceneLoc)) + { + return false; + } + else + { + return true; + } + } + + //When the offset position is outside the range of current scene, convert it to the offset position in the right quark. + //Return null if the new scene's left-bottom corner X or Y value is negative. + //Assumption: A position is uniquely identified by (locX, locY, offsetPos). + private bool ConvertPosition(uint preLocX, uint preLocY, Vector3 prePos, out uint curLocX, out uint curLocY, out Vector3 curPos) + { + Vector3 newPos; + int newLocX; + int newLocY; + //code copied from EntityTransferModule.Cross() + + newPos = prePos; + newLocX = (int)preLocX; + newLocY = (int)preLocY; + + int changeX = 1; + int changeY = 1; + + //Adjust the X values, if going east, changeX is positive, otherwise, it is negative + if (prePos.X >= 0) + { + changeX = (int)(prePos.X / (int)Constants.RegionSize); + } + else + { + changeX = (int)(prePos.X / (int)Constants.RegionSize) - 1 ; + } + newLocX = (int)preLocX + changeX; + newPos.X = prePos.X - (changeX * Constants.RegionSize); + + if (prePos.Y >= 0) + { + changeY = (int)(prePos.Y / (int)Constants.RegionSize); + } + else + { + changeY = (int)(prePos.Y / (int)Constants.RegionSize) - 1; + } + changeY = (int)(prePos.Y / (int)Constants.RegionSize); + newLocY = (int)preLocY + changeY; + newPos.Y = prePos.Y - (changeY * Constants.RegionSize); + + curLocX = (uint)newLocX; + curLocY = (uint)newLocY; + curPos = newPos; + + if (newLocX < 0 || newLocY < 0) + { + //reset the position + curLocX = preLocX; + curLocY = preLocY; + if (newLocX < 0) + { + curPos.X = 2; + } + if(newLocY<0) + { + curPos.Y = 2; + } + return false; + } + else + return true; + } + + + private void DebugSceneStats() + { + return; + /* + List avatars = m_scene.GetAvatars(); + List entities = m_scene.GetEntities(); + m_log.WarnFormat("There are {0} avatars and {1} entities in the scene", avatars.Count, entities.Count); + */ + } + + #region Console Command Interface + //IMPORTANT: these functions should only be actived for the ScriptEngineToSceneConnectorModule that is associated with the valid local scene + + private void InstallInterfaces() + { + Command cmdSyncStart = new Command("start", CommandIntentions.COMMAND_HAZARDOUS, SyncStart, "Begins synchronization with RegionSyncServer."); + //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."); + + //The following two commands are more for easier debugging purpose + Command cmdSyncSetQuarks = new Command("quarkSpace", CommandIntentions.COMMAND_HAZARDOUS, SetQuarkList, "Set the set of quarks to subscribe to. For debugging purpose. Should be issued before \"sync start\""); + cmdSyncSetQuarks.AddArgument("quarkSpace", "The (rectangle) space of quarks to subscribe, represented by x0_y0,x1_y1, the left-bottom and top-right corners of the rectangel space", "String"); + + Command cmdSyncSetQuarkSize = new Command("quarksize", CommandIntentions.COMMAND_HAZARDOUS, SetQuarkSize, "Set the size of each quark. For debugging purpose. Should be issued before \"sync quarks\""); + cmdSyncSetQuarkSize.AddArgument("quarksizeX", "The size on x axis of each quark", "Integer"); + cmdSyncSetQuarkSize.AddArgument("quarksizeY", "The size on y axis of each quark", "Integer"); + + Command cmdSyncRegister = new Command("register", CommandIntentions.COMMAND_HAZARDOUS, SyncRegister, "Register as an idle script engine. Sync'ing with Scene won't start until \"sync start\". "); + + //For debugging load balancing and migration process + Command cmdSyncStartLB = new Command("startLB", CommandIntentions.COMMAND_HAZARDOUS, SyncStartLB, "Register as an idle script engine. Sync'ing with Scene won't start until \"sync start\". "); + + m_commander.RegisterCommand("start", cmdSyncStart); + m_commander.RegisterCommand("stop", cmdSyncStop); + m_commander.RegisterCommand("status", cmdSyncStatus); + m_commander.RegisterCommand("quarkSpace", cmdSyncSetQuarks); + m_commander.RegisterCommand("register", cmdSyncRegister); + m_commander.RegisterCommand("startLB", cmdSyncStartLB); + + 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 SyncStart(Object[] args) + { + lock (m_client_lock) + { + //if (m_scriptEngineToSceneConnector != null) + if(m_SEToSceneConnectors.Count>0) + { + string authScenes = ""; + foreach (KeyValuePair valPair in m_SEToSceneConnectors) + { + authScenes += valPair.Key + ", "; + } + m_log.WarnFormat(LogHeader+": Already synchronized to "+authScenes); + return; + } + //m_log.Warn("[REGION SYNC CLIENT MODULE] Starting synchronization"); + m_log.Warn(LogHeader + ": Starting RegionSyncScriptEngine"); + + if (m_sceneNum > 1) + { + //If there is no arguments following "sync start", then be default we will connect to one or more scenes. + //we need to create a connector to each authoritative scene + foreach (KeyValuePair valPair in m_authScenesInfoByName) + { + string authSceneName = valPair.Key; + AuthSceneInfo authSceneInfo = valPair.Value; + + //create a new connector, the local end of each connector, however, is linked to the ValidScene only, + //since all objects will be contained in this scene only + ScriptEngineToSceneConnector scriptEngineToSceneConnector = new ScriptEngineToSceneConnector(m_scene, authSceneInfo.Addr, authSceneInfo.Port, m_debugWithViewer, authSceneName, m_syncConfig); + if (scriptEngineToSceneConnector.Start()) + { + m_SEToSceneConnectors.Add(authSceneName, scriptEngineToSceneConnector); + } + } + } + else + { + //Only one remote scene to connect to. Subscribe to whatever specified in the config file. + //List quarkStringList = RegionSyncUtil.QuarkStringToStringList(m_quarkListString); + //InitScriptEngineToSceneConnector(quarkStringList); + InitScriptEngineToSceneConnector(m_subscriptionSpaceString); + } + } + } + + private void SyncRegister(Object[] args) + { + //This should not happen. No-validLocalScene should not have register handlers for the command + //if (m_scene.RegionInfo.RegionName != m_validLocalScene) + // return; + + //Registration only, no state sync'ing yet. So only start the connector for the validLocalScene. (For now, we only test this with one scene, and + //quarks are smaller than a 256x256 scene. + string authSceneName = m_localToAuthSceneMapping[m_validLocalScene]; + AuthSceneInfo authSceneInfo = m_authScenesInfoByName[authSceneName]; + m_idleSEToSceneConnector = new ScriptEngineToSceneConnector(m_scene, authSceneInfo.Addr, authSceneInfo.Port, m_debugWithViewer, authSceneName, m_syncConfig); + m_idleSEToSceneConnector.RegisterIdle(); + } + + /// + /// The given ScriptEngineToSceneConnector, after having connected to the Scene (called its Start()), will + /// call this function to remove it self as an idle connector, and to be recorded as one working connector. + /// + /// + public void RecordSyncStartAfterLoadMigration(ScriptEngineToSceneConnector seToSceneConnector) + { + foreach (KeyValuePair valPair in m_authScenesInfoByName) + { + string authSceneName = valPair.Key; + AuthSceneInfo authSceneInfo = valPair.Value; + + string localScene = m_authToLocalSceneMapping[authSceneName]; + + if (localScene != m_scene.RegionInfo.RegionName) + continue; + + if (m_SEToSceneConnectors.ContainsKey(authSceneName)) + { + m_log.Warn(LogHeader + ": Connector to " + authSceneName + " is already considered connected"); + return; + } + + m_SEToSceneConnectors.Add(authSceneName, seToSceneConnector); + //there should only be one element in the dictionary if we reach this loop, anyway, we break from it. + break; + } + m_idleSEToSceneConnector = null; + } + + private void SyncStartLB(Object[] args) + { + string authSceneName = m_localToAuthSceneMapping[m_validLocalScene]; + ScriptEngineToSceneConnector sceneConnector = m_SEToSceneConnectors[authSceneName]; + sceneConnector.SendLoadBalanceRequest(); + } + + private void SetQuarkList(Object[] args) + { + m_subscriptionSpaceString = (string)args[0]; + + InitScriptEngineToSceneConnector(m_subscriptionSpaceString); + } + + private void SetQuarkSize(Object[] args) + { + QuarkInfo.SizeX = (int)args[0]; + QuarkInfo.SizeY = (int)args[1]; + + } + + private void InitScriptEngineToSceneConnector(string space) + { + + foreach (KeyValuePair valPair in m_authScenesInfoByName) + { + string authSceneName = valPair.Key; + AuthSceneInfo authSceneInfo = valPair.Value; + + string localScene = m_authToLocalSceneMapping[authSceneName]; + + if (localScene != m_scene.RegionInfo.RegionName) + continue; + + //create a new connector, the local end of each connector, however, is set of the ValidScene only, + //since all objects will be contained in this scene only + ScriptEngineToSceneConnector scriptEngineToSceneConnector = new ScriptEngineToSceneConnector(m_scene, authSceneInfo.Addr, authSceneInfo.Port, + m_debugWithViewer, authSceneName, space, m_syncConfig); + if (scriptEngineToSceneConnector.Start()) + { + m_SEToSceneConnectors.Add(authSceneName, scriptEngineToSceneConnector); + } + + break; //there should only be one element in the dictionary if we reach this loop, anyway, we break from it. + } + } + + private void SyncStop(Object[] args) + { + lock (m_client_lock) + { + //if (m_scriptEngineToSceneConnector == null) + if(m_SEToSceneConnectors.Count==0 && m_idleSEToSceneConnector==null) + { + m_log.WarnFormat("[REGION SYNC SCRIPT ENGINE MODULE] Already stopped"); + return; + } + + if (m_SEToSceneConnectors.Count > 0) + { + foreach (KeyValuePair valPair in m_SEToSceneConnectors) + { + ScriptEngineToSceneConnector connector = valPair.Value; + if (connector == null) + { + continue; + } + connector.Stop(); + } + m_SEToSceneConnectors.Clear(); + } + else if (m_idleSEToSceneConnector != null) + { + m_idleSEToSceneConnector.Stop(); + m_idleSEToSceneConnector = null; + } + + //m_scriptEngineToSceneConnector.Stop(); + //m_scriptEngineToSceneConnector = null; + m_log.Warn(LogHeader+": Stopping synchronization"); + } + + m_authScenesInfoByLoc.Clear(); + + //save script state and stop script instances + m_scene.EventManager.TriggerScriptEngineSyncStop(); + //remove all objects + m_scene.DeleteAllSceneObjects(); + + } + + private void SyncStatus(Object[] args) + { + lock (m_client_lock) + { + if (m_SEToSceneConnectors.Count == 0) + { + m_log.WarnFormat("[REGION SYNC SCRIPT ENGINE MODULE] Not currently synchronized"); + return; + } + m_log.WarnFormat("[REGION SYNC SCRIPT ENGINE MODULE] Synchronized"); + foreach (KeyValuePair pair in m_SEToSceneConnectors) + { + ScriptEngineToSceneConnector sceneConnector = pair.Value; + sceneConnector.ReportStatus(); + } + } + } + #endregion + } +} diff --git a/OpenSim/Region/Framework/Interfaces/IScriptEngineToSceneConnectorModule.cs b/OpenSim/Region/Framework/Interfaces/IScriptEngineToSceneConnectorModule.cs new file mode 100644 index 0000000000..da3744ba8b --- /dev/null +++ b/OpenSim/Region/Framework/Interfaces/IScriptEngineToSceneConnectorModule.cs @@ -0,0 +1,53 @@ +/* + * 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 copyright + * 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 OpenSim 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. + */ + +//KittyL: Added to support running script engine actor + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.Framework.Interfaces +{ + //the interface for Scene to sync with Script Engine + public interface IScriptEngineToSceneConnectorModule + { + bool Active { get; } + bool Synced { get; } + bool DebugWithViewer { get; } + //void SendCoarseLocations(); + /// + /// Update the property of prim with primID, where the prim is located at quark (LocX, LocY). The length of each quark is configurable. + /// + /// + /// + /// + /// + /// + void SendSetPrimProperties(uint locX, uint locY, UUID primID, string pName, object pValue); + } +} \ No newline at end of file diff --git a/OpenSim/Tools/OpenSim.32BitLaunch/OpenSim.32BitLaunch.csproj b/OpenSim/Tools/OpenSim.32BitLaunch/OpenSim.32BitLaunch.csproj index d829e693e8..95381396a8 100644 --- a/OpenSim/Tools/OpenSim.32BitLaunch/OpenSim.32BitLaunch.csproj +++ b/OpenSim/Tools/OpenSim.32BitLaunch/OpenSim.32BitLaunch.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,8 +10,29 @@ Properties OpenSim._32BitLaunch OpenSim.32BitLaunch - v2.0 + v3.5 512 + + + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true true @@ -47,6 +68,31 @@ OpenSim + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + +