corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
4 *
5 * - Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 //#define DEBUG_TIMING
28  
29 using System;
30 using System.Collections.Generic;
31 using System.Threading;
32 using OpenMetaverse.Packets;
33 using OpenMetaverse.Assets;
34  
35 namespace OpenMetaverse
36 {
37 /// <summary>
38 /// The current status of a texture request as it moves through the pipeline or final result of a texture request.
39 /// </summary>
40 public enum TextureRequestState
41 {
42 /// <summary>The initial state given to a request. Requests in this state
43 /// are waiting for an available slot in the pipeline</summary>
44 Pending,
45 /// <summary>A request that has been added to the pipeline and the request packet
46 /// has been sent to the simulator</summary>
47 Started,
48 /// <summary>A request that has received one or more packets back from the simulator</summary>
49 Progress,
50 /// <summary>A request that has received all packets back from the simulator</summary>
51 Finished,
52 /// <summary>A request that has taken longer than <seealso cref="Settings.PIPELINE_REQUEST_TIMEOUT"/>
53 /// to download OR the initial packet containing the packet information was never received</summary>
54 Timeout,
55 /// <summary>The texture request was aborted by request of the agent</summary>
56 Aborted,
57 /// <summary>The simulator replied to the request that it was not able to find the requested texture</summary>
58 NotFound
59 }
60 /// <summary>
61 /// A callback fired to indicate the status or final state of the requested texture. For progressive
62 /// downloads this will fire each time new asset data is returned from the simulator.
63 /// </summary>
64 /// <param name="state">The <see cref="TextureRequestState"/> indicating either Progress for textures not fully downloaded,
65 /// or the final result of the request after it has been processed through the TexturePipeline</param>
66 /// <param name="assetTexture">The <see cref="AssetTexture"/> object containing the Assets ID, raw data
67 /// and other information. For progressive rendering the <see cref="Asset.AssetData"/> will contain
68 /// the data from the beginning of the file. For failed, aborted and timed out requests it will contain
69 /// an empty byte array.</param>
70 public delegate void TextureDownloadCallback(TextureRequestState state, AssetTexture assetTexture);
71  
72 /// <summary>
73 /// Texture request download handler, allows a configurable number of download slots which manage multiple
74 /// concurrent texture downloads from the <seealso cref="Simulator"/>
75 /// </summary>
76 /// <remarks>This class makes full use of the internal <seealso cref="TextureCache"/>
77 /// system for full texture downloads.</remarks>
78 public class TexturePipeline
79 {
80 #if DEBUG_TIMING // Timing globals
81 /// <summary>The combined time it has taken for all textures requested sofar. This includes the amount of time the
82 /// texture spent waiting for a download slot, and the time spent retrieving the actual texture from the Grid</summary>
83 public static TimeSpan TotalTime;
84 /// <summary>The amount of time the request spent in the <see cref="TextureRequestState.Progress"/> state</summary>
85 public static TimeSpan NetworkTime;
86 /// <summary>The total number of bytes transferred since the TexturePipeline was started</summary>
87 public static int TotalBytes;
88 #endif
89 /// <summary>
90 /// A request task containing information and status of a request as it is processed through the <see cref="TexturePipeline"/>
91 /// </summary>
92 private class TaskInfo
93 {
94 /// <summary>The current <seealso cref="TextureRequestState"/> which identifies the current status of the request</summary>
95 public TextureRequestState State;
96 /// <summary>The Unique Request ID, This is also the Asset ID of the texture being requested</summary>
97 public UUID RequestID;
98 /// <summary>The slot this request is occupying in the threadpoolSlots array</summary>
99 public int RequestSlot;
100 /// <summary>The ImageType of the request.</summary>
101 public ImageType Type;
102  
103 /// <summary>The callback to fire when the request is complete, will include
104 /// the <seealso cref="TextureRequestState"/> and the <see cref="AssetTexture"/>
105 /// object containing the result data</summary>
106 public List<TextureDownloadCallback> Callbacks;
107 /// <summary>If true, indicates the callback will be fired whenever new data is returned from the simulator.
108 /// This is used to progressively render textures as portions of the texture are received.</summary>
109 public bool ReportProgress;
110 #if DEBUG_TIMING
111 /// <summary>The time the request was added to the the PipeLine</summary>
112 public DateTime StartTime;
113 /// <summary>The time the request was sent to the simulator</summary>
114 public DateTime NetworkTime;
115 #endif
116 /// <summary>An object that maintains the data of an request thats in-process.</summary>
117 public ImageDownload Transfer;
118 }
119  
120 /// <summary>A dictionary containing all pending and in-process transfer requests where the Key is both the RequestID
121 /// and also the Asset Texture ID, and the value is an object containing the current state of the request and also
122 /// the asset data as it is being re-assembled</summary>
123 private readonly Dictionary<UUID, TaskInfo> _Transfers;
124 /// <summary>Holds the reference to the <see cref="GridClient"/> client object</summary>
125 private readonly GridClient _Client;
126 /// <summary>Maximum concurrent texture requests allowed at a time</summary>
127 private readonly int maxTextureRequests;
128 /// <summary>An array of <see cref="AutoResetEvent"/> objects used to manage worker request threads</summary>
129 private readonly AutoResetEvent[] resetEvents;
130 /// <summary>An array of worker slots which shows the availablity status of the slot</summary>
131 private readonly int[] threadpoolSlots;
132 /// <summary>The primary thread which manages the requests.</summary>
133 private Thread downloadMaster;
134 /// <summary>true if the TexturePipeline is currently running</summary>
135 bool _Running;
136 /// <summary>A synchronization object used by the primary thread</summary>
137 private object lockerObject = new object();
138 /// <summary>A refresh timer used to increase the priority of stalled requests</summary>
139 private System.Timers.Timer RefreshDownloadsTimer;
140  
141 /// <summary>Current number of pending and in-process transfers</summary>
142 public int TransferCount { get { return _Transfers.Count; } }
143  
144 /// <summary>
145 /// Default constructor, Instantiates a new copy of the TexturePipeline class
146 /// </summary>
147 /// <param name="client">Reference to the instantiated <see cref="GridClient"/> object</param>
148 public TexturePipeline(GridClient client)
149 {
150 _Client = client;
151  
152 maxTextureRequests = client.Settings.MAX_CONCURRENT_TEXTURE_DOWNLOADS;
153  
154 resetEvents = new AutoResetEvent[maxTextureRequests];
155 threadpoolSlots = new int[maxTextureRequests];
156  
157 _Transfers = new Dictionary<UUID, TaskInfo>();
158  
159 // Pre-configure autoreset events and threadpool slots
160 for (int i = 0; i < maxTextureRequests; i++)
161 {
162 resetEvents[i] = new AutoResetEvent(true);
163 threadpoolSlots[i] = -1;
164 }
165  
166 // Handle client connected and disconnected events
167 client.Network.LoginProgress += delegate(object sender, LoginProgressEventArgs e) {
168 if (e.Status == LoginStatus.Success)
169 {
170 Startup();
171 }
172 };
173  
174 client.Network.Disconnected += delegate { Shutdown(); };
175 }
176  
177 /// <summary>
178 /// Initialize callbacks required for the TexturePipeline to operate
179 /// </summary>
180 public void Startup()
181 {
182 if (_Running)
183 return;
184  
185 if (downloadMaster == null)
186 {
187 // Instantiate master thread that manages the request pool
188 downloadMaster = new Thread(DownloadThread);
189 downloadMaster.Name = "TexturePipeline";
190 downloadMaster.IsBackground = true;
191 }
192  
193 _Running = true;
194  
195 _Client.Network.RegisterCallback(PacketType.ImageData, ImageDataHandler);
196 _Client.Network.RegisterCallback(PacketType.ImagePacket, ImagePacketHandler);
197 _Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, ImageNotInDatabaseHandler);
198 downloadMaster.Start();
199 if (RefreshDownloadsTimer == null)
200 {
201 RefreshDownloadsTimer = new System.Timers.Timer(Settings.PIPELINE_REFRESH_INTERVAL);
202 RefreshDownloadsTimer.Elapsed += RefreshDownloadsTimer_Elapsed;
203 RefreshDownloadsTimer.Start();
204 }
205 }
206  
207 /// <summary>
208 /// Shutdown the TexturePipeline and cleanup any callbacks or transfers
209 /// </summary>
210 public void Shutdown()
211 {
212 if (!_Running)
213 return;
214 #if DEBUG_TIMING
215 Logger.Log(String.Format("Combined Execution Time: {0}, Network Execution Time {1}, Network {2}K/sec, Image Size {3}",
216 TotalTime, NetworkTime, Math.Round(TotalBytes / NetworkTime.TotalSeconds / 60, 2), TotalBytes), Helpers.LogLevel.Debug);
217 #endif
218 if(null != RefreshDownloadsTimer) RefreshDownloadsTimer.Dispose();
219 RefreshDownloadsTimer = null;
220  
221 if (downloadMaster != null && downloadMaster.IsAlive)
222 {
223 downloadMaster.Abort();
224 }
225 downloadMaster = null;
226  
227 _Client.Network.UnregisterCallback(PacketType.ImageNotInDatabase, ImageNotInDatabaseHandler);
228 _Client.Network.UnregisterCallback(PacketType.ImageData, ImageDataHandler);
229 _Client.Network.UnregisterCallback(PacketType.ImagePacket, ImagePacketHandler);
230  
231 lock (_Transfers)
232 _Transfers.Clear();
233  
234 for (int i = 0; i < resetEvents.Length; i++)
235 if (resetEvents[i] != null)
236 resetEvents[i].Set();
237  
238 _Running = false;
239 }
240  
241 private void RefreshDownloadsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
242 {
243 lock (_Transfers)
244 {
245 foreach (TaskInfo transfer in _Transfers.Values)
246 {
247 if (transfer.State == TextureRequestState.Progress)
248 {
249 ImageDownload download = transfer.Transfer;
250  
251 // Find the first missing packet in the download
252 ushort packet = 0;
253 lock (download) if (download.PacketsSeen != null && download.PacketsSeen.Count > 0)
254 packet = GetFirstMissingPacket(download.PacketsSeen);
255  
256 if (download.TimeSinceLastPacket > 5000)
257 {
258 // We're not receiving data for this texture fast enough, bump up the priority by 5%
259 download.Priority *= 1.05f;
260  
261 download.TimeSinceLastPacket = 0;
262 RequestImage(download.ID, download.ImageType, download.Priority, download.DiscardLevel, packet);
263 }
264 if (download.TimeSinceLastPacket > _Client.Settings.PIPELINE_REQUEST_TIMEOUT)
265 {
266 resetEvents[transfer.RequestSlot].Set();
267 }
268 }
269 }
270 }
271 }
272  
273 /// <summary>
274 /// Request a texture asset from the simulator using the <see cref="TexturePipeline"/> system to
275 /// manage the requests and re-assemble the image from the packets received from the simulator
276 /// </summary>
277 /// <param name="textureID">The <see cref="UUID"/> of the texture asset to download</param>
278 /// <param name="imageType">The <see cref="ImageType"/> of the texture asset.
279 /// Use <see cref="ImageType.Normal"/> for most textures, or <see cref="ImageType.Baked"/> for baked layer texture assets</param>
280 /// <param name="priority">A float indicating the requested priority for the transfer. Higher priority values tell the simulator
281 /// to prioritize the request before lower valued requests. An image already being transferred using the <see cref="TexturePipeline"/> can have
282 /// its priority changed by resending the request with the new priority value</param>
283 /// <param name="discardLevel">Number of quality layers to discard.
284 /// This controls the end marker of the data sent</param>
285 /// <param name="packetStart">The packet number to begin the request at. A value of 0 begins the request
286 /// from the start of the asset texture</param>
287 /// <param name="callback">The <see cref="TextureDownloadCallback"/> callback to fire when the image is retrieved. The callback
288 /// will contain the result of the request and the texture asset data</param>
289 /// <param name="progressive">If true, the callback will be fired for each chunk of the downloaded image.
290 /// The callback asset parameter will contain all previously received chunks of the texture asset starting
291 /// from the beginning of the request</param>
292 public void RequestTexture(UUID textureID, ImageType imageType, float priority, int discardLevel, uint packetStart, TextureDownloadCallback callback, bool progressive)
293 {
294 if (textureID == UUID.Zero)
295 return;
296  
297 if (callback != null)
298 {
299 if (_Client.Assets.Cache.HasAsset(textureID))
300 {
301 ImageDownload image = new ImageDownload();
302 image.ID = textureID;
303 image.AssetData = _Client.Assets.Cache.GetCachedAssetBytes(textureID);
304 image.Size = image.AssetData.Length;
305 image.Transferred = image.AssetData.Length;
306 image.ImageType = imageType;
307 image.AssetType = AssetType.Texture;
308 image.Success = true;
309  
310 callback(TextureRequestState.Finished, new AssetTexture(image.ID, image.AssetData));
311  
312 _Client.Assets.FireImageProgressEvent(image.ID, image.Transferred, image.Size);
313 }
314 else
315 {
316 lock (_Transfers)
317 {
318 TaskInfo request;
319  
320 if (_Transfers.TryGetValue(textureID, out request))
321 {
322 request.Callbacks.Add(callback);
323 }
324 else
325 {
326 request = new TaskInfo();
327 request.State = TextureRequestState.Pending;
328 request.RequestID = textureID;
329 request.ReportProgress = progressive;
330 request.RequestSlot = -1;
331 request.Type = imageType;
332  
333 request.Callbacks = new List<TextureDownloadCallback>();
334 request.Callbacks.Add(callback);
335  
336 ImageDownload downloadParams = new ImageDownload();
337 downloadParams.ID = textureID;
338 downloadParams.Priority = priority;
339 downloadParams.ImageType = imageType;
340 downloadParams.DiscardLevel = discardLevel;
341  
342 request.Transfer = downloadParams;
343 #if DEBUG_TIMING
344 request.StartTime = DateTime.UtcNow;
345 #endif
346 _Transfers.Add(textureID, request);
347 }
348 }
349 }
350 }
351 }
352  
353 /// <summary>
354 /// Sends the actual request packet to the simulator
355 /// </summary>
356 /// <param name="imageID">The image to download</param>
357 /// <param name="type">Type of the image to download, either a baked
358 /// avatar texture or a normal texture</param>
359 /// <param name="priority">Priority level of the download. Default is
360 /// <c>1,013,000.0f</c></param>
361 /// <param name="discardLevel">Number of quality layers to discard.
362 /// This controls the end marker of the data sent</param>
363 /// <param name="packetNum">Packet number to start the download at.
364 /// This controls the start marker of the data sent</param>
365 /// <remarks>Sending a priority of 0 and a discardlevel of -1 aborts
366 /// download</remarks>
367 private void RequestImage(UUID imageID, ImageType type, float priority, int discardLevel, uint packetNum)
368 {
369 // Priority == 0 && DiscardLevel == -1 means cancel the transfer
370 if (priority.Equals(0) && discardLevel.Equals(-1))
371 {
372 AbortTextureRequest(imageID);
373 }
374 else
375 {
376 TaskInfo task;
377 if (TryGetTransferValue(imageID, out task))
378 {
379 if (task.Transfer.Simulator != null)
380 {
381 // Already downloading, just updating the priority
382 float percentComplete = ((float)task.Transfer.Transferred / (float)task.Transfer.Size) * 100f;
383 if (Single.IsNaN(percentComplete))
384 percentComplete = 0f;
385  
386 if (percentComplete > 0f)
387 Logger.DebugLog(String.Format("Updating priority on image transfer {0} to {1}, {2}% complete",
388 imageID, task.Transfer.Priority, Math.Round(percentComplete, 2)));
389 }
390 else
391 {
392 ImageDownload transfer = task.Transfer;
393 transfer.Simulator = _Client.Network.CurrentSim;
394 }
395  
396 // Build and send the request packet
397 RequestImagePacket request = new RequestImagePacket();
398 request.AgentData.AgentID = _Client.Self.AgentID;
399 request.AgentData.SessionID = _Client.Self.SessionID;
400 request.RequestImage = new RequestImagePacket.RequestImageBlock[1];
401 request.RequestImage[0] = new RequestImagePacket.RequestImageBlock();
402 request.RequestImage[0].DiscardLevel = (sbyte)discardLevel;
403 request.RequestImage[0].DownloadPriority = priority;
404 request.RequestImage[0].Packet = packetNum;
405 request.RequestImage[0].Image = imageID;
406 request.RequestImage[0].Type = (byte)type;
407  
408 _Client.Network.SendPacket(request, _Client.Network.CurrentSim);
409 }
410 else
411 {
412 Logger.Log("Received texture download request for a texture that isn't in the download queue: " + imageID, Helpers.LogLevel.Warning);
413 }
414 }
415 }
416  
417 /// <summary>
418 /// Cancel a pending or in process texture request
419 /// </summary>
420 /// <param name="textureID">The texture assets unique ID</param>
421 public void AbortTextureRequest(UUID textureID)
422 {
423 TaskInfo task;
424 if (TryGetTransferValue(textureID, out task))
425 {
426 // this means we've actually got the request assigned to the threadpool
427 if (task.State == TextureRequestState.Progress)
428 {
429 RequestImagePacket request = new RequestImagePacket();
430 request.AgentData.AgentID = _Client.Self.AgentID;
431 request.AgentData.SessionID = _Client.Self.SessionID;
432 request.RequestImage = new RequestImagePacket.RequestImageBlock[1];
433 request.RequestImage[0] = new RequestImagePacket.RequestImageBlock();
434 request.RequestImage[0].DiscardLevel = -1;
435 request.RequestImage[0].DownloadPriority = 0;
436 request.RequestImage[0].Packet = 0;
437 request.RequestImage[0].Image = textureID;
438 request.RequestImage[0].Type = (byte)task.Type;
439 _Client.Network.SendPacket(request);
440  
441 foreach (TextureDownloadCallback callback in task.Callbacks)
442 callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes));
443  
444 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
445  
446 resetEvents[task.RequestSlot].Set();
447  
448 RemoveTransfer(textureID);
449 }
450 else
451 {
452 RemoveTransfer(textureID);
453  
454 foreach (TextureDownloadCallback callback in task.Callbacks)
455 callback(TextureRequestState.Aborted, new AssetTexture(textureID, Utils.EmptyBytes));
456  
457 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
458 }
459 }
460 }
461  
462 /// <summary>
463 /// Master Download Thread, Queues up downloads in the threadpool
464 /// </summary>
465 private void DownloadThread()
466 {
467 int slot;
468  
469 while (_Running)
470 {
471 // find free slots
472 int pending = 0;
473 int active = 0;
474  
475 TaskInfo nextTask = null;
476  
477 lock (_Transfers)
478 {
479 foreach (KeyValuePair<UUID, TaskInfo> request in _Transfers)
480 {
481 if (request.Value.State == TextureRequestState.Pending)
482 {
483 nextTask = request.Value;
484 ++pending;
485 }
486 else if (request.Value.State == TextureRequestState.Progress)
487 {
488 ++active;
489 }
490 }
491 }
492  
493 if (pending > 0 && active <= maxTextureRequests)
494 {
495 slot = -1;
496 // find available slot for reset event
497 lock (lockerObject)
498 {
499 for (int i = 0; i < threadpoolSlots.Length; i++)
500 {
501 if (threadpoolSlots[i] == -1)
502 {
503 // found a free slot
504 threadpoolSlots[i] = 1;
505 slot = i;
506 break;
507 }
508 }
509 }
510  
511 // -1 = slot not available
512 if (slot != -1 && nextTask != null)
513 {
514 nextTask.State = TextureRequestState.Started;
515 nextTask.RequestSlot = slot;
516  
517 //Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", slot));
518 WorkPool.QueueUserWorkItem(TextureRequestDoWork, nextTask);
519 continue;
520 }
521 }
522  
523 // Queue was empty or all download slots are inuse, let's give up some CPU time
524 Thread.Sleep(500);
525 }
526  
527 Logger.Log("Texture pipeline shutting down", Helpers.LogLevel.Info);
528 }
529  
530  
531 /// <summary>
532 /// The worker thread that sends the request and handles timeouts
533 /// </summary>
534 /// <param name="threadContext">A <see cref="TaskInfo"/> object containing the request details</param>
535 private void TextureRequestDoWork(Object threadContext)
536 {
537 TaskInfo task = (TaskInfo)threadContext;
538  
539 task.State = TextureRequestState.Progress;
540  
541 #if DEBUG_TIMING
542 task.NetworkTime = DateTime.UtcNow;
543 #endif
544 // Find the first missing packet in the download
545 ushort packet = 0;
546 lock (task.Transfer) if (task.Transfer.PacketsSeen != null && task.Transfer.PacketsSeen.Count > 0)
547 packet = GetFirstMissingPacket(task.Transfer.PacketsSeen);
548  
549 // Request the texture
550 RequestImage(task.RequestID, task.Type, task.Transfer.Priority, task.Transfer.DiscardLevel, packet);
551  
552 // Set starting time
553 task.Transfer.TimeSinceLastPacket = 0;
554  
555 // Don't release this worker slot until texture is downloaded or timeout occurs
556 if (!resetEvents[task.RequestSlot].WaitOne())
557 {
558 // Timed out
559 Logger.Log("Worker " + task.RequestSlot + " timeout waiting for texture " + task.RequestID + " to download got " +
560 task.Transfer.Transferred + " of " + task.Transfer.Size, Helpers.LogLevel.Warning);
561  
562 AssetTexture texture = new AssetTexture(task.RequestID, task.Transfer.AssetData);
563 foreach (TextureDownloadCallback callback in task.Callbacks)
564 callback(TextureRequestState.Timeout, texture);
565  
566 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
567  
568 RemoveTransfer(task.RequestID);
569 }
570  
571 // Free up this download slot
572 lock (lockerObject)
573 threadpoolSlots[task.RequestSlot] = -1;
574 }
575  
576 private ushort GetFirstMissingPacket(SortedList<ushort, ushort> packetsSeen)
577 {
578 ushort packet = 0;
579  
580 lock (packetsSeen)
581 {
582 bool first = true;
583 foreach (KeyValuePair<ushort, ushort> packetSeen in packetsSeen)
584 {
585 if (first)
586 {
587 // Initially set this to the earliest packet received in the transfer
588 packet = packetSeen.Value;
589 first = false;
590 }
591 else
592 {
593 ++packet;
594  
595 // If there is a missing packet in the list, break and request the download
596 // resume here
597 if (packetSeen.Value != packet)
598 {
599 --packet;
600 break;
601 }
602 }
603 }
604  
605 ++packet;
606 }
607  
608 return packet;
609 }
610  
611 #region Raw Packet Handlers
612  
613 /// <summary>
614 /// Handle responses from the simulator that tell us a texture we have requested is unable to be located
615 /// or no longer exists. This will remove the request from the pipeline and free up a slot if one is in use
616 /// </summary>
617 /// <param name="sender">The sender</param>
618 /// <param name="e">The EventArgs object containing the packet data</param>
619 protected void ImageNotInDatabaseHandler(object sender, PacketReceivedEventArgs e)
620 {
621 ImageNotInDatabasePacket imageNotFoundData = (ImageNotInDatabasePacket)e.Packet;
622 TaskInfo task;
623  
624 if (TryGetTransferValue(imageNotFoundData.ImageID.ID, out task))
625 {
626 // cancel acive request and free up the threadpool slot
627 if (task.State == TextureRequestState.Progress)
628 resetEvents[task.RequestSlot].Set();
629  
630 // fire callback to inform the caller
631 foreach (TextureDownloadCallback callback in task.Callbacks)
632 callback(TextureRequestState.NotFound, new AssetTexture(imageNotFoundData.ImageID.ID, Utils.EmptyBytes));
633  
634 resetEvents[task.RequestSlot].Set();
635  
636 RemoveTransfer(imageNotFoundData.ImageID.ID);
637 }
638 else
639 {
640 Logger.Log("Received an ImageNotFound packet for an image we did not request: " + imageNotFoundData.ImageID.ID, Helpers.LogLevel.Warning);
641 }
642 }
643  
644 /// <summary>
645 /// Handles the remaining Image data that did not fit in the initial ImageData packet
646 /// </summary>
647 /// <param name="sender">The sender</param>
648 /// <param name="e">The EventArgs object containing the packet data</param>
649 protected void ImagePacketHandler(object sender, PacketReceivedEventArgs e)
650 {
651 ImagePacketPacket image = (ImagePacketPacket)e.Packet;
652 TaskInfo task;
653  
654 if (TryGetTransferValue(image.ImageID.ID, out task))
655 {
656 if (task.Transfer.Size == 0)
657 {
658 // We haven't received the header yet, block until it's received or times out
659 task.Transfer.HeaderReceivedEvent.WaitOne(1000 * 5, false);
660  
661 if (task.Transfer.Size == 0)
662 {
663 Logger.Log("Timed out while waiting for the image header to download for " +
664 task.Transfer.ID, Helpers.LogLevel.Warning, _Client);
665  
666 RemoveTransfer(task.Transfer.ID);
667 resetEvents[task.RequestSlot].Set(); // free up request slot
668  
669 foreach (TextureDownloadCallback callback in task.Callbacks)
670 callback(TextureRequestState.Timeout, new AssetTexture(task.RequestID, task.Transfer.AssetData));
671  
672 return;
673 }
674 }
675  
676 // The header is downloaded, we can insert this data in to the proper position
677 // Only insert if we haven't seen this packet before
678 lock (task.Transfer)
679 {
680 if (!task.Transfer.PacketsSeen.ContainsKey(image.ImageID.Packet))
681 {
682 task.Transfer.PacketsSeen[image.ImageID.Packet] = image.ImageID.Packet;
683 Buffer.BlockCopy(image.ImageData.Data, 0, task.Transfer.AssetData,
684 task.Transfer.InitialDataSize + (1000 * (image.ImageID.Packet - 1)),
685 image.ImageData.Data.Length);
686 task.Transfer.Transferred += image.ImageData.Data.Length;
687 }
688 }
689  
690 task.Transfer.TimeSinceLastPacket = 0;
691  
692 if (task.Transfer.Transferred >= task.Transfer.Size)
693 {
694 #if DEBUG_TIMING
695 DateTime stopTime = DateTime.UtcNow;
696 TimeSpan requestDuration = stopTime - task.StartTime;
697  
698 TimeSpan networkDuration = stopTime - task.NetworkTime;
699  
700 TotalTime += requestDuration;
701 NetworkTime += networkDuration;
702 TotalBytes += task.Transfer.Size;
703  
704 Logger.Log(
705 String.Format(
706 "Transfer Complete {0} [{1}] Total Request Time: {2}, Download Time {3}, Network {4}Kb/sec, Image Size {5} bytes",
707 task.RequestID, task.RequestSlot, requestDuration, networkDuration,
708 Math.Round(task.Transfer.Size/networkDuration.TotalSeconds/60, 2), task.Transfer.Size),
709 Helpers.LogLevel.Debug);
710 #endif
711  
712 task.Transfer.Success = true;
713 RemoveTransfer(task.Transfer.ID);
714 resetEvents[task.RequestSlot].Set(); // free up request slot
715 _Client.Assets.Cache.SaveAssetToCache(task.RequestID, task.Transfer.AssetData);
716 foreach (TextureDownloadCallback callback in task.Callbacks)
717 callback(TextureRequestState.Finished, new AssetTexture(task.RequestID, task.Transfer.AssetData));
718  
719 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
720 }
721 else
722 {
723 if (task.ReportProgress)
724 {
725 foreach (TextureDownloadCallback callback in task.Callbacks)
726 callback(TextureRequestState.Progress,
727 new AssetTexture(task.RequestID, task.Transfer.AssetData));
728 }
729 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred,
730 task.Transfer.Size);
731 }
732 }
733 }
734  
735 /// <summary>
736 /// Handle the initial ImageDataPacket sent from the simulator
737 /// </summary>
738 /// <param name="sender">The sender</param>
739 /// <param name="e">The EventArgs object containing the packet data</param>
740 protected void ImageDataHandler(object sender, PacketReceivedEventArgs e)
741 {
742 ImageDataPacket data = (ImageDataPacket)e.Packet;
743 TaskInfo task;
744  
745 if (TryGetTransferValue(data.ImageID.ID, out task))
746 {
747 // reset the timeout interval since we got data
748 task.Transfer.TimeSinceLastPacket = 0;
749  
750 lock (task.Transfer) if (task.Transfer.Size == 0)
751 {
752 task.Transfer.Codec = (ImageCodec)data.ImageID.Codec;
753 task.Transfer.PacketCount = data.ImageID.Packets;
754 task.Transfer.Size = (int)data.ImageID.Size;
755 task.Transfer.AssetData = new byte[task.Transfer.Size];
756 task.Transfer.AssetType = AssetType.Texture;
757 task.Transfer.PacketsSeen = new SortedList<ushort, ushort>();
758 Buffer.BlockCopy(data.ImageData.Data, 0, task.Transfer.AssetData, 0, data.ImageData.Data.Length);
759 task.Transfer.InitialDataSize = data.ImageData.Data.Length;
760 task.Transfer.Transferred += data.ImageData.Data.Length;
761 }
762  
763 task.Transfer.HeaderReceivedEvent.Set();
764  
765 if (task.Transfer.Transferred >= task.Transfer.Size)
766 {
767 #if DEBUG_TIMING
768 DateTime stopTime = DateTime.UtcNow;
769 TimeSpan requestDuration = stopTime - task.StartTime;
770  
771 TimeSpan networkDuration = stopTime - task.NetworkTime;
772  
773 TotalTime += requestDuration;
774 NetworkTime += networkDuration;
775 TotalBytes += task.Transfer.Size;
776  
777 Logger.Log(
778 String.Format(
779 "Transfer Complete {0} [{1}] Total Request Time: {2}, Download Time {3}, Network {4}Kb/sec, Image Size {5} bytes",
780 task.RequestID, task.RequestSlot, requestDuration, networkDuration,
781 Math.Round(task.Transfer.Size/networkDuration.TotalSeconds/60, 2), task.Transfer.Size),
782 Helpers.LogLevel.Debug);
783 #endif
784 task.Transfer.Success = true;
785 RemoveTransfer(task.RequestID);
786 resetEvents[task.RequestSlot].Set();
787  
788 _Client.Assets.Cache.SaveAssetToCache(task.RequestID, task.Transfer.AssetData);
789  
790 foreach (TextureDownloadCallback callback in task.Callbacks)
791 callback(TextureRequestState.Finished, new AssetTexture(task.RequestID, task.Transfer.AssetData));
792  
793 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred, task.Transfer.Size);
794 }
795 else
796 {
797 if (task.ReportProgress)
798 {
799 foreach (TextureDownloadCallback callback in task.Callbacks)
800 callback(TextureRequestState.Progress,
801 new AssetTexture(task.RequestID, task.Transfer.AssetData));
802 }
803  
804 _Client.Assets.FireImageProgressEvent(task.RequestID, task.Transfer.Transferred,
805 task.Transfer.Size);
806 }
807 }
808 }
809  
810 #endregion
811  
812 private bool TryGetTransferValue(UUID textureID, out TaskInfo task)
813 {
814 lock (_Transfers)
815 return _Transfers.TryGetValue(textureID, out task);
816 }
817  
818 private bool RemoveTransfer(UUID textureID)
819 {
820 lock (_Transfers)
821 return _Transfers.Remove(textureID);
822 }
823 }
824 }