engine almost working, backup before adding gas types.

This commit is contained in:
max
2026-05-07 20:07:15 +02:00
parent 14f5ba925f
commit 92d84eacfe
18 changed files with 1236 additions and 587 deletions

View File

@@ -1,9 +1,13 @@
using SFML.Graphics;
using SFML.Window;
using SFML.System;
using System.Diagnostics;
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;
@@ -11,190 +15,211 @@ public class Program
{
private const int SampleRate = 44100;
private const double DrawFrequency = 60.0;
private static Scenario scenario;
// Speed control
private static double desiredSpeed = 0.01;
private static double currentSpeed = desiredSpeed;
// 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 double lastDesiredSpeed = 0.1;
private static bool isRealTime = false;
private static volatile bool _timeWarpActive;
// Throttle smoothing (unused but kept)
private static double targetThrottle = 0.0;
private static double currentThrottle = 0.0;
private const double ThrottleSmoothing = 20.0;
// Thread load tracking
private static ThreadLoadTracker _loadTracker = new ThreadLoadTracker();
private static volatile bool running = true;
// 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;
// ---- Overlay text ----
private static Font? overlayFont;
private static Text? overlayText;
// Throttle control
private static double _throttleTarget = 1.0; // 01, set by arrow keys
private static double _throttleCurrent = 0.0; // actual current fraction (lerped)
private const double ThrottleLerpRate = 5.0; // times per second (speed of movement)
private const double ThrottleMinArea = 0.0000000000001; // 3.7e-5 m² ≈ 0.37 cm² (10% of pipe)
private const double ThrottleMaxArea = 0.00000000001; // 3.7 cm² (full open)
private static bool _wKeyHeld = false;
private static double _lastThrottleUpdateTime;
private const int TargetMaxFill = (int)(SampleRate * 0.2);
public static void Main()
{
var mode = new VideoMode(new Vector2u(1280, 720));
var window = new RenderWindow(mode, "FluidSim");
window.SetVerticalSyncEnabled(true);
window.Closed += (_, _) => { running = false; window.Close(); };
window.MouseWheelScrolled += OnMouseWheel;
window.KeyPressed += OnKeyPressed;
var window = CreateWindow();
LoadFont();
_scenario = (TestScenario)InitializeScenario();
_lastThrottleUpdateTime = 0.0;
// ---- Load font ----
try
{
overlayFont = new Font("fonts/FiraCodeNerdFont-Medium.ttf");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load font 'fonts/LiberationMono-Regular.ttf': {ex.Message}");
overlayFont = null; // will skip text drawing
}
_simRingBuffer = new SimulationRingBuffer(131072);
_soundEngine = new SoundEngine(_simRingBuffer) { Volume = 100 };
_soundEngine.Start();
if (overlayFont != null)
{
// SFML 3 Text(font, character size in pixels)
overlayText = new Text(overlayFont)
{
FillColor = Color.White,
Position = new Vector2f(10, 10)
};
}
var soundEngine = new SoundEngine(bufferCapacity: 16384);
soundEngine.Volume = 100;
soundEngine.Start();
scenario = new TestScenario();
scenario.Initialize(SampleRate);
var cts = new CancellationTokenSource();
Task.Run(() => SimulationLoop(cts.Token), cts.Token);
var stopwatch = Stopwatch.StartNew();
double lastDrawTime = 0.0;
double drawInterval = 1.0 / DrawFrequency;
double lastSpeedUpdateTime = stopwatch.Elapsed.TotalSeconds;
var simBuffer = new List<float>(4096);
double readIndex = 0.0;
for (int i = 0; i < 4; i++)
simBuffer.Add(scenario.Process());
long totalSimSteps = simBuffer.Count;
long totalOutputSamples = 0;
const int outputChunk = 256;
float[] outputBuf = new float[outputChunk];
while (window.IsOpen)
{
window.DispatchEvents();
double currentRealTime = stopwatch.Elapsed.TotalSeconds;
double dtClock = currentRealTime - lastSpeedUpdateTime;
lastSpeedUpdateTime = currentRealTime;
double now = stopwatch.Elapsed.TotalSeconds;
// Smooth simulation speed
double speedSmoothing = 8.0;
currentSpeed += (desiredSpeed - currentSpeed) * (1.0 - Math.Exp(-speedSmoothing * dtClock));
// ---- Playback speed smoothing ----
double targetSpeed = _timeWarpActive ? 1.0 : _desiredSpeed;
_currentDisplaySpeed += (targetSpeed - _currentDisplaySpeed) *
(1.0 - Math.Exp(-8.0 * (now - lastDrawTime)));
_soundEngine.Speed = _currentDisplaySpeed;
// Generate audio
double targetAudioClock = currentRealTime + 0.05;
while (totalOutputSamples < targetAudioClock * SampleRate && running)
// ---- 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)
{
int toGenerate = (int)Math.Min(
(long)outputChunk,
(long)(targetAudioClock * SampleRate) - totalOutputSamples
);
if (toGenerate <= 0) break;
double maxIndex = readIndex + (toGenerate - 1) * currentSpeed + 2;
int requiredSimIndex = (int)Math.Ceiling(maxIndex);
while (simBuffer.Count - 1 < requiredSimIndex)
{
simBuffer.Add(scenario.Process());
totalSimSteps++;
}
for (int i = 0; i < toGenerate; i++)
{
int i0 = (int)readIndex;
int i1 = i0 + 1;
double frac = readIndex - i0;
float y0 = simBuffer[Math.Clamp(i0, 0, simBuffer.Count - 1)];
float y1 = simBuffer[Math.Clamp(i1, 0, simBuffer.Count - 1)];
outputBuf[i] = (float)(y0 + (y1 - y0) * frac);
readIndex += currentSpeed;
while (readIndex >= 1.0 && simBuffer.Count > 2)
{
simBuffer.RemoveAt(0);
readIndex -= 1.0;
}
}
int accepted = soundEngine.WriteSamples(outputBuf, toGenerate);
totalOutputSamples += accepted;
if (accepted < toGenerate)
break;
_throttleCurrent = 0.0;
}
else
{
double smoothing = 1.0 - Math.Exp(-ThrottleLerpRate * dtThrottle);
_throttleCurrent += (throttleDesiredFraction - _throttleCurrent) * smoothing;
}
// Drawing
if (currentRealTime - lastDrawTime >= drawInterval)
{
double actualSpeed = totalOutputSamples / (currentRealTime * SampleRate);
double realtimePercent = totalOutputSamples / (currentRealTime * SampleRate) * 100.0;
double actualArea = ThrottleMinArea + (ThrottleMaxArea - ThrottleMinArea) * _throttleCurrent;
_scenario.ThrottleArea = actualArea;
// Update overlay text
if (overlayText != null)
// ---- Drawing ----
if (now - lastDrawTime >= 1.0 / DrawFrequency)
{
if (_overlayText != null)
{
string toggleHint = isRealTime ? "[Space] slow mo" : "[Space] real time";
string throttleHint = Keyboard.IsKeyPressed(Keyboard.Key.W) ? "W held" : "W released";
overlayText.DisplayedString =
$"{toggleHint} {throttleHint} " +
$"Speed: {currentSpeed:F3}x " +
$"RT: {realtimePercent:F1}%";
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);
// Draw the overlay on top
if (overlayText != null)
window.Draw(overlayText);
_scenario.Draw(window);
if (_overlayText != null) window.Draw(_overlayText);
window.Display();
lastDrawTime = currentRealTime;
lastDrawTime = now;
}
}
soundEngine.Dispose();
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)
{
bool wasRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
if (e.Delta > 0)
desiredSpeed *= ScrollFactor;
else if (e.Delta < 0)
desiredSpeed /= ScrollFactor;
desiredSpeed = Math.Clamp(desiredSpeed, MinSpeed, MaxSpeed);
if (!wasRealTime || Math.Abs(desiredSpeed - 1.0) > 1e-6)
lastDesiredSpeed = desiredSpeed;
isRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
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)
{
if (e.Code == Keyboard.Key.Space)
switch (e.Code)
{
if (isRealTime)
desiredSpeed = lastDesiredSpeed;
else
desiredSpeed = 1.0;
isRealTime = !isRealTime;
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;
}
}