Allow the server to ack no or multiple packets in the same frame.

This commit is contained in:
Paul Chote
2021-09-25 15:09:23 +01:00
committed by abcdefg30
parent a13d046304
commit 2d08f2bbfd
4 changed files with 40 additions and 13 deletions

View File

@@ -312,16 +312,26 @@ namespace OpenRA.Network
Send(ping); Send(ping);
record = false; record = false;
} }
else if (OrderIO.TryParseAck(p, out var ackFrame)) else if (OrderIO.TryParseAck(p, out var ackFrame, out var ackCount))
{ {
if (!sentOrders.TryDequeue(out var q)) if (ackCount > sentOrders.Count)
throw new InvalidOperationException("Received Ack with empty send queue"); throw new InvalidOperationException($"Received Ack for {ackCount} > {sentOrders.Count} frames.");
// The Acknowledgement packet is a placeholder that tells us to process the first packet in our // The Acknowledgement packet is a placeholder that tells us to process the first packet in our
// local sent buffer and the frame at which it should be applied. This is an optimization to avoid having // local sent buffer and the frame at which it should be applied. This is an optimization to avoid having
// to send the (much larger than 5 byte) packet back to us over the network. // to send the (much larger than 5 byte) packet back to us over the network.
orderManager.ReceiveOrders(clientId, (ackFrame, q.Orders)); OrderPacket packet;
Recorder?.Receive(clientId, q.Orders.Serialize(ackFrame)); if (ackCount != 1)
{
var orders = Enumerable.Range(0, ackCount)
.Select(i => sentOrders.Dequeue().Orders);
packet = OrderPacket.Combine(orders);
}
else
packet = sentOrders.Dequeue().Orders;
orderManager.ReceiveOrders(clientId, (ackFrame, packet));
Recorder?.Receive(clientId, packet.Serialize(ackFrame));
record = false; record = false;
} }
else if (OrderIO.TryParseOrderPacket(p.Data, out var orders)) else if (OrderIO.TryParseOrderPacket(p.Data, out var orders))

View File

@@ -63,6 +63,20 @@ namespace OpenRA.Network
ms.WriteArray(o.Serialize()); ms.WriteArray(o.Serialize());
return ms.ToArray(); return ms.ToArray();
} }
public static OrderPacket Combine(IEnumerable<OrderPacket> packets)
{
var orders = new List<Order>();
foreach (var packet in packets)
{
if (packet.orders == null)
throw new InvalidOperationException("OrderPacket.Combine can only be used with locally generated OrderPackets.");
orders.AddRange(packet.orders);
}
return new OrderPacket(orders.ToArray());
}
} }
public static class OrderIO public static class OrderIO
@@ -130,16 +144,17 @@ namespace OpenRA.Network
return true; return true;
} }
public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame) public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame, out byte count)
{ {
// Ack packets are only accepted from the server // Ack packets are only accepted from the server
if (packet.FromClient != 0 || packet.Data.Length != 5 || packet.Data[4] != (byte)OrderType.Ack) if (packet.FromClient != 0 || packet.Data.Length != 6 || packet.Data[4] != (byte)OrderType.Ack)
{ {
frame = 0; frame = count = 0;
return false; return false;
} }
frame = BitConverter.ToInt32(packet.Data, 0); frame = BitConverter.ToInt32(packet.Data, 0);
count = packet.Data[5];
return true; return true;
} }

View File

@@ -38,6 +38,7 @@ namespace OpenRA.Server
// - Order-specific data - see OpenRA.Game/Server/Order.cs for details // - Order-specific data - see OpenRA.Game/Server/Order.cs for details
// - 0x10: Order acknowledgement (sent from the server to a client in response to a packet with world orders) // - 0x10: Order acknowledgement (sent from the server to a client in response to a packet with world orders)
// - Int32 containing the frame number that the client should apply the orders it sent // - Int32 containing the frame number that the client should apply the orders it sent
// - byte containing the number of sent order packets to apply
// - 0x20: Ping // - 0x20: Ping
// - Int64 containing the server timestamp when the ping was generated // - Int64 containing the server timestamp when the ping was generated
// //
@@ -73,6 +74,6 @@ namespace OpenRA.Server
// The protocol for server and world orders // The protocol for server and world orders
// This applies after the handshake has completed, and is provided to support // This applies after the handshake has completed, and is provided to support
// alternative server implementations that wish to support multiple versions in parallel // alternative server implementations that wish to support multiple versions in parallel
public const int Orders = 16; public const int Orders = 17;
} }
} }

View File

@@ -627,13 +627,14 @@ namespace OpenRA.Server
return ms.GetBuffer(); return ms.GetBuffer();
} }
byte[] CreateAckFrame(int frame) byte[] CreateAckFrame(int frame, byte count)
{ {
var ms = new MemoryStream(13); var ms = new MemoryStream(14);
ms.WriteArray(BitConverter.GetBytes(5)); ms.WriteArray(BitConverter.GetBytes(6));
ms.WriteArray(BitConverter.GetBytes(0)); ms.WriteArray(BitConverter.GetBytes(0));
ms.WriteArray(BitConverter.GetBytes(frame)); ms.WriteArray(BitConverter.GetBytes(frame));
ms.WriteByte((byte)OrderType.Ack); ms.WriteByte((byte)OrderType.Ack);
ms.WriteByte(count);
return ms.GetBuffer(); return ms.GetBuffer();
} }
@@ -813,7 +814,7 @@ namespace OpenRA.Server
if (data.Length == 0 || data[0] != (byte)OrderType.SyncHash) if (data.Length == 0 || data[0] != (byte)OrderType.SyncHash)
{ {
frame += OrderLatency; frame += OrderLatency;
DispatchFrameToClient(conn, conn.PlayerIndex, CreateAckFrame(frame)); DispatchFrameToClient(conn, conn.PlayerIndex, CreateAckFrame(frame, 1));
// Track the last frame for each client so the disconnect handling can write // Track the last frame for each client so the disconnect handling can write
// an EndOfOrders marker with the correct frame number. // an EndOfOrders marker with the correct frame number.