using FluidSim.Audio; using FluidSim.Core; using FluidSim.Tests; using SFML.Graphics; using SFML.System; using SFML.Window; using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace FluidSim; public class Program { private const int SampleRate = 44100; private const double DrawFrequency = 60.0; // Playback speed private static double _desiredSpeed = 0.01; private static double _currentDisplaySpeed = _desiredSpeed; private const double MinSpeed = 0.0001; private const double MaxSpeed = 1.0; private const double ScrollFactor = 1.1; private static double _lastNormalSpeed = 0.1; private static bool _isRealTime = false; private static volatile bool _timeWarpActive; // Thread load tracking private static ThreadLoadTracker _loadTracker = new ThreadLoadTracker(); // Audio & simulation private static SimulationRingBuffer _simRingBuffer = null!; private static SoundEngine _soundEngine = null!; private static TestScenario _scenario = null!; // cast to access ThrottleArea private static Font? _overlayFont; private static Text? _overlayText; // Throttle control private static double _throttleTarget = 1.0; // 0‑1, set by arrow keys private static double _throttleCurrent = 0.0; // actual current fraction (lerped) private const double ThrottleLerpRate = 10.0; // times per second (speed of movement) private static bool _wKeyHeld = false; private static double _lastThrottleUpdateTime; private const int TargetMaxFill = (int)(SampleRate * 0.2); public static void Main() { var window = CreateWindow(); LoadFont(); _scenario = (TestScenario)InitializeScenario(); _lastThrottleUpdateTime = 0.0; _simRingBuffer = new SimulationRingBuffer(131072); _soundEngine = new SoundEngine(_simRingBuffer) { Volume = 100 }; _soundEngine.Start(); var cts = new CancellationTokenSource(); Task.Run(() => SimulationLoop(cts.Token), cts.Token); var stopwatch = Stopwatch.StartNew(); double lastDrawTime = 0.0; while (window.IsOpen) { window.DispatchEvents(); double now = stopwatch.Elapsed.TotalSeconds; // ---- Playback speed smoothing ---- double targetSpeed = _timeWarpActive ? 1.0 : _desiredSpeed; _currentDisplaySpeed += (targetSpeed - _currentDisplaySpeed) * (1.0 - Math.Exp(-8.0 * (now - lastDrawTime))); _soundEngine.Speed = _currentDisplaySpeed; // ---- Throttle update ---- double dtThrottle = now - _lastThrottleUpdateTime; _lastThrottleUpdateTime = now; double throttleDesiredFraction = _wKeyHeld ? _throttleTarget : 0.0; // Snap to zero instantly when target is zero (key released) if (throttleDesiredFraction == 0.0) { _throttleCurrent = 0.0; } else { double smoothing = 1.0 - Math.Exp(-ThrottleLerpRate * dtThrottle); _throttleCurrent += (throttleDesiredFraction - _throttleCurrent) * smoothing; } _scenario.Throttle = _throttleCurrent; // ---- Drawing ---- if (now - lastDrawTime >= 1.0 / DrawFrequency) { if (_overlayText != null) { string toggleHint = _isRealTime ? "[Space] slow mo" : "[Space] real time"; _overlayText.DisplayedString = $"{toggleHint} Speed: {_currentDisplaySpeed:F3}x RT: {(_currentDisplaySpeed * 100.0):F1}% Sim load: {_loadTracker.LoadPercent:F0}%\n" + $"Throttle: {_throttleCurrent * 100:F0}% Target: {_throttleTarget * 100:F0}% [W] {(_wKeyHeld ? "BLIP" : "---")}"; } window.Clear(Color.Black); _scenario.Draw(window); if (_overlayText != null) window.Draw(_overlayText); window.Display(); lastDrawTime = now; } } cts.Cancel(); _soundEngine.Dispose(); window.Dispose(); } private static void SimulationLoop(CancellationToken token) { while (!token.IsCancellationRequested) { long cycleStart = Stopwatch.GetTimestamp(); long workStart = Stopwatch.GetTimestamp(); float sample = _scenario.Process(); _simRingBuffer.Write(sample); long workEnd = Stopwatch.GetTimestamp(); while (_simRingBuffer.AvailableSamples > TargetMaxFill && !token.IsCancellationRequested) { Thread.Sleep(1); } long cycleEnd = Stopwatch.GetTimestamp(); double busyMs = (workEnd - workStart) / (double)Stopwatch.Frequency * 1000.0; double totalMs = (cycleEnd - cycleStart) / (double)Stopwatch.Frequency * 1000.0; _loadTracker.Record(busyMs, totalMs); } } // ---------- Window & input ---------- private static RenderWindow CreateWindow() { var mode = new VideoMode(new Vector2u(1280, 720)); var window = new RenderWindow(mode, "FluidSim"); window.SetVerticalSyncEnabled(false); window.SetFramerateLimit(60); window.Closed += (_, _) => window.Close(); window.MouseWheelScrolled += OnMouseWheel; window.KeyPressed += OnKeyPressed; window.KeyReleased += OnKeyReleased; return window; } private static void LoadFont() { try { _overlayFont = new Font("fonts/FiraCodeNerdFont-Medium.ttf"); } catch { _overlayFont = null; } if (_overlayFont != null) _overlayText = new Text(_overlayFont) { FillColor = Color.White, Position = new Vector2f(10, 10) }; } private static Scenario InitializeScenario() { var sc = new TestScenario(); sc.Initialize(SampleRate); return sc; } private static void OnMouseWheel(object? sender, MouseWheelScrollEventArgs e) { if (_timeWarpActive) return; if (e.Delta > 0) _desiredSpeed *= ScrollFactor; else if (e.Delta < 0) _desiredSpeed /= ScrollFactor; _desiredSpeed = Math.Clamp(_desiredSpeed, MinSpeed, MaxSpeed); _lastNormalSpeed = _desiredSpeed; _isRealTime = Math.Abs(_desiredSpeed - 1.0) < 1e-6; } private static void OnKeyPressed(object? sender, KeyEventArgs e) { switch (e.Code) { case Keyboard.Key.Space: _timeWarpActive = !_timeWarpActive; if (!_timeWarpActive) { _desiredSpeed = _lastNormalSpeed; _isRealTime = false; } break; case Keyboard.Key.W: _wKeyHeld = true; break; case Keyboard.Key.Up: _throttleTarget = Math.Min(1.0, _throttleTarget + 0.05); break; case Keyboard.Key.Down: _throttleTarget = Math.Max(0.0, _throttleTarget - 0.05); break; } } private static void OnKeyReleased(object? sender, KeyEventArgs e) { if (e.Code == Keyboard.Key.W) _wKeyHeld = false; } }