diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs
index f573c32d07..069987bd71 100644
--- a/OpenSim/Framework/IClientAPI.cs
+++ b/OpenSim/Framework/IClientAPI.cs
@@ -572,34 +572,69 @@ namespace OpenSim.Framework
public class IEntityUpdate
{
- public ISceneEntity Entity;
- public uint Flags;
+ private ISceneEntity m_entity;
+ private uint m_flags;
+ private int m_updateTime;
+
+ public ISceneEntity Entity
+ {
+ get { return m_entity; }
+ }
+
+ public uint Flags
+ {
+ get { return m_flags; }
+ }
+
+ public int UpdateTime
+ {
+ get { return m_updateTime; }
+ }
public virtual void Update(IEntityUpdate update)
{
- this.Flags |= update.Flags;
+ m_flags |= update.Flags;
+
+ // Use the older of the updates as the updateTime
+ if (Util.EnvironmentTickCountCompare(UpdateTime, update.UpdateTime) > 0)
+ m_updateTime = update.UpdateTime;
}
public IEntityUpdate(ISceneEntity entity, uint flags)
{
- Entity = entity;
- Flags = flags;
+ m_entity = entity;
+ m_flags = flags;
+ m_updateTime = Util.EnvironmentTickCount();
+ }
+
+ public IEntityUpdate(ISceneEntity entity, uint flags, Int32 updateTime)
+ {
+ m_entity = entity;
+ m_flags = flags;
+ m_updateTime = updateTime;
}
}
-
public class EntityUpdate : IEntityUpdate
{
- // public ISceneEntity Entity;
- // public PrimUpdateFlags Flags;
- public float TimeDilation;
+ private float m_timeDilation;
+
+ public float TimeDilation
+ {
+ get { return m_timeDilation; }
+ }
public EntityUpdate(ISceneEntity entity, PrimUpdateFlags flags, float timedilation)
- : base(entity,(uint)flags)
+ : base(entity, (uint)flags)
{
- //Entity = entity;
// Flags = flags;
- TimeDilation = timedilation;
+ m_timeDilation = timedilation;
+ }
+
+ public EntityUpdate(ISceneEntity entity, PrimUpdateFlags flags, float timedilation, Int32 updateTime)
+ : base(entity,(uint)flags,updateTime)
+ {
+ m_timeDilation = timedilation;
}
}
diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs
index 5a5046e547..aaa2724532 100644
--- a/OpenSim/Framework/Util.cs
+++ b/OpenSim/Framework/Util.cs
@@ -1537,6 +1537,23 @@ namespace OpenSim.Framework
return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1);
}
+ // Returns value of Tick Count A - TickCount B accounting for wrapping of TickCount
+ // Assumes both tcA and tcB came from previous calls to Util.EnvironmentTickCount().
+ // A positive return value indicates A occured later than B
+ public static Int32 EnvironmentTickCountCompare(Int32 tcA, Int32 tcB)
+ {
+ // A, B and TC are all between 0 and 0x3fffffff
+ int tc = EnvironmentTickCount();
+
+ if (tc - tcA >= 0)
+ tcA += EnvironmentTickCountMask + 1;
+
+ if (tc - tcB >= 0)
+ tcB += EnvironmentTickCountMask + 1;
+
+ return tcA - tcB;
+ }
+
///
/// Prints the call stack at any given point. Useful for debugging.
///
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
index 1f7e66dabb..14c5d6c679 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
@@ -3561,6 +3561,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP
m_entityUpdates.Enqueue(priority, new EntityUpdate(entity, updateFlags, m_scene.TimeDilation));
}
+ ///
+ /// Requeue an EntityUpdate when it was not acknowledged by the client.
+ /// We will update the priority and put it in the correct queue, merging update flags
+ /// with any other updates that may be queued for the same entity.
+ /// The original update time is used for the merged update.
+ ///
+ public void ResendPrimUpdate(EntityUpdate update)
+ {
+ // If the update exists in priority queue, it will be updated.
+ // If it does not exist then it will be added with the current (rather than its original) priority
+ uint priority = m_prioritizer.GetUpdatePriority(this, update.Entity);
+
+ lock (m_entityUpdates.SyncRoot)
+ m_entityUpdates.Enqueue(priority, update);
+ }
+
+ ///
+ /// Requeue a list of EntityUpdates when they were not acknowledged by the client.
+ /// We will update the priority and put it in the correct queue, merging update flags
+ /// with any other updates that may be queued for the same entity.
+ /// The original update time is used for the merged update.
+ ///
+ void ResendPrimUpdates(List updates)
+ {
+ foreach (EntityUpdate update in updates)
+ ResendPrimUpdate(update);
+ }
+
private void ProcessEntityUpdates(int maxUpdates)
{
OpenSim.Framework.Lazy> objectUpdateBlocks = new OpenSim.Framework.Lazy>();
@@ -3568,6 +3596,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP
OpenSim.Framework.Lazy> terseUpdateBlocks = new OpenSim.Framework.Lazy>();
OpenSim.Framework.Lazy> terseAgentUpdateBlocks = new OpenSim.Framework.Lazy>();
+ OpenSim.Framework.Lazy> objectUpdates = new OpenSim.Framework.Lazy>();
+ OpenSim.Framework.Lazy> compressedUpdates = new OpenSim.Framework.Lazy>();
+ OpenSim.Framework.Lazy> terseUpdates = new OpenSim.Framework.Lazy>();
+ OpenSim.Framework.Lazy> terseAgentUpdates = new OpenSim.Framework.Lazy>();
+
// Check to see if this is a flush
if (maxUpdates <= 0)
{
@@ -3583,7 +3616,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
float avgTimeDilation = 1.0f;
IEntityUpdate iupdate;
Int32 timeinqueue; // this is just debugging code & can be dropped later
-
+
while (updatesThisCall < maxUpdates)
{
lock (m_entityUpdates.SyncRoot)
@@ -3688,24 +3721,33 @@ namespace OpenSim.Region.ClientStack.LindenUDP
if (update.Entity is ScenePresence)
{
objectUpdateBlocks.Value.Add(CreateAvatarUpdateBlock((ScenePresence)update.Entity));
+ objectUpdates.Value.Add(update);
}
else
{
objectUpdateBlocks.Value.Add(CreatePrimUpdateBlock((SceneObjectPart)update.Entity, this.m_agentId));
+ objectUpdates.Value.Add(update);
}
}
else if (!canUseImproved)
{
compressedUpdateBlocks.Value.Add(CreateCompressedUpdateBlock((SceneObjectPart)update.Entity, updateFlags));
+ compressedUpdates.Value.Add(update);
}
else
{
if (update.Entity is ScenePresence && ((ScenePresence)update.Entity).UUID == AgentId)
+ {
// Self updates go into a special list
terseAgentUpdateBlocks.Value.Add(CreateImprovedTerseBlock(update.Entity, updateFlags.HasFlag(PrimUpdateFlags.Textures)));
+ terseAgentUpdates.Value.Add(update);
+ }
else
+ {
// Everything else goes here
terseUpdateBlocks.Value.Add(CreateImprovedTerseBlock(update.Entity, updateFlags.HasFlag(PrimUpdateFlags.Textures)));
+ terseUpdates.Value.Add(update);
+ }
}
#endregion Block Construction
@@ -3713,28 +3755,23 @@ namespace OpenSim.Region.ClientStack.LindenUDP
#region Packet Sending
-
- //const float TIME_DILATION = 1.0f;
-
-
ushort timeDilation = Utils.FloatToUInt16(avgTimeDilation, 0.0f, 1.0f);
-
+
if (terseAgentUpdateBlocks.IsValueCreated)
{
List blocks = terseAgentUpdateBlocks.Value;
-
+
ImprovedTerseObjectUpdatePacket packet = new ImprovedTerseObjectUpdatePacket();
packet.RegionData.RegionHandle = m_scene.RegionInfo.RegionHandle;
packet.RegionData.TimeDilation = timeDilation;
packet.ObjectData = new ImprovedTerseObjectUpdatePacket.ObjectDataBlock[blocks.Count];
-
+
for (int i = 0; i < blocks.Count; i++)
packet.ObjectData[i] = blocks[i];
-
-
- OutPacket(packet, ThrottleOutPacketType.Unknown, true);
+ // If any of the packets created from this call go unacknowledged, all of the updates will be resent
+ OutPacket(packet, ThrottleOutPacketType.Unknown, true, delegate() { ResendPrimUpdates(terseAgentUpdates.Value); });
}
-
+
if (objectUpdateBlocks.IsValueCreated)
{
List blocks = objectUpdateBlocks.Value;
@@ -3746,8 +3783,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
for (int i = 0; i < blocks.Count; i++)
packet.ObjectData[i] = blocks[i];
-
- OutPacket(packet, ThrottleOutPacketType.Task, true);
+ // If any of the packets created from this call go unacknowledged, all of the updates will be resent
+ OutPacket(packet, ThrottleOutPacketType.Task, true, delegate() { ResendPrimUpdates(objectUpdates.Value); });
}
if (compressedUpdateBlocks.IsValueCreated)
@@ -3761,10 +3798,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP
for (int i = 0; i < blocks.Count; i++)
packet.ObjectData[i] = blocks[i];
-
- OutPacket(packet, ThrottleOutPacketType.Task, true);
+ // If any of the packets created from this call go unacknowledged, all of the updates will be resent
+ OutPacket(packet, ThrottleOutPacketType.Task, true, delegate() { ResendPrimUpdates(compressedUpdates.Value); });
}
-
+
if (terseUpdateBlocks.IsValueCreated)
{
List blocks = terseUpdateBlocks.Value;
@@ -3776,8 +3813,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
for (int i = 0; i < blocks.Count; i++)
packet.ObjectData[i] = blocks[i];
-
- OutPacket(packet, ThrottleOutPacketType.Task, true);
+ // If any of the packets created from this call go unacknowledged, all of the updates will be resent
+ OutPacket(packet, ThrottleOutPacketType.Task, true, delegate() { ResendPrimUpdates(terseUpdates.Value); });
}
}
@@ -3969,7 +4006,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
{
SendFamilyProps = SendFamilyProps || update.SendFamilyProps;
SendObjectProps = SendObjectProps || update.SendObjectProps;
- Flags |= update.Flags;
+ // other properties may need to be updated by base class
+ base.Update(update);
}
}
@@ -11362,6 +11400,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// packets (the default), or false to disable splitting if the calling code
/// handles splitting manually
protected void OutPacket(Packet packet, ThrottleOutPacketType throttlePacketType, bool doAutomaticSplitting)
+ {
+ OutPacket(packet, throttlePacketType, doAutomaticSplitting, null);
+ }
+
+ ///
+ /// This is the starting point for sending a simulator packet out to the client
+ ///
+ /// Packet to send
+ /// Throttling category for the packet
+ /// True to automatically split oversized
+ /// packets (the default), or false to disable splitting if the calling code
+ /// handles splitting manually
+ /// The method to be called in the event this packet is reliable
+ /// and unacknowledged. The server will provide normal resend capability if you do not
+ /// provide your own method.
+ protected void OutPacket(Packet packet, ThrottleOutPacketType throttlePacketType, bool doAutomaticSplitting, UnackedPacketMethod method)
{
if (m_debugPacketLevel > 0)
{
@@ -11388,7 +11442,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
m_log.DebugFormat("[CLIENT]: Packet OUT {0}", packet.Type);
}
- m_udpServer.SendPacket(m_udpClient, packet, throttlePacketType, doAutomaticSplitting);
+ m_udpServer.SendPacket(m_udpClient, packet, throttlePacketType, doAutomaticSplitting, method);
}
public bool AddMoney(int debit)
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
index d08b25f79d..0848979c3a 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
@@ -297,7 +297,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
delegate(IClientAPI client)
{
if (client is LLClientView)
- SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category);
+ SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null);
}
);
}
@@ -309,7 +309,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
delegate(IClientAPI client)
{
if (client is LLClientView)
- SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category);
+ SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null);
}
);
}
@@ -322,7 +322,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
///
///
///
- public void SendPacket(LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting)
+ public void SendPacket(LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting, UnackedPacketMethod method)
{
// CoarseLocationUpdate packets cannot be split in an automated way
if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting)
@@ -339,13 +339,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
for (int i = 0; i < packetCount; i++)
{
byte[] data = datas[i];
- SendPacketData(udpClient, data, packet.Type, category);
+ SendPacketData(udpClient, data, packet.Type, category, method);
}
}
else
{
byte[] data = packet.ToBytes();
- SendPacketData(udpClient, data, packet.Type, category);
+ SendPacketData(udpClient, data, packet.Type, category, method);
}
}
@@ -356,7 +356,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
///
///
///
- public void SendPacketData(LLUDPClient udpClient, byte[] data, PacketType type, ThrottleOutPacketType category)
+ public void SendPacketData(LLUDPClient udpClient, byte[] data, PacketType type, ThrottleOutPacketType category, UnackedPacketMethod method)
{
int dataLength = data.Length;
bool doZerocode = (data[0] & Helpers.MSG_ZEROCODED) != 0;
@@ -411,7 +411,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
#region Queue or Send
- OutgoingPacket outgoingPacket = new OutgoingPacket(udpClient, buffer, category);
+ OutgoingPacket outgoingPacket = new OutgoingPacket(udpClient, buffer, category, null);
+ // If we were not provided a method for handling unacked, use the UDPServer default method
+ outgoingPacket.UnackedMethod = ((method == null) ? delegate() { ResendUnacked(outgoingPacket); } : method);
// If a Linden Lab 1.23.5 client receives an update packet after a kill packet for an object, it will
// continue to display the deleted object until relog. Therefore, we need to always queue a kill object
@@ -445,7 +447,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
packet.Header.Reliable = false;
packet.Packets = blocks.ToArray();
- SendPacket(udpClient, packet, ThrottleOutPacketType.Unknown, true);
+ SendPacket(udpClient, packet, ThrottleOutPacketType.Unknown, true, null);
}
}
@@ -458,17 +460,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP
// We *could* get OldestUnacked, but it would hurt performance and not provide any benefit
pc.PingID.OldestUnacked = 0;
- SendPacket(udpClient, pc, ThrottleOutPacketType.Unknown, false);
+ SendPacket(udpClient, pc, ThrottleOutPacketType.Unknown, false, null);
}
public void CompletePing(LLUDPClient udpClient, byte pingID)
{
CompletePingCheckPacket completePing = new CompletePingCheckPacket();
completePing.PingID.PingID = pingID;
- SendPacket(udpClient, completePing, ThrottleOutPacketType.Unknown, false);
+ SendPacket(udpClient, completePing, ThrottleOutPacketType.Unknown, false, null);
}
- public void ResendUnacked(LLUDPClient udpClient)
+ public void HandleUnacked(LLUDPClient udpClient)
{
if (!udpClient.IsConnected)
return;
@@ -488,33 +490,31 @@ namespace OpenSim.Region.ClientStack.LindenUDP
if (expiredPackets != null)
{
- //m_log.Debug("[LLUDPSERVER]: Resending " + expiredPackets.Count + " packets to " + udpClient.AgentID + ", RTO=" + udpClient.RTO);
-
+ //m_log.Debug("[LLUDPSERVER]: Handling " + expiredPackets.Count + " packets to " + udpClient.AgentID + ", RTO=" + udpClient.RTO);
// Exponential backoff of the retransmission timeout
udpClient.BackoffRTO();
-
- // Resend packets
- for (int i = 0; i < expiredPackets.Count; i++)
- {
- OutgoingPacket outgoingPacket = expiredPackets[i];
-
- //m_log.DebugFormat("[LLUDPSERVER]: Resending packet #{0} (attempt {1}), {2}ms have passed",
- // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount);
-
- // Set the resent flag
- outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT);
- outgoingPacket.Category = ThrottleOutPacketType.Resend;
-
- // Bump up the resend count on this packet
- Interlocked.Increment(ref outgoingPacket.ResendCount);
-
- // Requeue or resend the packet
- if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, false))
- SendPacketFinal(outgoingPacket);
- }
+ for (int i = 0; i < expiredPackets.Count; ++i)
+ expiredPackets[i].UnackedMethod();
}
}
+ public void ResendUnacked(OutgoingPacket outgoingPacket)
+ {
+ //m_log.DebugFormat("[LLUDPSERVER]: Resending packet #{0} (attempt {1}), {2}ms have passed",
+ // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount);
+
+ // Set the resent flag
+ outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT);
+ outgoingPacket.Category = ThrottleOutPacketType.Resend;
+
+ // Bump up the resend count on this packet
+ Interlocked.Increment(ref outgoingPacket.ResendCount);
+
+ // Requeue or resend the packet
+ if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, false))
+ SendPacketFinal(outgoingPacket);
+ }
+
public void Flush(LLUDPClient udpClient)
{
// FIXME: Implement?
@@ -1096,7 +1096,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
if (udpClient.IsConnected)
{
if (m_resendUnacked)
- ResendUnacked(udpClient);
+ HandleUnacked(udpClient);
if (m_sendAcks)
SendAcks(udpClient);
@@ -1152,7 +1152,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
nticksUnack++;
watch2.Start();
- ResendUnacked(udpClient);
+ HandleUnacked(udpClient);
watch2.Stop();
avgResendUnackedTicks = (nticksUnack - 1)/(float)nticksUnack * avgResendUnackedTicks + (watch2.ElapsedTicks / (float)nticksUnack);
diff --git a/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs b/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs
index 1a1a1cb4e3..f4f024b35f 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/OutgoingPacket.cs
@@ -31,6 +31,7 @@ using OpenMetaverse;
namespace OpenSim.Region.ClientStack.LindenUDP
{
+ public delegate void UnackedPacketMethod();
///
/// Holds a reference to the this packet is
/// destined for, along with the serialized packet data, sequence number
@@ -52,6 +53,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
public int TickCount;
/// Category this packet belongs to
public ThrottleOutPacketType Category;
+ /// The delegate to be called if this packet is determined to be unacknowledged
+ public UnackedPacketMethod UnackedMethod;
///
/// Default constructor
@@ -60,11 +63,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// Serialized packet data. If the flags or sequence number
/// need to be updated, they will be injected directly into this binary buffer
/// Throttling category for this packet
- public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category)
+ public OutgoingPacket(LLUDPClient client, UDPPacketBuffer buffer, ThrottleOutPacketType category, UnackedPacketMethod method)
{
Client = client;
Buffer = buffer;
Category = category;
+ UnackedMethod = method;
}
}
}