diff options
Diffstat (limited to 'OpenSim/Region/ClientStack/LindenUDP')
-rw-r--r-- | OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | 26 | ||||
-rw-r--r-- | OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | 664 |
2 files changed, 684 insertions, 6 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 146bc63..5f2fbac 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | |||
@@ -99,6 +99,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
99 | 99 | ||
100 | protected LLPacketServer m_networkServer; | 100 | protected LLPacketServer m_networkServer; |
101 | 101 | ||
102 | protected LLImageManager m_imageManager; | ||
103 | |||
102 | /* public variables */ | 104 | /* public variables */ |
103 | protected string m_firstName; | 105 | protected string m_firstName; |
104 | protected string m_lastName; | 106 | protected string m_lastName; |
@@ -471,6 +473,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
471 | m_PacketHandler.OnPacketStats += PopulateStats; | 473 | m_PacketHandler.OnPacketStats += PopulateStats; |
472 | 474 | ||
473 | RegisterLocalPacketHandlers(); | 475 | RegisterLocalPacketHandlers(); |
476 | m_imageManager = new LLImageManager(this, m_assetCache,Scene.RequestModuleInterface<OpenSim.Region.Environment.Interfaces.IJ2KDecoder>()); | ||
474 | } | 477 | } |
475 | 478 | ||
476 | public void SetDebugPacketLevel(int newDebugPacketLevel) | 479 | public void SetDebugPacketLevel(int newDebugPacketLevel) |
@@ -496,6 +499,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
496 | // Shut down timers | 499 | // Shut down timers |
497 | m_clientPingTimer.Stop(); | 500 | m_clientPingTimer.Stop(); |
498 | 501 | ||
502 | |||
499 | // This is just to give the client a reasonable chance of | 503 | // This is just to give the client a reasonable chance of |
500 | // flushing out all it's packets. There should probably | 504 | // flushing out all it's packets. There should probably |
501 | // be a better mechanism here | 505 | // be a better mechanism here |
@@ -510,7 +514,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
510 | if (!(shutdownCircuit)) | 514 | if (!(shutdownCircuit)) |
511 | { | 515 | { |
512 | GC.Collect(); | 516 | GC.Collect(); |
513 | 517 | m_imageManager = null; | |
514 | // Sends a KillPacket object, with which, the | 518 | // Sends a KillPacket object, with which, the |
515 | // blockingqueue dequeues and sees it's a killpacket | 519 | // blockingqueue dequeues and sees it's a killpacket |
516 | // and terminates within the context of the client thread. | 520 | // and terminates within the context of the client thread. |
@@ -532,6 +536,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
532 | m_log.DebugFormat( | 536 | m_log.DebugFormat( |
533 | "[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}", | 537 | "[CLIENT]: Close has been called with shutdownCircuit = {0} for {1} attached to scene {2}", |
534 | shutdownCircuit, Name, m_scene.RegionInfo.RegionName); | 538 | shutdownCircuit, Name, m_scene.RegionInfo.RegionName); |
539 | |||
540 | m_imageManager.Close(); | ||
535 | 541 | ||
536 | m_PacketHandler.Flush(); | 542 | m_PacketHandler.Flush(); |
537 | 543 | ||
@@ -2759,7 +2765,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
2759 | ushort numParts, UUID ImageUUID, uint ImageSize, byte[] ImageData, byte imageCodec) | 2765 | ushort numParts, UUID ImageUUID, uint ImageSize, byte[] ImageData, byte imageCodec) |
2760 | { | 2766 | { |
2761 | ImageDataPacket im = new ImageDataPacket(); | 2767 | ImageDataPacket im = new ImageDataPacket(); |
2762 | im.Header.Reliable = false; | 2768 | im.Header.Reliable = true; |
2763 | im.ImageID.Packets = numParts; | 2769 | im.ImageID.Packets = numParts; |
2764 | im.ImageID.ID = ImageUUID; | 2770 | im.ImageID.ID = ImageUUID; |
2765 | 2771 | ||
@@ -2775,7 +2781,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
2775 | public void SendImageNextPart(ushort partNumber, UUID imageUuid, byte[] imageData) | 2781 | public void SendImageNextPart(ushort partNumber, UUID imageUuid, byte[] imageData) |
2776 | { | 2782 | { |
2777 | ImagePacketPacket im = new ImagePacketPacket(); | 2783 | ImagePacketPacket im = new ImagePacketPacket(); |
2778 | im.Header.Reliable = false; | 2784 | im.Header.Reliable = true; |
2779 | im.ImageID.Packet = partNumber; | 2785 | im.ImageID.Packet = partNumber; |
2780 | im.ImageID.ID = imageUuid; | 2786 | im.ImageID.ID = imageUuid; |
2781 | im.ImageData.Data = imageData; | 2787 | im.ImageData.Data = imageData; |
@@ -4192,6 +4198,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
4192 | if (ProcessPacketMethod(Pack)) | 4198 | if (ProcessPacketMethod(Pack)) |
4193 | { | 4199 | { |
4194 | //there is a handler registered that handled this packet type | 4200 | //there is a handler registered that handled this packet type |
4201 | |||
4202 | // in the end, we dereference this, so we have to check if it's null | ||
4203 | if (m_imageManager != null) | ||
4204 | m_imageManager.ProcessImageQueue(3); | ||
4195 | return; | 4205 | return; |
4196 | } | 4206 | } |
4197 | 4207 | ||
@@ -5232,10 +5242,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
5232 | args.PacketNumber = imageRequest.RequestImage[i].Packet; | 5242 | args.PacketNumber = imageRequest.RequestImage[i].Packet; |
5233 | args.Priority = imageRequest.RequestImage[i].DownloadPriority; | 5243 | args.Priority = imageRequest.RequestImage[i].DownloadPriority; |
5234 | 5244 | ||
5235 | handlerTextureRequest = OnRequestTexture; | 5245 | //handlerTextureRequest = OnRequestTexture; |
5236 | 5246 | ||
5237 | if (handlerTextureRequest != null) | 5247 | //if (handlerTextureRequest != null) |
5238 | OnRequestTexture(this, args); | 5248 | //OnRequestTexture(this, args); |
5249 | m_imageManager.EnqueueReq(args); | ||
5239 | } | 5250 | } |
5240 | } | 5251 | } |
5241 | break; | 5252 | break; |
@@ -7374,6 +7385,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
7374 | #endregion | 7385 | #endregion |
7375 | } | 7386 | } |
7376 | 7387 | ||
7388 | // in the end, we dereference this, so we have to check if it's null | ||
7389 | if (m_imageManager != null ) | ||
7390 | m_imageManager.ProcessImageQueue(3); | ||
7377 | PacketPool.Instance.ReturnPacket(Pack); | 7391 | PacketPool.Instance.ReturnPacket(Pack); |
7378 | } | 7392 | } |
7379 | 7393 | ||
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs new file mode 100644 index 0000000..ac6a1fa --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | |||
@@ -0,0 +1,664 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
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 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSim Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using OpenMetaverse; | ||
31 | using OpenSim.Framework; | ||
32 | using OpenSim.Region.Environment.Interfaces; | ||
33 | using C5; | ||
34 | using OpenSim.Framework.Communications.Cache; | ||
35 | using OpenMetaverse.Imaging; | ||
36 | |||
37 | |||
38 | namespace OpenSim.Region.ClientStack.LindenUDP | ||
39 | { | ||
40 | |||
41 | /// <summary> | ||
42 | /// Client image priority + discardlevel sender/manager | ||
43 | /// </summary> | ||
44 | public class LLImageManager | ||
45 | { | ||
46 | /// <summary> | ||
47 | /// Priority Queue for images. Contains lots of data | ||
48 | /// </summary> | ||
49 | private readonly IPriorityQueue<Prio<J2KImage>> pq = new IntervalHeap<Prio<J2KImage>>(); | ||
50 | |||
51 | /// <summary> | ||
52 | /// Dictionary of PriorityQueue handles by AssetId | ||
53 | /// </summary> | ||
54 | private readonly Dictionary<UUID, IPriorityQueueHandle<Prio<J2KImage>>> PQHandles = | ||
55 | new Dictionary<UUID, IPriorityQueueHandle<Prio<J2KImage>>>(); | ||
56 | |||
57 | private LLClientView m_client; | ||
58 | private readonly AssetCache m_assetCache; | ||
59 | private bool m_shuttingdown = false; | ||
60 | private readonly IJ2KDecoder m_j2kDecodeModule; | ||
61 | |||
62 | private readonly AssetBase MissingSubstitute; | ||
63 | |||
64 | /// <summary> | ||
65 | /// Client image priority + discardlevel sender/manager | ||
66 | /// </summary> | ||
67 | /// <param name="client">LLClientView of client</param> | ||
68 | /// <param name="pAssetCache">The Asset retrieval system</param> | ||
69 | /// <param name="pJ2kDecodeModule">The Jpeg2000 Decoder</param> | ||
70 | public LLImageManager(LLClientView client, AssetCache pAssetCache, IJ2KDecoder pJ2kDecodeModule) | ||
71 | { | ||
72 | m_client = client; | ||
73 | m_assetCache = pAssetCache; | ||
74 | if (pAssetCache != null) | ||
75 | MissingSubstitute = pAssetCache.GetAsset(UUID.Parse("5748decc-f629-461c-9a36-a35a221fe21f"), true); | ||
76 | m_j2kDecodeModule = pJ2kDecodeModule; | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Enqueues a texture request | ||
81 | /// </summary> | ||
82 | /// <param name="req">Request from the client to get a texture</param> | ||
83 | public void EnqueueReq(TextureRequestArgs req) | ||
84 | { | ||
85 | if (m_shuttingdown) | ||
86 | return; | ||
87 | |||
88 | //if (req.RequestType == 1) // avatar body texture! | ||
89 | // return; | ||
90 | |||
91 | AddQueueItem(req.RequestedAssetID, (int)req.Priority + 100000); | ||
92 | //if (pq[PQHandles[req.RequestedAssetID]].data.Missing) | ||
93 | //{ | ||
94 | // pq[PQHandles[req.RequestedAssetID]] -= 900000; | ||
95 | //} | ||
96 | // | ||
97 | //if (pq[PQHandles[req.RequestedAssetID]].data.HasData && pq[PQHandles[req.RequestedAssetID]].data.Layers.Length > 0) | ||
98 | //{ | ||
99 | |||
100 | //} | ||
101 | |||
102 | pq[PQHandles[req.RequestedAssetID]].data.requestedUUID = req.RequestedAssetID; | ||
103 | pq[PQHandles[req.RequestedAssetID]].data.Priority = (int)req.Priority; | ||
104 | |||
105 | lock (pq[PQHandles[req.RequestedAssetID]].data) | ||
106 | pq[PQHandles[req.RequestedAssetID]].data.Update(req.DiscardLevel, (int)req.PacketNumber); | ||
107 | } | ||
108 | |||
109 | |||
110 | /// <summary> | ||
111 | /// Callback for the asset system | ||
112 | /// </summary> | ||
113 | /// <param name="assetID">UUID of the asset that we have received</param> | ||
114 | /// <param name="asset">AssetBase of the asset that we've received</param> | ||
115 | public void AssetDataCallback(UUID assetID, AssetBase asset) | ||
116 | { | ||
117 | if (m_shuttingdown) | ||
118 | return; | ||
119 | |||
120 | //Console.WriteLine("AssetCallback for assetId" + assetID); | ||
121 | |||
122 | if (asset == null || asset.Data == null) | ||
123 | { | ||
124 | lock (pq) | ||
125 | { | ||
126 | //pq[PQHandles[assetID]].data.Missing = true; | ||
127 | pq[PQHandles[assetID]].data.asset = MissingSubstitute; | ||
128 | pq[PQHandles[assetID]].data.Missing = false; | ||
129 | } | ||
130 | } | ||
131 | //else | ||
132 | |||
133 | |||
134 | pq[PQHandles[assetID]].data.asset = asset; | ||
135 | |||
136 | lock (pq[PQHandles[assetID]].data) | ||
137 | pq[PQHandles[assetID]].data.Update((int)pq[PQHandles[assetID]].data.Priority, (int)pq[PQHandles[assetID]].data.CurrentPacket); | ||
138 | |||
139 | |||
140 | |||
141 | } | ||
142 | |||
143 | /// <summary> | ||
144 | /// Processes the image queue. Pops count elements off and processes them | ||
145 | /// </summary> | ||
146 | /// <param name="count">number of images to peek off the queue</param> | ||
147 | public void ProcessImageQueue(int count) | ||
148 | { | ||
149 | if (m_shuttingdown) | ||
150 | return; | ||
151 | |||
152 | |||
153 | IPriorityQueueHandle<Prio<J2KImage>> h = null; | ||
154 | for (int j = 0; j < count; j++) | ||
155 | { | ||
156 | |||
157 | lock (pq) | ||
158 | { | ||
159 | if (!pq.IsEmpty) | ||
160 | { | ||
161 | //peek off the top | ||
162 | Prio<J2KImage> process = pq.FindMax(out h); | ||
163 | |||
164 | // Do we have the Asset Data? | ||
165 | if (!process.data.HasData) | ||
166 | { | ||
167 | // Did we request the asset data? | ||
168 | if (!process.data.dataRequested) | ||
169 | { | ||
170 | m_assetCache.GetAsset(process.data.requestedUUID, AssetDataCallback, true); | ||
171 | pq[h].data.dataRequested = true; | ||
172 | } | ||
173 | |||
174 | // Is the asset missing? | ||
175 | if (process.data.Missing) | ||
176 | { | ||
177 | |||
178 | //m_client.sendtextur | ||
179 | pq[h] -= 90000; | ||
180 | /* | ||
181 | { | ||
182 | OpenMetaverse.Packets.ImageNotInDatabasePacket imdback = | ||
183 | new OpenMetaverse.Packets.ImageNotInDatabasePacket(); | ||
184 | imdback.ImageID = | ||
185 | new OpenMetaverse.Packets.ImageNotInDatabasePacket.ImageIDBlock(); | ||
186 | imdback.ImageID.ID = process.data.requestedUUID; | ||
187 | m_client.OutPacket(imdback, ThrottleOutPacketType.Texture); | ||
188 | } | ||
189 | */ | ||
190 | |||
191 | // Substitute a blank image | ||
192 | process.data.asset = MissingSubstitute; | ||
193 | process.data.Missing = false; | ||
194 | |||
195 | // If the priority is less then -4billion, the client has forgotten about it. | ||
196 | if (pq[h] < -400000000) | ||
197 | { | ||
198 | RemoveItemFromQueue(pq[h].data.requestedUUID); | ||
199 | continue; | ||
200 | } | ||
201 | } | ||
202 | // Lower the priority to give the next image a chance | ||
203 | pq[h] -= 100000; | ||
204 | } | ||
205 | else if (process.data.HasData) | ||
206 | { | ||
207 | // okay, we've got the data | ||
208 | lock (process.data) | ||
209 | { | ||
210 | if (!process.data.J2KDecode && !process.data.J2KDecodeWaiting) | ||
211 | { | ||
212 | process.data.J2KDecodeWaiting = true; | ||
213 | |||
214 | // Do we have a jpeg decoder? | ||
215 | if (m_j2kDecodeModule != null) | ||
216 | { | ||
217 | // Send it off to the jpeg decoder | ||
218 | m_j2kDecodeModule.decode(process.data.requestedUUID, process.data.Data, | ||
219 | j2kDecodedCallback); | ||
220 | } | ||
221 | else | ||
222 | { | ||
223 | // no module, no layers, full resolution only | ||
224 | j2kDecodedCallback(process.data.AssetId, new OpenJPEG.J2KLayerInfo[0]); | ||
225 | } | ||
226 | |||
227 | |||
228 | |||
229 | } // Are we waiting? | ||
230 | else if (!process.data.J2KDecodeWaiting) | ||
231 | { | ||
232 | // Send more data at a time for higher discard levels | ||
233 | for (int i = 0; i < (2*(5 - process.data.DiscardLevel) + 1)*2; i++) | ||
234 | if (!process.data.SendPacket(m_client)) | ||
235 | { | ||
236 | pq[h] -= (500000*i); | ||
237 | break; | ||
238 | } | ||
239 | } | ||
240 | // If the priority is less then -4 billion, the client has forgotten about it, pop it off | ||
241 | if (pq[h] < -400000000) | ||
242 | { | ||
243 | RemoveItemFromQueue(pq[h].data.requestedUUID); | ||
244 | continue; | ||
245 | } | ||
246 | } | ||
247 | |||
248 | //pq[h] = process; | ||
249 | } | ||
250 | |||
251 | // uncomment the following line to see the upper most asset and the priority | ||
252 | //Console.WriteLine(process.ToString()); | ||
253 | |||
254 | // Lower priority to give the next image a chance to bubble up | ||
255 | pq[h] -= 50000; | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | |||
260 | } | ||
261 | |||
262 | |||
263 | /// <summary> | ||
264 | /// Callback for when the image has been decoded | ||
265 | /// </summary> | ||
266 | /// <param name="AssetId">The UUID of the Asset</param> | ||
267 | /// <param name="layers">The Jpeg2000 discard level Layer start and end byte offsets Array. 0 elements for failed or no decoder</param> | ||
268 | public void j2kDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) | ||
269 | { | ||
270 | // are we shutting down? if so, end. | ||
271 | if (m_shuttingdown) | ||
272 | return; | ||
273 | |||
274 | |||
275 | lock (PQHandles) | ||
276 | { | ||
277 | // Update our asset data | ||
278 | if (PQHandles.ContainsKey(AssetId)) | ||
279 | { | ||
280 | pq[PQHandles[AssetId]].data.Layers = layers; | ||
281 | pq[PQHandles[AssetId]].data.J2KDecode = true; | ||
282 | pq[PQHandles[AssetId]].data.J2KDecodeWaiting = false; | ||
283 | lock (pq[PQHandles[AssetId]].data) | ||
284 | pq[PQHandles[AssetId]].data.Update((int)pq[PQHandles[AssetId]].data.Priority, (int)pq[PQHandles[AssetId]].data.CurrentPacket); | ||
285 | |||
286 | // Send the first packet | ||
287 | pq[PQHandles[AssetId]].data.SendPacket(m_client); | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | |||
293 | /// <summary> | ||
294 | /// This image has had a good life. It's now expired. Remove it off the queue | ||
295 | /// </summary> | ||
296 | /// <param name="AssetId">UUID of asset to remove off the queue</param> | ||
297 | private void RemoveItemFromQueue(UUID AssetId) | ||
298 | { | ||
299 | lock (PQHandles) | ||
300 | { | ||
301 | if (PQHandles.ContainsKey(AssetId)) | ||
302 | { | ||
303 | IPriorityQueueHandle<Prio<J2KImage>> h = PQHandles[AssetId]; | ||
304 | PQHandles.Remove(AssetId); | ||
305 | pq.Delete(h); | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | |||
310 | |||
311 | /// <summary> | ||
312 | /// Adds an image to the queue and update priority | ||
313 | /// if the item is already in the queue, just update the priority | ||
314 | /// </summary> | ||
315 | /// <param name="AssetId">UUID of the asset</param> | ||
316 | /// <param name="priority">Priority to set</param> | ||
317 | private void AddQueueItem(UUID AssetId, int priority) | ||
318 | { | ||
319 | IPriorityQueueHandle<Prio<J2KImage>> h = null; | ||
320 | |||
321 | lock (PQHandles) | ||
322 | { | ||
323 | if (PQHandles.ContainsKey(AssetId)) | ||
324 | { | ||
325 | h = PQHandles[AssetId]; | ||
326 | pq[h] = pq[h].SetPriority(priority); | ||
327 | |||
328 | } | ||
329 | else | ||
330 | { | ||
331 | J2KImage newreq = new J2KImage(); | ||
332 | newreq.requestedUUID = AssetId; | ||
333 | pq.Add(ref h, new Prio<J2KImage>(newreq, priority)); | ||
334 | PQHandles.Add(AssetId, h); | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Okay, we're ending. Clean up on isle 9 | ||
341 | /// </summary> | ||
342 | public void Close() | ||
343 | { | ||
344 | m_shuttingdown = true; | ||
345 | |||
346 | lock (pq) | ||
347 | { | ||
348 | while (!pq.IsEmpty) | ||
349 | { | ||
350 | pq.DeleteMin(); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | |||
355 | lock (PQHandles) | ||
356 | PQHandles.Clear(); | ||
357 | m_client = null; | ||
358 | } | ||
359 | |||
360 | } | ||
361 | |||
362 | /// <summary> | ||
363 | /// Image Data for this send | ||
364 | /// Encapsulates the image sending data and method | ||
365 | /// </summary> | ||
366 | public class J2KImage | ||
367 | { | ||
368 | private AssetBase m_asset_ref = null; | ||
369 | public volatile int LastPacketNum = 0; | ||
370 | public volatile int DiscardLimit = 0; | ||
371 | public volatile bool dataRequested = false; | ||
372 | public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; | ||
373 | |||
374 | public const int FIRST_IMAGE_PACKET_SIZE = 600; | ||
375 | public const int IMAGE_PACKET_SIZE = 1000; | ||
376 | |||
377 | public volatile int DiscardLevel; | ||
378 | public float Priority; | ||
379 | public volatile int CurrentPacket = 1; | ||
380 | public volatile int StopPacket; | ||
381 | public bool Missing = false; | ||
382 | public bool J2KDecode = false; | ||
383 | public bool J2KDecodeWaiting = false; | ||
384 | |||
385 | private volatile bool sendFirstPacket = true; | ||
386 | |||
387 | // Having this *AND* the AssetId allows us to remap asset data to AssetIds as necessary. | ||
388 | public UUID requestedUUID = UUID.Zero; | ||
389 | |||
390 | public J2KImage(AssetBase asset) | ||
391 | { | ||
392 | m_asset_ref = asset; | ||
393 | } | ||
394 | |||
395 | public J2KImage() | ||
396 | { | ||
397 | |||
398 | } | ||
399 | |||
400 | public AssetBase asset | ||
401 | { | ||
402 | set { m_asset_ref = value; } | ||
403 | } | ||
404 | |||
405 | // We make the asset a reference so that we don't duplicate the byte[] | ||
406 | // it's read only anyway, so no worries here | ||
407 | // we want to avoid duplicating the byte[] for the images at all costs to avoid memory bloat! :) | ||
408 | |||
409 | /// <summary> | ||
410 | /// ID of the AssetBase | ||
411 | /// </summary> | ||
412 | public UUID AssetId | ||
413 | { | ||
414 | get { return m_asset_ref.FullID; } | ||
415 | } | ||
416 | |||
417 | /// <summary> | ||
418 | /// Asset Data | ||
419 | /// </summary> | ||
420 | public byte[] Data | ||
421 | { | ||
422 | get { return m_asset_ref.Data; } | ||
423 | } | ||
424 | |||
425 | /// <summary> | ||
426 | /// Returns true if we have the asset | ||
427 | /// </summary> | ||
428 | public bool HasData | ||
429 | { | ||
430 | get { return !(m_asset_ref == null); } | ||
431 | } | ||
432 | |||
433 | /// <summary> | ||
434 | /// Called from the PriorityQueue handle .ToString(). Prints data on this asset | ||
435 | /// </summary> | ||
436 | /// <returns></returns> | ||
437 | public override string ToString() | ||
438 | { | ||
439 | return string.Format("ID:{0}, RD:{1}, CP:{2}", requestedUUID, HasData, CurrentPacket); | ||
440 | } | ||
441 | |||
442 | /// <summary> | ||
443 | /// Returns the total number of packets needed to transfer this texture, | ||
444 | /// including the first packet of size FIRST_IMAGE_PACKET_SIZE | ||
445 | /// </summary> | ||
446 | /// <returns>Total number of packets needed to transfer this texture</returns> | ||
447 | public int TexturePacketCount() | ||
448 | { | ||
449 | if (!HasData) | ||
450 | return 0; | ||
451 | return ((m_asset_ref.Data.Length - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | ||
452 | } | ||
453 | |||
454 | /// <summary> | ||
455 | /// Returns the current byte offset for this transfer, calculated from | ||
456 | /// the CurrentPacket | ||
457 | /// </summary> | ||
458 | /// <returns>Current byte offset for this transfer</returns> | ||
459 | public int CurrentBytePosition() | ||
460 | { | ||
461 | if (CurrentPacket == 0) | ||
462 | return 0; | ||
463 | if (CurrentPacket == 1) | ||
464 | return FIRST_IMAGE_PACKET_SIZE; | ||
465 | |||
466 | int result = FIRST_IMAGE_PACKET_SIZE + (CurrentPacket - 2) * IMAGE_PACKET_SIZE; | ||
467 | if (result < 0) | ||
468 | { | ||
469 | result = FIRST_IMAGE_PACKET_SIZE; | ||
470 | } | ||
471 | return result; | ||
472 | } | ||
473 | |||
474 | /// <summary> | ||
475 | /// Returns the size, in bytes, of the last packet. This will be somewhere | ||
476 | /// between 1 and IMAGE_PACKET_SIZE bytes | ||
477 | /// </summary> | ||
478 | /// <returns>Size of the last packet in the transfer</returns> | ||
479 | public int LastPacketSize() | ||
480 | { | ||
481 | if (CurrentPacket == 1) | ||
482 | return m_asset_ref.Data.Length; | ||
483 | return (m_asset_ref.Data.Length - FIRST_IMAGE_PACKET_SIZE) % IMAGE_PACKET_SIZE; // m_asset_ref.Data.Length - (FIRST_IMAGE_PACKET_SIZE + ((TexturePacketCount() - 1) * IMAGE_PACKET_SIZE)); | ||
484 | } | ||
485 | |||
486 | /// <summary> | ||
487 | /// Find the packet number that contains a given byte position | ||
488 | /// </summary> | ||
489 | /// <param name="bytePosition">Byte position</param> | ||
490 | /// <returns>Packet number that contains the given byte position</returns> | ||
491 | int GetPacketForBytePosition(int bytePosition) | ||
492 | { | ||
493 | return ((bytePosition - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | ||
494 | } | ||
495 | |||
496 | /// <summary> | ||
497 | /// Updates the Image sending limits based on the discard | ||
498 | /// If we don't have any Layers, Send the full texture | ||
499 | /// </summary> | ||
500 | /// <param name="discardLevel">jpeg2000 discard level. 5-0</param> | ||
501 | /// <param name="packet">Which packet to start from</param> | ||
502 | public void Update(int discardLevel, int packet) | ||
503 | { | ||
504 | //Requests for 0 means that the client wants us to resend the whole image | ||
505 | //Requests for -1 mean 'update priority but don't change discard level' | ||
506 | |||
507 | if (packet == 0 || packet == -1) | ||
508 | return; | ||
509 | |||
510 | // Check if we've got layers | ||
511 | if (Layers.Length > 0) | ||
512 | { | ||
513 | DiscardLevel = Util.Clamp<int>(discardLevel, 0, Layers.Length - 1); | ||
514 | StopPacket = GetPacketForBytePosition(Layers[(Layers.Length - 1) - DiscardLevel].End); | ||
515 | CurrentPacket = Util.Clamp<int>(packet, 1, TexturePacketCount() - 1); | ||
516 | // sendFirstPacket = true; | ||
517 | } | ||
518 | else | ||
519 | { | ||
520 | // No layers, send full image | ||
521 | DiscardLevel = 0; | ||
522 | StopPacket = TexturePacketCount() - 1; | ||
523 | CurrentPacket = Util.Clamp<int>(packet, 1, TexturePacketCount() - 1); | ||
524 | |||
525 | } | ||
526 | } | ||
527 | |||
528 | /// <summary> | ||
529 | /// Sends a texture packet to the client. | ||
530 | /// </summary> | ||
531 | /// <param name="client">Client to send texture to</param> | ||
532 | /// <returns>true if a packet was sent, false if not</returns> | ||
533 | public bool SendPacket(LLClientView client) | ||
534 | { | ||
535 | // If we've hit the end of the send or if the client set -1, return false. | ||
536 | if (CurrentPacket > StopPacket || StopPacket == -1) | ||
537 | return false; | ||
538 | |||
539 | // The first packet contains up to 600 bytes and the details of the image. Number of packets, image size in bytes, etc. | ||
540 | // This packet only gets sent once unless we're restarting the transfer from 0! | ||
541 | if (sendFirstPacket) | ||
542 | { | ||
543 | sendFirstPacket = false; | ||
544 | |||
545 | // Do we have less then 1 packet's worth of data? | ||
546 | if (m_asset_ref.Data.Length <= FIRST_IMAGE_PACKET_SIZE) | ||
547 | { | ||
548 | // Send only 1 packet | ||
549 | client.SendImageFirstPart(1, requestedUUID , (uint)m_asset_ref.Data.Length, m_asset_ref.Data, 2); | ||
550 | CurrentPacket = 2; // Makes it so we don't come back to SendPacket and error trying to send a second packet | ||
551 | return true; | ||
552 | } | ||
553 | else | ||
554 | { | ||
555 | |||
556 | // Send first packet | ||
557 | byte[] firstImageData = new byte[FIRST_IMAGE_PACKET_SIZE]; | ||
558 | try { Buffer.BlockCopy(m_asset_ref.Data, 0, firstImageData, 0, FIRST_IMAGE_PACKET_SIZE); } | ||
559 | catch (Exception) | ||
560 | { | ||
561 | Console.WriteLine(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(), firstImageData.Length, FIRST_IMAGE_PACKET_SIZE)); | ||
562 | |||
563 | //m_log.Error("Texture data copy failed on first packet for " + m_asset_ref.FullID.ToString()); | ||
564 | //m_cancel = true; | ||
565 | //m_sending = false; | ||
566 | return false; | ||
567 | } | ||
568 | client.SendImageFirstPart((ushort)TexturePacketCount(), requestedUUID, (uint)m_asset_ref.Data.Length, firstImageData, 2); | ||
569 | ++CurrentPacket; // sets CurrentPacket to 1 | ||
570 | } | ||
571 | } | ||
572 | |||
573 | // figure out if we're on the last packet, if so, use the last packet size. If not, use 1000. | ||
574 | // we know that the total image size is greater then 1000 if we're here | ||
575 | int imagePacketSize = (CurrentPacket == (TexturePacketCount() ) ) ? LastPacketSize() : IMAGE_PACKET_SIZE; | ||
576 | |||
577 | //if (imagePacketSize > 0) | ||
578 | // imagePacketSize = IMAGE_PACKET_SIZE; | ||
579 | //if (imagePacketSize != 1000) | ||
580 | // Console.WriteLine("ENdPacket"); | ||
581 | //Console.WriteLine(String.Format("srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(),0, imagePacketSize)); | ||
582 | |||
583 | |||
584 | byte[] imageData = new byte[imagePacketSize]; | ||
585 | try { Buffer.BlockCopy(m_asset_ref.Data, CurrentBytePosition(), imageData, 0, imagePacketSize); } | ||
586 | catch (Exception e) | ||
587 | { | ||
588 | Console.WriteLine(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize:{3}, currpak:{4}, stoppak:{5}, totalpak:{6}", m_asset_ref.Data.Length, CurrentBytePosition(), | ||
589 | imageData.Length, imagePacketSize, CurrentPacket,StopPacket,TexturePacketCount())); | ||
590 | System.Console.WriteLine(e.ToString()); | ||
591 | //m_log.Error("Texture data copy failed for " + m_asset_ref.FullID.ToString()); | ||
592 | //m_cancel = true; | ||
593 | //m_sending = false; | ||
594 | return false; | ||
595 | } | ||
596 | |||
597 | // Send next packet to the client | ||
598 | client.SendImageNextPart((ushort)(CurrentPacket - 1), requestedUUID, imageData); | ||
599 | ++CurrentPacket; | ||
600 | return true; | ||
601 | } | ||
602 | |||
603 | } | ||
604 | |||
605 | /// <summary> | ||
606 | /// Generic Priority Queue element | ||
607 | /// Contains a Priority and a Reference type Data Element | ||
608 | /// </summary> | ||
609 | /// <typeparam name="D">Reference type data element</typeparam> | ||
610 | struct Prio<D> : IComparable<Prio<D>> where D : class | ||
611 | { | ||
612 | public D data; | ||
613 | private int priority; | ||
614 | |||
615 | public Prio(D data, int priority) | ||
616 | { | ||
617 | this.data = data; | ||
618 | this.priority = priority; | ||
619 | } | ||
620 | |||
621 | public int CompareTo(Prio<D> that) | ||
622 | { | ||
623 | return this.priority.CompareTo(that.priority); | ||
624 | } | ||
625 | |||
626 | public bool Equals(Prio<D> that) | ||
627 | { | ||
628 | return this.priority == that.priority; | ||
629 | } | ||
630 | |||
631 | public static Prio<D> operator +(Prio<D> tp, int delta) | ||
632 | { | ||
633 | return new Prio<D>(tp.data, tp.priority + delta); | ||
634 | } | ||
635 | |||
636 | public static bool operator <(Prio<D> tp, int check) | ||
637 | { | ||
638 | return (tp.priority < check); | ||
639 | } | ||
640 | |||
641 | public static bool operator >(Prio<D> tp, int check) | ||
642 | { | ||
643 | return (tp.priority > check); | ||
644 | } | ||
645 | |||
646 | public static Prio<D> operator -(Prio<D> tp, int delta) | ||
647 | { | ||
648 | if (tp.priority - delta < 0) | ||
649 | return new Prio<D>(tp.data, tp.priority - delta); | ||
650 | else | ||
651 | return new Prio<D>(tp.data, 0); | ||
652 | } | ||
653 | |||
654 | public override String ToString() | ||
655 | { | ||
656 | return String.Format("{0}[{1}]", data, priority); | ||
657 | } | ||
658 | |||
659 | internal Prio<D> SetPriority(int pPriority) | ||
660 | { | ||
661 | return new Prio<D>(this.data, pPriority); | ||
662 | } | ||
663 | } | ||
664 | } | ||