Move order latency control to the server.
This commit is contained in:
@@ -32,6 +32,7 @@ namespace OpenRA.Network
|
||||
public interface IConnection : IDisposable
|
||||
{
|
||||
int LocalClientId { get; }
|
||||
void StartGame();
|
||||
void Send(int frame, IEnumerable<Order> orders);
|
||||
void SendImmediate(IEnumerable<Order> orders);
|
||||
void SendSync(int frame, int syncHash, ulong defeatState);
|
||||
@@ -47,6 +48,12 @@ namespace OpenRA.Network
|
||||
|
||||
int IConnection.LocalClientId => LocalClientId;
|
||||
|
||||
void IConnection.StartGame()
|
||||
{
|
||||
// Inject an empty frame to fill the gap we are making by projecting forward orders
|
||||
orders.Enqueue((0, new OrderPacket(Array.Empty<Order>())));
|
||||
}
|
||||
|
||||
void IConnection.Send(int frame, IEnumerable<Order> o)
|
||||
{
|
||||
orders.Enqueue((frame, new OrderPacket(o.ToArray())));
|
||||
@@ -67,8 +74,9 @@ namespace OpenRA.Network
|
||||
while (immediateOrders.TryDequeue(out var i))
|
||||
orderManager.ReceiveImmediateOrders(LocalClientId, i);
|
||||
|
||||
// Project orders forward to the next frame
|
||||
while (orders.TryDequeue(out var o))
|
||||
orderManager.ReceiveOrders(LocalClientId, o);
|
||||
orderManager.ReceiveOrders(LocalClientId, (o.Frame + 1, o.Orders));
|
||||
|
||||
while (sync.TryDequeue(out var s))
|
||||
orderManager.ReceiveSync(s);
|
||||
@@ -207,6 +215,8 @@ namespace OpenRA.Network
|
||||
|
||||
int IConnection.LocalClientId => clientId;
|
||||
|
||||
void IConnection.StartGame() { }
|
||||
|
||||
void IConnection.Send(int frame, IEnumerable<Order> orders)
|
||||
{
|
||||
var o = new OrderPacket(orders.ToArray());
|
||||
@@ -270,19 +280,26 @@ namespace OpenRA.Network
|
||||
Recorder?.Receive(clientId, OrderIO.SerializeSync(s));
|
||||
}
|
||||
|
||||
while (sentOrders.TryDequeue(out var o))
|
||||
{
|
||||
orderManager.ReceiveOrders(clientId, o);
|
||||
Recorder?.Receive(clientId, o.Orders.Serialize(o.Frame));
|
||||
}
|
||||
|
||||
// Orders from other players
|
||||
while (receivedPackets.TryDequeue(out var p))
|
||||
{
|
||||
var record = true;
|
||||
if (OrderIO.TryParseDisconnect(p.Data, out var disconnectClient))
|
||||
orderManager.ReceiveDisconnect(disconnectClient);
|
||||
else if (OrderIO.TryParseSync(p.Data, out var sync))
|
||||
orderManager.ReceiveSync(sync);
|
||||
else if (OrderIO.TryParseAck(p, out var ackFrame))
|
||||
{
|
||||
if (!sentOrders.TryDequeue(out var q))
|
||||
throw new InvalidOperationException("Received Ack with empty send queue");
|
||||
|
||||
// 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
|
||||
// to send the (much larger than 5 byte) packet back to us over the network.
|
||||
orderManager.ReceiveOrders(clientId, (ackFrame, q.Orders));
|
||||
Recorder?.Receive(clientId, q.Orders.Serialize(ackFrame));
|
||||
record = false;
|
||||
}
|
||||
else if (OrderIO.TryParseOrderPacket(p.Data, out var orders))
|
||||
{
|
||||
if (orders.Frame == 0)
|
||||
@@ -293,7 +310,8 @@ namespace OpenRA.Network
|
||||
else
|
||||
throw new InvalidDataException($"Received unknown packet from client {p.FromClient} with length {p.Data.Length}");
|
||||
|
||||
Recorder?.Receive(p.FromClient, p.Data);
|
||||
if (record)
|
||||
Recorder?.Receive(p.FromClient, p.Data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace OpenRA
|
||||
{
|
||||
public enum OrderType : byte
|
||||
{
|
||||
Ack = 0x10,
|
||||
SyncHash = 0x65,
|
||||
Disconnect = 0xBF,
|
||||
Handshake = 0xFE,
|
||||
|
||||
@@ -106,6 +106,19 @@ namespace OpenRA.Network
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseAck((int FromClient, byte[] Data) packet, out int frame)
|
||||
{
|
||||
// Ack packets are only accepted from the server
|
||||
if (packet.FromClient != 0 || packet.Data.Length != 5 || packet.Data[4] != (byte)OrderType.Ack)
|
||||
{
|
||||
frame = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = BitConverter.ToInt32(packet.Data, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseOrderPacket(byte[] packet, out (int Frame, OrderPacket Orders) data)
|
||||
{
|
||||
// Not a valid packet
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace OpenRA.Network
|
||||
|
||||
public int NetFrameNumber { get; private set; }
|
||||
public int LocalFrameNumber;
|
||||
public int FramesAhead = 0;
|
||||
|
||||
public TickTime LastTickTime;
|
||||
|
||||
@@ -52,6 +51,7 @@ namespace OpenRA.Network
|
||||
|
||||
bool disposed;
|
||||
bool generateSyncReport = false;
|
||||
int sentOrdersFrame = 0;
|
||||
|
||||
public struct ClientOrder
|
||||
{
|
||||
@@ -87,9 +87,7 @@ namespace OpenRA.Network
|
||||
LocalFrameNumber = 0;
|
||||
LastTickTime.Value = Game.RunTime;
|
||||
|
||||
if (GameSaveLastFrame < 0)
|
||||
for (var i = NetFrameNumber; i <= FramesAhead; i++)
|
||||
Connection.Send(i, Array.Empty<Order>());
|
||||
Connection.StartGame();
|
||||
}
|
||||
|
||||
public OrderManager(IConnection conn)
|
||||
@@ -123,7 +121,7 @@ namespace OpenRA.Network
|
||||
|
||||
void SendImmediateOrders()
|
||||
{
|
||||
if (localImmediateOrders.Count != 0 && GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
||||
if (localImmediateOrders.Count != 0 && GameSaveLastFrame < NetFrameNumber)
|
||||
Connection.SendImmediate(localImmediateOrders);
|
||||
localImmediateOrders.Clear();
|
||||
}
|
||||
@@ -206,13 +204,11 @@ namespace OpenRA.Network
|
||||
|
||||
void SendOrders()
|
||||
{
|
||||
if (!GameStarted)
|
||||
return;
|
||||
|
||||
if (GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
||||
if (GameStarted && GameSaveLastFrame < NetFrameNumber && sentOrdersFrame < NetFrameNumber)
|
||||
{
|
||||
Connection.Send(NetFrameNumber + FramesAhead, localOrders);
|
||||
Connection.Send(NetFrameNumber, localOrders);
|
||||
localOrders.Clear();
|
||||
sentOrdersFrame = NetFrameNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,10 +221,9 @@ namespace OpenRA.Network
|
||||
// The IsReadyForNextFrame check above guarantees that all clients have sent a packet
|
||||
var (frameNumber, orders) = frameOrders.Dequeue();
|
||||
|
||||
// Orders are synchronised by sending an initial FramesAhead set of empty packets
|
||||
// and then making sure that we enqueue and process exactly one packet for each player each tick.
|
||||
// This may change in the future, so sanity check that the orders are for the frame we expect
|
||||
// and crash early instead of risking desyncs.
|
||||
// We expect every frame to have a queued order packet, even if it contains no orders, as this
|
||||
// controls the pacing of the game simulation.
|
||||
// Sanity check that we are processing the frame that we expect, so we can crash early instead of desyncing.
|
||||
if (frameNumber != NetFrameNumber)
|
||||
throw new InvalidDataException($"Attempted to process orders from client {clientId} for frame {frameNumber} on frame {NetFrameNumber}");
|
||||
|
||||
@@ -239,7 +234,7 @@ namespace OpenRA.Network
|
||||
}
|
||||
}
|
||||
|
||||
if (NetFrameNumber + FramesAhead >= GameSaveLastSyncFrame)
|
||||
if (NetFrameNumber >= GameSaveLastSyncFrame)
|
||||
{
|
||||
var defeatState = 0UL;
|
||||
for (var i = 0; i < World.Players.Length; i++)
|
||||
|
||||
@@ -124,6 +124,8 @@ namespace OpenRA.Network
|
||||
ordersFrame = orderLatency;
|
||||
}
|
||||
|
||||
void IConnection.StartGame() { }
|
||||
|
||||
// Do nothing: ignore locally generated orders
|
||||
void IConnection.Send(int frame, IEnumerable<Order> orders) { }
|
||||
void IConnection.SendImmediate(IEnumerable<Order> orders) { }
|
||||
|
||||
Reference in New Issue
Block a user