Requeue unacknowledged entity updates rather than resend then "as is".
Often, by the time the UDPServer realizes that an entity update packet has not been acknowledged, there is a newer update for the same entity already queued up or there is a higher priority update that should be sent first. This patch eliminates 1:1 packet resends for unacked entity update packets. Insteawd, unacked update packets are decomposed into the original entity updates and those updates are placed back into the priority queues based on their new priority but the original update timestamp. This will generally place them at the head of the line to be put back on the wire as a new outgoing packet but prevents the resend queue from filling up with multiple stale updates for the same entity. This new approach takes advantage of the UDP nature of the Linden protocol in that the intent of a reliable update packet is that if it goes unacknowledge, SOMETHING has to happen to get the update to the client. We are simply making sure that we are resending current object state rather than stale object state. Additionally, this patch includes a generalized callback mechanism so that any caller can specify their own method to call when a packet expires without being acknowledged. We use this mechanism to requeue update packets and otherwise use the UDPServer default method of just putting expired packets in the resend queue.bulletsim
parent
36c4e94ef7
commit
08d8a3e580
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the call stack at any given point. Useful for debugging.
|
||||
/// </summary>
|
||||
|
|
|
@ -3561,6 +3561,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
m_entityUpdates.Enqueue(priority, new EntityUpdate(entity, updateFlags, m_scene.TimeDilation));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
void ResendPrimUpdates(List<EntityUpdate> updates)
|
||||
{
|
||||
foreach (EntityUpdate update in updates)
|
||||
ResendPrimUpdate(update);
|
||||
}
|
||||
|
||||
private void ProcessEntityUpdates(int maxUpdates)
|
||||
{
|
||||
OpenSim.Framework.Lazy<List<ObjectUpdatePacket.ObjectDataBlock>> objectUpdateBlocks = new OpenSim.Framework.Lazy<List<ObjectUpdatePacket.ObjectDataBlock>>();
|
||||
|
@ -3568,6 +3596,11 @@ 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>>();
|
||||
|
||||
OpenSim.Framework.Lazy<List<EntityUpdate>> objectUpdates = new OpenSim.Framework.Lazy<List<EntityUpdate>>();
|
||||
OpenSim.Framework.Lazy<List<EntityUpdate>> compressedUpdates = new OpenSim.Framework.Lazy<List<EntityUpdate>>();
|
||||
OpenSim.Framework.Lazy<List<EntityUpdate>> terseUpdates = new OpenSim.Framework.Lazy<List<EntityUpdate>>();
|
||||
OpenSim.Framework.Lazy<List<EntityUpdate>> terseAgentUpdates = new OpenSim.Framework.Lazy<List<EntityUpdate>>();
|
||||
|
||||
// Check to see if this is a flush
|
||||
if (maxUpdates <= 0)
|
||||
{
|
||||
|
@ -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,10 +3755,6 @@ 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)
|
||||
|
@ -3730,9 +3768,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
|
||||
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)
|
||||
|
@ -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,8 +3798,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(compressedUpdates.Value); });
|
||||
}
|
||||
|
||||
if (terseUpdateBlocks.IsValueCreated)
|
||||
|
@ -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</param>
|
||||
protected void OutPacket(Packet packet, ThrottleOutPacketType throttlePacketType, bool doAutomaticSplitting)
|
||||
{
|
||||
OutPacket(packet, throttlePacketType, doAutomaticSplitting, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the starting point for sending a simulator packet out to the client
|
||||
/// </summary>
|
||||
/// <param name="packet">Packet to send</param>
|
||||
/// <param name="throttlePacketType">Throttling category for the packet</param>
|
||||
/// <param name="doAutomaticSplitting">True to automatically split oversized
|
||||
/// packets (the default), or false to disable splitting if the calling code
|
||||
/// handles splitting manually</param>
|
||||
/// <param name="method">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.</param>
|
||||
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)
|
||||
|
|
|
@ -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
|
|||
/// <param name="packet"></param>
|
||||
/// <param name="category"></param>
|
||||
/// <param name="allowSplitting"></param>
|
||||
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
|
|||
/// <param name="data"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="category"></param>
|
||||
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,16 +490,16 @@ 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();
|
||||
for (int i = 0; i < expiredPackets.Count; ++i)
|
||||
expiredPackets[i].UnackedMethod();
|
||||
}
|
||||
}
|
||||
|
||||
// Resend packets
|
||||
for (int i = 0; i < expiredPackets.Count; i++)
|
||||
public void ResendUnacked(OutgoingPacket outgoingPacket)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -512,8 +514,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, false))
|
||||
SendPacketFinal(outgoingPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush(LLUDPClient udpClient)
|
||||
{
|
||||
|
@ -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);
|
||||
|
|
|
@ -31,6 +31,7 @@ using OpenMetaverse;
|
|||
|
||||
namespace OpenSim.Region.ClientStack.LindenUDP
|
||||
{
|
||||
public delegate void UnackedPacketMethod();
|
||||
/// <summary>
|
||||
/// Holds a reference to the <seealso cref="LLUDPClient"/> 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;
|
||||
/// <summary>Category this packet belongs to</summary>
|
||||
public ThrottleOutPacketType Category;
|
||||
/// <summary>The delegate to be called if this packet is determined to be unacknowledged</summary>
|
||||
public UnackedPacketMethod UnackedMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
|
@ -60,11 +63,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP
|
|||
/// <param name="buffer">Serialized packet data. If the flags or sequence number
|
||||
/// need to be updated, they will be injected directly into this binary buffer</param>
|
||||
/// <param name="category">Throttling category for this packet</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue