From 3bdd4db3fd7eddb9e4f017cc45aa3eb2b3921111 Mon Sep 17 00:00:00 2001 From: Dahlia Trimble Date: Sat, 10 Jan 2009 01:46:47 +0000 Subject: Thanks jhurliman for a patch that implements progressive texture downloading - Mantis #2655 --- .../Agent/TextureDownload/TextureDownloadModule.cs | 58 ++-- .../Agent/TextureDownload/TextureNotFoundSender.cs | 1 - .../TextureDownload/UserTextureDownloadService.cs | 9 +- .../TextureSender/Tests/TextureSenderTests.cs | 2 +- .../Modules/Agent/TextureSender/TextureSender.cs | 307 ++++++++++++++------- 5 files changed, 245 insertions(+), 132 deletions(-) (limited to 'OpenSim/Region/Environment/Modules/Agent') diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureDownloadModule.cs b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureDownloadModule.cs index 35fa5ed..2f5ea23 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureDownloadModule.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureDownloadModule.cs @@ -241,44 +241,52 @@ namespace OpenSim.Region.Environment.Modules.Agent.TextureDownload { ITextureSender sender = null; - try - { +// try +// { while (true) { - sender = m_queueSenders.Dequeue(); - - if (sender.Cancel) + try { - TextureSent(sender); + sender = m_queueSenders.Dequeue(); - sender.Cancel = false; - } - else - { - bool finished = sender.SendTexturePacket(); - if (finished) + if (sender.Cancel) { TextureSent(sender); + + sender.Cancel = false; } else { - m_queueSenders.Enqueue(sender); + bool finished = sender.SendTexturePacket(); + if (finished) + { + TextureSent(sender); + } + else + { + m_queueSenders.Enqueue(sender); + } } - } - // Make sure that any sender we currently have can get garbage collected - sender = null; + // Make sure that any sender we currently have can get garbage collected + sender = null; - //m_log.InfoFormat("[TEXTURE] Texture sender queue size: {0}", m_queueSenders.Count()); + //m_log.InfoFormat("[TEXTURE] Texture sender queue size: {0}", m_queueSenders.Count()); + } + catch(Exception e) + { + m_log.ErrorFormat( + "[TEXTURE]: Texture send thread caught exception. The texture send was aborted. Exception is {0}", e); + } } - } - catch (Exception e) - { - // TODO: Let users in the sim and those entering it and possibly an external watchdog know what has happened - m_log.ErrorFormat( - "[TEXTURE]: Texture send thread terminating with exception. PLEASE REBOOT YOUR SIM - TEXTURES WILL NOT BE AVAILABLE UNTIL YOU DO. Exception is {0}", - e); - } +// } +// catch (Exception e) +// { +// // TODO: Let users in the sim and those entering it and possibly an external watchdog know what has happened +// m_log.ErrorFormat( +// "[TEXTURE]: Texture send thread terminating with exception. PLEASE REBOOT YOUR SIM - TEXTURES WILL NOT BE AVAILABLE UNTIL YOU DO. Exception is {0}", +// e); +// } } /// diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureNotFoundSender.cs b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureNotFoundSender.cs index c064064..044ee76 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureNotFoundSender.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/TextureNotFoundSender.cs @@ -28,7 +28,6 @@ using System.Reflection; using log4net; using OpenMetaverse; -using OpenMetaverse.Packets; using OpenSim.Framework; using OpenSim.Region.Environment.Interfaces; diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/UserTextureDownloadService.cs b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/UserTextureDownloadService.cs index d6c9877..aab4ad0 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureDownload/UserTextureDownloadService.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureDownload/UserTextureDownloadService.cs @@ -56,11 +56,10 @@ namespace OpenSim.Region.Environment.Modules.Agent.TextureDownload /// /// We will allow the client to request the same texture n times before dropping further requests /// - /// This number includes repeated requests for the same texture at different resolutions (which we don't - /// currently handle properly as far as I know). However, this situation should be handled in a more - /// sophisticated way. + /// This number contains repeated requests for the same texture at different resolutions (which + /// are handled since r7368). However, this situation should be handled in a more sophisticated way. /// - private static readonly int MAX_ALLOWED_TEXTURE_REQUESTS = 5; + private static readonly int MAX_ALLOWED_TEXTURE_REQUESTS = 15; /// /// XXX Also going to limit requests for found textures. @@ -149,7 +148,7 @@ namespace OpenSim.Region.Environment.Modules.Agent.TextureDownload m_scene.AddPendingDownloads(1); - TextureSender.TextureSender requestHandler = new TextureSender.TextureSender(m_client, e.DiscardLevel, e.PacketNumber); + TextureSender.TextureSender requestHandler = new TextureSender.TextureSender(m_client, e.DiscardLevel, e.PacketNumber, e.Priority); m_textureSenders.Add(e.RequestedAssetID, requestHandler); m_scene.AssetCache.GetAsset(e.RequestedAssetID, TextureCallback, true); diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureSender/Tests/TextureSenderTests.cs b/OpenSim/Region/Environment/Modules/Agent/TextureSender/Tests/TextureSenderTests.cs index 4049dfc..cff215b 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureSender/Tests/TextureSenderTests.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureSender/Tests/TextureSenderTests.cs @@ -53,7 +53,7 @@ namespace OpenSim.Region.Environment.Modules.Agent.TextureSender agent.startpos = Vector3.Zero; agent.CapsPath = "http://wibble.com"; - new TextureSender(new TestClient(agent), 0, 0); + new TextureSender(new TestClient(agent), 0, 0, 1.0f); } } } \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Agent/TextureSender/TextureSender.cs b/OpenSim/Region/Environment/Modules/Agent/TextureSender/TextureSender.cs index 57129dd..0e34271 100644 --- a/OpenSim/Region/Environment/Modules/Agent/TextureSender/TextureSender.cs +++ b/OpenSim/Region/Environment/Modules/Agent/TextureSender/TextureSender.cs @@ -27,106 +27,188 @@ using System; using System.Reflection; -using OpenMetaverse.Packets; using log4net; using OpenSim.Framework; using OpenSim.Region.Environment.Interfaces; namespace OpenSim.Region.Environment.Modules.Agent.TextureSender { - /// - /// A TextureSender handles the process of receiving a texture requested by the client from the - /// AssetCache, and then sending that texture back to the client. - /// - public class TextureSender : ITextureSender + public class ImageDownload { - private static readonly ILog m_log - = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public const int FIRST_IMAGE_PACKET_SIZE = 600; + public const int IMAGE_PACKET_SIZE = 1000; - /// - /// Records the number of times texture send has been called. - /// - public int counter = 0; + public OpenMetaverse.AssetTexture Texture; + public int DiscardLevel; + public float Priority; + public int CurrentPacket; + public int StopPacket; - public bool ImageLoaded = false; + public ImageDownload(OpenMetaverse.AssetTexture texture, int discardLevel, float priority, int packet) + { + Texture = texture; + Update(discardLevel, priority, packet); + } /// - /// Holds the texture asset to send. + /// Updates an image transfer with new information and recalculates + /// offsets /// - private AssetBase m_asset; + /// New requested discard level + /// New requested priority + /// New requested packet offset + public void Update(int discardLevel, float priority, int packet) + { + Priority = priority; + DiscardLevel = Clamp(discardLevel, 0, Texture.LayerInfo.Length - 1); + StopPacket = GetPacketForBytePosition(Texture.LayerInfo[(Texture.LayerInfo.Length - 1) - DiscardLevel].End); + CurrentPacket = Clamp(packet, 1, TexturePacketCount()); + } - //public UUID assetID { get { return m_asset.FullID; } } + /// + /// Returns the total number of packets needed to transfer this texture, + /// including the first packet of size FIRST_IMAGE_PACKET_SIZE + /// + /// Total number of packets needed to transfer this texture + public int TexturePacketCount() + { + return ((Texture.AssetData.Length - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; + } - // private bool m_cancel = false; + /// + /// Returns the current byte offset for this transfer, calculated from + /// the CurrentPacket + /// + /// Current byte offset for this transfer + public int CurrentBytePosition() + { + return FIRST_IMAGE_PACKET_SIZE + (CurrentPacket - 1) * IMAGE_PACKET_SIZE; + } - // See ITextureSender + /// + /// Returns the size, in bytes, of the last packet. This will be somewhere + /// between 1 and IMAGE_PACKET_SIZE bytes + /// + /// Size of the last packet in the transfer + public int LastPacketSize() + { + return Texture.AssetData.Length - (FIRST_IMAGE_PACKET_SIZE + ((TexturePacketCount() - 2) * IMAGE_PACKET_SIZE)); + } - // private bool m_sending = false; + /// + /// Find the packet number that contains a given byte position + /// + /// Byte position + /// Packet number that contains the given byte position + int GetPacketForBytePosition(int bytePosition) + { + return ((bytePosition - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE); + } /// - /// This is actually the number of extra packets required to send the texture data! We always assume - /// at least one is required. + /// Clamp a given value between a range /// - private int NumPackets = 0; + /// Value to clamp + /// Minimum allowable value + /// Maximum allowable value + /// A value inclusively between lower and upper + static int Clamp(int value, int min, int max) + { + // First we check to see if we're greater than the max + value = (value > max) ? max : value; + + // Then we check to see if we're less than the min. + value = (value < min) ? min : value; + + // There's no check to see if min > max. + return value; + } + } + + /// + /// A TextureSender handles the process of receiving a texture requested by the client from the + /// AssetCache, and then sending that texture back to the client. + /// + public class TextureSender : ITextureSender + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public bool ImageLoaded = false; /// - /// Holds the packet number to send next. In this case, each packet is 1000 bytes long and starts - /// at the 600th byte (0th indexed). + /// Holds the texture asset to send. /// - private int PacketCounter = 0; + private AssetBase m_asset; + private bool m_cancel = false; + private bool m_sending = false; + private bool sendFirstPacket = false; + private int initialDiscardLevel = 0; + private int initialPacketNum = 0; + private float initialPriority = 0.0f; - private int RequestedDiscardLevel = -1; + private ImageDownload download; private IClientAPI RequestUser; - private uint StartPacketNumber = 0; - public TextureSender(IClientAPI client, int discardLevel, uint packetNumber) + public TextureSender(IClientAPI client, int discardLevel, uint packetNumber, float priority) { RequestUser = client; - RequestedDiscardLevel = discardLevel; - StartPacketNumber = packetNumber; + initialDiscardLevel = discardLevel; + initialPacketNum = (int)packetNumber; + initialPriority = priority; } #region ITextureSender Members public bool Cancel { - get { return false; } - set - { - // m_cancel = value; - } + get { return m_cancel; } + set { m_cancel = value; } } public bool Sending { - get { return false; } - set - { - // m_sending = value; - } + get { return m_sending; } + set { m_sending = value; } } // See ITextureSender public void UpdateRequest(int discardLevel, uint packetNumber) { - RequestedDiscardLevel = discardLevel; - StartPacketNumber = packetNumber; - PacketCounter = (int) StartPacketNumber; + if (download == null) + return; + + lock (download) + { + if (discardLevel < download.DiscardLevel) + m_log.DebugFormat("Image download {0} is changing from DiscardLevel {1} to {2}", + m_asset.FullID, download.DiscardLevel, discardLevel); + + if (packetNumber != download.CurrentPacket) + m_log.DebugFormat("Image download {0} is changing from Packet {1} to {2}", + m_asset.FullID, download.CurrentPacket, packetNumber); + + download.Update(discardLevel, download.Priority, (int)packetNumber); + + sendFirstPacket = true; + } } // See ITextureSender public bool SendTexturePacket() { - //m_log.DebugFormat("[TEXTURE SENDER]: Sending packet for {0}", m_asset.FullID); - - SendPacket(); - counter++; - if ((NumPackets == 0) || (RequestedDiscardLevel == -1) || (PacketCounter > NumPackets) || - ((RequestedDiscardLevel > 0) && (counter > 50 + (NumPackets / (RequestedDiscardLevel + 1))))) + if (download != null && !m_cancel && (sendFirstPacket || download.CurrentPacket <= download.StopPacket)) + { + SendPacket(); + return false; + } + else { + m_sending = false; + m_cancel = true; + sendFirstPacket = false; return true; } - return false; } #endregion @@ -138,76 +220,101 @@ namespace OpenSim.Region.Environment.Modules.Agent.TextureSender public void TextureReceived(AssetBase asset) { m_asset = asset; - NumPackets = CalculateNumPackets(asset.Data.Length); - PacketCounter = (int) StartPacketNumber; - ImageLoaded = true; - } - /// - /// Sends a texture packet to the client. - /// - private void SendPacket() - { - if (PacketCounter <= NumPackets) + try { - if (PacketCounter == 0) + OpenMetaverse.AssetTexture texture = new OpenMetaverse.AssetTexture(m_asset.FullID, m_asset.Data); + if (texture.DecodeLayerBoundaries()) { - if (NumPackets == 0) + bool sane = true; + + // Sanity check all of the layers + for (int i = 0; i < texture.LayerInfo.Length; i++) { - RequestUser.SendImageFirstPart(1, m_asset.FullID, (uint)m_asset.Data.Length, m_asset.Data, 2); - PacketCounter++; + if (texture.LayerInfo[i].End > texture.AssetData.Length) + { + sane = false; + break; + } + } + + if (sane) + { + download = new ImageDownload(texture, initialDiscardLevel, initialPriority, initialPacketNum); + ImageLoaded = true; + m_sending = true; + m_cancel = false; + sendFirstPacket = true; + return; } else { - byte[] ImageData1 = new byte[600]; - Array.Copy(m_asset.Data, 0, ImageData1, 0, 600); - - RequestUser.SendImageFirstPart( - (ushort)(NumPackets), m_asset.FullID, (uint)m_asset.Data.Length, ImageData1, 2); - PacketCounter++; + m_log.Error("JPEG2000 texture decoding succeeded, but sanity check failed for " + + m_asset.FullID.ToString()); } } else { - int size = m_asset.Data.Length - 600 - (1000 * (PacketCounter - 1)); - if (size > 1000) size = 1000; - byte[] imageData = new byte[size]; - try - { - Array.Copy(m_asset.Data, 600 + (1000 * (PacketCounter - 1)), imageData, 0, size); - } - catch (ArgumentOutOfRangeException) - { - m_log.Error("[TEXTURE SENDER]: Unable to separate texture into multiple packets: Array bounds failure on asset:" + - m_asset.FullID.ToString()); - return; - } - - RequestUser.SendImageNextPart((ushort)PacketCounter, m_asset.FullID, imageData); - PacketCounter++; + m_log.Error("JPEG2000 texture decoding failed for " + m_asset.FullID.ToString()); } } + catch (Exception ex) + { + m_log.Error("JPEG2000 texture decoding threw an exception for " + m_asset.FullID.ToString(), ex); + } + + ImageLoaded = false; + m_sending = false; + m_cancel = true; } /// - /// Calculate the number of packets that will be required to send the texture loaded into this sender - /// This is actually the number of 1000 byte packets not including an initial 600 byte packet... + /// Sends a texture packet to the client. /// - /// - /// - private int CalculateNumPackets(int length) + private void SendPacket() { - int numPackets = 0; - - if (length > 600) + lock (download) { - //over 600 bytes so split up file - int restData = (length - 600); - int restPackets = ((restData + 999) / 1000); - numPackets = restPackets; - } + if (sendFirstPacket) + { + sendFirstPacket = false; - return numPackets; + if (m_asset.Data.Length <= ImageDownload.FIRST_IMAGE_PACKET_SIZE) + { + RequestUser.SendImageFirstPart(1, m_asset.FullID, (uint)m_asset.Data.Length, m_asset.Data, 2); + return; + } + else + { + byte[] firstImageData = new byte[ImageDownload.FIRST_IMAGE_PACKET_SIZE]; + try { Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, ImageDownload.FIRST_IMAGE_PACKET_SIZE); } + catch (Exception) + { + m_log.Error("Texture data copy failed on first packet for " + m_asset.FullID.ToString()); + m_cancel = true; + m_sending = false; + return; + } + RequestUser.SendImageFirstPart((ushort)download.TexturePacketCount(), m_asset.FullID, (uint)m_asset.Data.Length, firstImageData, 2); + } + } + + int imagePacketSize = (download.CurrentPacket == download.TexturePacketCount() - 1) ? + download.LastPacketSize() : ImageDownload.IMAGE_PACKET_SIZE; + + byte[] imageData = new byte[imagePacketSize]; + try { Buffer.BlockCopy(m_asset.Data, download.CurrentBytePosition(), imageData, 0, imagePacketSize); } + catch (Exception) + { + m_log.Error("Texture data copy failed for " + m_asset.FullID.ToString()); + m_cancel = true; + m_sending = false; + return; + } + + RequestUser.SendImageNextPart((ushort)download.CurrentPacket, m_asset.FullID, imageData); + ++download.CurrentPacket; + } } } } -- cgit v1.1