corrade-vassal – Rev 1
?pathlinks?
/*
* Copyright (c) 2006-2014, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
//#define DEBUG_TIMING
using System;
using System.Collections.Generic;
using System.Threading;
using OpenMetaverse.Packets;
using OpenMetaverse.Assets;
namespace OpenMetaverse
{
/// <summary>
/// The current status of a texture request as it moves through the pipeline or final result of a texture request.
/// </summary>
public enum TextureRequestState
{
/// <summary>The initial state given to a request. Requests in this state
/// are waiting for an available slot in the pipeline</summary>
Pending,
/// <summary>A request that has been added to the pipeline and the request packet
/// has been sent to the simulator</summary>
Started,
/// <summary>A request that has received one or more packets back from the simulator</summary>
Progress,
/// <summary>A request that has received all packets back from the simulator</summary>
Finished,
/// <summary>A request that has taken longer than <seealso cref="Settings.PIPELINE_REQUEST_TIMEOUT"/>
/// to download OR the initial packet containing the packet information was never received</summary>
Timeout,
/// <summary>The texture request was aborted by request of the agent</summary>
Aborted,
/// <summary>The simulator replied to the request that it was not able to find the requested texture</summary>
NotFound
}
/// <summary>
/// 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.
/// </summary>
/// <param name="state">The <see cref="TextureRequestState"/> indicating either Progress for textures not fully downloaded,
/// or the final result of the request after it has been processed through the TexturePipeline</param>
/// <param name="assetTexture">The <see cref="AssetTexture"/> object containing the Assets ID, raw data
/// and other information. For progressive rendering the <see cref="Asset.AssetData"/> will contain
/// the data from the beginning of the file. For failed, aborted and timed out requests it will contain
/// an empty byte array.</param>
public delegate void TextureDownloadCallback(TextureRequestState state, AssetTexture assetTexture);
/// <summary>
/// Texture request download handler, allows a configurable number of download slots which manage multiple
/// concurrent texture downloads from the <seealso cref="Simulator"/>
/// </summary>
/// <remarks>This class makes full use of the internal <seealso cref="TextureCache"/>
/// system for full texture downloads.</remarks>
public class TexturePipeline
{
#if DEBUG_TIMING // Timing globals
/// <summary>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</summary>
public static TimeSpan TotalTime;
/// <summary>The amount of time the request spent in the <see cref="TextureRequestState.Progress"/> state</summary>
public static TimeSpan NetworkTime;
/// <summary>The total number of bytes transferred since the TexturePipeline was started</summary>
public static int TotalBytes;
#endif
/// <summary>
/// A request task containing information and status of a request as it is processed through the <see cref="TexturePipeline"/>
/// </summary>
private class TaskInfo
{
/// <summary>The current <seealso cref="TextureRequestState"/> which identifies the current status of the request</summary>
public TextureRequestState State;
/// <summary>The Unique Request ID, This is also the Asset ID of the texture being requested</summary>
public UUID RequestID;
/// <summary>The slot this request is occupying in the threadpoolSlots array</summary>
public int RequestSlot;
/// <summary>The ImageType of the request.</summary>
public ImageType Type;
/// <summary>The callback to fire when the request is complete, will include
/// the <seealso cref="TextureRequestState"/> and the <see cref="AssetTexture"/>
/// object containing the result data</summary>
public List<TextureDownloadCallback> Callbacks;
/// <summary>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.</summary>
public bool ReportProgress;
#if DEBUG_TIMING
/// <summary>The time the request was added to the the PipeLine</summary>
public DateTime StartTime;
/// <summary>The time the request was sent to the simulator</summary>
public DateTime NetworkTime;
#endif
/// <summary>An object that maintains the data of an request thats in-process.</summary>
public ImageDownload Transfer;
}
/// <summary>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</summary>
private readonly Dictionary<UUID, TaskInfo> _Transfers;
/// <summary>Holds the reference to the <see cref="GridClient"/> client object</summary>
private readonly GridClient _Client;
/// <summary>Maximum concurrent texture requests allowed at a time</summary>
private readonly int maxTextureRequests;
/// <summary>An array of <see cref="AutoResetEvent"/> objects used to manage worker request threads</summary>
private readonly AutoResetEvent[] resetEvents;
/// <summary>An array of worker slots which shows the availablity status of the slot</summary>
private readonly int[] threadpoolSlots;
/// <summary>The primary thread which manages the requests.</summary>
private Thread downloadMaster;
/// <summary>true if the TexturePipeline is currently running</summary>
bool _Running;
/// <summary>A synchronization object used by the primary thread</summary>
private object lockerObject = new object();
/// <summary>A refresh timer used to increase the priority of stalled requests</summary>
private System.Timers.Timer RefreshDownloadsTimer;
/// <summary>Current number of pending and in-process transfers</summary>
public int TransferCount { get { return _Transfers.Count; } }
/// <summary>
/// Default constructor, Instantiates a new copy of the TexturePipeline class
/// </summary>
/// <param name="client">Reference to the instantiated <see cref="GridClient"/> object</param>
public TexturePipeline(GridClient client)
{
_Client = client;
maxTextureRequests = client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS;
resetEvents = new AutoResetEvent[maxTextureRequests];
threadpoolSlots = new int[maxTextureRequests];
_Transfers = new Dictionary<UUID, TaskInfo>();
// Pre-configure autoreset events and threadpool slots
for (int i = 0; i < maxTextureRequests; i++)
{
resetEvents[i] = new AutoResetEvent(true);
threadpoolSlots[i] = -1;
}
// Handle client connected and disconnected events
client.Network.LoginProgress += delegate(object sender, LoginProgressEventArgs e) {
if (e.Status == LoginStatus.Success)
{
Startup();
}
};
client.Network.Disconnected += delegate { Shutdown(); };
}
/// <summary>
/// Initialize callbacks required for the TexturePipeline to operate
/// </summary>
public void Startup()
{
if (_Running)
return;
if (downloadMaster == null)
{
// Instantiate master thread that manages the request pool
downloadMaster = new Thread(DownloadThread);
downloadMaster.Name = "TexturePipeline";
downloadMaster.IsBackground = true;
}
_Running = true;
_Client.Network.RegisterCallback(PacketType.ImageData, ImageDataHandler);
_Client.Network.RegisterCallback(PacketType.ImagePacket, ImagePacketHandler);
_Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, ImageNotInDatabaseHandler);
downloadMaster.Start();
if (RefreshDownloadsTimer == null)
{
RefreshDownloadsTimer = new System.Timers.Timer(Settings.PIPELINE_REFRESH_INTERVAL);
RefreshDownloadsTimer.Elapsed += RefreshDownloadsTimer_Elapsed;
RefreshDownloadsTimer.Start();
}
}
/// <summary>
/// Shutdown the TexturePipeline and cleanup any callbacks or transfers
/// </summary>
public 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
if(null != RefreshDownloadsTimer) RefreshDownloadsTimer.Dispose();
RefreshDownloadsTimer = null;
if (downloadMaster != null && downloadMaster.IsAlive)
{
downloadMaster.Abort();
}
downloadMaster = null;
_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;
}
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;
// Find the first missing packet in the download
ushort packet = 0;
lock (download) if (download.PacketsSeen != null && download.PacketsSeen.Count > 0)
packet = GetFirstMissingPacket(download.PacketsSeen);
if (download.TimeSinceLastPacket > 5000)
{
// We're not receiving data for this texture fast enough, bump up the priority by 5%
download.Priority *= 1.05f;
download.TimeSinceLastPacket = 0;
RequestImage(download.ID, download.ImageType, download.Priority, download.DiscardLevel, packet);
}
if (download.TimeSinceLastPacket > _Client.Settings.PIPELINE_REQUEST_TIMEOUT)
{
resetEvents[transfer.RequestSlot].Set();
}
}
}
}
}
/// <summary>
/// Request a texture asset from the simulator using the <see cref="TexturePipeline"/> system to
/// manage the requests and re-assemble the image from the packets received from the simulator
/// </summary>
/// <param name="textureID">The <see cref="UUID"/> of the texture asset to download</param>
/// <param name="imageType">The <see cref="ImageType"/> of the texture asset.
/// Use <see cref="ImageType.Normal"/> for most textures, or <see cref="ImageType.Baked"/> for baked layer texture assets</param>
/// <param name="priority">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 <see cref="TexturePipeline"/> can have
/// its priority changed by resending the request with the new priority value</param>
/// <param name="discardLevel">Number of quality layers to discard.
/// This controls the end marker of the data sent</param>
/// <param name="packetStart">The packet number to begin the request at. A value of 0 begins the request
/// from the start of the asset texture</param>
/// <param name="callback">The <see cref="TextureDownloadCallback"/> callback to fire when the image is retrieved. The callback
/// will contain the result of the request and the texture asset data</param>
/// <param name="progressive">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</param>
public void RequestTexture(UUID textureID, ImageType imageType, float priority, int discardLevel, uint packetStart, TextureDownloadCallback callback, bool progressive)
{
if (textureID == UUID.Zero)
return;
if (callback != null)
{
if (_Client.Assets.Cache.HasAsset(textureID))
{
ImageDownload image = new ImageDownload();
image.ID = textureID;
image.AssetData = _Client.Assets.Cache.GetCachedAssetBytes(textureID);
image.Size = image.AssetData.Length;
image.Transferred = image.AssetData.Length;
image.ImageType = imageType;
image.AssetType = AssetType.Texture;
image.Success = true;
callback(TextureRequestState.Finished, new AssetTexture(image.ID, image.AssetData));
_Client.Assets.FireImageProgressEvent(image.ID, image.Transferred, image.Size);
}
else
{
lock (_Transfers)
{
TaskInfo request;
if (_Transfers.TryGetValue(textureID, out request))
{
request.Callbacks.Add(callback);
}
else
{
request = new TaskInfo();
request.State = TextureRequestState.Pending;
request.RequestID = textureID;
request.ReportProgress = progressive;
request.RequestSlot = -1;
request.Type = imageType;
request.Callbacks = new List<TextureDownloadCallback>();
request.Callbacks.Add(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);
}
}
}
}
}
/// <summary>
/// Sends the actual request packet to the simulator
/// </summary>
/// <param name="imageID">The image to download</param>
/// <param name="type">Type of the image to download, either a baked
/// avatar texture or a normal texture</param>
/// <param name="priority">Priority level of the download. Default is
/// <c>1,013,000.0f</c></param>
/// <param name="discardLevel">Number of quality layers to discard.
/// This controls the end marker of the data sent</param>
/// <param name="packetNum">Packet number to start the download at.
/// This controls the start marker of the data sent</param>
/// <remarks>Sending a priority of 0 and a discardlevel of -1 aborts
/// download</remarks>
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
{
TaskInfo task;
if (TryGetTransferValue(imageID, out task))
{
if (task.Transfer.Simulator != null)
{
// Already downloading, just updating the priority
float percentComplete = ((float)task.Transfer.Transferred / (float)task.Transfer.Size) * 100f;
if (Single.IsNaN(percentComplete))
percentComplete = 0f;
if (percentComplete > 0f)
Logger.DebugLog(String.Format("Updating priority on image transfer {0} to {1}, {2}% complete",
imageID, task.Transfer.Priority, Math.Round(percentComplete, 2)));
}
else
{
ImageDownload transfer = task.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
{
Logger.Log("Received texture download request for a texture that isn't in the download queue: " + imageID, Helpers.LogLevel.Warning);
}
}
}
/// <summary>
/// Cancel a pending or in process texture request
/// </summary>
/// <param name="textureID">The texture assets unique ID</param>
public void AbortTextureRequest(UUID textureID)
{
TaskInfo task;
if (TryGetTransferValue(textureID, out task))
{
// this means we've actually got the request assigned to the threadpool
if (task.State == TextureRequestState.Progress)
{
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);
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes));
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
resetEvents[task.RequestSlot].Set();
RemoveTransfer(textureID);
}
else
{
RemoveTransfer(textureID);
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes));
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
}
}
}
/// <summary>
/// Master Download Thread, Queues up downloads in the threadpool
/// </summary>
private void DownloadThread()
{
int slot;
while (_Running)
{
// find free slots
int pending = 0;
int active = 0;
TaskInfo nextTask = null;
lock (_Transfers)
{
foreach (KeyValuePair<UUID, TaskInfo> request in _Transfers)
{
if (request.Value.State == TextureRequestState.Pending)
{
nextTask = request.Value;
++pending;
}
else if (request.Value.State == TextureRequestState.Progress)
{
++active;
}
}
}
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;
//Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", slot));
WorkPool.QueueUserWorkItem(TextureRequestDoWork, nextTask);
continue;
}
}
// 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);
}
/// <summary>
/// The worker thread that sends the request and handles timeouts
/// </summary>
/// <param name="threadContext">A <see cref="TaskInfo"/> object containing the request details</param>
private void TextureRequestDoWork(Object threadContext)
{
TaskInfo task = (TaskInfo)threadContext;
task.State = TextureRequestState.Progress;
#if DEBUG_TIMING
task.NetworkTime = DateTime.UtcNow;
#endif
// Find the first missing packet in the download
ushort packet = 0;
lock (task.Transfer) if (task.Transfer.PacketsSeen != null && task.Transfer.PacketsSeen.Count > 0)
packet = GetFirstMissingPacket(task.Transfer.PacketsSeen);
// Request the texture
RequestImage(task.RequestID, task.Type, task.Transfer.Priority, task.Transfer.DiscardLevel, packet);
// Set starting time
task.Transfer.TimeSinceLastPacket = 0;
// Don't release this worker slot until texture is downloaded or timeout occurs
if (!resetEvents[task.RequestSlot].WaitOne())
{
// Timed out
Logger.Log("Worker " + task.RequestSlot + " timeout waiting for texture " + task.RequestID + " to download got " +
task.Transfer.Transferred + " of " + task.Transfer.Size, Helpers.LogLevel.Warning);
AssetTexture texture = new AssetTexture(task.RequestID, task.Transfer.AssetData);
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Timeout, texture);
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
RemoveTransfer(task.RequestID);
}
// Free up this download slot
lock (lockerObject)
threadpoolSlots[task.RequestSlot] = -1;
}
private ushort GetFirstMissingPacket(SortedList<ushort, ushort> packetsSeen)
{
ushort packet = 0;
lock (packetsSeen)
{
bool first = true;
foreach (KeyValuePair<ushort, ushort> packetSeen in 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;
}
return packet;
}
#region Raw Packet Handlers
/// <summary>
/// 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
/// </summary>
/// <param name="sender">The sender</param>
/// <param name="e">The EventArgs object containing the packet data</param>
protected void ImageNotInDatabaseHandler(object sender, PacketReceivedEventArgs e)
{
ImageNotInDatabasePacket imageNotFoundData = (ImageNotInDatabasePacket)e.Packet;
TaskInfo task;
if (TryGetTransferValue(imageNotFoundData.ImageID.ID, out task))
{
// cancel acive request and free up the threadpool slot
if (task.State == TextureRequestState.Progress)
resetEvents[task.RequestSlot].Set();
// fire callback to inform the caller
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.NotFound, new AssetTexture(imageNotFoundData.ImageID.ID, Utils.EmptyBytes));
resetEvents[task.RequestSlot].Set();
RemoveTransfer(imageNotFoundData.ImageID.ID);
}
else
{
Logger.Log("Received an ImageNotFound packet for an image we did not request: " + imageNotFoundData.ImageID.ID, Helpers.LogLevel.Warning);
}
}
/// <summary>
/// Handles the remaining Image data that did not fit in the initial ImageData packet
/// </summary>
/// <param name="sender">The sender</param>
/// <param name="e">The EventArgs object containing the packet data</param>
protected void ImagePacketHandler(object sender, PacketReceivedEventArgs e)
{
ImagePacketPacket image = (ImagePacketPacket)e.Packet;
TaskInfo task;
if (TryGetTransferValue(image.ImageID.ID, out task))
{
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);
RemoveTransfer(task.Transfer.ID);
resetEvents[task.RequestSlot].Set(); // free up request slot
foreach (TextureDownloadCallback callback in task.Callbacks)
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)
{
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;
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;
RemoveTransfer(task.Transfer.ID);
resetEvents[task.RequestSlot].Set(); // free up request slot
_Client.Assets.Cache.SaveAssetToCache(task.RequestID, task.Transfer.AssetData);
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Finished, new AssetTexture(task.RequestID, task.Transfer.AssetData));
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
}
else
{
if (task.ReportProgress)
{
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Progress,
new AssetTexture(task.RequestID, task.Transfer.AssetData));
}
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred,
task.Transfer.Size);
}
}
}
/// <summary>
/// Handle the initial ImageDataPacket sent from the simulator
/// </summary>
/// <param name="sender">The sender</param>
/// <param name="e">The EventArgs object containing the packet data</param>
protected void ImageDataHandler(object sender, PacketReceivedEventArgs e)
{
ImageDataPacket data = (ImageDataPacket)e.Packet;
TaskInfo task;
if (TryGetTransferValue(data.ImageID.ID, out task))
{
// reset the timeout interval since we got data
task.Transfer.TimeSinceLastPacket = 0;
lock (task.Transfer) 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<ushort, ushort>();
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;
RemoveTransfer(task.RequestID);
resetEvents[task.RequestSlot].Set();
_Client.Assets.Cache.SaveAssetToCache(task.RequestID, task.Transfer.AssetData);
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Finished, new AssetTexture(task.RequestID, task.Transfer.AssetData));
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
}
else
{
if (task.ReportProgress)
{
foreach (TextureDownloadCallback callback in task.Callbacks)
callback(TextureRequestState.Progress,
new AssetTexture(task.RequestID, task.Transfer.AssetData));
}
_Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred,
task.Transfer.Size);
}
}
}
#endregion
private bool TryGetTransferValue(UUID textureID, out TaskInfo task)
{
lock (_Transfers)
return _Transfers.TryGetValue(textureID, out task);
}
private bool RemoveTransfer(UUID textureID)
{
lock (_Transfers)
return _Transfers.Remove(textureID);
}
}
}