fix(vfx): classify hardwareless particle emitters once
This commit is contained in:
parent
921712f412
commit
5b3fb17775
8 changed files with 374 additions and 18 deletions
|
|
@ -264,6 +264,29 @@ public sealed class ParticleHookSinkTests
|
|||
Assert.Contains($"0x{Owner:X8}", diagnostic, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingEmitterAsset_ReportsOnlyOnceAcrossManyOwners()
|
||||
{
|
||||
const uint missingId = 0x32DEAD00u;
|
||||
const uint secondOwner = Owner + 1u;
|
||||
var registry = new EmitterDescRegistry();
|
||||
var system = new ParticleSystem(registry, new Random(42));
|
||||
var poses = new MutablePoseSource();
|
||||
poses.Publish(Owner, Matrix4x4.Identity, Matrix4x4.Identity);
|
||||
poses.Publish(secondOwner, Matrix4x4.Identity, Matrix4x4.Identity);
|
||||
var sink = new ParticleHookSink(system, poses);
|
||||
var diagnostics = new List<string>();
|
||||
sink.DiagnosticSink = diagnostics.Add;
|
||||
|
||||
sink.OnHook(Owner, Vector3.Zero, Create(missingId, logicalId: 0u));
|
||||
sink.OnHook(secondOwner, Vector3.Zero, Create(missingId, logicalId: 0u));
|
||||
sink.OnHook(Owner, Vector3.Zero, Create(missingId, logicalId: 0u));
|
||||
|
||||
Assert.Empty(system.EnumerateEmitters());
|
||||
Assert.Single(diagnostics);
|
||||
Assert.Contains($"0x{missingId:X8}", diagnostics[0], StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingLivePose_HidesPresentationWithoutEndingEmitterLifetime()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Content;
|
||||
using AcDream.Core.Vfx;
|
||||
using DatReaderWriter.Lib.IO;
|
||||
using DatReaderWriter.Types;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -267,6 +269,35 @@ public sealed class ParticleSystemTests
|
|||
Assert.Same(desc, reg.Get(0x32001234u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitterDescRegistry_NegativeCachesMissingDatAndRegisterClearsFailure()
|
||||
{
|
||||
int calls = 0;
|
||||
var reg = new EmitterDescRegistry(_ =>
|
||||
{
|
||||
calls++;
|
||||
return null;
|
||||
});
|
||||
const uint emitterId = 0x3200DEADu;
|
||||
|
||||
Assert.False(reg.TryGet(emitterId, out _, out var first));
|
||||
Assert.False(reg.TryGet(emitterId, out _, out var second));
|
||||
|
||||
Assert.Equal(1, calls);
|
||||
Assert.Equal(
|
||||
EmitterDescResolutionFailureKind.MissingEmitterInfo,
|
||||
first.Kind);
|
||||
Assert.Equal(first, second);
|
||||
Assert.Equal(1, reg.FailureCount);
|
||||
|
||||
var desc = new EmitterDesc { DatId = emitterId };
|
||||
reg.Register(desc);
|
||||
Assert.True(reg.TryGet(emitterId, out var resolved, out var failure));
|
||||
Assert.Same(desc, resolved);
|
||||
Assert.Equal(EmitterDescResolutionFailureKind.None, failure.Kind);
|
||||
Assert.Equal(0, reg.FailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocalVelocity_TransformsABySpawnRotation()
|
||||
{
|
||||
|
|
@ -398,6 +429,31 @@ public sealed class ParticleSystemTests
|
|||
Assert.Equal(0u, EmitterDescRegistry.GetRetailHardwareGfxObjId(dat));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitterDescRegistry_ClassifiesAndCachesAuthoredHardwarelessEmitter()
|
||||
{
|
||||
const uint emitterId = 0x320002D6u;
|
||||
var dats = new FakeDatObjectSource();
|
||||
dats.Add(
|
||||
emitterId,
|
||||
new DatReaderWriter.DBObjs.ParticleEmitter
|
||||
{
|
||||
GfxObjId = 0x010016C9u,
|
||||
HwGfxObjId = 0u,
|
||||
});
|
||||
var reg = new EmitterDescRegistry(dats);
|
||||
|
||||
Assert.False(reg.TryGet(emitterId, out _, out var first));
|
||||
Assert.False(reg.TryGet(emitterId, out _, out var second));
|
||||
|
||||
Assert.Equal(
|
||||
EmitterDescResolutionFailureKind.InvalidHardwareGfxObjId,
|
||||
first.Kind);
|
||||
Assert.Equal(first, second);
|
||||
Assert.Equal(1, dats.GetCallCount(emitterId));
|
||||
Assert.Equal(1, reg.FailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetailParticleDegradeDistance_MatchesRetailEntrySelection()
|
||||
{
|
||||
|
|
@ -1100,4 +1156,31 @@ public sealed class ParticleSystemTests
|
|||
sys.Tick(0.01f);
|
||||
Assert.Equal(2, sys.ActiveParticleCount);
|
||||
}
|
||||
|
||||
private sealed class FakeDatObjectSource : IDatObjectSource
|
||||
{
|
||||
private readonly Dictionary<uint, IDBObj> _objects = new();
|
||||
private readonly Dictionary<uint, int> _calls = new();
|
||||
|
||||
public void Add(uint id, IDBObj value) => _objects[id] = value;
|
||||
|
||||
public int GetCallCount(uint id) =>
|
||||
_calls.TryGetValue(id, out int count) ? count : 0;
|
||||
|
||||
public T Get<T>(uint fileId) where T : IDBObj
|
||||
{
|
||||
_calls.TryGetValue(fileId, out int count);
|
||||
_calls[fileId] = count + 1;
|
||||
return _objects.TryGetValue(fileId, out IDBObj? value)
|
||||
&& value is T typed
|
||||
? typed
|
||||
: default!;
|
||||
}
|
||||
|
||||
public bool TryGet<T>(uint fileId, out T value) where T : IDBObj
|
||||
{
|
||||
value = Get<T>(fileId);
|
||||
return value is not null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue