mirror of
https://github.com/opensim/opensim.git
synced 2026-08-09 02:36:00 +08:00
Merge branch 'master' into careminster-presence-refactor
This commit is contained in:
@@ -301,77 +301,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
/// <summary>Used to adjust Sun Orbit values so Linden based viewers properly position sun</summary>
|
||||
private const float m_sunPainDaHalfOrbitalCutoff = 4.712388980384689858f;
|
||||
|
||||
// First log file or time has expired, start writing to a new log file
|
||||
//<MIC>
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
// THIS IS DEBUGGING CODE & SHOULD BE REMOVED
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
public class QueueLogger
|
||||
{
|
||||
public Int32 start = 0;
|
||||
public StreamWriter Log = null;
|
||||
private Dictionary<UUID,int> m_idMap = new Dictionary<UUID,int>();
|
||||
|
||||
public QueueLogger()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
String fname = String.Format("queue-{0}.log", now.ToString("yyyyMMddHHmmss"));
|
||||
Log = new StreamWriter(fname);
|
||||
|
||||
start = Util.EnvironmentTickCount();
|
||||
}
|
||||
|
||||
public int LookupID(UUID uuid)
|
||||
{
|
||||
int localid;
|
||||
if (! m_idMap.TryGetValue(uuid,out localid))
|
||||
{
|
||||
localid = m_idMap.Count + 1;
|
||||
m_idMap[uuid] = localid;
|
||||
}
|
||||
|
||||
return localid;
|
||||
}
|
||||
}
|
||||
|
||||
public static QueueLogger QueueLog = null;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
public void LogAvatarUpdateEvent(UUID client, UUID avatar, Int32 timeinqueue)
|
||||
{
|
||||
if (QueueLog == null)
|
||||
QueueLog = new QueueLogger();
|
||||
|
||||
Int32 ticks = Util.EnvironmentTickCountSubtract(QueueLog.start);
|
||||
lock(QueueLog)
|
||||
{
|
||||
int cid = QueueLog.LookupID(client);
|
||||
int aid = QueueLog.LookupID(avatar);
|
||||
QueueLog.Log.WriteLine("{0},AU,AV{1:D4},AV{2:D4},{3}",ticks,cid,aid,timeinqueue);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
public void LogQueueProcessEvent(UUID client, PriorityQueue queue, uint maxup)
|
||||
{
|
||||
if (QueueLog == null)
|
||||
QueueLog = new QueueLogger();
|
||||
|
||||
Int32 ticks = Util.EnvironmentTickCountSubtract(QueueLog.start);
|
||||
lock(QueueLog)
|
||||
{
|
||||
int cid = QueueLog.LookupID(client);
|
||||
QueueLog.Log.WriteLine("{0},PQ,AV{1:D4},{2},{3}",ticks,cid,maxup,queue.ToString());
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
//</MIC>
|
||||
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
protected static Dictionary<PacketType, PacketMethod> PacketHandlers = new Dictionary<PacketType, PacketMethod>(); //Global/static handlers for all clients
|
||||
|
||||
@@ -387,6 +316,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
private int m_cachedTextureSerial;
|
||||
private PriorityQueue m_entityUpdates;
|
||||
private PriorityQueue m_entityProps;
|
||||
private Prioritizer m_prioritizer;
|
||||
private bool m_disableFacelights = false;
|
||||
|
||||
@@ -435,9 +365,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
protected IAssetService m_assetService;
|
||||
private const bool m_checkPackets = true;
|
||||
|
||||
private Timer m_propertiesPacketTimer;
|
||||
private List<ObjectPropertiesPacket.ObjectDataBlock> m_propertiesBlocks = new List<ObjectPropertiesPacket.ObjectDataBlock>();
|
||||
|
||||
#endregion Class Members
|
||||
|
||||
#region Properties
|
||||
@@ -521,6 +448,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
m_scene = scene;
|
||||
|
||||
m_entityUpdates = new PriorityQueue(m_scene.Entities.Count);
|
||||
m_entityProps = new PriorityQueue(m_scene.Entities.Count);
|
||||
m_fullUpdateDataBlocksBuilder = new List<ObjectUpdatePacket.ObjectDataBlock>();
|
||||
m_killRecord = new HashSet<uint>();
|
||||
// m_attachmentsSent = new HashSet<uint>();
|
||||
@@ -544,9 +472,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
m_udpClient.OnQueueEmpty += HandleQueueEmpty;
|
||||
m_udpClient.OnPacketStats += PopulateStats;
|
||||
|
||||
m_propertiesPacketTimer = new Timer(100);
|
||||
m_propertiesPacketTimer.Elapsed += ProcessObjectPropertiesPacket;
|
||||
|
||||
m_prioritizer = new Prioritizer(m_scene);
|
||||
|
||||
RegisterLocalPacketHandlers();
|
||||
@@ -2468,7 +2393,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
packet.Effect = effectBlocks;
|
||||
|
||||
OutPacket(packet, ThrottleOutPacketType.State);
|
||||
// OutPacket(packet, ThrottleOutPacketType.State);
|
||||
OutPacket(packet, ThrottleOutPacketType.Task);
|
||||
}
|
||||
|
||||
public void SendAvatarProperties(UUID avatarID, string aboutText, string bornOn, Byte[] charterMember,
|
||||
@@ -3670,9 +3596,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
m_entityUpdates.Enqueue(priority, new EntityUpdate(entity, updateFlags, m_scene.TimeDilation));
|
||||
}
|
||||
|
||||
private Int32 m_LastQueueFill = 0;
|
||||
private uint m_maxUpdates = 0;
|
||||
|
||||
private void ProcessEntityUpdates(int maxUpdates)
|
||||
{
|
||||
OpenSim.Framework.Lazy<List<ObjectUpdatePacket.ObjectDataBlock>> objectUpdateBlocks = new OpenSim.Framework.Lazy<List<ObjectUpdatePacket.ObjectDataBlock>>();
|
||||
@@ -3680,41 +3603,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
OpenSim.Framework.Lazy<List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>> terseUpdateBlocks = new OpenSim.Framework.Lazy<List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>>();
|
||||
OpenSim.Framework.Lazy<List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>> terseAgentUpdateBlocks = new OpenSim.Framework.Lazy<List<ImprovedTerseObjectUpdatePacket.ObjectDataBlock>>();
|
||||
|
||||
// Check to see if this is a flush
|
||||
if (maxUpdates <= 0)
|
||||
{
|
||||
m_maxUpdates = Int32.MaxValue;
|
||||
maxUpdates = Int32.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_maxUpdates == 0 || m_LastQueueFill == 0)
|
||||
{
|
||||
m_maxUpdates = (uint)maxUpdates;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Util.EnvironmentTickCountSubtract(m_LastQueueFill) < 200)
|
||||
m_maxUpdates += 5;
|
||||
else
|
||||
m_maxUpdates = m_maxUpdates >> 1;
|
||||
}
|
||||
m_maxUpdates = Util.Clamp<uint>(m_maxUpdates,10,500);
|
||||
}
|
||||
m_LastQueueFill = Util.EnvironmentTickCount();
|
||||
|
||||
|
||||
int updatesThisCall = 0;
|
||||
|
||||
// We must lock for both manipulating the kill record and sending the packet, in order to avoid a race
|
||||
// condition where a kill can be processed before an out-of-date update for the same object.
|
||||
float avgTimeDilation = 1.0f;
|
||||
|
||||
EntityUpdate update;
|
||||
IEntityUpdate iupdate;
|
||||
Int32 timeinqueue; // this is just debugging code & can be dropped later
|
||||
|
||||
while (updatesThisCall < m_maxUpdates)
|
||||
|
||||
while (updatesThisCall < maxUpdates)
|
||||
{
|
||||
lock (m_entityUpdates.SyncRoot)
|
||||
if (!m_entityUpdates.TryDequeue(out update, out timeinqueue))
|
||||
if (!m_entityUpdates.TryDequeue(out iupdate, out timeinqueue))
|
||||
break;
|
||||
|
||||
EntityUpdate update = (EntityUpdate)iupdate;
|
||||
|
||||
avgTimeDilation += update.TimeDilation;
|
||||
avgTimeDilation *= 0.5f;
|
||||
|
||||
@@ -3770,7 +3680,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.ParentGroup.IsAttachment && m_disableFacelights)
|
||||
{
|
||||
if (part.ParentGroup.RootPart.Shape.State != (byte)AttachmentPoint.LeftHand &&
|
||||
@@ -3785,7 +3694,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
#region UpdateFlags to packet type conversion
|
||||
|
||||
PrimUpdateFlags updateFlags = update.Flags;
|
||||
PrimUpdateFlags updateFlags = (PrimUpdateFlags)update.Flags;
|
||||
|
||||
bool canUseCompressed = true;
|
||||
bool canUseImproved = true;
|
||||
@@ -3960,12 +3869,36 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
#endregion Primitive Packet/Data Sending Methods
|
||||
|
||||
// These are used to implement an adaptive backoff in the number
|
||||
// of updates converted to packets. Since we don't want packets
|
||||
// to sit in the queue with old data, only convert enough updates
|
||||
// to packets that can be sent in 200ms.
|
||||
private Int32 m_LastQueueFill = 0;
|
||||
private Int32 m_maxUpdates = 0;
|
||||
|
||||
void HandleQueueEmpty(ThrottleOutPacketTypeFlags categories)
|
||||
{
|
||||
if ((categories & ThrottleOutPacketTypeFlags.Task) != 0)
|
||||
{
|
||||
if (m_maxUpdates == 0 || m_LastQueueFill == 0)
|
||||
{
|
||||
m_maxUpdates = m_udpServer.PrimUpdatesPerCallback;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Util.EnvironmentTickCountSubtract(m_LastQueueFill) < 200)
|
||||
m_maxUpdates += 5;
|
||||
else
|
||||
m_maxUpdates = m_maxUpdates >> 1;
|
||||
}
|
||||
m_maxUpdates = Util.Clamp<Int32>(m_maxUpdates,10,500);
|
||||
m_LastQueueFill = Util.EnvironmentTickCount();
|
||||
|
||||
if (m_entityUpdates.Count > 0)
|
||||
ProcessEntityUpdates(m_udpServer.PrimUpdatesPerCallback);
|
||||
ProcessEntityUpdates(m_maxUpdates);
|
||||
|
||||
if (m_entityProps.Count > 0)
|
||||
ProcessEntityPropertyRequests(m_maxUpdates);
|
||||
}
|
||||
|
||||
if ((categories & ThrottleOutPacketTypeFlags.Texture) != 0)
|
||||
@@ -4079,47 +4012,167 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
OutPacket(pack, ThrottleOutPacketType.Task);
|
||||
}
|
||||
|
||||
public void SendObjectPropertiesFamilyData(uint RequestFlags, UUID ObjectUUID, UUID OwnerID, UUID GroupID,
|
||||
uint BaseMask, uint OwnerMask, uint GroupMask, uint EveryoneMask,
|
||||
uint NextOwnerMask, int OwnershipCost, byte SaleType, int SalePrice, uint Category,
|
||||
UUID LastOwnerID, string ObjectName, string Description)
|
||||
private class ObjectPropertyUpdate : IEntityUpdate
|
||||
{
|
||||
ObjectPropertiesFamilyPacket objPropFamilyPack = (ObjectPropertiesFamilyPacket)PacketPool.Instance.GetPacket(PacketType.ObjectPropertiesFamily);
|
||||
// TODO: don't create new blocks if recycling an old packet
|
||||
|
||||
ObjectPropertiesFamilyPacket.ObjectDataBlock objPropDB = new ObjectPropertiesFamilyPacket.ObjectDataBlock();
|
||||
objPropDB.RequestFlags = RequestFlags;
|
||||
objPropDB.ObjectID = ObjectUUID;
|
||||
if (OwnerID == GroupID)
|
||||
objPropDB.OwnerID = UUID.Zero;
|
||||
else
|
||||
objPropDB.OwnerID = OwnerID;
|
||||
objPropDB.GroupID = GroupID;
|
||||
objPropDB.BaseMask = BaseMask;
|
||||
objPropDB.OwnerMask = OwnerMask;
|
||||
objPropDB.GroupMask = GroupMask;
|
||||
objPropDB.EveryoneMask = EveryoneMask;
|
||||
objPropDB.NextOwnerMask = NextOwnerMask;
|
||||
|
||||
// TODO: More properties are needed in SceneObjectPart!
|
||||
objPropDB.OwnershipCost = OwnershipCost;
|
||||
objPropDB.SaleType = SaleType;
|
||||
objPropDB.SalePrice = SalePrice;
|
||||
objPropDB.Category = Category;
|
||||
objPropDB.LastOwnerID = LastOwnerID;
|
||||
objPropDB.Name = Util.StringToBytes256(ObjectName);
|
||||
objPropDB.Description = Util.StringToBytes256(Description);
|
||||
objPropFamilyPack.ObjectData = objPropDB;
|
||||
objPropFamilyPack.Header.Zerocoded = true;
|
||||
OutPacket(objPropFamilyPack, ThrottleOutPacketType.Task);
|
||||
internal bool SendFamilyProps;
|
||||
internal bool SendObjectProps;
|
||||
|
||||
public ObjectPropertyUpdate(ISceneEntity entity, uint flags, bool sendfam, bool sendobj)
|
||||
: base(entity,flags)
|
||||
{
|
||||
SendFamilyProps = sendfam;
|
||||
SendObjectProps = sendobj;
|
||||
}
|
||||
public void Update(ObjectPropertyUpdate update)
|
||||
{
|
||||
SendFamilyProps = SendFamilyProps || update.SendFamilyProps;
|
||||
SendObjectProps = SendObjectProps || update.SendObjectProps;
|
||||
Flags |= update.Flags;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendObjectPropertiesFamilyData(ISceneEntity entity, uint requestFlags)
|
||||
{
|
||||
uint priority = 0; // time based ordering only
|
||||
lock (m_entityProps.SyncRoot)
|
||||
m_entityProps.Enqueue(priority, new ObjectPropertyUpdate(entity,requestFlags,true,false));
|
||||
}
|
||||
|
||||
public void SendObjectPropertiesReply(
|
||||
UUID ItemID, ulong CreationDate, UUID CreatorUUID, UUID FolderUUID, UUID FromTaskUUID,
|
||||
UUID GroupUUID, short InventorySerial, UUID LastOwnerUUID, UUID ObjectUUID,
|
||||
UUID OwnerUUID, string TouchTitle, byte[] TextureID, string SitTitle, string ItemName,
|
||||
string ItemDescription, uint OwnerMask, uint NextOwnerMask, uint GroupMask, uint EveryoneMask,
|
||||
uint BaseMask, byte saleType, int salePrice)
|
||||
public void SendObjectPropertiesReply(ISceneEntity entity)
|
||||
{
|
||||
uint priority = 0; // time based ordering only
|
||||
lock (m_entityProps.SyncRoot)
|
||||
m_entityProps.Enqueue(priority, new ObjectPropertyUpdate(entity,0,false,true));
|
||||
}
|
||||
|
||||
private void ProcessEntityPropertyRequests(int maxUpdates)
|
||||
{
|
||||
OpenSim.Framework.Lazy<List<ObjectPropertiesFamilyPacket.ObjectDataBlock>> objectFamilyBlocks =
|
||||
new OpenSim.Framework.Lazy<List<ObjectPropertiesFamilyPacket.ObjectDataBlock>>();
|
||||
|
||||
OpenSim.Framework.Lazy<List<ObjectPropertiesPacket.ObjectDataBlock>> objectPropertiesBlocks =
|
||||
new OpenSim.Framework.Lazy<List<ObjectPropertiesPacket.ObjectDataBlock>>();
|
||||
|
||||
IEntityUpdate iupdate;
|
||||
Int32 timeinqueue; // this is just debugging code & can be dropped later
|
||||
|
||||
int updatesThisCall = 0;
|
||||
while (updatesThisCall < m_maxUpdates)
|
||||
{
|
||||
lock (m_entityProps.SyncRoot)
|
||||
if (!m_entityProps.TryDequeue(out iupdate, out timeinqueue))
|
||||
break;
|
||||
|
||||
ObjectPropertyUpdate update = (ObjectPropertyUpdate)iupdate;
|
||||
if (update.SendFamilyProps)
|
||||
{
|
||||
if (update.Entity is SceneObjectPart)
|
||||
{
|
||||
SceneObjectPart sop = (SceneObjectPart)update.Entity;
|
||||
ObjectPropertiesFamilyPacket.ObjectDataBlock objPropDB = CreateObjectPropertiesFamilyBlock(sop,update.Flags);
|
||||
objectFamilyBlocks.Value.Add(objPropDB);
|
||||
}
|
||||
}
|
||||
|
||||
if (update.SendObjectProps)
|
||||
{
|
||||
if (update.Entity is SceneObjectPart)
|
||||
{
|
||||
SceneObjectPart sop = (SceneObjectPart)update.Entity;
|
||||
ObjectPropertiesPacket.ObjectDataBlock objPropDB = CreateObjectPropertiesBlock(sop);
|
||||
objectPropertiesBlocks.Value.Add(objPropDB);
|
||||
}
|
||||
}
|
||||
|
||||
updatesThisCall++;
|
||||
}
|
||||
|
||||
|
||||
Int32 ppcnt = 0;
|
||||
Int32 pbcnt = 0;
|
||||
|
||||
if (objectPropertiesBlocks.IsValueCreated)
|
||||
{
|
||||
List<ObjectPropertiesPacket.ObjectDataBlock> blocks = objectPropertiesBlocks.Value;
|
||||
|
||||
ObjectPropertiesPacket packet = (ObjectPropertiesPacket)PacketPool.Instance.GetPacket(PacketType.ObjectProperties);
|
||||
packet.ObjectData = new ObjectPropertiesPacket.ObjectDataBlock[blocks.Count];
|
||||
for (int i = 0; i < blocks.Count; i++)
|
||||
packet.ObjectData[i] = blocks[i];
|
||||
|
||||
packet.Header.Zerocoded = true;
|
||||
OutPacket(packet, ThrottleOutPacketType.Task, true);
|
||||
|
||||
pbcnt += blocks.Count;
|
||||
ppcnt++;
|
||||
}
|
||||
|
||||
Int32 fpcnt = 0;
|
||||
Int32 fbcnt = 0;
|
||||
|
||||
if (objectFamilyBlocks.IsValueCreated)
|
||||
{
|
||||
List<ObjectPropertiesFamilyPacket.ObjectDataBlock> blocks = objectFamilyBlocks.Value;
|
||||
|
||||
// ObjectPropertiesFamilyPacket objPropFamilyPack =
|
||||
// (ObjectPropertiesFamilyPacket)PacketPool.Instance.GetPacket(PacketType.ObjectPropertiesFamily);
|
||||
//
|
||||
// objPropFamilyPack.ObjectData = new ObjectPropertiesFamilyPacket.ObjectDataBlock[blocks.Count];
|
||||
// for (int i = 0; i < blocks.Count; i++)
|
||||
// objPropFamilyPack.ObjectData[i] = blocks[i];
|
||||
//
|
||||
// OutPacket(objPropFamilyPack, ThrottleOutPacketType.Task, true);
|
||||
|
||||
// one packet per object block... uggh...
|
||||
for (int i = 0; i < blocks.Count; i++)
|
||||
{
|
||||
ObjectPropertiesFamilyPacket packet =
|
||||
(ObjectPropertiesFamilyPacket)PacketPool.Instance.GetPacket(PacketType.ObjectPropertiesFamily);
|
||||
|
||||
packet.ObjectData = blocks[i];
|
||||
packet.Header.Zerocoded = true;
|
||||
OutPacket(packet, ThrottleOutPacketType.Task);
|
||||
|
||||
fpcnt++;
|
||||
fbcnt++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// m_log.WarnFormat("[PACKETCOUNTS] queued {0} property packets with {1} blocks",ppcnt,pbcnt);
|
||||
// m_log.WarnFormat("[PACKETCOUNTS] queued {0} family property packets with {1} blocks",fpcnt,fbcnt);
|
||||
}
|
||||
|
||||
private ObjectPropertiesFamilyPacket.ObjectDataBlock CreateObjectPropertiesFamilyBlock(SceneObjectPart sop, uint requestFlags)
|
||||
{
|
||||
ObjectPropertiesFamilyPacket.ObjectDataBlock block = new ObjectPropertiesFamilyPacket.ObjectDataBlock();
|
||||
|
||||
block.RequestFlags = requestFlags;
|
||||
block.ObjectID = sop.UUID;
|
||||
if (sop.OwnerID == sop.GroupID)
|
||||
block.OwnerID = UUID.Zero;
|
||||
else
|
||||
block.OwnerID = sop.OwnerID;
|
||||
block.GroupID = sop.GroupID;
|
||||
block.BaseMask = sop.BaseMask;
|
||||
block.OwnerMask = sop.OwnerMask;
|
||||
block.GroupMask = sop.GroupMask;
|
||||
block.EveryoneMask = sop.EveryoneMask;
|
||||
block.NextOwnerMask = sop.NextOwnerMask;
|
||||
|
||||
// TODO: More properties are needed in SceneObjectPart!
|
||||
block.OwnershipCost = sop.OwnershipCost;
|
||||
block.SaleType = sop.ObjectSaleType;
|
||||
block.SalePrice = sop.SalePrice;
|
||||
block.Category = sop.Category;
|
||||
block.LastOwnerID = sop.CreatorID; // copied from old SOG call... is this right?
|
||||
block.Name = Util.StringToBytes256(sop.Name);
|
||||
block.Description = Util.StringToBytes256(sop.Description);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private ObjectPropertiesPacket.ObjectDataBlock CreateObjectPropertiesBlock(SceneObjectPart sop)
|
||||
{
|
||||
//ObjectPropertiesPacket proper = (ObjectPropertiesPacket)PacketPool.Instance.GetPacket(PacketType.ObjectProperties);
|
||||
// TODO: don't create new blocks if recycling an old packet
|
||||
@@ -4127,87 +4180,38 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
ObjectPropertiesPacket.ObjectDataBlock block =
|
||||
new ObjectPropertiesPacket.ObjectDataBlock();
|
||||
|
||||
block.ItemID = ItemID;
|
||||
block.CreationDate = CreationDate;
|
||||
block.CreatorID = CreatorUUID;
|
||||
block.FolderID = FolderUUID;
|
||||
block.FromTaskID = FromTaskUUID;
|
||||
block.GroupID = GroupUUID;
|
||||
block.InventorySerial = InventorySerial;
|
||||
block.ObjectID = sop.UUID;
|
||||
block.Name = Util.StringToBytes256(sop.Name);
|
||||
block.Description = Util.StringToBytes256(sop.Description);
|
||||
|
||||
block.LastOwnerID = LastOwnerUUID;
|
||||
// proper.ObjectData[0].LastOwnerID = UUID.Zero;
|
||||
|
||||
block.ObjectID = ObjectUUID;
|
||||
if (OwnerUUID == GroupUUID)
|
||||
block.CreationDate = (ulong)sop.CreationDate * 1000000; // viewer wants date in microseconds
|
||||
block.CreatorID = sop.CreatorID;
|
||||
block.GroupID = sop.GroupID;
|
||||
block.LastOwnerID = sop.LastOwnerID;
|
||||
if (sop.OwnerID == sop.GroupID)
|
||||
block.OwnerID = UUID.Zero;
|
||||
else
|
||||
block.OwnerID = OwnerUUID;
|
||||
block.TouchName = Util.StringToBytes256(TouchTitle);
|
||||
block.TextureID = TextureID;
|
||||
block.SitName = Util.StringToBytes256(SitTitle);
|
||||
block.Name = Util.StringToBytes256(ItemName);
|
||||
block.Description = Util.StringToBytes256(ItemDescription);
|
||||
block.OwnerMask = OwnerMask;
|
||||
block.NextOwnerMask = NextOwnerMask;
|
||||
block.GroupMask = GroupMask;
|
||||
block.EveryoneMask = EveryoneMask;
|
||||
block.BaseMask = BaseMask;
|
||||
// proper.ObjectData[0].AggregatePerms = 53;
|
||||
// proper.ObjectData[0].AggregatePermTextures = 0;
|
||||
// proper.ObjectData[0].AggregatePermTexturesOwner = 0;
|
||||
block.SaleType = saleType;
|
||||
block.SalePrice = salePrice;
|
||||
block.OwnerID = sop.OwnerID;
|
||||
|
||||
lock (m_propertiesPacketTimer)
|
||||
{
|
||||
m_propertiesBlocks.Add(block);
|
||||
block.ItemID = sop.FromUserInventoryItemID;
|
||||
block.FolderID = UUID.Zero; // sop.FromFolderID ??
|
||||
block.FromTaskID = UUID.Zero; // ???
|
||||
block.InventorySerial = (short)sop.InventorySerial;
|
||||
|
||||
SceneObjectPart root = sop.ParentGroup.RootPart;
|
||||
|
||||
int length = 0;
|
||||
foreach (ObjectPropertiesPacket.ObjectDataBlock b in m_propertiesBlocks)
|
||||
{
|
||||
length += b.Length;
|
||||
}
|
||||
if (length > 1100) // FIXME: use real MTU
|
||||
{
|
||||
ProcessObjectPropertiesPacket(null, null);
|
||||
m_propertiesPacketTimer.Stop();
|
||||
return;
|
||||
}
|
||||
block.TouchName = Util.StringToBytes256(root.TouchName);
|
||||
block.TextureID = new byte[0]; // TextureID ???
|
||||
block.SitName = Util.StringToBytes256(root.SitName);
|
||||
block.OwnerMask = root.OwnerMask;
|
||||
block.NextOwnerMask = root.NextOwnerMask;
|
||||
block.GroupMask = root.GroupMask;
|
||||
block.EveryoneMask = root.EveryoneMask;
|
||||
block.BaseMask = root.BaseMask;
|
||||
block.SaleType = root.ObjectSaleType;
|
||||
block.SalePrice = root.SalePrice;
|
||||
|
||||
m_propertiesPacketTimer.Stop();
|
||||
m_propertiesPacketTimer.Start();
|
||||
}
|
||||
|
||||
//proper.Header.Zerocoded = true;
|
||||
//OutPacket(proper, ThrottleOutPacketType.Task);
|
||||
}
|
||||
|
||||
private void ProcessObjectPropertiesPacket(Object sender, ElapsedEventArgs e)
|
||||
{
|
||||
ObjectPropertiesPacket proper = (ObjectPropertiesPacket)PacketPool.Instance.GetPacket(PacketType.ObjectProperties);
|
||||
|
||||
lock (m_propertiesPacketTimer)
|
||||
{
|
||||
m_propertiesPacketTimer.Stop();
|
||||
|
||||
if (m_propertiesBlocks.Count == 0)
|
||||
return;
|
||||
|
||||
proper.ObjectData = new ObjectPropertiesPacket.ObjectDataBlock[m_propertiesBlocks.Count];
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (ObjectPropertiesPacket.ObjectDataBlock b in m_propertiesBlocks)
|
||||
{
|
||||
proper.ObjectData[index++] = b;
|
||||
}
|
||||
|
||||
m_propertiesBlocks.Clear();
|
||||
}
|
||||
|
||||
proper.Header.Zerocoded = true;
|
||||
OutPacket(proper, ThrottleOutPacketType.Task);
|
||||
return block;
|
||||
}
|
||||
|
||||
#region Estate Data Sending Methods
|
||||
@@ -4548,6 +4552,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
public void SendForceClientSelectObjects(List<uint> ObjectIDs)
|
||||
{
|
||||
m_log.WarnFormat("[LLCLIENTVIEW] sending select with {0} objects", ObjectIDs.Count);
|
||||
|
||||
bool firstCall = true;
|
||||
const int MAX_OBJECTS_PER_PACKET = 251;
|
||||
ForceObjectSelectPacket pack = (ForceObjectSelectPacket)PacketPool.Instance.GetPacket(PacketType.ForceObjectSelect);
|
||||
@@ -11491,7 +11497,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
if (logPacket)
|
||||
m_log.DebugFormat("[CLIENT]: Packet OUT {0}", packet.Type);
|
||||
}
|
||||
|
||||
|
||||
m_udpServer.SendPacket(m_udpClient, packet, throttlePacketType, doAutomaticSplitting);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
private int m_nextOnQueueEmpty = 1;
|
||||
|
||||
/// <summary>Throttle bucket for this agent's connection</summary>
|
||||
private readonly TokenBucket m_throttle;
|
||||
private readonly TokenBucket m_throttleClient;
|
||||
/// <summary>Throttle bucket for this agent's connection</summary>
|
||||
private readonly TokenBucket m_throttleCategory;
|
||||
/// <summary>Throttle buckets for each packet category</summary>
|
||||
private readonly TokenBucket[] m_throttleCategories;
|
||||
/// <summary>Outgoing queues for throttled packets</summary>
|
||||
@@ -175,7 +177,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
m_maxRTO = maxRTO;
|
||||
|
||||
// Create a token bucket throttle for this client that has the scene token bucket as a parent
|
||||
m_throttle = new TokenBucket(parentThrottle, rates.TotalLimit, rates.Total);
|
||||
m_throttleClient = new TokenBucket(parentThrottle, rates.TotalLimit);
|
||||
// Create a token bucket throttle for the total categary with the client bucket as a throttle
|
||||
m_throttleCategory = new TokenBucket(m_throttleClient, rates.TotalLimit);
|
||||
// Create an array of token buckets for this clients different throttle categories
|
||||
m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT];
|
||||
|
||||
@@ -186,7 +190,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
// Initialize the packet outboxes, where packets sit while they are waiting for tokens
|
||||
m_packetOutboxes[i] = new OpenSim.Framework.LocklessQueue<OutgoingPacket>();
|
||||
// Initialize the token buckets that control the throttling for each category
|
||||
m_throttleCategories[i] = new TokenBucket(m_throttle, rates.GetLimit(type), rates.GetRate(type));
|
||||
m_throttleCategories[i] = new TokenBucket(m_throttleCategory, rates.GetLimit(type));
|
||||
}
|
||||
|
||||
// Default the retransmission timeout to three seconds
|
||||
@@ -207,6 +211,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
m_packetOutboxes[i].Clear();
|
||||
m_nextPackets[i] = null;
|
||||
}
|
||||
|
||||
// pull the throttle out of the scene throttle
|
||||
m_throttleClient.Parent.UnregisterRequest(m_throttleClient);
|
||||
OnPacketStats = null;
|
||||
OnQueueEmpty = null;
|
||||
}
|
||||
@@ -217,6 +224,26 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
/// <returns>Information about the client connection</returns>
|
||||
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
|
||||
// of pending and needed ACKs for every client every time some method wants information about
|
||||
// this connection is a recipe for poor performance
|
||||
@@ -224,13 +251,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
info.pendingAcks = new Dictionary<uint, uint>();
|
||||
info.needAck = new Dictionary<uint, byte[]>();
|
||||
|
||||
info.resendThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate;
|
||||
info.landThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate;
|
||||
info.windThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate;
|
||||
info.cloudThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate;
|
||||
info.taskThrottle = m_throttleCategories[(int)ThrottleOutPacketType.State].DripRate + m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate;
|
||||
info.assetThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate;
|
||||
info.textureThrottle = m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate;
|
||||
info.resendThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate;
|
||||
info.landThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate;
|
||||
info.windThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Wind].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.assetThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate;
|
||||
info.textureThrottle = (int)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate;
|
||||
info.totalThrottle = info.resendThrottle + info.landThrottle + info.windThrottle + info.cloudThrottle +
|
||||
info.taskThrottle + info.assetThrottle + info.textureThrottle;
|
||||
|
||||
@@ -318,8 +346,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4;
|
||||
int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
|
||||
// State is a subcategory of task that we allocate a percentage to
|
||||
int state = (int)((float)task * STATE_TASK_PERCENTAGE);
|
||||
task -= state;
|
||||
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,
|
||||
// otherwise a throttle could become permanently clogged
|
||||
@@ -340,40 +369,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
// Update the token buckets with new throttle values
|
||||
TokenBucket bucket;
|
||||
|
||||
bucket = m_throttle;
|
||||
bucket.MaxBurst = total;
|
||||
bucket = m_throttleCategory;
|
||||
bucket.RequestedDripRate = total;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Resend];
|
||||
bucket.DripRate = resend;
|
||||
bucket.MaxBurst = resend;
|
||||
bucket.RequestedDripRate = resend;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Land];
|
||||
bucket.DripRate = land;
|
||||
bucket.MaxBurst = land;
|
||||
bucket.RequestedDripRate = land;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Wind];
|
||||
bucket.DripRate = wind;
|
||||
bucket.MaxBurst = wind;
|
||||
bucket.RequestedDripRate = wind;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Cloud];
|
||||
bucket.DripRate = cloud;
|
||||
bucket.MaxBurst = cloud;
|
||||
bucket.RequestedDripRate = cloud;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Asset];
|
||||
bucket.DripRate = asset;
|
||||
bucket.MaxBurst = asset;
|
||||
bucket.RequestedDripRate = asset;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Task];
|
||||
bucket.DripRate = task + state;
|
||||
bucket.MaxBurst = task + state;
|
||||
bucket.RequestedDripRate = task;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.State];
|
||||
bucket.DripRate = state;
|
||||
bucket.MaxBurst = state;
|
||||
bucket.RequestedDripRate = state;
|
||||
|
||||
bucket = m_throttleCategories[(int)ThrottleOutPacketType.Texture];
|
||||
bucket.DripRate = texture;
|
||||
bucket.MaxBurst = texture;
|
||||
bucket.RequestedDripRate = texture;
|
||||
|
||||
// Reset the packed throttles cached data
|
||||
m_packedThrottles = null;
|
||||
@@ -388,14 +409,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
data = new byte[7 * 4];
|
||||
int i = 0;
|
||||
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Land].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)(m_throttleCategories[(int)ThrottleOutPacketType.Task].DripRate) +
|
||||
m_throttleCategories[(int)ThrottleOutPacketType.State].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Resend].RequestedDripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Land].RequestedDripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Wind].RequestedDripRate), 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;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Texture].RequestedDripRate), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes((float)m_throttleCategories[(int)ThrottleOutPacketType.Asset].RequestedDripRate), 0, data, i, 4); i += 4;
|
||||
|
||||
m_packedThrottles = data;
|
||||
}
|
||||
@@ -428,6 +448,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
|
||||
TokenBucket bucket = m_throttleCategories[category];
|
||||
|
||||
// Don't send this packet if there is already a packet waiting in the queue
|
||||
// even if we have the tokens to send it, tokens should go to the already
|
||||
// queued packets
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
queue.Enqueue(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!forceQueue && bucket.RemoveTokens(packet.Buffer.DataLength))
|
||||
{
|
||||
// Enough tokens were removed from the bucket, the packet will not be queued
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
}
|
||||
#endregion BinaryStats
|
||||
|
||||
m_throttle = new TokenBucket(null, sceneThrottleBps, sceneThrottleBps);
|
||||
m_throttle = new TokenBucket(null, sceneThrottleBps);
|
||||
ThrottleRates = new ThrottleRates(configSource);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enqueue(uint pqueue, EntityUpdate value)
|
||||
public bool Enqueue(uint pqueue, IEntityUpdate value)
|
||||
{
|
||||
LookupItem lookup;
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
if (m_lookupTable.TryGetValue(localid, out lookup))
|
||||
{
|
||||
entry = lookup.Heap[lookup.Handle].EntryOrder;
|
||||
value.Flags |= lookup.Heap[lookup.Handle].Value.Flags;
|
||||
value.Update(lookup.Heap[lookup.Handle].Value);
|
||||
lookup.Heap.Remove(lookup.Handle);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool TryDequeue(out EntityUpdate value, out Int32 timeinqueue)
|
||||
internal bool TryDequeue(out IEntityUpdate value, out Int32 timeinqueue)
|
||||
{
|
||||
for (int i = 0; i < m_numberOfQueues; ++i)
|
||||
{
|
||||
@@ -122,7 +122,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
}
|
||||
|
||||
timeinqueue = 0;
|
||||
value = default(EntityUpdate);
|
||||
value = default(IEntityUpdate);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,8 +175,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
#region MinHeapItem
|
||||
private struct MinHeapItem : IComparable<MinHeapItem>
|
||||
{
|
||||
private EntityUpdate value;
|
||||
internal EntityUpdate Value {
|
||||
private IEntityUpdate value;
|
||||
internal IEntityUpdate Value {
|
||||
get {
|
||||
return this.value;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
this.pqueue = pqueue;
|
||||
}
|
||||
|
||||
internal MinHeapItem(uint pqueue, UInt64 entryorder, EntityUpdate value)
|
||||
internal MinHeapItem(uint pqueue, UInt64 entryorder, IEntityUpdate value)
|
||||
{
|
||||
this.entrytime = Util.EnvironmentTickCount();
|
||||
this.entryorder = entryorder;
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
|
||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
{
|
||||
@@ -35,89 +39,126 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
/// </summary>
|
||||
public class TokenBucket
|
||||
{
|
||||
/// <summary>Parent bucket to this bucket, or null if this is a root
|
||||
/// bucket</summary>
|
||||
TokenBucket parent;
|
||||
/// <summary>Size of the bucket in bytes. If zero, the bucket has
|
||||
/// infinite capacity</summary>
|
||||
int maxBurst;
|
||||
/// <summary>Rate that the bucket fills, in bytes per millisecond. If
|
||||
/// zero, the bucket always remains full</summary>
|
||||
int tokensPerMS;
|
||||
/// <summary>Number of tokens currently in the bucket</summary>
|
||||
int content;
|
||||
/// <summary>Time of the last drip, in system ticks</summary>
|
||||
int lastDrip;
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private static Int32 m_counter = 0;
|
||||
|
||||
private Int32 m_identifier;
|
||||
|
||||
/// <summary>
|
||||
/// Number of ticks (ms) per quantum, drip rate and max burst
|
||||
/// are defined over this interval.
|
||||
/// </summary>
|
||||
private const Int32 m_ticksPerQuantum = 1000;
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// This is the number of quantums worth of packets that can
|
||||
/// be accommodated during a burst
|
||||
/// </summary>
|
||||
private const Double m_quantumsPerBurst = 1.5;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private const Int32 m_minimumDripRate = 1400;
|
||||
|
||||
/// <summary>Time of the last drip, in system ticks</summary>
|
||||
private Int32 m_lastDrip;
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes that can be sent at this moment. This is the
|
||||
/// current number of tokens in the bucket
|
||||
/// </summary>
|
||||
private Int64 m_tokenCount;
|
||||
|
||||
/// <summary>
|
||||
/// Map of children buckets and their requested maximum burst rate
|
||||
/// </summary>
|
||||
private Dictionary<TokenBucket,Int64> m_children = new Dictionary<TokenBucket,Int64>();
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The parent bucket of this bucket, or null if this bucket has no
|
||||
/// parent. The parent bucket will limit the aggregate bandwidth of all
|
||||
/// of its children buckets
|
||||
/// </summary>
|
||||
private TokenBucket m_parent;
|
||||
public TokenBucket Parent
|
||||
{
|
||||
get { return parent; }
|
||||
get { return m_parent; }
|
||||
set { m_parent = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum burst rate in bytes per second. This is the maximum number
|
||||
/// of tokens that can accumulate in the bucket at any one time
|
||||
/// of tokens that can accumulate in the bucket at any one time. This
|
||||
/// also sets the total request for leaf nodes
|
||||
/// </summary>
|
||||
public int MaxBurst
|
||||
private Int64 m_burstRate;
|
||||
public Int64 RequestedBurstRate
|
||||
{
|
||||
get { return maxBurst; }
|
||||
set { maxBurst = (value >= 0 ? value : 0); }
|
||||
get { return m_burstRate; }
|
||||
set { m_burstRate = (value < 0 ? 0 : value); }
|
||||
}
|
||||
|
||||
public Int64 BurstRate
|
||||
{
|
||||
get {
|
||||
double rate = RequestedBurstRate * BurstRateModifier();
|
||||
if (rate < m_minimumDripRate * m_quantumsPerBurst)
|
||||
rate = m_minimumDripRate * m_quantumsPerBurst;
|
||||
|
||||
return (Int64) rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The speed limit of this bucket in bytes per second. This is the
|
||||
/// number of tokens that are added to the bucket per second
|
||||
/// number of tokens that are added to the bucket per quantum
|
||||
/// </summary>
|
||||
/// <remarks>Tokens are added to the bucket any time
|
||||
/// <seealso cref="RemoveTokens"/> is called, at the granularity of
|
||||
/// the system tick interval (typically around 15-22ms)</remarks>
|
||||
public int DripRate
|
||||
private Int64 m_dripRate;
|
||||
public Int64 RequestedDripRate
|
||||
{
|
||||
get { return tokensPerMS * 1000; }
|
||||
set
|
||||
{
|
||||
if (value == 0)
|
||||
tokensPerMS = 0;
|
||||
else
|
||||
{
|
||||
int bpms = (int)((float)value / 1000.0f);
|
||||
get { return (m_dripRate == 0 ? m_totalDripRequest : m_dripRate); }
|
||||
set {
|
||||
m_dripRate = (value < 0 ? 0 : value);
|
||||
m_burstRate = (Int64)((double)m_dripRate * m_quantumsPerBurst);
|
||||
m_totalDripRequest = m_dripRate;
|
||||
if (m_parent != null)
|
||||
m_parent.RegisterRequest(this,m_dripRate);
|
||||
}
|
||||
}
|
||||
|
||||
if (bpms <= 0)
|
||||
tokensPerMS = 1; // 1 byte/ms is the minimum granularity
|
||||
else
|
||||
tokensPerMS = bpms;
|
||||
}
|
||||
public Int64 DripRate
|
||||
{
|
||||
get {
|
||||
if (m_parent == null)
|
||||
return Math.Min(RequestedDripRate,TotalDripRequest);
|
||||
|
||||
double rate = (double)RequestedDripRate * m_parent.DripRateModifier();
|
||||
if (rate < m_minimumDripRate)
|
||||
rate = m_minimumDripRate;
|
||||
|
||||
return (Int64)rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The speed limit of this bucket in bytes per millisecond
|
||||
/// The current total of the requested maximum burst rates of
|
||||
/// this bucket's children buckets.
|
||||
/// </summary>
|
||||
public int DripPerMS
|
||||
{
|
||||
get { return tokensPerMS; }
|
||||
}
|
||||
private Int64 m_totalDripRequest;
|
||||
public Int64 TotalDripRequest
|
||||
{
|
||||
get { return m_totalDripRequest; }
|
||||
set { m_totalDripRequest = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes that can be sent at this moment. This is the
|
||||
/// current number of tokens in the bucket
|
||||
/// <remarks>If this bucket has a parent bucket that does not have
|
||||
/// enough tokens for a request, <seealso cref="RemoveTokens"/> will
|
||||
/// return false regardless of the content of this bucket</remarks>
|
||||
/// </summary>
|
||||
public int Content
|
||||
{
|
||||
get { return content; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
@@ -128,46 +169,114 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
/// zero if this bucket has no maximum capacity</param>
|
||||
/// <param name="dripRate">Rate that the bucket fills, in bytes per
|
||||
/// second. If zero, the bucket always remains full</param>
|
||||
public TokenBucket(TokenBucket parent, int maxBurst, int dripRate)
|
||||
public TokenBucket(TokenBucket parent, Int64 dripRate)
|
||||
{
|
||||
this.parent = parent;
|
||||
MaxBurst = maxBurst;
|
||||
DripRate = dripRate;
|
||||
lastDrip = Environment.TickCount;
|
||||
m_identifier = m_counter++;
|
||||
|
||||
Parent = parent;
|
||||
RequestedDripRate = dripRate;
|
||||
// TotalDripRequest = dripRate; // this will be overwritten when a child node registers
|
||||
// MaxBurst = (Int64)((double)dripRate * m_quantumsPerBurst);
|
||||
m_lastDrip = Environment.TickCount & Int32.MaxValue;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Compute a modifier for the MaxBurst rate. This is 1.0, meaning
|
||||
/// no modification if the requested bandwidth is less than the
|
||||
/// max burst bandwidth all the way to the root of the throttle
|
||||
/// hierarchy. However, if any of the parents is over-booked, then
|
||||
/// the modifier will be less than 1.
|
||||
/// </summary>
|
||||
private double DripRateModifier()
|
||||
{
|
||||
Int64 driprate = DripRate;
|
||||
return driprate >= TotalDripRequest ? 1.0 : (double)driprate / (double)TotalDripRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private double BurstRateModifier()
|
||||
{
|
||||
// for now... burst rate is always m_quantumsPerBurst (constant)
|
||||
// larger than drip rate so the ratio of burst requests is the
|
||||
// same as the drip ratio
|
||||
return DripRateModifier();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register drip rate requested by a child of this throttle. Pass the
|
||||
/// changes up the hierarchy.
|
||||
/// </summary>
|
||||
public void RegisterRequest(TokenBucket child, Int64 request)
|
||||
{
|
||||
m_children[child] = request;
|
||||
// m_totalDripRequest = m_children.Values.Sum();
|
||||
|
||||
m_totalDripRequest = 0;
|
||||
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
||||
m_totalDripRequest += cref.Value;
|
||||
|
||||
// Pass the new values up to the parent
|
||||
if (m_parent != null)
|
||||
m_parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the rate requested by a child of this throttle. Pass the
|
||||
/// changes up the hierarchy.
|
||||
/// </summary>
|
||||
public void UnregisterRequest(TokenBucket child)
|
||||
{
|
||||
m_children.Remove(child);
|
||||
// m_totalDripRequest = m_children.Values.Sum();
|
||||
|
||||
m_totalDripRequest = 0;
|
||||
foreach (KeyValuePair<TokenBucket, Int64> cref in m_children)
|
||||
m_totalDripRequest += cref.Value;
|
||||
|
||||
// Pass the new values up to the parent
|
||||
if (m_parent != null)
|
||||
m_parent.RegisterRequest(this,Math.Min(RequestedDripRate, TotalDripRequest));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a given number of tokens from the bucket
|
||||
/// </summary>
|
||||
/// <param name="amount">Number of tokens to remove from the bucket</param>
|
||||
/// <returns>True if the requested number of tokens were removed from
|
||||
/// the bucket, otherwise false</returns>
|
||||
public bool RemoveTokens(int amount)
|
||||
public bool RemoveTokens(Int64 amount)
|
||||
{
|
||||
if (maxBurst == 0)
|
||||
// Deposit tokens for this interval
|
||||
Drip();
|
||||
|
||||
// If we have enough tokens then remove them and return
|
||||
if (m_tokenCount - amount >= 0)
|
||||
{
|
||||
// we don't have to remove from the parent, the drip rate is already
|
||||
// reflective of the drip rate limits in the parent
|
||||
m_tokenCount -= amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (amount > maxBurst)
|
||||
{
|
||||
throw new Exception("amount " + amount + " exceeds maxBurst " + maxBurst);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Drip();
|
||||
/// <summary>
|
||||
/// Deposit tokens into the bucket from a child bucket that did
|
||||
/// not use all of its available tokens
|
||||
/// </summary>
|
||||
private void Deposit(Int64 count)
|
||||
{
|
||||
m_tokenCount += count;
|
||||
|
||||
if (content < amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent != null && !parent.RemoveTokens(amount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
content -= amount;
|
||||
return true;
|
||||
// Deposit the overflow in the parent bucket, this is how we share
|
||||
// unused bandwidth
|
||||
Int64 burstrate = BurstRate;
|
||||
if (m_tokenCount > burstrate)
|
||||
m_tokenCount = burstrate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,30 +285,29 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
/// call to Drip
|
||||
/// </summary>
|
||||
/// <returns>True if tokens were added to the bucket, otherwise false</returns>
|
||||
public bool Drip()
|
||||
private void Drip()
|
||||
{
|
||||
if (tokensPerMS == 0)
|
||||
// This should never happen... means we are a leaf node and were created
|
||||
// with no drip rate...
|
||||
if (DripRate == 0)
|
||||
{
|
||||
content = maxBurst;
|
||||
return true;
|
||||
m_log.WarnFormat("[TOKENBUCKET] something odd is happening and drip rate is 0");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the interval over which we are adding tokens, never add
|
||||
// more than a single quantum of tokens
|
||||
Int32 now = Environment.TickCount & Int32.MaxValue;
|
||||
Int32 deltaMS = Math.Min(now - m_lastDrip, m_ticksPerQuantum);
|
||||
|
||||
int now = Environment.TickCount;
|
||||
int deltaMS = now - lastDrip;
|
||||
lastDrip = now;
|
||||
m_lastDrip = now;
|
||||
|
||||
// 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
|
||||
if (deltaMS <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return;
|
||||
|
||||
long dripAmount = (long)deltaMS * (long)tokensPerMS + (long)content;
|
||||
if (dripAmount > maxBurst)
|
||||
{
|
||||
dripAmount = maxBurst;
|
||||
}
|
||||
content = (int)dripAmount;
|
||||
return true;
|
||||
Deposit(deltaMS * DripRate / m_ticksPerQuantum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user