Files
OpenRA/OpenRA.Game/Network/OrderManager.cs
Paul Chote 8533aa8d26 Disable sync reports when we know we won't need them.
Generating the sync report takes ~twice as long as
a normal tick, and occurs once every 3 ticks.

These reports record of all of the synced state
(separate to the sync hash, which is still calculated)
in order to generate the syncreport.log of the game
desyncs. This perf overhead is completely unnecessary
when we know that we won't have other syncreports to
compare against (singleplayer, replays).

Disabling report generation in these cases gives
us an easy 40% average tick-time win.
2018-09-10 19:44:06 +02:00

217 lines
5.6 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Primitives;
namespace OpenRA.Network
{
public sealed class OrderManager : IDisposable
{
static readonly IEnumerable<Session.Client> NoClients = new Session.Client[] { };
readonly SyncReport syncReport;
readonly FrameData frameData = new FrameData();
public Session LobbyInfo = new Session();
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
public World World;
public readonly string Host;
public readonly int Port;
public readonly string Password = "";
public string ServerError = "Server is not responding";
public bool AuthenticationFailed = false;
public ExternalMod ServerExternalMod = null;
public int NetFrameNumber { get; private set; }
public int LocalFrameNumber;
public int FramesAhead = 0;
public long LastTickTime = Game.RunTime;
public bool GameStarted { get { return NetFrameNumber != 0; } }
public IConnection Connection { get; private set; }
List<Order> localOrders = new List<Order>();
List<ChatLine> chatCache = new List<ChatLine>();
public readonly ReadOnlyList<ChatLine> ChatCache;
bool disposed;
bool generateSyncReport = false;
void OutOfSync(int frame)
{
syncReport.DumpSyncReport(frame, frameData.OrdersForFrame(World, frame));
throw new InvalidOperationException("Out of sync in frame {0}.\n Compare syncreport.log with other players.".F(frame));
}
public void StartGame()
{
if (GameStarted)
return;
// Generating sync reports is expensive, so only do it if we have
// other players to compare against if a desync did occur
generateSyncReport = !(Connection is ReplayConnection) && LobbyInfo.NonBotClients.Count() > 1;
NetFrameNumber = 1;
for (var i = NetFrameNumber; i <= FramesAhead; i++)
Connection.Send(i, new List<byte[]>());
}
public OrderManager(string host, int port, string password, IConnection conn)
{
Host = host;
Port = port;
Password = password;
Connection = conn;
syncReport = new SyncReport(this);
ChatCache = new ReadOnlyList<ChatLine>(chatCache);
AddChatLine += CacheChatLine;
}
public void IssueOrders(Order[] orders)
{
foreach (var order in orders)
IssueOrder(order);
}
public void IssueOrder(Order order)
{
localOrders.Add(order);
}
public Action<Color, string, string> AddChatLine = (c, n, s) => { };
void CacheChatLine(Color color, string name, string text)
{
chatCache.Add(new ChatLine(color, name, text));
}
public void TickImmediate()
{
var immediateOrders = localOrders.Where(o => o.IsImmediate).ToList();
if (immediateOrders.Count != 0)
Connection.SendImmediate(immediateOrders.Select(o => o.Serialize()).ToList());
localOrders.RemoveAll(o => o.IsImmediate);
var immediatePackets = new List<Pair<int, byte[]>>();
Connection.Receive(
(clientId, packet) =>
{
var frame = BitConverter.ToInt32(packet, 0);
if (packet.Length == 5 && packet[4] == 0xBF)
frameData.ClientQuit(clientId, frame);
else if (packet.Length >= 5 && packet[4] == 0x65)
CheckSync(packet);
else if (frame == 0)
immediatePackets.Add(Pair.New(clientId, packet));
else
frameData.AddFrameOrders(clientId, frame, packet);
});
foreach (var p in immediatePackets)
{
foreach (var o in p.Second.ToOrderList(World))
{
UnitOrders.ProcessOrder(this, World, p.First, o);
// A mod switch or other event has pulled the ground from beneath us
if (disposed)
return;
}
}
}
Dictionary<int, byte[]> syncForFrame = new Dictionary<int, byte[]>();
void CheckSync(byte[] packet)
{
var frame = BitConverter.ToInt32(packet, 0);
byte[] existingSync;
if (syncForFrame.TryGetValue(frame, out 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);
}
public bool IsReadyForNextFrame
{
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame(NetFrameNumber); }
}
public IEnumerable<Session.Client> GetClientsNotReadyForNextFrame
{
get
{
return NetFrameNumber >= 1
? frameData.ClientsNotReadyForFrame(NetFrameNumber)
.Select(a => LobbyInfo.ClientWithIndex(a))
: NoClients;
}
}
public void Tick()
{
if (!IsReadyForNextFrame)
throw new InvalidOperationException();
Connection.Send(NetFrameNumber + FramesAhead, localOrders.Select(o => o.Serialize()).ToList());
localOrders.Clear();
foreach (var order in frameData.OrdersForFrame(World, NetFrameNumber))
UnitOrders.ProcessOrder(this, World, order.Client, order.Order);
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash()));
if (generateSyncReport)
syncReport.UpdateSyncReport();
++NetFrameNumber;
}
public void Dispose()
{
disposed = true;
if (Connection != null)
Connection.Dispose();
}
}
public class ChatLine
{
public readonly Color Color;
public readonly string Name;
public readonly string Text;
public ChatLine(Color c, string n, string t)
{
Color = c;
Name = n;
Text = t;
}
}
}