aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs')
-rw-r--r--OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs567
1 files changed, 567 insertions, 0 deletions
diff --git a/OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs b/OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs
new file mode 100644
index 0000000..aed9465
--- /dev/null
+++ b/OpenSim/Region/ClientStack/FunSLUDP/LLPacketQueue.cs
@@ -0,0 +1,567 @@
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
28using System;
29using System.Collections.Generic;
30using System.Threading;
31using System.Timers;
32using libsecondlife;
33using libsecondlife.Packets;
34using OpenSim.Framework;
35using OpenSim.Framework.Statistics;
36using OpenSim.Framework.Statistics.Interfaces;
37using Timer=System.Timers.Timer;
38
39namespace OpenSim.Region.ClientStack.LindenUDP
40{
41 public class LLPacketQueue : IPullStatsProvider
42 {
43 private static readonly log4net.ILog m_log
44 = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
45
46 private bool m_enabled = true;
47
48 private BlockingQueue<LLQueItem> SendQueue;
49
50 private Queue<LLQueItem> IncomingPacketQueue;
51 private Queue<LLQueItem> OutgoingPacketQueue;
52 private Queue<LLQueItem> ResendOutgoingPacketQueue;
53 private Queue<LLQueItem> LandOutgoingPacketQueue;
54 private Queue<LLQueItem> WindOutgoingPacketQueue;
55 private Queue<LLQueItem> CloudOutgoingPacketQueue;
56 private Queue<LLQueItem> TaskOutgoingPacketQueue;
57 private Queue<LLQueItem> TaskLowpriorityPacketQueue;
58 private Queue<LLQueItem> TextureOutgoingPacketQueue;
59 private Queue<LLQueItem> AssetOutgoingPacketQueue;
60
61 // private Dictionary<uint, uint> PendingAcks = new Dictionary<uint, uint>();
62 // private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>();
63
64 // All throttle times and number of bytes are calculated by dividing by this value
65 // This value also determines how many times per throttletimems the timer will run
66 // If throttleimems is 1000 ms, then the timer will fire every 1000/7 milliseconds
67
68 private int throttleTimeDivisor = 7;
69
70 private int throttletimems = 1000;
71
72 private LLPacketThrottle ResendThrottle;
73 private LLPacketThrottle LandThrottle;
74 private LLPacketThrottle WindThrottle;
75 private LLPacketThrottle CloudThrottle;
76 private LLPacketThrottle TaskThrottle;
77 private LLPacketThrottle AssetThrottle;
78 private LLPacketThrottle TextureThrottle;
79 private LLPacketThrottle TotalThrottle;
80
81 // private long LastThrottle;
82 // private long ThrottleInterval;
83 private Timer throttleTimer;
84
85 private LLUUID m_agentId;
86
87 public LLPacketQueue(LLUUID agentId)
88 {
89 // While working on this, the BlockingQueue had me fooled for a bit.
90 // The Blocking queue causes the thread to stop until there's something
91 // in it to process. it's an on-purpose threadlock though because
92 // without it, the clientloop will suck up all sim resources.
93
94 SendQueue = new BlockingQueue<LLQueItem>();
95
96 IncomingPacketQueue = new Queue<LLQueItem>();
97 OutgoingPacketQueue = new Queue<LLQueItem>();
98 ResendOutgoingPacketQueue = new Queue<LLQueItem>();
99 LandOutgoingPacketQueue = new Queue<LLQueItem>();
100 WindOutgoingPacketQueue = new Queue<LLQueItem>();
101 CloudOutgoingPacketQueue = new Queue<LLQueItem>();
102 TaskOutgoingPacketQueue = new Queue<LLQueItem>();
103 TaskLowpriorityPacketQueue = new Queue<LLQueItem>();
104 TextureOutgoingPacketQueue = new Queue<LLQueItem>();
105 AssetOutgoingPacketQueue = new Queue<LLQueItem>();
106
107
108 // Set up the throttle classes (min, max, current) in bytes
109 ResendThrottle = new LLPacketThrottle(5000, 100000, 16000);
110 LandThrottle = new LLPacketThrottle(1000, 100000, 2000);
111 WindThrottle = new LLPacketThrottle(0, 100000, 0);
112 CloudThrottle = new LLPacketThrottle(0, 100000, 0);
113 TaskThrottle = new LLPacketThrottle(1000, 800000, 3000);
114 AssetThrottle = new LLPacketThrottle(1000, 800000, 1000);
115 TextureThrottle = new LLPacketThrottle(1000, 800000, 4000);
116 // Total Throttle trumps all
117 // Number of bytes allowed to go out per second. (256kbps per client)
118 TotalThrottle = new LLPacketThrottle(0, 1500000, 28000);
119
120 throttleTimer = new Timer((int) (throttletimems/throttleTimeDivisor));
121 throttleTimer.Elapsed += new ElapsedEventHandler(ThrottleTimerElapsed);
122 throttleTimer.Start();
123
124 // TIMERS needed for this
125 // LastThrottle = DateTime.Now.Ticks;
126 // ThrottleInterval = (long)(throttletimems/throttleTimeDivisor);
127
128 m_agentId = agentId;
129
130 if (StatsManager.SimExtraStats != null)
131 {
132 StatsManager.SimExtraStats.RegisterPacketQueueStatsProvider(m_agentId, this);
133 }
134 }
135
136 /* STANDARD QUEUE MANIPULATION INTERFACES */
137
138
139 public void Enqueue(LLQueItem item)
140 {
141 if (!m_enabled)
142 {
143 return;
144 }
145 // We could micro lock, but that will tend to actually
146 // probably be worse than just synchronizing on SendQueue
147
148 if (item == null)
149 {
150 SendQueue.Enqueue(item);
151 return;
152 }
153
154 if (item.Incoming)
155 {
156 SendQueue.PriorityEnqueue(item);
157 return;
158 }
159
160 lock (this)
161 {
162 switch (item.throttleType)
163 {
164 case ThrottleOutPacketType.Resend:
165 ThrottleCheck(ref ResendThrottle, ref ResendOutgoingPacketQueue, item);
166 break;
167 case ThrottleOutPacketType.Texture:
168 ThrottleCheck(ref TextureThrottle, ref TextureOutgoingPacketQueue, item);
169 break;
170 case ThrottleOutPacketType.Task:
171 ThrottleCheck(ref TaskThrottle, ref TaskOutgoingPacketQueue, item);
172 break;
173 case ThrottleOutPacketType.LowpriorityTask:
174 ThrottleCheck(ref TaskThrottle, ref TaskLowpriorityPacketQueue, item);
175 break;
176 case ThrottleOutPacketType.Land:
177 ThrottleCheck(ref LandThrottle, ref LandOutgoingPacketQueue, item);
178 break;
179 case ThrottleOutPacketType.Asset:
180 ThrottleCheck(ref AssetThrottle, ref AssetOutgoingPacketQueue, item);
181 break;
182 case ThrottleOutPacketType.Cloud:
183 ThrottleCheck(ref CloudThrottle, ref CloudOutgoingPacketQueue, item);
184 break;
185 case ThrottleOutPacketType.Wind:
186 ThrottleCheck(ref WindThrottle, ref WindOutgoingPacketQueue, item);
187 break;
188
189 default:
190 // Acknowledgements and other such stuff should go directly to the blocking Queue
191 // Throttling them may and likely 'will' be problematic
192 SendQueue.PriorityEnqueue(item);
193 break;
194 }
195 }
196 }
197
198 public LLQueItem Dequeue()
199 {
200 return SendQueue.Dequeue();
201 }
202
203 public void Flush()
204 {
205 lock (this)
206 {
207 while (PacketsWaiting())
208 {
209 //Now comes the fun part.. we dump all our elements into m_packetQueue that we've saved up.
210 if (ResendOutgoingPacketQueue.Count > 0)
211 {
212 SendQueue.Enqueue(ResendOutgoingPacketQueue.Dequeue());
213 }
214 if (LandOutgoingPacketQueue.Count > 0)
215 {
216 SendQueue.Enqueue(LandOutgoingPacketQueue.Dequeue());
217 }
218 if (WindOutgoingPacketQueue.Count > 0)
219 {
220 SendQueue.Enqueue(WindOutgoingPacketQueue.Dequeue());
221 }
222 if (CloudOutgoingPacketQueue.Count > 0)
223 {
224 SendQueue.Enqueue(CloudOutgoingPacketQueue.Dequeue());
225 }
226 if (TaskOutgoingPacketQueue.Count > 0)
227 {
228 SendQueue.PriorityEnqueue(TaskOutgoingPacketQueue.Dequeue());
229 }
230 if (TaskLowpriorityPacketQueue.Count > 0)
231 {
232 SendQueue.Enqueue(TaskLowpriorityPacketQueue.Dequeue());
233 }
234 if (TextureOutgoingPacketQueue.Count > 0)
235 {
236 SendQueue.Enqueue(TextureOutgoingPacketQueue.Dequeue());
237 }
238 if (AssetOutgoingPacketQueue.Count > 0)
239 {
240 SendQueue.Enqueue(AssetOutgoingPacketQueue.Dequeue());
241 }
242 }
243 // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets");
244 }
245 }
246
247 public void Close()
248 {
249 Flush();
250
251 m_enabled = false;
252 throttleTimer.Stop();
253
254 if (StatsManager.SimExtraStats != null)
255 {
256 StatsManager.SimExtraStats.DeregisterPacketQueueStatsProvider(m_agentId);
257 }
258 }
259
260 private void ResetCounters()
261 {
262 ResendThrottle.Reset();
263 LandThrottle.Reset();
264 WindThrottle.Reset();
265 CloudThrottle.Reset();
266 TaskThrottle.Reset();
267 AssetThrottle.Reset();
268 TextureThrottle.Reset();
269 TotalThrottle.Reset();
270 }
271
272 private bool PacketsWaiting()
273 {
274 return (ResendOutgoingPacketQueue.Count > 0 ||
275 LandOutgoingPacketQueue.Count > 0 ||
276 WindOutgoingPacketQueue.Count > 0 ||
277 CloudOutgoingPacketQueue.Count > 0 ||
278 TaskOutgoingPacketQueue.Count > 0 ||
279 TaskLowpriorityPacketQueue.Count > 0 ||
280 AssetOutgoingPacketQueue.Count > 0 ||
281 TextureOutgoingPacketQueue.Count > 0);
282 }
283
284 public void ProcessThrottle()
285 {
286 // I was considering this.. Will an event fire if the thread it's on is blocked?
287
288 // Then I figured out.. it doesn't really matter.. because this thread won't be blocked for long
289 // The General overhead of the UDP protocol gets sent to the queue un-throttled by this
290 // so This'll pick up about around the right time.
291
292 int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once.
293 int throttleLoops = 0;
294
295 // We're going to dequeue all of the saved up packets until
296 // we've hit the throttle limit or there's no more packets to send
297 lock (this)
298 {
299 ResetCounters();
300 // m_log.Info("[THROTTLE]: Entering Throttle");
301 while (TotalThrottle.UnderLimit() && PacketsWaiting() &&
302 (throttleLoops <= MaxThrottleLoops))
303 {
304 throttleLoops++;
305 //Now comes the fun part.. we dump all our elements into m_packetQueue that we've saved up.
306 if (ResendThrottle.UnderLimit() && ResendOutgoingPacketQueue.Count > 0)
307 {
308 LLQueItem qpack = ResendOutgoingPacketQueue.Dequeue();
309
310 SendQueue.Enqueue(qpack);
311 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
312 ResendThrottle.Add(qpack.Packet.ToBytes().Length);
313 }
314 if (LandThrottle.UnderLimit() && LandOutgoingPacketQueue.Count > 0)
315 {
316 LLQueItem qpack = LandOutgoingPacketQueue.Dequeue();
317
318 SendQueue.Enqueue(qpack);
319 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
320 LandThrottle.Add(qpack.Packet.ToBytes().Length);
321 }
322 if (WindThrottle.UnderLimit() && WindOutgoingPacketQueue.Count > 0)
323 {
324 LLQueItem qpack = WindOutgoingPacketQueue.Dequeue();
325
326 SendQueue.Enqueue(qpack);
327 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
328 WindThrottle.Add(qpack.Packet.ToBytes().Length);
329 }
330 if (CloudThrottle.UnderLimit() && CloudOutgoingPacketQueue.Count > 0)
331 {
332 LLQueItem qpack = CloudOutgoingPacketQueue.Dequeue();
333
334 SendQueue.Enqueue(qpack);
335 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
336 CloudThrottle.Add(qpack.Packet.ToBytes().Length);
337 }
338 if (TaskThrottle.UnderLimit() && (TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0))
339 {
340 LLQueItem qpack;
341 if (TaskOutgoingPacketQueue.Count > 0)
342 {
343 qpack = TaskOutgoingPacketQueue.Dequeue();
344 SendQueue.PriorityEnqueue(qpack);
345 }
346 else
347 {
348 qpack = TaskLowpriorityPacketQueue.Dequeue();
349 SendQueue.Enqueue(qpack);
350 }
351 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
352 TaskThrottle.Add(qpack.Packet.ToBytes().Length);
353 }
354 if (TextureThrottle.UnderLimit() && TextureOutgoingPacketQueue.Count > 0)
355 {
356 LLQueItem qpack = TextureOutgoingPacketQueue.Dequeue();
357
358 SendQueue.Enqueue(qpack);
359 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
360 TextureThrottle.Add(qpack.Packet.ToBytes().Length);
361 }
362 if (AssetThrottle.UnderLimit() && AssetOutgoingPacketQueue.Count > 0)
363 {
364 LLQueItem qpack = AssetOutgoingPacketQueue.Dequeue();
365
366 SendQueue.Enqueue(qpack);
367 TotalThrottle.Add(qpack.Packet.ToBytes().Length);
368 AssetThrottle.Add(qpack.Packet.ToBytes().Length);
369 }
370 }
371 // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets");
372 }
373 }
374
375 private void ThrottleTimerElapsed(object sender, ElapsedEventArgs e)
376 {
377 // just to change the signature, and that ProcessThrottle
378 // will be used elsewhere possibly
379 ProcessThrottle();
380 }
381
382 private void ThrottleCheck(ref LLPacketThrottle throttle, ref Queue<LLQueItem> q, LLQueItem item)
383 {
384 // The idea.. is if the packet throttle queues are empty
385 // and the client is under throttle for the type. Queue
386 // it up directly. This basically short cuts having to
387 // wait for the timer to fire to put things into the
388 // output queue
389
390 if ((q.Count == 0) && (throttle.UnderLimit()))
391 {
392 Monitor.Enter(this);
393 throttle.Add(item.Packet.ToBytes().Length);
394 TotalThrottle.Add(item.Packet.ToBytes().Length);
395 SendQueue.Enqueue(item);
396 Monitor.Pulse(this);
397 Monitor.Exit(this);
398 }
399 else
400 {
401 q.Enqueue(item);
402 }
403 }
404
405
406 private static int ScaleThrottle(int value, int curmax, int newmax)
407 {
408 return (value / curmax) * newmax;
409 }
410
411 public byte[] GetThrottlesPacked(float multiplier)
412 {
413 int singlefloat = 4;
414 float tResend = ResendThrottle.Throttle*multiplier;
415 float tLand = LandThrottle.Throttle*multiplier;
416 float tWind = WindThrottle.Throttle*multiplier;
417 float tCloud = CloudThrottle.Throttle*multiplier;
418 float tTask = TaskThrottle.Throttle*multiplier;
419 float tTexture = TextureThrottle.Throttle*multiplier;
420 float tAsset = AssetThrottle.Throttle*multiplier;
421
422 byte[] throttles = new byte[singlefloat*7];
423 int i = 0;
424 Buffer.BlockCopy(BitConverter.GetBytes(tResend), 0, throttles, singlefloat*i, singlefloat);
425 i++;
426 Buffer.BlockCopy(BitConverter.GetBytes(tLand), 0, throttles, singlefloat*i, singlefloat);
427 i++;
428 Buffer.BlockCopy(BitConverter.GetBytes(tWind), 0, throttles, singlefloat*i, singlefloat);
429 i++;
430 Buffer.BlockCopy(BitConverter.GetBytes(tCloud), 0, throttles, singlefloat*i, singlefloat);
431 i++;
432 Buffer.BlockCopy(BitConverter.GetBytes(tTask), 0, throttles, singlefloat*i, singlefloat);
433 i++;
434 Buffer.BlockCopy(BitConverter.GetBytes(tTexture), 0, throttles, singlefloat*i, singlefloat);
435 i++;
436 Buffer.BlockCopy(BitConverter.GetBytes(tAsset), 0, throttles, singlefloat*i, singlefloat);
437
438 return throttles;
439 }
440
441 public void SetThrottleFromClient(byte[] throttle)
442 {
443 // From mantis http://opensimulator.org/mantis/view.php?id=1374
444 // it appears that sometimes we are receiving empty throttle byte arrays.
445 // TODO: Investigate this behaviour
446 if (throttle.Length == 0)
447 {
448 m_log.Warn("[PACKET QUEUE]: SetThrottleFromClient unexpectedly received a throttle byte array containing no elements!");
449 return;
450 }
451
452 int tResend = -1;
453 int tLand = -1;
454 int tWind = -1;
455 int tCloud = -1;
456 int tTask = -1;
457 int tTexture = -1;
458 int tAsset = -1;
459 int tall = -1;
460 int singlefloat = 4;
461
462 //Agent Throttle Block contains 7 single floatingpoint values.
463 int j = 0;
464
465 // Some Systems may be big endian...
466 // it might be smart to do this check more often...
467 if (!BitConverter.IsLittleEndian)
468 for (int i = 0; i < 7; i++)
469 Array.Reverse(throttle, j + i*singlefloat, singlefloat);
470
471 // values gotten from libsecondlife.org/wiki/Throttle. Thanks MW_
472 // bytes
473 // Convert to integer, since.. the full fp space isn't used.
474 tResend = (int) BitConverter.ToSingle(throttle, j);
475 j += singlefloat;
476 tLand = (int) BitConverter.ToSingle(throttle, j);
477 j += singlefloat;
478 tWind = (int) BitConverter.ToSingle(throttle, j);
479 j += singlefloat;
480 tCloud = (int) BitConverter.ToSingle(throttle, j);
481 j += singlefloat;
482 tTask = (int) BitConverter.ToSingle(throttle, j);
483 j += singlefloat;
484 tTexture = (int) BitConverter.ToSingle(throttle, j);
485 j += singlefloat;
486 tAsset = (int) BitConverter.ToSingle(throttle, j);
487
488 tall = tResend + tLand + tWind + tCloud + tTask + tTexture + tAsset;
489 /*
490 m_log.Info("[CLIENT]: Client AgentThrottle - Got throttle:resendbytes=" + tResend +
491 " landbytes=" + tLand +
492 " windbytes=" + tWind +
493 " cloudbytes=" + tCloud +
494 " taskbytes=" + tTask +
495 " texturebytes=" + tTexture +
496 " Assetbytes=" + tAsset +
497 " Allbytes=" + tall);
498 */
499
500 // Total Sanity
501 // Make sure that the client sent sane total values.
502
503 // If the client didn't send acceptable values....
504 // Scale the clients values down until they are acceptable.
505
506 if (tall <= TotalThrottle.Max)
507 {
508 ResendThrottle.Throttle = tResend;
509 LandThrottle.Throttle = tLand;
510 WindThrottle.Throttle = tWind;
511 CloudThrottle.Throttle = tCloud;
512 TaskThrottle.Throttle = tTask;
513 TextureThrottle.Throttle = tTexture;
514 AssetThrottle.Throttle = tAsset;
515 TotalThrottle.Throttle = tall;
516 }
517// else if (tall < 1)
518// {
519// // client is stupid, penalize him by minning everything
520// ResendThrottle.Throttle = ResendThrottle.Min;
521// LandThrottle.Throttle = LandThrottle.Min;
522// WindThrottle.Throttle = WindThrottle.Min;
523// CloudThrottle.Throttle = CloudThrottle.Min;
524// TaskThrottle.Throttle = TaskThrottle.Min;
525// TextureThrottle.Throttle = TextureThrottle.Min;
526// AssetThrottle.Throttle = AssetThrottle.Min;
527// TotalThrottle.Throttle = TotalThrottle.Min;
528// }
529 else
530 {
531 // we're over so figure out percentages and use those
532 ResendThrottle.Throttle = tResend;
533
534 LandThrottle.Throttle = ScaleThrottle(tLand, tall, TotalThrottle.Max);
535 WindThrottle.Throttle = ScaleThrottle(tWind, tall, TotalThrottle.Max);
536 CloudThrottle.Throttle = ScaleThrottle(tCloud, tall, TotalThrottle.Max);
537 TaskThrottle.Throttle = ScaleThrottle(tTask, tall, TotalThrottle.Max);
538 TextureThrottle.Throttle = ScaleThrottle(tTexture, tall, TotalThrottle.Max);
539 AssetThrottle.Throttle = ScaleThrottle(tAsset, tall, TotalThrottle.Max);
540 TotalThrottle.Throttle = TotalThrottle.Max;
541 }
542 // effectively wiggling the slider causes things reset
543// ResetCounters(); // DO NOT reset, better to send less for one period than more
544 }
545
546 // See IPullStatsProvider
547 public string GetStats()
548 {
549 return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}",
550 SendQueue.Count(),
551 IncomingPacketQueue.Count,
552 OutgoingPacketQueue.Count,
553 ResendOutgoingPacketQueue.Count,
554 LandOutgoingPacketQueue.Count,
555 WindOutgoingPacketQueue.Count,
556 CloudOutgoingPacketQueue.Count,
557 TaskOutgoingPacketQueue.Count,
558 TextureOutgoingPacketQueue.Count,
559 AssetOutgoingPacketQueue.Count);
560 }
561
562 public LLQueItem[] GetQueueArray()
563 {
564 return SendQueue.GetQueueArray();
565 }
566 }
567}