-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #104 from asdawej/dev-playback
Dev for playback
- Loading branch information
Showing
6 changed files
with
123 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,143 +1,68 @@ | ||
using Google.Protobuf; | ||
using Protobuf; | ||
using System; | ||
using System.IO; | ||
using System.IO.Compression; | ||
|
||
namespace Playback | ||
{ | ||
public class FileFormatNotLegalException(string fileName) : Exception | ||
{ | ||
private readonly string fileName = fileName; | ||
public override string Message => $"The file: " + this.fileName + " is not a legal playback file for THUAI6."; | ||
} | ||
|
||
public class MessageReader : IDisposable | ||
{ | ||
private FileStream? fs; | ||
private CodedInputStream cos; | ||
private GZipStream gzs; | ||
private byte[] buffer; | ||
public bool Finished { get; private set; } = false; | ||
/// <summary> | ||
/// 回放文件绝对路径 | ||
/// </summary> | ||
public string FileName { get; } | ||
|
||
public readonly uint teamCount; | ||
public readonly uint playerCount; | ||
|
||
const int bufferMaxSize = 10 * 1024 * 1024; // 10M | ||
private readonly CodedInputStream cis; // Protobuf类型二进制输入流 | ||
|
||
public bool Disposed { get; private set; } = false; | ||
|
||
public MessageReader(string fileName) | ||
{ | ||
if (!fileName.EndsWith(PlayBackConstant.ExtendedName)) | ||
{ | ||
fileName += PlayBackConstant.ExtendedName; | ||
} | ||
|
||
fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); | ||
|
||
try | ||
{ | ||
var prefixLen = PlayBackConstant.Prefix.Length; | ||
byte[] bt = new byte[prefixLen + sizeof(UInt32) * 2]; | ||
fs.Read(bt, 0, bt.Length); | ||
for (int i = 0; i < prefixLen; ++i) | ||
{ | ||
if (bt[i] != PlayBackConstant.Prefix[i]) throw new FileFormatNotLegalException(fileName); | ||
} | ||
|
||
teamCount = BitConverter.ToUInt32(bt, prefixLen); | ||
playerCount = BitConverter.ToUInt32(bt, prefixLen + sizeof(UInt32)); | ||
} | ||
catch | ||
{ | ||
throw new FileFormatNotLegalException(fileName); | ||
} | ||
|
||
gzs = new GZipStream(fs, CompressionMode.Decompress); | ||
var tmpBuffer = new byte[bufferMaxSize]; | ||
var bufferSize = gzs.Read(tmpBuffer); | ||
if (bufferSize == 0) | ||
{ | ||
buffer = tmpBuffer; | ||
Finished = true; | ||
} | ||
else if (bufferSize != bufferMaxSize) // 不留空位,防止 CodedInputStream 获取信息错误 | ||
{ | ||
if (bufferSize == 0) | ||
{ | ||
Finished = true; | ||
} | ||
buffer = new byte[bufferSize]; | ||
Array.Copy(tmpBuffer, buffer, bufferSize); | ||
} | ||
else | ||
{ | ||
buffer = tmpBuffer; | ||
} | ||
cos = new CodedInputStream(buffer); | ||
Utils.FileNameRegular(ref fileName); | ||
FileStream fs = File.OpenRead(fileName); | ||
FileName = fs.Name; | ||
(teamCount, playerCount) = fs.ReadHeader(); | ||
GZipStream gzs = new(fs, CompressionMode.Decompress); | ||
cis = new(gzs); | ||
} | ||
|
||
public MessageToClient? ReadOne() | ||
{ | ||
beginRead: | ||
if (Finished) | ||
return null; | ||
var pos = cos.Position; | ||
if (Disposed) return null; | ||
if (cis.IsAtEnd) return null; | ||
MessageToClient ret = new(); | ||
try | ||
{ | ||
MessageToClient? msg = new(); | ||
cos.ReadMessage(msg); | ||
return msg; | ||
cis.ReadMessage(ret); | ||
return ret; | ||
} | ||
catch (InvalidProtocolBufferException) | ||
catch | ||
{ | ||
var leftByte = buffer.Length - pos; // 上次读取剩余的字节 | ||
if (buffer.Length < bufferMaxSize / 2) | ||
{ | ||
var newBuffer = new byte[bufferMaxSize]; | ||
for (int i = 0; i < leftByte; i++) | ||
{ | ||
newBuffer[i] = buffer[pos + i]; | ||
} | ||
buffer = newBuffer; | ||
} | ||
else | ||
{ | ||
for (int i = 0; i < leftByte; ++i) | ||
{ | ||
buffer[i] = buffer[pos + i]; | ||
} | ||
} | ||
var bufferSize = gzs.Read(buffer, (int)leftByte, (int)(buffer.Length - leftByte)) + leftByte; | ||
if (bufferSize == leftByte) | ||
{ | ||
Finished = true; | ||
return null; | ||
} | ||
if (bufferSize != buffer.Length) // 不留空位,防止 CodedInputStream 获取信息错误 | ||
{ | ||
var tmpBuffer = new byte[bufferSize]; | ||
Array.Copy(buffer, tmpBuffer, bufferSize); | ||
buffer = tmpBuffer; | ||
} | ||
cos = new CodedInputStream(buffer); | ||
goto beginRead; | ||
return null; | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Finished = true; | ||
if (fs == null) | ||
return; | ||
if (fs.CanRead) | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (Disposed) return; | ||
if (disposing) | ||
{ | ||
fs.Close(); | ||
cis.Dispose(); | ||
} | ||
Disposed = true; | ||
} | ||
|
||
~MessageReader() | ||
{ | ||
Dispose(); | ||
Dispose(false); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,60 @@ | ||
using Google.Protobuf; | ||
using Protobuf; | ||
using System; | ||
using System.IO; | ||
using System.IO.Compression; | ||
|
||
namespace Playback | ||
{ | ||
public class MessageWriter : IDisposable | ||
{ | ||
private FileStream fs; | ||
private CodedOutputStream cos; | ||
private MemoryStream ms; | ||
private GZipStream gzs; | ||
private const int memoryCapacity = 10 * 1024 * 1024; // 10M | ||
/// <summary> | ||
/// 回放文件绝对路径 | ||
/// </summary> | ||
public string FileName { get; } | ||
|
||
private static void ClearMemoryStream(MemoryStream msToClear) | ||
{ | ||
msToClear.Position = 0; | ||
msToClear.SetLength(0); | ||
} | ||
private readonly CodedOutputStream cos; // Protobuf类型二进制输出流 | ||
|
||
public bool Disposed { get; private set; } = false; | ||
|
||
public MessageWriter(string fileName, uint teamCount, uint playerCount) | ||
{ | ||
if (!fileName.EndsWith(PlayBackConstant.ExtendedName)) | ||
{ | ||
fileName += PlayBackConstant.ExtendedName; | ||
} | ||
|
||
fs = new FileStream(fileName, FileMode.Create, FileAccess.Write); | ||
fs.Write(PlayBackConstant.Prefix); // 写入前缀 | ||
|
||
fs.Write(BitConverter.GetBytes((UInt32)teamCount)); // 写入队伍数 | ||
fs.Write(BitConverter.GetBytes((UInt32)playerCount)); // 写入每队的玩家人数 | ||
ms = new MemoryStream(memoryCapacity); | ||
cos = new CodedOutputStream(ms); | ||
gzs = new GZipStream(fs, CompressionMode.Compress); | ||
Utils.FileNameRegular(ref fileName); | ||
FileStream fs = File.Create(fileName); | ||
FileName = fs.Name; | ||
fs.WriteHeader(teamCount, playerCount); | ||
GZipStream gzs = new(fs, CompressionMode.Compress); | ||
cos = new(gzs); | ||
} | ||
|
||
public void WriteOne(MessageToClient msg) | ||
{ | ||
if (Disposed) return; | ||
cos.WriteMessage(msg); | ||
if (ms.Length > memoryCapacity) | ||
Flush(); | ||
} | ||
|
||
public void Flush() | ||
{ | ||
if (fs.CanWrite) | ||
{ | ||
cos.Flush(); | ||
gzs.Write(ms.GetBuffer(), 0, (int)ms.Length); | ||
gzs.Flush(); | ||
ClearMemoryStream(ms); | ||
fs.Flush(); | ||
} | ||
cos.Flush(); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
if (fs.CanWrite) | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (Disposed) return; | ||
if (disposing) | ||
{ | ||
Flush(); | ||
cos.Dispose(); | ||
gzs.Dispose(); | ||
fs.Dispose(); | ||
} | ||
Disposed = true; | ||
} | ||
|
||
~MessageWriter() | ||
{ | ||
Dispose(); | ||
Dispose(false); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,67 @@ | ||
namespace Playback | ||
namespace Playback; | ||
public static class Constants | ||
{ | ||
public static class PlayBackConstant | ||
/// <summary> | ||
/// 回放版本 | ||
/// </summary> | ||
public const int Version = 7; | ||
/// <summary> | ||
/// 回放文件扩展名 | ||
/// </summary> | ||
public static readonly string FileExtension = $".thuai{Version}.pb"; | ||
/// <summary> | ||
/// 回放文件头 | ||
/// </summary> | ||
public static readonly byte[] FileHeader = [(byte)'P', (byte)'B', Version, 0]; | ||
} | ||
/// <summary> | ||
/// 回放文件格式错误 | ||
/// </summary> | ||
/// <param name="fileName"></param> | ||
public class FileFormatNotLegalException(string fileName) : Exception | ||
{ | ||
public string FileName { get; } = fileName; | ||
public override string Message { get; } | ||
= $"The file: {fileName} is not a legal playback file for THUAI{Constants.Version}."; | ||
} | ||
public static class Utils | ||
{ | ||
/// <summary> | ||
/// 回放文件名正则化 | ||
/// </summary> | ||
/// <param name="fileName">文件名</param> | ||
public static void FileNameRegular(ref string fileName) | ||
{ | ||
if (!fileName.EndsWith(Constants.FileExtension)) | ||
{ | ||
fileName += Constants.FileExtension; | ||
} | ||
} | ||
/// <summary> | ||
/// 文件头数据写入 | ||
/// </summary> | ||
/// <param name="fs">文件写入流</param> | ||
/// <param name="teamCount">队伍数</param> | ||
/// <param name="playerCount">玩家数</param> | ||
public static void WriteHeader(this FileStream fs, uint teamCount, uint playerCount) | ||
{ | ||
BinaryWriter bw = new(fs); | ||
bw.Write(Constants.FileHeader); // 写入文件头 | ||
bw.Write(teamCount); // 写入队伍数 | ||
bw.Write(playerCount); // 写入玩家数 | ||
} | ||
/// <summary> | ||
/// 文件头数据读取 | ||
/// </summary> | ||
/// <returns></returns> | ||
/// <param name="fs">文件读取流</param> | ||
/// <exception cref="FileFormatNotLegalException"></exception> | ||
public static (uint teamCount, uint playerCount) ReadHeader(this FileStream fs) | ||
{ | ||
public static string ExtendedName = ".thuaipb"; | ||
public static byte[] Prefix = { (byte)'P', (byte)'B', 6, 0 }; // 文件前缀,用于标识文件类型,版本号为6 | ||
BinaryReader br = new(fs); | ||
if (!br.ReadBytes(Constants.FileHeader.Length) // 判断文件头 | ||
.SequenceEqual(Constants.FileHeader)) | ||
throw new FileFormatNotLegalException(fs.Name); | ||
return (br.ReadUInt32(), br.ReadUInt32()); // 读取队伍数和每队玩家人数 | ||
} | ||
} |