refactor(streaming): extract landblock build factory
This commit is contained in:
parent
e4a9664f09
commit
5af101b2f1
4 changed files with 1361 additions and 794 deletions
509
tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs
Normal file
509
tests/AcDream.App.Tests/Streaming/LandblockBuildFactoryTests.cs
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
using System.Reflection;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class LandblockBuildFactoryTests
|
||||
{
|
||||
private const uint LandblockId = 0xA9B4FFFFu;
|
||||
private const uint AdjacentLandblockId = 0xA9B5FFFFu;
|
||||
private static readonly LandblockBuildOrigin HoltburgOrigin = new(0xA9, 0xB4);
|
||||
|
||||
[Fact]
|
||||
public void BuildFar_ReturnsOnlyHeightmapAndCapturedOrigin()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
var heightmap = new LandBlock { Id = LandblockId };
|
||||
proxy.Add(LandblockId, heightmap);
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadFar));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Same(heightmap, result.Landblock.Heightmap);
|
||||
Assert.Empty(result.Landblock.Entities);
|
||||
Assert.Same(PhysicsDatBundle.Empty, result.Landblock.PhysicsDats);
|
||||
Assert.Null(result.EnvCells);
|
||||
Assert.Equal(HoltburgOrigin, result.Origin);
|
||||
Assert.Equal([(typeof(LandBlock), LandblockId)], proxy.Reads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_RejectsUnspecifiedOriginBeforeReadingDat()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
Assert.Throws<ArgumentException>(() => factory.Build(
|
||||
new LandblockBuildRequest(
|
||||
LandblockId,
|
||||
LandblockStreamJobKind.LoadFar,
|
||||
Generation: 1,
|
||||
Origin: default)));
|
||||
Assert.Empty(proxy.Reads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_MissingHeightmapReturnsNullWithoutPartialPayload()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadFar));
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.Equal([(typeof(LandBlock), LandblockId)], proxy.Reads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildNear_MissingLandblockInfoReturnsCompleteEmptyNearPayload()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadNear));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Landblock.Entities);
|
||||
var envCells = Assert.IsType<AcDream.App.Rendering.Wb.EnvCellLandblockBuild>(
|
||||
result.EnvCells);
|
||||
Assert.Empty(envCells.VisibilityCells);
|
||||
Assert.Empty(envCells.Shells);
|
||||
var physics = Assert.IsType<PhysicsDatBundle>(result.Landblock.PhysicsDats);
|
||||
Assert.Null(physics.Info);
|
||||
Assert.Empty(physics.EnvCells);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildNear_MissingEnvCellSkipsOnlyThatCellWithoutPartialVisibilityEntry()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
AddNearFixture(proxy, LandblockId, environmentId: 1, cellCount: 2);
|
||||
uint missingCell = (LandblockId & 0xFFFF0000u) | 0x0101u;
|
||||
proxy.Remove<EnvCell>(missingCell);
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
LandblockBuild? result = factory.Build(Request(LandblockStreamJobKind.LoadNear));
|
||||
|
||||
Assert.NotNull(result);
|
||||
var envCells = Assert.IsType<AcDream.App.Rendering.Wb.EnvCellLandblockBuild>(
|
||||
result.EnvCells);
|
||||
Assert.Equal(
|
||||
[(LandblockId & 0xFFFF0000u) | 0x0100u],
|
||||
envCells.VisibilityCells.Select(cell => cell.CellId));
|
||||
var physics = Assert.IsType<PhysicsDatBundle>(result.Landblock.PhysicsDats);
|
||||
Assert.Single(physics.EnvCells);
|
||||
Assert.DoesNotContain(missingCell, physics.EnvCells.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_SerializesCompleteNearTransactionsOnSharedGate()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
AddNearFixture(proxy, LandblockId, environmentId: 1);
|
||||
AddNearFixture(proxy, AdjacentLandblockId, environmentId: 2);
|
||||
object gate = new();
|
||||
var factory = Factory(dat, gate);
|
||||
|
||||
Assert.NotNull(factory.Build(Request(
|
||||
LandblockId,
|
||||
LandblockStreamJobKind.LoadNear,
|
||||
generation: 1,
|
||||
HoltburgOrigin)));
|
||||
(Type Type, uint Id)[] firstSequence = proxy.Reads.ToArray();
|
||||
proxy.ClearReads();
|
||||
Assert.NotNull(factory.Build(Request(
|
||||
AdjacentLandblockId,
|
||||
LandblockStreamJobKind.LoadNear,
|
||||
generation: 1,
|
||||
new LandblockBuildOrigin(0xA9, 0xB5))));
|
||||
(Type Type, uint Id)[] secondSequence = proxy.Reads.ToArray();
|
||||
proxy.ClearReads();
|
||||
proxy.ReadDelay = TimeSpan.FromMilliseconds(5);
|
||||
|
||||
Task<LandblockBuild?> first = Task.Run(() =>
|
||||
factory.Build(Request(
|
||||
LandblockId,
|
||||
LandblockStreamJobKind.LoadNear,
|
||||
generation: 2,
|
||||
HoltburgOrigin)));
|
||||
Task<LandblockBuild?> second = Task.Run(() =>
|
||||
factory.Build(Request(
|
||||
AdjacentLandblockId,
|
||||
LandblockStreamJobKind.LoadNear,
|
||||
generation: 3,
|
||||
new LandblockBuildOrigin(0xA9, 0xB5))));
|
||||
LandblockBuild?[] results = await Task.WhenAll(first, second);
|
||||
|
||||
Assert.All(results, Assert.NotNull);
|
||||
Assert.Equal(1, proxy.MaxConcurrentReads);
|
||||
(Type Type, uint Id)[] combined = proxy.Reads.ToArray();
|
||||
Assert.True(
|
||||
combined.SequenceEqual(firstSequence.Concat(secondSequence))
|
||||
|| combined.SequenceEqual(secondSequence.Concat(firstSequence)),
|
||||
"Near-build DAT reads interleaved instead of remaining one serialized transaction.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_UsesTheSuppliedSharedReaderGate()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
|
||||
object gate = new();
|
||||
var factory = Factory(dat, gate);
|
||||
using var started = new ManualResetEventSlim();
|
||||
|
||||
Monitor.Enter(gate);
|
||||
Task<LandblockBuild?> build;
|
||||
try
|
||||
{
|
||||
build = Task.Run(() =>
|
||||
{
|
||||
started.Set();
|
||||
return factory.Build(Request(LandblockStreamJobKind.LoadFar));
|
||||
});
|
||||
Assert.True(started.Wait(TimeSpan.FromSeconds(2)));
|
||||
Assert.False(proxy.ReadObserved.Wait(TimeSpan.FromMilliseconds(100)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(gate);
|
||||
}
|
||||
|
||||
Assert.NotNull(await build);
|
||||
Assert.True(proxy.ReadObserved.IsSet);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(LandblockStreamJobKind.LoadNear)]
|
||||
[InlineData(LandblockStreamJobKind.PromoteToNear)]
|
||||
public void Build_NearAndPromoteIncludeEveryValidZeroSubmeshVisibilityCell(
|
||||
LandblockStreamJobKind kind)
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
AddNearFixture(proxy, LandblockId, environmentId: 1, cellCount: 2);
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
LandblockBuild? result = factory.Build(Request(kind));
|
||||
|
||||
Assert.NotNull(result);
|
||||
var envCells = Assert.IsType<AcDream.App.Rendering.Wb.EnvCellLandblockBuild>(
|
||||
result.EnvCells);
|
||||
var physics = Assert.IsType<PhysicsDatBundle>(result.Landblock.PhysicsDats);
|
||||
Assert.Equal(
|
||||
[(LandblockId & 0xFFFF0000u) | 0x0100u, (LandblockId & 0xFFFF0000u) | 0x0101u],
|
||||
envCells.VisibilityCells.Select(cell => cell.CellId));
|
||||
Assert.Empty(envCells.Shells);
|
||||
Assert.NotNull(physics.Info);
|
||||
Assert.Equal(2, physics.EnvCells.Count);
|
||||
Assert.Single(physics.Environments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorSnapshotsTheRetailHeightTable()
|
||||
{
|
||||
var dat = CreateDat(out _);
|
||||
var heights = Enumerable.Range(0, 256).Select(value => (float)value).ToArray();
|
||||
var factory = new LandblockBuildFactory(
|
||||
dat,
|
||||
new object(),
|
||||
heights,
|
||||
new PhysicsDataCache());
|
||||
heights[42] = -1f;
|
||||
|
||||
var snapshot = Assert.IsType<float[]>(typeof(LandblockBuildFactory)
|
||||
.GetField("_heightTable", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.GetValue(factory));
|
||||
Assert.NotSame(heights, snapshot);
|
||||
Assert.Equal(42f, snapshot[42]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorRejectsIncompleteRetailHeightTable()
|
||||
{
|
||||
var dat = CreateDat(out _);
|
||||
|
||||
Assert.Throws<ArgumentException>(() => new LandblockBuildFactory(
|
||||
dat,
|
||||
new object(),
|
||||
new float[255],
|
||||
new PhysicsDataCache()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_ExceptionReleasesSharedGateForTheNextTransaction()
|
||||
{
|
||||
var dat = CreateDat(out RecordingDatProxy proxy);
|
||||
proxy.Add(LandblockId, new LandBlock { Id = LandblockId });
|
||||
proxy.ExceptionToThrow = new InvalidDataException("fixture read failure");
|
||||
var factory = Factory(dat, new object());
|
||||
|
||||
Assert.Throws<InvalidDataException>(() =>
|
||||
factory.Build(Request(LandblockStreamJobKind.LoadFar)));
|
||||
proxy.ExceptionToThrow = null;
|
||||
|
||||
Assert.NotNull(factory.Build(Request(LandblockStreamJobKind.LoadFar)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FactorySurface_HasNoRendererWorldStateOrLiveOriginDependency()
|
||||
{
|
||||
Type type = typeof(LandblockBuildFactory);
|
||||
Type[] dependencyTypes = type
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.Select(field => field.FieldType)
|
||||
.Concat(type.GetConstructors().SelectMany(ctor =>
|
||||
ctor.GetParameters().Select(parameter => parameter.ParameterType)))
|
||||
.ToArray();
|
||||
|
||||
Assert.DoesNotContain(dependencyTypes, dependency =>
|
||||
dependency.FullName?.Contains("GameWindow", StringComparison.Ordinal) == true);
|
||||
Assert.DoesNotContain(dependencyTypes, dependency =>
|
||||
dependency.FullName?.Contains("GpuWorldState", StringComparison.Ordinal) == true);
|
||||
Assert.DoesNotContain(dependencyTypes, dependency =>
|
||||
dependency.FullName?.Contains("LiveWorldOrigin", StringComparison.Ordinal) == true);
|
||||
Assert.DoesNotContain(dependencyTypes, dependency =>
|
||||
dependency.Namespace?.StartsWith("Silk.NET", StringComparison.Ordinal) == true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InstalledDatNearBuild_IsDeterministicAndClosesEntityPhysicsPayload()
|
||||
{
|
||||
string? datDirectory = ResolveDatDirectory();
|
||||
if (datDirectory is null)
|
||||
throw SkipException.ForSkip("Installed retail DATs are required.");
|
||||
|
||||
using var dat = new BoundedTestDatCollection(datDirectory);
|
||||
var bounded = (IDatReaderWriter)dat;
|
||||
Region? region = bounded.Get<Region>(0x13000000u);
|
||||
float[]? heights = region?.LandDefs.LandHeightTable;
|
||||
Assert.NotNull(heights);
|
||||
Assert.True(heights.Length >= 256);
|
||||
var factory = new LandblockBuildFactory(
|
||||
bounded,
|
||||
new object(),
|
||||
heights,
|
||||
new PhysicsDataCache());
|
||||
LandblockBuildRequest request = Request(LandblockStreamJobKind.LoadNear, generation: 4);
|
||||
|
||||
LandblockBuild? first = factory.Build(request);
|
||||
LandblockBuild? second = factory.Build(request);
|
||||
|
||||
Assert.NotNull(first);
|
||||
Assert.NotNull(second);
|
||||
Assert.Equal(HoltburgOrigin, first.Origin);
|
||||
Assert.NotNull(first.EnvCells);
|
||||
Assert.NotEmpty(first.Landblock.Entities);
|
||||
AssertEntityBuildsEqual(first.Landblock.Entities, second.Landblock.Entities);
|
||||
Assert.Equal(
|
||||
first.EnvCells.VisibilityCells.Select(cell => cell.CellId),
|
||||
second.EnvCells!.VisibilityCells.Select(cell => cell.CellId));
|
||||
Assert.Equal(
|
||||
first.EnvCells.Shells.Select(shell => shell.GeometryId),
|
||||
second.EnvCells.Shells.Select(shell => shell.GeometryId));
|
||||
|
||||
LandBlockInfo? info = bounded.Get<LandBlockInfo>(
|
||||
(LandblockId & 0xFFFF0000u) | 0xFFFEu);
|
||||
Assert.NotNull(info);
|
||||
var expectedVisibilityCells = new List<uint>();
|
||||
uint firstCellId = (LandblockId & 0xFFFF0000u) | 0x0100u;
|
||||
for (uint offset = 0; offset < info.NumCells; offset++)
|
||||
{
|
||||
uint cellId = firstCellId + offset;
|
||||
EnvCell? cell = bounded.Get<EnvCell>(cellId);
|
||||
if (cell is null || cell.EnvironmentId == 0)
|
||||
continue;
|
||||
DatReaderWriter.DBObjs.Environment? environment =
|
||||
bounded.Get<DatReaderWriter.DBObjs.Environment>(
|
||||
0x0D000000u | cell.EnvironmentId);
|
||||
if (environment?.Cells.ContainsKey(cell.CellStructure) == true)
|
||||
expectedVisibilityCells.Add(cellId);
|
||||
}
|
||||
Assert.Equal(
|
||||
expectedVisibilityCells,
|
||||
first.EnvCells.VisibilityCells.Select(cell => cell.CellId));
|
||||
|
||||
PhysicsDatBundle physics = Assert.IsType<PhysicsDatBundle>(
|
||||
first.Landblock.PhysicsDats);
|
||||
Assert.NotNull(physics.Info);
|
||||
foreach (WorldEntity entity in first.Landblock.Entities)
|
||||
{
|
||||
if ((entity.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
|
||||
Assert.Contains(entity.SourceGfxObjOrSetupId, physics.Setups.Keys);
|
||||
foreach (MeshRef mesh in entity.MeshRefs)
|
||||
Assert.Contains(mesh.GfxObjId, physics.GfxObjs.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
private static LandblockBuildFactory Factory(IDatReaderWriter dat, object gate) =>
|
||||
new(dat, gate, new float[256], new PhysicsDataCache());
|
||||
|
||||
private static LandblockBuildRequest Request(
|
||||
LandblockStreamJobKind kind,
|
||||
ulong generation = 1) =>
|
||||
new(LandblockId, kind, generation, HoltburgOrigin);
|
||||
|
||||
private static LandblockBuildRequest Request(
|
||||
uint landblockId,
|
||||
LandblockStreamJobKind kind,
|
||||
ulong generation,
|
||||
LandblockBuildOrigin origin) =>
|
||||
new(landblockId, kind, generation, origin);
|
||||
|
||||
private static void AddNearFixture(
|
||||
RecordingDatProxy proxy,
|
||||
uint landblockId,
|
||||
ushort environmentId,
|
||||
uint cellCount = 1)
|
||||
{
|
||||
proxy.Add(landblockId, new LandBlock { Id = landblockId });
|
||||
proxy.Add(
|
||||
(landblockId & 0xFFFF0000u) | 0xFFFEu,
|
||||
new LandBlockInfo { NumCells = cellCount });
|
||||
|
||||
var environment = new DatReaderWriter.DBObjs.Environment
|
||||
{
|
||||
Id = 0x0D000000u | environmentId,
|
||||
};
|
||||
for (uint offset = 0; offset < cellCount; offset++)
|
||||
{
|
||||
ushort structure = checked((ushort)(offset + 1));
|
||||
uint cellId = (landblockId & 0xFFFF0000u) | (0x0100u + offset);
|
||||
proxy.Add(cellId, new EnvCell
|
||||
{
|
||||
Id = cellId,
|
||||
EnvironmentId = environmentId,
|
||||
CellStructure = structure,
|
||||
Position = new Frame
|
||||
{
|
||||
Orientation = Quaternion.Identity,
|
||||
},
|
||||
});
|
||||
environment.Cells[structure] = EmptyCellStruct();
|
||||
}
|
||||
proxy.Add(environment.Id, environment);
|
||||
}
|
||||
|
||||
private static CellStruct EmptyCellStruct() => new()
|
||||
{
|
||||
VertexArray = new VertexArray
|
||||
{
|
||||
Vertices = new Dictionary<ushort, SWVertex>(),
|
||||
},
|
||||
};
|
||||
|
||||
private static IDatReaderWriter CreateDat(out RecordingDatProxy proxy)
|
||||
{
|
||||
IDatReaderWriter dat = DispatchProxy.Create<IDatReaderWriter, RecordingDatProxy>();
|
||||
proxy = (RecordingDatProxy)(object)dat;
|
||||
return dat;
|
||||
}
|
||||
|
||||
private static void AssertEntityBuildsEqual(
|
||||
IReadOnlyList<WorldEntity> expected,
|
||||
IReadOnlyList<WorldEntity> actual)
|
||||
{
|
||||
Assert.Equal(expected.Count, actual.Count);
|
||||
for (int i = 0; i < expected.Count; i++)
|
||||
{
|
||||
WorldEntity left = expected[i];
|
||||
WorldEntity right = actual[i];
|
||||
Assert.Equal(left.Id, right.Id);
|
||||
Assert.Equal(left.SourceGfxObjOrSetupId, right.SourceGfxObjOrSetupId);
|
||||
Assert.Equal(left.Position, right.Position);
|
||||
Assert.Equal(left.Rotation, right.Rotation);
|
||||
Assert.Equal(left.ParentCellId, right.ParentCellId);
|
||||
Assert.Equal(left.EffectCellId, right.EffectCellId);
|
||||
Assert.Equal(left.MeshRefs.Count, right.MeshRefs.Count);
|
||||
for (int part = 0; part < left.MeshRefs.Count; part++)
|
||||
Assert.Equal(left.MeshRefs[part], right.MeshRefs[part]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ResolveDatDirectory()
|
||||
{
|
||||
string? configured = System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
if (!string.IsNullOrWhiteSpace(configured) && Directory.Exists(configured))
|
||||
return configured;
|
||||
const string installed = @"C:\Turbine\Asheron's Call";
|
||||
if (Directory.Exists(installed))
|
||||
return installed;
|
||||
string documents = Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call");
|
||||
return Directory.Exists(documents) ? documents : null;
|
||||
}
|
||||
|
||||
private class RecordingDatProxy : DispatchProxy
|
||||
{
|
||||
private readonly Dictionary<(Type Type, uint Id), object> _objects = new();
|
||||
private readonly object _gate = new();
|
||||
private int _activeReads;
|
||||
|
||||
internal List<(Type Type, uint Id)> Reads { get; } = new();
|
||||
internal int MaxConcurrentReads { get; private set; }
|
||||
internal TimeSpan ReadDelay { get; set; }
|
||||
internal Exception? ExceptionToThrow { get; set; }
|
||||
internal ManualResetEventSlim ReadObserved { get; } = new();
|
||||
|
||||
internal void Add<T>(uint id, T value) where T : class =>
|
||||
_objects[(typeof(T), id)] = value;
|
||||
|
||||
internal void Remove<T>(uint id) where T : class =>
|
||||
_objects.Remove((typeof(T), id));
|
||||
|
||||
internal void ClearReads()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
Reads.Clear();
|
||||
MaxConcurrentReads = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
|
||||
{
|
||||
if (targetMethod?.Name == "Get" && args is [uint id])
|
||||
{
|
||||
Type type = targetMethod.ReturnType;
|
||||
int active = Interlocked.Increment(ref _activeReads);
|
||||
ReadObserved.Set();
|
||||
lock (_gate)
|
||||
{
|
||||
Reads.Add((type, id));
|
||||
MaxConcurrentReads = Math.Max(MaxConcurrentReads, active);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ReadDelay > TimeSpan.Zero)
|
||||
Thread.Sleep(ReadDelay);
|
||||
if (ExceptionToThrow is { } error)
|
||||
throw error;
|
||||
return _objects.TryGetValue((type, id), out object? value)
|
||||
? value
|
||||
: null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref _activeReads);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetMethod?.ReturnType == typeof(void))
|
||||
return null;
|
||||
if (targetMethod?.ReturnType.IsValueType == true)
|
||||
return Activator.CreateInstance(targetMethod.ReturnType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -232,27 +232,45 @@ public sealed class LandblockBuildOriginTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindowBuildAndPublication_UseCapturedOriginInsteadOfLiveCenter()
|
||||
public void BuildFactoryAndGameWindowPublication_UseCapturedOriginInsteadOfLiveCenter()
|
||||
{
|
||||
string root = FindRepoRoot();
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
string gameWindowSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string buildSection = Slice(
|
||||
source,
|
||||
"private AcDream.App.Streaming.LandblockBuild? BuildLandblockForStreaming(",
|
||||
"private void AdvanceLandblockPresentationRetirement(");
|
||||
string buildSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Streaming",
|
||||
"LandblockBuildFactory.cs"));
|
||||
string publicationSection = Slice(
|
||||
source,
|
||||
gameWindowSource,
|
||||
"private void ApplyLoadedTerrainLocked(",
|
||||
"private void OnUpdate(double dt)");
|
||||
|
||||
Assert.Contains("request.Origin", buildSection, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveCenterX", buildSection, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveCenterY", buildSection, StringComparison.Ordinal);
|
||||
Assert.Contains("request.Origin", buildSource, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveCenterX", buildSource, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveCenterY", buildSource, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"BuildLandblockForStreaming",
|
||||
gameWindowSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"BuildSceneryEntitiesForStreaming",
|
||||
gameWindowSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"BuildInteriorEntitiesForStreaming",
|
||||
gameWindowSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"BuildPhysicsDatBundle",
|
||||
gameWindowSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("build.Origin.CenterX", publicationSection, StringComparison.Ordinal);
|
||||
Assert.Contains("build.Origin.CenterY", publicationSection, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_liveCenterX", publicationSection, StringComparison.Ordinal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue