Remove packet byte wrangling from OrderManager.
This commit is contained in:
@@ -31,10 +31,10 @@ namespace OpenRA.Network
|
|||||||
public interface IConnection : IDisposable
|
public interface IConnection : IDisposable
|
||||||
{
|
{
|
||||||
int LocalClientId { get; }
|
int LocalClientId { get; }
|
||||||
void Send(int frame, List<byte[]> orders);
|
void Send(int frame, IEnumerable<Order> orders);
|
||||||
void SendImmediate(IEnumerable<byte[]> orders);
|
void SendImmediate(IEnumerable<Order> orders);
|
||||||
void SendSync(int frame, byte[] syncData);
|
void SendSync(int frame, int syncHash, ulong defeatState);
|
||||||
void Receive(Action<int, byte[]> packetFn);
|
void Receive(OrderManager orderManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EchoConnection : IConnection
|
public class EchoConnection : IConnection
|
||||||
@@ -50,32 +50,29 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public virtual int LocalClientId => 1;
|
public virtual int LocalClientId => 1;
|
||||||
|
|
||||||
public virtual void Send(int frame, List<byte[]> orders)
|
public virtual void Send(int frame, IEnumerable<Order> orders)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||||
foreach (var o in orders)
|
foreach (var o in orders)
|
||||||
ms.WriteArray(o);
|
ms.WriteArray(o.Serialize());
|
||||||
Send(ms.ToArray());
|
Send(ms.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SendImmediate(IEnumerable<byte[]> orders)
|
public virtual void SendImmediate(IEnumerable<Order> orders)
|
||||||
{
|
{
|
||||||
foreach (var o in orders)
|
foreach (var o in orders)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
ms.WriteArray(BitConverter.GetBytes(0));
|
ms.WriteArray(BitConverter.GetBytes(0));
|
||||||
ms.WriteArray(o);
|
ms.WriteArray(o.Serialize());
|
||||||
Send(ms.ToArray());
|
Send(ms.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SendSync(int frame, byte[] syncData)
|
public virtual void SendSync(int frame, int syncHash, ulong defeatState)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + syncData.Length);
|
Send(OrderIO.SerializeSync(frame, syncHash, defeatState));
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
|
||||||
ms.WriteArray(syncData);
|
|
||||||
Send(ms.GetBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Send(byte[] packet)
|
protected virtual void Send(byte[] packet)
|
||||||
@@ -90,11 +87,22 @@ namespace OpenRA.Network
|
|||||||
receivedPackets.Enqueue(packet);
|
receivedPackets.Enqueue(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Receive(Action<int, byte[]> packetFn)
|
public virtual void Receive(OrderManager orderManager)
|
||||||
{
|
{
|
||||||
while (receivedPackets.TryDequeue(out var p))
|
while (receivedPackets.TryDequeue(out var p))
|
||||||
{
|
{
|
||||||
packetFn(p.FromClient, p.Data);
|
if (OrderIO.TryParseDisconnect(p.Data, out var disconnectClient))
|
||||||
|
orderManager.ReceiveDisconnect(disconnectClient);
|
||||||
|
else if (OrderIO.TryParseSync(p.Data, out var syncFrame, out var syncHash, out var defeatState))
|
||||||
|
orderManager.ReceiveSync(syncFrame, syncHash, defeatState);
|
||||||
|
else if (OrderIO.TryParseOrderPacket(p.Data, out var ordersFrame, out var orders))
|
||||||
|
{
|
||||||
|
if (ordersFrame == 0)
|
||||||
|
orderManager.ReceiveImmediateOrders(p.FromClient, orders);
|
||||||
|
else
|
||||||
|
orderManager.ReceiveOrders(p.FromClient, ordersFrame, orders);
|
||||||
|
}
|
||||||
|
|
||||||
Recorder?.Receive(p.FromClient, p.Data);
|
Recorder?.Receive(p.FromClient, p.Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,12 +255,9 @@ namespace OpenRA.Network
|
|||||||
public override int LocalClientId => clientId;
|
public override int LocalClientId => clientId;
|
||||||
public ConnectionState ConnectionState => connectionState;
|
public ConnectionState ConnectionState => connectionState;
|
||||||
|
|
||||||
public override void SendSync(int frame, byte[] syncData)
|
public override void SendSync(int frame, int syncHash, ulong defeatState)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + syncData.Length);
|
queuedSyncPackets.Add(OrderIO.SerializeSync(frame, syncHash, defeatState));
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
|
||||||
ms.WriteArray(syncData);
|
|
||||||
queuedSyncPackets.Add(ms.GetBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Send(byte[] packet)
|
protected override void Send(byte[] packet)
|
||||||
|
|||||||
@@ -372,11 +372,16 @@ namespace OpenRA
|
|||||||
case TargetType.Terrain:
|
case TargetType.Terrain:
|
||||||
if (fields.HasField(OrderFields.TargetIsCell))
|
if (fields.HasField(OrderFields.TargetIsCell))
|
||||||
{
|
{
|
||||||
w.Write(Target.SerializableCell.Value);
|
w.Write(Target.SerializableCell.Value.Bits);
|
||||||
w.Write((byte)Target.SerializableSubCell);
|
w.Write((byte)Target.SerializableSubCell);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
w.Write(Target.SerializablePos);
|
{
|
||||||
|
w.Write(Target.SerializablePos.X);
|
||||||
|
w.Write(Target.SerializablePos.Y);
|
||||||
|
w.Write(Target.SerializablePos.Z);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,7 +397,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fields.HasField(OrderFields.ExtraLocation))
|
if (fields.HasField(OrderFields.ExtraLocation))
|
||||||
w.Write(ExtraLocation);
|
w.Write(ExtraLocation.Bits);
|
||||||
|
|
||||||
if (fields.HasField(OrderFields.ExtraData))
|
if (fields.HasField(OrderFields.ExtraData))
|
||||||
w.Write(ExtraData);
|
w.Write(ExtraData);
|
||||||
|
|||||||
@@ -9,70 +9,126 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
{
|
{
|
||||||
public static class OrderIO
|
public class OrderPacket
|
||||||
{
|
{
|
||||||
static readonly List<Order> EmptyOrderList = new List<Order>(0);
|
readonly Order[] orders;
|
||||||
|
readonly MemoryStream data;
|
||||||
public static List<Order> ToOrderList(this byte[] bytes, World world)
|
public OrderPacket(Order[] orders)
|
||||||
{
|
{
|
||||||
// PERF: Skip empty order frames, often per client each frame
|
this.orders = orders;
|
||||||
if (bytes.Length == 4)
|
data = null;
|
||||||
return EmptyOrderList;
|
}
|
||||||
|
|
||||||
var ms = new MemoryStream(bytes, 4, bytes.Length - 4);
|
public OrderPacket(MemoryStream data)
|
||||||
var reader = new BinaryReader(ms);
|
{
|
||||||
var ret = new List<Order>();
|
orders = null;
|
||||||
while (ms.Position < ms.Length)
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Order> GetOrders(World world)
|
||||||
|
{
|
||||||
|
return orders ?? ParseData(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Order> ParseData(World world)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
// Order deserialization depends on the current world state,
|
||||||
|
// so must be deferred until we are ready to consume them.
|
||||||
|
var reader = new BinaryReader(data);
|
||||||
|
while (data.Position < data.Length)
|
||||||
{
|
{
|
||||||
var o = Order.Deserialize(world, reader);
|
var o = Order.Deserialize(world, reader);
|
||||||
if (o != null)
|
if (o != null)
|
||||||
ret.Add(o);
|
yield return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] SerializeSync(int sync, ulong defeatState)
|
public byte[] Serialize(int frame)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(Order.SyncHashOrderLength);
|
if (data != null)
|
||||||
using (var writer = new BinaryWriter(ms))
|
return data.ToArray();
|
||||||
{
|
|
||||||
writer.Write((byte)OrderType.SyncHash);
|
|
||||||
writer.Write(sync);
|
|
||||||
writer.Write(defeatState);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||||
|
foreach (var o in orders)
|
||||||
|
ms.WriteArray(o.Serialize());
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OrderIO
|
||||||
|
{
|
||||||
|
static readonly OrderPacket NoOrders = new OrderPacket(Array.Empty<Order>());
|
||||||
|
|
||||||
|
public static byte[] SerializeSync(int frame, int syncHash, ulong defeatState)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
||||||
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||||
|
ms.WriteByte((byte)OrderType.SyncHash);
|
||||||
|
ms.WriteArray(BitConverter.GetBytes(syncHash));
|
||||||
|
ms.WriteArray(BitConverter.GetBytes(defeatState));
|
||||||
return ms.GetBuffer();
|
return ms.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int2 ReadInt2(this BinaryReader r)
|
public static bool TryParseDisconnect(byte[] packet, out int clientId)
|
||||||
{
|
{
|
||||||
var x = r.ReadInt32();
|
if (packet.Length == Order.DisconnectOrderLength + 4 && packet[4] == (byte)OrderType.Disconnect)
|
||||||
var y = r.ReadInt32();
|
{
|
||||||
return new int2(x, y);
|
clientId = BitConverter.ToInt32(packet, 5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId = 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(this BinaryWriter w, int2 p)
|
public static bool TryParseSync(byte[] packet, out int frame, out int syncHash, out ulong defeatState)
|
||||||
{
|
{
|
||||||
w.Write(p.X);
|
if (packet.Length != 4 + Order.SyncHashOrderLength || packet[4] != (byte)OrderType.SyncHash)
|
||||||
w.Write(p.Y);
|
{
|
||||||
|
frame = syncHash = 0;
|
||||||
|
defeatState = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = BitConverter.ToInt32(packet, 0);
|
||||||
|
syncHash = BitConverter.ToInt32(packet, 5);
|
||||||
|
defeatState = BitConverter.ToUInt64(packet, 9);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(this BinaryWriter w, CPos cell)
|
public static bool TryParseOrderPacket(byte[] packet, out int frame, out OrderPacket orders)
|
||||||
{
|
{
|
||||||
w.Write(cell.Bits);
|
// Not a valid packet
|
||||||
}
|
if (packet.Length < 4)
|
||||||
|
{
|
||||||
|
frame = 0;
|
||||||
|
orders = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void Write(this BinaryWriter w, WPos pos)
|
// Wrong packet type
|
||||||
{
|
if (packet.Length >= 5 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
|
||||||
w.Write(pos.X);
|
{
|
||||||
w.Write(pos.Y);
|
frame = 0;
|
||||||
w.Write(pos.Z);
|
orders = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = BitConverter.ToInt32(packet, 0);
|
||||||
|
|
||||||
|
// PERF: Skip empty order frames, often per client each frame
|
||||||
|
orders = packet.Length > 4 ? new OrderPacket(new MemoryStream(packet, 4, packet.Length - 4)) : NoOrders;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Widgets;
|
using OpenRA.Widgets;
|
||||||
|
|
||||||
@@ -22,8 +21,8 @@ namespace OpenRA.Network
|
|||||||
public sealed class OrderManager : IDisposable
|
public sealed class OrderManager : IDisposable
|
||||||
{
|
{
|
||||||
readonly SyncReport syncReport;
|
readonly SyncReport syncReport;
|
||||||
|
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new Dictionary<int, Queue<(int, OrderPacket)>>();
|
||||||
readonly Dictionary<int, Queue<byte[]>> pendingPackets = new Dictionary<int, Queue<byte[]>>();
|
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new Dictionary<int, (int, ulong)>();
|
||||||
|
|
||||||
public Session LobbyInfo = new Session();
|
public Session LobbyInfo = new Session();
|
||||||
public Session.Client LocalClient => LobbyInfo.ClientWithIndex(Connection.LocalClientId);
|
public Session.Client LocalClient => LobbyInfo.ClientWithIndex(Connection.LocalClientId);
|
||||||
@@ -78,7 +77,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
foreach (var client in LobbyInfo.Clients)
|
foreach (var client in LobbyInfo.Clients)
|
||||||
if (!client.IsBot)
|
if (!client.IsBot)
|
||||||
pendingPackets.Add(client.Index, new Queue<byte[]>());
|
pendingOrders.Add(client.Index, new Queue<(int, OrderPacket)>());
|
||||||
|
|
||||||
// Generating sync reports is expensive, so only do it if we have
|
// Generating sync reports is expensive, so only do it if we have
|
||||||
// other players to compare against if a desync did occur
|
// other players to compare against if a desync did occur
|
||||||
@@ -90,7 +89,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
if (GameSaveLastFrame < 0)
|
if (GameSaveLastFrame < 0)
|
||||||
for (var i = NetFrameNumber; i <= FramesAhead; i++)
|
for (var i = NetFrameNumber; i <= FramesAhead; i++)
|
||||||
Connection.Send(i, new List<byte[]>());
|
Connection.Send(i, Array.Empty<Order>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderManager(IConnection conn)
|
public OrderManager(IConnection conn)
|
||||||
@@ -125,74 +124,68 @@ namespace OpenRA.Network
|
|||||||
void SendImmediateOrders()
|
void SendImmediateOrders()
|
||||||
{
|
{
|
||||||
if (localImmediateOrders.Count != 0 && GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
if (localImmediateOrders.Count != 0 && GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
||||||
Connection.SendImmediate(localImmediateOrders.Select(o => o.Serialize()));
|
Connection.SendImmediate(localImmediateOrders);
|
||||||
localImmediateOrders.Clear();
|
localImmediateOrders.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReceiveDisconnect(int clientIndex)
|
||||||
|
{
|
||||||
|
// HACK: The shellmap relies on ticking a disposed OM
|
||||||
|
if (disposed && World.Type != WorldType.Shellmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pendingOrders.Remove(clientIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveSync(int frame, int syncHash, ulong defeatState)
|
||||||
|
{
|
||||||
|
// HACK: The shellmap relies on ticking a disposed OM
|
||||||
|
if (disposed && World.Type != WorldType.Shellmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (syncForFrame.TryGetValue(frame, out var s))
|
||||||
|
{
|
||||||
|
if (s.SyncHash != syncHash || s.DefeatState != defeatState)
|
||||||
|
OutOfSync(frame);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
syncForFrame.Add(frame, (syncHash, defeatState));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveImmediateOrders(int clientId, OrderPacket orders)
|
||||||
|
{
|
||||||
|
// HACK: The shellmap relies on ticking a disposed OM
|
||||||
|
if (disposed && World.Type != WorldType.Shellmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var o in orders.GetOrders(World))
|
||||||
|
{
|
||||||
|
UnitOrders.ProcessOrder(this, World, clientId, o);
|
||||||
|
|
||||||
|
// A mod switch or other event has pulled the ground from beneath us
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveOrders(int clientId, int frame, OrderPacket orders)
|
||||||
|
{
|
||||||
|
// HACK: The shellmap relies on ticking a disposed OM
|
||||||
|
if (disposed && World.Type != WorldType.Shellmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pendingOrders.TryGetValue(clientId, out var queue))
|
||||||
|
queue.Enqueue((frame, orders));
|
||||||
|
else
|
||||||
|
Log.Write("debug", $"Received packet from disconnected client '{clientId}'");
|
||||||
|
}
|
||||||
|
|
||||||
void ReceiveAllOrdersAndCheckSync()
|
void ReceiveAllOrdersAndCheckSync()
|
||||||
{
|
{
|
||||||
Connection.Receive(
|
Connection.Receive(this);
|
||||||
(clientId, packet) =>
|
|
||||||
{
|
|
||||||
// HACK: The shellmap relies on ticking a disposed OM
|
|
||||||
if (disposed && World.Type != WorldType.Shellmap)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var frame = BitConverter.ToInt32(packet, 0);
|
|
||||||
if (packet.Length == Order.DisconnectOrderLength + 4 && packet[4] == (byte)OrderType.Disconnect)
|
|
||||||
{
|
|
||||||
pendingPackets.Remove(BitConverter.ToInt32(packet, 5));
|
|
||||||
}
|
|
||||||
else if (packet.Length > 4 && packet[4] == (byte)OrderType.SyncHash)
|
|
||||||
{
|
|
||||||
if (packet.Length != 4 + Order.SyncHashOrderLength)
|
|
||||||
{
|
|
||||||
Log.Write("debug", $"Dropped sync order with length {packet.Length}. Expected length {4 + Order.SyncHashOrderLength}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckSync(packet);
|
|
||||||
}
|
|
||||||
else if (frame == 0)
|
|
||||||
{
|
|
||||||
foreach (var o in packet.ToOrderList(World))
|
|
||||||
{
|
|
||||||
UnitOrders.ProcessOrder(this, World, clientId, o);
|
|
||||||
|
|
||||||
// A mod switch or other event has pulled the ground from beneath us
|
|
||||||
if (disposed)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (pendingPackets.TryGetValue(clientId, out var queue))
|
|
||||||
queue.Enqueue(packet);
|
|
||||||
else
|
|
||||||
Log.Write("debug", $"Received packet from disconnected client '{clientId}'");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<int, byte[]> syncForFrame = new Dictionary<int, byte[]>();
|
bool IsReadyForNextFrame => GameStarted && pendingOrders.All(p => p.Value.Count > 0);
|
||||||
|
|
||||||
void CheckSync(byte[] packet)
|
|
||||||
{
|
|
||||||
var frame = BitConverter.ToInt32(packet, 0);
|
|
||||||
if (syncForFrame.TryGetValue(frame, out var existingSync))
|
|
||||||
{
|
|
||||||
if (packet.Length != existingSync.Length)
|
|
||||||
OutOfSync(frame);
|
|
||||||
else
|
|
||||||
for (var i = 0; i < packet.Length; i++)
|
|
||||||
if (packet[i] != existingSync[i])
|
|
||||||
OutOfSync(frame);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
syncForFrame.Add(frame, packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsReadyForNextFrame => GameStarted && pendingPackets.All(p => p.Value.Count > 0);
|
|
||||||
|
|
||||||
int SuggestedTimestep
|
int SuggestedTimestep
|
||||||
{
|
{
|
||||||
@@ -218,7 +211,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
if (GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
if (GameSaveLastFrame < NetFrameNumber + FramesAhead)
|
||||||
{
|
{
|
||||||
Connection.Send(NetFrameNumber + FramesAhead, localOrders.Select(o => o.Serialize()).ToList());
|
Connection.Send(NetFrameNumber + FramesAhead, localOrders);
|
||||||
localOrders.Clear();
|
localOrders.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,20 +220,19 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var clientOrders = new List<ClientOrder>();
|
var clientOrders = new List<ClientOrder>();
|
||||||
|
|
||||||
foreach (var (clientId, clientPackets) in pendingPackets)
|
foreach (var (clientId, frameOrders) in pendingOrders)
|
||||||
{
|
{
|
||||||
// The IsReadyForNextFrame check above guarantees that all clients have sent a packet
|
// The IsReadyForNextFrame check above guarantees that all clients have sent a packet
|
||||||
var frameData = clientPackets.Dequeue();
|
var (frameNumber, orders) = frameOrders.Dequeue();
|
||||||
|
|
||||||
// Orders are synchronised by sending an initial FramesAhead set of empty packets
|
// 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.
|
// 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
|
// 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.
|
// and crash early instead of risking desyncs.
|
||||||
var frameNumber = BitConverter.ToInt32(frameData, 0);
|
|
||||||
if (frameNumber != NetFrameNumber)
|
if (frameNumber != NetFrameNumber)
|
||||||
throw new InvalidDataException($"Attempted to process orders from client {clientId} for frame {frameNumber} on frame {NetFrameNumber}");
|
throw new InvalidDataException($"Attempted to process orders from client {clientId} for frame {frameNumber} on frame {NetFrameNumber}");
|
||||||
|
|
||||||
foreach (var order in frameData.ToOrderList(World))
|
foreach (var order in orders.GetOrders(World))
|
||||||
{
|
{
|
||||||
UnitOrders.ProcessOrder(this, World, clientId, order);
|
UnitOrders.ProcessOrder(this, World, clientId, order);
|
||||||
clientOrders.Add(new ClientOrder { Client = clientId, Order = order });
|
clientOrders.Add(new ClientOrder { Client = clientId, Order = order });
|
||||||
@@ -254,10 +246,10 @@ namespace OpenRA.Network
|
|||||||
if (World.Players[i].WinState == WinState.Lost)
|
if (World.Players[i].WinState == WinState.Lost)
|
||||||
defeatState |= 1UL << i;
|
defeatState |= 1UL << i;
|
||||||
|
|
||||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash(), defeatState));
|
Connection.SendSync(NetFrameNumber, World.SyncHash(), defeatState);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0, 0));
|
Connection.SendSync(NetFrameNumber, 0, 0);
|
||||||
|
|
||||||
if (generateSyncReport)
|
if (generateSyncReport)
|
||||||
using (new PerfSample("sync_report"))
|
using (new PerfSample("sync_report"))
|
||||||
@@ -287,7 +279,7 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
// Check whether or not we will be ready for a tick next frame
|
// Check whether or not we will be ready for a tick next frame
|
||||||
// We don't need to include ourselves in the equation because we can always generate orders this frame
|
// We don't need to include ourselves in the equation because we can always generate orders this frame
|
||||||
shouldTick = pendingPackets.All(p => p.Key == Connection.LocalClientId || p.Value.Count > 0);
|
shouldTick = pendingOrders.All(p => p.Key == Connection.LocalClientId || p.Value.Count > 0);
|
||||||
|
|
||||||
// Send orders only if we are currently ready, this prevents us sending orders too soon if we are
|
// Send orders only if we are currently ready, this prevents us sending orders too soon if we are
|
||||||
// stalling
|
// stalling
|
||||||
|
|||||||
@@ -25,20 +25,14 @@ namespace OpenRA.Network
|
|||||||
public (int ClientId, byte[] Packet)[] Packets;
|
public (int ClientId, byte[] Packet)[] Packets;
|
||||||
}
|
}
|
||||||
|
|
||||||
Queue<Chunk> chunks = new Queue<Chunk>();
|
readonly Queue<Chunk> chunks = new Queue<Chunk>();
|
||||||
Queue<byte[]> sync = new Queue<byte[]>();
|
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = new Queue<(int, int, ulong)>();
|
||||||
|
readonly Dictionary<int, int> lastClientsFrame = new Dictionary<int, int>();
|
||||||
readonly int orderLatency;
|
readonly int orderLatency;
|
||||||
int ordersFrame;
|
int ordersFrame;
|
||||||
|
|
||||||
Dictionary<int, int> lastClientsFrame = new Dictionary<int, int>();
|
|
||||||
|
|
||||||
public int LocalClientId => -1;
|
public int LocalClientId => -1;
|
||||||
|
|
||||||
public IPEndPoint EndPoint => throw new NotSupportedException("A replay connection doesn't have an endpoint");
|
|
||||||
|
|
||||||
public string ErrorMessage => null;
|
|
||||||
|
|
||||||
public readonly int TickCount;
|
public readonly int TickCount;
|
||||||
public readonly int FinalGameTick;
|
public readonly int FinalGameTick;
|
||||||
public readonly bool IsValid;
|
public readonly bool IsValid;
|
||||||
@@ -76,13 +70,15 @@ namespace OpenRA.Network
|
|||||||
if (frame == 0)
|
if (frame == 0)
|
||||||
{
|
{
|
||||||
// Parse replay metadata from orders stream
|
// Parse replay metadata from orders stream
|
||||||
var orders = packet.ToOrderList(null);
|
if (OrderIO.TryParseOrderPacket(packet, out _, out var orders))
|
||||||
foreach (var o in orders)
|
|
||||||
{
|
{
|
||||||
if (o.OrderString == "StartGame")
|
foreach (var o in orders.GetOrders(null))
|
||||||
IsValid = true;
|
{
|
||||||
else if (o.OrderString == "SyncInfo" && !IsValid)
|
if (o.OrderString == "StartGame")
|
||||||
LobbyInfo = Session.Deserialize(o.TargetString);
|
IsValid = true;
|
||||||
|
else if (o.OrderString == "SyncInfo" && !IsValid)
|
||||||
|
LobbyInfo = Session.Deserialize(o.TargetString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -131,28 +127,42 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do nothing: ignore locally generated orders
|
// Do nothing: ignore locally generated orders
|
||||||
public void Send(int frame, List<byte[]> orders) { }
|
public void Send(int frame, IEnumerable<Order> orders) { }
|
||||||
public void SendImmediate(IEnumerable<byte[]> orders) { }
|
public void SendImmediate(IEnumerable<Order> orders) { }
|
||||||
|
|
||||||
public void SendSync(int frame, byte[] syncData)
|
public void SendSync(int frame, int syncHash, ulong defeatState)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + syncData.Length);
|
sync.Enqueue((frame, syncHash, defeatState));
|
||||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
|
||||||
ms.WriteArray(syncData);
|
|
||||||
sync.Enqueue(ms.GetBuffer());
|
|
||||||
|
|
||||||
// Store the current frame so Receive() can return the next chunk of orders.
|
// Store the current frame so Receive() can return the next chunk of orders.
|
||||||
ordersFrame = frame + orderLatency;
|
ordersFrame = frame + orderLatency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(Action<int, byte[]> packetFn)
|
public void Receive(OrderManager orderManager)
|
||||||
{
|
{
|
||||||
while (sync.Count != 0)
|
while (sync.Count != 0)
|
||||||
packetFn(LocalClientId, sync.Dequeue());
|
{
|
||||||
|
var (syncFrame, syncHash, defeatState) = sync.Dequeue();
|
||||||
|
orderManager.ReceiveSync(syncFrame, syncHash, defeatState);
|
||||||
|
}
|
||||||
|
|
||||||
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
|
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
|
||||||
|
{
|
||||||
foreach (var o in chunks.Dequeue().Packets)
|
foreach (var o in chunks.Dequeue().Packets)
|
||||||
packetFn(o.ClientId, o.Packet);
|
{
|
||||||
|
if (OrderIO.TryParseDisconnect(o.Packet, out var disconnectClient))
|
||||||
|
orderManager.ReceiveDisconnect(disconnectClient);
|
||||||
|
else if (OrderIO.TryParseSync(o.Packet, out var syncFrame, out var syncHash, out var defeatState))
|
||||||
|
orderManager.ReceiveSync(syncFrame, syncHash, defeatState);
|
||||||
|
else if (OrderIO.TryParseOrderPacket(o.Packet, out var frame, out var orders))
|
||||||
|
{
|
||||||
|
if (frame == 0)
|
||||||
|
orderManager.ReceiveImmediateOrders(o.ClientId, orders);
|
||||||
|
else
|
||||||
|
orderManager.ReceiveOrders(o.ClientId, frame, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
static bool IsGameStart(byte[] data)
|
static bool IsGameStart(byte[] data)
|
||||||
{
|
{
|
||||||
if (data.Length > 4 && (data[4] == (byte)OrderType.Disconnect || data[4] == (byte)OrderType.SyncHash))
|
if (!OrderIO.TryParseOrderPacket(data, out var frame, out var orders))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var frame = BitConverter.ToInt32(data, 0);
|
return frame == 0 && orders.GetOrders(null).Any(o => o.OrderString == "StartGame");
|
||||||
return frame == 0 && data.ToOrderList(null).Any(o => o.OrderString == "StartGame");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplayRecorder(Func<string> chooseFilename)
|
public ReplayRecorder(Func<string> chooseFilename)
|
||||||
|
|||||||
Reference in New Issue
Block a user