From 41a29662ec5cfefa33ad0b7b83cfbd244d694e71 Mon Sep 17 00:00:00 2001 From: Tretiner Date: Sat, 27 Jul 2024 18:41:19 +0500 Subject: [PATCH] Using memory pooling and spans for tick parsing --- SoulstormReplayReader.Benchmarks/Benchy.cs | 4 +- .../BenchyRecords.txt | 101 ++++------------ .../Domain/Action/GameActionModel.cs | 4 +- .../SoulstormReplayReader.Core.csproj | 4 +- SoulstormReplayReader.Core/SsReplayReader.cs | 111 ++++++++++-------- .../Utils/SpanReader.cs | 59 ++++++++++ .../SoulstormReplayReader.Playground.csproj | 6 +- 7 files changed, 152 insertions(+), 137 deletions(-) create mode 100644 SoulstormReplayReader.Core/Utils/SpanReader.cs diff --git a/SoulstormReplayReader.Benchmarks/Benchy.cs b/SoulstormReplayReader.Benchmarks/Benchy.cs index a69c2ba..5384127 100644 --- a/SoulstormReplayReader.Benchmarks/Benchy.cs +++ b/SoulstormReplayReader.Benchmarks/Benchy.cs @@ -24,9 +24,9 @@ public class Benchy [Params( false - , + , true - )] + )] public bool SkipImages; [GlobalSetup] diff --git a/SoulstormReplayReader.Benchmarks/BenchyRecords.txt b/SoulstormReplayReader.Benchmarks/BenchyRecords.txt index 95b6546..e8e9e05 100644 --- a/SoulstormReplayReader.Benchmarks/BenchyRecords.txt +++ b/SoulstormReplayReader.Benchmarks/BenchyRecords.txt @@ -12,26 +12,9 @@ * * | Method | SkipImages | Mean | Error | StdDev | Gen0 | Allocated | * |---------- |----------- |----------:|---------:|---------:|--------:|----------:| - * | ReadFully | False | 187.55 us | 3.079 us | 2.880 us | 45.4102 | 93.52 KB | - * | ReadFully | True | 26.44 us | 0.335 us | 0.297 us | 6.4392 | 13.17 KB | - * - * | Method | SkipImages | Mean | Error | StdDev | Gen0 | Allocated | - * |---------- |----------- |----------:|---------:|---------:|--------:|----------:| * | ReadFully | False | 217.73 us | 4.190 us | 4.483 us | 45.4102 | 93.9 KB | * | ReadFully | True | 23.28 us | 0.391 us | 0.366 us | 6.1340 | 12.54 KB | * - * | Method | SkipImages | Mean | Error | StdDev | Gen0 | Allocated | - * |---------- |----------- |----------:|---------:|---------:|--------:|----------:| - * | ReadFully | False | 211.93 us | 2.418 us | 2.144 us | 43.4570 | 89.84 KB | - * | ReadFully | True | 20.60 us | 0.296 us | 0.262 us | 4.3640 | 8.94 KB | - * - * | Method | SkipImages | UseFileStream | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | - * |---------- |----------- |-------------- |----------:|----------:|----------:|----------:|--------:|-------:|----------:| - * | ReadFully | False | False | 265.56 us | 13.334 us | 39.316 us | 258.33 us | 43.4570 | 0.4883 | 90.08 KB | - * | ReadFully | False | True | 599.75 us | 14.393 us | 39.644 us | 587.87 us | 42.9688 | 0.9766 | 90.08 KB | - * | ReadFully | True | False | 21.15 us | 0.385 us | 0.674 us | 21.01 us | 4.4861 | - | 9.17 KB | - * | ReadFully | True | True | 70.86 us | 1.392 us | 1.302 us | 70.92 us | 4.3945 | - | 9.17 KB | - * * | Method | SkipImages | UseFileStream | Mean | Error | StdDev | Gen0 | Allocated | * |---------- |----------- |-------------- |----------:|----------:|----------:|--------:|----------:| * | ReadFully | False | False | 237.75 us | 4.725 us | 13.092 us | 43.4570 | 88.95 KB | @@ -78,21 +61,6 @@ Init Replay.Actions list with capacity of Replay.TotalTicks / 2 | ReadFull | 4v4.rec | False | 1,417.59 us | 28.016 us | 23.395 us | 1,407.82 us | 97.6563 | 68.3594 | 27.3438 | 616.45 KB | | ReadFull | 4v4.rec | True | 1,098.68 us | 21.912 us | 38.949 us | 1,087.36 us | 70.3125 | 54.6875 | 27.3438 | 414.01 KB | -| Method | Replay | SkipImages | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated | -|----------- |---------------- |----------- |--------------:|------------:|------------:|--------------:|----------:|---------:|---------:|-----------:| -| ReadHeader | 1v1.rec | False | 1.572 us | 0.0294 us | 0.0275 us | 1.572 us | 0.6695 | - | - | 1.37 KB | -| ReadInfo | 1v1.rec | False | 141.536 us | 2.6164 us | 2.3194 us | 141.797 us | 42.4805 | - | - | 87.67 KB | -| ReadFull | 1v1.rec | False | 141.130 us | 1.8004 us | 1.4056 us | 141.015 us | 43.2129 | - | - | 89.06 KB | -| ReadHeader | 1v1.rec | True | 1.680 us | 0.0376 us | 0.1074 us | 1.646 us | 0.6695 | - | - | 1.37 KB | -| ReadInfo | 1v1.rec | True | 11.819 us | 0.2225 us | 0.3955 us | 11.720 us | 3.2501 | - | - | 6.64 KB | -| ReadFull | 1v1.rec | True | 14.857 us | 0.2916 us | 0.4088 us | 14.860 us | 3.9215 | - | - | 8.03 KB | -| ReadHeader | 4p_withbots.rec | False | 1.849 us | 0.0362 us | 0.0355 us | 1.844 us | 0.7801 | - | - | 1.59 KB | -| ReadInfo | 4p_withbots.rec | False | 75.350 us | 1.4535 us | 1.4927 us | 74.821 us | 22.2168 | - | - | 45.76 KB | -| ReadFull | 4p_withbots.rec | False | 15,368.249 us | 303.3914 us | 755.5498 us | 15,409.927 us | 1078.1250 | 890.6250 | 421.8750 | 6492.96 KB | -| ReadHeader | 4p_withbots.rec | True | 1.912 us | 0.0382 us | 0.1000 us | 1.884 us | 0.7782 | - | - | 1.59 KB | -| ReadInfo | 4p_withbots.rec | True | 11.963 us | 0.1580 us | 0.1401 us | 11.948 us | 2.5177 | - | - | 5.16 KB | -| ReadFull | 4p_withbots.rec | True | 14,558.302 us | 288.3673 us | 803.8528 us | 14,454.221 us | 1046.8750 | 906.2500 | 390.6250 | 6452.2 KB | - | Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |--------- |---------------- |----------- |-------------:|-----------:|-----------:|----------:|---------:|---------:|-----------:| | ReadFull | 1v1.rec | False | 151.58 us | 2.997 us | 5.703 us | 43.2129 | 0.2441 | - | 89.02 KB | @@ -115,33 +83,6 @@ Init Replay.Actions list with capacity of Replay.TotalTicks / 2 | ReadFull | 4v4.rec | False | 1,444.44 us | 28.807 us | 48.130 us | 97.6563 | 68.3594 | 27.3438 | 616.81 KB | | ReadFull | 4v4.rec | True | 1,082.37 us | 18.606 us | 19.107 us | 70.3125 | 54.6875 | 27.3438 | 414.53 KB | -| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|--------- |---------------- |----------- |-------------:|-----------:|-------------:|----------:|---------:|---------:|-----------:| -| ReadFull | 1v1.rec | False | 102.87 us | 1.552 us | 2.176 us | 43.4570 | - | - | 89 KB | -| ReadFull | 1v1.rec | True | 15.57 us | 0.310 us | 0.839 us | 3.9063 | - | - | 8.03 KB | -| ReadFull | 2v2v2.rec | False | 733.72 us | 14.360 us | 18.672 us | 124.0234 | 41.9922 | - | 477.07 KB | -| ReadFull | 2v2v2.rec | True | 461.28 us | 8.453 us | 15.877 us | 75.1953 | 23.4375 | - | 234.35 KB | -| ReadFull | 4p_withbots.rec | False | 17,231.75 us | 589.520 us | 1,738.212 us | 1031.2500 | 843.7500 | 375.0000 | 6493.06 KB | -| ReadFull | 4p_withbots.rec | True | 16,119.01 us | 320.763 us | 710.788 us | 1000.0000 | 875.0000 | 343.7500 | 6452.12 KB | -| ReadFull | 4v4.rec | False | 1,561.33 us | 29.811 us | 31.898 us | 97.6563 | 68.3594 | 27.3438 | 616.81 KB | -| ReadFull | 4v4.rec | True | 1,188.38 us | 23.616 us | 27.196 us | 70.3125 | 54.6875 | 27.3438 | 414.53 KB | - -// ascii string equals with stackalloc bytes sequenceEqual no ignore case -| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|--------- |---------------- |----------- |-------------:|-----------:|-----------:|----------:|---------:|---------:|-----------:| -| ReadFull | 1v1.rec | True | 13.17 us | 0.258 us | 0.265 us | 3.7079 | - | - | 7.6 KB | -| ReadFull | 2v2v2.rec | True | 400.75 us | 7.956 us | 12.848 us | 76.6602 | 20.9961 | - | 233.86 KB | -| ReadFull | 4p_withbots.rec | True | 14,380.66 us | 285.889 us | 768.022 us | 1062.5000 | 921.8750 | 406.2500 | 6451.56 KB | -| ReadFull | 4v4.rec | True | 1,069.55 us | 20.197 us | 20.741 us | 70.3125 | 54.6875 | 27.3438 | 414.01 KB | - -// ascii string equals with stackalloc bytes custom sequenceEqual with ignore case -| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|--------- |---------------- |----------- |-------------:|-----------:|-----------:|----------:|---------:|---------:|-----------:| -| ReadFull | 1v1.rec | True | 13.96 us | 0.273 us | 0.507 us | 3.7079 | - | - | 7.6 KB | -| ReadFull | 2v2v2.rec | True | 409.48 us | 7.863 us | 6.970 us | 77.1484 | 20.9961 | - | 233.86 KB | -| ReadFull | 4p_withbots.rec | True | 13,161.68 us | 238.163 us | 185.942 us | 1000.0000 | 875.0000 | 343.7500 | 6451.48 KB | -| ReadFull | 4v4.rec | True | 1,049.49 us | 20.183 us | 17.892 us | 70.3125 | 54.6875 | 27.3438 | 414.01 KB | - // Extract of logic and cleaning ExBinaryReader | Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |--------- |---------------- |----------- |-------------:|-----------:|-----------:|----------:|---------:|---------:|-----------:| @@ -155,24 +96,26 @@ Init Replay.Actions list with capacity of Replay.TotalTicks / 2 | ReadFull | 4v4.rec | True | 1,106.32 us | 21.747 us | 35.731 us | 70.3125 | 54.6875 | 27.3438 | 414.01 KB | -26.07.2024 -| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|--------- |---------------- |----------- |------------:|----------:|----------:|----------:|---------:|---------:|-----------:| -| ReadFull | 1v1.rec | False | 109.6 us | 1.65 us | 1.46 us | 42.4805 | 7.0801 | - | 88.45 KB | -| ReadFull | 2v2v2.rec | False | 735.7 us | 14.28 us | 12.66 us | 109.3750 | 58.5938 | - | 476.2 KB | -| ReadFull | 4p_withbots.rec | False | 14,794.2 us | 295.22 us | 635.50 us | 1078.1250 | 906.2500 | 421.8750 | 6492.21 KB | -| ReadFull | 4v4.rec | False | 1,352.9 us | 16.24 us | 14.40 us | 93.7500 | 64.4531 | 27.3438 | 615.98 KB | - - -27.07.2024 Images parsing is better -| Method | Replay | SkipImages | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated | -|--------- |---------------- |----------- |-------------:|-----------:|-------------:|-------------:|----------:|---------:|---------:|-----------:| -| ReadFull | 1v1.rec | False | 42.87 us | 0.473 us | 0.395 us | 42.80 us | 42.5415 | 7.0801 | - | 88.27 KB | -| ReadFull | 1v1.rec | True | 12.87 us | 0.095 us | 0.080 us | 12.87 us | 3.6926 | - | - | 7.55 KB | -| ReadFull | 2v2v2.rec | False | 551.09 us | 10.990 us | 23.656 us | 541.74 us | 107.4219 | 53.7109 | - | 475.77 KB | -| ReadFull | 2v2v2.rec | True | 427.14 us | 3.915 us | 3.269 us | 427.37 us | 74.7070 | 20.9961 | - | 233.8 KB | -| ReadFull | 4p_withbots.rec | False | 14,880.91 us | 294.969 us | 561.210 us | 15,118.21 us | 1078.1250 | 906.2500 | 421.8750 | 6492.25 KB | -| ReadFull | 4p_withbots.rec | True | 15,426.41 us | 403.218 us | 1,103.803 us | 15,222.37 us | 1046.8750 | 890.6250 | 390.6250 | 6451.52 KB | -| ReadFull | 4v4.rec | False | 1,227.00 us | 24.521 us | 28.238 us | 1,222.49 us | 93.7500 | 64.4531 | 27.3438 | 615.61 KB | -| ReadFull | 4v4.rec | True | 1,152.13 us | 26.309 us | 73.340 us | 1,117.18 us | 70.3125 | 54.6875 | 27.3438 | 413.96 KB | +// 27.07.2024 Images parsing is better +| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|--------- |---------------- |----------- |-------------:|-----------:|-------------:|----------:|---------:|---------:|-----------:| +| ReadFull | 1v1.rec | False | 42.87 us | 0.473 us | 0.395 us | 42.5415 | 7.0801 | - | 88.27 KB | +| ReadFull | 1v1.rec | True | 12.87 us | 0.095 us | 0.080 us | 3.6926 | - | - | 7.55 KB | +| ReadFull | 2v2v2.rec | False | 551.09 us | 10.990 us | 23.656 us | 107.4219 | 53.7109 | - | 475.77 KB | +| ReadFull | 2v2v2.rec | True | 427.14 us | 3.915 us | 3.269 us | 74.7070 | 20.9961 | - | 233.8 KB | +| ReadFull | 4p_withbots.rec | False | 14,880.91 us | 294.969 us | 561.210 us | 1078.1250 | 906.2500 | 421.8750 | 6492.25 KB | +| ReadFull | 4p_withbots.rec | True | 15,426.41 us | 403.218 us | 1,103.803 us | 1046.8750 | 890.6250 | 390.6250 | 6451.52 KB | +| ReadFull | 4v4.rec | False | 1,227.00 us | 24.521 us | 28.238 us | 93.7500 | 64.4531 | 27.3438 | 615.61 KB | +| ReadFull | 4v4.rec | True | 1,152.13 us | 26.309 us | 73.340 us | 70.3125 | 54.6875 | 27.3438 | 413.96 KB | +// 27.07.2024 Using Memory pooling and spans for tick parsing +| Method | Replay | SkipImages | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|--------- |---------------- |----------- |-------------:|-----------:|-----------:|----------:|---------:|---------:|-----------:| +| ReadFull | 1v1.rec | False | 47.21 us | 0.916 us | 1.091 us | 42.5415 | 7.0801 | - | 88.27 KB | +| ReadFull | 1v1.rec | True | 12.81 us | 0.090 us | 0.075 us | 3.6926 | - | - | 7.55 KB | +| ReadFull | 2v2v2.rec | False | 456.30 us | 5.616 us | 5.253 us | 105.4688 | 54.6875 | - | 475.77 KB | +| ReadFull | 2v2v2.rec | True | 359.79 us | 6.755 us | 5.988 us | 74.7070 | 21.4844 | - | 233.8 KB | +| ReadFull | 4p_withbots.rec | False | 14,020.18 us | 278.529 us | 581.394 us | 1093.7500 | 984.3750 | 437.5000 | 6491.99 KB | +| ReadFull | 4p_withbots.rec | True | 13,371.46 us | 267.402 us | 383.500 us | 1093.7500 | 984.3750 | 453.1250 | 6451.67 KB | +| ReadFull | 4v4.rec | False | 1,095.98 us | 14.512 us | 12.865 us | 93.7500 | 64.4531 | 27.3438 | 615.62 KB | +| ReadFull | 4v4.rec | True | 954.46 us | 9.933 us | 9.291 us | 70.3125 | 54.6875 | 27.3438 | 413.96 KB | \ No newline at end of file diff --git a/SoulstormReplayReader.Core/Domain/Action/GameActionModel.cs b/SoulstormReplayReader.Core/Domain/Action/GameActionModel.cs index 4aeabbd..518e53a 100644 --- a/SoulstormReplayReader.Core/Domain/Action/GameActionModel.cs +++ b/SoulstormReplayReader.Core/Domain/Action/GameActionModel.cs @@ -1,8 +1,8 @@ namespace SoulstormReplayReader.Core.Domain.Action; -public sealed class GameActionModel(int tick) : IGameAction +public sealed class GameActionModel : IGameAction { - public int Tick { get; } = tick; + public int Tick { get; set; } public int PlayerId { get; set; } public int PlayerActionCount { get; set; } diff --git a/SoulstormReplayReader.Core/SoulstormReplayReader.Core.csproj b/SoulstormReplayReader.Core/SoulstormReplayReader.Core.csproj index b9eb0b2..a455802 100644 --- a/SoulstormReplayReader.Core/SoulstormReplayReader.Core.csproj +++ b/SoulstormReplayReader.Core/SoulstormReplayReader.Core.csproj @@ -1,11 +1,11 @@ - Library net8.0 enable true - Debug;Release;DebugLogging + Debug;Release AnyCPU + 1.0.0 diff --git a/SoulstormReplayReader.Core/SsReplayReader.cs b/SoulstormReplayReader.Core/SsReplayReader.cs index 44e5e2d..4496f09 100644 --- a/SoulstormReplayReader.Core/SsReplayReader.cs +++ b/SoulstormReplayReader.Core/SsReplayReader.cs @@ -1,5 +1,5 @@ -using System.Buffers.Binary; -using System.Runtime.CompilerServices; +using System.Buffers; +using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Text; using SoulstormReplayReader.Core.Domain; @@ -431,9 +431,33 @@ public sealed class SsReplayReader(Stream stream) : IDisposable if (tickType == TickType.Normal) { if (tickSize == 17) - SkipEmptyTick(); + { + _binaryReader.Skip(tickSize); // Всегда 17 байт + } else - ReadOrdinaryTick(); + { + byte[] rentBytes = null; + var bytes = tickSize switch + { + > 0 and < 0xFF => stackalloc byte[tickSize], + _ => rentBytes = ArrayPool.Shared.Rent(tickSize) + }; + + try + { + bytes = _binaryReader.ReadBytes(bytes[..tickSize]); + ParseOrdinaryTick(bytes); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + finally + { + if (rentBytes is not null) + ArrayPool.Shared.Return(rentBytes); + } + } CurrentTick++; } @@ -443,36 +467,32 @@ public sealed class SsReplayReader(Stream stream) : IDisposable ReadWierdTick((int)tickType, tickSize); } - - // Всегда 17 байт - private void SkipEmptyTick() + private void ParseOrdinaryTick(Span bytes) { - _binaryReader.Skip(17); - } + var spanReader = new SpanReader(bytes); - - private void ReadOrdinaryTick() - { - // -- TICK HEADER -- - _binaryReader.Skip(1); // 0x50 - var tickCount = _binaryReader.ReadInt32(); - _binaryReader.Skip(8); // int32 "Номер действия игрока" ??? + int32 Random - var playerChunksCount = _binaryReader.ReadInt32(); + // -- TICK HEADER -- size: 17 + spanReader.Skip(1); // 1 byte = 0x50 + var tickCount = spanReader.ReadInt32(); + spanReader.Skip(8); // 8 bytes = int32 "Номер действия игрока" ??? + int32 Random + var playerChunksCount = spanReader.ReadInt32(); // -- TICK BODY -- for (var i = 0; i < playerChunksCount; i++) // для каждого игрока предусмотрен свой чанк со своим размером { - // -- PLAYER CHUNK HEADER -- - _binaryReader.Skip(8); // ??? - var playerChunkSize = _binaryReader.ReadInt32(); - _binaryReader.Skip(1); // то же самое + // -- PLAYER CHUNK HEADER -- size: 13 + пока размер чанка != 0 + spanReader.Skip(8); // 8 bytes = ??? + var playerChunkSize = spanReader.ReadInt32(); + spanReader.Skip(1); // 1 byte = playerChunkSize, но байт // -- PLAYER CHUNK BODY -- while (playerChunkSize != 0) { - var actionSize = _binaryReader.ReadInt16(); // размер захватывает 2 байта след чанка + var actionSize = spanReader.ReadInt16(); // размер захватывает 2 байта (размера) следующего экшена - var action = ReadPlayerAction(tickCount, actionSize); + spanReader.SliceToOffset(); + var action = ParsePlayerAction(ref spanReader, actionSize - 2); + action.Tick = tickCount; Replay.Actions.Add(action); playerChunkSize -= actionSize; @@ -480,34 +500,21 @@ public sealed class SsReplayReader(Stream stream) : IDisposable } } - - private GameActionModel ReadPlayerAction(int tickCount, int actionSize) + private GameActionModel ParsePlayerAction(ref SpanReader spanReader, int actionSize) { - var actionEndPos = _binaryReader.Position + actionSize - 2; - // -- ACTION HEADER -- - _binaryReader.SkipInt32(); // какое то время (свое для каждого игрока) + spanReader.Skip(4); // 4 bytes = какое то время (свое для каждого игрока) // (начинается с рандомного значения и увеличивается по ходу игры) // Скорее всего является global timer - // var s_cmd = _binaryReader.ReadInt32(); - // var s_param = _binaryReader.ReadInt32(); - // var s_shiftPressed = _binaryReader.ReadByte() == 1; - // var s_playerId = _binaryReader.Skip(2).ReadInt16() % 10; - // - // - // var leftOverBytes2 = actionEndPos - _binaryReader.Position; - // Console.WriteLine(_binaryReader.ReadBytes((int)leftOverBytes2).ToContentString()); - // return new GameActionModel(1); - - var cmd = _binaryReader.ReadInt32(); - var arg = _binaryReader.ReadByte(); // int32 in game - var subCmd = _binaryReader.ReadInt16(); - var someNum = _binaryReader.ReadByte(); - var shiftPressed = _binaryReader.ReadByte() == 1; - - var playerId = _binaryReader.Skip(2).ReadInt16() % 10; - var playerActionCount = _binaryReader.ReadInt16(); + var cmd = spanReader.ReadInt32(); + var arg = spanReader.ReadByte(); // int32 in game + var subCmd = spanReader.ReadInt16(); + var someNum = spanReader.ReadByte(); + var shiftPressed = spanReader.ReadByte() == 1; + // 2 bytes = player id + var playerId = spanReader.Skip(2).ReadInt16() % 10; + var playerActionCount = spanReader.ReadInt16(); #if DEBUGLOGGING var selectedCount = _binaryReader.ReadByte(); @@ -523,10 +530,10 @@ public sealed class SsReplayReader(Stream stream) : IDisposable var primaryId = _binaryReader.ReadInt32(); var actionBodyDataType = _binaryReader.ReadByte(); #else - _binaryReader.Skip(7); + // _binaryReader.Skip(7); #endif - var curAction = new GameActionModel(tickCount) + var curAction = new GameActionModel { Cmd = cmd, Arg = arg, @@ -542,6 +549,8 @@ public sealed class SsReplayReader(Stream stream) : IDisposable if (CheckForBugs) Replay.Players[playerId].BugChecker?.Check(curAction); + spanReader.Skip((uint)(actionSize - spanReader.offset)); + #if DEBUGLOGGING // -- ACTION BODY -- @@ -565,8 +574,8 @@ public sealed class SsReplayReader(Stream stream) : IDisposable // } #else // -- ACTION BODY -- - var leftOverBytes = actionEndPos - _binaryReader.Position; - _binaryReader.Skip((int)leftOverBytes); + // var leftOverBytes = actionEndPos - _binaryReader.Position; + // _binaryReader.Skip((int)leftOverBytes); #endif return curAction; @@ -577,7 +586,7 @@ public sealed class SsReplayReader(Stream stream) : IDisposable { var tickType = _binaryReader.ReadInt32(); _binaryReader.Skip(4); // Размер тика - _binaryReader.Skip(1); // То же самое + _binaryReader.Skip(1); // Размер тика, но байт if (tickType == 0) ReadPlayerQuit(); diff --git a/SoulstormReplayReader.Core/Utils/SpanReader.cs b/SoulstormReplayReader.Core/Utils/SpanReader.cs new file mode 100644 index 0000000..0267fec --- /dev/null +++ b/SoulstormReplayReader.Core/Utils/SpanReader.cs @@ -0,0 +1,59 @@ +namespace SoulstormReplayReader.Core.Utils; + +public ref struct SpanReader +{ + public Span bytes; + public int offset = 0; + + public SpanReader(Span bytes) + { + this.bytes = bytes; + } + + public SpanReader SliceToOffset() + { + bytes = bytes[offset..]; + offset = 0; + return this; + } + + public SpanReader Skip(uint count) + { + offset += (int)count; + return this; + } + + public byte ReadByte() + { + var value = bytes[offset]; + offset++; + return value; + } + + public short ReadInt16() + { + var value = BitConverter.ToInt16(bytes[offset..]); + offset += sizeof(short); + return value; + } + + public int ReadInt32() + { + var value = BitConverter.ToInt32(bytes[offset..]); + offset += sizeof(int); + return value; + } + + public long ReadInt64() + { + var value = BitConverter.ToInt64(bytes[offset..]); + offset += sizeof(long); + return value; + } + + public readonly void RequireInRange() + { + if (offset >= bytes.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + } +} \ No newline at end of file diff --git a/SoulstormReplayReader.Playground/SoulstormReplayReader.Playground.csproj b/SoulstormReplayReader.Playground/SoulstormReplayReader.Playground.csproj index 3c21d8d..eac181f 100644 --- a/SoulstormReplayReader.Playground/SoulstormReplayReader.Playground.csproj +++ b/SoulstormReplayReader.Playground/SoulstormReplayReader.Playground.csproj @@ -8,7 +8,11 @@ - + + + + +