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;