From 8e01f75784bd7b719a6957de40daf949eb01fcdf Mon Sep 17 00:00:00 2001 From: Teravus Ovares Date: Mon, 19 Jan 2009 17:11:57 +0000 Subject: * Progressive texture patch + PriorityQueue put into the LLClient namespace. * Updates LibOMV to r2362 --- .../Region/ClientStack/LindenUDP/LLClientView.cs | 26 +- .../Region/ClientStack/LindenUDP/LLImageManager.cs | 664 +++++++++++++++++++++ 2 files changed, 684 insertions(+), 6 deletions(-) create mode 100644 OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs (limited to 'OpenSim/Region/ClientStack/LindenUDP') diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 146bc63..5f2fbac 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -99,6 +99,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP protected LLPacketServer m_networkServer; + protected LLImageManager m_imageManager; + /* public variables */ protected string m_firstName; protected string m_lastName; @@ -471,6 +473,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_PacketHandler.OnPacketStats += PopulateStats; RegisterLocalPacketHandlers(); + m_imageManager = new LLImageManager(this, m_assetCache,Scene.RequestModuleInterface()); } public void SetDebugPacketLevel(int newDebugPacketLevel) @@ -496,6 +499,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Shut down timers m_clientPingTimer.Stop(); + // This is just to give the client a reasonable chance of // flushing out all it's packets. There should probably // be a better mechanism here @@ -510,7 +514,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (!(shutdownCircuit)) { GC.Collect(); - + m_imageManager = null; // Sends a KillPacket object, with which, the // blockingqueue dequeues and sees it's a killpacket // and terminates within the context of the client thread. @@ -532,6 +536,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP m_log.DebugFormat( "[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}", shutdownCircuit, Name, m_scene.RegionInfo.RegionName); + + m_imageManager.Close(); m_PacketHandler.Flush(); @@ -2759,7 +2765,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP ushort numParts, UUID ImageUUID, uint ImageSize, byte[] ImageData, byte imageCodec) { ImageDataPacket im = new ImageDataPacket(); - im.Header.Reliable = false; + im.Header.Reliable = true; im.ImageID.Packets = numParts; im.ImageID.ID = ImageUUID; @@ -2775,7 +2781,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendImageNextPart(ushort partNumber, UUID imageUuid, byte[] imageData) { ImagePacketPacket im = new ImagePacketPacket(); - im.Header.Reliable = false; + im.Header.Reliable = true; im.ImageID.Packet = partNumber; im.ImageID.ID = imageUuid; im.ImageData.Data = imageData; @@ -4192,6 +4198,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (ProcessPacketMethod(Pack)) { //there is a handler registered that handled this packet type + + // in the end, we dereference this, so we have to check if it's null + if (m_imageManager != null) + m_imageManager.ProcessImageQueue(3); return; } @@ -5232,10 +5242,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP args.PacketNumber = imageRequest.RequestImage[i].Packet; args.Priority = imageRequest.RequestImage[i].DownloadPriority; - handlerTextureRequest = OnRequestTexture; + //handlerTextureRequest = OnRequestTexture; - if (handlerTextureRequest != null) - OnRequestTexture(this, args); + //if (handlerTextureRequest != null) + //OnRequestTexture(this, args); + m_imageManager.EnqueueReq(args); } } break; @@ -7374,6 +7385,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP #endregion } + // in the end, we dereference this, so we have to check if it's null + if (m_imageManager != null ) + m_imageManager.ProcessImageQueue(3); PacketPool.Instance.ReturnPacket(Pack); } diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs new file mode 100644 index 0000000..ac6a1fa --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs @@ -0,0 +1,664 @@ +/* + * 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 OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using C5; +using OpenSim.Framework.Communications.Cache; +using OpenMetaverse.Imaging; + + +namespace OpenSim.Region.ClientStack.LindenUDP +{ + + /// + /// Client image priority + discardlevel sender/manager + /// + public class LLImageManager + { + /// + /// Priority Queue for images. Contains lots of data + /// + private readonly IPriorityQueue> pq = new IntervalHeap>(); + + /// + /// Dictionary of PriorityQueue handles by AssetId + /// + private readonly Dictionary>> PQHandles = + new Dictionary>>(); + + private LLClientView m_client; + private readonly AssetCache m_assetCache; + private bool m_shuttingdown = false; + private readonly IJ2KDecoder m_j2kDecodeModule; + + private readonly AssetBase MissingSubstitute; + + /// + /// Client image priority + discardlevel sender/manager + /// + /// LLClientView of client + /// The Asset retrieval system + /// The Jpeg2000 Decoder + public LLImageManager(LLClientView client, AssetCache pAssetCache, IJ2KDecoder pJ2kDecodeModule) + { + m_client = client; + m_assetCache = pAssetCache; + if (pAssetCache != null) + MissingSubstitute = pAssetCache.GetAsset(UUID.Parse("5748decc-f629-461c-9a36-a35a221fe21f"), true); + m_j2kDecodeModule = pJ2kDecodeModule; + } + + /// + /// Enqueues a texture request + /// + /// Request from the client to get a texture + public void EnqueueReq(TextureRequestArgs req) + { + if (m_shuttingdown) + return; + + //if (req.RequestType == 1) // avatar body texture! + // return; + + AddQueueItem(req.RequestedAssetID, (int)req.Priority + 100000); + //if (pq[PQHandles[req.RequestedAssetID]].data.Missing) + //{ + // pq[PQHandles[req.RequestedAssetID]] -= 900000; + //} + // + //if (pq[PQHandles[req.RequestedAssetID]].data.HasData && pq[PQHandles[req.RequestedAssetID]].data.Layers.Length > 0) + //{ + + //} + + pq[PQHandles[req.RequestedAssetID]].data.requestedUUID = req.RequestedAssetID; + pq[PQHandles[req.RequestedAssetID]].data.Priority = (int)req.Priority; + + lock (pq[PQHandles[req.RequestedAssetID]].data) + pq[PQHandles[req.RequestedAssetID]].data.Update(req.DiscardLevel, (int)req.PacketNumber); + } + + + /// + /// Callback for the asset system + /// + /// UUID of the asset that we have received + /// AssetBase of the asset that we've received + public void AssetDataCallback(UUID assetID, AssetBase asset) + { + if (m_shuttingdown) + return; + + //Console.WriteLine("AssetCallback for assetId" + assetID); + + if (asset == null || asset.Data == null) + { + lock (pq) + { + //pq[PQHandles[assetID]].data.Missing = true; + pq[PQHandles[assetID]].data.asset = MissingSubstitute; + pq[PQHandles[assetID]].data.Missing = false; + } + } + //else + + + pq[PQHandles[assetID]].data.asset = asset; + + lock (pq[PQHandles[assetID]].data) + pq[PQHandles[assetID]].data.Update((int)pq[PQHandles[assetID]].data.Priority, (int)pq[PQHandles[assetID]].data.CurrentPacket); + + + + } + + /// + /// Processes the image queue. Pops count elements off and processes them + /// + /// number of images to peek off the queue + public void ProcessImageQueue(int count) + { + if (m_shuttingdown) + return; + + + IPriorityQueueHandle> h = null; + for (int j = 0; j < count; j++) + { + + lock (pq) + { + if (!pq.IsEmpty) + { + //peek off the top + Prio process = pq.FindMax(out h); + + // Do we have the Asset Data? + if (!process.data.HasData) + { + // Did we request the asset data? + if (!process.data.dataRequested) + { + m_assetCache.GetAsset(process.data.requestedUUID, AssetDataCallback, true); + pq[h].data.dataRequested = true; + } + + // Is the asset missing? + if (process.data.Missing) + { + + //m_client.sendtextur + pq[h] -= 90000; + /* + { + OpenMetaverse.Packets.ImageNotInDatabasePacket imdback = + new OpenMetaverse.Packets.ImageNotInDatabasePacket(); + imdback.ImageID = + new OpenMetaverse.Packets.ImageNotInDatabasePacket.ImageIDBlock(); + imdback.ImageID.ID = process.data.requestedUUID; + m_client.OutPacket(imdback, ThrottleOutPacketType.Texture); + } + */ + + // Substitute a blank image + process.data.asset = MissingSubstitute; + process.data.Missing = false; + + // If the priority is less then -4billion, the client has forgotten about it. + if (pq[h] < -400000000) + { + RemoveItemFromQueue(pq[h].data.requestedUUID); + continue; + } + } + // Lower the priority to give the next image a chance + pq[h] -= 100000; + } + else if (process.data.HasData) + { + // okay, we've got the data + lock (process.data) + { + if (!process.data.J2KDecode && !process.data.J2KDecodeWaiting) + { + process.data.J2KDecodeWaiting = true; + + // Do we have a jpeg decoder? + if (m_j2kDecodeModule != null) + { + // Send it off to the jpeg decoder + m_j2kDecodeModule.decode(process.data.requestedUUID, process.data.Data, + j2kDecodedCallback); + } + else + { + // no module, no layers, full resolution only + j2kDecodedCallback(process.data.AssetId, new OpenJPEG.J2KLayerInfo[0]); + } + + + + } // Are we waiting? + else if (!process.data.J2KDecodeWaiting) + { + // Send more data at a time for higher discard levels + for (int i = 0; i < (2*(5 - process.data.DiscardLevel) + 1)*2; i++) + if (!process.data.SendPacket(m_client)) + { + pq[h] -= (500000*i); + break; + } + } + // If the priority is less then -4 billion, the client has forgotten about it, pop it off + if (pq[h] < -400000000) + { + RemoveItemFromQueue(pq[h].data.requestedUUID); + continue; + } + } + + //pq[h] = process; + } + + // uncomment the following line to see the upper most asset and the priority + //Console.WriteLine(process.ToString()); + + // Lower priority to give the next image a chance to bubble up + pq[h] -= 50000; + } + } + } + + } + + + /// + /// Callback for when the image has been decoded + /// + /// The UUID of the Asset + /// The Jpeg2000 discard level Layer start and end byte offsets Array. 0 elements for failed or no decoder + public void j2kDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) + { + // are we shutting down? if so, end. + if (m_shuttingdown) + return; + + + lock (PQHandles) + { + // Update our asset data + if (PQHandles.ContainsKey(AssetId)) + { + pq[PQHandles[AssetId]].data.Layers = layers; + pq[PQHandles[AssetId]].data.J2KDecode = true; + pq[PQHandles[AssetId]].data.J2KDecodeWaiting = false; + lock (pq[PQHandles[AssetId]].data) + pq[PQHandles[AssetId]].data.Update((int)pq[PQHandles[AssetId]].data.Priority, (int)pq[PQHandles[AssetId]].data.CurrentPacket); + + // Send the first packet + pq[PQHandles[AssetId]].data.SendPacket(m_client); + } + } + } + + + /// + /// This image has had a good life. It's now expired. Remove it off the queue + /// + /// UUID of asset to remove off the queue + private void RemoveItemFromQueue(UUID AssetId) + { + lock (PQHandles) + { + if (PQHandles.ContainsKey(AssetId)) + { + IPriorityQueueHandle> h = PQHandles[AssetId]; + PQHandles.Remove(AssetId); + pq.Delete(h); + } + } + } + + + /// + /// Adds an image to the queue and update priority + /// if the item is already in the queue, just update the priority + /// + /// UUID of the asset + /// Priority to set + private void AddQueueItem(UUID AssetId, int priority) + { + IPriorityQueueHandle> h = null; + + lock (PQHandles) + { + if (PQHandles.ContainsKey(AssetId)) + { + h = PQHandles[AssetId]; + pq[h] = pq[h].SetPriority(priority); + + } + else + { + J2KImage newreq = new J2KImage(); + newreq.requestedUUID = AssetId; + pq.Add(ref h, new Prio(newreq, priority)); + PQHandles.Add(AssetId, h); + } + } + } + + /// + /// Okay, we're ending. Clean up on isle 9 + /// + public void Close() + { + m_shuttingdown = true; + + lock (pq) + { + while (!pq.IsEmpty) + { + pq.DeleteMin(); + } + } + + + lock (PQHandles) + PQHandles.Clear(); + m_client = null; + } + + } + + /// + /// Image Data for this send + /// Encapsulates the image sending data and method + /// + public class J2KImage + { + private AssetBase m_asset_ref = null; + public volatile int LastPacketNum = 0; + public volatile int DiscardLimit = 0; + public volatile bool dataRequested = false; + public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; + + public const int FIRST_IMAGE_PACKET_SIZE = 600; + public const int IMAGE_PACKET_SIZE = 1000; + + public volatile int DiscardLevel; + public float Priority; + public volatile int CurrentPacket = 1; + public volatile int StopPacket; + public bool Missing = false; + public bool J2KDecode = false; + public bool J2KDecodeWaiting = false; + + private volatile bool sendFirstPacket = true; + + // Having this *AND* the AssetId allows us to remap asset data to AssetIds as necessary. + public UUID requestedUUID = UUID.Zero; + + public J2KImage(AssetBase asset) + { + m_asset_ref = asset; + } + + public J2KImage() + { + + } + + public AssetBase asset + { + set { m_asset_ref = value; } + } + + // We make the asset a reference so that we don't duplicate the byte[] + // it's read only anyway, so no worries here + // we want to avoid duplicating the byte[] for the images at all costs to avoid memory bloat! :) + + /// + /// ID of the AssetBase + /// + public UUID AssetId + { + get { return m_asset_ref.FullID; } + } + + /// + /// Asset Data + /// + public byte[] Data + { + get { return m_asset_ref.Data; } + } + + /// + /// Returns true if we have the asset + /// + public bool HasData + { + get { return !(m_asset_ref == null); } + } + + /// + /// Called from the PriorityQueue handle .ToString(). Prints data on this asset + /// + /// + public override string ToString() + { + return string.Format("ID:{0}, RD:{1}, CP:{2}", requestedUUID, HasData, CurrentPacket); + } + + /// + /// 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() + { + if (!HasData) + return 0; + return ((m_asset_ref.Data.Length - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; + } + + /// + /// Returns the current byte offset for this transfer, calculated from + /// the CurrentPacket + /// + /// Current byte offset for this transfer + public int CurrentBytePosition() + { + if (CurrentPacket == 0) + return 0; + if (CurrentPacket == 1) + return FIRST_IMAGE_PACKET_SIZE; + + int result = FIRST_IMAGE_PACKET_SIZE + (CurrentPacket - 2) * IMAGE_PACKET_SIZE; + if (result < 0) + { + result = FIRST_IMAGE_PACKET_SIZE; + } + return result; + } + + /// + /// 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() + { + if (CurrentPacket == 1) + return m_asset_ref.Data.Length; + return (m_asset_ref.Data.Length - FIRST_IMAGE_PACKET_SIZE) % IMAGE_PACKET_SIZE; // m_asset_ref.Data.Length - (FIRST_IMAGE_PACKET_SIZE + ((TexturePacketCount() - 1) * IMAGE_PACKET_SIZE)); + } + + /// + /// 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) + 1; + } + + /// + /// Updates the Image sending limits based on the discard + /// If we don't have any Layers, Send the full texture + /// + /// jpeg2000 discard level. 5-0 + /// Which packet to start from + public void Update(int discardLevel, int packet) + { + //Requests for 0 means that the client wants us to resend the whole image + //Requests for -1 mean 'update priority but don't change discard level' + + if (packet == 0 || packet == -1) + return; + + // Check if we've got layers + if (Layers.Length > 0) + { + DiscardLevel = Util.Clamp(discardLevel, 0, Layers.Length - 1); + StopPacket = GetPacketForBytePosition(Layers[(Layers.Length - 1) - DiscardLevel].End); + CurrentPacket = Util.Clamp(packet, 1, TexturePacketCount() - 1); + // sendFirstPacket = true; + } + else + { + // No layers, send full image + DiscardLevel = 0; + StopPacket = TexturePacketCount() - 1; + CurrentPacket = Util.Clamp(packet, 1, TexturePacketCount() - 1); + + } + } + + /// + /// Sends a texture packet to the client. + /// + /// Client to send texture to + /// true if a packet was sent, false if not + public bool SendPacket(LLClientView client) + { + // If we've hit the end of the send or if the client set -1, return false. + if (CurrentPacket > StopPacket || StopPacket == -1) + return false; + + // The first packet contains up to 600 bytes and the details of the image. Number of packets, image size in bytes, etc. + // This packet only gets sent once unless we're restarting the transfer from 0! + if (sendFirstPacket) + { + sendFirstPacket = false; + + // Do we have less then 1 packet's worth of data? + if (m_asset_ref.Data.Length <= FIRST_IMAGE_PACKET_SIZE) + { + // Send only 1 packet + client.SendImageFirstPart(1, requestedUUID , (uint)m_asset_ref.Data.Length, m_asset_ref.Data, 2); + CurrentPacket = 2; // Makes it so we don't come back to SendPacket and error trying to send a second packet + return true; + } + else + { + + // Send first packet + byte[] firstImageData = new byte[FIRST_IMAGE_PACKET_SIZE]; + try { Buffer.BlockCopy(m_asset_ref.Data, 0, firstImageData, 0, FIRST_IMAGE_PACKET_SIZE); } + catch (Exception) + { + Console.WriteLine(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(), firstImageData.Length, FIRST_IMAGE_PACKET_SIZE)); + + //m_log.Error("Texture data copy failed on first packet for " + m_asset_ref.FullID.ToString()); + //m_cancel = true; + //m_sending = false; + return false; + } + client.SendImageFirstPart((ushort)TexturePacketCount(), requestedUUID, (uint)m_asset_ref.Data.Length, firstImageData, 2); + ++CurrentPacket; // sets CurrentPacket to 1 + } + } + + // figure out if we're on the last packet, if so, use the last packet size. If not, use 1000. + // we know that the total image size is greater then 1000 if we're here + int imagePacketSize = (CurrentPacket == (TexturePacketCount() ) ) ? LastPacketSize() : IMAGE_PACKET_SIZE; + + //if (imagePacketSize > 0) + // imagePacketSize = IMAGE_PACKET_SIZE; + //if (imagePacketSize != 1000) + // Console.WriteLine("ENdPacket"); + //Console.WriteLine(String.Format("srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(),0, imagePacketSize)); + + + byte[] imageData = new byte[imagePacketSize]; + try { Buffer.BlockCopy(m_asset_ref.Data, CurrentBytePosition(), imageData, 0, imagePacketSize); } + catch (Exception e) + { + Console.WriteLine(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize:{3}, currpak:{4}, stoppak:{5}, totalpak:{6}", m_asset_ref.Data.Length, CurrentBytePosition(), + imageData.Length, imagePacketSize, CurrentPacket,StopPacket,TexturePacketCount())); + System.Console.WriteLine(e.ToString()); + //m_log.Error("Texture data copy failed for " + m_asset_ref.FullID.ToString()); + //m_cancel = true; + //m_sending = false; + return false; + } + + // Send next packet to the client + client.SendImageNextPart((ushort)(CurrentPacket - 1), requestedUUID, imageData); + ++CurrentPacket; + return true; + } + + } + + /// + /// Generic Priority Queue element + /// Contains a Priority and a Reference type Data Element + /// + /// Reference type data element + struct Prio : IComparable> where D : class + { + public D data; + private int priority; + + public Prio(D data, int priority) + { + this.data = data; + this.priority = priority; + } + + public int CompareTo(Prio that) + { + return this.priority.CompareTo(that.priority); + } + + public bool Equals(Prio that) + { + return this.priority == that.priority; + } + + public static Prio operator +(Prio tp, int delta) + { + return new Prio(tp.data, tp.priority + delta); + } + + public static bool operator <(Prio tp, int check) + { + return (tp.priority < check); + } + + public static bool operator >(Prio tp, int check) + { + return (tp.priority > check); + } + + public static Prio operator -(Prio tp, int delta) + { + if (tp.priority - delta < 0) + return new Prio(tp.data, tp.priority - delta); + else + return new Prio(tp.data, 0); + } + + public override String ToString() + { + return String.Format("{0}[{1}]", data, priority); + } + + internal Prio SetPriority(int pPriority) + { + return new Prio(this.data, pPriority); + } + } +} -- cgit v1.1