2008-12-17 03:49:42 +00:00
/ *
2009-05-07 16:10:52 +00:00
* Copyright ( c ) 2009 , openmetaverse . org
2008-12-17 03:49:42 +00:00
* 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 .
* /
2009-05-09 00:10:10 +00:00
//#define DEBUG_TIMING
2009-05-07 16:10:52 +00:00
2008-12-17 03:49:42 +00:00
using System ;
using System.Collections.Generic ;
using System.Threading ;
2009-05-07 16:10:52 +00:00
using OpenMetaverse.Packets ;
2008-12-17 03:49:42 +00:00
namespace OpenMetaverse
{
/// <summary>
2009-05-07 16:10:52 +00:00
/// 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.
2008-12-17 03:49:42 +00:00
/// </summary>
2009-05-07 16:10:52 +00:00
/// <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>
2008-12-17 03:49:42 +00:00
public class TexturePipeline
{
2009-05-07 16:10:52 +00:00
#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
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
/// <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>
2008-12-17 03:49:42 +00:00
public UUID RequestID ;
2009-05-07 16:10:52 +00:00
/// <summary>The slot this request is occupying in the threadpoolSlots array</summary>
public int RequestSlot ;
/// <summary>The ImageType of the request.</summary>
2008-12-17 03:49:42 +00:00
public ImageType Type ;
2009-05-09 00:06:10 +00:00
2009-05-07 16:10:52 +00:00
/// <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>
2009-05-09 00:06:10 +00:00
public List < TextureDownloadCallback > Callbacks ;
2009-05-07 16:10:52 +00:00
/// <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 ;
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
/// <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 readonly Thread downloadMaster ;
/// <summary>true if the TexturePipeline is currently running</summary>
bool _Running ;
/// <summary>A synchronization object used by the primary thread</summary>
private static object lockerObject = new object ( ) ;
/// <summary>A refresh timer used to increase the priority of stalled requests</summary>
private readonly System . Timers . Timer RefreshDownloadsTimer =
new System . Timers . Timer ( Settings . PIPELINE_REFRESH_INTERVAL ) ;
2008-12-17 03:49:42 +00:00
2009-05-08 06:57:39 +00:00
/// <summary>Current number of pending and in-process transfers</summary>
public int TransferCount { get { return _Transfers . Count ; } }
2008-12-17 03:49:42 +00:00
/// <summary>
2009-05-07 16:10:52 +00:00
/// Default constructor, Instantiates a new copy of the TexturePipeline class
2008-12-17 03:49:42 +00:00
/// </summary>
2009-05-07 16:10:52 +00:00
/// <param name="client">Reference to the instantiated <see cref="GridClient"/> object</param>
public TexturePipeline ( GridClient client )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
_Client = client ;
2009-05-08 21:26:14 +00:00
2009-05-07 16:10:52 +00:00
maxTextureRequests = client . Settings . MAX_CONCURRENT_TEXTURE_DOWNLOADS ;
2008-12-17 03:49:42 +00:00
resetEvents = new AutoResetEvent [ maxTextureRequests ] ;
threadpoolSlots = new int [ maxTextureRequests ] ;
2009-05-07 16:10:52 +00:00
_Transfers = new Dictionary < UUID , TaskInfo > ( ) ;
// Pre-configure autoreset events and threadpool slots
2008-12-17 03:49:42 +00:00
for ( int i = 0 ; i < maxTextureRequests ; i + + )
{
2009-05-07 16:10:52 +00:00
resetEvents [ i ] = new AutoResetEvent ( true ) ;
2008-12-17 03:49:42 +00:00
threadpoolSlots [ i ] = - 1 ;
}
2009-05-07 16:10:52 +00:00
// Handle client connected and disconnected events
client . Network . OnConnected + = delegate { Startup ( ) ; } ;
client . Network . OnDisconnected + = delegate { Shutdown ( ) ; } ;
// Instantiate master thread that manages the request pool
downloadMaster = new Thread ( DownloadThread ) ;
downloadMaster . Name = "TexturePipeline" ;
downloadMaster . IsBackground = true ;
RefreshDownloadsTimer . Elapsed + = RefreshDownloadsTimer_Elapsed ;
}
/// <summary>
/// Initialize callbacks required for the TexturePipeline to operate
/// </summary>
private void Startup ( )
{
if ( _Running )
return ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
_Client . Network . RegisterCallback ( PacketType . ImageData , ImageDataHandler ) ;
_Client . Network . RegisterCallback ( PacketType . ImagePacket , ImagePacketHandler ) ;
_Client . Network . RegisterCallback ( PacketType . ImageNotInDatabase , ImageNotInDatabaseHandler ) ;
2008-12-17 03:49:42 +00:00
downloadMaster . Start ( ) ;
2009-05-07 16:10:52 +00:00
RefreshDownloadsTimer . Start ( ) ;
_Running = true ;
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
/// <summary>
/// Shutdown the TexturePipeline and cleanup any callbacks or transfers
/// </summary>
private void Shutdown ( )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
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 ( ) ;
2008-12-17 03:49:42 +00:00
for ( int i = 0 ; i < resetEvents . Length ; i + + )
if ( resetEvents [ i ] ! = null )
resetEvents [ i ] . Set ( ) ;
2009-05-07 16:10:52 +00:00
_Running = false ;
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
private void RefreshDownloadsTimer_Elapsed ( object sender , System . Timers . ElapsedEventArgs e )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
lock ( _Transfers )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
foreach ( TaskInfo transfer in _Transfers . Values )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( transfer . State = = TextureRequestState . Progress )
{
ImageDownload download = transfer . Transfer ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
uint packet = 0 ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
if ( download . PacketsSeen ! = null & & download . PacketsSeen . Count > 0 )
{
lock ( download . PacketsSeen )
{
bool first = true ;
foreach ( KeyValuePair < ushort , ushort > 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 ) ;
}
}
}
2008-12-17 03:49:42 +00:00
}
}
/// <summary>
2009-05-07 16:10:52 +00:00
/// 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
2008-12-17 03:49:42 +00:00
/// </summary>
2009-05-07 16:10:52 +00:00
/// <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 )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( textureID = = UUID . Zero )
return ;
if ( callback ! = null )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( _Client . Assets . Cache . HasImage ( textureID ) )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
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 ;
2009-05-09 00:06:10 +00:00
callback ( TextureRequestState . Finished , new AssetTexture ( image . ID , image . AssetData ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( image . ID , image . Transferred , image . Size ) ;
2008-12-17 03:49:42 +00:00
}
else
{
2009-05-07 16:10:52 +00:00
lock ( _Transfers )
{
2009-05-09 00:06:10 +00:00
if ( _Transfers . ContainsKey ( textureID ) )
{
TaskInfo request = _Transfers [ textureID ] ;
request . Callbacks . Add ( callback ) ;
}
else
2009-05-07 16:10:52 +00:00
{
TaskInfo request = new TaskInfo ( ) ;
request . State = TextureRequestState . Pending ;
request . RequestID = textureID ;
request . ReportProgress = progressive ;
request . RequestSlot = - 1 ;
request . Type = imageType ;
2009-05-09 00:06:10 +00:00
request . Callbacks = new List < TextureDownloadCallback > ( ) ;
request . Callbacks . Add ( callback ) ;
2009-05-07 16:10:52 +00:00
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 ) ;
}
}
2008-12-17 03:49:42 +00:00
}
}
}
/// <summary>
2009-05-07 16:10:52 +00:00
/// Sends the actual request packet to the simulator
2008-12-17 03:49:42 +00:00
/// </summary>
2009-05-07 16:10:52 +00:00
/// <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 )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
// Priority == 0 && DiscardLevel == -1 means cancel the transfer
if ( priority . Equals ( 0 ) & & discardLevel . Equals ( - 1 ) )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
AbortTextureRequest ( imageID ) ;
}
else
{
lock ( _Transfers )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
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 ;
}
2008-12-19 03:39:29 +00:00
2009-05-07 16:10:52 +00:00
// 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
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
Logger . Log ( "Received texture download request for a texture that isn't in the download queue: " + imageID , Helpers . LogLevel . Warning ) ;
2008-12-19 03:39:29 +00:00
}
}
2009-05-07 16:10:52 +00:00
}
}
2008-12-19 03:39:29 +00:00
2009-05-07 16:10:52 +00:00
/// <summary>
/// Cancel a pending or in process texture request
/// </summary>
/// <param name="textureID">The texture assets unique ID</param>
public void AbortTextureRequest ( UUID textureID )
{
lock ( _Transfers )
{
if ( _Transfers . ContainsKey ( textureID ) )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
TaskInfo task = _Transfers [ textureID ] ;
// 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 ) ;
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Aborted , new AssetTexture ( textureID , Utils . EmptyBytes ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2008-12-19 03:39:29 +00:00
2009-05-07 16:10:52 +00:00
resetEvents [ task . RequestSlot ] . Set ( ) ;
_Transfers . Remove ( textureID ) ;
}
else
{
_Transfers . Remove ( textureID ) ;
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Aborted , new AssetTexture ( textureID , Utils . EmptyBytes ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2009-05-07 16:10:52 +00:00
}
2008-12-19 03:39:29 +00:00
}
2008-12-17 03:49:42 +00:00
}
}
/// <summary>
/// Master Download Thread, Queues up downloads in the threadpool
/// </summary>
private void DownloadThread ( )
{
2009-05-07 16:10:52 +00:00
int slot ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
while ( _Running )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
// find free slots
int pending = 0 ;
int active = 0 ;
TaskInfo nextTask = null ;
lock ( _Transfers )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
foreach ( UUID request in _Transfers . Keys )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( _Transfers [ request ] . State = = TextureRequestState . Pending )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
nextTask = _Transfers [ request ] ;
pending + + ;
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
if ( _Transfers [ request ] . State = = TextureRequestState . Progress )
active + + ;
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
if ( pending > 0 & & active < = maxTextureRequests )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
slot = - 1 ;
// find available slot for reset event
lock ( lockerObject )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
for ( int i = 0 ; i < threadpoolSlots . Length ; i + + )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
if ( threadpoolSlots [ i ] = = - 1 )
{
// found a free slot
threadpoolSlots [ i ] = 1 ;
slot = i ;
break ;
}
2008-12-19 03:39:29 +00:00
}
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
// -1 = slot not available
if ( slot ! = - 1 & & nextTask ! = null )
2008-12-19 03:39:29 +00:00
{
2009-05-07 16:10:52 +00:00
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 ;
2008-12-19 03:39:29 +00:00
}
2008-12-17 03:49:42 +00:00
}
2009-05-07 19:00:43 +00:00
2009-05-07 16:10:52 +00:00
// Queue was empty or all download slots are inuse, let's give up some CPU time
2008-12-17 03:49:42 +00:00
Thread . Sleep ( 500 ) ;
}
Logger . Log ( "Texture pipeline shutting down" , Helpers . LogLevel . Info ) ;
}
2009-05-07 16:10:52 +00:00
/// <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>
2008-12-17 03:49:42 +00:00
private void TextureRequestDoWork ( Object threadContext )
{
2009-05-07 16:10:52 +00:00
TaskInfo task = ( TaskInfo ) threadContext ;
task . State = TextureRequestState . Progress ;
#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 [ task . RequestSlot ] . WaitOne ( _Client . Settings . PIPELINE_REQUEST_TIMEOUT , false ) )
{
// 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 ) ;
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Timeout , new AssetTexture ( task . RequestID , task . Transfer . AssetData ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2009-05-07 16:10:52 +00:00
lock ( _Transfers )
_Transfers . Remove ( task . RequestID ) ;
}
// free up this download slot
lock ( lockerObject )
threadpoolSlots [ task . RequestSlot ] = - 1 ;
}
#region Raw Packet Handlers
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
/// <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="packet">The <see cref="ImageNotInDatabasePacket"/></param>
/// <param name="simulator">The <see cref="Simulator"/> sending this packet</param>
private void ImageNotInDatabaseHandler ( Packet packet , Simulator simulator )
{
ImageNotInDatabasePacket imageNotFoundData = ( ImageNotInDatabasePacket ) packet ;
lock ( _Transfers )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( _Transfers . ContainsKey ( imageNotFoundData . ImageID . ID ) )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
// 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
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . NotFound , new AssetTexture ( imageNotFoundData . ImageID . ID , Utils . EmptyBytes ) ) ;
2009-05-07 16:10:52 +00:00
resetEvents [ task . RequestSlot ] . Set ( ) ;
_Transfers . Remove ( imageNotFoundData . ImageID . ID ) ;
}
2008-12-17 03:49:42 +00:00
else
{
2009-05-07 16:10:52 +00:00
Logger . Log ( "Received an ImageNotFound packet for an image we did not request: " + imageNotFoundData . ImageID . ID , Helpers . LogLevel . Warning ) ;
2008-12-17 03:49:42 +00:00
}
}
2009-05-07 16:10:52 +00:00
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
/// <summary>
/// Handles the remaining Image data that did not fit in the initial ImageData packet
/// </summary>
private void ImagePacketHandler ( Packet packet , Simulator simulator )
{
ImagePacketPacket image = ( ImagePacketPacket ) packet ;
TaskInfo task = null ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
lock ( _Transfers )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
if ( _Transfers . ContainsKey ( image . ImageID . ID ) )
{
task = _Transfers [ image . ImageID . ID ] ;
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
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 ) ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
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
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Timeout , new AssetTexture ( task . RequestID , task . Transfer . AssetData ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2009-05-09 00:06:10 +00:00
2009-05-07 16:10:52 +00:00
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 ) ;
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Finished , new AssetTexture ( task . RequestID , task . Transfer . AssetData ) ) ;
2009-05-07 16:10:52 +00:00
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2009-05-07 16:10:52 +00:00
}
else
{
if ( task . ReportProgress )
2009-05-09 00:06:10 +00:00
{
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 ) ;
}
2009-05-07 16:10:52 +00:00
}
}
}
2008-12-17 03:49:42 +00:00
}
2009-05-07 16:10:52 +00:00
/// <summary>
/// Handle the initial ImageDataPacket sent from the simulator
/// </summary>
/// <param name="packet"></param>
/// <param name="simulator"></param>
private void ImageDataHandler ( Packet packet , Simulator simulator )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
ImageDataPacket data = ( ImageDataPacket ) packet ;
TaskInfo task = null ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
lock ( _Transfers )
{
if ( _Transfers . ContainsKey ( data . ImageID . ID ) )
{
task = _Transfers [ data . ImageID . ID ] ;
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
if ( task ! = null )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
// reset the timeout interval since we got data
resetEvents [ task . RequestSlot ] . Reset ( ) ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
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 ;
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
task . Transfer . HeaderReceivedEvent . Set ( ) ;
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
if ( task . Transfer . Transferred > = task . Transfer . Size )
2008-12-17 03:49:42 +00:00
{
2009-05-07 16:10:52 +00:00
#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 ) ;
2009-05-09 00:06:10 +00:00
foreach ( TextureDownloadCallback callback in task . Callbacks )
callback ( TextureRequestState . Finished , new AssetTexture ( task . RequestID , task . Transfer . AssetData ) ) ;
2009-05-08 21:26:14 +00:00
_Client . Assets . FireImageProgressEvent ( task . RequestID , task . Transfer . Transferred , task . Transfer . Size ) ;
2008-12-17 03:49:42 +00:00
}
else
{
2009-05-07 16:10:52 +00:00
if ( task . ReportProgress )
2009-05-09 00:06:10 +00:00
{
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 ) ;
}
2008-12-17 03:49:42 +00:00
}
}
}
2009-05-07 16:10:52 +00:00
}
2008-12-17 03:49:42 +00:00
2009-05-07 16:10:52 +00:00
#endregion
2008-12-17 03:49:42 +00:00
}
}