From db5a42ffac7e46d88ef434b939230290d78530a7 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sun, 16 Mar 2014 21:31:38 -0700 Subject: varregion: send terrain patches from where the avatar outward if the parameter [Terrain]SendTerrainUpdatesByViewDistance=true. This tracks which patches have been sent to each client and outputs the patches as the avatar moves. --- .../CoreModules/World/Terrain/TerrainModule.cs | 292 ++++++++++++++++++--- 1 file changed, 259 insertions(+), 33 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index 6e1e8d6..0d7321d 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -96,9 +96,87 @@ namespace OpenSim.Region.CoreModules.World.Terrain private Scene m_scene; private volatile bool m_tainted; private readonly Stack m_undo = new Stack(5); - + private String m_InitialTerrain = "pinhead-island"; + // If true, send terrain patch updates to clients based on their view distance + private bool m_sendTerrainUpdatesByViewDistance = false; + + // Class to keep the per client collection of terrain patches that must be sent. + // A patch is set to 'true' meaning it should be sent to the client. Once the + // patch packet is queued to the client, the bit for that patch is set to 'false'. + private class PatchUpdates + { + private bool[,] updated; // for each patch, whether it needs to be sent to this client + private int updateCount; // number of patches that need to be sent + public ScenePresence Presence; // a reference to the client to send to + public PatchUpdates(TerrainData terrData, ScenePresence pPresence) + { + updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize]; + updateCount = 0; + Presence = pPresence; + // Initially, send all patches to the client + SetAll(true); + } + // Returns 'true' if there are any patches marked for sending + public bool HasUpdates() + { + return (updateCount > 0); + } + public void SetByXY(int x, int y, bool state) + { + this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state); + } + public bool GetByPatch(int patchX, int patchY) + { + return updated[patchX, patchY]; + } + public void SetByPatch(int patchX, int patchY, bool state) + { + bool prevState = updated[patchX, patchY]; + if (!prevState && state) + updateCount++; + if (prevState && !state) + updateCount--; + updated[patchX, patchY] = state; + } + public void SetAll(bool state) + { + updateCount = 0; + for (int xx = 0; xx < updated.GetLength(0); xx++) + for (int yy = 0; yy < updated.GetLength(1); yy++) + updated[xx, yy] = state; + if (state) + updateCount = updated.GetLength(0) * updated.GetLength(1); + } + // Logically OR's the terrain data's patch taint map into this client's update map. + public void SetAll(TerrainData terrData) + { + if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize) + || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize)) + { + throw new Exception( + String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>", + LogHeader, updated.GetLength(0), updated.GetLength(1), + terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize) + ); + } + for (int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize) + { + for (int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize) + { + // Only set tainted. The patch bit may be set if the patch was to be sent later. + if (terrData.IsTaintedAt(xx, yy, false)) + { + this.SetByXY(xx, yy, true); + } + } + } + } + } + // The flags of which terrain patches to send for each of the ScenePresence's + private Dictionary m_perClientPatchUpdates = new Dictionary(); + /// /// Human readable list of terrain file extensions that are supported. /// @@ -127,7 +205,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain { IConfig terrainConfig = config.Configs["Terrain"]; if (terrainConfig != null) + { m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); + m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance); + } } public void AddRegion(Scene scene) @@ -422,9 +503,46 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } + // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients. + // ITerrainModule.TaintTerrain() public void TaintTerrain () { - m_channel.GetTerrainData().TaintAllTerrain(); + lock (m_perClientPatchUpdates) + { + // Set the flags for all clients so the tainted patches will be sent out + foreach (PatchUpdates pups in m_perClientPatchUpdates.Values) + { + pups.SetAll(m_scene.Heightmap.GetTerrainData()); + } + } + } + + // ITerrainModule.PushTerrain() + public void PushTerrain(IClientAPI pClient) + { + if (m_sendTerrainUpdatesByViewDistance) + { + ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId); + if (presence != null) + { + lock (m_perClientPatchUpdates) + { + PatchUpdates pups; + if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups)) + { + // There is a ScenePresence without a send patch map. Create one. + pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence); + m_perClientPatchUpdates.Add(presence.UUID, pups); + } + pups.SetAll(true); + } + } + } + else + { + // The traditional way is to call into the protocol stack to send them all. + pClient.SendLayerData(new float[10]); + } } #region Plugin Loading Methods @@ -676,6 +794,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } } + + // This event also causes changes to be sent to the clients + CheckSendingPatchesToClients(); + + // If things changes, generate some events if (shouldTaint) { m_scene.EventManager.TriggerTerrainTainted(); @@ -749,31 +872,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain presence.ControllingClient.OnLandUndo -= client_OnLandUndo; presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain; } + if (m_perClientPatchUpdates.ContainsKey(client)) + { + m_perClientPatchUpdates.Remove(client); + } } /// - /// Checks to see if the terrain has been modified since last check - /// but won't attempt to limit those changes to the limits specified in the estate settings - /// currently invoked by the command line operations in the region server only - /// - private void CheckForTerrainUpdates() - { - CheckForTerrainUpdates(false); - } - - /// - /// Checks to see if the terrain has been modified since last check. - /// If it has been modified, every all the terrain patches are sent to the client. - /// If the call is asked to respect the estate settings for terrain_raise_limit and - /// terrain_lower_limit, it will clamp terrain updates between these values - /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces - /// should height map deltas be limited to the estate settings limits - /// - private void CheckForTerrainUpdates(bool respectEstateSettings) - { - } - - /// /// Scan over changes in the terrain and limit height changes. This enforces the /// non-estate owner limits on rate of terrain editting. /// Returns 'true' if any heights were limited. @@ -857,17 +962,138 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// The patch corner to send private void SendToClients(TerrainData terrData, int x, int y) { - // We know the actual terrain data passed is ignored. This kludge saves changing IClientAPI. - //float[] heightMap = terrData.GetFloatsSerialized(); - float[] heightMap = new float[10]; - m_scene.ForEachClient( - delegate(IClientAPI controller) + if (m_sendTerrainUpdatesByViewDistance) + { + // Add that this patch needs to be sent to the accounting for each client. + lock (m_perClientPatchUpdates) + { + m_scene.ForEachScenePresence(presence => + { + PatchUpdates thisClientUpdates; + if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates)) + { + // There is a ScenePresence without a send patch map. Create one. + thisClientUpdates = new PatchUpdates(terrData, presence); + m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates); + } + thisClientUpdates.SetByXY(x, y, true); + } + ); + } + } + else + { + // Legacy update sending where the update is sent out as soon as noticed + // We know the actual terrain data passed is ignored. This kludge saves changing IClientAPI. + //float[] heightMap = terrData.GetFloatsSerialized(); + float[] heightMap = new float[10]; + m_scene.ForEachClient( + delegate(IClientAPI controller) + { + controller.SendLayerData(x / Constants.TerrainPatchSize, + y / Constants.TerrainPatchSize, + heightMap); + } + ); + } + } + + private class PatchesToSend : IComparable + { + public int PatchX; + public int PatchY; + public float Dist; + public PatchesToSend(int pX, int pY, float pDist) + { + PatchX = pX; + PatchY = pY; + Dist = pDist; + } + public int CompareTo(PatchesToSend other) + { + return Dist.CompareTo(other.Dist); + } + } + + // Called each frame time to see if there are any patches to send to any of the + // ScenePresences. + // Loop through all the per-client info and send any patches necessary. + private void CheckSendingPatchesToClients() + { + lock (m_perClientPatchUpdates) + { + foreach (PatchUpdates pups in m_perClientPatchUpdates.Values) + { + if (pups.HasUpdates()) + { + // There is something that could be sent to this client. + List toSend = GetModifiedPatchesInViewDistance(pups); + if (toSend.Count > 0) + { + m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2}", + LogHeader, toSend.Count, pups.Presence.Name); + // Sort the patches to send by the distance from the presence + toSend.Sort(); + foreach (PatchesToSend pts in toSend) + { + // TODO: one can send multiple patches in a packet. Do that. + pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null); + // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land); + } + } + } + } + } + } + + private List GetModifiedPatchesInViewDistance(PatchUpdates pups) + { + List ret = new List(); + + ScenePresence presence = pups.Presence; + if (presence == null) + return ret; + + // See if there are patches within our view distance to send. + int startX = (((int) (presence.AbsolutePosition.X - presence.DrawDistance))/Constants.TerrainPatchSize) - 2; + startX = Math.Max(startX, 0); + startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize); + int startY = (((int) (presence.AbsolutePosition.Y - presence.DrawDistance))/Constants.TerrainPatchSize) - 2; + startY = Math.Max(startY, 0); + startY = Math.Min(startY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); + int endX = (((int) (presence.AbsolutePosition.X + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; + endX = Math.Max(endX, 0); + endX = Math.Min(endX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize); + int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; + endY = Math.Max(endY, 0); + endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); + // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. start=<{1},{2}>, end=<{3},{4}>", + // LogHeader, startX, startY, endX, endY); + for (int x = startX; x < endX; x++) + { + for (int y = startY; y < endY; y++) + { + //Need to make sure we don't send the same ones over and over + Vector3 presencePos = presence.AbsolutePosition; + Vector3 patchPos = new Vector3(x * Constants.TerrainPatchSize, y * Constants.TerrainPatchSize, presencePos.Z); + if (pups.GetByPatch(x, y)) { - controller.SendLayerData( x / Constants.TerrainPatchSize, - y / Constants.TerrainPatchSize, - heightMap); + //Check which has less distance, camera or avatar position, both have to be done. + //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off + if (Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50) + || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50)) + { + //They can see it, send it to them + pups.SetByPatch(x, y, false); + float dist = Vector3.DistanceSquared(presencePos, patchPos); + ret.Add(new PatchesToSend(x, y, dist)); + //Wait and send them all at once + // pups.client.SendLayerData(x, y, null); + } } - ); + } + } + return ret; } private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action, -- cgit v1.1 From 22dade6463ce55945c5059af0dc115318297dc1c Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Thu, 20 Mar 2014 22:10:27 -0700 Subject: varregion: More tweeking to only sending patches within avatar draw distance. Still has problems with child avatars. --- .../Framework/EntityTransfer/EntityTransferModule.cs | 13 +++++++------ OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index e583590..70b0e0a 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -820,7 +820,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer agentCircuit.Id0 = currentAgentCircuit.Id0; } - if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + // if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + if (NeedsNewAgent(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) { // brand new agent, let's create a new caps seed agentCircuit.CapsPath = CapsUtil.GetRandomCapsObjectPath(); @@ -894,7 +895,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer IClientIPEndpoint ipepClient; string capsPath = String.Empty; - if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + if (NeedsNewAgent(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Determined that region {0} at {1},{2} needs new child agent for incoming agent {3} from {4}", @@ -1070,7 +1071,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone - if (NeedsClosing(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) + if (NeedsClosing(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) { if (!sp.Scene.IncomingPreCloseClient(sp)) return; @@ -1140,7 +1141,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer IClientIPEndpoint ipepClient; string capsPath = String.Empty; - if (NeedsNewAgent(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) + if (NeedsNewAgent(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY)) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Determined that region {0} at {1},{2} needs new child agent for agent {3} from {4}", @@ -1236,7 +1237,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sp.MakeChildAgent(); // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone - if (NeedsClosing(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) + if (NeedsClosing(sp.Scene.DefaultDrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) { if (!sp.Scene.IncomingPreCloseClient(sp)) return; @@ -2366,7 +2367,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { // The area to check is as big as the current region. // We presume all adjacent regions are the same size as this region. - uint dd = Math.Max((uint)avatar.DrawDistance, + uint dd = Math.Max((uint)avatar.Scene.DefaultDrawDistance, Math.Max(Scene.RegionInfo.RegionSizeX, Scene.RegionInfo.RegionSizeY)); uint startX = Util.RegionToWorldLoc(pRegionLocX) - dd + Constants.RegionSize/2; diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index 0d7321d..ad17d6d 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -1054,7 +1054,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (presence == null) return ret; - // See if there are patches within our view distance to send. + // Compute the area of patches within our draw distance int startX = (((int) (presence.AbsolutePosition.X - presence.DrawDistance))/Constants.TerrainPatchSize) - 2; startX = Math.Max(startX, 0); startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize); @@ -1067,8 +1067,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; endY = Math.Max(endY, 0); endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); - // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. start=<{1},{2}>, end=<{3},{4}>", - // LogHeader, startX, startY, endX, endY); + m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. ddist={1}, start=<{2},{3}>, end=<{4},{5}>", + LogHeader, presence.DrawDistance, startX, startY, endX, endY); for (int x = startX; x < endX; x++) { for (int y = startY; y < endY; y++) -- cgit v1.1 From 0300ec45ebc7b8696c64bf4575bf674a6b1a8fa7 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sat, 31 May 2014 12:10:50 -0700 Subject: Modifications to debugging printouts. No functional changes. --- .../ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs | 10 ++++++++-- OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs | 13 ++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs index 3b38c71..d5fca71 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/LocalGridServiceConnector.cs @@ -199,6 +199,8 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid public GridRegion GetRegionByPosition(UUID scopeID, int x, int y) { GridRegion region = null; + uint regionX = Util.WorldToRegionLoc((uint)x); + uint regionY = Util.WorldToRegionLoc((uint)y); // First see if it's a neighbour, even if it isn't on this sim. // Neighbour data is cached in memory, so this is fast @@ -222,11 +224,15 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid { region = m_GridService.GetRegionByPosition(scopeID, x, y); if (region == null) + { m_log.DebugFormat("{0} GetRegionByPosition. Region not found by grid service. Pos=<{1},{2}>", - LogHeader, x, y); + LogHeader, regionX, regionY); + } else + { m_log.DebugFormat("{0} GetRegionByPosition. Requested region {1} from grid service. Pos=<{2},{3}>", - LogHeader, region.RegionName, x, y); + LogHeader, region.RegionName, regionX, regionY); + } } return region; } diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index ad17d6d..c22c5b6 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -174,6 +174,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } } + // The flags of which terrain patches to send for each of the ScenePresence's private Dictionary m_perClientPatchUpdates = new Dictionary(); @@ -1030,8 +1031,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain List toSend = GetModifiedPatchesInViewDistance(pups); if (toSend.Count > 0) { - m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2}", - LogHeader, toSend.Count, pups.Presence.Name); + // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}", + // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName); // Sort the patches to send by the distance from the presence toSend.Sort(); foreach (PatchesToSend pts in toSend) @@ -1067,8 +1068,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; endY = Math.Max(endY, 0); endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); - m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. ddist={1}, start=<{2},{3}>, end=<{4},{5}>", - LogHeader, presence.DrawDistance, startX, startY, endX, endY); + // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. rName={1}, ddist={2}, apos={3}, start=<{4},{5}>, end=<{6},{7}>", + // LogHeader, m_scene.RegionInfo.RegionName, + // presence.DrawDistance, presence.AbsolutePosition, + // startX, startY, endX, endY); for (int x = startX; x < endX; x++) { for (int y = startY; y < endY; y++) @@ -1079,7 +1082,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (pups.GetByPatch(x, y)) { //Check which has less distance, camera or avatar position, both have to be done. - //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off + //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off if (Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50) || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50)) { -- cgit v1.1