Merge branch 'queuetest' into careminster-presence-refactor
commit
a6c53b1ba2
|
@ -42,22 +42,40 @@ namespace OpenSim.Framework
|
||||||
|
|
||||||
public delegate bool UpdatePriorityHandler(ref uint priority, ISceneEntity entity);
|
public delegate bool UpdatePriorityHandler(ref uint priority, ISceneEntity entity);
|
||||||
|
|
||||||
// Heap[0] for self updates
|
/// <summary>
|
||||||
// Heap[1..12] for entity updates
|
/// Total number of queues (priorities) available
|
||||||
|
/// </summary>
|
||||||
public const uint NumberOfQueues = 12;
|
public const uint NumberOfQueues = 12;
|
||||||
public const uint ImmediateQueue = 0;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of queuest (priorities) that are processed immediately
|
||||||
|
/// </summary.
|
||||||
|
public const uint NumberOfImmediateQueues = 2;
|
||||||
|
|
||||||
private MinHeap<MinHeapItem>[] m_heaps = new MinHeap<MinHeapItem>[NumberOfQueues];
|
private MinHeap<MinHeapItem>[] m_heaps = new MinHeap<MinHeapItem>[NumberOfQueues];
|
||||||
private Dictionary<uint, LookupItem> m_lookupTable;
|
private Dictionary<uint, LookupItem> m_lookupTable;
|
||||||
|
|
||||||
|
// internal state used to ensure the deqeues are spread across the priority
|
||||||
|
// queues "fairly". queuecounts is the amount to pull from each queue in
|
||||||
|
// each pass. weighted towards the higher priority queues
|
||||||
private uint m_nextQueue = 0;
|
private uint m_nextQueue = 0;
|
||||||
|
private uint m_countFromQueue = 0;
|
||||||
|
private uint[] m_queueCounts = { 8, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1 };
|
||||||
|
|
||||||
|
// next request is a counter of the number of updates queued, it provides
|
||||||
|
// a total ordering on the updates coming through the queue and is more
|
||||||
|
// lightweight (and more discriminating) than tick count
|
||||||
private UInt64 m_nextRequest = 0;
|
private UInt64 m_nextRequest = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lock for enqueue and dequeue operations on the priority queue
|
||||||
|
/// </summary>
|
||||||
private object m_syncRoot = new object();
|
private object m_syncRoot = new object();
|
||||||
public object SyncRoot {
|
public object SyncRoot {
|
||||||
get { return this.m_syncRoot; }
|
get { return this.m_syncRoot; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region constructor
|
||||||
public PriorityQueue() : this(MinHeap<MinHeapItem>.DEFAULT_CAPACITY) { }
|
public PriorityQueue() : this(MinHeap<MinHeapItem>.DEFAULT_CAPACITY) { }
|
||||||
|
|
||||||
public PriorityQueue(int capacity)
|
public PriorityQueue(int capacity)
|
||||||
|
@ -66,8 +84,16 @@ namespace OpenSim.Framework
|
||||||
|
|
||||||
for (int i = 0; i < m_heaps.Length; ++i)
|
for (int i = 0; i < m_heaps.Length; ++i)
|
||||||
m_heaps[i] = new MinHeap<MinHeapItem>(capacity);
|
m_heaps[i] = new MinHeap<MinHeapItem>(capacity);
|
||||||
}
|
|
||||||
|
|
||||||
|
m_nextQueue = NumberOfImmediateQueues;
|
||||||
|
m_countFromQueue = m_queueCounts[m_nextQueue];
|
||||||
|
}
|
||||||
|
#endregion Constructor
|
||||||
|
|
||||||
|
#region PublicMethods
|
||||||
|
/// <summary>
|
||||||
|
/// Return the number of items in the queues
|
||||||
|
/// </summary>
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -75,10 +101,14 @@ namespace OpenSim.Framework
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < m_heaps.Length; ++i)
|
for (int i = 0; i < m_heaps.Length; ++i)
|
||||||
count += m_heaps[i].Count;
|
count += m_heaps[i].Count;
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue an item into the specified priority queue
|
||||||
|
/// </summary>
|
||||||
public bool Enqueue(uint pqueue, IEntityUpdate value)
|
public bool Enqueue(uint pqueue, IEntityUpdate value)
|
||||||
{
|
{
|
||||||
LookupItem lookup;
|
LookupItem lookup;
|
||||||
|
@ -100,32 +130,62 @@ namespace OpenSim.Framework
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove an item from one of the queues. Specifically, it removes the
|
||||||
|
/// oldest item from the next queue in order to provide fair access to
|
||||||
|
/// all of the queues
|
||||||
|
/// </summary>
|
||||||
public bool TryDequeue(out IEntityUpdate value, out Int32 timeinqueue)
|
public bool TryDequeue(out IEntityUpdate value, out Int32 timeinqueue)
|
||||||
{
|
{
|
||||||
// If there is anything in priority queue 0, return it first no
|
// If there is anything in priority queue 0, return it first no
|
||||||
// matter what else. Breaks fairness. But very useful.
|
// matter what else. Breaks fairness. But very useful.
|
||||||
if (m_heaps[ImmediateQueue].Count > 0)
|
for (int iq = 0; iq < NumberOfImmediateQueues; iq++)
|
||||||
{
|
{
|
||||||
MinHeapItem item = m_heaps[ImmediateQueue].RemoveMin();
|
if (m_heaps[iq].Count > 0)
|
||||||
|
{
|
||||||
|
MinHeapItem item = m_heaps[iq].RemoveMin();
|
||||||
|
m_lookupTable.Remove(item.Value.Entity.LocalId);
|
||||||
|
timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime);
|
||||||
|
value = item.Value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To get the fair queing, we cycle through each of the
|
||||||
|
// queues when finding an element to dequeue.
|
||||||
|
// We pull (NumberOfQueues - QueueIndex) items from each queue in order
|
||||||
|
// to give lower numbered queues a higher priority and higher percentage
|
||||||
|
// of the bandwidth.
|
||||||
|
|
||||||
|
// Check for more items to be pulled from the current queue
|
||||||
|
if (m_heaps[m_nextQueue].Count > 0 && m_countFromQueue > 0)
|
||||||
|
{
|
||||||
|
m_countFromQueue--;
|
||||||
|
|
||||||
|
MinHeapItem item = m_heaps[m_nextQueue].RemoveMin();
|
||||||
m_lookupTable.Remove(item.Value.Entity.LocalId);
|
m_lookupTable.Remove(item.Value.Entity.LocalId);
|
||||||
timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime);
|
timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime);
|
||||||
value = item.Value;
|
value = item.Value;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the next non-immediate queue with updates in it
|
||||||
for (int i = 0; i < NumberOfQueues; ++i)
|
for (int i = 0; i < NumberOfQueues; ++i)
|
||||||
{
|
{
|
||||||
// To get the fair queing, we cycle through each of the
|
m_nextQueue = (uint)((m_nextQueue + 1) % NumberOfQueues);
|
||||||
// queues when finding an element to dequeue, this code
|
m_countFromQueue = m_queueCounts[m_nextQueue];
|
||||||
// assumes that the distribution of updates in the queues
|
|
||||||
// is polynomial, probably quadractic (eg distance of PI * R^2)
|
|
||||||
uint h = (uint)((m_nextQueue + i) % NumberOfQueues);
|
|
||||||
if (m_heaps[h].Count > 0)
|
|
||||||
{
|
|
||||||
m_nextQueue = (uint)((h + 1) % NumberOfQueues);
|
|
||||||
|
|
||||||
MinHeapItem item = m_heaps[h].RemoveMin();
|
// if this is one of the immediate queues, just skip it
|
||||||
|
if (m_nextQueue < NumberOfImmediateQueues)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (m_heaps[m_nextQueue].Count > 0)
|
||||||
|
{
|
||||||
|
m_countFromQueue--;
|
||||||
|
|
||||||
|
MinHeapItem item = m_heaps[m_nextQueue].RemoveMin();
|
||||||
m_lookupTable.Remove(item.Value.Entity.LocalId);
|
m_lookupTable.Remove(item.Value.Entity.LocalId);
|
||||||
timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime);
|
timeinqueue = Util.EnvironmentTickCountSubtract(item.EntryTime);
|
||||||
value = item.Value;
|
value = item.Value;
|
||||||
|
@ -139,6 +199,10 @@ namespace OpenSim.Framework
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reapply the prioritization function to each of the updates currently
|
||||||
|
/// stored in the priority queues.
|
||||||
|
/// </summary
|
||||||
public void Reprioritize(UpdatePriorityHandler handler)
|
public void Reprioritize(UpdatePriorityHandler handler)
|
||||||
{
|
{
|
||||||
MinHeapItem item;
|
MinHeapItem item;
|
||||||
|
@ -174,17 +238,18 @@ namespace OpenSim.Framework
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
string s = "";
|
string s = "";
|
||||||
for (int i = 0; i < NumberOfQueues; i++)
|
for (int i = 0; i < NumberOfQueues; i++)
|
||||||
{
|
s += String.Format("{0,7} ",m_heaps[i].Count);
|
||||||
if (s != "") s += ",";
|
|
||||||
s += m_heaps[i].Count.ToString();
|
|
||||||
}
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion PublicMethods
|
||||||
|
|
||||||
#region MinHeapItem
|
#region MinHeapItem
|
||||||
private struct MinHeapItem : IComparable<MinHeapItem>
|
private struct MinHeapItem : IComparable<MinHeapItem>
|
||||||
{
|
{
|
||||||
|
|
|
@ -394,6 +394,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
public ulong ActiveGroupPowers { get { return m_activeGroupPowers; } }
|
public ulong ActiveGroupPowers { get { return m_activeGroupPowers; } }
|
||||||
public bool IsGroupMember(UUID groupID) { return m_groupPowers.ContainsKey(groupID); }
|
public bool IsGroupMember(UUID groupID) { return m_groupPowers.ContainsKey(groupID); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity update queues
|
||||||
|
/// </summary>
|
||||||
|
public PriorityQueue EntityUpdateQueue { get { return m_entityUpdates; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First name of the agent/avatar represented by the client
|
/// First name of the agent/avatar represented by the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3625,7 +3630,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// Remove the update packet from the list of packets waiting for acknowledgement
|
// Remove the update packet from the list of packets waiting for acknowledgement
|
||||||
// because we are requeuing the list of updates. They will be resent in new packets
|
// because we are requeuing the list of updates. They will be resent in new packets
|
||||||
// with the most recent state and priority.
|
// with the most recent state and priority.
|
||||||
m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber, 0, true);
|
m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber);
|
||||||
|
|
||||||
|
// Count this as a resent packet since we are going to requeue all of the updates contained in it
|
||||||
|
Interlocked.Increment(ref m_udpClient.PacketsResent);
|
||||||
|
|
||||||
foreach (EntityUpdate update in updates)
|
foreach (EntityUpdate update in updates)
|
||||||
ResendPrimUpdate(update);
|
ResendPrimUpdate(update);
|
||||||
}
|
}
|
||||||
|
@ -4092,7 +4101,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// Remove the update packet from the list of packets waiting for acknowledgement
|
// Remove the update packet from the list of packets waiting for acknowledgement
|
||||||
// because we are requeuing the list of updates. They will be resent in new packets
|
// because we are requeuing the list of updates. They will be resent in new packets
|
||||||
// with the most recent state.
|
// with the most recent state.
|
||||||
m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber, 0, true);
|
m_udpClient.NeedAcks.Remove(oPacket.SequenceNumber);
|
||||||
|
|
||||||
|
// Count this as a resent packet since we are going to requeue all of the updates contained in it
|
||||||
|
Interlocked.Increment(ref m_udpClient.PacketsResent);
|
||||||
|
|
||||||
foreach (ObjectPropertyUpdate update in updates)
|
foreach (ObjectPropertyUpdate update in updates)
|
||||||
ResendPropertyUpdate(update);
|
ResendPropertyUpdate(update);
|
||||||
}
|
}
|
||||||
|
@ -11513,7 +11526,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public byte[] GetThrottlesPacked(float multiplier)
|
public byte[] GetThrottlesPacked(float multiplier)
|
||||||
{
|
{
|
||||||
return m_udpClient.GetThrottlesPacked();
|
return m_udpClient.GetThrottlesPacked(multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -182,9 +182,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
m_maxRTO = maxRTO;
|
m_maxRTO = maxRTO;
|
||||||
|
|
||||||
// Create a token bucket throttle for this client that has the scene token bucket as a parent
|
// Create a token bucket throttle for this client that has the scene token bucket as a parent
|
||||||
m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.TotalLimit);
|
m_throttleClient = new AdaptiveTokenBucket(parentThrottle, rates.Total, rates.AdaptiveThrottlesEnabled);
|
||||||
// Create a token bucket throttle for the total categary with the client bucket as a throttle
|
// Create a token bucket throttle for the total categary with the client bucket as a throttle
|
||||||
m_throttleCategory = new TokenBucket(m_throttleClient, rates.TotalLimit);
|
m_throttleCategory = new TokenBucket(m_throttleClient, rates.Total);
|
||||||
// Create an array of token buckets for this clients different throttle categories
|
// Create an array of token buckets for this clients different throttle categories
|
||||||
m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT];
|
m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT];
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// Initialize the packet outboxes, where packets sit while they are waiting for tokens
|
// Initialize the packet outboxes, where packets sit while they are waiting for tokens
|
||||||
m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue<OutgoingPacket>();
|
m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue<OutgoingPacket>();
|
||||||
// Initialize the token buckets that control the throttling for each category
|
// Initialize the token buckets that control the throttling for each category
|
||||||
m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetLimit(type));
|
m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetRate(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default the retransmission timeout to three seconds
|
// Default the retransmission timeout to three seconds
|
||||||
|
@ -229,26 +229,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
/// <returns>Information about the client connection</returns>
|
/// <returns>Information about the client connection</returns>
|
||||||
public ClientInfo GetClientInfo()
|
public ClientInfo GetClientInfo()
|
||||||
{
|
{
|
||||||
///<mic>
|
|
||||||
TokenBucket tb;
|
|
||||||
|
|
||||||
tb = m_throttleClient.Parent;
|
|
||||||
m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest,"ROOT");
|
|
||||||
|
|
||||||
tb = m_throttleClient;
|
|
||||||
m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest," CLIENT");
|
|
||||||
|
|
||||||
tb = m_throttleCategory;
|
|
||||||
m_log.WarnFormat("[TOKENS] {3}: Actual={0},Request={1},TotalRequest={2}",tb.DripRate,tb.RequestedDripRate,tb.TotalDripRequest," CATEGORY");
|
|
||||||
|
|
||||||
for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++)
|
|
||||||
{
|
|
||||||
tb = m_throttleCategories[i];
|
|
||||||
m_log.WarnFormat("[TOKENS] {4} <{0}:{1}>: Actual={2},Requested={3}",AgentID,i,tb.DripRate,tb.RequestedDripRate," BUCKET");
|
|
||||||
}
|
|
||||||
|
|
||||||
///</mic>
|
|
||||||
|
|
||||||
// TODO: This data structure is wrong in so many ways. Locking and copying the entire lists
|
// TODO: This data structure is wrong in so many ways. Locking and copying the entire lists
|
||||||
// of pending and needed ACKs for every client every time some method wants information about
|
// of pending and needed ACKs for every client every time some method wants information about
|
||||||
// this connection is a recipe for poor performance
|
// this connection is a recipe for poor performance
|
||||||
|
@ -260,12 +240,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate;
|
info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate;
|
||||||
info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate;
|
info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate;
|
||||||
info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate;
|
info.cloudThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate;
|
||||||
// info.taskThrottle = m_throttleCategories[(int)ThrottleOutPacketType.State].DripRate + m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate;
|
|
||||||
info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate;
|
info.taskThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate;
|
||||||
info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate;
|
info.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate;
|
||||||
info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate;
|
info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate;
|
||||||
info.totalThrottle = info.resendThrottle + info.landThrottle + info.windThrottle + info.cloudThrottle +
|
info.totalThrottle = (int)m_throttleCategory.DripRate;
|
||||||
info.taskThrottle + info.assetThrottle + info.textureThrottle;
|
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -352,8 +330,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
|
int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
|
||||||
// State is a subcategory of task that we allocate a percentage to
|
// State is a subcategory of task that we allocate a percentage to
|
||||||
int state = 0;
|
int state = 0;
|
||||||
// int state = (int)((float)task * STATE_TASK_PERCENTAGE);
|
|
||||||
// task -= state;
|
|
||||||
|
|
||||||
// Make sure none of the throttles are set below our packet MTU,
|
// Make sure none of the throttles are set below our packet MTU,
|
||||||
// otherwise a throttle could become permanently clogged
|
// otherwise a throttle could become permanently clogged
|
||||||
|
@ -364,19 +340,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
task = Math.Max(task, LLUDPServer.MTU);
|
task = Math.Max(task, LLUDPServer.MTU);
|
||||||
texture = Math.Max(texture, LLUDPServer.MTU);
|
texture = Math.Max(texture, LLUDPServer.MTU);
|
||||||
asset = Math.Max(asset, LLUDPServer.MTU);
|
asset = Math.Max(asset, LLUDPServer.MTU);
|
||||||
state = Math.Max(state, LLUDPServer.MTU);
|
|
||||||
|
|
||||||
int total = resend + land + wind + cloud + task + texture + asset + state;
|
//int total = resend + land + wind + cloud + task + texture + asset;
|
||||||
|
//m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}",
|
||||||
//m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, State={8}, Total={9}",
|
// AgentID, resend, land, wind, cloud, task, texture, asset, total);
|
||||||
// AgentID, resend, land, wind, cloud, task, texture, asset, state, total);
|
|
||||||
|
|
||||||
// Update the token buckets with new throttle values
|
// Update the token buckets with new throttle values
|
||||||
TokenBucket bucket;
|
TokenBucket bucket;
|
||||||
|
|
||||||
bucket = m_throttleCategory;
|
|
||||||
bucket.RequestedDripRate = total;
|
|
||||||
|
|
||||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend];
|
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend];
|
||||||
bucket.RequestedDripRate = resend;
|
bucket.RequestedDripRate = resend;
|
||||||
|
|
||||||
|
@ -405,22 +376,38 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
m_packedThrottles = null;
|
m_packedThrottles = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetThrottlesPacked()
|
public byte[] GetThrottlesPacked(float multiplier)
|
||||||
{
|
{
|
||||||
byte[] data = m_packedThrottles;
|
byte[] data = m_packedThrottles;
|
||||||
|
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
|
float rate;
|
||||||
|
|
||||||
data = new byte[7 * 4];
|
data = new byte[7 * 4];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate), 0, data, i, 4); i += 4;
|
// multiply by 8 to convert bytes back to bits
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate), 0, data, i, 4); i += 4;
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate * 8 * multiplier;
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate), 0, data, i, 4); i += 4;
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate), 0, data, i, 4); i += 4;
|
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate), 0, data, i, 4); i += 4;
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate * 8 * multiplier;
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate), 0, data, i, 4); i += 4;
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate), 0, data, i, 4); i += 4;
|
|
||||||
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate * 8 * multiplier;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].RequestedDripRate * 8 * multiplier;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Task].RequestedDripRate * 8 * multiplier;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate * 8 * multiplier;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
|
rate = (float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate * 8 * multiplier;
|
||||||
|
Buffer.BlockCopy(Utils.FloatToBytes(rate), 0, data, i, 4); i += 4;
|
||||||
|
|
||||||
m_packedThrottles = data;
|
m_packedThrottles = data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -672,7 +672,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
if (packet.Header.AppendedAcks && packet.Header.AckList != null)
|
if (packet.Header.AppendedAcks && packet.Header.AckList != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < packet.Header.AckList.Length; i++)
|
for (int i = 0; i < packet.Header.AckList.Length; i++)
|
||||||
udpClient.NeedAcks.Remove(packet.Header.AckList[i], now, packet.Header.Resent);
|
udpClient.NeedAcks.Acknowledge(packet.Header.AckList[i], now, packet.Header.Resent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle PacketAck packets
|
// Handle PacketAck packets
|
||||||
|
@ -681,7 +681,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
PacketAckPacket ackPacket = (PacketAckPacket)packet;
|
PacketAckPacket ackPacket = (PacketAckPacket)packet;
|
||||||
|
|
||||||
for (int i = 0; i < ackPacket.Packets.Length; i++)
|
for (int i = 0; i < ackPacket.Packets.Length; i++)
|
||||||
udpClient.NeedAcks.Remove(ackPacket.Packets[i].ID, now, packet.Header.Resent);
|
udpClient.NeedAcks.Acknowledge(ackPacket.Packets[i].ID, now, packet.Header.Resent);
|
||||||
|
|
||||||
// We don't need to do anything else with PacketAck packets
|
// We don't need to do anything else with PacketAck packets
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -52,30 +52,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
public int Texture;
|
public int Texture;
|
||||||
/// <summary>Drip rate for asset packets</summary>
|
/// <summary>Drip rate for asset packets</summary>
|
||||||
public int Asset;
|
public int Asset;
|
||||||
/// <summary>Drip rate for state packets</summary>
|
|
||||||
public int State;
|
|
||||||
/// <summary>Drip rate for the parent token bucket</summary>
|
/// <summary>Drip rate for the parent token bucket</summary>
|
||||||
public int Total;
|
public int Total;
|
||||||
|
|
||||||
/// <summary>Maximum burst rate for resent packets</summary>
|
/// <summary>Flag used to enable adaptive throttles</summary>
|
||||||
public int ResendLimit;
|
public bool AdaptiveThrottlesEnabled;
|
||||||
/// <summary>Maximum burst rate for land packets</summary>
|
|
||||||
public int LandLimit;
|
|
||||||
/// <summary>Maximum burst rate for wind packets</summary>
|
|
||||||
public int WindLimit;
|
|
||||||
/// <summary>Maximum burst rate for cloud packets</summary>
|
|
||||||
public int CloudLimit;
|
|
||||||
/// <summary>Maximum burst rate for task (state and transaction) packets</summary>
|
|
||||||
public int TaskLimit;
|
|
||||||
/// <summary>Maximum burst rate for texture packets</summary>
|
|
||||||
public int TextureLimit;
|
|
||||||
/// <summary>Maximum burst rate for asset packets</summary>
|
|
||||||
public int AssetLimit;
|
|
||||||
/// <summary>Maximum burst rate for state packets</summary>
|
|
||||||
public int StateLimit;
|
|
||||||
/// <summary>Burst rate for the parent token bucket</summary>
|
|
||||||
public int TotalLimit;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -86,26 +69,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"];
|
IConfig throttleConfig = config.Configs["ClientStack.LindenUDP"];
|
||||||
|
|
||||||
Resend = throttleConfig.GetInt("resend_default", 12500);
|
Resend = throttleConfig.GetInt("resend_default", 6625);
|
||||||
Land = throttleConfig.GetInt("land_default", 1000);
|
Land = throttleConfig.GetInt("land_default", 9125);
|
||||||
Wind = throttleConfig.GetInt("wind_default", 1000);
|
Wind = throttleConfig.GetInt("wind_default", 1750);
|
||||||
Cloud = throttleConfig.GetInt("cloud_default", 1000);
|
Cloud = throttleConfig.GetInt("cloud_default", 1750);
|
||||||
Task = throttleConfig.GetInt("task_default", 1000);
|
Task = throttleConfig.GetInt("task_default", 18500);
|
||||||
Texture = throttleConfig.GetInt("texture_default", 1000);
|
Texture = throttleConfig.GetInt("texture_default", 18500);
|
||||||
Asset = throttleConfig.GetInt("asset_default", 1000);
|
Asset = throttleConfig.GetInt("asset_default", 10500);
|
||||||
State = throttleConfig.GetInt("state_default", 1000);
|
|
||||||
|
|
||||||
ResendLimit = throttleConfig.GetInt("resend_limit", 18750);
|
|
||||||
LandLimit = throttleConfig.GetInt("land_limit", 29750);
|
|
||||||
WindLimit = throttleConfig.GetInt("wind_limit", 18750);
|
|
||||||
CloudLimit = throttleConfig.GetInt("cloud_limit", 18750);
|
|
||||||
TaskLimit = throttleConfig.GetInt("task_limit", 18750);
|
|
||||||
TextureLimit = throttleConfig.GetInt("texture_limit", 55750);
|
|
||||||
AssetLimit = throttleConfig.GetInt("asset_limit", 27500);
|
|
||||||
StateLimit = throttleConfig.GetInt("state_limit", 37000);
|
|
||||||
|
|
||||||
Total = throttleConfig.GetInt("client_throttle_max_bps", 0);
|
Total = throttleConfig.GetInt("client_throttle_max_bps", 0);
|
||||||
TotalLimit = Total;
|
|
||||||
|
AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false);
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
|
@ -128,34 +102,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
return Texture;
|
return Texture;
|
||||||
case ThrottleOutPacketType.Asset:
|
case ThrottleOutPacketType.Asset:
|
||||||
return Asset;
|
return Asset;
|
||||||
case ThrottleOutPacketType.State:
|
|
||||||
return State;
|
|
||||||
case ThrottleOutPacketType.Unknown:
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetLimit(ThrottleOutPacketType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ThrottleOutPacketType.Resend:
|
|
||||||
return ResendLimit;
|
|
||||||
case ThrottleOutPacketType.Land:
|
|
||||||
return LandLimit;
|
|
||||||
case ThrottleOutPacketType.Wind:
|
|
||||||
return WindLimit;
|
|
||||||
case ThrottleOutPacketType.Cloud:
|
|
||||||
return CloudLimit;
|
|
||||||
case ThrottleOutPacketType.Task:
|
|
||||||
return TaskLimit;
|
|
||||||
case ThrottleOutPacketType.Texture:
|
|
||||||
return TextureLimit;
|
|
||||||
case ThrottleOutPacketType.Asset:
|
|
||||||
return AssetLimit;
|
|
||||||
case ThrottleOutPacketType.State:
|
|
||||||
return StateLimit;
|
|
||||||
case ThrottleOutPacketType.Unknown:
|
case ThrottleOutPacketType.Unknown:
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -29,6 +29,8 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using OpenSim.Framework;
|
||||||
|
|
||||||
using log4net;
|
using log4net;
|
||||||
|
|
||||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
|
@ -177,7 +179,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
RequestedDripRate = dripRate;
|
RequestedDripRate = dripRate;
|
||||||
// TotalDripRequest = dripRate; // this will be overwritten when a child node registers
|
// TotalDripRequest = dripRate; // this will be overwritten when a child node registers
|
||||||
// MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst);
|
// MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst);
|
||||||
m_lastDrip = Environment.TickCount & Int32.MaxValue;
|
m_lastDrip = Util.EnvironmentTickCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Constructor
|
#endregion Constructor
|
||||||
|
@ -211,12 +213,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RegisterRequest(TokenBucket child, Int64 request)
|
public void RegisterRequest(TokenBucket child, Int64 request)
|
||||||
{
|
{
|
||||||
m_children[child] = request;
|
lock (m_children)
|
||||||
// m_totalDripRequest = m_children.Values.Sum();
|
{
|
||||||
|
m_children[child] = request;
|
||||||
|
// m_totalDripRequest = m_children.Values.Sum();
|
||||||
|
|
||||||
m_totalDripRequest = 0;
|
m_totalDripRequest = 0;
|
||||||
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
||||||
m_totalDripRequest += cref.Value;
|
m_totalDripRequest += cref.Value;
|
||||||
|
}
|
||||||
|
|
||||||
// Pass the new values up to the parent
|
// Pass the new values up to the parent
|
||||||
if (m_parent != null)
|
if (m_parent != null)
|
||||||
|
@ -229,12 +234,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UnregisterRequest(TokenBucket child)
|
public void UnregisterRequest(TokenBucket child)
|
||||||
{
|
{
|
||||||
m_children.Remove(child);
|
lock (m_children)
|
||||||
// m_totalDripRequest = m_children.Values.Sum();
|
{
|
||||||
|
m_children.Remove(child);
|
||||||
|
// m_totalDripRequest = m_children.Values.Sum();
|
||||||
|
|
||||||
m_totalDripRequest = 0;
|
m_totalDripRequest = 0;
|
||||||
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
||||||
m_totalDripRequest += cref.Value;
|
m_totalDripRequest += cref.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Pass the new values up to the parent
|
// Pass the new values up to the parent
|
||||||
if (m_parent != null)
|
if (m_parent != null)
|
||||||
|
@ -297,10 +306,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
|
|
||||||
// Determine the interval over which we are adding tokens, never add
|
// Determine the interval over which we are adding tokens, never add
|
||||||
// more than a single quantum of tokens
|
// more than a single quantum of tokens
|
||||||
Int32 now = Environment.TickCount & Int32.MaxValue;
|
Int32 deltaMS = Math.Min(Util.EnvironmentTickCountSubtract(m_lastDrip), m_ticksPerQuantum);
|
||||||
Int32 deltaMS = Math.Min(now - m_lastDrip, m_ticksPerQuantum);
|
m_lastDrip = Util.EnvironmentTickCount();
|
||||||
|
|
||||||
m_lastDrip = now;
|
|
||||||
|
|
||||||
// This can be 0 in the very unusual case that the timer wrapped
|
// This can be 0 in the very unusual case that the timer wrapped
|
||||||
// It can be 0 if we try add tokens at a sub-tick rate
|
// It can be 0 if we try add tokens at a sub-tick rate
|
||||||
|
@ -315,10 +322,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
{
|
{
|
||||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
|
|
||||||
// <summary>
|
/// <summary>
|
||||||
// The minimum rate for flow control.
|
/// The minimum rate for flow control. Minimum drip rate is one
|
||||||
// </summary>
|
/// packet per second. Open the throttle to 15 packets per second
|
||||||
protected const Int64 m_minimumFlow = m_minimumDripRate * 10;
|
/// or about 160kbps.
|
||||||
|
/// </summary>
|
||||||
|
protected const Int64 m_minimumFlow = m_minimumDripRate * 15;
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
// The maximum rate for flow control. Drip rate can never be
|
// The maximum rate for flow control. Drip rate can never be
|
||||||
|
@ -331,6 +340,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
set { m_maxDripRate = (value == 0 ? 0 : Math.Max(value,m_minimumFlow)); }
|
set { m_maxDripRate = (value == 0 ? 0 : Math.Max(value,m_minimumFlow)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool m_enabled = false;
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
//
|
//
|
||||||
// </summary>
|
// </summary>
|
||||||
|
@ -348,9 +359,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// <summary>
|
// <summary>
|
||||||
//
|
//
|
||||||
// </summary>
|
// </summary>
|
||||||
public AdaptiveTokenBucket(TokenBucket parent, Int64 maxDripRate) : base(parent,m_minimumFlow)
|
public AdaptiveTokenBucket(TokenBucket parent, Int64 maxDripRate, bool enabled) : base(parent,maxDripRate)
|
||||||
{
|
{
|
||||||
MaxDripRate = maxDripRate;
|
m_enabled = enabled;
|
||||||
|
|
||||||
|
if (m_enabled)
|
||||||
|
{
|
||||||
|
m_log.WarnFormat("[TOKENBUCKET] Adaptive throttle enabled");
|
||||||
|
MaxDripRate = maxDripRate;
|
||||||
|
AdjustedDripRate = m_minimumFlow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
|
@ -359,7 +377,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
public void ExpirePackets(Int32 count)
|
public void ExpirePackets(Int32 count)
|
||||||
{
|
{
|
||||||
// m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count);
|
// m_log.WarnFormat("[ADAPTIVEBUCKET] drop {0} by {1} expired packets",AdjustedDripRate,count);
|
||||||
AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count));
|
if (m_enabled)
|
||||||
|
AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count));
|
||||||
}
|
}
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
|
@ -367,7 +386,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// </summary>
|
// </summary>
|
||||||
public void AcknowledgePackets(Int32 count)
|
public void AcknowledgePackets(Int32 count)
|
||||||
{
|
{
|
||||||
AdjustedDripRate = AdjustedDripRate + count;
|
if (m_enabled)
|
||||||
|
AdjustedDripRate = AdjustedDripRate + count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
/// <summary>Holds packets that need to be added to the unacknowledged list</summary>
|
/// <summary>Holds packets that need to be added to the unacknowledged list</summary>
|
||||||
private LocklessQueue<OutgoingPacket> m_pendingAdds = new LocklessQueue<OutgoingPacket>();
|
private LocklessQueue<OutgoingPacket> m_pendingAdds = new LocklessQueue<OutgoingPacket>();
|
||||||
/// <summary>Holds information about pending acknowledgements</summary>
|
/// <summary>Holds information about pending acknowledgements</summary>
|
||||||
private LocklessQueue<PendingAck> m_pendingRemoves = new LocklessQueue<PendingAck>();
|
private LocklessQueue<PendingAck> m_pendingAcknowledgements = new LocklessQueue<PendingAck>();
|
||||||
|
/// <summary>Holds information about pending removals</summary>
|
||||||
|
private LocklessQueue<uint> m_pendingRemoves = new LocklessQueue<uint>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add an unacked packet to the collection
|
/// Add an unacked packet to the collection
|
||||||
|
@ -83,15 +85,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks a packet as acknowledged
|
/// Marks a packet as acknowledged
|
||||||
|
/// This method is used when an acknowledgement is received from the network for a previously
|
||||||
|
/// sent packet. Effects of removal this way are to update unacked byte count, adjust RTT
|
||||||
|
/// and increase throttle to the coresponding client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sequenceNumber">Sequence number of the packet to
|
/// <param name="sequenceNumber">Sequence number of the packet to
|
||||||
/// acknowledge</param>
|
/// acknowledge</param>
|
||||||
/// <param name="currentTime">Current value of Environment.TickCount</param>
|
/// <param name="currentTime">Current value of Environment.TickCount</param>
|
||||||
/// <remarks>This does not immediately acknowledge the packet, it only
|
/// <remarks>This does not immediately acknowledge the packet, it only
|
||||||
/// queues the ack so it can be handled in a thread-safe way later</remarks>
|
/// queues the ack so it can be handled in a thread-safe way later</remarks>
|
||||||
public void Remove(uint sequenceNumber, int currentTime, bool fromResend)
|
public void Acknowledge(uint sequenceNumber, int currentTime, bool fromResend)
|
||||||
{
|
{
|
||||||
m_pendingRemoves.Enqueue(new PendingAck(sequenceNumber, currentTime, fromResend));
|
m_pendingAcknowledgements.Enqueue(new PendingAck(sequenceNumber, currentTime, fromResend));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a packet as no longer needing acknowledgement without a received acknowledgement.
|
||||||
|
/// This method is called when a packet expires and we no longer need an acknowledgement.
|
||||||
|
/// When some reliable packet types expire, they are handled in a way other than simply
|
||||||
|
/// resending them. The only effect of removal this way is to update unacked byte count.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequenceNumber">Sequence number of the packet to
|
||||||
|
/// acknowledge</param>
|
||||||
|
/// <remarks>The does not immediately remove the packet, it only queues the removal
|
||||||
|
/// so it can be handled in a thread safe way later</remarks>
|
||||||
|
public void Remove(uint sequenceNumber)
|
||||||
|
{
|
||||||
|
m_pendingRemoves.Enqueue(sequenceNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -151,15 +171,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
m_packets[pendingAdd.SequenceNumber] = pendingAdd;
|
m_packets[pendingAdd.SequenceNumber] = pendingAdd;
|
||||||
|
|
||||||
// Process all the pending removes, including updating statistics and round-trip times
|
// Process all the pending removes, including updating statistics and round-trip times
|
||||||
PendingAck pendingRemove;
|
PendingAck pendingAcknowledgement;
|
||||||
OutgoingPacket ackedPacket;
|
while (m_pendingAcknowledgements.TryDequeue(out pendingAcknowledgement))
|
||||||
while (m_pendingRemoves.TryDequeue(out pendingRemove))
|
|
||||||
{
|
{
|
||||||
if (m_packets.TryGetValue(pendingRemove.SequenceNumber, out ackedPacket))
|
OutgoingPacket ackedPacket;
|
||||||
|
if (m_packets.TryGetValue(pendingAcknowledgement.SequenceNumber, out ackedPacket))
|
||||||
{
|
{
|
||||||
if (ackedPacket != null)
|
if (ackedPacket != null)
|
||||||
{
|
{
|
||||||
m_packets.Remove(pendingRemove.SequenceNumber);
|
m_packets.Remove(pendingAcknowledgement.SequenceNumber);
|
||||||
|
|
||||||
// As with other network applications, assume that an acknowledged packet is an
|
// As with other network applications, assume that an acknowledged packet is an
|
||||||
// indication that the network can handle a little more load, speed up the transmission
|
// indication that the network can handle a little more load, speed up the transmission
|
||||||
|
@ -168,16 +188,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||||
// Update stats
|
// Update stats
|
||||||
Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength);
|
Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength);
|
||||||
|
|
||||||
if (!pendingRemove.FromResend)
|
if (!pendingAcknowledgement.FromResend)
|
||||||
{
|
{
|
||||||
// Calculate the round-trip time for this packet and its ACK
|
// Calculate the round-trip time for this packet and its ACK
|
||||||
int rtt = pendingRemove.RemoveTime - ackedPacket.TickCount;
|
int rtt = pendingAcknowledgement.RemoveTime - ackedPacket.TickCount;
|
||||||
if (rtt > 0)
|
if (rtt > 0)
|
||||||
ackedPacket.Client.UpdateRoundTrip(rtt);
|
ackedPacket.Client.UpdateRoundTrip(rtt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint pendingRemove;
|
||||||
|
while(m_pendingRemoves.TryDequeue(out pendingRemove))
|
||||||
|
{
|
||||||
|
OutgoingPacket removedPacket;
|
||||||
|
if (m_packets.TryGetValue(pendingRemove, out removedPacket))
|
||||||
|
{
|
||||||
|
if (removedPacket != null)
|
||||||
|
{
|
||||||
|
m_packets.Remove(pendingRemove);
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
Interlocked.Add(ref removedPacket.Client.UnackedBytes, -removedPacket.Buffer.DataLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
// If this is an update for our own avatar give it the highest priority
|
// If this is an update for our own avatar give it the highest priority
|
||||||
if (client.AgentId == entity.UUID)
|
if (client.AgentId == entity.UUID)
|
||||||
return PriorityQueue.ImmediateQueue;
|
return 0;
|
||||||
|
|
||||||
uint priority;
|
uint priority;
|
||||||
|
|
||||||
|
@ -119,16 +119,40 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
private uint GetPriorityByTime(IClientAPI client, ISceneEntity entity)
|
private uint GetPriorityByTime(IClientAPI client, ISceneEntity entity)
|
||||||
{
|
{
|
||||||
return 1;
|
// And anything attached to this avatar gets top priority as well
|
||||||
|
if (entity is SceneObjectPart)
|
||||||
|
{
|
||||||
|
SceneObjectPart sop = (SceneObjectPart)entity;
|
||||||
|
if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PriorityQueue.NumberOfImmediateQueues; // first queue past the immediate queues
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint GetPriorityByDistance(IClientAPI client, ISceneEntity entity)
|
private uint GetPriorityByDistance(IClientAPI client, ISceneEntity entity)
|
||||||
{
|
{
|
||||||
|
// And anything attached to this avatar gets top priority as well
|
||||||
|
if (entity is SceneObjectPart)
|
||||||
|
{
|
||||||
|
SceneObjectPart sop = (SceneObjectPart)entity;
|
||||||
|
if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return ComputeDistancePriority(client,entity,false);
|
return ComputeDistancePriority(client,entity,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint GetPriorityByFrontBack(IClientAPI client, ISceneEntity entity)
|
private uint GetPriorityByFrontBack(IClientAPI client, ISceneEntity entity)
|
||||||
{
|
{
|
||||||
|
// And anything attached to this avatar gets top priority as well
|
||||||
|
if (entity is SceneObjectPart)
|
||||||
|
{
|
||||||
|
SceneObjectPart sop = (SceneObjectPart)entity;
|
||||||
|
if (sop.ParentGroup.RootPart.IsAttachment && client.AgentId == sop.ParentGroup.RootPart.AttachedAvatar)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return ComputeDistancePriority(client,entity,true);
|
return ComputeDistancePriority(client,entity,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +229,10 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
// And convert the distance to a priority queue, this computation gives queues
|
// And convert the distance to a priority queue, this computation gives queues
|
||||||
// at 10, 20, 40, 80, 160, 320, 640, and 1280m
|
// at 10, 20, 40, 80, 160, 320, 640, and 1280m
|
||||||
uint pqueue = 1;
|
uint pqueue = PriorityQueue.NumberOfImmediateQueues;
|
||||||
for (int i = 0; i < 8; i++)
|
uint queues = PriorityQueue.NumberOfQueues - PriorityQueue.NumberOfImmediateQueues;
|
||||||
|
|
||||||
|
for (int i = 0; i < queues - 1; i++)
|
||||||
{
|
{
|
||||||
if (distance < 10 * Math.Pow(2.0,i))
|
if (distance < 10 * Math.Pow(2.0,i))
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -3049,18 +3049,17 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
cadu.GroupAccess = 0;
|
cadu.GroupAccess = 0;
|
||||||
cadu.Position = AbsolutePosition;
|
cadu.Position = AbsolutePosition;
|
||||||
cadu.regionHandle = m_rootRegionHandle;
|
cadu.regionHandle = m_rootRegionHandle;
|
||||||
float multiplier = 1;
|
|
||||||
int innacurateNeighbors = m_scene.GetInaccurateNeighborCount();
|
|
||||||
if (innacurateNeighbors != 0)
|
|
||||||
{
|
|
||||||
multiplier = 1f / (float)innacurateNeighbors;
|
|
||||||
}
|
|
||||||
if (multiplier <= 0f)
|
|
||||||
{
|
|
||||||
multiplier = 0.25f;
|
|
||||||
}
|
|
||||||
|
|
||||||
//m_log.Info("[NeighborThrottle]: " + m_scene.GetInaccurateNeighborCount().ToString() + " - m: " + multiplier.ToString());
|
// Throttles
|
||||||
|
float multiplier = 1;
|
||||||
|
int childRegions = m_knownChildRegions.Count;
|
||||||
|
if (childRegions != 0)
|
||||||
|
multiplier = 1f / childRegions;
|
||||||
|
|
||||||
|
// Minimum throttle for a child region is 1/4 of the root region throttle
|
||||||
|
if (multiplier <= 0.25f)
|
||||||
|
multiplier = 0.25f;
|
||||||
|
|
||||||
cadu.throttles = ControllingClient.GetThrottlesPacked(multiplier);
|
cadu.throttles = ControllingClient.GetThrottlesPacked(multiplier);
|
||||||
cadu.Velocity = Velocity;
|
cadu.Velocity = Velocity;
|
||||||
|
|
||||||
|
@ -3456,16 +3455,14 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
// Throttles
|
// Throttles
|
||||||
float multiplier = 1;
|
float multiplier = 1;
|
||||||
int innacurateNeighbors = m_scene.GetInaccurateNeighborCount();
|
int childRegions = m_knownChildRegions.Count;
|
||||||
if (innacurateNeighbors != 0)
|
if (childRegions != 0)
|
||||||
{
|
multiplier = 1f / childRegions;
|
||||||
multiplier = 1f / innacurateNeighbors;
|
|
||||||
}
|
// Minimum throttle for a child region is 1/4 of the root region throttle
|
||||||
if (multiplier <= 0f)
|
if (multiplier <= 0.25f)
|
||||||
{
|
|
||||||
multiplier = 0.25f;
|
multiplier = 0.25f;
|
||||||
}
|
|
||||||
//m_log.Info("[NeighborThrottle]: " + m_scene.GetInaccurateNeighborCount().ToString() + " - m: " + multiplier.ToString());
|
|
||||||
cAgent.Throttles = ControllingClient.GetThrottlesPacked(multiplier);
|
cAgent.Throttles = ControllingClient.GetThrottlesPacked(multiplier);
|
||||||
|
|
||||||
cAgent.HeadRotation = m_headrotation;
|
cAgent.HeadRotation = m_headrotation;
|
||||||
|
|
|
@ -81,6 +81,14 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
lock (m_scenes)
|
lock (m_scenes)
|
||||||
m_scenes[scene.RegionInfo.RegionID] = scene;
|
m_scenes[scene.RegionInfo.RegionID] = scene;
|
||||||
|
|
||||||
|
scene.AddCommand(
|
||||||
|
this, "show pqueues",
|
||||||
|
"show pqueues [full]",
|
||||||
|
"Show priority queue data for each client",
|
||||||
|
"Without the 'full' option, only root agents are shown."
|
||||||
|
+ " With the 'full' option child agents are also shown.",
|
||||||
|
ShowPQueuesReport);
|
||||||
|
|
||||||
scene.AddCommand(
|
scene.AddCommand(
|
||||||
this, "show queues",
|
this, "show queues",
|
||||||
"show queues [full]",
|
"show queues [full]",
|
||||||
|
@ -119,6 +127,11 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
// m_log.DebugFormat("[LINDEN UDP INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
|
// m_log.DebugFormat("[LINDEN UDP INFO MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void ShowPQueuesReport(string module, string[] cmd)
|
||||||
|
{
|
||||||
|
MainConsole.Instance.Output(GetPQueuesReport(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
protected void ShowQueuesReport(string module, string[] cmd)
|
protected void ShowQueuesReport(string module, string[] cmd)
|
||||||
{
|
{
|
||||||
MainConsole.Instance.Output(GetQueuesReport(cmd));
|
MainConsole.Instance.Output(GetQueuesReport(cmd));
|
||||||
|
@ -155,6 +168,80 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate UDP Queue data report for each client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="showParams"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected string GetPQueuesReport(string[] showParams)
|
||||||
|
{
|
||||||
|
bool showChildren = false;
|
||||||
|
string pname = "";
|
||||||
|
|
||||||
|
if (showParams.Length > 2 && showParams[2] == "full")
|
||||||
|
showChildren = true;
|
||||||
|
else if (showParams.Length > 3)
|
||||||
|
pname = showParams[2] + " " + showParams[3];
|
||||||
|
|
||||||
|
StringBuilder report = new StringBuilder();
|
||||||
|
|
||||||
|
int columnPadding = 2;
|
||||||
|
int maxNameLength = 18;
|
||||||
|
int maxRegionNameLength = 14;
|
||||||
|
int maxTypeLength = 4;
|
||||||
|
int totalInfoFieldsLength = maxNameLength + columnPadding + maxRegionNameLength + columnPadding + maxTypeLength + columnPadding;
|
||||||
|
|
||||||
|
report.Append(GetColumnEntry("User", maxNameLength, columnPadding));
|
||||||
|
report.Append(GetColumnEntry("Region", maxRegionNameLength, columnPadding));
|
||||||
|
report.Append(GetColumnEntry("Type", maxTypeLength, columnPadding));
|
||||||
|
|
||||||
|
report.AppendFormat(
|
||||||
|
"{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7} {10,7} {11,7}\n",
|
||||||
|
"Pri 0",
|
||||||
|
"Pri 1",
|
||||||
|
"Pri 2",
|
||||||
|
"Pri 3",
|
||||||
|
"Pri 4",
|
||||||
|
"Pri 5",
|
||||||
|
"Pri 6",
|
||||||
|
"Pri 7",
|
||||||
|
"Pri 8",
|
||||||
|
"Pri 9",
|
||||||
|
"Pri 10",
|
||||||
|
"Pri 11");
|
||||||
|
|
||||||
|
lock (m_scenes)
|
||||||
|
{
|
||||||
|
foreach (Scene scene in m_scenes.Values)
|
||||||
|
{
|
||||||
|
scene.ForEachClient(
|
||||||
|
delegate(IClientAPI client)
|
||||||
|
{
|
||||||
|
if (client is LLClientView)
|
||||||
|
{
|
||||||
|
bool isChild = scene.PresenceChildStatus(client.AgentId);
|
||||||
|
if (isChild && !showChildren)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string name = client.Name;
|
||||||
|
if (pname != "" && name != pname)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string regionName = scene.RegionInfo.RegionName;
|
||||||
|
|
||||||
|
report.Append(GetColumnEntry(name, maxNameLength, columnPadding));
|
||||||
|
report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding));
|
||||||
|
report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding));
|
||||||
|
report.AppendLine(((LLClientView)client).EntityUpdateQueue.ToString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return report.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate UDP Queue data report for each client
|
/// Generate UDP Queue data report for each client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -163,10 +250,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
protected string GetQueuesReport(string[] showParams)
|
protected string GetQueuesReport(string[] showParams)
|
||||||
{
|
{
|
||||||
bool showChildren = false;
|
bool showChildren = false;
|
||||||
|
string pname = "";
|
||||||
|
|
||||||
if (showParams.Length > 2 && showParams[2] == "full")
|
if (showParams.Length > 2 && showParams[2] == "full")
|
||||||
showChildren = true;
|
showChildren = true;
|
||||||
|
else if (showParams.Length > 3)
|
||||||
|
pname = showParams[2] + " " + showParams[3];
|
||||||
|
|
||||||
StringBuilder report = new StringBuilder();
|
StringBuilder report = new StringBuilder();
|
||||||
|
|
||||||
int columnPadding = 2;
|
int columnPadding = 2;
|
||||||
|
@ -224,6 +314,9 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string name = client.Name;
|
string name = client.Name;
|
||||||
|
if (pname != "" && name != pname)
|
||||||
|
return;
|
||||||
|
|
||||||
string regionName = scene.RegionInfo.RegionName;
|
string regionName = scene.RegionInfo.RegionName;
|
||||||
|
|
||||||
report.Append(GetColumnEntry(name, maxNameLength, columnPadding));
|
report.Append(GetColumnEntry(name, maxNameLength, columnPadding));
|
||||||
|
@ -249,10 +342,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
protected string GetThrottlesReport(string[] showParams)
|
protected string GetThrottlesReport(string[] showParams)
|
||||||
{
|
{
|
||||||
bool showChildren = false;
|
bool showChildren = false;
|
||||||
|
string pname = "";
|
||||||
|
|
||||||
if (showParams.Length > 2 && showParams[2] == "full")
|
if (showParams.Length > 2 && showParams[2] == "full")
|
||||||
showChildren = true;
|
showChildren = true;
|
||||||
|
else if (showParams.Length > 3)
|
||||||
|
pname = showParams[2] + " " + showParams[3];
|
||||||
|
|
||||||
StringBuilder report = new StringBuilder();
|
StringBuilder report = new StringBuilder();
|
||||||
|
|
||||||
int columnPadding = 2;
|
int columnPadding = 2;
|
||||||
|
@ -302,7 +398,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
if (client is LLClientView)
|
if (client is LLClientView)
|
||||||
{
|
{
|
||||||
LLClientView llClient = client as LLClientView;
|
LLClientView llClient = client as LLClientView;
|
||||||
|
|
||||||
if (firstClient)
|
if (firstClient)
|
||||||
{
|
{
|
||||||
report.AppendLine(GetServerThrottlesReport(llClient.UDPServer));
|
report.AppendLine(GetServerThrottlesReport(llClient.UDPServer));
|
||||||
|
@ -314,6 +410,9 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string name = client.Name;
|
string name = client.Name;
|
||||||
|
if (pname != "" && name != pname)
|
||||||
|
return;
|
||||||
|
|
||||||
string regionName = scene.RegionInfo.RegionName;
|
string regionName = scene.RegionInfo.RegionName;
|
||||||
|
|
||||||
LLUDPClient llUdpClient = llClient.UDPClient;
|
LLUDPClient llUdpClient = llClient.UDPClient;
|
||||||
|
@ -352,7 +451,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
int maxRegionNameLength = 14;
|
int maxRegionNameLength = 14;
|
||||||
int maxTypeLength = 4;
|
int maxTypeLength = 4;
|
||||||
|
|
||||||
string name = "SERVER AGENT LIMITS";
|
string name = "SERVER AGENT RATES";
|
||||||
|
|
||||||
report.Append(GetColumnEntry(name, maxNameLength, columnPadding));
|
report.Append(GetColumnEntry(name, maxNameLength, columnPadding));
|
||||||
report.Append(GetColumnEntry("-", maxRegionNameLength, columnPadding));
|
report.Append(GetColumnEntry("-", maxRegionNameLength, columnPadding));
|
||||||
|
@ -362,13 +461,13 @@ namespace OpenSim.Region.CoreModules.UDP.Linden
|
||||||
report.AppendFormat(
|
report.AppendFormat(
|
||||||
"{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}",
|
"{0,7} {1,8} {2,7} {3,7} {4,7} {5,7} {6,9} {7,7}",
|
||||||
(throttleRates.Total * 8) / 1000,
|
(throttleRates.Total * 8) / 1000,
|
||||||
(throttleRates.ResendLimit * 8) / 1000,
|
(throttleRates.Resend * 8) / 1000,
|
||||||
(throttleRates.LandLimit * 8) / 1000,
|
(throttleRates.Land * 8) / 1000,
|
||||||
(throttleRates.WindLimit * 8) / 1000,
|
(throttleRates.Wind * 8) / 1000,
|
||||||
(throttleRates.CloudLimit * 8) / 1000,
|
(throttleRates.Cloud * 8) / 1000,
|
||||||
(throttleRates.TaskLimit * 8) / 1000,
|
(throttleRates.Task * 8) / 1000,
|
||||||
(throttleRates.TextureLimit * 8) / 1000,
|
(throttleRates.Texture * 8) / 1000,
|
||||||
(throttleRates.AssetLimit * 8) / 1000);
|
(throttleRates.Asset * 8) / 1000);
|
||||||
|
|
||||||
return report.ToString();
|
return report.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,30 +362,24 @@
|
||||||
;
|
;
|
||||||
;client_throttle_max_bps = 196608
|
;client_throttle_max_bps = 196608
|
||||||
|
|
||||||
; Per-client bytes per second rates for the various throttle categories.
|
; Adaptive throttling attempts to limit network overload when multiple
|
||||||
; These are default values that will be overriden by clients
|
; clients login by starting each connection more slowly. Disabled by
|
||||||
|
; default
|
||||||
;
|
;
|
||||||
;resend_default = 12500
|
;enable_adaptive_throttles = true
|
||||||
;land_default = 1000
|
|
||||||
;wind_default = 1000
|
|
||||||
;cloud_default = 1000
|
|
||||||
;task_default = 1000
|
|
||||||
;texture_default = 1000
|
|
||||||
;asset_default = 1000
|
|
||||||
;state_default = 1000
|
|
||||||
|
|
||||||
; Per-client maximum burst rates in bytes per second for the various
|
; Per-client bytes per second rates for the various throttle categories.
|
||||||
; throttle categories. These are default values that will be overriden by
|
; These are default values that will be overriden by clients. These
|
||||||
; clients
|
; defaults are approximately equivalent to the throttles set by the Imprudence
|
||||||
;
|
; viewer when maximum bandwidth is set to 350kbps
|
||||||
;resend_limit = 18750
|
|
||||||
;land_limit = 29750
|
;resend_default = 6625
|
||||||
;wind_limit = 18750
|
;land_default = 9125
|
||||||
;cloud_limit = 18750
|
;wind_default = 1750
|
||||||
;task_limit = 18750
|
;cloud_default = 1750
|
||||||
;texture_limit = 55750
|
;task_default = 18500
|
||||||
;asset_limit = 27500
|
;texture_default = 18500
|
||||||
;state_limit = 37000
|
;asset_default = 10500
|
||||||
|
|
||||||
; Configures how ObjectUpdates are aggregated. These numbers
|
; Configures how ObjectUpdates are aggregated. These numbers
|
||||||
; do not literally mean how many updates will be put in each
|
; do not literally mean how many updates will be put in each
|
||||||
|
|
Loading…
Reference in New Issue