From 6385e7ebaa5d7556ef59090189097ff0363c85a0 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 3 Mar 2014 12:41:36 +1300 Subject: [PATCH 1/3] Stylecop ReplayConnection. --- OpenRA.Game/Network/ReplayConnection.cs | 46 ++++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/OpenRA.Game/Network/ReplayConnection.cs b/OpenRA.Game/Network/ReplayConnection.cs index a705a2196b..7ee257a06f 100755 --- a/OpenRA.Game/Network/ReplayConnection.cs +++ b/OpenRA.Game/Network/ReplayConnection.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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. For more information, @@ -19,41 +19,45 @@ namespace OpenRA.Network FileStream replayStream; List sync = new List(); - public ReplayConnection( string replayFilename ) { replayStream = File.OpenRead( replayFilename ); } - public int LocalClientId { get { return 0; } } public ConnectionState ConnectionState { get { return ConnectionState.Connected; } } - // do nothing; ignore locally generated orders - public void Send( int frame, List orders ) { } - public void SendImmediate( List orders ) { } - - public void SendSync( int frame, byte[] syncData ) + public ReplayConnection(string replayFilename) { - var ms = new MemoryStream(); - ms.Write( BitConverter.GetBytes( frame ) ); - ms.Write( syncData ); - sync.Add( ms.ToArray() ); + replayStream = File.OpenRead(replayFilename); } - public void Receive( Action packetFn ) + // do nothing; ignore locally generated orders + public void Send(int frame, List orders) { } + public void SendImmediate(List orders) { } + + public void SendSync(int frame, byte[] syncData) { - while( sync.Count != 0 ) + var ms = new MemoryStream(); + ms.Write(BitConverter.GetBytes(frame)); + ms.Write(syncData); + sync.Add(ms.ToArray()); + } + + public void Receive(Action packetFn) + { + while (sync.Count != 0) { - packetFn( LocalClientId, sync[ 0 ] ); - sync.RemoveAt( 0 ); + packetFn(LocalClientId, sync[0]); + sync.RemoveAt(0); } - if( replayStream == null ) return; + if (replayStream == null) + return; - var reader = new BinaryReader( replayStream ); + var reader = new BinaryReader(replayStream); - while( replayStream.Position < replayStream.Length ) + while (replayStream.Position < replayStream.Length) { var client = reader.ReadInt32(); var packetLen = reader.ReadInt32(); - var packet = reader.ReadBytes( packetLen ); - packetFn( client, packet ); + var packet = reader.ReadBytes(packetLen); + packetFn(client, packet); } replayStream = null; From bce0a7b39eb929f7fabe26a674b08e1ff9df1fb8 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 5 Mar 2014 18:08:18 +1300 Subject: [PATCH 2/3] Overhaul replay parsing. Fixes #4608. --- OpenRA.Game/Network/Replay.cs | 31 ++-------- OpenRA.Game/Network/ReplayConnection.cs | 77 +++++++++++++++++++------ 2 files changed, 64 insertions(+), 44 deletions(-) diff --git a/OpenRA.Game/Network/Replay.cs b/OpenRA.Game/Network/Replay.cs index 3e6970d756..155489bcc4 100644 --- a/OpenRA.Game/Network/Replay.cs +++ b/OpenRA.Game/Network/Replay.cs @@ -12,7 +12,6 @@ using System; namespace OpenRA.Network { - /* HACK: a maze of twisty little hacks... */ public class Replay { public readonly string Filename; @@ -22,34 +21,12 @@ namespace OpenRA.Network public Replay(string filename) { Filename = filename; - var lastFrame = 0; - var hasSeenGameStart = false; - var lobbyInfo = null as Session; using (var conn = new ReplayConnection(filename)) - conn.Receive((client, packet) => - { - var frame = BitConverter.ToInt32(packet, 0); - if (packet.Length == 5 && packet[4] == 0xBF) - return; // disconnect - else if (packet.Length >= 5 && packet[4] == 0x65) - return; // sync - else if (frame == 0) - { - /* decode this to recover lobbyinfo, etc */ - var orders = packet.ToOrderList(null); - foreach (var o in orders) - if (o.OrderString == "StartGame") - hasSeenGameStart = true; - else if (o.OrderString == "SyncInfo" && !hasSeenGameStart) - lobbyInfo = Session.Deserialize(o.TargetString); - } - else - lastFrame = Math.Max(lastFrame, frame); - }); - - Duration = lastFrame * Game.NetTickScale; - LobbyInfo = lobbyInfo; + { + Duration = conn.TickCount * Game.NetTickScale; + LobbyInfo = conn.LobbyInfo; + } } public Map Map() diff --git a/OpenRA.Game/Network/ReplayConnection.cs b/OpenRA.Game/Network/ReplayConnection.cs index 7ee257a06f..7e031fc486 100755 --- a/OpenRA.Game/Network/ReplayConnection.cs +++ b/OpenRA.Game/Network/ReplayConnection.cs @@ -11,23 +11,74 @@ using System; using System.Collections.Generic; using System.IO; +using OpenRA.FileFormats; namespace OpenRA.Network { public class ReplayConnection : IConnection { - FileStream replayStream; + class Chunk + { + public int Frame; + public List> Packets = new List>(); + } + + Queue chunks = new Queue(); List sync = new List(); + int ordersFrame = 1; public int LocalClientId { get { return 0; } } public ConnectionState ConnectionState { get { return ConnectionState.Connected; } } + public readonly int TickCount; + public readonly bool IsValid; + public readonly Session LobbyInfo; public ReplayConnection(string replayFilename) { - replayStream = File.OpenRead(replayFilename); + // Parse replay data into a struct that can be fed to the game in chunks + // to avoid issues with all immediate orders being resolved on the first tick. + using (var rs = File.OpenRead(replayFilename)) + { + var chunk = new Chunk(); + + while (rs.Position < rs.Length) + { + var client = rs.ReadInt32(); + var packetLen = rs.ReadInt32(); + var packet = rs.ReadBytes(packetLen); + var frame = BitConverter.ToInt32(packet, 0); + chunk.Packets.Add(Pair.New(client, packet)); + + if (packet.Length == 5 && packet[4] == 0xBF) + continue; // disconnect + else if (packet.Length >= 5 && packet[4] == 0x65) + continue; // sync + else if (frame == 0) + { + // Parse replay metadata from orders stream + var orders = packet.ToOrderList(null); + foreach (var o in orders) + { + if (o.OrderString == "StartGame") + IsValid = true; + else if (o.OrderString == "SyncInfo" && !IsValid) + LobbyInfo = Session.Deserialize(o.TargetString); + } + } + else + { + // Regular order - finalize the chunk + chunk.Frame = frame; + chunks.Enqueue(chunk); + chunk = new Chunk(); + + TickCount = Math.Max(TickCount, frame); + } + } + } } - // do nothing; ignore locally generated orders + // Do nothing: ignore locally generated orders public void Send(int frame, List orders) { } public void SendImmediate(List orders) { } @@ -37,6 +88,9 @@ namespace OpenRA.Network ms.Write(BitConverter.GetBytes(frame)); ms.Write(syncData); sync.Add(ms.ToArray()); + + // Store the current frame so Receive() can return the next chunk of orders. + ordersFrame = frame + 1; } public void Receive(Action packetFn) @@ -47,20 +101,9 @@ namespace OpenRA.Network sync.RemoveAt(0); } - if (replayStream == null) - return; - - var reader = new BinaryReader(replayStream); - - while (replayStream.Position < replayStream.Length) - { - var client = reader.ReadInt32(); - var packetLen = reader.ReadInt32(); - var packet = reader.ReadBytes(packetLen); - packetFn(client, packet); - } - - replayStream = null; + while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame) + foreach (var o in chunks.Dequeue().Packets) + packetFn(o.First, o.Second); } public void Dispose() { } From 306271e93fb016d3e0beab94aa5ef2565ab007fc Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 5 Mar 2014 18:14:05 +1300 Subject: [PATCH 3/3] Update changelog. --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c9ad1d084b..e776ac8067 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ NEW: Added a toggle spectators to multiplayer. Removed the ability of commandos to plant C4 on walls. Order lines are now shown on unit selection. + Fixed chat synchronization in replays. Dune 2000: Added the Atreides grenadier from the 1.06 patch. Added randomized tiles for Sand and Rock terrain.