Move pre-window loading, startup application, live settings mutation, toon context, quality reapply, and SettingsVM loans behind one RuntimeSettingsController. Preserve retail command behavior, ordered target publication, draft semantics, and retryable failure convergence while removing duplicate GameWindow state and feature bodies. Co-authored-by: Codex <codex@openai.com>
255 lines
9 KiB
C#
255 lines
9 KiB
C#
using System.Reflection;
|
|
using AcDream.App.Rendering;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class PrivatePresentationRendererTests
|
|
{
|
|
private static readonly RenderFrameInput Input = new(0.125d, 1600, 900);
|
|
private static readonly WorldRenderFrameOutcome World = new(3, 12, true);
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(true, false)]
|
|
[InlineData(false, true)]
|
|
[InlineData(true, true)]
|
|
public void Render_PreservesPortalPaperdollGameplayDevToolsScreenshotOrder(
|
|
bool portalVisible,
|
|
bool screenshotCaptured)
|
|
{
|
|
List<string> calls = [];
|
|
var portal = new Portal(calls);
|
|
var paperdoll = new Paperdoll(calls);
|
|
var gameplay = new GameplayUi(calls);
|
|
var devTools = new DevTools(calls);
|
|
var screenshots = new Screenshots(calls, screenshotCaptured);
|
|
var presentation = new PrivatePresentationRenderer(
|
|
portal,
|
|
new FoundationSource(portalVisible),
|
|
paperdoll,
|
|
gameplay,
|
|
devTools,
|
|
screenshots);
|
|
|
|
PrivatePresentationFrameOutcome outcome = presentation.Render(Input, World);
|
|
|
|
Assert.Equal(
|
|
["portal", "paperdoll", "gameplay-ui", "devtools-render", "screenshot"],
|
|
calls);
|
|
Assert.Equal(new PrivatePresentationFrameOutcome(
|
|
portalVisible,
|
|
screenshotCaptured), outcome);
|
|
Assert.Equal((Input.ViewportWidth, Input.ViewportHeight), portal.Size);
|
|
Assert.Equal((Input.ViewportWidth, Input.ViewportHeight), screenshots.Size);
|
|
Assert.Equal(Input, gameplay.Input);
|
|
Assert.Equal(Input, devTools.Input);
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_OptionalLayersMayBeAbsentButPortalOutcomeRemainsObserved()
|
|
{
|
|
List<string> calls = [];
|
|
var presentation = new PrivatePresentationRenderer(
|
|
new Portal(calls),
|
|
new FoundationSource(portalVisible: true),
|
|
paperdoll: null,
|
|
gameplayUi: null,
|
|
devTools: null,
|
|
screenshots: null);
|
|
|
|
PrivatePresentationFrameOutcome outcome = presentation.Render(Input, World);
|
|
|
|
Assert.Equal(["portal"], calls);
|
|
Assert.True(outcome.PortalViewportDrawn);
|
|
Assert.False(outcome.ScreenshotCaptured);
|
|
}
|
|
|
|
[Fact]
|
|
public void Render_ReportsThePreparedPortalSnapshotWhenDrawMutatesTheSource()
|
|
{
|
|
List<string> calls = [];
|
|
var foundation = new MutableFoundationSource(portalVisible: true);
|
|
var presentation = new PrivatePresentationRenderer(
|
|
new MutatingPortal(calls, () => foundation.PortalVisible = false),
|
|
foundation,
|
|
paperdoll: null,
|
|
gameplayUi: null,
|
|
devTools: null,
|
|
screenshots: null);
|
|
|
|
PrivatePresentationFrameOutcome outcome = presentation.Render(Input, World);
|
|
|
|
Assert.Equal(["portal"], calls);
|
|
Assert.False(foundation.PortalVisible);
|
|
Assert.True(outcome.PortalViewportDrawn);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_RequiresTheCanonicalPortalOwner()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new PrivatePresentationRenderer(null!, new FoundationSource(false), null, null, null, null));
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
new PrivatePresentationRenderer(new Portal([]), null!, null, null, null, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void ExtractedPresentationOwner_RetainsNoDirectWindowOrDelegate()
|
|
{
|
|
FieldInfo[] fields = typeof(PrivatePresentationRenderer).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
[Fact]
|
|
public void RetainedUiAdapter_UsesTheDocumentedRuntimeBindingBoundary()
|
|
{
|
|
FieldInfo[] adapterFields = typeof(RetainedGameplayUiFrame).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
Assert.Equal(
|
|
[
|
|
typeof(AcDream.App.UI.RetailUiRuntime),
|
|
typeof(Silk.NET.Input.IInputContext),
|
|
],
|
|
adapterFields.Select(field => field.FieldType).OrderBy(type => type.FullName));
|
|
Assert.DoesNotContain(
|
|
adapterFields,
|
|
field => field.FieldType == typeof(GameWindow)
|
|
|| typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
|
|
FieldInfo bindings = typeof(AcDream.App.UI.RetailUiRuntime).GetField(
|
|
"_bindings",
|
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
|
Assert.Equal(typeof(AcDream.App.UI.RetailUiRuntimeBindings), bindings.FieldType);
|
|
Assert.Contains(
|
|
typeof(AcDream.App.UI.RetailUiRuntimeBindings).GetProperties(),
|
|
property => property.PropertyType.Name.EndsWith("Bindings", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void KnownSharedOwnerPaths_ArePinnedWithoutClaimingRecursivePurity()
|
|
{
|
|
static Type[] FieldTypes(Type owner) => owner.GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic)
|
|
.Select(field => field.FieldType)
|
|
.ToArray();
|
|
|
|
Assert.Contains(
|
|
typeof(AcDream.App.UI.RetailUiRuntime),
|
|
FieldTypes(typeof(RetainedGameplayUiFrame)));
|
|
Assert.Contains(
|
|
typeof(AcDream.App.UI.UiViewport),
|
|
FieldTypes(typeof(RetailPaperdollFrameView)));
|
|
Assert.Contains(
|
|
typeof(AcDream.App.UI.UiElement),
|
|
FieldTypes(typeof(PaperdollInventoryVisibility)));
|
|
Assert.Contains(
|
|
typeof(AcDream.UI.Abstractions.Panels.Debug.DebugVM),
|
|
FieldTypes(typeof(DevToolsPanelSet)));
|
|
Assert.Contains(
|
|
typeof(AcDream.App.Settings.IRuntimeSettingsPreviewSource),
|
|
FieldTypes(typeof(RuntimeWorldFrameSettingsPreview)));
|
|
Assert.Contains(
|
|
typeof(AcDream.App.Streaming.LocalPlayerTeleportController),
|
|
FieldTypes(typeof(LocalPlayerPortalViewport)));
|
|
Assert.Contains(
|
|
typeof(AcDream.App.Streaming.ILocalPlayerTeleportInputLifetime),
|
|
FieldTypes(typeof(AcDream.App.Streaming.LocalPlayerTeleportController)));
|
|
|
|
Assert.DoesNotContain(
|
|
FieldTypes(typeof(PrivateFrameScreenshot)),
|
|
type => typeof(Delegate).IsAssignableFrom(type));
|
|
Assert.Null(typeof(AcDream.App.Diagnostics.FrameScreenshotController).GetField(
|
|
"_getSize",
|
|
BindingFlags.Instance | BindingFlags.NonPublic));
|
|
}
|
|
|
|
private sealed class Portal(List<string> calls) : IPrivatePortalViewport
|
|
{
|
|
public (int Width, int Height) Size { get; private set; }
|
|
|
|
public void Draw(int width, int height)
|
|
{
|
|
Size = (width, height);
|
|
calls.Add("portal");
|
|
}
|
|
}
|
|
|
|
private sealed class FoundationSource(bool portalVisible) : IRenderFrameFoundationSource
|
|
{
|
|
public RenderFrameFoundation Foundation { get; } = new(
|
|
portalVisible,
|
|
default,
|
|
default);
|
|
}
|
|
|
|
private sealed class MutableFoundationSource(bool portalVisible) :
|
|
IRenderFrameFoundationSource
|
|
{
|
|
public bool PortalVisible { get; set; } = portalVisible;
|
|
|
|
public RenderFrameFoundation Foundation => new(
|
|
PortalVisible,
|
|
default,
|
|
default);
|
|
}
|
|
|
|
private sealed class MutatingPortal(List<string> calls, Action mutate) :
|
|
IPrivatePortalViewport
|
|
{
|
|
public void Draw(int width, int height)
|
|
{
|
|
calls.Add("portal");
|
|
mutate();
|
|
}
|
|
}
|
|
|
|
private sealed class Paperdoll(List<string> calls) : IPrivatePaperdollFrame
|
|
{
|
|
public void Render() => calls.Add("paperdoll");
|
|
}
|
|
|
|
private sealed class GameplayUi(List<string> calls) : IRetainedGameplayUiFrame
|
|
{
|
|
public RenderFrameInput Input { get; private set; }
|
|
|
|
public void Render(double deltaSeconds, int width, int height)
|
|
{
|
|
Input = new RenderFrameInput(deltaSeconds, width, height);
|
|
calls.Add("gameplay-ui");
|
|
}
|
|
}
|
|
|
|
private sealed class DevTools(List<string> calls) : IDevToolsFrameLifecycle
|
|
{
|
|
public RenderFrameInput Input { get; private set; }
|
|
|
|
public void BeginFrame(float deltaSeconds) => calls.Add("devtools-begin");
|
|
|
|
public void AbortFrame() => calls.Add("devtools-abort");
|
|
|
|
public void Render(double deltaSeconds, int viewportWidth, int viewportHeight)
|
|
{
|
|
Input = new RenderFrameInput(deltaSeconds, viewportWidth, viewportHeight);
|
|
calls.Add("devtools-render");
|
|
}
|
|
}
|
|
|
|
private sealed class Screenshots(List<string> calls, bool result) :
|
|
IPrivateFrameScreenshot
|
|
{
|
|
public (int Width, int Height) Size { get; private set; }
|
|
|
|
public bool CapturePending(int width, int height)
|
|
{
|
|
Size = (width, height);
|
|
calls.Add("screenshot");
|
|
return result;
|
|
}
|
|
}
|
|
}
|