perf(ui): retain shaped report text

This commit is contained in:
Erik 2026-07-25 05:11:12 +02:00
parent c0bec56dfe
commit f2a015be8e
11 changed files with 525 additions and 38 deletions

View file

@ -92,6 +92,46 @@ public sealed class CharacterControllerTests
text => text.EventId == CharacterController.MainTextId);
}
[Fact]
public void Stable_draws_reuse_report_until_source_or_layout_changes()
{
ImportedLayout layout = FixtureLoader.LoadCharacterInformation();
int builds = 0;
int deaths = 1;
Action? changed = null;
using CharacterInformationUiController controller =
CharacterController.Bind(
layout,
() =>
{
builds++;
return new CharacterSheet { Deaths = deaths };
},
subscribeChanged: handler =>
{
changed = handler;
return new TestSubscription();
});
UiText text = Assert.IsType<UiText>(
layout.FindElement(CharacterController.MainTextId));
IReadOnlyList<UiText.Line> first = text.LinesProvider();
IReadOnlyList<UiText.Line> second = text.LinesProvider();
Assert.Same(first, second);
Assert.Equal(1, builds);
deaths = 2;
changed!();
IReadOnlyList<UiText.Line> changedLines = text.LinesProvider();
Assert.NotSame(first, changedLines);
Assert.Equal(2, builds);
text.Width -= 24f;
IReadOnlyList<UiText.Line> resized = text.LinesProvider();
Assert.NotSame(changedLines, resized);
Assert.Equal(2, builds);
}
private static string Report(CharacterSheet sheet)
=> CharacterController.BuildReport(sheet, CharacterInfoStrings.English);
@ -101,4 +141,11 @@ public sealed class CharacterControllerTests
private static ImportedLayout FakeLayout()
=> new(new UiPanel { Width = 300f, Height = 362f },
new Dictionary<uint, UiElement>());
private sealed class TestSubscription : IDisposable
{
public void Dispose()
{
}
}
}

View file

@ -0,0 +1,79 @@
using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
public sealed class UiTextLayoutCacheTests
{
[Fact]
public void Stable_content_and_layout_borrow_same_shaped_lines()
{
var text = new UiText { Width = 120f };
int shapes = 0;
var cache = new UiTextLayoutCache<string>(
text,
(target, value) =>
{
shapes++;
return IndicatorDetailText.Shape(target, value);
},
"alpha beta gamma",
StringComparer.Ordinal);
IReadOnlyList<UiText.Line> first = cache.GetLines();
IReadOnlyList<UiText.Line> second = cache.GetLines();
Assert.Same(first, second);
Assert.Equal(1, shapes);
cache.SetValue(new string("alpha beta gamma".ToCharArray()));
Assert.Same(first, cache.GetLines());
Assert.Equal(1, shapes);
}
[Fact]
public void Content_width_and_font_inputs_invalidate_shaping()
{
var text = new UiText { Width = 120f };
int shapes = 0;
var cache = new UiTextLayoutCache<string>(
text,
(target, value) =>
{
shapes++;
return IndicatorDetailText.Shape(target, value);
},
"alpha beta gamma",
StringComparer.Ordinal);
IReadOnlyList<UiText.Line> first = cache.GetLines();
cache.SetValue("delta epsilon");
IReadOnlyList<UiText.Line> changed = cache.GetLines();
text.Width = 48f;
IReadOnlyList<UiText.Line> resized = cache.GetLines();
Assert.NotSame(first, changed);
Assert.NotSame(changed, resized);
Assert.Equal(3, shapes);
}
[Fact]
public void Stable_provider_poll_allocates_no_managed_memory()
{
var text = new UiText { Width = 120f };
var cache = new UiTextLayoutCache<string>(
text,
static (target, value) => IndicatorDetailText.Shape(target, value),
"alpha beta gamma",
StringComparer.Ordinal);
Func<IReadOnlyList<UiText.Line>> provider = cache.Provider;
_ = provider();
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 1_000; i++)
_ = provider();
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.Equal(0L, allocated);
}
}