feat(vfx): port retail effect scheduling and delivery
This commit is contained in:
parent
363e046112
commit
96ddfdf175
28 changed files with 2473 additions and 691 deletions
|
|
@ -18,9 +18,15 @@ public sealed class RetailDatLoaderTests
|
|||
private sealed class RawDatabase : IDatDatabase
|
||||
{
|
||||
private readonly Dictionary<uint, byte[]> _entries = new();
|
||||
private int _activeReads;
|
||||
private int _maxConcurrentReads;
|
||||
private int _totalReads;
|
||||
|
||||
public DatDatabase Db => null!;
|
||||
public int Iteration => 0;
|
||||
public int ReadDelayMilliseconds { get; set; }
|
||||
public int MaxConcurrentReads => Volatile.Read(ref _maxConcurrentReads);
|
||||
public int TotalReads => Volatile.Read(ref _totalReads);
|
||||
public void Add(uint id, byte[] bytes) => _entries[id] = bytes;
|
||||
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj => _entries.Keys;
|
||||
public bool TryGet<T>(uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj
|
||||
|
|
@ -28,8 +34,28 @@ public sealed class RetailDatLoaderTests
|
|||
value = default;
|
||||
return false;
|
||||
}
|
||||
public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value) =>
|
||||
_entries.TryGetValue(fileId, out value);
|
||||
public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value)
|
||||
{
|
||||
Interlocked.Increment(ref _totalReads);
|
||||
int active = Interlocked.Increment(ref _activeReads);
|
||||
int observed;
|
||||
do
|
||||
{
|
||||
observed = Volatile.Read(ref _maxConcurrentReads);
|
||||
}
|
||||
while (active > observed
|
||||
&& Interlocked.CompareExchange(ref _maxConcurrentReads, active, observed) != observed);
|
||||
try
|
||||
{
|
||||
if (ReadDelayMilliseconds > 0)
|
||||
Thread.Sleep(ReadDelayMilliseconds);
|
||||
return _entries.TryGetValue(fileId, out value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref _activeReads);
|
||||
}
|
||||
}
|
||||
public bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead)
|
||||
{
|
||||
if (!_entries.TryGetValue(fileId, out byte[]? value))
|
||||
|
|
@ -176,6 +202,47 @@ public sealed class RetailDatLoaderTests
|
|||
Assert.Throws<InvalidDataException>(() => animations.LoadAnimation(wrongAnimationKey));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PhysicsScriptLoader_AllowsConcurrentFirstReads()
|
||||
{
|
||||
const uint firstDid = 0x33010010u;
|
||||
const uint secondDid = 0x33010011u;
|
||||
var portal = new RawDatabase { ReadDelayMilliseconds = 40 };
|
||||
byte[] first = ProjectileVfxDatFixtures.OrdinaryPhysicsScript.ToArray();
|
||||
byte[] second = ProjectileVfxDatFixtures.OrdinaryPhysicsScript.ToArray();
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(first, firstDid);
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(second, secondDid);
|
||||
portal.Add(firstDid, first);
|
||||
portal.Add(secondDid, second);
|
||||
var loader = new RetailPhysicsScriptLoader(portal);
|
||||
|
||||
await Task.WhenAll(
|
||||
Task.Run(() => loader.LoadPhysicsScript(firstDid)),
|
||||
Task.Run(() => loader.LoadPhysicsScript(secondDid)));
|
||||
|
||||
Assert.Equal(2, portal.MaxConcurrentReads);
|
||||
Assert.Equal(2, portal.TotalReads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PhysicsScriptLoader_CoalescesConcurrentReadsForTheSameDid()
|
||||
{
|
||||
const uint scriptDid = 0x33010012u;
|
||||
var portal = new RawDatabase { ReadDelayMilliseconds = 40 };
|
||||
byte[] bytes = ProjectileVfxDatFixtures.OrdinaryPhysicsScript.ToArray();
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(bytes, scriptDid);
|
||||
portal.Add(scriptDid, bytes);
|
||||
var loader = new RetailPhysicsScriptLoader(portal);
|
||||
|
||||
DatPhysicsScript?[] loaded = await Task.WhenAll(
|
||||
Task.Run(() => loader.LoadPhysicsScript(scriptDid)),
|
||||
Task.Run(() => loader.LoadPhysicsScript(scriptDid)));
|
||||
|
||||
Assert.All(loaded, Assert.NotNull);
|
||||
Assert.Same(loaded[0], loaded[1]);
|
||||
Assert.Equal(1, portal.TotalReads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parsers_RejectTrailingOrTruncatedEntries()
|
||||
{
|
||||
|
|
@ -192,6 +259,17 @@ public sealed class RetailDatLoaderTests
|
|||
RetailAnimationLoader.Parse(animation.Concat(new byte[] { 0 }).ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhysicsScriptParserRejectsNonFiniteHookTime()
|
||||
{
|
||||
byte[] script = ProjectileVfxDatFixtures.OrdinaryPhysicsScript.ToArray();
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian(
|
||||
script.AsSpan(8, 8),
|
||||
BitConverter.DoubleToInt64Bits(double.NaN));
|
||||
|
||||
Assert.Throws<InvalidDataException>(() => RetailPhysicsScriptLoader.Parse(script));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parsers_RejectUnsupportedHookTypesWithoutReturningPartialObjects()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue