#pragma once #include "FluidSim/Scenario.hpp" #include "FluidSim/TwoStrokeScenario.hpp" #include #include namespace fluidsim::port { // Shared desktop controls/snapshot contract. Two-stroke access is serialized; // the twin already protects its own simulation state. class DesktopScenario { public: explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, bool saab_turbo = true) { if (kind == ScenarioKind::TwoStroke) two_ = std::make_unique(44100, config); else twin_ = std::make_unique(44100, kind == ScenarioKind::SaabB205 ? FourStrokePreset::SaabB205 : FourStrokePreset::Twin, kind == ScenarioKind::TurboTwin || (kind == ScenarioKind::SaabB205 && saab_turbo)); } AudioFrame process() { if (twin_) return twin_->process(); const std::lock_guard lock(mutex_); return two_->process(); } void set_controls(float pedal, float load, float clutch) { visit([&](auto& scenario) { scenario.set_controls(pedal, load, clutch); }); } void shift_up() { visit([](auto& scenario) { scenario.shift_up(); }); } void shift_down() { visit([](auto& scenario) { scenario.shift_down(); }); } void set_neutral() { visit([](auto& scenario) { scenario.set_neutral(); }); } void toggle_mechanical_sound() { if (twin_) twin_->toggle_mechanical_sound(); } void set_audio_scene(OutdoorScene scene) { visit([&](auto& scenario) { scenario.set_audio_scene(scene); }); } ScenarioSnapshot snapshot() const { if (twin_) return twin_->snapshot(); const std::lock_guard lock(mutex_); const auto source = two_->snapshot(); ScenarioSnapshot s; s.two_stroke = true; s.cylinder_count = 1; s.racing_exhaust = two_->configuration.exhaust == TwoStrokeExhaust::Racing; s.rpm = source.rpm; s.speed_kmh = two_->vehicle.speed * 3.6f; s.kinetic_energy = two_->vehicle.kinetic_energy(); s.gear = two_->vehicle.current_gear; s.clutch = two_->clutch; s.average_power = two_->crankshaft.average_power; s.average_torque = two_->crankshaft.average_torque; s.cylinder_pressure1 = source.cylinder_pressure; s.cylinder_temperature1 = two_->cylinder.temperature(); s.cylinder_piston_fraction1 = (two_->cylinder.volume - two_->cylinder.clearance_volume()) / two_->cylinder.swept_volume(); s.crankcase_pressure = source.crankcase_pressure; s.intake_pressure = source.intake_pressure; s.exhaust_pressure = two_->pipes[3].state(0).pressure; s.throttle = source.throttle; s.pedal = source.pedal; s.target_afr = two_->ecu.target_afr(); s.valve1_intake_area = source.transfer_area; s.valve1_exhaust_area = source.exhaust_area; s.reed_open = source.reed_open; s.fuel_cut = source.fuel_cut; s.idle_control = two_->ecu.idle_active(); s.rev_limit1 = two_->ecu.rev_limited(); s.audio_scene = source.audio_scene; s.intake_flow = two_->intake_flow; s.exhaust_flow = two_->exhaust_flow; s.capture_pipes(two_->pipes); return s; } private: template void visit(Function&& function) { const std::lock_guard lock(mutex_); if (twin_) function(*twin_); else function(*two_); } std::unique_ptr twin_; std::unique_ptr two_; mutable std::mutex mutex_; }; } // namespace fluidsim::port