Creator, plainly: "the entire point of a ticker is to tick… so, tick." The static strip was my reading of an earlier exchange; it is gone. How it ticks without becoming the compositor's most expensive layer: - `ScoreboardRenderer.RenderRun` rasterises the run ONCE per value change at its natural width plus a gap. - `ScoreboardPresenter` crops a 1920-wide window at a moving offset each tick — a row-wise byte copy, no text layout, no re-raster. - `TickScoreboard` runs on the existing 30 Hz meter clock and only while the strip is shown, so a hidden or disabled scoreboard costs nothing. The dev simulation uses the same path, so what is previewed is what ships. Test updated to the real contract: no tick → identical pixels; a tick → different pixels; a value change → re-rasterised run. (The old test asserted frame-instance identity, which a cropped window can never satisfy.)
74 lines
2.9 KiB
C#
74 lines
2.9 KiB
C#
using System.Linq;
|
|
using System.Threading;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.Services.Compositor;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>The strip's cache is a performance rule with teeth: it must rebuild when a displayed
|
|
/// value moves and must NOT rebuild otherwise, or the compositor pays for a raster per tick into
|
|
/// pixels that did not change.</summary>
|
|
public class ScoreboardPresenterTests
|
|
{
|
|
private static ChatMessage Member(string name) => new() { Kind = ChatEventKind.NewMember, AuthorName = name };
|
|
|
|
[Fact]
|
|
public void The_strip_rebuilds_only_when_a_displayed_value_moves()
|
|
{
|
|
System.Exception? failure = null;
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
var recorder = new ScoreboardRecorder();
|
|
var tiles = new ScoreboardTiles
|
|
{
|
|
SuperChats = false, WatchHours = false, Subscribers = false,
|
|
NewMembers = true, Milestones = false, LastMember = true,
|
|
LastSuperChat = false, LastGift = false,
|
|
};
|
|
var presenter = new ScoreboardPresenter(recorder, new ScoreboardStyle());
|
|
presenter.SetTiles(tiles);
|
|
|
|
// Before a session: nothing counted, nothing to draw.
|
|
Assert.Null(presenter.Frame);
|
|
|
|
recorder.StartSession();
|
|
recorder.Record(Member("First"));
|
|
var first = presenter.Frame;
|
|
Assert.NotNull(first);
|
|
|
|
// No tick, no change: the same pixels come back (the run is cached).
|
|
Assert.Equal(first!.BgraPixels, presenter.Frame!.BgraPixels);
|
|
|
|
// A TICK moves the window: the pixels must differ.
|
|
presenter.Advance(0.5);
|
|
var ticked = presenter.Frame;
|
|
Assert.NotNull(ticked);
|
|
Assert.NotEqual(first.BgraPixels, ticked!.BgraPixels);
|
|
|
|
// A visible change re-rasterises the run.
|
|
recorder.Record(Member("Second"));
|
|
var second = presenter.Frame;
|
|
Assert.NotNull(second);
|
|
Assert.NotEqual(ticked.BgraPixels, second!.BgraPixels);
|
|
|
|
// A gift-recipient event changes nothing displayed: same window, same pixels.
|
|
var beforeGift = presenter.Frame!.BgraPixels.ToArray();
|
|
recorder.Record(new ChatMessage { Kind = ChatEventKind.GiftReceived, AuthorName = "Recipient" });
|
|
Assert.Equal(beforeGift, presenter.Frame!.BgraPixels);
|
|
|
|
// Disabling every tile returns no frame, and the layer draws nothing.
|
|
presenter.SetTiles(ScoreboardTiles.None);
|
|
Assert.Null(presenter.Frame);
|
|
}
|
|
catch (System.Exception ex) { failure = ex; }
|
|
});
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
thread.Join();
|
|
Assert.Null(failure);
|
|
}
|
|
}
|