From 0aa0dad47868d6f64fa19f81b0f5daf35196fc3b Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sun, 1 Jun 2014 19:22:26 -0700 Subject: [PATCH] Send multiple terrain patches per terrain update packet if terrain draw distance optimization is enabled. Makes terrain editting a lot snappier. --- .../ClientStack/Linden/UDP/LLClientView.cs | 124 ++++++++++++++---- .../World/Terrain/TerrainModule.cs | 14 +- .../Framework/Scenes/TerrainCompressor.cs | 7 +- 3 files changed, 118 insertions(+), 27 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index c28e58d6a9..44386c9ea9 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -1154,6 +1154,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// Send the region heightmap to the client + /// This method is only called when not doing intellegent terrain patch sending and + /// is only called when the scene presence is initially created and sends all of the + /// region's patches to the client. /// /// heightmap public virtual void SendLayerData(float[] map) @@ -1237,9 +1240,49 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Legacy form of invocation that passes around a bare data array. // Just ignore what was passed and use the real terrain info that is part of the scene. + // As a HORRIBLE kludge in an attempt to not change the definition of IClientAPI, + // there is a special form for specifying multiple terrain patches to send. + // The form is to pass 'px' as negative the number of patches to send and to + // pass the float array as pairs of patch X and Y coordinates. So, passing 'px' + // as -2 and map= [3, 5, 8, 4] would mean to send two terrain heightmap patches + // and the patches to send are <3,5> and <8,4>. public void SendLayerData(int px, int py, float[] map) { - SendLayerData(px, py, m_scene.Heightmap.GetTerrainData()); + if (px >= 0) + { + SendLayerData(px, py, m_scene.Heightmap.GetTerrainData()); + } + else + { + int numPatches = -px; + int[] xPatches = new int[numPatches]; + int[] yPatches = new int[numPatches]; + for (int pp = 0; pp < numPatches; pp++) + { + xPatches[pp] = (int)map[pp * 2]; + yPatches[pp] = (int)map[pp * 2 + 1]; + } + + // DebugSendingPatches("SendLayerData", xPatches, yPatches); + + SendLayerData(xPatches, yPatches, m_scene.Heightmap.GetTerrainData()); + } + } + + private void DebugSendingPatches(string pWho, int[] pX, int[] pY) + { + if (m_log.IsDebugEnabled) + { + int numPatches = pX.Length; + string Xs = ""; + string Ys = ""; + for (int pp = 0; pp < numPatches; pp++) + { + Xs += String.Format("{0}", (int)pX[pp]) + ","; + Ys += String.Format("{0}", (int)pY[pp]) + ","; + } + m_log.DebugFormat("{0} {1}: numPatches={2}, X={3}, Y={4}", LogHeader, pWho, numPatches, Xs, Ys); + } } /// @@ -1251,6 +1294,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Patch coordinate (y) 0..15 /// heightmap public void SendLayerData(int px, int py, TerrainData terrData) + { + int[] xPatches = new[] { px }; + int[] yPatches = new[] { py }; + SendLayerData(xPatches, yPatches, terrData); + } + + private void SendLayerData(int[] px, int[] py, TerrainData terrData) { try { @@ -1259,31 +1309,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP patchInd[0] = px + (py * Constants.TerrainPatchSize); LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(terrData.GetFloatsSerialized(), patchInd); */ - LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, px, py); - - // When a user edits the terrain, so much data is sent, the data queues up fast and presents a sub optimal editing experience. - // To alleviate this issue, when the user edits the terrain, we start skipping the queues until they're done editing the terrain. - // We also make them unreliable because it's extremely likely that multiple packets will be sent for a terrain patch area - // invalidating previous packets for that area. - - // It's possible for an editing user to flood themselves with edited packets but the majority of use cases are such that only a - // tiny percentage of users will be editing the terrain. Other, non-editing users will see the edits much slower. - - // One last note on this topic, by the time users are going to be editing the terrain, it's extremely likely that the sim will - // have rezzed already and therefore this is not likely going to cause any additional issues with lost packets, objects or terrain - // patches. - - // m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we only have one cache miss. - if (m_justEditedTerrain) + // Many, many patches could have been passed to us. Since the patches will be compressed + // into variable sized blocks, we cannot pre-compute how many will fit into one + // packet. While some fancy packing algorithm is possible, 4 seems to always fit. + int PatchesAssumedToFit = 4; + for (int pcnt = 0; pcnt < px.Length; pcnt += PatchesAssumedToFit) { - layerpack.Header.Reliable = false; - OutPacket(layerpack, ThrottleOutPacketType.Unknown ); - } - else - { - layerpack.Header.Reliable = true; - OutPacket(layerpack, ThrottleOutPacketType.Land); + int remaining = Math.Min(px.Length - pcnt, PatchesAssumedToFit); + int[] xPatches = new int[remaining]; + int[] yPatches = new int[remaining]; + for (int ii = 0; ii < remaining; ii++) + { + xPatches[ii] = px[pcnt + ii]; + yPatches[ii] = py[pcnt + ii]; + } + LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, xPatches, yPatches); + // DebugSendingPatches("SendLayerDataInternal", xPatches, yPatches); + + SendTheLayerPacket(layerpack); } + // LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, px, py); + } catch (Exception e) { @@ -1291,6 +1337,36 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } + // When a user edits the terrain, so much data is sent, the data queues up fast and presents a + // sub optimal editing experience. To alleviate this issue, when the user edits the terrain, we + // start skipping the queues until they're done editing the terrain. We also make them + // unreliable because it's extremely likely that multiple packets will be sent for a terrain patch + // area invalidating previous packets for that area. + + // It's possible for an editing user to flood themselves with edited packets but the majority + // of use cases are such that only a tiny percentage of users will be editing the terrain. + // Other, non-editing users will see the edits much slower. + + // One last note on this topic, by the time users are going to be editing the terrain, it's + // extremely likely that the sim will have rezzed already and therefore this is not likely going + // to cause any additional issues with lost packets, objects or terrain patches. + + // m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we + // only have one cache miss. + private void SendTheLayerPacket(LayerDataPacket layerpack) + { + if (m_justEditedTerrain) + { + layerpack.Header.Reliable = false; + OutPacket(layerpack, ThrottleOutPacketType.Unknown ); + } + else + { + layerpack.Header.Reliable = true; + OutPacket(layerpack, ThrottleOutPacketType.Land); + } + } + /// /// Send the wind matrix to the client /// diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index c22c5b6d7b..51016c2117 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -1035,12 +1035,24 @@ namespace OpenSim.Region.CoreModules.World.Terrain // 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) { - // 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); } + */ + + int[] xPieces = new int[toSend.Count]; + int[] yPieces = new int[toSend.Count]; + float[] patchPieces = new float[toSend.Count * 2]; + int pieceIndex = 0; + foreach (PatchesToSend pts in toSend) + { + patchPieces[pieceIndex++] = pts.PatchX; + patchPieces[pieceIndex++] = pts.PatchY; + } + pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces); } } } diff --git a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs index 396f1e8f03..fc8f8cd2c5 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs @@ -119,6 +119,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP xPieces[0] = patchX; // patch X dimension yPieces[0] = patchY; + return CreateLandPacket(terrData, xPieces, yPieces); + } + + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] xPieces, int[] yPieces) + { byte landPacketType = (byte)TerrainPatch.LayerType.Land; if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize) { @@ -148,8 +153,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Array of indexes in the grid of patches. /// /// - /// - /// /// public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] x, int[] y, byte type) {