using AcDream.App.Audio; using AcDream.App.Rendering; using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.App.UI; using AcDream.UI.Abstractions.Panels.Settings; using AcDream.UI.Abstractions.Settings; using Silk.NET.Maths; using Silk.NET.Windowing; namespace AcDream.App.Settings; internal interface IRuntimeDisplayWindowTarget { void Apply(DisplaySettings display); } internal interface IRuntimeQualityApplicationTarget { void SetAlphaToCoverage(bool enabled); void SetAnisotropic(int level); void PublishRenderRange(int nearRadius, int farRadius); void ReconfigureStreamingRadii(int nearRadius, int farRadius); void SetCompletionBudget(int maxCompletionsPerFrame); } internal interface IRuntimeUiLockTarget { void Apply(bool locked); } internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarget { private readonly IWindow _window; public SilkRuntimeDisplayWindowTarget(IWindow window) { _window = window ?? throw new ArgumentNullException(nameof(window)); } public void Apply(DisplaySettings display) { ArgumentNullException.ThrowIfNull(display); if (TryParseResolution(display.Resolution, out int width, out int height) && (_window.Size.X != width || _window.Size.Y != height)) { _window.Size = new Vector2D(width, height); } WindowState desired = display.Fullscreen ? WindowState.Fullscreen : WindowState.Normal; if (_window.WindowState != desired) _window.WindowState = desired; } internal static bool TryParseResolution( string spec, out int width, out int height) { width = height = 0; if (string.IsNullOrWhiteSpace(spec)) return false; string[] parts = spec.Split('x', 2); return parts.Length == 2 && int.TryParse(parts[0], out width) && int.TryParse(parts[1], out height) && width > 0 && height > 0; } } internal sealed class RuntimeSettingsStartupTargets : IRuntimeSettingsStartupTarget { private readonly IRuntimeDisplayWindowTarget _displayWindow; private readonly DisplayFramePacingController _pacing; private readonly CameraController _cameras; private readonly OpenAlAudioEngine? _audio; public RuntimeSettingsStartupTargets( IRuntimeDisplayWindowTarget displayWindow, DisplayFramePacingController pacing, CameraController cameras, OpenAlAudioEngine? audio) { _displayWindow = displayWindow ?? throw new ArgumentNullException(nameof(displayWindow)); _pacing = pacing ?? throw new ArgumentNullException(nameof(pacing)); _cameras = cameras ?? throw new ArgumentNullException(nameof(cameras)); _audio = audio; } public void ApplyDisplay(DisplaySettings display) { ArgumentNullException.ThrowIfNull(display); _pacing.RefreshActiveMonitor(); _pacing.ApplyPreference(display.VSync); _displayWindow.Apply(display); ApplyFieldOfView(_cameras, display.FieldOfView); } public void ApplyAudio(AudioSettings audio) => ApplyAudio(_audio, audio); internal static void ApplyFieldOfView( CameraController cameras, float degrees) { float radians = degrees * (MathF.PI / 180f); cameras.Orbit.FovY = radians; cameras.Fly.FovY = radians; if (cameras.Chase is not null) cameras.Chase.FovY = radians; } internal static void ApplyAudio( OpenAlAudioEngine? engine, AudioSettings audio) { ArgumentNullException.ThrowIfNull(audio); if (engine is not { IsAvailable: true }) return; engine.MasterVolume = audio.Master; engine.MusicVolume = audio.Music; engine.SfxVolume = audio.Sfx; engine.AmbientVolume = audio.Ambient; } } internal sealed class RuntimeQualityApplicationTarget : IRuntimeQualityApplicationTarget { private readonly WbDrawDispatcher _dispatcher; private readonly TerrainAtlas _terrainAtlas; private readonly StreamingController _streaming; private readonly WorldRenderRangeState _renderRange; public RuntimeQualityApplicationTarget( WbDrawDispatcher dispatcher, TerrainAtlas terrainAtlas, StreamingController streaming, WorldRenderRangeState renderRange) { _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); _terrainAtlas = terrainAtlas ?? throw new ArgumentNullException(nameof(terrainAtlas)); _streaming = streaming ?? throw new ArgumentNullException(nameof(streaming)); _renderRange = renderRange ?? throw new ArgumentNullException(nameof(renderRange)); } public void SetAlphaToCoverage(bool enabled) => _dispatcher.AlphaToCoverage = enabled; public void SetAnisotropic(int level) => _terrainAtlas.SetAnisotropic(level); public void PublishRenderRange(int nearRadius, int farRadius) { _renderRange.NearRadius = nearRadius; _renderRange.FarRadius = farRadius; } public void ReconfigureStreamingRadii(int nearRadius, int farRadius) => _streaming.ReconfigureRadii(nearRadius, farRadius); public void SetCompletionBudget(int maxCompletionsPerFrame) => _streaming.MaxCompletionsPerFrame = maxCompletionsPerFrame; } internal sealed class RuntimeUiLockTarget(UiRoot root) : IRuntimeUiLockTarget { private readonly UiRoot _root = root ?? throw new ArgumentNullException(nameof(root)); public void Apply(bool locked) => _root.UiLocked = locked; } internal sealed class NullRuntimeUiLockTarget : IRuntimeUiLockTarget { public static NullRuntimeUiLockTarget Instance { get; } = new(); private NullRuntimeUiLockTarget() { } public void Apply(bool locked) { } } /// /// Complete late-bound target for changes made after startup. Construction and /// binding are inert; only an explicit controller command mutates borrowers. /// internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets { private readonly IRuntimeDisplayWindowTarget _displayWindow; private readonly IRuntimeQualityApplicationTarget _quality; private readonly IRuntimeUiLockTarget _uiLock; private readonly Action _log; public RuntimeSettingsTargets( IRuntimeDisplayWindowTarget displayWindow, WbDrawDispatcher dispatcher, TerrainAtlas terrainAtlas, StreamingController streaming, WorldRenderRangeState renderRange, UiRoot? uiRoot, Action? log = null) : this( displayWindow, new RuntimeQualityApplicationTarget( dispatcher, terrainAtlas, streaming, renderRange), uiRoot is null ? NullRuntimeUiLockTarget.Instance : new RuntimeUiLockTarget(uiRoot), log) { } internal RuntimeSettingsTargets( IRuntimeDisplayWindowTarget displayWindow, IRuntimeQualityApplicationTarget quality, IRuntimeUiLockTarget uiLock, Action? log = null) { _displayWindow = displayWindow ?? throw new ArgumentNullException(nameof(displayWindow)); _quality = quality ?? throw new ArgumentNullException(nameof(quality)); _uiLock = uiLock ?? throw new ArgumentNullException(nameof(uiLock)); _log = log ?? Console.WriteLine; } public void ApplyDisplayWindowState(DisplaySettings display) => _displayWindow.Apply(display); public void ApplyQuality(QualitySettings quality) { _quality.SetAlphaToCoverage(quality.AlphaToCoverage); _quality.SetAnisotropic(quality.AnisotropicLevel); _quality.PublishRenderRange(quality.NearRadius, quality.FarRadius); _quality.ReconfigureStreamingRadii(quality.NearRadius, quality.FarRadius); _quality.SetCompletionBudget(quality.MaxCompletionsPerFrame); _log( $"[QUALITY] Streaming reconciled: nearRadius={quality.NearRadius}, " + $"farRadius={quality.FarRadius}, " + $"maxCompletions={quality.MaxCompletionsPerFrame}"); } public void ApplyUiLock(bool locked) => _uiLock.Apply(locked); }