Improve replay metadata and the replay browser

List of changes:

* Better and more filters with new layout, for both mods.

* Rename/Delete/Detele all functionality.

* Simplified ReplayMetadata class considerably by introducing a new
GameInformation data object. The new GameInformation class contains
more information than previously available so the new solution is not
compatible with old replays, meaning it can't read old replays.

* Better and cleaner game information gathering in order to be written
at the end of the replay file.

* Revert changes to ReplayConnection, no longer necessary.

* Better exception message on missing sprites and fonts.

* New "SpawnOccupant" class that holds all the information needed by the
MapPreviewWidget to visualize a spawn point. It was using Session.Client
before and it was necessary to separate it to be able to show information
not available at lobby time.

* Fix keyboard focus UI bug when closing a window would not remove focus.
This commit is contained in:
Pavlos Touboulidis
2014-05-01 19:39:47 +03:00
parent 042910bd5e
commit de0a5ebd43
21 changed files with 1125 additions and 412 deletions

View File

@@ -22,67 +22,33 @@ namespace OpenRA.FileFormats
public const int MetaEndMarker = -2;
public const int MetaVersion = 0x00000001;
public readonly GameInformation GameInfo;
public string FilePath { get; private set; }
public DateTime EndTimestampUtc { get; private set; }
public TimeSpan Duration { get { return EndTimestampUtc - StartTimestampUtc; } }
public WinState Outcome { get; private set; }
public readonly Lazy<Session> LobbyInfo;
public readonly DateTime StartTimestampUtc;
readonly string lobbyInfoData;
ReplayMetadata()
public ReplayMetadata(GameInformation info)
{
Outcome = WinState.Undefined;
if (info == null)
throw new ArgumentNullException("info");
GameInfo = info;
}
public ReplayMetadata(DateTime startGameTimestampUtc, Session lobbyInfo)
: this()
ReplayMetadata(BinaryReader reader, string path)
{
if (startGameTimestampUtc.Kind == DateTimeKind.Unspecified)
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "startGameTimestamp");
FilePath = path;
StartTimestampUtc = startGameTimestampUtc.ToUniversalTime();
lobbyInfoData = lobbyInfo.Serialize();
LobbyInfo = Exts.Lazy(() => Session.Deserialize(this.lobbyInfoData));
}
public void FinalizeReplayMetadata(DateTime endGameTimestampUtc, WinState outcome)
{
if (endGameTimestampUtc.Kind == DateTimeKind.Unspecified)
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "endGameTimestampUtc");
EndTimestampUtc = endGameTimestampUtc.ToUniversalTime();
Outcome = outcome;
}
ReplayMetadata(BinaryReader reader)
: this()
{
// Read start marker
if (reader.ReadInt32() != MetaStartMarker)
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
// Read version
var version = reader.ReadInt32();
if (version > MetaVersion)
if (version != MetaVersion)
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
// Read start game timestamp
StartTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
// Read end game timestamp
EndTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
// Read game outcome
WinState outcome;
if (Enum.TryParse(ReadUtf8String(reader), true, out outcome))
Outcome = outcome;
// Read lobby info
lobbyInfoData = ReadUtf8String(reader);
LobbyInfo = Exts.Lazy(() => Session.Deserialize(this.lobbyInfoData));
// Read game info
string data = ReadUtf8String(reader);
GameInfo = GameInformation.Deserialize(data);
}
public void Write(BinaryWriter writer)
@@ -94,19 +60,8 @@ namespace OpenRA.FileFormats
// Write data
int dataLength = 0;
{
// Write start game timestamp
writer.Write(StartTimestampUtc.Ticks);
dataLength += sizeof(long);
// Write end game timestamp
writer.Write(EndTimestampUtc.Ticks);
dataLength += sizeof(long);
// Write game outcome
dataLength += WriteUtf8String(writer, Outcome.ToString());
// Write lobby info data
dataLength += WriteUtf8String(writer, lobbyInfoData);
dataLength += WriteUtf8String(writer, GameInfo.Serialize());
}
// Write total length & end marker
@@ -114,76 +69,43 @@ namespace OpenRA.FileFormats
writer.Write(MetaEndMarker);
}
public static ReplayMetadata Read(string path, bool enableFallbackMethod = true)
public void RenameFile(string newFilenameWithoutExtension)
{
Func<DateTime> timestampProvider = () =>
{
try
{
return File.GetCreationTimeUtc(path);
}
catch
{
return DateTime.MinValue;
}
};
using (var fs = new FileStream(path, FileMode.Open))
{
var o = Read(fs, enableFallbackMethod, timestampProvider);
if (o != null)
o.FilePath = path;
return o;
}
var newPath = Path.Combine(Path.GetDirectoryName(FilePath), newFilenameWithoutExtension) + ".rep";
File.Move(FilePath, newPath);
FilePath = newPath;
}
static ReplayMetadata Read(FileStream fs, bool enableFallbackMethod, Func<DateTime> fallbackTimestampProvider)
public static ReplayMetadata Read(string path)
{
using (var fs = new FileStream(path, FileMode.Open))
return Read(fs, path);
}
static ReplayMetadata Read(FileStream fs, string path)
{
if (!fs.CanSeek)
return null;
fs.Seek(-(4 + 4), SeekOrigin.End);
using (var reader = new BinaryReader(fs))
{
// Disposing the BinaryReader will dispose the underlying stream
// and we don't want that because ReplayConnection may use the
// stream as well.
//
// Fixed in .NET 4.5.
// See: http://msdn.microsoft.com/en-us/library/gg712804%28v=vs.110%29.aspx
if (fs.CanSeek)
var dataLength = reader.ReadInt32();
if (reader.ReadInt32() == MetaEndMarker)
{
fs.Seek(-(4 + 4), SeekOrigin.End);
var dataLength = reader.ReadInt32();
if (reader.ReadInt32() == MetaEndMarker)
// go back end marker + length storage + data + version + start marker
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
try
{
// go back end marker + length storage + data + version + start marker
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
try
{
return new ReplayMetadata(reader);
}
catch (InvalidOperationException ex)
{
Log.Write("debug", ex.ToString());
}
catch (NotSupportedException ex)
{
Log.Write("debug", ex.ToString());
}
return new ReplayMetadata(reader, path);
}
// Reset the stream position or the ReplayConnection will fail later
fs.Seek(0, SeekOrigin.Begin);
}
if (enableFallbackMethod)
{
using (var conn = new ReplayConnection(fs))
catch (InvalidOperationException ex)
{
var replay = new ReplayMetadata(fallbackTimestampProvider(), conn.LobbyInfo);
if (conn.TickCount == 0)
return null;
var seconds = (int)Math.Ceiling((conn.TickCount * Game.NetTickScale) / 25f);
replay.EndTimestampUtc = replay.StartTimestampUtc.AddSeconds(seconds);
return replay;
Log.Write("debug", ex.ToString());
}
catch (NotSupportedException ex)
{
Log.Write("debug", ex.ToString());
}
}
}
@@ -210,10 +132,5 @@ namespace OpenRA.FileFormats
{
return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
}
public MapPreview MapPreview
{
get { return Game.modData.MapCache[LobbyInfo.Value.GlobalSettings.Map]; }
}
}
}