diff --git a/OpenMetaverse/Types/CircularHashedQueue.cs b/OpenMetaverse/Types/CircularHashedQueue.cs
new file mode 100644
index 00000000..2624cbae
--- /dev/null
+++ b/OpenMetaverse/Types/CircularHashedQueue.cs
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2009, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+
+namespace OpenMetaverse
+{
+ ///
+ /// A circular queue with a hash-based Contains function
+ ///
+ public class CircularHashedQueue
+ {
+ public readonly T[] Items;
+
+ Dictionary hashSet;
+ int first;
+ int next;
+ int capacity;
+ object syncRoot;
+
+ public int First { get { return first; } }
+ public int Next { get { return next; } }
+
+ public CircularHashedQueue(int capacity)
+ {
+ this.capacity = capacity;
+ Items = new T[capacity];
+ hashSet = new Dictionary(capacity);
+ syncRoot = new object();
+ }
+
+ ///
+ /// Copy constructor
+ ///
+ /// Circular hashed queue to copy
+ public CircularHashedQueue(CircularHashedQueue queue)
+ {
+ lock (queue.syncRoot)
+ {
+ capacity = queue.capacity;
+ Items = new T[capacity];
+ hashSet = new Dictionary(queue.hashSet);
+ syncRoot = new object();
+
+ for (int i = 0; i < capacity; i++)
+ Items[i] = queue.Items[i];
+
+ first = queue.first;
+ next = queue.next;
+ }
+ }
+
+ public void Clear()
+ {
+ lock (syncRoot)
+ {
+ // Explicitly remove references to help garbage collection
+ for (int i = 0; i < capacity; i++)
+ Items[i] = default(T);
+
+ first = next;
+
+ hashSet.Clear();
+ }
+ }
+
+ public bool Contains(T value)
+ {
+ return hashSet.ContainsKey(value);
+ }
+
+ public void Enqueue(T value)
+ {
+ lock (syncRoot)
+ {
+ Items[next] = value;
+ next = (next + 1) % capacity;
+ if (next == first) first = (first + 1) % capacity;
+
+ hashSet[value] = true;
+ }
+ }
+
+ public bool TryEnqueue(T value)
+ {
+ lock (syncRoot)
+ {
+ if (hashSet.ContainsKey(value))
+ return false;
+
+ Items[next] = value;
+ next = (next + 1) % capacity;
+ if (next == first) first = (first + 1) % capacity;
+
+ hashSet[value] = true;
+
+ return true;
+ }
+ }
+
+ public T Dequeue()
+ {
+ lock (syncRoot)
+ {
+ T value = Items[first];
+ Items[first] = default(T);
+
+ if (first != next)
+ first = (first + 1) % capacity;
+
+ hashSet.Remove(value);
+
+ return value;
+ }
+ }
+
+ public T DequeueLast()
+ {
+ lock (syncRoot)
+ {
+ // If the next element is right behind the first element (queue is full),
+ // back up the first element by one
+ int firstTest = first - 1;
+ if (firstTest < 0) firstTest = capacity - 1;
+
+ if (firstTest == next)
+ {
+ --next;
+ if (next < 0) next = capacity - 1;
+
+ --first;
+ if (first < 0) first = capacity - 1;
+ }
+ else if (first != next)
+ {
+ --next;
+ if (next < 0) next = capacity - 1;
+ }
+
+ T value = Items[next];
+ Items[next] = default(T);
+
+ hashSet.Remove(value);
+
+ return value;
+ }
+ }
+ }
+}
diff --git a/OpenMetaverse/Types/TokenBucket.cs b/OpenMetaverse/Types/TokenBucket.cs
new file mode 100644
index 00000000..1325e641
--- /dev/null
+++ b/OpenMetaverse/Types/TokenBucket.cs
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2009, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+
+namespace OpenMetaverse
+{
+ ///
+ /// A hierarchical token bucket for bandwidth throttling. See
+ /// http://en.wikipedia.org/wiki/Token_bucket for more information
+ ///
+ public class TokenBucket
+ {
+ /// Parent bucket to this bucket, or null if this is a root
+ /// bucket
+ TokenBucket parent;
+ /// Size of the bucket in bytes. If zero, the bucket has
+ /// infinite capacity
+ int maxBurst;
+ /// Rate that the bucket fills, in bytes per millisecond. If
+ /// zero, the bucket always remains full
+ int tokensPerMS;
+ /// Time of the last drip, in system ticks
+ int lastDrip;
+ /// Number of tokens currently in the bucket
+ int content;
+
+ public int MaxBurst
+ {
+ get { return maxBurst; }
+ set { maxBurst = (value >= 0 ? value : 0); }
+ }
+ public int DripRate
+ {
+ get { return tokensPerMS * 1000; }
+ set
+ {
+ if (value == 0)
+ tokensPerMS = 0;
+ else if (value / 1000 <= 0)
+ tokensPerMS = 1; // 1 byte/ms is the minimum granularity
+ else
+ tokensPerMS = value / 1000;
+ }
+ }
+ public int Content { get { return content; } }
+
+ ///
+ /// Default constructor
+ ///
+ /// Parent bucket if this is a child bucket, or
+ /// null if this is a root bucket
+ /// Maximum size of the bucket in bytes, or
+ /// zero if this bucket has no maximum capacity
+ /// Rate that the bucket fills, in bytes per
+ /// second. If zero, the bucket always remains full
+ public TokenBucket(TokenBucket parent, int maxBurst, int dripRate)
+ {
+ this.parent = parent;
+ MaxBurst = maxBurst;
+ DripRate = dripRate;
+ lastDrip = Environment.TickCount & Int32.MaxValue;
+ }
+
+ ///
+ /// Remove a given number of tokens from the bucket
+ ///
+ /// Number of tokens to remove from the bucket
+ /// True if the requested number of tokens were removed from
+ /// the bucket, otherwise false
+ public bool RemoveTokens(int amount)
+ {
+ if (maxBurst == 0)
+ return true;
+
+ Drip();
+
+ if (content - amount >= 0)
+ {
+ if (parent != null && !parent.RemoveTokens(amount))
+ return false;
+
+ content -= amount;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Remove a given number of tokens from the bucket
+ ///
+ /// Number of tokens to remove from the bucket
+ /// True if tokens were added to the bucket
+ /// during this call, otherwise false
+ /// True if the requested number of tokens were removed from
+ /// the bucket, otherwise false
+ public bool RemoveTokens(int amount, out bool dripSucceeded)
+ {
+ if (maxBurst == 0)
+ {
+ dripSucceeded = true;
+ return true;
+ }
+
+ dripSucceeded = Drip();
+
+ if (content - amount >= 0)
+ {
+ if (parent != null && !parent.RemoveTokens(amount))
+ return false;
+
+ content -= amount;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Add tokens to the bucket over time. The number of tokens added each
+ /// call depends on the length of time that has passed since the last
+ /// call to Drip
+ ///
+ /// True if tokens were added to the bucket, otherwise false
+ bool Drip()
+ {
+ if (tokensPerMS == 0)
+ {
+ content = maxBurst;
+ return true;
+ }
+ else
+ {
+ int now = Environment.TickCount & Int32.MaxValue;
+ int deltaMS = now - lastDrip;
+
+ if (deltaMS <= 0)
+ {
+ if (deltaMS < 0)
+ lastDrip = now;
+ return false;
+ }
+
+ int dripAmount = deltaMS * tokensPerMS;
+
+ content = Math.Min(content + dripAmount, maxBurst);
+ lastDrip = now;
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/prebuild.xml b/prebuild.xml
index 998c3d92..7bd06044 100644
--- a/prebuild.xml
+++ b/prebuild.xml
@@ -164,6 +164,8 @@
+
+