From 14869cab4814a104a834a36781cd31a7868669c5 Mon Sep 17 00:00:00 2001 From: Jim Radford Date: Thu, 7 May 2009 16:10:52 +0000 Subject: [PATCH] LIBOMV-505 Merge in jradford-texturepipeline branch This changes the way texture requests are handled, the AssetManager RequestImage method signatures have been changed and are now expecting the callback with the request. Progressive requests for textures are supported for rendering viewers Tuning parameters have been moved to Settings git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@2699 52acb1d6-8a22-11de-b505-999d5b087335 --- OpenMetaverse.GUI/MiniMap.cs | 19 +- OpenMetaverse/AppearanceManager.cs | 24 +- OpenMetaverse/AssetManager.cs | 570 +++--------- OpenMetaverse/Settings.cs | 19 + OpenMetaverse/TexturePipeline.cs | 855 +++++++++++++----- Programs/PrimWorkshop/frmBrowser.cs | 26 +- .../Commands/Inventory/DumpOutfitCommand.cs | 33 +- .../Commands/Prims/DownloadTextureCommand.cs | 27 +- .../Commands/Prims/ExportCommand.cs | 25 +- .../Commands/Prims/TexturesCommand.cs | 15 +- .../examples/groupmanager/frmGroupInfo.cs | 29 +- 11 files changed, 883 insertions(+), 759 deletions(-) diff --git a/OpenMetaverse.GUI/MiniMap.cs b/OpenMetaverse.GUI/MiniMap.cs index e7216e5d..003a86f4 100644 --- a/OpenMetaverse.GUI/MiniMap.cs +++ b/OpenMetaverse.GUI/MiniMap.cs @@ -102,7 +102,6 @@ namespace OpenMetaverse.GUI private void InitializeClient(GridClient client) { _Client = client; - _Client.Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); _Client.Grid.OnCoarseLocationUpdate += new GridManager.CoarseLocationUpdateCallback(Grid_OnCoarseLocationUpdate); _Client.Network.OnCurrentSimChanged += new NetworkManager.CurrentSimChangedCallback(Network_OnCurrentSimChanged); } @@ -194,15 +193,6 @@ namespace OpenMetaverse.GUI _MousePosition = e.Location; } - void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) - { - if (asset.AssetID == _MapImageID) - { - ManagedImage nullImage; - OpenJPEG.DecodeToImage(asset.AssetData, out nullImage, out _MapLayer); - } - } - void Grid_OnCoarseLocationUpdate(Simulator sim, List newEntries, List removedEntries) { UpdateMiniMap(sim); @@ -216,7 +206,14 @@ namespace OpenMetaverse.GUI SetMapLayer(null); _MapImageID = region.MapImageID; - Client.Assets.RequestImage(_MapImageID, ImageType.Baked); + ManagedImage nullImage; + + Client.Assets.RequestImage(_MapImageID, ImageType.Baked, + delegate(TextureRequestState state, AssetTexture asset) + { + if(state == TextureRequestState.Finished) + OpenJPEG.DecodeToImage(asset.AssetData, out nullImage, out _MapLayer); + }); } } diff --git a/OpenMetaverse/AppearanceManager.cs b/OpenMetaverse/AppearanceManager.cs index 8fde1155..14a47774 100644 --- a/OpenMetaverse/AppearanceManager.cs +++ b/OpenMetaverse/AppearanceManager.cs @@ -644,10 +644,10 @@ namespace OpenMetaverse // Register an asset download callback to get wearable data AssetManager.AssetReceivedCallback assetCallback = new AssetManager.AssetReceivedCallback(Assets_OnAssetReceived); - AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); + //AssetManager.ImageReceivedCallback imageCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); AssetManager.AssetUploadedCallback uploadCallback = new AssetManager.AssetUploadedCallback(Assets_OnAssetUploaded); Assets.OnAssetReceived += assetCallback; - Assets.OnImageReceived += imageCallback; + //Assets.OnImageReceived += imageCallback; Assets.OnAssetUploaded += uploadCallback; // Download assets for what we are wearing and fill in AgentTextures @@ -667,7 +667,7 @@ namespace OpenMetaverse if (bake) CachedResponseEvent.WaitOne(); // Unregister the image download and asset upload callbacks - Assets.OnImageReceived -= imageCallback; + //Assets.OnImageReceived -= imageCallback; Assets.OnAssetUploaded -= uploadCallback; Logger.DebugLog("CachedResponseEvent completed", Client); @@ -1342,7 +1342,7 @@ namespace OpenMetaverse foreach (UUID image in imgKeys) { // Download all the images we need for baking - Assets.RequestImage(image, ImageType.Normal, 1013000.0f, 0, 0); + Assets.RequestImage(image, ImageType.Normal, Assets_OnImageReceived); } } } @@ -1412,18 +1412,18 @@ namespace OpenMetaverse } } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture assetTexture) + private void Assets_OnImageReceived(TextureRequestState state, AssetTexture assetTexture) { lock (ImageDownloads) { - if (ImageDownloads.ContainsKey(image.ID)) + if (ImageDownloads.ContainsKey(assetTexture.AssetID)) { - ImageDownloads.Remove(image.ID); + ImageDownloads.Remove(assetTexture.AssetID); // NOTE: This image may occupy more than one TextureIndex! We must finish this loop for (int at = 0; at < AgentTextures.Length; at++) { - if (AgentTextures[at] == image.ID) + if (AgentTextures[at] == assetTexture.AssetID) { TextureIndex index = (TextureIndex)at; BakeType type = Baker.BakeTypeFor(index); @@ -1437,15 +1437,15 @@ namespace OpenMetaverse if (PendingBakes.ContainsKey(type)) { - if (image.Success) + if (state == TextureRequestState.Finished) { Logger.DebugLog("Finished downloading texture for " + index.ToString(), Client); - OpenJPEG.DecodeToImage(image.AssetData, out assetTexture.Image); + OpenJPEG.DecodeToImage(assetTexture.AssetData, out assetTexture.Image); baked = PendingBakes[type].AddTexture(index, assetTexture, false); } else { - Logger.Log("Texture for " + index.ToString() + " failed to download, " + + Logger.Log("Texture for " + index + " failed to download, " + "bake will be incomplete", Helpers.LogLevel.Warning, Client); baked = PendingBakes[type].MissingTexture(index); } @@ -1475,7 +1475,7 @@ namespace OpenMetaverse } else { - Logger.Log("Received an image download callback for an image we did not request " + image.ID.ToString(), + Logger.Log("Received an image download callback for an image we did not request " + assetTexture.AssetID, Helpers.LogLevel.Warning, Client); } } diff --git a/OpenMetaverse/AssetManager.cs b/OpenMetaverse/AssetManager.cs index 7c4627b7..74c7c394 100644 --- a/OpenMetaverse/AssetManager.cs +++ b/OpenMetaverse/AssetManager.cs @@ -224,13 +224,11 @@ namespace OpenMetaverse { public ushort PacketCount; public ImageCodec Codec; - public bool NotFound; public Simulator Simulator; public SortedList PacketsSeen; public ImageType ImageType; public int DiscardLevel; public float Priority; - internal int InitialDataSize; internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false); @@ -256,8 +254,23 @@ namespace OpenMetaverse } } + /// + /// + /// public class ImageRequest { + public UUID ImageID; + public ImageType Type; + public float Priority; + public int DiscardLevel; + + /// + /// + /// + /// + /// + /// + /// public ImageRequest(UUID imageid, ImageType type, float priority, int discardLevel) { ImageID = imageid; @@ -265,10 +278,7 @@ namespace OpenMetaverse Priority = priority; DiscardLevel = discardLevel; } - public UUID ImageID; - public ImageType Type; - public float Priority; - public int DiscardLevel; + } #endregion Transfer Classes @@ -325,10 +335,6 @@ namespace OpenMetaverse /// public event XferReceivedCallback OnXferReceived; /// - public event ImageReceivedCallback OnImageReceived; - /// - public event ImageReceiveProgressCallback OnImageReceiveProgress; - /// public event AssetUploadedCallback OnAssetUploaded; /// public event UploadProgressCallback OnUploadProgress; @@ -339,12 +345,15 @@ namespace OpenMetaverse /// Texture download cache public TextureCache Cache; + private TexturePipeline Texture; + private GridClient Client; + private Dictionary Transfers = new Dictionary(); + private AssetUpload PendingUpload; private object PendingUploadLock = new object(); private volatile bool WaitingForUploadConfirm = false; - private System.Timers.Timer RefreshDownloadsTimer = new System.Timers.Timer(500.0); /// /// Default constructor @@ -354,16 +363,12 @@ namespace OpenMetaverse { Client = client; Cache = new TextureCache(client); + Texture = new TexturePipeline(client); // Transfer packets for downloading large assets Client.Network.RegisterCallback(PacketType.TransferInfo, new NetworkManager.PacketCallback(TransferInfoHandler)); Client.Network.RegisterCallback(PacketType.TransferPacket, new NetworkManager.PacketCallback(TransferPacketHandler)); - // Image downloading packets - Client.Network.RegisterCallback(PacketType.ImageData, new NetworkManager.PacketCallback(ImageDataHandler)); - Client.Network.RegisterCallback(PacketType.ImagePacket, new NetworkManager.PacketCallback(ImagePacketHandler)); - Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, new NetworkManager.PacketCallback(ImageNotInDatabaseHandler)); - // Xfer packets for uploading large assets Client.Network.RegisterCallback(PacketType.RequestXfer, new NetworkManager.PacketCallback(RequestXferHandler)); Client.Network.RegisterCallback(PacketType.ConfirmXferPacket, new NetworkManager.PacketCallback(ConfirmXferPacketHandler)); @@ -375,66 +380,6 @@ namespace OpenMetaverse // Simulator is responding to a request to download a file Client.Network.RegisterCallback(PacketType.InitiateDownload, new NetworkManager.PacketCallback(InitiateDownloadPacketHandler)); - // HACK: Re-request stale pending image downloads - RefreshDownloadsTimer.Elapsed += new System.Timers.ElapsedEventHandler(RefreshDownloadsTimer_Elapsed); - RefreshDownloadsTimer.Start(); - } - - private void RefreshDownloadsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - lock (Transfers) - { - foreach (Transfer transfer in Transfers.Values) - { - if (transfer is ImageDownload) - { - ImageDownload download = (ImageDownload)transfer; - - uint packet = 0; - - if (download.PacketsSeen != null && download.PacketsSeen.Count > 0) - { - lock (download.PacketsSeen) - { - bool first = true; - foreach (KeyValuePair packetSeen in download.PacketsSeen) - { - if (first) - { - // Initially set this to the earliest packet received in the transfer - packet = packetSeen.Value; - first = false; - } - else - { - ++packet; - - // If there is a missing packet in the list, break and request the download - // resume here - if (packetSeen.Value != packet) - { - --packet; - break; - } - } - } - - ++packet; - } - } - - if (download.TimeSinceLastPacket > 5000) - { - if (download.DiscardLevel > 0) - { - --download.DiscardLevel; - } - download.TimeSinceLastPacket = 0; - RequestImage(download.ID, download.ImageType, download.Priority, download.DiscardLevel, packet); - } - } - } - } } /// @@ -574,183 +519,6 @@ namespace OpenMetaverse throw new Exception("This function is not implemented yet!"); } - /// - /// Initiate an image download. This is an asynchronous function - /// - /// The image to download - /// Type of the image to download, either a baked - /// avatar texture or a normal texture - public void RequestImage(UUID imageID, ImageType type) - { - RequestImage(imageID, type, 1013000.0f, 0, 0); - } - - /// - /// Initiate an image download. This is an asynchronous function - /// - /// The image to download - /// Type of the image to download, either a baked - /// avatar texture or a normal texture - /// Priority level of the download. Default is - /// 1,013,000.0f - /// Number of quality layers to discard. - /// This controls the end marker of the data sent - /// Packet number to start the download at. - /// This controls the start marker of the data sent - /// Sending a priority of 0 and a discardlevel of -1 aborts - /// download - public void RequestImage(UUID imageID, ImageType type, float priority, int discardLevel, uint packetNum) - { - if (Cache.HasImage(imageID)) - { - ImageDownload transfer = Cache.GetCachedImage(imageID); - transfer.ImageType = type; - - if (null != transfer) - { - if (null != OnImageReceived) - { - AssetTexture asset = new AssetTexture(transfer.ID, transfer.AssetData); - - try { OnImageReceived(transfer, asset); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - return; - } - } - - // Priority == 0 && DiscardLevel == -1 means cancel the transfer - if (priority.Equals(0) && discardLevel.Equals(-1)) - { - if (Transfers.ContainsKey(imageID)) - Transfers.Remove(imageID); - - RequestImagePacket cancel = new RequestImagePacket(); - cancel.AgentData.AgentID = Client.Self.AgentID; - cancel.AgentData.SessionID = Client.Self.SessionID; - cancel.RequestImage = new RequestImagePacket.RequestImageBlock[1]; - cancel.RequestImage[0] = new RequestImagePacket.RequestImageBlock(); - cancel.RequestImage[0].DiscardLevel = -1; - cancel.RequestImage[0].DownloadPriority = 0; - cancel.RequestImage[0].Packet = 0; - cancel.RequestImage[0].Image = imageID; - cancel.RequestImage[0].Type = 0; - } - else - { - Simulator currentSim = Client.Network.CurrentSim; - - if (!Transfers.ContainsKey(imageID)) - { - // New download - ImageDownload transfer = new ImageDownload(); - transfer.ID = imageID; - transfer.Simulator = currentSim; - transfer.ImageType = type; - transfer.DiscardLevel = discardLevel; - transfer.Priority = priority; - - // Add this transfer to the dictionary - lock (Transfers) Transfers[transfer.ID] = transfer; - - Logger.DebugLog("Adding image " + imageID.ToString() + " to the download queue"); - } - else - { - // Already downloading, just updating the priority - Transfer transfer = Transfers[imageID]; - float percentComplete = ((float)transfer.Transferred / (float)transfer.Size) * 100f; - if (Single.IsNaN(percentComplete)) - percentComplete = 0f; - - Logger.DebugLog(String.Format("Updating priority on image transfer {0}, {1}% complete", - imageID, Math.Round(percentComplete, 2))); - } - - // Build and send the request packet - RequestImagePacket request = new RequestImagePacket(); - request.AgentData.AgentID = Client.Self.AgentID; - request.AgentData.SessionID = Client.Self.SessionID; - request.RequestImage = new RequestImagePacket.RequestImageBlock[1]; - request.RequestImage[0] = new RequestImagePacket.RequestImageBlock(); - request.RequestImage[0].DiscardLevel = (sbyte)discardLevel; - request.RequestImage[0].DownloadPriority = priority; - request.RequestImage[0].Packet = packetNum; - request.RequestImage[0].Image = imageID; - request.RequestImage[0].Type = (byte)type; - - Client.Network.SendPacket(request, currentSim); - } - } - - /// - /// Requests multiple Images - /// - /// List of requested images - public void RequestImages(List Images) - { - for (int iri = 0; iri < Images.Count; iri++) - { - if (Transfers.ContainsKey(Images[iri].ImageID)) - { - Images.RemoveAt(iri); - } - - if (Cache.HasImage(Images[iri].ImageID)) - { - ImageDownload transfer = Cache.GetCachedImage(Images[iri].ImageID); - if (null != transfer) - { - if (null != OnImageReceived) - { - AssetTexture asset = new AssetTexture(transfer.ID, transfer.AssetData); - - try { OnImageReceived(transfer, asset); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - - Images.RemoveAt(iri); - } - } - } - - if (Images.Count > 0) - { - // Build and send the request packet - RequestImagePacket request = new RequestImagePacket(); - request.AgentData.AgentID = Client.Self.AgentID; - request.AgentData.SessionID = Client.Self.SessionID; - request.RequestImage = new RequestImagePacket.RequestImageBlock[Images.Count]; - - for (int iru = 0; iru < Images.Count; ++iru) - { - ImageDownload transfer = new ImageDownload(); - //transfer.AssetType = AssetType.Texture // Handled in ImageDataHandler. - transfer.ID = Images[iru].ImageID; - transfer.Simulator = Client.Network.CurrentSim; - transfer.ImageType = Images[iru].Type; - transfer.DiscardLevel = Images[iru].DiscardLevel; - transfer.Priority = Images[iru].Priority; - - // Add this transfer to the dictionary - lock (Transfers) Transfers[transfer.ID] = transfer; - request.RequestImage[iru] = new RequestImagePacket.RequestImageBlock(); - request.RequestImage[iru].DiscardLevel = (sbyte)Images[iru].DiscardLevel; - request.RequestImage[iru].DownloadPriority = Images[iru].Priority; - request.RequestImage[iru].Packet = 0; - request.RequestImage[iru].Image = Images[iru].ImageID; - request.RequestImage[iru].Type = (byte)Images[iru].Type; - } - - Client.Network.SendPacket(request, Client.Network.CurrentSim); - } - else - { - Logger.Log("RequestImages() called for an image(s) we are already downloading or an empty list, ignoring", - Helpers.LogLevel.Info, Client); - } - } - /// /// Used to force asset data into the PendingUpload property, ie: for raw terrain uploads /// @@ -888,6 +656,131 @@ namespace OpenMetaverse } } + /// + /// Request a texture asset from the simulator using the system to + /// manage the requests and re-assemble the image from the packets received from the simulator + /// + /// The of the texture asset to download + /// The of the texture asset. + /// Use for most textures, or for baked layer texture assets + /// A float indicating the requested priority for the transfer. Higher priority values tell the simulator + /// to prioritize the request before lower valued requests. An image already being transferred using the can have + /// its priority changed by resending the request with the new priority value + /// Number of quality layers to discard. + /// This controls the end marker of the data sent + /// The packet number to begin the request at. A value of 0 begins the request + /// from the start of the asset texture + /// The callback to fire when the image is retrieved. The callback + /// will contain the result of the request and the texture asset data + /// If true, the callback will be fired for each chunk of the downloaded image. + /// The callback asset parameter will contain all previously received chunks of the texture asset starting + /// from the beginning of the request + /// + /// Request an image and fire a callback when the request is complete + /// + /// Client.Assets.RequestImage(UUID.Parse("c307629f-e3a1-4487-5e88-0d96ac9d4965"), ImageType.Normal, TextureDownloader_OnDownloadFinished); + /// + /// private void TextureDownloader_OnDownloadFinished(TextureRequestState state, AssetTexture asset) + /// { + /// if(state == TextureRequestState.Finished) + /// { + /// Console.WriteLine("Texture {0} ({1} bytes) has been successfully downloaded", + /// asset.AssetID, + /// asset.AssetData.Length); + /// } + /// } + /// + /// Request an image and use an inline anonymous method to handle the downloaded texture data + /// + /// Client.Assets.RequestImage(UUID.Parse("c307629f-e3a1-4487-5e88-0d96ac9d4965"), ImageType.Normal, delegate(TextureRequestState state, AssetTexture asset) + /// { + /// if(state == TextureRequestState.Finished) + /// { + /// Console.WriteLine("Texture {0} ({1} bytes) has been successfully downloaded", + /// asset.AssetID, + /// asset.AssetData.Length); + /// } + /// } + /// ); + /// + /// Request a texture, decode the texture to a bitmap image and apply it to a imagebox + /// + /// Client.Assets.RequestImage(UUID.Parse("c307629f-e3a1-4487-5e88-0d96ac9d4965"), ImageType.Normal, TextureDownloader_OnDownloadFinished); + /// + /// private void TextureDownloader_OnDownloadFinished(TextureRequestState state, AssetTexture asset) + /// { + /// if(state == TextureRequestState.Finished) + /// { + /// ManagedImage imgData; + /// Image bitmap; + /// + /// if (state == TextureRequestState.Finished) + /// { + /// OpenJPEG.DecodeToImage(assetTexture.AssetData, out imgData, out bitmap); + /// picInsignia.Image = bitmap; + /// } + /// } + /// } + /// + /// + public void RequestImage(UUID textureID, ImageType imageType, float priority, int discardLevel, + uint packetStart, TextureDownloadCallback callback, bool progress) + { + Texture.RequestTexture(textureID, imageType, priority, discardLevel, packetStart, callback, progress); + } + + /// + /// Overload: Request a texture asset from the simulator using the system to + /// manage the requests and re-assemble the image from the packets received from the simulator + /// + /// The of the texture asset to download + /// The callback to fire when the image is retrieved. The callback + /// will contain the result of the request and the texture asset data + public void RequestImage(UUID textureID, TextureDownloadCallback callback) + { + RequestImage(textureID, ImageType.Normal, 101300.0f, 0, 0, callback, false); + } + + /// + /// Overload: Request a texture asset from the simulator using the system to + /// manage the requests and re-assemble the image from the packets received from the simulator + /// + /// The of the texture asset to download + /// The of the texture asset. + /// Use for most textures, or for baked layer texture assets + /// The callback to fire when the image is retrieved. The callback + /// will contain the result of the request and the texture asset data + public void RequestImage(UUID textureID, ImageType imageType, TextureDownloadCallback callback) + { + RequestImage(textureID, imageType, 101300.0f, 0, 0, callback, false); + } + + /// + /// Overload: Request a texture asset from the simulator using the system to + /// manage the requests and re-assemble the image from the packets received from the simulator + /// + /// The of the texture asset to download + /// The of the texture asset. + /// Use for most textures, or for baked layer texture assets + /// The callback to fire when the image is retrieved. The callback + /// will contain the result of the request and the texture asset data + /// If true, the callback will be fired for each chunk of the downloaded image. + /// The callback asset parameter will contain all previously received chunks of the texture asset starting + /// from the beginning of the request + public void RequestImage(UUID textureID, ImageType imageType, TextureDownloadCallback callback, bool progress) + { + RequestImage(textureID, imageType, 101300.0f, 0, 0, callback, progress); + } + + /// + /// Cancel a texture request + /// + /// The texture assets + public void RequestImageCancel(UUID textureID) + { + Texture.AbortTextureRequest(textureID); + } + #region Helpers private Asset CreateAssetWrapper(AssetType type) @@ -1366,176 +1259,5 @@ namespace OpenMetaverse #endregion Xfer Callbacks - #region Image Callbacks - - /// - /// Handles the Image Data packet which includes the ID and Size of the image, - /// along with the first block of data for the image. If the image is small enough - /// there will be no additional packets - /// - private void ImageDataHandler(Packet packet, Simulator simulator) - { - ImageDataPacket data = (ImageDataPacket)packet; - ImageDownload transfer = null; - - Logger.DebugLog(String.Format("ImageData: Size={0}, Packets={1}", data.ImageID.Size, data.ImageID.Packets)); - - lock (Transfers) - { - if (Transfers.ContainsKey(data.ImageID.ID)) - { - transfer = (ImageDownload)Transfers[data.ImageID.ID]; - - // Don't set header information if we have already - // received it (due to re-request) - if (transfer.Size == 0) - { - //Client.DebugLog("Received first " + data.ImageData.Data.Length + " bytes for image " + - // data.ImageID.ID.ToString()); - - if (OnImageReceiveProgress != null) - { - try { OnImageReceiveProgress(data.ImageID.ID, 0, data.ImageData.Data.Length, transfer.Size); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - - transfer.Codec = (ImageCodec)data.ImageID.Codec; - transfer.PacketCount = data.ImageID.Packets; - transfer.Size = (int)data.ImageID.Size; - transfer.AssetData = new byte[transfer.Size]; - transfer.AssetType = AssetType.Texture; - transfer.PacketsSeen = new SortedList(); - Buffer.BlockCopy(data.ImageData.Data, 0, transfer.AssetData, 0, data.ImageData.Data.Length); - transfer.InitialDataSize = data.ImageData.Data.Length; - transfer.Transferred += data.ImageData.Data.Length; - - // Check if we downloaded the full image - if (transfer.Transferred >= transfer.Size) - { - Transfers.Remove(transfer.ID); - transfer.Success = true; - Cache.SaveImageToCache(transfer.ID, transfer.AssetData); - } - } - } - } - - if (transfer != null) - { - transfer.HeaderReceivedEvent.Set(); - - if (OnImageReceived != null && transfer.Transferred >= transfer.Size) - { - AssetTexture asset = new AssetTexture(transfer.ID, transfer.AssetData); - - try { OnImageReceived(transfer, asset); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - } - } - - /// - /// Handles the remaining Image data that did not fit in the initial ImageData packet - /// - private void ImagePacketHandler(Packet packet, Simulator simulator) - { - ImagePacketPacket image = (ImagePacketPacket)packet; - ImageDownload transfer = null; - - lock (Transfers) - { - if (Transfers.ContainsKey(image.ImageID.ID)) - { - transfer = (ImageDownload)Transfers[image.ImageID.ID]; - - if (transfer.Size == 0) - { - // We haven't received the header yet, block until it's received or times out - transfer.HeaderReceivedEvent.WaitOne(1000 * 5, false); - - if (transfer.Size == 0) - { - Logger.Log("Timed out while waiting for the image header to download for " + - transfer.ID.ToString(), Helpers.LogLevel.Warning, Client); - - transfer.Success = false; - Transfers.Remove(transfer.ID); - goto Callback; - } - } - - // The header is downloaded, we can insert this data in to the proper position - // Only insert if we haven't seen this packet before - lock (transfer.PacketsSeen) - { - if (!transfer.PacketsSeen.ContainsKey(image.ImageID.Packet)) - { - transfer.PacketsSeen[image.ImageID.Packet] = image.ImageID.Packet; - Buffer.BlockCopy(image.ImageData.Data, 0, transfer.AssetData, - transfer.InitialDataSize + (1000 * (image.ImageID.Packet - 1)), - image.ImageData.Data.Length); - transfer.Transferred += image.ImageData.Data.Length; - } - } - - //Client.DebugLog("Received " + image.ImageData.Data.Length + "/" + transfer.Transferred + - // "/" + transfer.Size + " bytes for image " + image.ImageID.ID.ToString()); - - transfer.TimeSinceLastPacket = 0; - - if (OnImageReceiveProgress != null) - { - try { OnImageReceiveProgress(image.ImageID.ID, image.ImageID.Packet, transfer.Transferred, transfer.Size); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - - // Check if we downloaded the full image - if (transfer.Transferred >= transfer.Size) - { - Cache.SaveImageToCache(transfer.ID, transfer.AssetData); - transfer.Success = true; - Transfers.Remove(transfer.ID); - } - } - } - - Callback: - - if (transfer != null && OnImageReceived != null && (transfer.Transferred >= transfer.Size || transfer.Size == 0)) - { - AssetTexture asset = new AssetTexture(transfer.ID, transfer.AssetData); - - try { OnImageReceived(transfer, asset); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - } - - /// - /// The requested image does not exist on the asset server - /// - private void ImageNotInDatabaseHandler(Packet packet, Simulator simulator) - { - ImageNotInDatabasePacket notin = (ImageNotInDatabasePacket)packet; - ImageDownload transfer = null; - - lock (Transfers) - { - if (Transfers.ContainsKey(notin.ImageID.ID)) - { - transfer = (ImageDownload)Transfers[notin.ImageID.ID]; - transfer.NotFound = true; - Transfers.Remove(transfer.ID); - } - } - - // Fire the event with our transfer that contains Success = false; - if (transfer != null && OnImageReceived != null) - { - try { OnImageReceived(transfer, null); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - } - - #endregion Image Callbacks } } diff --git a/OpenMetaverse/Settings.cs b/OpenMetaverse/Settings.cs index ebb64b60..0ee3f7fa 100644 --- a/OpenMetaverse/Settings.cs +++ b/OpenMetaverse/Settings.cs @@ -287,6 +287,25 @@ namespace OpenMetaverse public bool THROTTLE_OUTGOING_PACKETS = true; #endregion + #region Texture Pipeline + + /// The maximum number of concurrent texture downloads allowed + /// Increasing this number will not necessarily increase texture retrieval times due to + /// simulator throttles + public int MAX_CONCURRENT_TEXTURE_DOWNLOADS = 4; + + /// + /// The Refresh timer inteval is used to set the delay between checks for stalled texture downloads + /// + /// This is a static variable which applies to all instances + public static float PIPELINE_REFRESH_INTERVAL = 500.0f; + + /// + /// Textures taking longer than this value will be flagged as timed out and removed from the pipeline + /// + public int PIPELINE_REQUEST_TIMEOUT = 45*1000; + #endregion + #region Logging Configuration /// diff --git a/OpenMetaverse/TexturePipeline.cs b/OpenMetaverse/TexturePipeline.cs index d7c5ebb7..46c917b8 100644 --- a/OpenMetaverse/TexturePipeline.cs +++ b/OpenMetaverse/TexturePipeline.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, openmetaverse.org + * Copyright (c) 2009, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -24,198 +24,436 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#define DEBUG_TIMING + using System; using System.Collections.Generic; using System.Threading; +using OpenMetaverse.Packets; namespace OpenMetaverse { /// - /// Texture request download handler, allows a configurable number of download slots + /// The current status of a texture request as it moves through the pipeline or final result of a texture request. /// + public enum TextureRequestState + { + /// The initial state given to a request. Requests in this state + /// are waiting for an available slot in the pipeline + Pending, + /// A request that has been added to the pipeline and the request packet + /// has been sent to the simulator + Started, + /// A request that has received one or more packets back from the simulator + Progress, + /// A request that has received all packets back from the simulator + Finished, + /// A request that has taken longer than + /// to download OR the initial packet containing the packet information was never received + Timeout, + /// The texture request was aborted by request of the agent + Aborted, + /// The simulator replied to the request that it was not able to find the requested texture + NotFound + } + /// + /// A callback fired to indicate the status or final state of the requested texture. For progressive + /// downloads this will fire each time new asset data is returned from the simulator. + /// + /// The indicating either Progress for textures not fully downloaded, + /// or the final result of the request after it has been processed through the TexturePipeline + /// The object containing the Assets ID, raw data + /// and other information. For progressive rendering the will contain + /// the data from the beginning of the file. For failed, aborted and timed out requests it will contain + /// an empty byte array. + public delegate void TextureDownloadCallback(TextureRequestState state, AssetTexture assetTexture); + + /// + /// Texture request download handler, allows a configurable number of download slots which manage multiple + /// concurrent texture downloads from the + /// + /// This class makes full use of the internal + /// system for full texture downloads. public class TexturePipeline { - class TaskInfo +#if DEBUG_TIMING // Timing globals + /// The combined time it has taken for all textures requested sofar. This includes the amount of time the + /// texture spent waiting for a download slot, and the time spent retrieving the actual texture from the Grid + public static TimeSpan TotalTime; + /// The amount of time the request spent in the state + public static TimeSpan NetworkTime; + /// The total number of bytes transferred since the TexturePipeline was started + public static int TotalBytes; +#endif + /// + /// A request task containing information and status of a request as it is processed through the + /// + private class TaskInfo { + /// The current which identifies the current status of the request + public TextureRequestState State; + /// The Unique Request ID, This is also the Asset ID of the texture being requested public UUID RequestID; - public int RequestNbr; + /// The slot this request is occupying in the threadpoolSlots array + public int RequestSlot; + /// The ImageType of the request. public ImageType Type; - - public TaskInfo(UUID reqID, int reqNbr, ImageType type) - { - RequestID = reqID; - RequestNbr = reqNbr; - Type = type; - } + /// The callback to fire when the request is complete, will include + /// the and the + /// object containing the result data + public TextureDownloadCallback Callback; + /// If true, indicates the callback will be fired whenever new data is returned from the simulator. + /// This is used to progressively render textures as portions of the texture are received. + public bool ReportProgress; +#if DEBUG_TIMING + /// The time the request was added to the the PipeLine + public DateTime StartTime; + /// The time the request was sent to the simulator + public DateTime NetworkTime; +#endif + /// An object that maintains the data of an request thats in-process. + public ImageDownload Transfer; } - public delegate void DownloadFinishedCallback(UUID id, bool success); - public delegate void DownloadProgressCallback(UUID image, int recieved, int total); - - /// Fired when a texture download completes - public event DownloadFinishedCallback OnDownloadFinished; - /// Fired when some texture data is received - public event DownloadProgressCallback OnDownloadProgress; - - public int CurrentCount { get { return currentRequests.Count; } } - public int QueuedCount { get { return requestQueue.Count; } } - - GridClient client; - /// Maximum concurrent texture requests - int maxTextureRequests; - /// Queue for image requests that have not been sent out yet - List requestQueue; - /// Current texture downloads - Dictionary currentRequests; - /// Storage for completed texture downloads - Dictionary completedDownloads; - AutoResetEvent[] resetEvents; - int[] threadpoolSlots; - Thread downloadMaster; - bool running; - object syncObject = new object(); + /// A dictionary containing all pending and in-process transfer requests where the Key is both the RequestID + /// and also the Asset Texture ID, and the value is an object containing the current state of the request and also + /// the asset data as it is being re-assembled + private readonly Dictionary _Transfers; + /// Holds the reference to the client object + private readonly GridClient _Client; + /// Maximum concurrent texture requests allowed at a time + private readonly int maxTextureRequests; + /// An array of objects used to manage worker request threads + private readonly AutoResetEvent[] resetEvents; + /// An array of worker slots which shows the availablity status of the slot + private readonly int[] threadpoolSlots; + /// The primary thread which manages the requests. + private readonly Thread downloadMaster; + /// true if the TexturePipeline is currently running + bool _Running; + /// A synchronization object used by the primary thread + private static object lockerObject = new object(); + /// A refresh timer used to increase the priority of stalled requests + private readonly System.Timers.Timer RefreshDownloadsTimer = + new System.Timers.Timer(Settings.PIPELINE_REFRESH_INTERVAL); /// - /// Default constructor + /// Default constructor, Instantiates a new copy of the TexturePipeline class /// - /// Reference to SecondLife client - /// Maximum number of concurrent texture requests - public TexturePipeline(GridClient client, int maxRequests) + /// Reference to the instantiated object + public TexturePipeline(GridClient client) { - running = true; - this.client = client; - maxTextureRequests = maxRequests; + _Client = client; + maxTextureRequests = client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS; - requestQueue = new List(); - currentRequests = new Dictionary(maxTextureRequests); - completedDownloads = new Dictionary(); resetEvents = new AutoResetEvent[maxTextureRequests]; threadpoolSlots = new int[maxTextureRequests]; - // Pre-configure autoreset events/download slots + _Transfers = new Dictionary(); + + // Pre-configure autoreset events and threadpool slots for (int i = 0; i < maxTextureRequests; i++) { - resetEvents[i] = new AutoResetEvent(false); + resetEvents[i] = new AutoResetEvent(true); threadpoolSlots[i] = -1; } - client.Assets.OnImageReceived += Assets_OnImageReceived; - client.Assets.OnImageReceiveProgress += Assets_OnImageReceiveProgress; + // Handle client connected and disconnected events + client.Network.OnConnected += delegate { Startup(); }; + client.Network.OnDisconnected += delegate { Shutdown(); }; - // Fire up the texture download thread - downloadMaster = new Thread(new ThreadStart(DownloadThread)); - downloadMaster.Start(); + // Instantiate master thread that manages the request pool + downloadMaster = new Thread(DownloadThread); + downloadMaster.Name = "TexturePipeline"; + downloadMaster.IsBackground = true; + + RefreshDownloadsTimer.Elapsed += RefreshDownloadsTimer_Elapsed; + } - public void Shutdown() + /// + /// Initialize callbacks required for the TexturePipeline to operate + /// + private void Startup() { - client.Assets.OnImageReceived -= Assets_OnImageReceived; - client.Assets.OnImageReceiveProgress -= Assets_OnImageReceiveProgress; + if(_Running) + return; - requestQueue.Clear(); + _Client.Network.RegisterCallback(PacketType.ImageData, ImageDataHandler); + _Client.Network.RegisterCallback(PacketType.ImagePacket, ImagePacketHandler); + _Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, ImageNotInDatabaseHandler); + downloadMaster.Start(); + RefreshDownloadsTimer.Start(); + + _Running = true; + } + + /// + /// Shutdown the TexturePipeline and cleanup any callbacks or transfers + /// + private void Shutdown() + { + if(!_Running) + return; +#if DEBUG_TIMING + Logger.Log(String.Format("Combined Execution Time: {0}, Network Execution Time {1}, Network {2}K/sec, Image Size {3}", + TotalTime, NetworkTime, Math.Round(TotalBytes / NetworkTime.TotalSeconds / 60, 2), TotalBytes), Helpers.LogLevel.Debug); +#endif + RefreshDownloadsTimer.Stop(); + + _Client.Network.UnregisterCallback(PacketType.ImageNotInDatabase, ImageNotInDatabaseHandler); + _Client.Network.UnregisterCallback(PacketType.ImageData, ImageDataHandler); + _Client.Network.UnregisterCallback(PacketType.ImagePacket, ImagePacketHandler); + + lock (_Transfers) + _Transfers.Clear(); for (int i = 0; i < resetEvents.Length; i++) if (resetEvents[i] != null) resetEvents[i].Set(); - running = false; + _Running = false; + } + + private void RefreshDownloadsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + lock (_Transfers) + { + foreach (TaskInfo transfer in _Transfers.Values) + { + if (transfer.State == TextureRequestState.Progress) + { + ImageDownload download = transfer.Transfer; + + uint packet = 0; + + if (download.PacketsSeen != null && download.PacketsSeen.Count > 0) + { + lock (download.PacketsSeen) + { + bool first = true; + foreach (KeyValuePair packetSeen in download.PacketsSeen) + { + if (first) + { + // Initially set this to the earliest packet received in the transfer + packet = packetSeen.Value; + first = false; + } + else + { + ++packet; + + // If there is a missing packet in the list, break and request the download + // resume here + if (packetSeen.Value != packet) + { + --packet; + break; + } + } + } + + ++packet; + } + } + + if (download.TimeSinceLastPacket > 5000) + { + if (download.DiscardLevel > 0) + { + --download.DiscardLevel; + } + download.TimeSinceLastPacket = 0; + RequestImage(download.ID, download.ImageType, download.Priority, download.DiscardLevel, packet); + } + } + } + } } /// - /// Request a texture be downloaded, once downloaded OnImageRenderReady event will be fired - /// containing texture key which can be used to retrieve texture with GetTextureToRender method + /// Request a texture asset from the simulator using the system to + /// manage the requests and re-assemble the image from the packets received from the simulator /// - /// Texture to request - /// Type of the requested texture - public void RequestTexture(UUID textureID, ImageType type) + /// The of the texture asset to download + /// The of the texture asset. + /// Use for most textures, or for baked layer texture assets + /// A float indicating the requested priority for the transfer. Higher priority values tell the simulator + /// to prioritize the request before lower valued requests. An image already being transferred using the can have + /// its priority changed by resending the request with the new priority value + /// Number of quality layers to discard. + /// This controls the end marker of the data sent + /// The packet number to begin the request at. A value of 0 begins the request + /// from the start of the asset texture + /// The callback to fire when the image is retrieved. The callback + /// will contain the result of the request and the texture asset data + /// If true, the callback will be fired for each chunk of the downloaded image. + /// The callback asset parameter will contain all previously received chunks of the texture asset starting + /// from the beginning of the request + public void RequestTexture(UUID textureID, ImageType imageType, float priority, int discardLevel, uint packetStart, TextureDownloadCallback callback, bool progressive) { - lock (syncObject) - { - if (client.Assets.Cache.HasImage(textureID)) - { - // Add to rendering dictionary - if (!completedDownloads.ContainsKey(textureID)) - { - completedDownloads.Add(textureID, client.Assets.Cache.GetCachedImage(textureID)); + if (textureID == UUID.Zero) + return; - // Let any subscribers know about it - if (OnDownloadFinished != null) - OnDownloadFinished(textureID, true); + if (callback != null) + { + if (_Client.Assets.Cache.HasImage(textureID)) + { + ImageDownload image = new ImageDownload(); + image.ID = textureID; + image.AssetData = _Client.Assets.Cache.GetCachedImageBytes(textureID); + image.Size = image.AssetData.Length; + image.Transferred = image.AssetData.Length; + image.ImageType = imageType; + image.AssetType = AssetType.Texture; + image.Success = true; + + AssetTexture asset = new AssetTexture(image.ID, image.AssetData); + callback(TextureRequestState.Finished, /*image,*/ asset); + } + else + { + lock (_Transfers) + { + if (!_Transfers.ContainsKey(textureID)) + { + TaskInfo request = new TaskInfo(); + request.State = TextureRequestState.Pending; + request.RequestID = textureID; + request.ReportProgress = progressive; + request.RequestSlot = -1; + request.Type = imageType; + request.Callback = callback; + + ImageDownload downloadParams = new ImageDownload(); + downloadParams.ID = textureID; + downloadParams.Priority = priority; + downloadParams.ImageType = imageType; + downloadParams.DiscardLevel = discardLevel; + + request.Transfer = downloadParams; +#if DEBUG_TIMING + request.StartTime = DateTime.UtcNow; +#endif + _Transfers.Add(textureID, request); + } + } + } + } + } + + /// + /// Sends the actual request packet to the simulator + /// + /// The image to download + /// Type of the image to download, either a baked + /// avatar texture or a normal texture + /// Priority level of the download. Default is + /// 1,013,000.0f + /// Number of quality layers to discard. + /// This controls the end marker of the data sent + /// Packet number to start the download at. + /// This controls the start marker of the data sent + /// Sending a priority of 0 and a discardlevel of -1 aborts + /// download + private void RequestImage(UUID imageID, ImageType type, float priority, int discardLevel, uint packetNum) + { + // Priority == 0 && DiscardLevel == -1 means cancel the transfer + if (priority.Equals(0) && discardLevel.Equals(-1)) + { + AbortTextureRequest(imageID); + } + else + { + + lock (_Transfers) + { + if (_Transfers.ContainsKey(imageID)) + { + if (_Transfers[imageID].Transfer.Simulator != null) + { + // Already downloading, just updating the priority + TaskInfo task = _Transfers[imageID]; + + float percentComplete = (task.Transfer.Transferred / (float)task.Transfer.Size) * 100f; + if (Single.IsNaN(percentComplete)) + percentComplete = 0f; + + + + if (percentComplete > 0) + Logger.DebugLog(String.Format("Updating priority on image transfer {0}, {1}% complete", + imageID, Math.Round(percentComplete, 2))); + } + else + { + ImageDownload transfer = _Transfers[imageID].Transfer; + transfer.Simulator = _Client.Network.CurrentSim; + } + + // Build and send the request packet + RequestImagePacket request = new RequestImagePacket(); + request.AgentData.AgentID = _Client.Self.AgentID; + request.AgentData.SessionID = _Client.Self.SessionID; + request.RequestImage = new RequestImagePacket.RequestImageBlock[1]; + request.RequestImage[0] = new RequestImagePacket.RequestImageBlock(); + request.RequestImage[0].DiscardLevel = (sbyte)discardLevel; + request.RequestImage[0].DownloadPriority = priority; + request.RequestImage[0].Packet = packetNum; + request.RequestImage[0].Image = imageID; + request.RequestImage[0].Type = (byte)type; + + _Client.Network.SendPacket(request, _Client.Network.CurrentSim); } else { - // This image has already been served up, ignore this request + Logger.Log("Received texture download request for a texture that isn't in the download queue: " + imageID, Helpers.LogLevel.Warning); } } - else - { - // Make sure the request isn't already queued up - foreach (TaskInfo task in requestQueue) - { - if (task.RequestID == textureID) - return; - } - - // Make sure we aren't already downloading the texture - if (!currentRequests.ContainsKey(textureID)) - requestQueue.Add(new TaskInfo(textureID, 0, type)); - } } } /// - /// retrieve texture information from dictionary + /// Cancel a pending or in process texture request /// - /// Texture ID - /// ImageDownload object - public ImageDownload GetTextureToRender(UUID textureID) + /// The texture assets unique ID + public void AbortTextureRequest(UUID textureID) { - lock (syncObject) + lock (_Transfers) { - if (completedDownloads.ContainsKey(textureID)) + if (_Transfers.ContainsKey(textureID)) { - return completedDownloads[textureID]; - } - else - { - Logger.Log("Requested texture data for texture that does not exist in dictionary", Helpers.LogLevel.Warning); - return null; - } - } - } + TaskInfo task = _Transfers[textureID]; - /// - /// Remove no longer necessary texture from dictionary - /// - /// - public bool RemoveFromPipeline(UUID textureID) - { - lock (syncObject) - return completedDownloads.Remove(textureID); - } - - public void AbortDownload(UUID textureID) - { - lock (syncObject) - { - for (int i = 0; i < requestQueue.Count; i++) - { - TaskInfo task = requestQueue[i]; - - if (task.RequestID == textureID) + // this means we've actually got the request assigned to the threadpool + if (task.State == TextureRequestState.Progress) { - requestQueue.RemoveAt(i); - --i; + RequestImagePacket request = new RequestImagePacket(); + request.AgentData.AgentID = _Client.Self.AgentID; + request.AgentData.SessionID = _Client.Self.SessionID; + request.RequestImage = new RequestImagePacket.RequestImageBlock[1]; + request.RequestImage[0] = new RequestImagePacket.RequestImageBlock(); + request.RequestImage[0].DiscardLevel = -1; + request.RequestImage[0].DownloadPriority = 0; + request.RequestImage[0].Packet = 0; + request.RequestImage[0].Image = textureID; + request.RequestImage[0].Type = (byte)task.Type; + _Client.Network.SendPacket(request); + + task.Callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes)); + + + resetEvents[task.RequestSlot].Set(); + + _Transfers.Remove(textureID); + } + else + { + _Transfers.Remove(textureID); + + task.Callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes)); } - } - - int current; - if (currentRequests.TryGetValue(textureID, out current)) - { - currentRequests.Remove(textureID); - resetEvents[current].Set(); - - // FIXME: Send an abort packet } } } @@ -225,133 +463,310 @@ namespace OpenMetaverse /// private void DownloadThread() { - int reqNbr; + int slot; - while (running) + while (_Running) { - if (requestQueue.Count > 0) + // find free slots + int pending = 0; + int active = 0; + + TaskInfo nextTask = null; + + lock (_Transfers) { - reqNbr = -1; - // find available slot for reset event - for (int i = 0; i < threadpoolSlots.Length; i++) + foreach (UUID request in _Transfers.Keys) { - if (threadpoolSlots[i] == -1) + if (_Transfers[request].State == TextureRequestState.Pending) { - threadpoolSlots[i] = 1; - reqNbr = i; - break; - } - } - - if (reqNbr != -1) - { - TaskInfo task = null; - lock (syncObject) - { - if (requestQueue.Count > 0) - { - task = requestQueue[0]; - requestQueue.RemoveAt(0); - } + nextTask = _Transfers[request]; + pending++; } - if (task != null) - { - task.RequestNbr = reqNbr; - - Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr)); - ThreadPool.QueueUserWorkItem(TextureRequestDoWork, task); - continue; - } + if (_Transfers[request].State == TextureRequestState.Progress) + active++; } } - // Queue was empty, let's give up some CPU time + if (pending > 0 && active <= maxTextureRequests) + { + slot = -1; + // find available slot for reset event + lock (lockerObject) + { + for (int i = 0; i < threadpoolSlots.Length; i++) + { + if (threadpoolSlots[i] == -1) + { + // found a free slot + threadpoolSlots[i] = 1; + slot = i; + break; + } + } + } + + // -1 = slot not available + if (slot != -1 && nextTask != null) + { + + nextTask.State = TextureRequestState.Started; + nextTask.RequestSlot = slot; + nextTask.Transfer = new ImageDownload(); + nextTask.Transfer.ID = nextTask.RequestID; + + //Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", slot)); + ThreadPool.QueueUserWorkItem(TextureRequestDoWork, nextTask); + continue; + + } + } + + if(pending <= 0) + Console.WriteLine("No Pending Downloads... {0} Still Running", active); + // Queue was empty or all download slots are inuse, let's give up some CPU time Thread.Sleep(500); } Logger.Log("Texture pipeline shutting down", Helpers.LogLevel.Info); } + + /// + /// The worker thread that sends the request and handles timeouts + /// + /// A object containing the request details private void TextureRequestDoWork(Object threadContext) { - TaskInfo ti = (TaskInfo)threadContext; + TaskInfo task = (TaskInfo) threadContext; + + task.State = TextureRequestState.Progress; - lock (syncObject) - { - if (currentRequests.ContainsKey(ti.RequestID)) - { - threadpoolSlots[ti.RequestNbr] = -1; - return; - } - else - { - currentRequests.Add(ti.RequestID, ti.RequestNbr); - } - } - - Logger.DebugLog(String.Format("Worker {0} Requesting {1}", ti.RequestNbr, ti.RequestID)); - - resetEvents[ti.RequestNbr].Reset(); - client.Assets.RequestImage(ti.RequestID, ti.Type); +#if DEBUG_TIMING + task.NetworkTime = DateTime.UtcNow; +#endif + // start the timeout timer + resetEvents[task.RequestSlot].Reset(); + RequestImage(task.RequestID, task.Type, 1013000.0f, 0, 0); // don't release this worker slot until texture is downloaded or timeout occurs - if (!resetEvents[ti.RequestNbr].WaitOne(45 * 1000, false)) + if (!resetEvents[task.RequestSlot].WaitOne(_Client.Settings.PIPELINE_REQUEST_TIMEOUT, false)) { // Timed out - Logger.Log("Worker " + ti.RequestNbr + " Timeout waiting for Texture " + ti.RequestID + " to Download", Helpers.LogLevel.Warning); + Logger.Log("Worker " + task.RequestSlot + " Timeout waiting for Texture " + task.RequestID + " to Download Got " + task.Transfer.Transferred + " of " + task.Transfer.Size, Helpers.LogLevel.Warning); - lock (syncObject) - currentRequests.Remove(ti.RequestID); + task.Callback(TextureRequestState.Timeout, new AssetTexture(task.RequestID, task.Transfer.AssetData)); - if (OnDownloadFinished != null) - OnDownloadFinished(ti.RequestID, false); + lock (_Transfers) + _Transfers.Remove(task.RequestID); } // free up this download slot - threadpoolSlots[ti.RequestNbr] = -1; + lock(lockerObject) + threadpoolSlots[task.RequestSlot] = -1; } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) + #region Raw Packet Handlers + + /// + /// Handle responses from the simulator that tell us a texture we have requested is unable to be located + /// or no longer exists. This will remove the request from the pipeline and free up a slot if one is in use + /// + /// The + /// The sending this packet + private void ImageNotInDatabaseHandler(Packet packet, Simulator simulator) { - int requestNbr; - bool found; - - lock (syncObject) - found = currentRequests.TryGetValue(image.ID, out requestNbr); - - if (asset != null && found) + ImageNotInDatabasePacket imageNotFoundData = (ImageNotInDatabasePacket)packet; + lock (_Transfers) { - Logger.DebugLog(String.Format("Worker {0} Downloaded texture {1}", requestNbr, image.ID)); - - // Free up this slot in the ThreadPool - lock (syncObject) - currentRequests.Remove(image.ID); - - resetEvents[requestNbr].Set(); - - if (image.Success) + if (_Transfers.ContainsKey(imageNotFoundData.ImageID.ID)) { - // Add to the completed texture dictionary - lock (syncObject) - completedDownloads[image.ID] = image; - } + // cancel acive request and free up the threadpool slot + TaskInfo task = _Transfers[imageNotFoundData.ImageID.ID]; + if (task.State == TextureRequestState.Progress) + { + resetEvents[task.RequestSlot].Set(); + } + + // fire callback to inform the caller + task.Callback(TextureRequestState.NotFound, new AssetTexture(imageNotFoundData.ImageID.ID, Utils.EmptyBytes)); + + resetEvents[task.RequestSlot].Set(); + + _Transfers.Remove(imageNotFoundData.ImageID.ID); + } else { - Logger.Log(String.Format("Download of texture {0} failed. NotFound={1}", image.ID, image.NotFound), - Helpers.LogLevel.Warning); + Logger.Log("Received an ImageNotFound packet for an image we did not request: " + imageNotFoundData.ImageID.ID, Helpers.LogLevel.Warning); } - - // Let any subscribers know about it - if (OnDownloadFinished != null) - OnDownloadFinished(image.ID, image.Success); } } - private void Assets_OnImageReceiveProgress(UUID image, int lastPacket, int recieved, int total) + /// + /// Handles the remaining Image data that did not fit in the initial ImageData packet + /// + private void ImagePacketHandler(Packet packet, Simulator simulator) { - if (OnDownloadProgress != null && currentRequests.ContainsKey(image)) - OnDownloadProgress(image, recieved, total); + ImagePacketPacket image = (ImagePacketPacket)packet; + TaskInfo task = null; + + lock (_Transfers) + { + if (_Transfers.ContainsKey(image.ImageID.ID)) + { + task = _Transfers[image.ImageID.ID]; + } + + + if (task != null) + { + if (task.Transfer.Size == 0) + { + // We haven't received the header yet, block until it's received or times out + task.Transfer.HeaderReceivedEvent.WaitOne(1000*5, false); + + if (task.Transfer.Size == 0) + { + Logger.Log("Timed out while waiting for the image header to download for " + + task.Transfer.ID, Helpers.LogLevel.Warning, _Client); + + _Transfers.Remove(task.Transfer.ID); + resetEvents[task.RequestSlot].Set(); // free up request slot + task.Callback(TextureRequestState.Timeout, new AssetTexture(task.RequestID, task.Transfer.AssetData)); + return; + } + } + + // The header is downloaded, we can insert this data in to the proper position + // Only insert if we haven't seen this packet before + lock (task.Transfer.PacketsSeen) + { + if (!task.Transfer.PacketsSeen.ContainsKey(image.ImageID.Packet)) + { + task.Transfer.PacketsSeen[image.ImageID.Packet] = image.ImageID.Packet; + Buffer.BlockCopy(image.ImageData.Data, 0, task.Transfer.AssetData, + task.Transfer.InitialDataSize + (1000*(image.ImageID.Packet - 1)), + image.ImageData.Data.Length); + task.Transfer.Transferred += image.ImageData.Data.Length; + } + } + + task.Transfer.TimeSinceLastPacket = 0; + //resetEvents[task.RequestNbr].Reset(); + + if (task.Transfer.Transferred >= task.Transfer.Size) + { +#if DEBUG_TIMING + DateTime stopTime = DateTime.UtcNow; + TimeSpan requestDuration = stopTime - task.StartTime; + + TimeSpan networkDuration = stopTime - task.NetworkTime; + + TotalTime += requestDuration; + NetworkTime += networkDuration; + TotalBytes += task.Transfer.Size; + + Logger.Log( + String.Format( + "Transfer Complete {0} [{1}] Total Request Time: {2}, Download Time {3}, Network {4}Kb/sec, Image Size {5} bytes", + task.RequestID, task.RequestSlot, requestDuration, networkDuration, + Math.Round(task.Transfer.Size/networkDuration.TotalSeconds/60, 2), task.Transfer.Size), + Helpers.LogLevel.Debug); +#endif + + task.Transfer.Success = true; + _Transfers.Remove(task.Transfer.ID); + resetEvents[task.RequestSlot].Set(); // free up request slot + _Client.Assets.Cache.SaveImageToCache(task.RequestID, task.Transfer.AssetData); + + AssetTexture asset = new AssetTexture(task.RequestID, task.Transfer.AssetData); + task.Callback(TextureRequestState.Finished, /*task.Transfer,*/ new AssetTexture(task.RequestID, task.Transfer.AssetData)); + } + else + { + if (task.ReportProgress) + task.Callback(TextureRequestState.Progress, new AssetTexture(task.RequestID, task.Transfer.AssetData)); + } + } + } + } + + /// + /// Handle the initial ImageDataPacket sent from the simulator + /// + /// + /// + private void ImageDataHandler(Packet packet, Simulator simulator) + { + ImageDataPacket data = (ImageDataPacket) packet; + TaskInfo task = null; + + lock (_Transfers) + { + if (_Transfers.ContainsKey(data.ImageID.ID)) + { + task = _Transfers[data.ImageID.ID]; + } + + + if (task != null) + { + // reset the timeout interval since we got data + resetEvents[task.RequestSlot].Reset(); + + if (task.Transfer.Size == 0) + { + task.Transfer.Codec = (ImageCodec) data.ImageID.Codec; + task.Transfer.PacketCount = data.ImageID.Packets; + task.Transfer.Size = (int) data.ImageID.Size; + task.Transfer.AssetData = new byte[task.Transfer.Size]; + task.Transfer.AssetType = AssetType.Texture; + task.Transfer.PacketsSeen = new SortedList(); + Buffer.BlockCopy(data.ImageData.Data, 0, task.Transfer.AssetData, 0, data.ImageData.Data.Length); + task.Transfer.InitialDataSize = data.ImageData.Data.Length; + task.Transfer.Transferred += data.ImageData.Data.Length; + } + + task.Transfer.HeaderReceivedEvent.Set(); + + if (task.Transfer.Transferred >= task.Transfer.Size) + { +#if DEBUG_TIMING + DateTime stopTime = DateTime.UtcNow; + TimeSpan requestDuration = stopTime - task.StartTime; + + TimeSpan networkDuration = stopTime - task.NetworkTime; + + TotalTime += requestDuration; + NetworkTime += networkDuration; + TotalBytes += task.Transfer.Size; + + Logger.Log( + String.Format( + "Transfer Complete {0} [{1}] Total Request Time: {2}, Download Time {3}, Network {4}Kb/sec, Image Size {5} bytes", + task.RequestID, task.RequestSlot, requestDuration, networkDuration, + Math.Round(task.Transfer.Size/networkDuration.TotalSeconds/60, 2), task.Transfer.Size), + Helpers.LogLevel.Debug); +#endif + task.Transfer.Success = true; + _Transfers.Remove(task.RequestID); + resetEvents[task.RequestSlot].Set(); + + _Client.Assets.Cache.SaveImageToCache(task.RequestID, task.Transfer.AssetData); + //AssetTexture asset = new AssetTexture(task.RequestID, task.Transfer.AssetData); + task.Callback(TextureRequestState.Finished, new AssetTexture(task.RequestID, task.Transfer.AssetData)); + } + else + { + if (task.ReportProgress) + { + task.Callback(TextureRequestState.Progress, new AssetTexture(task.RequestID, task.Transfer.AssetData)); + } + } + } } } + + #endregion + } } diff --git a/Programs/PrimWorkshop/frmBrowser.cs b/Programs/PrimWorkshop/frmBrowser.cs index cebc364d..d3c4837b 100644 --- a/Programs/PrimWorkshop/frmBrowser.cs +++ b/Programs/PrimWorkshop/frmBrowser.cs @@ -35,7 +35,6 @@ namespace PrimWorkshop int TotalPrims; // Textures - TexturePipeline TextureDownloader; Dictionary Textures = new Dictionary(); // Terrain @@ -147,7 +146,7 @@ namespace PrimWorkshop Client.Settings.ALWAYS_REQUEST_OBJECTS = true; Client.Settings.SEND_AGENT_UPDATES = true; Client.Settings.USE_TEXTURE_CACHE = true; - Client.Settings.TEXTURE_CACHE_DIR = Application.StartupPath + System.IO.Path.DirectorySeparatorChar + "cache"; + //Client.Settings.TEXTURE_CACHE_DIR = Application.StartupPath + System.IO.Path.DirectorySeparatorChar + "cache"; Client.Settings.ALWAYS_REQUEST_PARCEL_ACL = false; Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL = false; // Crank up the throttle on texture downloads @@ -166,13 +165,6 @@ namespace PrimWorkshop Client.Terrain.OnLandPatch += new TerrainManager.LandPatchCallback(Terrain_OnLandPatch); Client.Parcels.OnSimParcelsDownloaded += new ParcelManager.SimParcelsDownloaded(Parcels_OnSimParcelsDownloaded); - // Initialize the texture download pipeline - if (TextureDownloader != null) - TextureDownloader.Shutdown(); - TextureDownloader = new TexturePipeline(Client, 10); - TextureDownloader.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(TextureDownloader_OnDownloadFinished); - TextureDownloader.OnDownloadProgress += new TexturePipeline.DownloadProgressCallback(TextureDownloader_OnDownloadProgress); - // Initialize the camera object InitCamera(); @@ -976,7 +968,7 @@ namespace PrimWorkshop if (!Textures.ContainsKey(teFace.TextureID)) { // We haven't constructed this image in OpenGL yet, get ahold of it - TextureDownloader.RequestTexture(teFace.TextureID, ImageType.Normal); + Client.Assets.RequestImage(teFace.TextureID, ImageType.Normal, TextureDownloader_OnDownloadFinished); } } } @@ -1401,19 +1393,23 @@ StartRender: #region Texture Downloading - private void TextureDownloader_OnDownloadFinished(UUID id, bool success) + private void TextureDownloader_OnDownloadFinished(TextureRequestState state, AssetTexture asset) { bool alpha = false; ManagedImage imgData = null; byte[] raw = null; + + bool success = (state == TextureRequestState.Finished); + + UUID id = asset.AssetID; try { // Load the image off the disk if (success) { - ImageDownload download = TextureDownloader.GetTextureToRender(id); - if (OpenJPEG.DecodeToImage(download.AssetData, out imgData)) + //ImageDownload download = TextureDownloader.GetTextureToRender(id); + if (OpenJPEG.DecodeToImage(asset.AssetData, out imgData)) { raw = imgData.ExportRaw(); @@ -1614,10 +1610,6 @@ StartRender: // Set the login button back to login state cmdLogin.Text = "Login"; - // Shutdown the texture downloader - if (TextureDownloader != null) - TextureDownloader.Shutdown(); - // Enable input controls txtFirst.Enabled = txtLast.Enabled = txtPass.Enabled = true; } diff --git a/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs b/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs index bcd0ce23..ee5a560f 100644 --- a/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/DumpOutfitCommand.cs @@ -10,7 +10,6 @@ namespace OpenMetaverse.TestClient public class DumpOutfitCommand : Command { List OutfitAssets = new List(); - AssetManager.ImageReceivedCallback ImageReceivedHandler; public DumpOutfitCommand(TestClient testClient) { @@ -18,7 +17,6 @@ namespace OpenMetaverse.TestClient Description = "Dumps all of the textures from an avatars outfit to the hard drive. Usage: dumpoutfit [avatar-uuid]"; Category = CommandCategory.Inventory; - ImageReceivedHandler = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); } public override string Execute(string[] args, UUID fromAgentID) @@ -35,9 +33,7 @@ namespace OpenMetaverse.TestClient { for (int i = 0; i < Client.Network.Simulators.Count; i++) { - Avatar targetAv; - - targetAv = Client.Network.Simulators[i].ObjectsAvatars.Find( + Avatar targetAv = Client.Network.Simulators[i].ObjectsAvatars.Find( delegate(Avatar avatar) { return avatar.ID == target; @@ -49,7 +45,6 @@ namespace OpenMetaverse.TestClient StringBuilder output = new StringBuilder("Downloading "); lock (OutfitAssets) OutfitAssets.Clear(); - Client.Assets.OnImageReceived += ImageReceivedHandler; for (int j = 0; j < targetAv.Textures.FaceTextures.Length; j++) { @@ -71,8 +66,7 @@ namespace OpenMetaverse.TestClient } OutfitAssets.Add(face.TextureID); - Client.Assets.RequestImage(face.TextureID, type, 100000.0f, 0, 0); - + Client.Assets.RequestImage(face.TextureID, type, Assets_OnImageReceived); output.Append(((AppearanceManager.TextureIndex)j).ToString()); output.Append(" "); } @@ -86,24 +80,24 @@ namespace OpenMetaverse.TestClient return "Couldn't find avatar " + target.ToString(); } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture assetTexture) + private void Assets_OnImageReceived(TextureRequestState state, AssetTexture assetTexture) { lock (OutfitAssets) { - if (OutfitAssets.Contains(image.ID)) + if (OutfitAssets.Contains(assetTexture.AssetID)) { - if (image.Success) + if (state == TextureRequestState.Finished) { try { - File.WriteAllBytes(image.ID.ToString() + ".jp2", image.AssetData); - Console.WriteLine("Wrote JPEG2000 image " + image.ID.ToString() + ".jp2"); + File.WriteAllBytes(assetTexture.AssetID + ".jp2", assetTexture.AssetData); + Console.WriteLine("Wrote JPEG2000 image " + assetTexture.AssetID + ".jp2"); ManagedImage imgData; - OpenJPEG.DecodeToImage(image.AssetData, out imgData); + OpenJPEG.DecodeToImage(assetTexture.AssetData, out imgData); byte[] tgaFile = imgData.ExportTGA(); - File.WriteAllBytes(image.ID.ToString() + ".tga", tgaFile); - Console.WriteLine("Wrote TGA image " + image.ID.ToString() + ".tga"); + File.WriteAllBytes(assetTexture.AssetID + ".tga", tgaFile); + Console.WriteLine("Wrote TGA image " + assetTexture.AssetID + ".tga"); } catch (Exception e) { @@ -112,13 +106,10 @@ namespace OpenMetaverse.TestClient } else { - Console.WriteLine("Failed to download image " + image.ID.ToString()); + Console.WriteLine("Failed to download image " + assetTexture.AssetID); } - OutfitAssets.Remove(image.ID); - - if (OutfitAssets.Count == 0) - Client.Assets.OnImageReceived -= ImageReceivedHandler; + OutfitAssets.Remove(assetTexture.AssetID); } } } diff --git a/Programs/examples/TestClient/Commands/Prims/DownloadTextureCommand.cs b/Programs/examples/TestClient/Commands/Prims/DownloadTextureCommand.cs index d4994678..bc9b2437 100644 --- a/Programs/examples/TestClient/Commands/Prims/DownloadTextureCommand.cs +++ b/Programs/examples/TestClient/Commands/Prims/DownloadTextureCommand.cs @@ -9,8 +9,8 @@ namespace OpenMetaverse.TestClient { UUID TextureID; AutoResetEvent DownloadHandle = new AutoResetEvent(false); - ImageDownload Image; AssetTexture Asset; + TextureRequestState resultState; public DownloadTextureCommand(TestClient testClient) { @@ -19,8 +19,6 @@ namespace OpenMetaverse.TestClient "Usage: downloadtexture [texture-uuid] [discardlevel]"; Category = CommandCategory.Inventory; - testClient.Assets.OnImageReceiveProgress += new AssetManager.ImageReceiveProgressCallback(Assets_OnImageReceiveProgress); - testClient.Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); } public override string Execute(string[] args, UUID fromAgentID) @@ -30,7 +28,6 @@ namespace OpenMetaverse.TestClient TextureID = UUID.Zero; DownloadHandle.Reset(); - Image = null; Asset = null; if (UUID.TryParse(args[0], out TextureID)) @@ -43,31 +40,31 @@ namespace OpenMetaverse.TestClient return "Usage: downloadtexture [texture-uuid] [discardlevel]"; } - Client.Assets.RequestImage(TextureID, ImageType.Normal, 1000000.0f, discardLevel, 0); + Client.Assets.RequestImage(TextureID, ImageType.Normal, Assets_OnImageReceived); if (DownloadHandle.WaitOne(120 * 1000, false)) { - if (Image != null && Image.Success) + if (resultState == TextureRequestState.Finished) { if (Asset != null && Asset.Decode()) { - try { File.WriteAllBytes(Image.ID.ToString() + ".jp2", Asset.AssetData); } + try { File.WriteAllBytes(Asset.AssetID + ".jp2", Asset.AssetData); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client, ex); } - return String.Format("Saved {0}.jp2 ({1}x{2})", Image.ID, Asset.Image.Width, Asset.Image.Height); + return String.Format("Saved {0}.jp2 ({1}x{2})", Asset.AssetID, Asset.Image.Width, Asset.Image.Height); } else { return "Failed to decode texture " + TextureID.ToString(); } } - else if (Image != null && Image.NotFound) + else if (resultState == TextureRequestState.NotFound) { return "Simulator reported texture not found: " + TextureID.ToString(); } else { - return "Download failed for texture " + TextureID.ToString(); + return "Download failed for texture " + TextureID + " " + resultState; } } else @@ -81,18 +78,12 @@ namespace OpenMetaverse.TestClient } } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) + private void Assets_OnImageReceived(TextureRequestState state, AssetTexture asset) { - Image = image; + resultState = state; Asset = asset; DownloadHandle.Set(); } - - private void Assets_OnImageReceiveProgress(UUID image, int lastPacket, int recieved, int total) - { - if (image == TextureID) - Console.WriteLine(String.Format("Texture {0}: Received {1} / {2} (Packet: {3})", image, recieved, total, lastPacket)); - } } } diff --git a/Programs/examples/TestClient/Commands/Prims/ExportCommand.cs b/Programs/examples/TestClient/Commands/Prims/ExportCommand.cs index 04ce706f..c2dc5db4 100644 --- a/Programs/examples/TestClient/Commands/Prims/ExportCommand.cs +++ b/Programs/examples/TestClient/Commands/Prims/ExportCommand.cs @@ -22,7 +22,6 @@ namespace OpenMetaverse.TestClient { testClient.Objects.OnObjectPropertiesFamily += new ObjectManager.ObjectPropertiesFamilyCallback(Objects_OnObjectPropertiesFamily); testClient.Objects.OnObjectProperties += new ObjectManager.ObjectPropertiesCallback(Objects_OnObjectProperties); - testClient.Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); testClient.Avatars.OnPointAt += new AvatarManager.PointAtCallback(Avatars_OnPointAt); Name = "export"; @@ -143,7 +142,10 @@ namespace OpenMetaverse.TestClient } // Download all of the textures in the export list - Client.Assets.RequestImages(textureRequests); + foreach (ImageRequest request in textureRequests) + { + Client.Assets.RequestImage(request.ImageID, request.Type, Assets_OnImageReceived); + } return "XML exported, began downloading " + Textures.Count + " textures"; } @@ -176,33 +178,34 @@ namespace OpenMetaverse.TestClient return AllPropertiesReceived.WaitOne(2000 + msPerRequest * objects.Count, false); } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) + private void Assets_OnImageReceived(TextureRequestState state, AssetTexture asset) { - if (Textures.Contains(image.ID)) + + if (state == TextureRequestState.Finished && Textures.Contains(asset.AssetID)) { lock (Textures) - Textures.Remove(image.ID); + Textures.Remove(asset.AssetID); - if (image.Success) + if (state == TextureRequestState.Finished) { - try { File.WriteAllBytes(image.ID.ToString() + ".jp2", asset.AssetData); } + try { File.WriteAllBytes(asset.AssetID + ".jp2", asset.AssetData); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } if (asset.Decode()) { - try { File.WriteAllBytes(image.ID.ToString() + ".tga", asset.Image.ExportTGA()); } + try { File.WriteAllBytes(asset.AssetID + ".tga", asset.Image.ExportTGA()); } catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, Client); } } else { - Logger.Log("Failed to decode image " + image.ID.ToString(), Helpers.LogLevel.Error, Client); + Logger.Log("Failed to decode image " + asset.AssetID, Helpers.LogLevel.Error, Client); } - Logger.Log("Finished downloading image " + image.ID.ToString(), Helpers.LogLevel.Info, Client); + Logger.Log("Finished downloading image " + asset.AssetID, Helpers.LogLevel.Info, Client); } else { - Logger.Log("Failed to download image " + image.ID.ToString(), Helpers.LogLevel.Warning, Client); + Logger.Log("Failed to download image " + asset.AssetID + ":" + state, Helpers.LogLevel.Warning, Client); } } } diff --git a/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs b/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs index 521dc63c..e6920e56 100644 --- a/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs +++ b/Programs/examples/TestClient/Commands/Prims/TexturesCommand.cs @@ -19,7 +19,6 @@ namespace OpenMetaverse.TestClient testClient.Objects.OnNewPrim += new ObjectManager.NewPrimCallback(Objects_OnNewPrim); testClient.Objects.OnNewAvatar += new ObjectManager.NewAvatarCallback(Objects_OnNewAvatar); - testClient.Assets.OnImageReceived += new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); } public override string Execute(string[] args, UUID fromAgentID) @@ -72,7 +71,7 @@ namespace OpenMetaverse.TestClient break; } - Client.Assets.RequestImage(face.TextureID, type); + Client.Assets.RequestImage(face.TextureID, type, Assets_OnImageReceived); } } } @@ -93,21 +92,21 @@ namespace OpenMetaverse.TestClient if (!alreadyRequested.ContainsKey(face.TextureID)) { alreadyRequested[face.TextureID] = face.TextureID; - Client.Assets.RequestImage(face.TextureID, ImageType.Normal); + Client.Assets.RequestImage(face.TextureID, ImageType.Normal, Assets_OnImageReceived); } } } } } - private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset) + private void Assets_OnImageReceived(TextureRequestState state, AssetTexture asset) { - if (enabled && alreadyRequested.ContainsKey(image.ID)) + if (state == TextureRequestState.Finished && enabled && alreadyRequested.ContainsKey(asset.AssetID)) { - if (image.Success) - Logger.DebugLog(String.Format("Finished downloading texture {0} ({1} bytes)", image.ID, image.Size)); + if (state == TextureRequestState.Finished) + Logger.DebugLog(String.Format("Finished downloading texture {0} ({1} bytes)", asset.AssetID, asset.AssetData.Length)); else - Logger.Log("Failed to download texture " + image.ID.ToString(), Helpers.LogLevel.Warning); + Logger.Log("Failed to download texture " + asset.AssetID + ": " + state, Helpers.LogLevel.Warning); } } } diff --git a/Programs/examples/groupmanager/frmGroupInfo.cs b/Programs/examples/groupmanager/frmGroupInfo.cs index 99e09b86..b74c0465 100644 --- a/Programs/examples/groupmanager/frmGroupInfo.cs +++ b/Programs/examples/groupmanager/frmGroupInfo.cs @@ -24,7 +24,6 @@ namespace groupmanager GroupManager.GroupMembersCallback GroupMembersCallback; GroupManager.GroupTitlesCallback GroupTitlesCallback; AvatarManager.AvatarNamesCallback AvatarNamesCallback; - AssetManager.ImageReceivedCallback ImageReceivedCallback; public frmGroupInfo(Group group, GridClient client) { @@ -40,13 +39,11 @@ namespace groupmanager GroupMembersCallback = new GroupManager.GroupMembersCallback(GroupMembersHandler); GroupTitlesCallback = new GroupManager.GroupTitlesCallback(GroupTitlesHandler); AvatarNamesCallback = new AvatarManager.AvatarNamesCallback(AvatarNamesHandler); - ImageReceivedCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived); Group = group; Client = client; // Register the callbacks for this form - Client.Assets.OnImageReceived += ImageReceivedCallback; Client.Groups.OnGroupProfile += GroupProfileCallback; Client.Groups.OnGroupMembers += GroupMembersCallback; Client.Groups.OnGroupTitles += GroupTitlesCallback; @@ -61,7 +58,6 @@ namespace groupmanager ~frmGroupInfo() { // Unregister the callbacks for this form - Client.Assets.OnImageReceived -= ImageReceivedCallback; Client.Groups.OnGroupProfile -= GroupProfileCallback; Client.Groups.OnGroupMembers -= GroupMembersCallback; Client.Groups.OnGroupTitles -= GroupTitlesCallback; @@ -73,24 +69,23 @@ namespace groupmanager Profile = profile; if (Group.InsigniaID != UUID.Zero) - Client.Assets.RequestImage(Group.InsigniaID, ImageType.Normal, 113000.0f, 0, 0); + Client.Assets.RequestImage(Group.InsigniaID, ImageType.Normal, + delegate(TextureRequestState state, AssetTexture assetTexture) + { + ManagedImage imgData; + Image bitmap; + + if (state == TextureRequestState.Finished) + { + OpenJPEG.DecodeToImage(assetTexture.AssetData, out imgData, out bitmap); + picInsignia.Image = bitmap; + } + }); if (this.InvokeRequired) this.BeginInvoke(new MethodInvoker(UpdateProfile)); } - void Assets_OnImageReceived(ImageDownload image, AssetTexture assetTexture) - { - ManagedImage imgData; - Image bitmap; - - if (image.Success) - { - OpenJPEG.DecodeToImage(image.AssetData, out imgData, out bitmap); - picInsignia.Image = bitmap; - } - } - private void UpdateProfile() { lblGroupName.Text = Profile.Name;