-
-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for seeking to arbitrary ticks #49
Conversation
…` and `DemoParser.MoveNextAsync`
|
@@ -9,18 +9,25 @@ namespace DemoFile; | |||
|
|||
public sealed partial class DemoParser | |||
{ | |||
private readonly ArrayPool<byte> _bytePool = ArrayPool<byte>.Create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use shared ArrayPool, or allow the user to specify it (in constructor, or as a property)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the reason is, shared ArrayPool (or custom provided) may already have ready buffers, so that way you avoid allocating new buffers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the simplest way of doing this:
private readonly ArrayPool<byte> _bytePool = ArrayPool<byte>.Shared;
public ArrayPool<byte> BytePool { get => _bytePool; init => _bytePool = value; }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason to not use the shared ArrayPool is that it is thread-safe, and the locking is unnecessary overhead. I can't think of other libraries that expose the ArrayPool that is used.
the reason is, shared ArrayPool (or custom provided) may already have ready buffers, so that way you avoid allocating new buffers
This is true, but as long as the same few arrays are used during parsing, it doesn't really matter. We're talking allocations of a handful of arrays here over the whole demo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik every ArrayPool is thread-safe : https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1?view=net-8.0#thread-safety
but, you're right that there will be only a few arrays alive at any given time, so I guess it's fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
speaking of which, I've seen some places where 16 length array is rented, this creates overhead, maybe it's better to use Span<byte> span = stackalloc byte[16];
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't stackalloc in async methods
when i do this: await demo.StartReadingAsync(File.OpenRead(path), default);
await demo.SeekToTickAsync(new DemoTick(500), default); it gives me:
|
Just pushed a fix for the IndexOutOfRangeException - can you try now? |
If I seek to 5000, it gives me 1 player death event, when I seek to 50000, there are no death events. Is this expected ? |
Also,
|
You can't depend on game events etc. occuring while seeking. This is the reason why Valve removed the round_start/end events as you can't rely on them to know when rounds are starting and finishing. As for this:
Please can you share the code that reproduces the exception? |
var path = "test.dem";
var demo = new DemoParser();
demo.Source1GameEvents.PlayerDeath += e =>
{
Console.WriteLine($"{e.Attacker?.PlayerName} [{e.Weapon}] {e.Player?.PlayerName}");
};
await demo.StartReadingAsync(File.OpenRead(path), default);
await demo.SeekToTickAsync(new DemoTick(50000), default); |
But why are game events firing while seeking ? In my understanding, parser should ignore all events (and entity changes) while seeking. |
I've just pushed another change that should fix the seeking to tick 50,000 issue |
It works now, but game events are still being fired. Is it possible to seek without invoking any callbacks ? Or without parsing anything except StringTables ? |
I get some weird error:
with this code : public static async Task TestBug(string path)
{
var stream = new MemoryStream(File.ReadAllBytes(path));
var demo = new DemoParser();
await demo.StartReadingAsync(stream, default);
var tickCount = demo.TickCount.Value;
const int FullPacketInterval = 64 * 60;
for (int i = FullPacketInterval * 4; i <= tickCount; i += FullPacketInterval)
{
Console.WriteLine($"Seeking to tick {i}");
demo = new DemoParser();
//stream = new MemoryStream(File.ReadAllBytes(path));
stream.Position = 0;
await demo.StartReadingAsync(stream, default);
await demo.SeekToTickAsync(new DemoTick(i), default);
while (await demo.MoveNextAsync(default))
{
}
}
} |
This is because DemFullPackets only contains the string tables that changed since the last DemFullPacket. I'm going to have to rethink how stringtables are stored to allow seeking. Will let you know when I've pushed a fix. |
This PR adds a new
SeekToTickAsync
method that can jump to arbitrary ticks within the demo. It supports seeking backwards and forwards, and makes use of Source 2 demo 'snapshot' ticks to do this efficiently.Closes #47