146 lines
7.0 KiB
C++
146 lines
7.0 KiB
C++
#pragma once
|
|
#include "FluidSim/Scenario.hpp"
|
|
#include "FluidSim/ScenarioCatalog.hpp"
|
|
#include "FluidSim/TwoStrokeScenario.hpp"
|
|
#include <memory>
|
|
#include <mutex>
|
|
|
|
namespace fluidsim::port {
|
|
// Shared desktop controls and snapshots. Each simulation owns its state lock.
|
|
class DesktopScenario {
|
|
public:
|
|
explicit DesktopScenario(const ScenarioExample& e)
|
|
: DesktopScenario(e.kind,e.two,e.exhaust,e.junctions,e.engine,e.title) {}
|
|
explicit DesktopScenario(ScenarioKind kind, TwoStrokeConfig config = {}, ExhaustConfig exhaust = {}, JunctionConfig junctions = {}, EngineConfig engine = {}, std::string name = {}) : name_(std::move(name)),exhaust_(exhaust),junctions_(junctions) {
|
|
if (kind == ScenarioKind::TwoStroke)
|
|
two_ = std::make_unique<TwoStrokeScenario>(44100, config);
|
|
else {
|
|
if(!engine.cylinders)engine=preset_engine_definition(kind==ScenarioKind::SaabB205?
|
|
FourStrokePreset::SaabB205:kind==ScenarioKind::AmericanV8?FourStrokePreset::AmericanV8:FourStrokePreset::Twin).engine;
|
|
four_ = std::make_unique<FourStrokeScenario>(44100,std::move(engine),exhaust,junctions);
|
|
}
|
|
}
|
|
AudioFrame process() {
|
|
const std::lock_guard lock(mutex_);
|
|
if (four_)
|
|
return four_->process();
|
|
return two_->process();
|
|
}
|
|
void set_controls(float pedal, float load, float clutch) {
|
|
visit([&](auto& scenario) { pedal_=pedal;load_=load;clutch_=clutch;scenario.set_controls(pedal, load, clutch); });
|
|
}
|
|
void set_starter(bool engaged) { visit([&](auto& scenario) { starter_=engaged;scenario.set_starter(engaged); }); }
|
|
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 set_pipe_solver(PipeSolver solver) { visit([&](auto& scenario) { solver_=solver;scenario.set_pipe_solver(solver); }); }
|
|
void set_audio_scene(OutdoorScene scene) {
|
|
visit([&](auto& scenario) { scene_=scene;scenario.set_audio_scene(scene); });
|
|
}
|
|
void set_listener(AudioListener listener) {
|
|
visit([&](auto& scenario) { listener_=listener;scenario.set_listener(listener); });
|
|
}
|
|
void set_tuning(const EngineConfig& tuning) {
|
|
const std::lock_guard lock(mutex_);
|
|
if(four_)four_->set_tuning(tuning);
|
|
}
|
|
// Build on a background thread. Publishing takes the same lock as process(),
|
|
// so no solver/port pointers can be observed during the swap.
|
|
void rebuild_engine(const EngineConfig& engine) {
|
|
validate_engine_config(engine,"pipe rebuild");
|
|
auto replacement=std::make_unique<FourStrokeScenario>(44100,engine,exhaust_,junctions_);
|
|
{
|
|
const std::lock_guard lock(mutex_);
|
|
if(!four_)throw std::invalid_argument("Live editor requires a four-stroke engine");
|
|
replacement->set_controls(pedal_,load_,clutch_);
|
|
replacement->set_starter(starter_);replacement->set_pipe_solver(solver_);
|
|
replacement->set_audio_scene(scene_);replacement->set_listener(listener_);
|
|
four_.swap(replacement);
|
|
}
|
|
}
|
|
void set_cells_per_meter(float density) {
|
|
mesh_cell_count(.1f,density);
|
|
EngineConfig engine;TwoStrokeConfig two_config;bool is_two;
|
|
{
|
|
const std::lock_guard lock(mutex_);
|
|
is_two=bool(two_);
|
|
if(is_two) two_config=two_->configuration; else engine=four_->engine_config;
|
|
}
|
|
std::unique_ptr<FourStrokeScenario> next_four;
|
|
std::unique_ptr<TwoStrokeScenario> next_two;
|
|
if(is_two) { two_config.cells_per_meter=density;two_config.exhaust_cell_length=1/density;next_two=std::make_unique<TwoStrokeScenario>(44100,two_config); }
|
|
else { engine.cells_per_meter=density;next_four=std::make_unique<FourStrokeScenario>(44100,engine,exhaust_,junctions_); }
|
|
{
|
|
const std::lock_guard lock(mutex_);
|
|
const auto restore=[&](auto& scenario) {
|
|
scenario.set_controls(pedal_,load_,clutch_);scenario.set_starter(starter_);
|
|
scenario.set_pipe_solver(solver_);scenario.set_audio_scene(scene_);scenario.set_listener(listener_);
|
|
};
|
|
if(is_two) {restore(*next_two);two_.swap(next_two);}
|
|
else {restore(*next_four);four_.swap(next_four);}
|
|
}
|
|
}
|
|
ScenarioSnapshot snapshot() const {
|
|
const std::lock_guard lock(mutex_);
|
|
if (four_) { auto s=four_->snapshot();s.example_name=name_;s.cells_per_meter=four_->engine_config.cells_per_meter;return s; }
|
|
const auto source = two_->snapshot();
|
|
ScenarioSnapshot s;
|
|
s.example_name=name_;
|
|
s.pipe_solver=two_->pipes.front().solver;
|
|
s.two_stroke = true;
|
|
s.cells_per_meter=two_->configuration.cells_per_meter;
|
|
s.cylinder_angles[0]=two_->crankshaft.angle;
|
|
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_->carburetor.indicated_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.ignition_cut = source.ignition_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);
|
|
two_->capture_geometry(s);
|
|
return s;
|
|
}
|
|
|
|
private:
|
|
template<class Function> void visit(Function&& function) {
|
|
const std::lock_guard lock(mutex_);
|
|
if (four_) function(*four_);
|
|
else function(*two_);
|
|
}
|
|
std::string name_;
|
|
ExhaustConfig exhaust_;
|
|
JunctionConfig junctions_;
|
|
float pedal_{},load_{},clutch_{};
|
|
bool starter_{};
|
|
PipeSolver solver_{PipeSolver::Rusanov};
|
|
OutdoorScene scene_{OutdoorScene::Wall};
|
|
AudioListener listener_{};
|
|
std::unique_ptr<FourStrokeScenario> four_;
|
|
std::unique_ptr<TwoStrokeScenario> two_;
|
|
mutable std::mutex mutex_;
|
|
};
|
|
} // namespace fluidsim::port
|