diff options
author | Teravus Ovares | 2009-04-10 08:30:21 +0000 |
---|---|---|
committer | Teravus Ovares | 2009-04-10 08:30:21 +0000 |
commit | 515bf6d7dcce10d5e32db489c0d6247bd3b2a615 (patch) | |
tree | 84a6d08e761a2f7d5758b44f2cc71dc98eaec5d7 /OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | |
parent | Handle ObjectSpin* packets to spin physical prims on Ctrl+Shift+Drag (diff) | |
download | opensim-SC-515bf6d7dcce10d5e32db489c0d6247bd3b2a615.zip opensim-SC-515bf6d7dcce10d5e32db489c0d6247bd3b2a615.tar.gz opensim-SC-515bf6d7dcce10d5e32db489c0d6247bd3b2a615.tar.bz2 opensim-SC-515bf6d7dcce10d5e32db489c0d6247bd3b2a615.tar.xz |
* Patch from RemedyTomm Mantis 3440
* Revamps the server side texture pipeline
* Textures should load faster, get clogged less, and be less blurry
* Minor tweak to ensure the outgoing texture throttle stays private.
* Fixes mantis 3440
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | 917 |
1 files changed, 410 insertions, 507 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs index 0fb27d4..6bfab90 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | |||
@@ -27,7 +27,6 @@ | |||
27 | 27 | ||
28 | using System; | 28 | using System; |
29 | using System.Collections.Generic; | 29 | using System.Collections.Generic; |
30 | using C5; | ||
31 | using OpenMetaverse; | 30 | using OpenMetaverse; |
32 | using OpenMetaverse.Imaging; | 31 | using OpenMetaverse.Imaging; |
33 | using OpenSim.Framework; | 32 | using OpenSim.Framework; |
@@ -38,642 +37,546 @@ using System.Reflection; | |||
38 | namespace OpenSim.Region.ClientStack.LindenUDP | 37 | namespace OpenSim.Region.ClientStack.LindenUDP |
39 | { | 38 | { |
40 | 39 | ||
41 | /// <summary> | ||
42 | /// Client image priority + discardlevel sender/manager | ||
43 | /// </summary> | ||
44 | public class LLImageManager | 40 | public class LLImageManager |
45 | { | 41 | { |
42 | |||
43 | //Public interfaces: | ||
44 | //Constructor - (LLClientView, IAssetCache, IJ2KDecoder); | ||
45 | //void EnqueueReq - (TextureRequestArgs) | ||
46 | //ProcessImageQueue | ||
47 | //Close | ||
48 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
49 | private bool m_shuttingdown = false; | ||
50 | |||
51 | private LLClientView m_client; //Client we're assigned to | ||
52 | private IAssetCache m_assetCache; //Asset Cache | ||
53 | private IJ2KDecoder m_j2kDecodeModule; //Our J2K module | ||
46 | 54 | ||
47 | /// <summary> | 55 | private readonly AssetBase m_missingsubstitute; //Sustitute for bad decodes |
48 | /// Priority Queue for images. Contains lots of data | 56 | private Dictionary<UUID,J2KImage> m_imagestore; // Our main image storage dictionary |
49 | /// </summary> | 57 | private SortedList<double,UUID> m_priorities; // For fast image lookup based on priority |
50 | private readonly IPriorityQueue<Prio<J2KImage>> pq = new IntervalHeap<Prio<J2KImage>>(); | 58 | private Dictionary<int, int> m_priorityresolver; //Enabling super fast assignment of images with the same priorities |
51 | 59 | ||
52 | /// <summary> | 60 | private const double doubleMinimum = .0000001; |
53 | /// Dictionary of PriorityQueue handles by AssetId | 61 | //Constructor |
54 | /// </summary> | ||
55 | private readonly Dictionary<UUID, IPriorityQueueHandle<Prio<J2KImage>>> PQHandles = | ||
56 | new Dictionary<UUID, IPriorityQueueHandle<Prio<J2KImage>>>(); | ||
57 | |||
58 | private LLClientView m_client; | ||
59 | private readonly IAssetCache m_assetCache; | ||
60 | private bool m_shuttingdown = false; | ||
61 | private readonly IJ2KDecoder m_j2kDecodeModule; | ||
62 | |||
63 | private readonly AssetBase MissingSubstitute; | ||
64 | |||
65 | /// <summary> | ||
66 | /// Client image priority + discardlevel sender/manager | ||
67 | /// </summary> | ||
68 | /// <param name="client">LLClientView of client</param> | ||
69 | /// <param name="pAssetCache">The Asset retrieval system</param> | ||
70 | /// <param name="pJ2kDecodeModule">The Jpeg2000 Decoder</param> | ||
71 | public LLImageManager(LLClientView client, IAssetCache pAssetCache, IJ2KDecoder pJ2kDecodeModule) | 62 | public LLImageManager(LLClientView client, IAssetCache pAssetCache, IJ2KDecoder pJ2kDecodeModule) |
72 | { | 63 | { |
64 | |||
65 | m_imagestore = new Dictionary<UUID,J2KImage>(); | ||
66 | m_priorities = new SortedList<double,UUID>(); | ||
67 | m_priorityresolver = new Dictionary<int, int>(); | ||
73 | m_client = client; | 68 | m_client = client; |
74 | m_assetCache = pAssetCache; | 69 | m_assetCache = pAssetCache; |
75 | if (pAssetCache != null) | 70 | if (pAssetCache != null) |
76 | MissingSubstitute = pAssetCache.GetAsset(UUID.Parse("5748decc-f629-461c-9a36-a35a221fe21f"), true); | 71 | m_missingsubstitute = pAssetCache.GetAsset(UUID.Parse("5748decc-f629-461c-9a36-a35a221fe21f"), true); |
77 | m_j2kDecodeModule = pJ2kDecodeModule; | 72 | m_j2kDecodeModule = pJ2kDecodeModule; |
78 | } | 73 | } |
79 | 74 | ||
80 | /// <summary> | 75 | public void EnqueueReq(TextureRequestArgs newRequest) |
81 | /// Enqueues a texture request | ||
82 | /// </summary> | ||
83 | /// <param name="req">Request from the client to get a texture</param> | ||
84 | public void EnqueueReq(TextureRequestArgs req) | ||
85 | { | 76 | { |
86 | if (m_shuttingdown) | 77 | //newRequest is the properties of our new texture fetch request. |
87 | return; | 78 | //Basically, here is where we queue up "new" requests.. |
79 | // .. or modify existing requests to suit. | ||
88 | 80 | ||
89 | //if (req.RequestType == 1) // avatar body texture! | 81 | //Make sure we're not shutting down.. |
90 | // return; | 82 | if (!m_shuttingdown) |
83 | { | ||
91 | 84 | ||
92 | AddQueueItem(req.RequestedAssetID, (int)req.Priority + 100000); | 85 | //Do we already know about this UUID? |
93 | //if (pq[PQHandles[req.RequestedAssetID]].data.Missing) | 86 | if (m_imagestore.ContainsKey(newRequest.RequestedAssetID)) |
94 | //{ | 87 | { |
95 | // pq[PQHandles[req.RequestedAssetID]] -= 900000; | 88 | //Check the packet sequence to make sure this isn't older than |
96 | //} | 89 | //one we've already received |
97 | // | ||
98 | //if (pq[PQHandles[req.RequestedAssetID]].data.HasData && pq[PQHandles[req.RequestedAssetID]].data.Layers.Length > 0) | ||
99 | //{ | ||
100 | 90 | ||
101 | //} | 91 | J2KImage imgrequest = m_imagestore[newRequest.RequestedAssetID]; |
102 | 92 | ||
103 | pq[PQHandles[req.RequestedAssetID]].data.requestedUUID = req.RequestedAssetID; | 93 | //if (newRequest.requestSequence > imgrequest.m_lastSequence) |
104 | pq[PQHandles[req.RequestedAssetID]].data.Priority = (int)req.Priority; | 94 | //{ |
95 | imgrequest.m_lastSequence = newRequest.requestSequence; | ||
105 | 96 | ||
106 | lock (pq[PQHandles[req.RequestedAssetID]].data) | 97 | //First of all, is this being killed? |
107 | pq[PQHandles[req.RequestedAssetID]].data.Update(req.DiscardLevel, (int)req.PacketNumber); | 98 | if (newRequest.Priority == 0.0f && newRequest.DiscardLevel == -1) |
108 | } | 99 | { |
100 | //Remove the old priority | ||
101 | m_priorities.Remove(imgrequest.m_designatedPriorityKey); | ||
102 | m_imagestore.Remove(imgrequest.m_requestedUUID); | ||
103 | imgrequest = null; | ||
104 | } | ||
105 | else | ||
106 | { | ||
109 | 107 | ||
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 | 108 | ||
120 | //m_log.Debug("AssetCallback for assetId" + assetID); | 109 | //Check the priority |
110 | double priority = imgrequest.m_requestedPriority; | ||
111 | if (priority != newRequest.Priority) | ||
112 | { | ||
113 | //Remove the old priority | ||
114 | m_priorities.Remove(imgrequest.m_designatedPriorityKey); | ||
115 | //Assign a new unique priority | ||
116 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
117 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); | ||
118 | } | ||
121 | 119 | ||
122 | if (asset == null || asset.Data == null) | 120 | //Update the requested discard level |
123 | { | 121 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
124 | lock (pq) | 122 | |
125 | { | 123 | //Update the requested packet number |
126 | //pq[PQHandles[assetID]].data.Missing = true; | 124 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
127 | pq[PQHandles[assetID]].data.asset = MissingSubstitute; | 125 | |
128 | pq[PQHandles[assetID]].data.Missing = false; | 126 | //Run an update |
127 | imgrequest.RunUpdate(); | ||
128 | } | ||
129 | //} | ||
129 | } | 130 | } |
130 | } | 131 | else |
131 | //else | 132 | { |
133 | J2KImage imgrequest = new J2KImage(); | ||
132 | 134 | ||
133 | pq[PQHandles[assetID]].data.asset = asset; | 135 | //Assign our missing substitute |
136 | imgrequest.m_MissingSubstitute = m_missingsubstitute; | ||
134 | 137 | ||
135 | //lock (pq[PQHandles[assetID]].data) | 138 | //Assign our decoder module |
136 | pq[PQHandles[assetID]].data.Update((int)pq[PQHandles[assetID]].data.Priority, pq[PQHandles[assetID]].data.CurrentPacket); | 139 | imgrequest.m_j2kDecodeModule = m_j2kDecodeModule; |
137 | } | ||
138 | 140 | ||
139 | /// <summary> | 141 | //Assign our asset cache module |
140 | /// Processes the image queue. Pops count elements off and processes them | 142 | imgrequest.m_assetCache = m_assetCache; |
141 | /// </summary> | ||
142 | /// <param name="count">number of images to peek off the queue</param> | ||
143 | public void ProcessImageQueue(int count) | ||
144 | { | ||
145 | if (m_shuttingdown) | ||
146 | return; | ||
147 | 143 | ||
148 | IPriorityQueueHandle<Prio<J2KImage>> h = null; | 144 | //Assign a priority based on our request |
149 | for (int j = 0; j < count; j++) | 145 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); |
150 | { | ||
151 | lock (pq) | ||
152 | { | ||
153 | if (!pq.IsEmpty) | ||
154 | { | ||
155 | //peek off the top | ||
156 | Prio<J2KImage> process = pq.FindMax(out h); | ||
157 | 146 | ||
158 | // Do we have the Asset Data? | 147 | //Assign the requested discard level |
159 | if (!process.data.HasData) | 148 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
160 | { | ||
161 | // Did we request the asset data? | ||
162 | if (!process.data.dataRequested) | ||
163 | { | ||
164 | m_assetCache.GetAsset(process.data.requestedUUID, AssetDataCallback, true); | ||
165 | pq[h].data.dataRequested = true; | ||
166 | } | ||
167 | 149 | ||
168 | // Is the asset missing? | 150 | //Assign the requested packet number |
169 | if (process.data.Missing) | 151 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
170 | { | ||
171 | 152 | ||
172 | //m_client.sendtextur | 153 | //Assign the requested priority |
173 | pq[h] -= 90000; | 154 | imgrequest.m_requestedPriority = newRequest.Priority; |
174 | /* | ||
175 | { | ||
176 | OpenMetaverse.Packets.ImageNotInDatabasePacket imdback = | ||
177 | new OpenMetaverse.Packets.ImageNotInDatabasePacket(); | ||
178 | imdback.ImageID = | ||
179 | new OpenMetaverse.Packets.ImageNotInDatabasePacket.ImageIDBlock(); | ||
180 | imdback.ImageID.ID = process.data.requestedUUID; | ||
181 | m_client.OutPacket(imdback, ThrottleOutPacketType.Texture); | ||
182 | } | ||
183 | */ | ||
184 | |||
185 | // Substitute a blank image | ||
186 | process.data.asset = MissingSubstitute; | ||
187 | process.data.Missing = false; | ||
188 | |||
189 | // If the priority is less then -4billion, the client has forgotten about it. | ||
190 | if (pq[h] < -400000000) | ||
191 | { | ||
192 | RemoveItemFromQueue(pq[h].data.requestedUUID); | ||
193 | continue; | ||
194 | } | ||
195 | } | ||
196 | // Lower the priority to give the next image a chance | ||
197 | pq[h] -= 100000; | ||
198 | } | ||
199 | else if (process.data.HasData) | ||
200 | { | ||
201 | // okay, we've got the data | ||
202 | lock (process.data) | ||
203 | { | ||
204 | if (!process.data.J2KDecode && !process.data.J2KDecodeWaiting) | ||
205 | { | ||
206 | process.data.J2KDecodeWaiting = true; | ||
207 | |||
208 | // Do we have a jpeg decoder? | ||
209 | if (m_j2kDecodeModule != null) | ||
210 | { | ||
211 | // Send it off to the jpeg decoder | ||
212 | m_j2kDecodeModule.decode(process.data.requestedUUID, process.data.Data, | ||
213 | j2kDecodedCallback); | ||
214 | } | ||
215 | else | ||
216 | { | ||
217 | // no module, no layers, full resolution only | ||
218 | j2kDecodedCallback(process.data.AssetId, new OpenJPEG.J2KLayerInfo[0]); | ||
219 | } | ||
220 | } // Are we waiting? | ||
221 | else if (!process.data.J2KDecodeWaiting) | ||
222 | { | ||
223 | // Send more data at a time for higher discard levels | ||
224 | bool done = false; | ||
225 | for (int i = 0; i < (2*(process.data.DiscardLevel) + 1)*2; i++) | ||
226 | if (!process.data.SendPacket(m_client)) | ||
227 | { | ||
228 | done = true; | ||
229 | pq[h] -= (500000*i); | ||
230 | break; | ||
231 | } | ||
232 | if (!done) | ||
233 | { | ||
234 | for (int i = 0; i < (2 * (5- process.data.DiscardLevel) + 1) * 2; i++) | ||
235 | if (!process.data.SendPacket(m_client)) | ||
236 | { | ||
237 | done = true; | ||
238 | pq[h] -= (500000 * i); | ||
239 | break; | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | // If the priority is less then -4 billion, the client has forgotten about it, pop it off | ||
244 | if (pq[h] < -400000000) | ||
245 | { | ||
246 | RemoveItemFromQueue(pq[h].data.requestedUUID); | ||
247 | continue; | ||
248 | } | ||
249 | } | ||
250 | 155 | ||
251 | //pq[h] = process; | 156 | //Assign the asset uuid |
252 | } | 157 | imgrequest.m_requestedUUID = newRequest.RequestedAssetID; |
253 | 158 | ||
254 | // uncomment the following line to see the upper most asset and the priority | 159 | m_imagestore.Add(imgrequest.m_requestedUUID, imgrequest); |
255 | //m_log.Debug(process.ToString()); | 160 | |
161 | //Run an update | ||
162 | imgrequest.RunUpdate(); | ||
256 | 163 | ||
257 | // Lower priority to give the next image a chance to bubble up | ||
258 | pq[h] -= 50000; | ||
259 | } | ||
260 | } | 164 | } |
261 | } | 165 | } |
262 | } | 166 | } |
263 | 167 | ||
264 | /// <summary> | 168 | private double AssignPriority(UUID pAssetID, double pPriority) |
265 | /// Callback for when the image has been decoded | ||
266 | /// </summary> | ||
267 | /// <param name="AssetId">The UUID of the Asset</param> | ||
268 | /// <param name="layers">The Jpeg2000 discard level Layer start and end byte offsets Array. 0 elements for failed or no decoder</param> | ||
269 | public void j2kDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) | ||
270 | { | 169 | { |
271 | // are we shutting down? if so, end. | 170 | |
272 | if (m_shuttingdown) | 171 | //First, find out if we can just assign directly |
273 | return; | 172 | if (m_priorityresolver.ContainsKey((int)pPriority) == false) |
274 | |||
275 | lock (PQHandles) | ||
276 | { | 173 | { |
277 | // Update our asset data | 174 | m_priorities.Add((double)((int)pPriority), pAssetID); |
278 | if (PQHandles.ContainsKey(AssetId)) | 175 | m_priorityresolver.Add((int)pPriority, 0); |
279 | { | 176 | return (double)((int)pPriority); |
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 | } | 177 | } |
290 | } | 178 | else |
291 | |||
292 | /// <summary> | ||
293 | /// This image has had a good life. It's now expired. Remove it off the queue | ||
294 | /// </summary> | ||
295 | /// <param name="AssetId">UUID of asset to remove off the queue</param> | ||
296 | private void RemoveItemFromQueue(UUID AssetId) | ||
297 | { | ||
298 | lock (PQHandles) | ||
299 | { | 179 | { |
300 | if (PQHandles.ContainsKey(AssetId)) | 180 | //Use the hash lookup goodness of a secondary dictionary to find a free slot |
301 | { | 181 | double mFreePriority = ((int)pPriority) + (doubleMinimum * (m_priorityresolver[(int)pPriority] + 1)); |
302 | IPriorityQueueHandle<Prio<J2KImage>> h = PQHandles[AssetId]; | 182 | m_priorities[mFreePriority] = pAssetID; |
303 | PQHandles.Remove(AssetId); | 183 | m_priorityresolver[(int)pPriority]++; |
304 | pq.Delete(h); | 184 | return mFreePriority; |
305 | } | ||
306 | } | 185 | } |
307 | } | ||
308 | 186 | ||
309 | 187 | ||
310 | /// <summary> | ||
311 | /// Adds an image to the queue and update priority | ||
312 | /// if the item is already in the queue, just update the priority | ||
313 | /// </summary> | ||
314 | /// <param name="AssetId">UUID of the asset</param> | ||
315 | /// <param name="priority">Priority to set</param> | ||
316 | private void AddQueueItem(UUID AssetId, int priority) | ||
317 | { | ||
318 | IPriorityQueueHandle<Prio<J2KImage>> h = null; | ||
319 | 188 | ||
320 | lock (PQHandles) | 189 | } |
190 | |||
191 | public void ProcessImageQueue(int count) | ||
192 | { | ||
193 | |||
194 | //Count is the number of textures we want to process in one go. | ||
195 | //As part of this class re-write, that number will probably rise | ||
196 | //since we're processing in a more efficient manner. | ||
197 | |||
198 | int numCollected = 0; | ||
199 | //First of all make sure our packet queue isn't above our threshold | ||
200 | if (m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount < 200) | ||
321 | { | 201 | { |
322 | if (PQHandles.ContainsKey(AssetId)) | 202 | |
203 | for (int x = m_priorities.Count - 1; x > -1; x--) | ||
323 | { | 204 | { |
324 | h = PQHandles[AssetId]; | 205 | |
325 | pq[h] = pq[h].SetPriority(priority); | 206 | J2KImage imagereq = m_imagestore[m_priorities.Values[x]]; |
207 | if (imagereq.m_decoded == true && !imagereq.m_completedSendAtCurrentDiscardLevel) | ||
208 | { | ||
326 | 209 | ||
327 | } | 210 | numCollected++; |
328 | else | 211 | //SendPackets will send up to ten packets per cycle |
329 | { | 212 | //m_log.Debug("Processing packet with priority of " + imagereq.m_designatedPriorityKey.ToString()); |
330 | J2KImage newreq = new J2KImage(); | 213 | if (imagereq.SendPackets(m_client)) |
331 | newreq.requestedUUID = AssetId; | 214 | { |
332 | pq.Add(ref h, new Prio<J2KImage>(newreq, priority)); | 215 | //Send complete |
333 | PQHandles.Add(AssetId, h); | 216 | imagereq.m_completedSendAtCurrentDiscardLevel = true; |
217 | //Re-assign priority to bottom | ||
218 | //Remove the old priority | ||
219 | m_priorities.Remove(imagereq.m_designatedPriorityKey); | ||
220 | int lowest; | ||
221 | if (m_priorities.Count > 0) | ||
222 | { | ||
223 | lowest = (int)m_priorities.Keys[0]; | ||
224 | lowest--; | ||
225 | } | ||
226 | else | ||
227 | { | ||
228 | lowest = -10000; | ||
229 | } | ||
230 | m_priorities.Add((double)lowest, imagereq.m_requestedUUID); | ||
231 | imagereq.m_designatedPriorityKey = (double)lowest; | ||
232 | if (m_priorityresolver.ContainsKey((int)lowest)) | ||
233 | { | ||
234 | m_priorityresolver[(int)lowest]++; | ||
235 | } | ||
236 | else | ||
237 | { | ||
238 | m_priorityresolver.Add((int)lowest, 0); | ||
239 | } | ||
240 | } | ||
241 | //m_log.Debug("...now has priority of " + imagereq.m_designatedPriorityKey.ToString()); | ||
242 | if (numCollected == count) | ||
243 | { | ||
244 | break; | ||
245 | } | ||
246 | } | ||
334 | } | 247 | } |
335 | } | 248 | } |
249 | |||
250 | |||
251 | |||
336 | } | 252 | } |
337 | 253 | ||
338 | /// <summary> | 254 | //Faux destructor |
339 | /// Okay, we're ending. Clean up on isle 9 | ||
340 | /// </summary> | ||
341 | public void Close() | 255 | public void Close() |
342 | { | 256 | { |
257 | |||
343 | m_shuttingdown = true; | 258 | m_shuttingdown = true; |
344 | 259 | m_j2kDecodeModule = null; | |
345 | lock (pq) | 260 | m_assetCache = null; |
346 | { | ||
347 | while (!pq.IsEmpty) | ||
348 | { | ||
349 | pq.DeleteMin(); | ||
350 | } | ||
351 | } | ||
352 | |||
353 | lock (PQHandles) | ||
354 | PQHandles.Clear(); | ||
355 | m_client = null; | 261 | m_client = null; |
356 | } | 262 | } |
263 | |||
264 | |||
357 | } | 265 | } |
358 | 266 | ||
359 | /// <summary> | 267 | /* |
360 | /// Image Data for this send | 268 | * |
361 | /// Encapsulates the image sending data and method | 269 | * J2KImage |
362 | /// </summary> | 270 | * |
271 | * We use this class to store image data and associated request data and attributes | ||
272 | * | ||
273 | * | ||
274 | * | ||
275 | */ | ||
276 | |||
363 | public class J2KImage | 277 | public class J2KImage |
364 | { | 278 | { |
365 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 279 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
366 | private AssetBase m_asset_ref = null; | 280 | public double m_designatedPriorityKey; |
367 | public volatile int LastPacketNum = 0; | 281 | public double m_requestedPriority = 0.0d; |
368 | public volatile int DiscardLimit = 0; | 282 | public uint m_lastSequence = 0; |
369 | public volatile bool dataRequested = false; | 283 | public uint m_requestedPacketNumber; |
284 | public sbyte m_requestedDiscardLevel; | ||
285 | public UUID m_requestedUUID; | ||
286 | public IJ2KDecoder m_j2kDecodeModule; | ||
287 | public IAssetCache m_assetCache; | ||
370 | public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; | 288 | public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; |
371 | 289 | public AssetBase m_MissingSubstitute = null; | |
372 | public const int FIRST_IMAGE_PACKET_SIZE = 600; | 290 | public bool m_decoded = false; |
373 | public const int IMAGE_PACKET_SIZE = 1000; | 291 | public bool m_completedSendAtCurrentDiscardLevel; |
374 | 292 | ||
375 | public volatile int DiscardLevel; | 293 | private sbyte m_discardLevel=-1; |
376 | public float Priority; | 294 | private uint m_packetNumber; |
377 | public volatile int CurrentPacket = 1; | 295 | private bool m_decoderequested = false; |
378 | public volatile int StopPacket; | 296 | private bool m_hasasset = false; |
379 | public bool Missing = false; | 297 | private bool m_asset_requested = false; |
380 | public bool J2KDecode = false; | 298 | private bool m_sentinfo = false; |
381 | public bool J2KDecodeWaiting = false; | 299 | private uint m_stopPacket = 0; |
382 | 300 | private const int cImagePacketSize = 1000; | |
383 | private volatile bool sendFirstPacket = true; | 301 | private const int cFirstPacketSize = 600; |
384 | 302 | private AssetBase m_asset = null; | |
385 | // Having this *AND* the AssetId allows us to remap asset data to AssetIds as necessary. | 303 | |
386 | public UUID requestedUUID = UUID.Zero; | 304 | |
387 | 305 | public uint m_pPacketNumber | |
388 | public J2KImage(AssetBase asset) | ||
389 | { | 306 | { |
390 | m_asset_ref = asset; | 307 | get { return m_packetNumber; } |
391 | } | 308 | } |
392 | 309 | public uint m_pStopPacketNumber | |
393 | public J2KImage() | ||
394 | { | 310 | { |
395 | 311 | get { return m_stopPacket; } | |
396 | } | 312 | } |
397 | 313 | ||
398 | public AssetBase asset | 314 | public byte[] Data |
399 | { | 315 | { |
400 | set { m_asset_ref = value; } | 316 | get { return m_asset.Data; } |
401 | } | 317 | } |
402 | 318 | ||
403 | // We make the asset a reference so that we don't duplicate the byte[] | 319 | public ushort TexturePacketCount() |
404 | // it's read only anyway, so no worries here | ||
405 | // we want to avoid duplicating the byte[] for the images at all costs to avoid memory bloat! :) | ||
406 | |||
407 | /// <summary> | ||
408 | /// ID of the AssetBase | ||
409 | /// </summary> | ||
410 | public UUID AssetId | ||
411 | { | 320 | { |
412 | get { return m_asset_ref.FullID; } | 321 | if (!m_decoded) |
322 | return 0; | ||
323 | return (ushort)(((m_asset.Data.Length - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1); | ||
413 | } | 324 | } |
414 | 325 | ||
415 | /// <summary> | 326 | public void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) |
416 | /// Asset Data | ||
417 | /// </summary> | ||
418 | public byte[] Data | ||
419 | { | 327 | { |
420 | get { return m_asset_ref.Data; } | 328 | Layers = layers; |
329 | m_decoded = true; | ||
330 | RunUpdate(); | ||
421 | } | 331 | } |
422 | 332 | ||
423 | /// <summary> | 333 | public void AssetDataCallback(UUID AssetID, AssetBase asset) |
424 | /// Returns true if we have the asset | ||
425 | /// </summary> | ||
426 | public bool HasData | ||
427 | { | 334 | { |
428 | get { return !(m_asset_ref == null); } | 335 | m_hasasset = true; |
336 | if (asset == null || asset.Data == null) | ||
337 | { | ||
338 | m_asset = m_MissingSubstitute; | ||
339 | } | ||
340 | else | ||
341 | { | ||
342 | m_asset = asset; | ||
343 | } | ||
344 | RunUpdate(); | ||
429 | } | 345 | } |
430 | 346 | ||
431 | /// <summary> | 347 | private int GetPacketForBytePosition(int bytePosition) |
432 | /// Called from the PriorityQueue handle .ToString(). Prints data on this asset | ||
433 | /// </summary> | ||
434 | /// <returns></returns> | ||
435 | public override string ToString() | ||
436 | { | 348 | { |
437 | return string.Format("ID:{0}, RD:{1}, CP:{2}", requestedUUID, HasData, CurrentPacket); | 349 | return ((bytePosition - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1; |
438 | } | 350 | } |
439 | 351 | public int LastPacketSize() | |
440 | /// <summary> | ||
441 | /// Returns the total number of packets needed to transfer this texture, | ||
442 | /// including the first packet of size FIRST_IMAGE_PACKET_SIZE | ||
443 | /// </summary> | ||
444 | /// <returns>Total number of packets needed to transfer this texture</returns> | ||
445 | public int TexturePacketCount() | ||
446 | { | 352 | { |
447 | if (!HasData) | 353 | if (m_packetNumber == 1) |
448 | return 0; | 354 | return m_asset.Data.Length; |
449 | return ((m_asset_ref.Data.Length - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | 355 | return (m_asset.Data.Length - cFirstPacketSize) % cImagePacketSize; |
450 | } | 356 | } |
451 | 357 | ||
452 | /// <summary> | ||
453 | /// Returns the current byte offset for this transfer, calculated from | ||
454 | /// the CurrentPacket | ||
455 | /// </summary> | ||
456 | /// <returns>Current byte offset for this transfer</returns> | ||
457 | public int CurrentBytePosition() | 358 | public int CurrentBytePosition() |
458 | { | 359 | { |
459 | if (CurrentPacket == 0) | 360 | if (m_packetNumber == 0) |
460 | return 0; | 361 | return 0; |
461 | if (CurrentPacket == 1) | 362 | if (m_packetNumber == 1) |
462 | return FIRST_IMAGE_PACKET_SIZE; | 363 | return cFirstPacketSize; |
463 | 364 | ||
464 | int result = FIRST_IMAGE_PACKET_SIZE + (CurrentPacket - 2) * IMAGE_PACKET_SIZE; | 365 | int result = cFirstPacketSize + ((int)m_packetNumber - 2) * cImagePacketSize; |
465 | if (result < 0) | 366 | if (result < 0) |
466 | { | 367 | { |
467 | result = FIRST_IMAGE_PACKET_SIZE; | 368 | result = cFirstPacketSize; |
468 | } | 369 | } |
469 | return result; | 370 | return result; |
470 | } | 371 | } |
471 | 372 | public bool SendFirstPacket(LLClientView client) | |
472 | /// <summary> | ||
473 | /// Returns the size, in bytes, of the last packet. This will be somewhere | ||
474 | /// between 1 and IMAGE_PACKET_SIZE bytes | ||
475 | /// </summary> | ||
476 | /// <returns>Size of the last packet in the transfer</returns> | ||
477 | public int LastPacketSize() | ||
478 | { | ||
479 | if (CurrentPacket == 1) | ||
480 | return m_asset_ref.Data.Length; | ||
481 | 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)); | ||
482 | } | ||
483 | |||
484 | /// <summary> | ||
485 | /// Find the packet number that contains a given byte position | ||
486 | /// </summary> | ||
487 | /// <param name="bytePosition">Byte position</param> | ||
488 | /// <returns>Packet number that contains the given byte position</returns> | ||
489 | int GetPacketForBytePosition(int bytePosition) | ||
490 | { | ||
491 | return ((bytePosition - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | ||
492 | } | ||
493 | |||
494 | /// <summary> | ||
495 | /// Updates the Image sending limits based on the discard | ||
496 | /// If we don't have any Layers, Send the full texture | ||
497 | /// </summary> | ||
498 | /// <param name="discardLevel">jpeg2000 discard level. 5-0</param> | ||
499 | /// <param name="packet">Which packet to start from</param> | ||
500 | public void Update(int discardLevel, int packet) | ||
501 | { | 373 | { |
502 | //Requests for 0 means that the client wants us to resend the whole image | ||
503 | //Requests for -1 mean 'update priority but don't change discard level' | ||
504 | 374 | ||
505 | if (packet == 0 || packet == -1) | 375 | // Do we have less then 1 packet's worth of data? |
506 | return; | 376 | if (m_asset.Data.Length <= cFirstPacketSize) |
507 | |||
508 | // Check if we've got layers | ||
509 | if (Layers.Length > 0) | ||
510 | { | 377 | { |
511 | DiscardLevel = Util.Clamp<int>(discardLevel, 0, Layers.Length - 1); | 378 | // Send only 1 packet |
512 | StopPacket = GetPacketForBytePosition(Layers[(Layers.Length - 1) - DiscardLevel].End); | 379 | client.SendImageFirstPart(1, m_requestedUUID, (uint)m_asset.Data.Length, m_asset.Data, 2); |
513 | CurrentPacket = Util.Clamp<int>(packet, 1, TexturePacketCount() - 1); | 380 | m_stopPacket = 0; |
514 | // sendFirstPacket = true; | 381 | return true; |
515 | } | 382 | } |
516 | else | 383 | else |
517 | { | 384 | { |
518 | // No layers, send full image | 385 | byte[] firstImageData = new byte[cFirstPacketSize]; |
519 | DiscardLevel = 0; | 386 | try |
520 | StopPacket = TexturePacketCount(); | 387 | { |
521 | CurrentPacket = Util.Clamp<int>(packet, 1, TexturePacketCount() - 1); | 388 | Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, (int)cFirstPacketSize); |
522 | } | 389 | client.SendImageFirstPart(TexturePacketCount(), m_requestedUUID, (uint)m_asset.Data.Length, firstImageData, 2); |
523 | } | ||
524 | |||
525 | /// <summary> | ||
526 | /// Sends a texture packet to the client. | ||
527 | /// </summary> | ||
528 | /// <param name="client">Client to send texture to</param> | ||
529 | /// <returns>true if a packet was sent, false if not</returns> | ||
530 | public bool SendPacket(LLClientView client) | ||
531 | { | ||
532 | // If we've hit the end of the send or if the client set -1, return false. | ||
533 | if (CurrentPacket > StopPacket || StopPacket == -1) | ||
534 | return false; | ||
535 | |||
536 | // The first packet contains up to 600 bytes and the details of the image. Number of packets, image size in bytes, etc. | ||
537 | // This packet only gets sent once unless we're restarting the transfer from 0! | ||
538 | if (sendFirstPacket) | ||
539 | { | ||
540 | sendFirstPacket = false; | ||
541 | |||
542 | // Do we have less then 1 packet's worth of data? | ||
543 | if (m_asset_ref.Data.Length <= FIRST_IMAGE_PACKET_SIZE) | ||
544 | { | ||
545 | // Send only 1 packet | ||
546 | client.SendImageFirstPart(1, requestedUUID , (uint)m_asset_ref.Data.Length, m_asset_ref.Data, 2); | ||
547 | CurrentPacket = 2; // Makes it so we don't come back to SendPacket and error trying to send a second packet | ||
548 | return true; | ||
549 | } | 390 | } |
550 | else | 391 | catch (Exception) |
551 | { | 392 | { |
552 | // Send first packet | 393 | m_log.Error("Texture block copy failed. Possibly out of memory?"); |
553 | byte[] firstImageData = new byte[FIRST_IMAGE_PACKET_SIZE]; | 394 | return true; |
554 | try { Buffer.BlockCopy(m_asset_ref.Data, 0, firstImageData, 0, FIRST_IMAGE_PACKET_SIZE); } | ||
555 | catch (Exception) | ||
556 | { | ||
557 | m_log.Error(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(), firstImageData.Length, FIRST_IMAGE_PACKET_SIZE)); | ||
558 | |||
559 | //m_log.Error("Texture data copy failed on first packet for " + m_asset_ref.FullID.ToString()); | ||
560 | //m_cancel = true; | ||
561 | //m_sending = false; | ||
562 | return false; | ||
563 | } | ||
564 | client.SendImageFirstPart((ushort)TexturePacketCount(), requestedUUID, (uint)m_asset_ref.Data.Length, firstImageData, 2); | ||
565 | ++CurrentPacket; // sets CurrentPacket to 1 | ||
566 | } | 395 | } |
567 | } | 396 | } |
397 | return false; | ||
568 | 398 | ||
569 | // figure out if we're on the last packet, if so, use the last packet size. If not, use 1000. | 399 | } |
570 | // we know that the total image size is greater then 1000 if we're here | 400 | private bool SendPacket(LLClientView client) |
571 | int imagePacketSize = (CurrentPacket == (TexturePacketCount() ) ) ? LastPacketSize() : IMAGE_PACKET_SIZE; | 401 | { |
572 | 402 | bool complete = false; | |
573 | //if (imagePacketSize > 0) | 403 | int imagePacketSize = ((int)m_packetNumber == (TexturePacketCount())) ? LastPacketSize() : cImagePacketSize; |
574 | // imagePacketSize = IMAGE_PACKET_SIZE; | ||
575 | //if (imagePacketSize != 1000) | ||
576 | // m_log.Debug("ENdPacket"); | ||
577 | //m_log.Debug(String.Format("srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize{3}", m_asset_ref.Data.Length, CurrentBytePosition(),0, imagePacketSize)); | ||
578 | |||
579 | bool atEnd = false; | ||
580 | 404 | ||
581 | // edge case | 405 | if ((CurrentBytePosition() + cImagePacketSize) > m_asset.Data.Length) |
582 | if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_asset_ref.Data.Length) | ||
583 | { | 406 | { |
584 | imagePacketSize = LastPacketSize(); | 407 | imagePacketSize = LastPacketSize(); |
585 | atEnd = true; | 408 | complete=true; |
586 | // edge case 2! | 409 | if ((CurrentBytePosition() + imagePacketSize) > m_asset.Data.Length) |
587 | if ((CurrentBytePosition() + imagePacketSize) > m_asset_ref.Data.Length) | ||
588 | { | 410 | { |
589 | imagePacketSize = m_asset_ref.Data.Length - CurrentBytePosition(); | 411 | imagePacketSize = m_asset.Data.Length - CurrentBytePosition(); |
590 | atEnd = true; | 412 | complete = true; |
591 | } | 413 | } |
592 | } | 414 | } |
415 | |||
416 | //It's concievable that the client might request packet one | ||
417 | //from a one packet image, which is really packet 0, | ||
418 | //which would leave us with a negative imagePacketSize.. | ||
419 | if (imagePacketSize > 0) | ||
420 | { | ||
421 | byte[] imageData = new byte[imagePacketSize]; | ||
422 | try | ||
423 | { | ||
424 | Buffer.BlockCopy(m_asset.Data, CurrentBytePosition(), imageData, 0, imagePacketSize); | ||
425 | } | ||
426 | catch (Exception e) | ||
427 | { | ||
428 | m_log.Error("Error copying texture block. Out of memory? imagePacketSize was " + imagePacketSize.ToString() + " on packet " + m_packetNumber.ToString() + " out of " + m_stopPacket.ToString() + ". Exception: " + e.ToString()); | ||
429 | return false; | ||
430 | } | ||
593 | 431 | ||
594 | byte[] imageData = new byte[imagePacketSize]; | 432 | //Send the packet |
595 | try { Buffer.BlockCopy(m_asset_ref.Data, CurrentBytePosition(), imageData, 0, imagePacketSize); } | 433 | client.SendImageNextPart((ushort)(m_packetNumber-1), m_requestedUUID, imageData); |
596 | catch (Exception e) | 434 | |
435 | } | ||
436 | if (complete) | ||
597 | { | 437 | { |
598 | m_log.Error(String.Format("Err: srcLen:{0}, BytePos:{1}, desLen:{2}, pktsize:{3}, currpak:{4}, stoppak:{5}, totalpak:{6}", m_asset_ref.Data.Length, CurrentBytePosition(), | ||
599 | imageData.Length, imagePacketSize, CurrentPacket, StopPacket, TexturePacketCount())); | ||
600 | m_log.Error(e.ToString()); | ||
601 | //m_log.Error("Texture data copy failed for " + m_asset_ref.FullID.ToString()); | ||
602 | //m_cancel = true; | ||
603 | //m_sending = false; | ||
604 | return false; | 438 | return false; |
605 | } | 439 | } |
440 | else | ||
441 | { | ||
442 | return true; | ||
443 | } | ||
606 | 444 | ||
607 | // Send next packet to the client | ||
608 | client.SendImageNextPart((ushort)(CurrentPacket - 1), requestedUUID, imageData); | ||
609 | 445 | ||
610 | ++CurrentPacket; | 446 | } |
447 | public bool SendPackets(LLClientView client) | ||
448 | { | ||
611 | 449 | ||
612 | if (atEnd) | 450 | if (!m_completedSendAtCurrentDiscardLevel) |
613 | CurrentPacket = StopPacket + 1; | 451 | { |
452 | if (m_packetNumber <= m_stopPacket) | ||
453 | { | ||
614 | 454 | ||
615 | return true; | 455 | bool SendMore = true; |
616 | } | 456 | if (!m_sentinfo || (m_packetNumber == 0)) |
457 | { | ||
458 | if (SendFirstPacket(client)) | ||
459 | { | ||
460 | SendMore = false; | ||
461 | } | ||
462 | m_sentinfo = true; | ||
463 | m_packetNumber++; | ||
464 | } | ||
617 | 465 | ||
618 | } | 466 | if (m_packetNumber < 2) |
467 | { | ||
468 | m_packetNumber = 2; | ||
469 | } | ||
470 | |||
471 | int count=0; | ||
472 | while (SendMore && count < 5 && m_packetNumber <= m_stopPacket) | ||
473 | { | ||
474 | count++; | ||
475 | SendMore = SendPacket(client); | ||
476 | m_packetNumber++; | ||
477 | } | ||
478 | if (m_packetNumber > m_stopPacket) | ||
479 | { | ||
619 | 480 | ||
620 | /// <summary> | 481 | return true; |
621 | /// Generic Priority Queue element | ||
622 | /// Contains a Priority and a Reference type Data Element | ||
623 | /// </summary> | ||
624 | /// <typeparam name="D">Reference type data element</typeparam> | ||
625 | struct Prio<D> : IComparable<Prio<D>> where D : class | ||
626 | { | ||
627 | public D data; | ||
628 | private int priority; | ||
629 | 482 | ||
630 | public Prio(D data, int priority) | 483 | } |
631 | { | ||
632 | this.data = data; | ||
633 | this.priority = priority; | ||
634 | } | ||
635 | 484 | ||
636 | public int CompareTo(Prio<D> that) | 485 | } |
637 | { | ||
638 | return priority.CompareTo(that.priority); | ||
639 | } | ||
640 | 486 | ||
641 | public bool Equals(Prio<D> that) | 487 | } |
642 | { | 488 | return false; |
643 | return priority == that.priority; | ||
644 | } | 489 | } |
645 | 490 | ||
646 | public static Prio<D> operator +(Prio<D> tp, int delta) | 491 | public void RunUpdate() |
647 | { | 492 | { |
648 | return new Prio<D>(tp.data, tp.priority + delta); | 493 | //This is where we decide what we need to update |
649 | } | 494 | //and assign the real discardLevel and packetNumber |
495 | //assuming of course that the connected client might be bonkers | ||
650 | 496 | ||
651 | public static bool operator <(Prio<D> tp, int check) | 497 | if (!m_hasasset) |
652 | { | 498 | { |
653 | return (tp.priority < check); | ||
654 | } | ||
655 | 499 | ||
656 | public static bool operator >(Prio<D> tp, int check) | 500 | if (!m_asset_requested) |
657 | { | 501 | { |
658 | return (tp.priority > check); | 502 | m_asset_requested = true; |
659 | } | 503 | m_assetCache.GetAsset(m_requestedUUID, AssetDataCallback, true); |
660 | 504 | ||
661 | public static Prio<D> operator -(Prio<D> tp, int delta) | 505 | } |
662 | { | 506 | |
663 | if (tp.priority - delta < 0) | 507 | } |
664 | return new Prio<D>(tp.data, tp.priority - delta); | ||
665 | else | 508 | else |
666 | return new Prio<D>(tp.data, 0); | 509 | { |
667 | } | ||
668 | 510 | ||
669 | public override String ToString() | ||
670 | { | ||
671 | return String.Format("{0}[{1}]", data, priority); | ||
672 | } | ||
673 | 511 | ||
674 | internal Prio<D> SetPriority(int pPriority) | 512 | if (!m_decoded) |
675 | { | 513 | { |
676 | return new Prio<D>(this.data, pPriority); | 514 | //We need to decode the requested image first |
515 | if (!m_decoderequested) | ||
516 | { | ||
517 | //Request decode | ||
518 | m_decoderequested = true; | ||
519 | // Do we have a jpeg decoder? | ||
520 | if (m_j2kDecodeModule != null) | ||
521 | { | ||
522 | // Send it off to the jpeg decoder | ||
523 | m_j2kDecodeModule.decode(m_requestedUUID, Data, J2KDecodedCallback); | ||
524 | |||
525 | } | ||
526 | else | ||
527 | { | ||
528 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
529 | } | ||
530 | } | ||
531 | |||
532 | } | ||
533 | else | ||
534 | { | ||
535 | |||
536 | |||
537 | //discardLevel of -1 means just update the priority | ||
538 | if (m_requestedDiscardLevel != -1) | ||
539 | { | ||
540 | |||
541 | //Evaluate the discard level | ||
542 | //First, is it positive? | ||
543 | if (m_requestedDiscardLevel >= 0) | ||
544 | { | ||
545 | if (m_requestedDiscardLevel > Layers.Length - 1) | ||
546 | { | ||
547 | m_discardLevel = (sbyte)(Layers.Length - 1); | ||
548 | } | ||
549 | else | ||
550 | { | ||
551 | m_discardLevel = m_requestedDiscardLevel; | ||
552 | } | ||
553 | |||
554 | //Calculate the m_stopPacket | ||
555 | if (Layers.Length > 0) | ||
556 | { | ||
557 | m_stopPacket = (uint)GetPacketForBytePosition(Layers[(Layers.Length - 1) - m_discardLevel].End); | ||
558 | } | ||
559 | else | ||
560 | { | ||
561 | m_stopPacket = TexturePacketCount(); | ||
562 | } | ||
563 | //Don't reset packet number unless we're waiting or it's ahead of us | ||
564 | if (m_completedSendAtCurrentDiscardLevel || m_requestedPacketNumber>m_packetNumber) | ||
565 | { | ||
566 | m_packetNumber = m_requestedPacketNumber; | ||
567 | } | ||
568 | |||
569 | if (m_packetNumber <= m_stopPacket) | ||
570 | { | ||
571 | m_completedSendAtCurrentDiscardLevel = false; | ||
572 | } | ||
573 | |||
574 | } | ||
575 | |||
576 | } | ||
577 | } | ||
578 | } | ||
677 | } | 579 | } |
580 | |||
678 | } | 581 | } |
679 | } | 582 | } |