Add metadata block to replays

The replay files are just streams all network communication so to
get any info out of them it is necessary to play back the stream
until the wanted information is reached.

This introduces a new metadata block placed at the end of the
replay files and logic to read the new block, or fall back to
playing back the stream for older files.

The replay browser is also updated to use the metadata information
instead of reading the replay stream directly.
This commit is contained in:
Pavlos Touboulidis
2014-04-28 00:44:04 +03:00
parent 4454c0c2f8
commit 98a05b61b3
8 changed files with 353 additions and 86 deletions

View File

@@ -35,51 +35,63 @@ namespace OpenRA.Network
public ReplayConnection(string 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);
}
}
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
}
ordersFrame = LobbyInfo.GlobalSettings.OrderLatency;
}
public ReplayConnection(FileStream rs)
{
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
}
void Read(FileStream rs, ref int TickCount, ref bool IsValid, ref Session LobbyInfo)
{
// 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.
var chunk = new Chunk();
while (rs.Position < rs.Length)
{
var client = rs.ReadInt32();
if (client == FileFormats.ReplayMetadata.MetaStartMarker)
break;
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
public void Send(int frame, List<byte[]> orders) { }
public void SendImmediate(List<byte[]> orders) { }

View File

@@ -12,12 +12,16 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Widgets;
namespace OpenRA.Network
{
class ReplayRecorderConnection : IConnection
{
public ReplayMetadata Metadata;
public WinState LocalGameState = WinState.Undefined;
IConnection inner;
BinaryWriter writer;
Func<string> chooseFilename;
@@ -101,6 +105,12 @@ namespace OpenRA.Network
if (disposed)
return;
if (Metadata != null)
{
Metadata.FinalizeReplayMetadata(DateTime.UtcNow, LocalGameState);
Metadata.Write(writer);
}
writer.Close();
inner.Dispose();
disposed = true;