feat(scoreboard): a master on/off switch that also stops the polling
Creator: "even if it's not visible, we want to avoid polling." ScoreboardEnabled is the layer's master switch in the properties panel. Off means two things: - the strip frame is null in every scene (per-scene eye toggles stay independent), and - the cadence policy returns None at every phase — a scoreboard nobody can see must not spend quota. The switch is persisted (Scoreboard.Enabled), because one that resets would quietly resume polling. The polling gate is ScoreboardPollsEnabled: master switch AND at least one scene with a visible scoreboard — so hiding it in all five scenes also stops the polls. Guards: ScoreboardCadenceTests adds the switch off across live/test/recording; ScoreboardProperties pins the checkbox and the VM surface.
This commit is contained in:
@@ -453,6 +453,9 @@
|
||||
<!-- Scoreboard config (creator 2026-10-30): the ego wall's billboard —
|
||||
which eight facts show, and how the strip is painted. -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsScoreboard, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<CheckBox Style="{StaticResource YtCheckBox}" Margin="0,0,0,4"
|
||||
Content="Show the scoreboard" IsChecked="{Binding ScoreboardEnabled, Mode=TwoWay}"
|
||||
ToolTip="Off hides the strip in every scene AND stops the channel polling."/>
|
||||
<TextBlock Text="Facts" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
||||
<WrapPanel Margin="10,0,0,0">
|
||||
<CheckBox Style="{StaticResource YtCheckBox}" Content="Subscribers" Margin="0,0,10,4"
|
||||
|
||||
@@ -29,8 +29,13 @@ public static class ScoreboardCadence
|
||||
public static readonly TimeSpan AnalyticsInterval = TimeSpan.FromMinutes(30);
|
||||
public static readonly TimeSpan SubscriberInterval = TimeSpan.FromSeconds(60);
|
||||
|
||||
public static ScoreboardPoll Due(SessionPhase phase, DateTime lastAnalyticsUtc, DateTime lastSubscriberUtc, DateTime nowUtc)
|
||||
/// <summary><paramref name="enabled"/> is the layer's master switch. Off means NOTHING is
|
||||
/// polled, whatever the session is doing — a hidden scoreboard has no reason to spend quota
|
||||
/// (creator ruling 2026-10-30).</summary>
|
||||
public static ScoreboardPoll Due(SessionPhase phase, DateTime lastAnalyticsUtc, DateTime lastSubscriberUtc,
|
||||
DateTime nowUtc, bool enabled = true)
|
||||
{
|
||||
if (!enabled) return ScoreboardPoll.None;
|
||||
if (phase == SessionPhase.Idle) return ScoreboardPoll.None;
|
||||
|
||||
var analyticsDue = phase == SessionPhase.Live && nowUtc - lastAnalyticsUtc >= AnalyticsInterval;
|
||||
|
||||
@@ -51,6 +51,12 @@ public partial class LayoutStore : IDisposable
|
||||
|
||||
public void SaveMonoOutput(bool enabled) => UpsertSetting("Audio.MonoOutput", enabled ? "1" : "0");
|
||||
|
||||
/// <summary>Scoreboard master switch (creator 2026-10-30). Persisted because "off" also means
|
||||
/// "stop polling" — a switch that resets would quietly resume spending quota.</summary>
|
||||
public bool LoadScoreboardEnabled() => GetSetting("Scoreboard.Enabled") != "0";
|
||||
|
||||
public void SaveScoreboardEnabled(bool enabled) => UpsertSetting("Scoreboard.Enabled", enabled ? "1" : "0");
|
||||
|
||||
/// <summary>The user's chosen record output folder (TASK 18), or null to use the
|
||||
/// default <c>%APPDATA%\ytLlive\recordings</c>. Restored at startup.</summary>
|
||||
public string? LoadRecordFolder()
|
||||
|
||||
@@ -28,6 +28,28 @@ public partial class MainViewModel
|
||||
|
||||
internal ScoreboardRecorder ScoreboardRecorder => _scoreboard;
|
||||
|
||||
private bool _scoreboardEnabled = true;
|
||||
|
||||
/// <summary>The scoreboard's master switch. Off hides the strip in EVERY scene (the per-scene
|
||||
/// eye stays independent) and — the point of the switch — stops all polling: a scoreboard nobody
|
||||
/// can see must not spend quota.</summary>
|
||||
public bool ScoreboardEnabled
|
||||
{
|
||||
get => _scoreboardEnabled;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _scoreboardEnabled, value)) return;
|
||||
_layoutStore.SaveScoreboardEnabled(value);
|
||||
RefreshScoreboardPreviews();
|
||||
OnPropertyChanged(nameof(ScoreboardPollsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>What the cadence policy asks: the master switch AND at least one scene showing it.</summary>
|
||||
internal bool ScoreboardPollsEnabled =>
|
||||
_scoreboardEnabled && Scenes.Any(scene =>
|
||||
scene.Elements.OfType<Source>().Any(s => s.IsScoreboard && s.IsVisible && s.IsEnabled));
|
||||
|
||||
internal ScoreboardTiles ScoreboardTilesState => _scoreboardTiles;
|
||||
|
||||
/// <summary>Composited strip frame for the output path (cached by the presenter).</summary>
|
||||
@@ -146,7 +168,7 @@ public partial class MainViewModel
|
||||
/// must never touch a WriteableBitmap, so this runs on the UI thread only.</summary>
|
||||
private void RefreshScoreboardPreviews()
|
||||
{
|
||||
var frame = _scoreboardPresenter?.Frame;
|
||||
var frame = _scoreboardEnabled ? _scoreboardPresenter?.Frame : null;
|
||||
foreach (var scene in Scenes)
|
||||
foreach (var source in scene.Elements.OfType<Source>().Where(s => s.IsScoreboard))
|
||||
{
|
||||
|
||||
@@ -279,6 +279,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
MonoOutput = _layoutStore.LoadMonoOutput(); // applied to the mixer by the setter
|
||||
_musicPlayer.UserGain = (float)Helpers.GainDb.FromDb(TraxDefaultDb); // background by default
|
||||
InitScoreboard();
|
||||
ScoreboardEnabled = _layoutStore.LoadScoreboardEnabled();
|
||||
try
|
||||
{
|
||||
var s = _layoutStore.GetSetting("ZoomShape");
|
||||
|
||||
@@ -32,6 +32,11 @@ public class ScoreboardCadenceTests
|
||||
Assert.Equal(ScoreboardPoll.None,
|
||||
ScoreboardCadence.Due(SessionPhase.Live, old, now.AddSeconds(-10), now));
|
||||
|
||||
// The master switch beats every other rule: off means silent, whatever the session is.
|
||||
Assert.Equal(ScoreboardPoll.None, ScoreboardCadence.Due(SessionPhase.Live, old, old, now, enabled: false));
|
||||
Assert.Equal(ScoreboardPoll.None, ScoreboardCadence.Due(SessionPhase.Test, old, old, now, enabled: false));
|
||||
Assert.Equal(ScoreboardPoll.None, ScoreboardCadence.Due(SessionPhase.Recording, old, old, now, enabled: false));
|
||||
|
||||
// Subscribers alone mid-window.
|
||||
Assert.Equal(ScoreboardPoll.SubscribersOnly,
|
||||
ScoreboardCadence.Due(SessionPhase.Live, now, old, now));
|
||||
|
||||
@@ -17,6 +17,9 @@ public class ScoreboardPropertiesTests
|
||||
// Gated on the selected layer being a scoreboard.
|
||||
Assert.Contains("SelectedElement.IsScoreboard", panel);
|
||||
|
||||
// The master switch sits in the same panel: off hides the strip and stops the polling.
|
||||
Assert.Contains("ScoreboardEnabled", panel);
|
||||
|
||||
// The eight fact switches.
|
||||
foreach (var binding in new[]
|
||||
{
|
||||
@@ -48,6 +51,7 @@ public class ScoreboardPropertiesTests
|
||||
{
|
||||
"ScoreboardTextColor", "ScoreboardBackgroundColor", "ScoreboardBackgroundOpacity",
|
||||
"ScoreboardFontSize", "ScoreboardColorOptions", "ScoreboardShowSubscribers",
|
||||
"ScoreboardEnabled",
|
||||
})
|
||||
Assert.NotNull(type.GetProperty(name));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user