corrade-vassal – Blame information for rev 1
?pathlinks?
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 | } |