From d3754f4b5ee5f6b6f44eaa787e0ec4c27f105748 Mon Sep 17 00:00:00 2001 From: David Walschots Date: Thu, 21 Oct 2021 09:44:52 +0200 Subject: [PATCH] perf: decrease systems variable read/write and hashing time (#6021) --- Cargo.lock | 17 + scripts/lint-rust.sh | 2 +- .../src/main.rs | 69 ++- .../src/electrical/alternating_current.rs | 78 ++- .../src/electrical/direct_current.rs | 79 ++- .../a320_systems/src/electrical/mod.rs | 144 ++--- src/systems/a320_systems/src/fuel.rs | 17 +- src/systems/a320_systems/src/hydraulic.rs | 479 +++++++++------- src/systems/a320_systems/src/lib.rs | 45 +- src/systems/a320_systems/src/pneumatic.rs | 5 +- .../a320_systems/src/power_consumption.rs | 110 ++-- src/systems/a320_systems_wasm/src/lib.rs | 340 +++++++----- src/systems/guidelines.md | 22 + src/systems/systems/Cargo.toml | 1 + .../systems/src/apu/air_intake_flap.rs | 33 +- src/systems/systems/src/apu/aps3200.rs | 45 +- .../systems/src/apu/electronic_control_box.rs | 54 +- src/systems/systems/src/apu/mod.rs | 136 ++--- src/systems/systems/src/electrical/battery.rs | 126 ++--- .../src/electrical/battery_charge_limiter.rs | 47 +- .../systems/src/electrical/consumption.rs | 134 +++-- .../src/electrical/emergency_generator.rs | 44 +- .../src/electrical/engine_generator.rs | 152 +++--- .../src/electrical/external_power_source.rs | 50 +- src/systems/systems/src/electrical/mod.rs | 509 ++++++++++-------- .../systems/src/electrical/static_inverter.rs | 52 +- src/systems/systems/src/electrical/test.rs | 15 +- .../src/electrical/transformer_rectifier.rs | 52 +- src/systems/systems/src/engine/leap_engine.rs | 16 +- src/systems/systems/src/engine/mod.rs | 33 +- .../systems/src/hydraulic/brake_circuit.rs | 280 ++++++---- .../systems/src/hydraulic/linear_actuator.rs | 76 ++- src/systems/systems/src/hydraulic/mod.rs | 353 +++++++----- .../systems/src/hydraulic/update_iterator.rs | 48 +- src/systems/systems/src/landing_gear/mod.rs | 56 +- src/systems/systems/src/navigation/adirs.rs | 336 +++++++----- src/systems/systems/src/overhead/mod.rs | 457 ++++++++++------ src/systems/systems/src/pressurization/mod.rs | 59 +- src/systems/systems/src/simulation/mod.rs | 186 +++++-- src/systems/systems/src/simulation/test.rs | 195 +++++-- .../systems/src/simulation/update_context.rs | 102 +++- src/systems/systems_wasm/Cargo.toml | 1 + src/systems/systems_wasm/src/electrical.rs | 56 +- src/systems/systems_wasm/src/failures.rs | 8 +- src/systems/systems_wasm/src/lib.rs | 258 +++++---- 45 files changed, 3236 insertions(+), 2141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 140f88772c5..8dd8869aefb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.69" @@ -323,6 +329,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.4" @@ -889,6 +904,7 @@ dependencies = [ name = "systems" version = "0.1.0" dependencies = [ + "fxhash", "nalgebra", "ntest", "num-derive", @@ -902,6 +918,7 @@ dependencies = [ name = "systems_wasm" version = "0.1.0" dependencies = [ + "fxhash", "msfs", "systems", "uom", diff --git a/scripts/lint-rust.sh b/scripts/lint-rust.sh index 08faa239a29..a46025a82ba 100755 --- a/scripts/lint-rust.sh +++ b/scripts/lint-rust.sh @@ -4,4 +4,4 @@ set -ex cargo fmt -- --check -cargo clippy --all-targets --all-features -- -D warnings -A clippy::too_many_arguments +cargo clippy --all-targets --all-features -- -D warnings -A clippy::too_many_arguments -A deprecated diff --git a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs index 4dfcf02aeae..6547dfae9f4 100644 --- a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs +++ b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs @@ -4,7 +4,11 @@ use plotlib::style::LineStyle; use plotlib::view::ContinuousView; use std::time::Duration; pub use systems::hydraulic::*; -use systems::{shared::ElectricalBusType, simulation::UpdateContext}; +use systems::{ + electrical::Electricity, + shared::ElectricalBusType, + simulation::{test::TestVariableRegistry, InitContext, UpdateContext}, +}; use uom::si::{ acceleration::foot_per_second_squared, angle::radian, @@ -18,7 +22,6 @@ use uom::si::{ volume_rate::gallon_per_second, }; -extern crate rustplotlib; use rustplotlib::Figure; struct TestHydraulicLoopController { @@ -229,14 +232,18 @@ fn green_loop_edp_simulation(path: &str) { let edp1_var_names = vec!["Delta Vol Max".to_string(), "pump rpm".to_string()]; let mut edp1_history = History::new(edp1_var_names); - let mut edp1 = engine_driven_pump(); + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + + let mut edp1 = engine_driven_pump(&mut init_context); let mut edp1_controller = TestPumpController::commanding_pressurise(); - let mut green_loop = hydraulic_loop("GREEN"); + let mut green_loop = hydraulic_loop(&mut init_context, "GREEN"); let green_loop_controller = TestHydraulicLoopController::commanding_open_fire_shutoff_valve(); let edp_rpm = 3000.; - let context = context(Duration::from_millis(100)); + let context = context(&mut init_context, Duration::from_millis(100)); let green_acc_var_names = vec![ "Loop Pressure".to_string(), @@ -383,21 +390,25 @@ fn yellow_green_ptu_loop_simulation(path: &str) { ]; let mut accu_yellow_history = History::new(yellow_acc_var_names); - let mut epump = electric_pump(); + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + + let mut epump = electric_pump(&mut init_context); let mut epump_controller = TestPumpController::commanding_depressurise(); - let mut yellow_loop = hydraulic_loop("YELLOW"); + let mut yellow_loop = hydraulic_loop(&mut init_context, "YELLOW"); - let mut edp1 = engine_driven_pump(); + let mut edp1 = engine_driven_pump(&mut init_context); let mut edp1_controller = TestPumpController::commanding_depressurise(); - let mut green_loop = hydraulic_loop("GREEN"); + let mut green_loop = hydraulic_loop(&mut init_context, "GREEN"); let loop_controller = TestHydraulicLoopController::commanding_open_fire_shutoff_valve(); - let mut ptu = PowerTransferUnit::new(); + let mut ptu = PowerTransferUnit::new(&mut init_context); let mut ptu_controller = TestPowerTransferUnitController::commanding_disabled(); - let context = context(Duration::from_millis(100)); + let context = context(&mut init_context, Duration::from_millis(100)); loop_history.init( 0.0, @@ -645,23 +656,27 @@ fn yellow_epump_plus_edp2_with_ptu(path: &str) { ]; let mut accu_yellow_history = History::new(yellow_acc_var_names); - let mut epump = electric_pump(); + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + + let mut epump = electric_pump(&mut init_context); let mut epump_controller = TestPumpController::commanding_depressurise(); - let mut yellow_loop = hydraulic_loop("YELLOW"); + let mut yellow_loop = hydraulic_loop(&mut init_context, "YELLOW"); - let mut edp2 = engine_driven_pump(); + let mut edp2 = engine_driven_pump(&mut init_context); let mut edp2_controller = TestPumpController::commanding_depressurise(); let edp_rpm = 3300.; - let mut green_loop = hydraulic_loop("GREEN"); + let mut green_loop = hydraulic_loop(&mut init_context, "GREEN"); let loop_controller = TestHydraulicLoopController::commanding_open_fire_shutoff_valve(); - let mut ptu = PowerTransferUnit::new(); + let mut ptu = PowerTransferUnit::new(&mut init_context); let ptu_controller = TestPowerTransferUnitController::commanding_enabled(); - let context = context(Duration::from_millis(100)); + let context = context(&mut init_context, Duration::from_millis(100)); loop_history.init( 0.0, @@ -790,9 +805,10 @@ fn yellow_epump_plus_edp2_with_ptu(path: &str) { accu_yellow_history.show_matplotlib("yellow_epump_plus_edp2_with_ptu()_Yellow_acc", &path); } -fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { +fn hydraulic_loop(context: &mut InitContext, loop_color: &str) -> HydraulicLoop { match loop_color { "GREEN" => HydraulicLoop::new( + context, loop_color, true, false, @@ -806,6 +822,7 @@ fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { Pressure::new::(1750.), ), "YELLOW" => HydraulicLoop::new( + context, loop_color, false, true, @@ -819,6 +836,7 @@ fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { Pressure::new::(1750.), ), _ => HydraulicLoop::new( + context, loop_color, false, false, @@ -834,16 +852,21 @@ fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { } } -fn electric_pump() -> ElectricPump { - ElectricPump::new("DEFAULT", ElectricalBusType::AlternatingCurrentEssential) +fn electric_pump(context: &mut InitContext) -> ElectricPump { + ElectricPump::new( + context, + "DEFAULT", + ElectricalBusType::AlternatingCurrentEssential, + ) } -fn engine_driven_pump() -> EngineDrivenPump { - EngineDrivenPump::new("DEFAULT") +fn engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { + EngineDrivenPump::new(context, "DEFAULT") } -fn context(delta_time: Duration) -> UpdateContext { +fn context(context: &mut InitContext, delta_time: Duration) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), diff --git a/src/systems/a320_systems/src/electrical/alternating_current.rs b/src/systems/a320_systems/src/electrical/alternating_current.rs index 16566d240b4..a179efdb286 100644 --- a/src/systems/a320_systems/src/electrical/alternating_current.rs +++ b/src/systems/a320_systems/src/electrical/alternating_current.rs @@ -3,11 +3,11 @@ use super::{ A320ElectricalOverheadPanel, A320EmergencyElectricalOverheadPanel, }; use std::time::Duration; +use systems::simulation::InitContext; use systems::{ electrical::{ - AlternatingCurrentElectricalSystem, Contactor, ElectricalBus, - ElectricalElementIdentifierProvider, Electricity, EmergencyGenerator, EngineGenerator, - ExternalPowerSource, TransformerRectifier, + AlternatingCurrentElectricalSystem, Contactor, ElectricalBus, Electricity, + EmergencyGenerator, EngineGenerator, ExternalPowerSource, TransformerRectifier, }, shared::{ AuxiliaryPowerUnitElectrical, DelayedTrueLogicGate, ElectricalBusType, EngineCorrectedN2, @@ -37,46 +37,34 @@ pub(super) struct A320AlternatingCurrentElectrical { ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor: Contactor, } impl A320AlternatingCurrentElectrical { - pub fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + pub fn new(context: &mut InitContext) -> Self { A320AlternatingCurrentElectrical { - main_power_sources: A320MainPowerSources::new(identifier_provider), - ac_ess_feed_contactors: A320AcEssFeedContactors::new(identifier_provider), - ac_bus_1: ElectricalBus::new( - ElectricalBusType::AlternatingCurrent(1), - identifier_provider, - ), - ac_bus_2: ElectricalBus::new( - ElectricalBusType::AlternatingCurrent(2), - identifier_provider, - ), - ac_ess_bus: ElectricalBus::new( - ElectricalBusType::AlternatingCurrentEssential, - identifier_provider, - ), + main_power_sources: A320MainPowerSources::new(context), + ac_ess_feed_contactors: A320AcEssFeedContactors::new(context), + ac_bus_1: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_bus_2: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + ac_ess_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrentEssential), ac_ess_shed_bus: ElectricalBus::new( + context, ElectricalBusType::AlternatingCurrentEssentialShed, - identifier_provider, ), - ac_ess_shed_contactor: Contactor::new("8XH", identifier_provider), - tr_1: TransformerRectifier::new(1, identifier_provider), - tr_2: TransformerRectifier::new(2, identifier_provider), - ac_bus_2_to_tr_2_contactor: Contactor::new("14PU", identifier_provider), - tr_ess: TransformerRectifier::new(3, identifier_provider), - ac_ess_to_tr_ess_contactor: Contactor::new("15XE1", identifier_provider), - emergency_gen_contactor: Contactor::new("2XE", identifier_provider), - static_inv_to_ac_ess_bus_contactor: Contactor::new("15XE2", identifier_provider), + ac_ess_shed_contactor: Contactor::new(context, "8XH"), + tr_1: TransformerRectifier::new(context, 1), + tr_2: TransformerRectifier::new(context, 2), + ac_bus_2_to_tr_2_contactor: Contactor::new(context, "14PU"), + tr_ess: TransformerRectifier::new(context, 3), + ac_ess_to_tr_ess_contactor: Contactor::new(context, "15XE1"), + emergency_gen_contactor: Contactor::new(context, "2XE"), + static_inv_to_ac_ess_bus_contactor: Contactor::new(context, "15XE2"), ac_stat_inv_bus: ElectricalBus::new( + context, ElectricalBusType::AlternatingCurrentStaticInverter, - identifier_provider, ), ac_gnd_flt_service_bus: ElectricalBus::new( + context, ElectricalBusType::AlternatingCurrentGndFltService, - identifier_provider, - ), - ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor: Contactor::new( - "12XN", - identifier_provider, ), + ext_pwr_to_ac_gnd_flt_service_bus_and_tr_2_contactor: Contactor::new(context, "12XN"), } } @@ -358,18 +346,18 @@ struct A320MainPowerSources { ext_pwr_contactor: Contactor, } impl A320MainPowerSources { - fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + fn new(context: &mut InitContext) -> Self { A320MainPowerSources { - engine_1_gen: EngineGenerator::new(1, identifier_provider), - engine_2_gen: EngineGenerator::new(2, identifier_provider), + engine_1_gen: EngineGenerator::new(context, 1), + engine_2_gen: EngineGenerator::new(context, 2), engine_generator_contactors: [ - Contactor::new("9XU1", identifier_provider), - Contactor::new("9XU2", identifier_provider), + Contactor::new(context, "9XU1"), + Contactor::new(context, "9XU2"), ], - bus_tie_1_contactor: Contactor::new("11XU1", identifier_provider), - bus_tie_2_contactor: Contactor::new("11XU2", identifier_provider), - apu_gen_contactor: Contactor::new("3XS", identifier_provider), - ext_pwr_contactor: Contactor::new("3XG", identifier_provider), + bus_tie_1_contactor: Contactor::new(context, "11XU1"), + bus_tie_2_contactor: Contactor::new(context, "11XU2"), + apu_gen_contactor: Contactor::new(context, "3XS"), + ext_pwr_contactor: Contactor::new(context, "3XG"), } } @@ -491,10 +479,10 @@ pub(super) struct A320AcEssFeedContactors { impl A320AcEssFeedContactors { pub const AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS: Duration = Duration::from_secs(3); - fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + fn new(context: &mut InitContext) -> Self { A320AcEssFeedContactors { - ac_ess_feed_contactor_1: Contactor::new("3XC1", identifier_provider), - ac_ess_feed_contactor_2: Contactor::new("3XC2", identifier_provider), + ac_ess_feed_contactor_1: Contactor::new(context, "3XC1"), + ac_ess_feed_contactor_2: Contactor::new(context, "3XC2"), ac_ess_feed_contactor_delay_logic_gate: DelayedTrueLogicGate::new( A320AcEssFeedContactors::AC_ESS_FEED_TO_AC_BUS_2_DELAY_IN_SECONDS, ), diff --git a/src/systems/a320_systems/src/electrical/direct_current.rs b/src/systems/a320_systems/src/electrical/direct_current.rs index f76a1c5cd1b..c526d6aa884 100644 --- a/src/systems/a320_systems/src/electrical/direct_current.rs +++ b/src/systems/a320_systems/src/electrical/direct_current.rs @@ -2,11 +2,11 @@ use super::{ A320AlternatingCurrentElectricalSystem, A320DirectCurrentElectricalSystem, A320ElectricalOverheadPanel, }; +use systems::simulation::InitContext; use systems::{ electrical::{ - Battery, BatteryChargeLimiter, Contactor, ElectricalBus, - ElectricalElementIdentifierProvider, Electricity, EmergencyElectrical, EmergencyGenerator, - StaticInverter, + Battery, BatteryChargeLimiter, Contactor, ElectricalBus, Electricity, EmergencyElectrical, + EmergencyGenerator, StaticInverter, }, shared::{ ApuMaster, ApuStart, AuxiliaryPowerUnitElectrical, ContactorSignal, ElectricalBusType, @@ -49,57 +49,42 @@ pub(super) struct A320DirectCurrentElectrical { dc_bus_2_to_dc_gnd_flt_service_bus_contactor: Contactor, } impl A320DirectCurrentElectrical { - pub fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + pub fn new(context: &mut InitContext) -> Self { A320DirectCurrentElectrical { - dc_bus_1: ElectricalBus::new(ElectricalBusType::DirectCurrent(1), identifier_provider), - dc_bus_1_tie_contactor: Contactor::new("1PC1", identifier_provider), - dc_bus_2: ElectricalBus::new(ElectricalBusType::DirectCurrent(2), identifier_provider), - dc_bus_2_tie_contactor: Contactor::new("1PC2", identifier_provider), - dc_bat_bus: ElectricalBus::new( - ElectricalBusType::DirectCurrentBattery, - identifier_provider, - ), - dc_ess_bus: ElectricalBus::new( - ElectricalBusType::DirectCurrentEssential, - identifier_provider, - ), - dc_bat_bus_to_dc_ess_bus_contactor: Contactor::new("4PC", identifier_provider), + dc_bus_1: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_bus_1_tie_contactor: Contactor::new(context, "1PC1"), + dc_bus_2: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), + dc_bus_2_tie_contactor: Contactor::new(context, "1PC2"), + dc_bat_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentBattery), + dc_ess_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentEssential), + dc_bat_bus_to_dc_ess_bus_contactor: Contactor::new(context, "4PC"), dc_ess_shed_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentEssentialShed, - identifier_provider, - ), - dc_ess_shed_contactor: Contactor::new("8PH", identifier_provider), - battery_1: Battery::full(1, identifier_provider), - battery_1_contactor: Contactor::new("6PB1", identifier_provider), - battery_1_charge_limiter: BatteryChargeLimiter::new(1, "6PB1"), - battery_2: Battery::full(2, identifier_provider), - battery_2_contactor: Contactor::new("6PB2", identifier_provider), - battery_2_charge_limiter: BatteryChargeLimiter::new(2, "6PB2"), - hot_bus_2_to_dc_ess_bus_contactor: Contactor::new("2XB2", identifier_provider), - hot_bus_1_to_static_inv_contactor: Contactor::new("2XB1", identifier_provider), - static_inverter: StaticInverter::new(identifier_provider), - hot_bus_1: ElectricalBus::new( - ElectricalBusType::DirectCurrentHot(1), - identifier_provider, ), - hot_bus_2: ElectricalBus::new( - ElectricalBusType::DirectCurrentHot(2), - identifier_provider, - ), - tr_1_contactor: Contactor::new("5PU1", identifier_provider), - tr_2_contactor: Contactor::new("5PU2", identifier_provider), - tr_ess_contactor: Contactor::new("3PE", identifier_provider), - apu_start_contactors: Contactor::new("10KA_AND_5KA", identifier_provider), - apu_start_motor_bus: ElectricalBus::new(APU_START_MOTOR_BUS_TYPE, identifier_provider), + dc_ess_shed_contactor: Contactor::new(context, "8PH"), + battery_1: Battery::full(context, 1), + battery_1_contactor: Contactor::new(context, "6PB1"), + battery_1_charge_limiter: BatteryChargeLimiter::new(context, 1, "6PB1"), + battery_2: Battery::full(context, 2), + battery_2_contactor: Contactor::new(context, "6PB2"), + battery_2_charge_limiter: BatteryChargeLimiter::new(context, 2, "6PB2"), + hot_bus_2_to_dc_ess_bus_contactor: Contactor::new(context, "2XB2"), + hot_bus_1_to_static_inv_contactor: Contactor::new(context, "2XB1"), + static_inverter: StaticInverter::new(context), + hot_bus_1: ElectricalBus::new(context, ElectricalBusType::DirectCurrentHot(1)), + hot_bus_2: ElectricalBus::new(context, ElectricalBusType::DirectCurrentHot(2)), + tr_1_contactor: Contactor::new(context, "5PU1"), + tr_2_contactor: Contactor::new(context, "5PU2"), + tr_ess_contactor: Contactor::new(context, "3PE"), + apu_start_contactors: Contactor::new(context, "10KA_AND_5KA"), + apu_start_motor_bus: ElectricalBus::new(context, APU_START_MOTOR_BUS_TYPE), dc_gnd_flt_service_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentGndFltService, - identifier_provider, - ), - tr_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new("3PX", identifier_provider), - dc_bus_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new( - "8PN", - identifier_provider, ), + tr_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new(context, "3PX"), + dc_bus_2_to_dc_gnd_flt_service_bus_contactor: Contactor::new(context, "8PN"), } } diff --git a/src/systems/a320_systems/src/electrical/mod.rs b/src/systems/a320_systems/src/electrical/mod.rs index 44278c4cb89..b0606f821ba 100644 --- a/src/systems/a320_systems/src/electrical/mod.rs +++ b/src/systems/a320_systems/src/electrical/mod.rs @@ -10,12 +10,14 @@ use self::{ pub(super) use direct_current::APU_START_MOTOR_BUS_TYPE; #[cfg(test)] use systems::electrical::Battery; + +use systems::simulation::VariableIdentifier; use systems::{ accept_iterable, electrical::{ - AlternatingCurrentElectricalSystem, BatteryPushButtons, - ElectricalElementIdentifierProvider, Electricity, EmergencyElectrical, EmergencyGenerator, - EngineGeneratorPushButtons, ExternalPowerSource, StaticInverter, TransformerRectifier, + AlternatingCurrentElectricalSystem, BatteryPushButtons, Electricity, EmergencyElectrical, + EmergencyGenerator, EngineGeneratorPushButtons, ExternalPowerSource, StaticInverter, + TransformerRectifier, }, overhead::{ AutoOffFaultPushButton, FaultIndication, FaultReleasePushButton, MomentaryPushButton, @@ -27,11 +29,14 @@ use systems::{ LandingGearRealPosition, RamAirTurbineHydraulicLoopPressurised, }, simulation::{ - SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, Write, + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + Write, }, }; pub(super) struct A320Electrical { + galley_is_shed_id: VariableIdentifier, + alternating_current: A320AlternatingCurrentElectrical, direct_current: A320DirectCurrentElectrical, main_galley: MainGalley, @@ -40,16 +45,15 @@ pub(super) struct A320Electrical { emergency_gen: EmergencyGenerator, } impl A320Electrical { - pub fn new( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> A320Electrical { + pub fn new(context: &mut InitContext) -> A320Electrical { A320Electrical { - alternating_current: A320AlternatingCurrentElectrical::new(identifier_provider), - direct_current: A320DirectCurrentElectrical::new(identifier_provider), + galley_is_shed_id: context.get_identifier("ELEC_GALLEY_IS_SHED".to_owned()), + alternating_current: A320AlternatingCurrentElectrical::new(context), + direct_current: A320DirectCurrentElectrical::new(context), main_galley: MainGalley::new(), secondary_galley: SecondaryGalley::new(), emergency_elec: EmergencyElectrical::new(), - emergency_gen: EmergencyGenerator::new(identifier_provider), + emergency_gen: EmergencyGenerator::new(context), } } @@ -198,7 +202,7 @@ impl SimulationElement for A320Electrical { } fn write(&self, writer: &mut SimulatorWriter) { - writer.write("ELEC_GALLEY_IS_SHED", self.galley_is_shed()) + writer.write(&self.galley_is_shed_id, self.galley_is_shed()) } } impl EmergencyElectricalState for A320Electrical { @@ -231,26 +235,26 @@ pub(super) struct A320ElectricalOverheadPanel { commercial: OnOffFaultPushButton, } impl A320ElectricalOverheadPanel { - pub fn new() -> A320ElectricalOverheadPanel { + pub fn new(context: &mut InitContext) -> A320ElectricalOverheadPanel { A320ElectricalOverheadPanel { batteries: [ - AutoOffFaultPushButton::new_auto("ELEC_BAT_1"), - AutoOffFaultPushButton::new_auto("ELEC_BAT_2"), + AutoOffFaultPushButton::new_auto(context, "ELEC_BAT_1"), + AutoOffFaultPushButton::new_auto(context, "ELEC_BAT_2"), ], idgs: [ - FaultReleasePushButton::new_in("ELEC_IDG_1"), - FaultReleasePushButton::new_in("ELEC_IDG_2"), + FaultReleasePushButton::new_in(context, "ELEC_IDG_1"), + FaultReleasePushButton::new_in(context, "ELEC_IDG_2"), ], generators: [ - OnOffFaultPushButton::new_on("ELEC_ENG_GEN_1"), - OnOffFaultPushButton::new_on("ELEC_ENG_GEN_2"), + OnOffFaultPushButton::new_on(context, "ELEC_ENG_GEN_1"), + OnOffFaultPushButton::new_on(context, "ELEC_ENG_GEN_2"), ], - apu_gen: OnOffFaultPushButton::new_on("ELEC_APU_GEN"), - bus_tie: AutoOffFaultPushButton::new_auto("ELEC_BUS_TIE"), - ac_ess_feed: NormalAltnFaultPushButton::new_normal("ELEC_AC_ESS_FEED"), - galy_and_cab: AutoOffFaultPushButton::new_auto("ELEC_GALY_AND_CAB"), - ext_pwr: OnOffAvailablePushButton::new_off("ELEC_EXT_PWR"), - commercial: OnOffFaultPushButton::new_on("ELEC_COMMERCIAL"), + apu_gen: OnOffFaultPushButton::new_on(context, "ELEC_APU_GEN"), + bus_tie: AutoOffFaultPushButton::new_auto(context, "ELEC_BUS_TIE"), + ac_ess_feed: NormalAltnFaultPushButton::new_normal(context, "ELEC_AC_ESS_FEED"), + galy_and_cab: AutoOffFaultPushButton::new_auto(context, "ELEC_GALY_AND_CAB"), + ext_pwr: OnOffAvailablePushButton::new_off(context, "ELEC_EXT_PWR"), + commercial: OnOffFaultPushButton::new_on(context, "ELEC_COMMERCIAL"), } } @@ -344,11 +348,17 @@ pub(super) struct A320EmergencyElectricalOverheadPanel { rat_and_emer_gen_man_on: MomentaryPushButton, } impl A320EmergencyElectricalOverheadPanel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { - gen_1_line: OnOffFaultPushButton::new_on("EMER_ELEC_GEN_1_LINE"), - rat_and_emergency_gen_fault: FaultIndication::new("EMER_ELEC_RAT_AND_EMER_GEN"), - rat_and_emer_gen_man_on: MomentaryPushButton::new("EMER_ELEC_RAT_AND_EMER_GEN"), + gen_1_line: OnOffFaultPushButton::new_on(context, "EMER_ELEC_GEN_1_LINE"), + rat_and_emergency_gen_fault: FaultIndication::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), + rat_and_emer_gen_man_on: MomentaryPushButton::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), } } @@ -397,7 +407,7 @@ mod a320_electrical { test_bed.run(); - assert!(test_bed.contains_key("ELEC_GALLEY_IS_SHED")); + assert!(test_bed.contains_variable_with_name("ELEC_GALLEY_IS_SHED")); } } @@ -408,8 +418,8 @@ mod a320_electrical_circuit_tests { use std::{cell::Ref, time::Duration}; use systems::{ electrical::{ - ElectricalElement, ElectricalElementIdentifier, Electricity, ElectricitySource, - ExternalPowerSource, Potential, + ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, + Electricity, ElectricitySource, ExternalPowerSource, Potential, INTEGRATED_DRIVE_GENERATOR_STABILIZATION_TIME_IN_MILLISECONDS, }, failures::FailureType, @@ -418,8 +428,8 @@ mod a320_electrical_circuit_tests { PotentialOrigin, }, simulation::{ - test::{SimulationTestBed, TestBed}, - Aircraft, Read, + test::{ReadByName, SimulationTestBed, TestBed, WriteByName}, + Aircraft, }, }; use uom::si::f64::*; @@ -2036,9 +2046,9 @@ mod a320_electrical_circuit_tests { should_close_start_contactor: bool, } impl TestApu { - fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - identifier: identifier_provider.next(), + identifier: context.next_electrical_identifier(), is_available: false, start_motor_is_powered: false, should_close_start_contactor: false, @@ -2223,14 +2233,14 @@ mod a320_electrical_circuit_tests { engine_fire_push_buttons: TestEngineFirePushButtons, } impl A320ElectricalTestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { engines: [TestEngine::new(), TestEngine::new()], - ext_pwr: ExternalPowerSource::new(electricity), - elec: A320Electrical::new(electricity), - overhead: A320ElectricalOverheadPanel::new(), - emergency_overhead: A320EmergencyElectricalOverheadPanel::new(), - apu: TestApu::new(electricity), + ext_pwr: ExternalPowerSource::new(context), + elec: A320Electrical::new(context), + overhead: A320ElectricalOverheadPanel::new(context), + emergency_overhead: A320EmergencyElectricalOverheadPanel::new(context), + apu: TestApu::new(context), apu_overhead: TestApuOverhead::new(), engine_fire_push_buttons: TestEngineFirePushButtons::new(), } @@ -2349,9 +2359,7 @@ mod a320_electrical_circuit_tests { impl A320ElectricalTestBed { fn new() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - A320ElectricalTestAircraft::new(electricity) - }), + test_bed: SimulationTestBed::new(A320ElectricalTestAircraft::new), } } @@ -2382,7 +2390,7 @@ mod a320_electrical_circuit_tests { } fn connected_external_power(mut self) -> Self { - self.write("EXTERNAL POWER AVAILABLE:1", true); + self.write_by_name("EXTERNAL POWER AVAILABLE:1", true); self.without_triggering_emergency_elec(|x| x.run()) } @@ -2441,7 +2449,7 @@ mod a320_electrical_circuit_tests { } fn gen_off(mut self, number: usize) -> Self { - self.write(&format!("OVHD_ELEC_ENG_GEN_{}_PB_IS_ON", number), false); + self.write_by_name(&format!("OVHD_ELEC_ENG_GEN_{}_PB_IS_ON", number), false); self } @@ -2451,27 +2459,27 @@ mod a320_electrical_circuit_tests { } fn gen_1_line_off(mut self) -> Self { - self.write("OVHD_EMER_ELEC_GEN_1_LINE_PB_IS_ON", false); + self.write_by_name("OVHD_EMER_ELEC_GEN_1_LINE_PB_IS_ON", false); self } fn apu_gen_off(mut self) -> Self { - self.write("OVHD_ELEC_APU_GEN_PB_IS_ON", false); + self.write_by_name("OVHD_ELEC_APU_GEN_PB_IS_ON", false); self } fn ext_pwr_on(mut self) -> Self { - self.write("OVHD_ELEC_EXT_PWR_PB_IS_ON", true); + self.write_by_name("OVHD_ELEC_EXT_PWR_PB_IS_ON", true); self } fn ext_pwr_off(mut self) -> Self { - self.write("OVHD_ELEC_EXT_PWR_PB_IS_ON", false); + self.write_by_name("OVHD_ELEC_EXT_PWR_PB_IS_ON", false); self } fn ac_ess_feed_altn(mut self) -> Self { - self.write("OVHD_ELEC_AC_ESS_FEED_PB_IS_NORMAL", false); + self.write_by_name("OVHD_ELEC_AC_ESS_FEED_PB_IS_NORMAL", false); self } @@ -2484,12 +2492,12 @@ mod a320_electrical_circuit_tests { } fn bat(mut self, number: usize, auto: bool) -> Self { - self.write(&format!("OVHD_ELEC_BAT_{}_PB_IS_AUTO", number), auto); + self.write_by_name(&format!("OVHD_ELEC_BAT_{}_PB_IS_AUTO", number), auto); self } fn bus_tie(mut self, auto: bool) -> Self { - self.write("OVHD_ELEC_BUS_TIE_PB_IS_AUTO", auto); + self.write_by_name("OVHD_ELEC_BUS_TIE_PB_IS_AUTO", auto); self } @@ -2502,12 +2510,12 @@ mod a320_electrical_circuit_tests { } fn commercial_off(mut self) -> Self { - self.write("OVHD_ELEC_COMMERCIAL_PB_IS_ON", false); + self.write_by_name("OVHD_ELEC_COMMERCIAL_PB_IS_ON", false); self } fn galy_and_cab_off(mut self) -> Self { - self.write("OVHD_ELEC_GALY_AND_CAB_PB_IS_AUTO", false); + self.write_by_name("OVHD_ELEC_GALY_AND_CAB_PB_IS_AUTO", false); self } @@ -2522,7 +2530,7 @@ mod a320_electrical_circuit_tests { } fn rat_and_emer_gen_man_on_pressed(mut self) -> Self { - self.write("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_IS_PRESSED", true); + self.write_by_name("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_IS_PRESSED", true); self } @@ -2532,7 +2540,7 @@ mod a320_electrical_circuit_tests { } fn apu_start_contactors_closed(&mut self) -> bool { - self.read("ELEC_CONTACTOR_10KA_AND_5KA_IS_CLOSED") + self.read_by_name("ELEC_CONTACTOR_10KA_AND_5KA_IS_CLOSED") } fn apu_start_motor_is_powered(&self) -> bool { @@ -2630,28 +2638,36 @@ mod a320_electrical_circuit_tests { } fn ac_ess_feed_has_fault(&mut self) -> bool { - self.read("OVHD_ELEC_AC_ESS_FEED_PB_HAS_FAULT") + self.read_by_name("OVHD_ELEC_AC_ESS_FEED_PB_HAS_FAULT") } fn gen_has_fault(&mut self, number: usize) -> bool { - self.read(&format!("OVHD_ELEC_ENG_GEN_{}_PB_HAS_FAULT", number)) + self.read_by_name(&format!("OVHD_ELEC_ENG_GEN_{}_PB_HAS_FAULT", number)) } fn rat_and_emer_gen_has_fault(&mut self) -> bool { - self.read("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_HAS_FAULT") + self.read_by_name("OVHD_EMER_ELEC_RAT_AND_EMER_GEN_HAS_FAULT") } fn galley_is_shed(&mut self) -> bool { - self.read("ELEC_GALLEY_IS_SHED") + self.read_by_name("ELEC_GALLEY_IS_SHED") } fn both_ac_ess_feed_contactors_open(&mut self) -> bool { - !Read::::read(self, "ELEC_CONTACTOR_3XC1_IS_CLOSED") - && !Read::::read(self, "ELEC_CONTACTOR_3XC2_IS_CLOSED") + !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_3XC1_IS_CLOSED", + ) && !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_3XC2_IS_CLOSED", + ) } fn dc_bus_2_tie_contactor_is_open(&mut self) -> bool { - !Read::::read(self, "ELEC_CONTACTOR_1PC2_IS_CLOSED") + !ReadByName::::read_by_name( + self, + "ELEC_CONTACTOR_1PC2_IS_CLOSED", + ) } fn run(self) -> Self { diff --git a/src/systems/a320_systems/src/fuel.rs b/src/systems/a320_systems/src/fuel.rs index 4862309923b..fa3ae57a072 100644 --- a/src/systems/a320_systems/src/fuel.rs +++ b/src/systems/a320_systems/src/fuel.rs @@ -1,13 +1,22 @@ -use systems::simulation::{Read, SimulationElement, SimulatorReader}; +use systems::simulation::{ + InitContext, Read, SimulationElement, SimulatorReader, VariableIdentifier, +}; use uom::si::{f64::*, mass::kilogram}; pub struct A320Fuel { + unlimited_fuel_id: VariableIdentifier, + fuel_tank_left_main_quantity_id: VariableIdentifier, + unlimited_fuel: bool, left_inner_tank_fuel_quantity: Mass, } impl A320Fuel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { A320Fuel { + unlimited_fuel_id: context.get_identifier("UNLIMITED FUEL".to_owned()), + fuel_tank_left_main_quantity_id: context + .get_identifier("FUEL TANK LEFT MAIN QUANTITY".to_owned()), + unlimited_fuel: false, left_inner_tank_fuel_quantity: Mass::new::(0.), } @@ -19,7 +28,7 @@ impl A320Fuel { } impl SimulationElement for A320Fuel { fn read(&mut self, reader: &mut SimulatorReader) { - self.unlimited_fuel = reader.read("UNLIMITED FUEL"); - self.left_inner_tank_fuel_quantity = reader.read("FUEL TANK LEFT MAIN QUANTITY"); + self.unlimited_fuel = reader.read(&self.unlimited_fuel_id); + self.left_inner_tank_fuel_quantity = reader.read(&self.fuel_tank_left_main_quantity_id); } } diff --git a/src/systems/a320_systems/src/hydraulic.rs b/src/systems/a320_systems/src/hydraulic.rs index d5fa1b99619..13f6caab22c 100644 --- a/src/systems/a320_systems/src/hydraulic.rs +++ b/src/systems/a320_systems/src/hydraulic.rs @@ -47,6 +47,8 @@ use systems::{ }, }; +use systems::simulation::{InitContext, VariableIdentifier}; + struct A320CargoDoorFactory {} impl A320CargoDoorFactory { fn a320_cargo_door_actuator( @@ -94,12 +96,20 @@ impl A320CargoDoorFactory { HydraulicLinearActuatorAssembly::new(cargo_door_actuator, cargo_door_body) } - fn new_a320_cargo_door(id: &str) -> CargoDoor { - CargoDoor::new(id, A320CargoDoorFactory::a320_cargo_door_assembly()) + fn new_a320_cargo_door(context: &mut InitContext, id: &str) -> CargoDoor { + CargoDoor::new( + context, + id, + A320CargoDoorFactory::a320_cargo_door_assembly(), + ) } } pub(super) struct A320Hydraulic { + yellow_epump_flow_id: VariableIdentifier, + blue_epump_flow_id: VariableIdentifier, + ptu_high_pitch_sound_id: VariableIdentifier, + core_hydraulic_updater: FixedStepLoop, physics_updater: MaxFixedStepLoop, @@ -182,14 +192,19 @@ impl A320Hydraulic { // Refresh rate of max fixed step loop for fast physics const HYDRAULIC_SIM_MAX_TIME_STEP_MILLISECONDS: Duration = Duration::from_millis(50); - pub(super) fn new() -> A320Hydraulic { + pub(super) fn new(context: &mut InitContext) -> A320Hydraulic { A320Hydraulic { + yellow_epump_flow_id: context.get_identifier("HYD_YELLOW_EPUMP_FLOW".to_owned()), + blue_epump_flow_id: context.get_identifier("HYD_BLUE_EPUMP_FLOW".to_owned()), + ptu_high_pitch_sound_id: context.get_identifier("HYD_PTU_HIGH_PITCH_SOUND".to_owned()), + core_hydraulic_updater: FixedStepLoop::new(Self::HYDRAULIC_SIM_TIME_STEP), physics_updater: MaxFixedStepLoop::new(Self::HYDRAULIC_SIM_MAX_TIME_STEP_MILLISECONDS), - brake_computer: A320HydraulicBrakeComputerUnit::new(), + brake_computer: A320HydraulicBrakeComputerUnit::new(context), blue_loop: HydraulicLoop::new( + context, "BLUE", false, false, @@ -204,6 +219,7 @@ impl A320Hydraulic { ), blue_loop_controller: A320HydraulicLoopController::new(None), green_loop: HydraulicLoop::new( + context, "GREEN", true, false, @@ -218,6 +234,7 @@ impl A320Hydraulic { ), green_loop_controller: A320HydraulicLoopController::new(Some(1)), yellow_loop: HydraulicLoop::new( + context, "YELLOW", false, true, @@ -236,8 +253,9 @@ impl A320Hydraulic { Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), ), - engine_driven_pump_1: EngineDrivenPump::new("GREEN"), + engine_driven_pump_1: EngineDrivenPump::new(context, "GREEN"), engine_driven_pump_1_controller: A320EngineDrivenPumpController::new( + context, 1, vec![Self::GREEN_EDP_CONTROL_POWER_BUS1], ), @@ -246,8 +264,9 @@ impl A320Hydraulic { Pressure::new::(Self::MIN_PRESS_EDP_SECTION_HI_HYST), Pressure::new::(Self::MIN_PRESS_EDP_SECTION_LO_HYST), ), - engine_driven_pump_2: EngineDrivenPump::new("YELLOW"), + engine_driven_pump_2: EngineDrivenPump::new(context, "YELLOW"), engine_driven_pump_2_controller: A320EngineDrivenPumpController::new( + context, 2, vec![ Self::YELLOW_EDP_CONTROL_POWER_BUS1, @@ -255,34 +274,43 @@ impl A320Hydraulic { ], ), - blue_electric_pump: ElectricPump::new("BLUE", Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS), + blue_electric_pump: ElectricPump::new( + context, + "BLUE", + Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS, + ), blue_electric_pump_controller: A320BlueElectricPumpController::new( + context, Self::BLUE_ELEC_PUMP_CONTROL_POWER_BUS, ), yellow_electric_pump: ElectricPump::new( + context, "YELLOW", Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ), yellow_electric_pump_controller: A320YellowElectricPumpController::new( + context, Self::YELLOW_ELEC_PUMP_CONTROL_POWER_BUS, Self::YELLOW_ELEC_PUMP_CONTROL_FROM_CARGO_DOOR_OPERATION_POWER_BUS, ), - pushback_tug: PushbackTug::new(), + pushback_tug: PushbackTug::new(context), - ram_air_turbine: RamAirTurbine::new(), + ram_air_turbine: RamAirTurbine::new(context), ram_air_turbine_controller: A320RamAirTurbineController::new( Self::RAT_CONTROL_SOLENOID1_POWER_BUS, Self::RAT_CONTROL_SOLENOID2_POWER_BUS, ), - power_transfer_unit: PowerTransferUnit::new(), + power_transfer_unit: PowerTransferUnit::new(context), power_transfer_unit_controller: A320PowerTransferUnitController::new( + context, Self::PTU_CONTROL_POWER_BUS, ), braking_circuit_norm: BrakeCircuit::new( + context, "NORM", Volume::new::(0.), Volume::new::(0.), @@ -290,21 +318,29 @@ impl A320Hydraulic { ), braking_circuit_altn: BrakeCircuit::new( + context, "ALTN", Volume::new::(1.5), Volume::new::(0.5), Volume::new::(0.13), ), - braking_force: A320BrakingForce::new(), + braking_force: A320BrakingForce::new(context), forward_cargo_door: A320CargoDoorFactory::new_a320_cargo_door( + context, + Self::FORWARD_CARGO_DOOR_ID, + ), + forward_cargo_door_controller: A320DoorController::new( + context, Self::FORWARD_CARGO_DOOR_ID, ), - forward_cargo_door_controller: A320DoorController::new(Self::FORWARD_CARGO_DOOR_ID), - aft_cargo_door: A320CargoDoorFactory::new_a320_cargo_door(Self::AFT_CARGO_DOOR_ID), - aft_cargo_door_controller: A320DoorController::new(Self::AFT_CARGO_DOOR_ID), + aft_cargo_door: A320CargoDoorFactory::new_a320_cargo_door( + context, + Self::AFT_CARGO_DOOR_ID, + ), + aft_cargo_door_controller: A320DoorController::new(context, Self::AFT_CARGO_DOOR_ID), } } @@ -736,19 +772,19 @@ impl SimulationElement for A320Hydraulic { fn write(&self, writer: &mut SimulatorWriter) { writer.write( - "HYD_YELLOW_EPUMP_FLOW", + &self.yellow_epump_flow_id, self.yellow_electric_pump_estimated_flow() .get::(), ); writer.write( - "HYD_BLUE_EPUMP_FLOW", + &self.blue_epump_flow_id, self.blue_electric_pump_estimated_flow() .get::(), ); writer.write( - "HYD_PTU_HIGH_PITCH_SOUND", + &self.ptu_high_pitch_sound_id, self.is_ptu_running_high_pitch_sound(), ); } @@ -779,6 +815,9 @@ impl HydraulicLoopController for A320HydraulicLoopController { } struct A320EngineDrivenPumpController { + green_pump_low_press_id: VariableIdentifier, + yellow_pump_low_press_id: VariableIdentifier, + is_powered: bool, powered_by: Vec, engine_number: usize, @@ -789,8 +828,17 @@ struct A320EngineDrivenPumpController { impl A320EngineDrivenPumpController { const MIN_ENGINE_OIL_PRESS_THRESHOLD_TO_INHIBIT_FAULT: f64 = 18.; - fn new(engine_number: usize, powered_by: Vec) -> Self { + fn new( + context: &mut InitContext, + engine_number: usize, + powered_by: Vec, + ) -> Self { Self { + green_pump_low_press_id: context + .get_identifier("HYD_GREEN_EDPUMP_LOW_PRESS".to_owned()), + yellow_pump_low_press_id: context + .get_identifier("HYD_YELLOW_EDPUMP_LOW_PRESS".to_owned()), + is_powered: false, powered_by, engine_number, @@ -867,9 +915,9 @@ impl PumpController for A320EngineDrivenPumpController { impl SimulationElement for A320EngineDrivenPumpController { fn write(&self, writer: &mut SimulatorWriter) { if self.engine_number == 1 { - writer.write("HYD_GREEN_EDPUMP_LOW_PRESS", self.is_pressure_low); + writer.write(&self.green_pump_low_press_id, self.is_pressure_low); } else if self.engine_number == 2 { - writer.write("HYD_YELLOW_EDPUMP_LOW_PRESS", self.is_pressure_low); + writer.write(&self.yellow_pump_low_press_id, self.is_pressure_low); } else { panic!("The A320 only supports two engines."); } @@ -881,6 +929,8 @@ impl SimulationElement for A320EngineDrivenPumpController { } struct A320BlueElectricPumpController { + low_press_id: VariableIdentifier, + is_powered: bool, powered_by: ElectricalBusType, should_pressurise: bool, @@ -890,8 +940,10 @@ struct A320BlueElectricPumpController { impl A320BlueElectricPumpController { const MIN_ENGINE_OIL_PRESS_THRESHOLD_TO_INHIBIT_FAULT: f64 = 18.; - fn new(powered_by: ElectricalBusType) -> Self { + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { Self { + low_press_id: context.get_identifier("HYD_BLUE_EPUMP_LOW_PRESS".to_owned()), + is_powered: false, powered_by, should_pressurise: false, @@ -976,7 +1028,7 @@ impl PumpController for A320BlueElectricPumpController { impl SimulationElement for A320BlueElectricPumpController { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("HYD_BLUE_EPUMP_LOW_PRESS", self.is_pressure_low); + writer.write(&self.low_press_id, self.is_pressure_low); } fn receive_power(&mut self, buses: &impl ElectricalBuses) { @@ -985,6 +1037,8 @@ impl SimulationElement for A320BlueElectricPumpController { } struct A320YellowElectricPumpController { + low_press_id: VariableIdentifier, + is_powered: bool, powered_by: ElectricalBusType, powered_by_when_cargo_door_operation: ElectricalBusType, @@ -998,10 +1052,13 @@ impl A320YellowElectricPumpController { Duration::from_secs(20); fn new( + context: &mut InitContext, powered_by: ElectricalBusType, powered_by_when_cargo_door_operation: ElectricalBusType, ) -> Self { Self { + low_press_id: context.get_identifier("HYD_YELLOW_EPUMP_LOW_PRESS".to_owned()), + is_powered: false, powered_by, powered_by_when_cargo_door_operation, @@ -1061,7 +1118,7 @@ impl PumpController for A320YellowElectricPumpController { } impl SimulationElement for A320YellowElectricPumpController { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("HYD_YELLOW_EPUMP_LOW_PRESS", self.is_pressure_low); + writer.write(&self.low_press_id, self.is_pressure_low); } fn receive_power(&mut self, buses: &impl ElectricalBuses) { @@ -1075,6 +1132,10 @@ impl SimulationElement for A320YellowElectricPumpController { } struct A320PowerTransferUnitController { + park_brake_lever_pos_id: VariableIdentifier, + general_eng_1_starter_active_id: VariableIdentifier, + general_eng_2_starter_active_id: VariableIdentifier, + is_powered: bool, powered_by: ElectricalBusType, should_enable: bool, @@ -1090,8 +1151,14 @@ impl A320PowerTransferUnitController { const DURATION_AFTER_WHICH_NWS_PIN_IS_REMOVED_AFTER_PUSHBACK: Duration = Duration::from_secs(15); - fn new(powered_by: ElectricalBusType) -> Self { + fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { Self { + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + general_eng_1_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:1".to_owned()), + general_eng_2_starter_active_id: context + .get_identifier("GENERAL ENG STARTER ACTIVE:2".to_owned()), + is_powered: false, powered_by, should_enable: false, @@ -1152,9 +1219,9 @@ impl PowerTransferUnitController for A320PowerTransferUnitController { } impl SimulationElement for A320PowerTransferUnitController { fn read(&mut self, reader: &mut SimulatorReader) { - self.parking_brake_lever_pos = reader.read("PARK_BRAKE_LEVER_POS"); - self.eng_1_master_on = reader.read("GENERAL ENG STARTER ACTIVE:1"); - self.eng_2_master_on = reader.read("GENERAL ENG STARTER ACTIVE:2"); + self.parking_brake_lever_pos = reader.read(&self.park_brake_lever_pos_id); + self.eng_1_master_on = reader.read(&self.general_eng_1_starter_active_id); + self.eng_2_master_on = reader.read(&self.general_eng_2_starter_active_id); } fn receive_power(&mut self, buses: &impl ElectricalBuses) { @@ -1170,8 +1237,6 @@ struct A320RamAirTurbineController { solenoid_2_bus: ElectricalBusType, should_deploy: bool, - eng_1_master_on: bool, - eng_2_master_on: bool, } impl A320RamAirTurbineController { fn new(solenoid_1_bus: ElectricalBusType, solenoid_2_bus: ElectricalBusType) -> Self { @@ -1183,8 +1248,6 @@ impl A320RamAirTurbineController { solenoid_2_bus, should_deploy: false, - eng_1_master_on: false, - eng_2_master_on: false, } } @@ -1211,11 +1274,6 @@ impl RamAirTurbineController for A320RamAirTurbineController { } } impl SimulationElement for A320RamAirTurbineController { - fn read(&mut self, reader: &mut SimulatorReader) { - self.eng_1_master_on = reader.read("GENERAL ENG STARTER ACTIVE:1"); - self.eng_2_master_on = reader.read("GENERAL ENG STARTER ACTIVE:2"); - } - fn receive_power(&mut self, buses: &impl ElectricalBuses) { self.is_solenoid_1_powered = buses.is_powered(self.solenoid_1_bus); self.is_solenoid_2_powered = buses.is_powered(self.solenoid_2_bus); @@ -1223,6 +1281,12 @@ impl SimulationElement for A320RamAirTurbineController { } struct A320HydraulicBrakeComputerUnit { + park_brake_lever_pos_id: VariableIdentifier, + gear_handle_position_id: VariableIdentifier, + antiskid_brakes_active_id: VariableIdentifier, + left_brake_pedal_input_id: VariableIdentifier, + right_brake_pedal_input_id: VariableIdentifier, + autobrake_controller: A320AutobrakeController, parking_brake_demand: bool, is_gear_lever_down: bool, @@ -1254,9 +1318,16 @@ impl A320HydraulicBrakeComputerUnit { const PILOT_INPUT_DETECTION_TRESHOLD: f64 = 0.2; - fn new() -> A320HydraulicBrakeComputerUnit { + fn new(context: &mut InitContext) -> A320HydraulicBrakeComputerUnit { A320HydraulicBrakeComputerUnit { - autobrake_controller: A320AutobrakeController::new(), + park_brake_lever_pos_id: context.get_identifier("PARK_BRAKE_LEVER_POS".to_owned()), + gear_handle_position_id: context.get_identifier("GEAR HANDLE POSITION".to_owned()), + antiskid_brakes_active_id: context.get_identifier("ANTISKID BRAKES ACTIVE".to_owned()), + left_brake_pedal_input_id: context.get_identifier("LEFT_BRAKE_PEDAL_INPUT".to_owned()), + right_brake_pedal_input_id: context + .get_identifier("RIGHT_BRAKE_PEDAL_INPUT".to_owned()), + + autobrake_controller: A320AutobrakeController::new(context), // Position of parking brake lever parking_brake_demand: true, is_gear_lever_down: true, @@ -1443,15 +1514,22 @@ impl SimulationElement for A320HydraulicBrakeComputerUnit { } fn read(&mut self, reader: &mut SimulatorReader) { - self.parking_brake_demand = reader.read("PARK_BRAKE_LEVER_POS"); - self.is_gear_lever_down = reader.read("GEAR HANDLE POSITION"); - self.anti_skid_activated = reader.read("ANTISKID BRAKES ACTIVE"); - self.left_brake_pilot_input = Ratio::new::(reader.read("LEFT_BRAKE_PEDAL_INPUT")); - self.right_brake_pilot_input = Ratio::new::(reader.read("RIGHT_BRAKE_PEDAL_INPUT")); + self.parking_brake_demand = reader.read(&self.park_brake_lever_pos_id); + self.is_gear_lever_down = reader.read(&self.gear_handle_position_id); + self.anti_skid_activated = reader.read(&self.antiskid_brakes_active_id); + self.left_brake_pilot_input = + Ratio::new::(reader.read(&self.left_brake_pedal_input_id)); + self.right_brake_pilot_input = + Ratio::new::(reader.read(&self.right_brake_pedal_input_id)); } } struct A320BrakingForce { + brake_left_force_factor_id: VariableIdentifier, + brake_right_force_factor_id: VariableIdentifier, + trailing_edge_flaps_left_percent_id: VariableIdentifier, + trailing_edge_flaps_right_percent_id: VariableIdentifier, + left_braking_force: f64, right_braking_force: f64, @@ -1463,8 +1541,17 @@ impl A320BrakingForce { const FLAPS_BREAKPOINTS: [f64; 3] = [0., 50., 100.]; const FLAPS_PENALTY_PERCENT: [f64; 3] = [5., 5., 0.]; - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { A320BrakingForce { + brake_left_force_factor_id: context + .get_identifier("BRAKE LEFT FORCE FACTOR".to_owned()), + brake_right_force_factor_id: context + .get_identifier("BRAKE RIGHT FORCE FACTOR".to_owned()), + trailing_edge_flaps_left_percent_id: context + .get_identifier("TRAILING EDGE FLAPS LEFT PERCENT".to_owned()), + trailing_edge_flaps_right_percent_id: context + .get_identifier("TRAILING EDGE FLAPS RIGHT PERCENT".to_owned()), + left_braking_force: 0., right_braking_force: 0., @@ -1525,13 +1612,13 @@ impl A320BrakingForce { impl SimulationElement for A320BrakingForce { fn write(&self, writer: &mut SimulatorWriter) { // BRAKE XXXX FORCE FACTOR is the actual braking force we want the plane to generate in the simulator - writer.write("BRAKE LEFT FORCE FACTOR", self.left_braking_force); - writer.write("BRAKE RIGHT FORCE FACTOR", self.right_braking_force); + writer.write(&self.brake_left_force_factor_id, self.left_braking_force); + writer.write(&self.brake_right_force_factor_id, self.right_braking_force); } fn read(&mut self, reader: &mut SimulatorReader) { - let left_flap: f64 = reader.read("TRAILING EDGE FLAPS LEFT PERCENT"); - let right_flap: f64 = reader.read("TRAILING EDGE FLAPS RIGHT PERCENT"); + let left_flap: f64 = reader.read(&self.trailing_edge_flaps_left_percent_id); + let right_flap: f64 = reader.read(&self.trailing_edge_flaps_right_percent_id); self.flap_position = (left_flap + right_flap) / 2.; } } @@ -1545,7 +1632,7 @@ enum DoorControlState { } struct A320DoorController { - requested_position_id: String, + requested_position_id: VariableIdentifier, control_state: DoorControlState, @@ -1565,9 +1652,9 @@ impl A320DoorController { // Delay from the ground crew unlocking the door to the time they start requiring up movement in control panel const DELAY_UNLOCK_TO_HYDRAULIC_CONTROL: Duration = Duration::from_secs(5); - fn new(id: &str) -> Self { + fn new(context: &mut InitContext, id: &str) -> Self { Self { - requested_position_id: format!("{}_DOOR_CARGO_OPEN_REQ", id), + requested_position_id: context.get_identifier(format!("{}_DOOR_CARGO_OPEN_REQ", id)), control_state: DoorControlState::DownLocked, position_requested: Ratio::new::(0.), @@ -1695,18 +1782,22 @@ impl SimulationElement for A320DoorController { struct CargoDoor { hydraulic_assembly: HydraulicLinearActuatorAssembly, - position_id: String, - locked_id: String, + position_id: VariableIdentifier, + locked_id: VariableIdentifier, position: Ratio, is_locked: bool, } impl CargoDoor { - fn new(id: &str, hydraulic_assembly: HydraulicLinearActuatorAssembly) -> Self { + fn new( + context: &mut InitContext, + id: &str, + hydraulic_assembly: HydraulicLinearActuatorAssembly, + ) -> Self { Self { hydraulic_assembly, - position_id: format!("{}_DOOR_CARGO_POSITION", id), - locked_id: format!("{}_DOOR_CARGO_LOCKED", id), + position_id: context.get_identifier(format!("{}_DOOR_CARGO_POSITION", id)), + locked_id: context.get_identifier(format!("{}_DOOR_CARGO_LOCKED", id)), position: Ratio::new::(0.), @@ -1746,6 +1837,9 @@ impl SimulationElement for CargoDoor { } struct PushbackTug { + angle_id: VariableIdentifier, + state_id: VariableIdentifier, + angle: f64, previous_angle: f64, // Type of pushback: @@ -1760,8 +1854,11 @@ struct PushbackTug { impl PushbackTug { const STATE_NO_PUSHBACK: f64 = 3.; - fn new() -> Self { + fn new(context: &mut InitContext) -> Self { Self { + angle_id: context.get_identifier("PUSHBACK ANGLE".to_owned()), + state_id: context.get_identifier("PUSHBACK STATE".to_owned()), + angle: 0., previous_angle: 0., state: Self::STATE_NO_PUSHBACK, @@ -1790,14 +1887,19 @@ impl PushbackTug { impl SimulationElement for PushbackTug { fn read(&mut self, reader: &mut SimulatorReader) { self.previous_angle = self.angle; - self.angle = reader.read("PUSHBACK ANGLE"); - self.state = reader.read("PUSHBACK STATE"); + self.angle = reader.read(&self.angle_id); + self.state = reader.read(&self.state_id); } } /// Autobrake controller computes the state machine of the autobrake logic, and the deceleration target /// that we expect for the plane pub struct A320AutobrakeController { + armed_mode_id: VariableIdentifier, + decel_light_id: VariableIdentifier, + spoilers_ground_spoilers_active_id: VariableIdentifier, + external_disarm_event_id: VariableIdentifier, + deceleration_governor: AutobrakeDecelerationGovernor, target: Acceleration, @@ -1831,8 +1933,14 @@ impl A320AutobrakeController { const MARGIN_PERCENT_TO_TARGET_TO_SHOW_DECEL_IN_LO_MED: f64 = 80.; const TARGET_TO_SHOW_DECEL_IN_MAX_MS2: f64 = -2.7; - fn new() -> A320AutobrakeController { + fn new(context: &mut InitContext) -> A320AutobrakeController { A320AutobrakeController { + armed_mode_id: context.get_identifier("AUTOBRAKES_ARMED_MODE".to_owned()), + decel_light_id: context.get_identifier("AUTOBRAKES_DECEL_LIGHT".to_owned()), + spoilers_ground_spoilers_active_id: context + .get_identifier("SPOILERS_GROUND_SPOILERS_ACTIVE".to_owned()), + external_disarm_event_id: context.get_identifier("AUTOBRAKE_DISARM".to_owned()), + deceleration_governor: AutobrakeDecelerationGovernor::new(), target: Acceleration::new::(0.), mode: AutobrakeMode::NONE, @@ -2009,17 +2117,17 @@ impl A320AutobrakeController { } impl SimulationElement for A320AutobrakeController { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("AUTOBRAKES_ARMED_MODE", self.mode as u8 as f64); - writer.write("AUTOBRAKES_DECEL_LIGHT", self.is_decelerating()); + writer.write(&self.armed_mode_id, self.mode as u8 as f64); + writer.write(&self.decel_light_id, self.is_decelerating()); } fn read(&mut self, reader: &mut SimulatorReader) { self.last_ground_spoilers_are_deployed = self.ground_spoilers_are_deployed; - self.ground_spoilers_are_deployed = reader.read("SPOILERS_GROUND_SPOILERS_ACTIVE"); - self.external_disarm_event = reader.read("AUTOBRAKE_DISARM"); + self.ground_spoilers_are_deployed = reader.read(&self.spoilers_ground_spoilers_active_id); + self.external_disarm_event = reader.read(&self.external_disarm_event_id); // Reading current mode in sim to initialize correct mode if sim changes it (from .FLT files for example) - self.mode = reader.read_f64("AUTOBRAKES_ARMED_MODE").into(); + self.mode = reader.read_f64(&self.armed_mode_id).into(); } } @@ -2033,15 +2141,15 @@ pub(super) struct A320HydraulicOverheadPanel { blue_epump_override_push_button: MomentaryOnPushButton, } impl A320HydraulicOverheadPanel { - pub(super) fn new() -> A320HydraulicOverheadPanel { + pub(super) fn new(context: &mut InitContext) -> A320HydraulicOverheadPanel { A320HydraulicOverheadPanel { - edp1_push_button: AutoOffFaultPushButton::new_auto("HYD_ENG_1_PUMP"), - edp2_push_button: AutoOffFaultPushButton::new_auto("HYD_ENG_2_PUMP"), - blue_epump_push_button: AutoOffFaultPushButton::new_auto("HYD_EPUMPB"), - ptu_push_button: AutoOffFaultPushButton::new_auto("HYD_PTU"), - rat_push_button: MomentaryPushButton::new("HYD_RAT_MAN_ON"), - yellow_epump_push_button: AutoOnFaultPushButton::new_auto("HYD_EPUMPY"), - blue_epump_override_push_button: MomentaryOnPushButton::new("HYD_EPUMPY_OVRD"), + edp1_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_1_PUMP"), + edp2_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_ENG_2_PUMP"), + blue_epump_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_EPUMPB"), + ptu_push_button: AutoOffFaultPushButton::new_auto(context, "HYD_PTU"), + rat_push_button: MomentaryPushButton::new(context, "HYD_RAT_MAN_ON"), + yellow_epump_push_button: AutoOnFaultPushButton::new_auto(context, "HYD_EPUMPY"), + blue_epump_override_push_button: MomentaryOnPushButton::new(context, "HYD_EPUMPY_OVRD"), } } @@ -2138,8 +2246,8 @@ mod tests { use systems::landing_gear::{LandingGear, LandingGearControlInterfaceUnit}; use systems::shared::EmergencyElectricalState; use systems::shared::PotentialOrigin; - use systems::simulation::test::TestBed; - use systems::simulation::{test::SimulationTestBed, Aircraft}; + use systems::simulation::test::{ReadByName, TestBed, WriteByName}; + use systems::simulation::{test::SimulationTestBed, Aircraft, InitContext}; use uom::si::{ length::foot, ratio::{percent, ratio}, @@ -2151,9 +2259,12 @@ mod tests { } impl A320TestEmergencyElectricalOverheadPanel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { A320TestEmergencyElectricalOverheadPanel { - rat_and_emer_gen_man_on: MomentaryPushButton::new("EMER_ELEC_RAT_AND_EMER_GEN"), + rat_and_emer_gen_man_on: MomentaryPushButton::new( + context, + "EMER_ELEC_RAT_AND_EMER_GEN", + ), } } } @@ -2234,16 +2345,18 @@ mod tests { is_dc_hot_2_powered: bool, } impl A320HydraulicsTestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - engine_1: LeapEngine::new(1), - engine_2: LeapEngine::new(2), - hydraulics: A320Hydraulic::new(), - overhead: A320HydraulicOverheadPanel::new(), - autobrake_panel: AutobrakePanel::new(), - emergency_electrical_overhead: A320TestEmergencyElectricalOverheadPanel::new(), - engine_fire_overhead: EngineFireOverheadPanel::new(), - landing_gear: LandingGear::new(), + engine_1: LeapEngine::new(context, 1), + engine_2: LeapEngine::new(context, 2), + hydraulics: A320Hydraulic::new(context), + overhead: A320HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + emergency_electrical_overhead: A320TestEmergencyElectricalOverheadPanel::new( + context, + ), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + landing_gear: LandingGear::new(context), lgciu1: LandingGearControlInterfaceUnit::new( ElectricalBusType::DirectCurrentEssential, ), @@ -2251,40 +2364,34 @@ mod tests { 2, )), electrical: A320TestElectrical::new(), - ext_pwr: ExternalPowerSource::new(electricity), + ext_pwr: ExternalPowerSource::new(context), powered_source: TestElectricitySource::powered( + context, PotentialOrigin::EngineGenerator(1), - electricity, ), ac_ground_service_bus: ElectricalBus::new( + context, ElectricalBusType::AlternatingCurrentGndFltService, - electricity, ), dc_ground_service_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentGndFltService, - electricity, - ), - ac_1_bus: ElectricalBus::new( - ElectricalBusType::AlternatingCurrent(1), - electricity, - ), - ac_2_bus: ElectricalBus::new( - ElectricalBusType::AlternatingCurrent(2), - electricity, ), - dc_1_bus: ElectricalBus::new(ElectricalBusType::DirectCurrent(1), electricity), - dc_2_bus: ElectricalBus::new(ElectricalBusType::DirectCurrent(2), electricity), + ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), + dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), dc_ess_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentEssential, - electricity, ), dc_hot_1_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentHot(1), - electricity, ), dc_hot_2_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentHot(2), - electricity, ), is_ac_ground_service_powered: true, is_dc_ground_service_powered: true, @@ -2502,9 +2609,7 @@ mod tests { impl A320HydraulicsTestBed { fn new() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - A320HydraulicsTestAircraft::new(electricity) - }), + test_bed: SimulationTestBed::new(A320HydraulicsTestAircraft::new), } } @@ -2543,7 +2648,7 @@ mod tests { } fn is_cargo_fwd_door_locked_down(&mut self) -> bool { - self.read("FWD_DOOR_CARGO_LOCKED") + self.read_by_name("FWD_DOOR_CARGO_LOCKED") } fn is_cargo_fwd_door_locked_up(&self) -> bool { @@ -2551,91 +2656,95 @@ mod tests { } fn cargo_fwd_door_position(&mut self) -> f64 { - self.read("FWD_DOOR_CARGO_POSITION") + self.read_by_name("FWD_DOOR_CARGO_POSITION") } fn cargo_aft_door_position(&mut self) -> f64 { - self.read("AFT_DOOR_CARGO_POSITION") + self.read_by_name("AFT_DOOR_CARGO_POSITION") } fn green_pressure(&mut self) -> Pressure { - self.read("HYD_GREEN_PRESSURE") + self.read_by_name("HYD_GREEN_PRESSURE") } fn blue_pressure(&mut self) -> Pressure { - self.read("HYD_BLUE_PRESSURE") + self.read_by_name("HYD_BLUE_PRESSURE") } fn yellow_pressure(&mut self) -> Pressure { - self.read("HYD_YELLOW_PRESSURE") + self.read_by_name("HYD_YELLOW_PRESSURE") } fn get_yellow_reservoir_volume(&mut self) -> Volume { - self.read("HYD_YELLOW_RESERVOIR") + self.read_by_name("HYD_YELLOW_RESERVOIR") } fn is_green_edp_press_low(&mut self) -> bool { - self.read("HYD_GREEN_EDPUMP_LOW_PRESS") + self.read_by_name("HYD_GREEN_EDPUMP_LOW_PRESS") } fn is_green_edp_press_low_fault(&mut self) -> bool { - self.read("OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT") + self.read_by_name("OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT") } fn is_yellow_edp_press_low_fault(&mut self) -> bool { - self.read("OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT") + self.read_by_name("OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT") } fn is_yellow_edp_press_low(&mut self) -> bool { - self.read("HYD_YELLOW_EDPUMP_LOW_PRESS") + self.read_by_name("HYD_YELLOW_EDPUMP_LOW_PRESS") } fn is_yellow_epump_press_low(&mut self) -> bool { - self.read("HYD_YELLOW_EPUMP_LOW_PRESS") + self.read_by_name("HYD_YELLOW_EPUMP_LOW_PRESS") } fn is_blue_epump_press_low(&mut self) -> bool { - self.read("HYD_BLUE_EPUMP_LOW_PRESS") + self.read_by_name("HYD_BLUE_EPUMP_LOW_PRESS") } fn is_blue_epump_press_low_fault(&mut self) -> bool { - self.read("OVHD_HYD_EPUMPB_PB_HAS_FAULT") + self.read_by_name("OVHD_HYD_EPUMPB_PB_HAS_FAULT") } fn blue_epump_override_is_on(&mut self) -> bool { - self.read("OVHD_HYD_EPUMPY_OVRD_IS_ON") + self.read_by_name("OVHD_HYD_EPUMPY_OVRD_IS_ON") } fn get_brake_left_yellow_pressure(&mut self) -> Pressure { - self.read("HYD_BRAKE_ALTN_LEFT_PRESS") + self.read_by_name("HYD_BRAKE_ALTN_LEFT_PRESS") } fn get_brake_right_yellow_pressure(&mut self) -> Pressure { - self.read("HYD_BRAKE_ALTN_RIGHT_PRESS") + self.read_by_name("HYD_BRAKE_ALTN_RIGHT_PRESS") } fn get_green_reservoir_volume(&mut self) -> Volume { - self.read("HYD_GREEN_RESERVOIR") + self.read_by_name("HYD_GREEN_RESERVOIR") } fn get_blue_reservoir_volume(&mut self) -> Volume { - self.read("HYD_BLUE_RESERVOIR") + self.read_by_name("HYD_BLUE_RESERVOIR") } fn autobrake_mode(&mut self) -> AutobrakeMode { - self.read_f64("AUTOBRAKES_ARMED_MODE").into() + ReadByName::::read_by_name( + self, + "AUTOBRAKES_ARMED_MODE", + ) + .into() } fn get_brake_left_green_pressure(&mut self) -> Pressure { - self.read("HYD_BRAKE_NORM_LEFT_PRESS") + self.read_by_name("HYD_BRAKE_NORM_LEFT_PRESS") } fn get_brake_right_green_pressure(&mut self) -> Pressure { - self.read("HYD_BRAKE_NORM_RIGHT_PRESS") + self.read_by_name("HYD_BRAKE_NORM_RIGHT_PRESS") } fn get_brake_yellow_accumulator_pressure(&mut self) -> Pressure { - self.read("HYD_BRAKE_ALTN_ACC_PRESS") + self.read_by_name("HYD_BRAKE_ALTN_ACC_PRESS") } fn get_brake_yellow_accumulator_fluid_volume(&self) -> Volume { @@ -2643,11 +2752,11 @@ mod tests { } fn get_rat_position(&mut self) -> f64 { - self.read("HYD_RAT_STOW_POSITION") + self.read_by_name("HYD_RAT_STOW_POSITION") } fn get_rat_rpm(&mut self) -> f64 { - self.read("A32NX_HYD_RAT_RPM") + self.read_by_name("A32NX_HYD_RAT_RPM") } fn rat_deploy_commanded(&self) -> bool { @@ -2655,13 +2764,17 @@ mod tests { } fn is_fire_valve_eng1_closed(&mut self) -> bool { - !Read::::read(self, "HYD_GREEN_FIRE_VALVE_OPENED") - && !self.query(|a| a.hydraulics.green_loop.is_fire_shutoff_valve_opened()) + !ReadByName::::read_by_name( + self, + "HYD_GREEN_FIRE_VALVE_OPENED", + ) && !self.query(|a| a.hydraulics.green_loop.is_fire_shutoff_valve_opened()) } fn is_fire_valve_eng2_closed(&mut self) -> bool { - !Read::::read(self, "HYD_YELLOW_FIRE_VALVE_OPENED") - && !self.query(|a| a.hydraulics.yellow_loop.is_fire_shutoff_valve_opened()) + !ReadByName::::read_by_name( + self, + "HYD_YELLOW_FIRE_VALVE_OPENED", + ) && !self.query(|a| a.hydraulics.yellow_loop.is_fire_shutoff_valve_opened()) } fn engines_off(self) -> Self { @@ -2669,7 +2782,7 @@ mod tests { } fn external_power(mut self, is_connected: bool) -> Self { - self.write("EXTERNAL POWER AVAILABLE:1", is_connected); + self.write_by_name("EXTERNAL POWER AVAILABLE:1", is_connected); if is_connected { self = self.on_the_ground(); @@ -2688,12 +2801,12 @@ mod tests { self.set_indicated_altitude(Length::new::(0.)); self.set_on_ground(false); self.set_indicated_airspeed(Velocity::new::(135.)); - self.write( + self.write_by_name( LandingGear::GEAR_CENTER_COMPRESSION, Ratio::new::(0.5), ); - self.write(LandingGear::GEAR_LEFT_COMPRESSION, Ratio::new::(0.8)); - self.write( + self.write_by_name(LandingGear::GEAR_LEFT_COMPRESSION, Ratio::new::(0.8)); + self.write_by_name( LandingGear::GEAR_RIGHT_COMPRESSION, Ratio::new::(0.8), ); @@ -2712,22 +2825,22 @@ mod tests { } fn set_eng1_fire_button(mut self, is_active: bool) -> Self { - self.write("FIRE_BUTTON_ENG1", is_active); + self.write_by_name("FIRE_BUTTON_ENG1", is_active); self } fn set_eng2_fire_button(mut self, is_active: bool) -> Self { - self.write("FIRE_BUTTON_ENG2", is_active); + self.write_by_name("FIRE_BUTTON_ENG2", is_active); self } fn open_fwd_cargo_door(mut self) -> Self { - self.write("FWD_DOOR_CARGO_OPEN_REQ", 1.); + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 1.); self } fn close_fwd_cargo_door(mut self) -> Self { - self.write("FWD_DOOR_CARGO_OPEN_REQ", 0.); + self.write_by_name("FWD_DOOR_CARGO_OPEN_REQ", 0.); self } @@ -2735,111 +2848,111 @@ mod tests { if is_pushed_back { let mut rng = rand::thread_rng(); - self.write("PUSHBACK ANGLE", rng.gen_range(0.0..0.1)); - self.write("PUSHBACK STATE", 0.); + self.write_by_name("PUSHBACK ANGLE", rng.gen_range(0.0..0.1)); + self.write_by_name("PUSHBACK STATE", 0.); } else { - self.write("PUSHBACK STATE", 3.); + self.write_by_name("PUSHBACK STATE", 3.); } self } fn start_eng1(mut self, n2: Ratio) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:1", true); - self.write("ENGINE_N2:1", n2); + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", true); + self.write_by_name("ENGINE_N2:1", n2); self } fn start_eng2(mut self, n2: Ratio) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:2", true); - self.write("ENGINE_N2:2", n2); + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", true); + self.write_by_name("ENGINE_N2:2", n2); self } fn stop_eng1(mut self) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:1", false); - self.write("ENGINE_N2:1", 0.); + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 0.); self } fn stopping_eng1(mut self) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:1", false); - self.write("ENGINE_N2:1", 25.); + self.write_by_name("GENERAL ENG STARTER ACTIVE:1", false); + self.write_by_name("ENGINE_N2:1", 25.); self } fn stop_eng2(mut self) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:2", false); - self.write("ENGINE_N2:2", 0.); + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 0.); self } fn stopping_eng2(mut self) -> Self { - self.write("GENERAL ENG STARTER ACTIVE:2", false); - self.write("ENGINE_N2:2", 25.); + self.write_by_name("GENERAL ENG STARTER ACTIVE:2", false); + self.write_by_name("ENGINE_N2:2", 25.); self } fn set_park_brake(mut self, is_set: bool) -> Self { - self.write("PARK_BRAKE_LEVER_POS", is_set); + self.write_by_name("PARK_BRAKE_LEVER_POS", is_set); self } fn set_gear_up(mut self) -> Self { - self.write("GEAR CENTER POSITION", 0.); - self.write("GEAR LEFT POSITION", 0.); - self.write("GEAR RIGHT POSITION", 0.); - self.write("GEAR HANDLE POSITION", false); + self.write_by_name("GEAR CENTER POSITION", 0.); + self.write_by_name("GEAR LEFT POSITION", 0.); + self.write_by_name("GEAR RIGHT POSITION", 0.); + self.write_by_name("GEAR HANDLE POSITION", false); self } fn set_gear_down(mut self) -> Self { - self.write("GEAR CENTER POSITION", 100.); - self.write("GEAR LEFT POSITION", 100.); - self.write("GEAR RIGHT POSITION", 100.); - self.write("GEAR HANDLE POSITION", true); + self.write_by_name("GEAR CENTER POSITION", 100.); + self.write_by_name("GEAR LEFT POSITION", 100.); + self.write_by_name("GEAR RIGHT POSITION", 100.); + self.write_by_name("GEAR HANDLE POSITION", true); self } fn set_anti_skid(mut self, is_set: bool) -> Self { - self.write("ANTISKID BRAKES ACTIVE", is_set); + self.write_by_name("ANTISKID BRAKES ACTIVE", is_set); self } fn set_yellow_e_pump(mut self, is_auto: bool) -> Self { - self.write("OVHD_HYD_EPUMPY_PB_IS_AUTO", is_auto); + self.write_by_name("OVHD_HYD_EPUMPY_PB_IS_AUTO", is_auto); self } fn set_blue_e_pump(mut self, is_auto: bool) -> Self { - self.write("OVHD_HYD_EPUMPB_PB_IS_AUTO", is_auto); + self.write_by_name("OVHD_HYD_EPUMPB_PB_IS_AUTO", is_auto); self } fn set_blue_e_pump_ovrd_pressed(mut self, is_pressed: bool) -> Self { - self.write("OVHD_HYD_EPUMPY_OVRD_IS_PRESSED", is_pressed); + self.write_by_name("OVHD_HYD_EPUMPY_OVRD_IS_PRESSED", is_pressed); self } fn set_green_ed_pump(mut self, is_auto: bool) -> Self { - self.write("OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO", is_auto); + self.write_by_name("OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO", is_auto); self } fn set_yellow_ed_pump(mut self, is_auto: bool) -> Self { - self.write("OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO", is_auto); + self.write_by_name("OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO", is_auto); self } fn set_ptu_state(mut self, is_auto: bool) -> Self { - self.write("OVHD_HYD_PTU_PB_IS_AUTO", is_auto); + self.write_by_name("OVHD_HYD_PTU_PB_IS_AUTO", is_auto); self } @@ -2907,38 +3020,38 @@ mod tests { fn set_brake(mut self, name: &str, position_percent: Ratio) -> Self { let scaled_value = position_percent.get::(); - self.write(name, scaled_value.min(1.).max(0.)); + self.write_by_name(name, scaled_value.min(1.).max(0.)); self } fn set_autobrake_low(mut self) -> Self { - self.write("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", true); + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", true); self = self.run_one_tick(); - self.write("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", false); + self.write_by_name("OVHD_AUTOBRK_LOW_ON_IS_PRESSED", false); self } fn set_autobrake_med(mut self) -> Self { - self.write("OVHD_AUTOBRK_MED_ON_IS_PRESSED", true); + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", true); self = self.run_one_tick(); - self.write("OVHD_AUTOBRK_MED_ON_IS_PRESSED", false); + self.write_by_name("OVHD_AUTOBRK_MED_ON_IS_PRESSED", false); self } fn set_autobrake_max(mut self) -> Self { - self.write("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", true); + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", true); self = self.run_one_tick(); - self.write("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", false); + self.write_by_name("OVHD_AUTOBRK_MAX_ON_IS_PRESSED", false); self } fn set_deploy_spoilers(mut self) -> Self { - self.write("SPOILERS_GROUND_SPOILERS_ACTIVE", true); + self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", true); self } fn set_retract_spoilers(mut self) -> Self { - self.write("SPOILERS_GROUND_SPOILERS_ACTIVE", false); + self.write_by_name("SPOILERS_GROUND_SPOILERS_ACTIVE", false); self } diff --git a/src/systems/a320_systems/src/lib.rs b/src/systems/a320_systems/src/lib.rs index feb1b46772e..ac482c3f650 100644 --- a/src/systems/a320_systems/src/lib.rs +++ b/src/systems/a320_systems/src/lib.rs @@ -1,3 +1,5 @@ +extern crate systems; + mod electrical; mod fuel; mod hydraulic; @@ -11,6 +13,7 @@ use electrical::{ }; use hydraulic::{A320Hydraulic, A320HydraulicOverheadPanel}; use power_consumption::A320PowerConsumption; +use systems::simulation::InitContext; use systems::{ apu::{ Aps3200ApuGenerator, Aps3200StartMotor, AuxiliaryPowerUnit, AuxiliaryPowerUnitFactory, @@ -53,36 +56,36 @@ pub struct A320 { pressurization: Pressurization, } impl A320 { - pub fn new(electricity: &mut Electricity) -> A320 { + pub fn new(context: &mut InitContext) -> A320 { A320 { - adirs: AirDataInertialReferenceSystem::new(), - adirs_overhead: AirDataInertialReferenceSystemOverheadPanel::new(), + adirs: AirDataInertialReferenceSystem::new(context), + adirs_overhead: AirDataInertialReferenceSystemOverheadPanel::new(context), apu: AuxiliaryPowerUnitFactory::new_aps3200( + context, 1, - electricity, APU_START_MOTOR_BUS_TYPE, ElectricalBusType::DirectCurrentBattery, ElectricalBusType::DirectCurrentBattery, ), - apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(), - apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(), - pneumatic_overhead: A320PneumaticOverheadPanel::new(), - electrical_overhead: A320ElectricalOverheadPanel::new(), - emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel::new(), - fuel: A320Fuel::new(), - engine_1: LeapEngine::new(1), - engine_2: LeapEngine::new(2), - engine_fire_overhead: EngineFireOverheadPanel::new(), - electrical: A320Electrical::new(electricity), - power_consumption: A320PowerConsumption::new(), - ext_pwr: ExternalPowerSource::new(electricity), + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(context), + apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(context), + pneumatic_overhead: A320PneumaticOverheadPanel::new(context), + electrical_overhead: A320ElectricalOverheadPanel::new(context), + emergency_electrical_overhead: A320EmergencyElectricalOverheadPanel::new(context), + fuel: A320Fuel::new(context), + engine_1: LeapEngine::new(context, 1), + engine_2: LeapEngine::new(context, 2), + engine_fire_overhead: EngineFireOverheadPanel::new(context), + electrical: A320Electrical::new(context), + power_consumption: A320PowerConsumption::new(context), + ext_pwr: ExternalPowerSource::new(context), lgciu1: LandingGearControlInterfaceUnit::new(ElectricalBusType::DirectCurrentEssential), lgciu2: LandingGearControlInterfaceUnit::new(ElectricalBusType::DirectCurrent(2)), - hydraulic: A320Hydraulic::new(), - hydraulic_overhead: A320HydraulicOverheadPanel::new(), - autobrake_panel: AutobrakePanel::new(), - landing_gear: LandingGear::new(), - pressurization: Pressurization::new(), + hydraulic: A320Hydraulic::new(context), + hydraulic_overhead: A320HydraulicOverheadPanel::new(context), + autobrake_panel: AutobrakePanel::new(context), + landing_gear: LandingGear::new(context), + pressurization: Pressurization::new(context), } } } diff --git a/src/systems/a320_systems/src/pneumatic.rs b/src/systems/a320_systems/src/pneumatic.rs index 4baeefb150c..e4df569be5d 100644 --- a/src/systems/a320_systems/src/pneumatic.rs +++ b/src/systems/a320_systems/src/pneumatic.rs @@ -1,3 +1,4 @@ +use systems::simulation::InitContext; use systems::{ overhead::OnOffFaultPushButton, simulation::{SimulationElement, SimulationElementVisitor}, @@ -7,9 +8,9 @@ pub struct A320PneumaticOverheadPanel { apu_bleed: OnOffFaultPushButton, } impl A320PneumaticOverheadPanel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { A320PneumaticOverheadPanel { - apu_bleed: OnOffFaultPushButton::new_on("PNEU_APU_BLEED"), + apu_bleed: OnOffFaultPushButton::new_on(context, "PNEU_APU_BLEED"), } } diff --git a/src/systems/a320_systems/src/power_consumption.rs b/src/systems/a320_systems/src/power_consumption.rs index e08180c955a..6cdd183fdcd 100644 --- a/src/systems/a320_systems/src/power_consumption.rs +++ b/src/systems/a320_systems/src/power_consumption.rs @@ -1,3 +1,4 @@ +use systems::simulation::InitContext; use systems::{ electrical::consumption::{FlightPhasePowerConsumer, PowerConsumerFlightPhase}, shared::ElectricalBusType, @@ -25,10 +26,11 @@ pub(super) struct A320PowerConsumption { dc_gnd_flt_service_consumer: FlightPhasePowerConsumer, } impl A320PowerConsumption { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { // The watts in this function are all provided by komp. Self { - ac_bus_1_consumer: FlightPhasePowerConsumer::from( + ac_bus_1_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrent(1), ) .demand([ @@ -57,7 +59,8 @@ impl A320PowerConsumption { Power::new::(30243.1), ), ]), - ac_bus_2_consumer: FlightPhasePowerConsumer::from( + ac_bus_2_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrent(2), ) .demand([ @@ -86,7 +89,8 @@ impl A320PowerConsumption { Power::new::(24475.8), ), ]), - ac_ess_bus_consumer: FlightPhasePowerConsumer::from( + ac_ess_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrentEssential, ) .demand([ @@ -103,7 +107,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(715.7)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(715.7)), ]), - ac_ess_shed_bus_consumer: FlightPhasePowerConsumer::from( + ac_ess_shed_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrentEssentialShed, ) .demand([ @@ -120,7 +125,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(823.5)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(823.5)), ]), - ac_stat_inv_bus_consumer: FlightPhasePowerConsumer::from( + ac_stat_inv_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrentStaticInverter, ) .demand([ @@ -137,7 +143,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(135.)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(135.)), ]), - ac_gnd_flt_service_consumer: FlightPhasePowerConsumer::from( + ac_gnd_flt_service_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::AlternatingCurrentGndFltService, ) .demand([ @@ -154,37 +161,44 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(2628.)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(3663.)), ]), - dc_bus_1_consumer: FlightPhasePowerConsumer::from(ElectricalBusType::DirectCurrent(1)) - .demand([ - ( - PowerConsumerFlightPhase::BeforeStart, - Power::new::(252.), - ), - ( - PowerConsumerFlightPhase::AfterStart, - Power::new::(308.), - ), - (PowerConsumerFlightPhase::Takeoff, Power::new::(364.)), - (PowerConsumerFlightPhase::Flight, Power::new::(280.)), - (PowerConsumerFlightPhase::Landing, Power::new::(364.)), - (PowerConsumerFlightPhase::TaxiIn, Power::new::(336.)), - ]), - dc_bus_2_consumer: FlightPhasePowerConsumer::from(ElectricalBusType::DirectCurrent(2)) - .demand([ - ( - PowerConsumerFlightPhase::BeforeStart, - Power::new::(532.), - ), - ( - PowerConsumerFlightPhase::AfterStart, - Power::new::(448.), - ), - (PowerConsumerFlightPhase::Takeoff, Power::new::(392.)), - (PowerConsumerFlightPhase::Flight, Power::new::(392.)), - (PowerConsumerFlightPhase::Landing, Power::new::(392.)), - (PowerConsumerFlightPhase::TaxiIn, Power::new::(448.)), - ]), - dc_ess_bus_consumer: FlightPhasePowerConsumer::from( + dc_bus_1_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(252.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(308.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(364.)), + (PowerConsumerFlightPhase::Flight, Power::new::(280.)), + (PowerConsumerFlightPhase::Landing, Power::new::(364.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(336.)), + ]), + dc_bus_2_consumer: FlightPhasePowerConsumer::new( + context, + ElectricalBusType::DirectCurrent(2), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(532.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(448.), + ), + (PowerConsumerFlightPhase::Takeoff, Power::new::(392.)), + (PowerConsumerFlightPhase::Flight, Power::new::(392.)), + (PowerConsumerFlightPhase::Landing, Power::new::(392.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(448.)), + ]), + dc_ess_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentEssential, ) .demand([ @@ -201,7 +215,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(168.)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(140.)), ]), - dc_ess_shed_bus_consumer: FlightPhasePowerConsumer::from( + dc_ess_shed_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentEssentialShed, ) .demand([ @@ -218,7 +233,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(196.)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(168.)), ]), - dc_bat_bus_consumer: FlightPhasePowerConsumer::from( + dc_bat_bus_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentBattery, ) .demand([ @@ -235,7 +251,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(28.)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(28.)), ]), - dc_hot_bus_1_consumer: FlightPhasePowerConsumer::from( + dc_hot_bus_1_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentHot(1), ) .demand([ @@ -252,7 +269,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(15.3)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(11.)), ]), - dc_hot_bus_2_consumer: FlightPhasePowerConsumer::from( + dc_hot_bus_2_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentHot(2), ) .demand([ @@ -269,7 +287,8 @@ impl A320PowerConsumption { (PowerConsumerFlightPhase::Landing, Power::new::(24.3)), (PowerConsumerFlightPhase::TaxiIn, Power::new::(24.3)), ]), - dc_gnd_flt_service_consumer: FlightPhasePowerConsumer::from( + dc_gnd_flt_service_consumer: FlightPhasePowerConsumer::new( + context, ElectricalBusType::DirectCurrentGndFltService, ) .demand([ @@ -326,8 +345,3 @@ impl SimulationElement for A320PowerConsumption { visitor.visit(self); } } -impl Default for A320PowerConsumption { - fn default() -> Self { - A320PowerConsumption::new() - } -} diff --git a/src/systems/a320_systems_wasm/src/lib.rs b/src/systems/a320_systems_wasm/src/lib.rs index 66c3ab25fbf..653d9222de7 100644 --- a/src/systems/a320_systems_wasm/src/lib.rs +++ b/src/systems/a320_systems_wasm/src/lib.rs @@ -1,128 +1,127 @@ #![cfg(any(target_arch = "wasm32", doc))] -use std::{ - error::Error, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; use msfs::{ legacy::{AircraftVariable, NamedVariable}, - sim_connect, sim_connect::SimConnect, sim_connect::{SimConnectRecv, SIMCONNECT_OBJECT_ID_USER}, sys, }; use a320_systems::A320; -use systems::{failures::FailureType, simulation::Simulation}; +use systems::{ + failures::FailureType, + simulation::{VariableIdentifier, VariableRegistry}, +}; use systems_wasm::{ - f64_to_sim_connect_32k_pos, sim_connect_32k_pos_to_f64, MsfsAspectCtor, MsfsHandlerBuilder, - SimulatorAspect, + f64_to_sim_connect_32k_pos, sim_connect_32k_pos_to_f64, MsfsAspectCtor, MsfsSimulationBuilder, + MsfsVariableRegistry, SimulatorAspect, }; #[msfs::gauge(name=systems)] async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { let mut sim_connect = gauge.open_simconnect("systems")?; - let mut handler = MsfsHandlerBuilder::new("A32NX_", sim_connect.as_mut()) - .with_electrical_buses(vec![ - ("AC_1", 2), - ("AC_1", 2), - ("AC_2", 3), - ("AC_ESS", 4), - ("AC_ESS_SHED", 5), - ("AC_STAT_INV", 6), - ("AC_GND_FLT_SVC", 14), - ("DC_1", 7), - ("DC_2", 8), - ("DC_ESS", 9), - ("DC_ESS_SHED", 10), - ("DC_BAT", 11), - ("DC_HOT_1", 12), - ("DC_HOT_2", 13), - ("DC_GND_FLT_SVC", 15), - ]) - .with_auxiliary_power_unit("OVHD_APU_START_PB_IS_AVAILABLE", 8)? - .with::()? - .with::()? - .with::()? - .with_failures(vec![ - (24_000, FailureType::TransformerRectifier(1)), - (24_001, FailureType::TransformerRectifier(2)), - (24_002, FailureType::TransformerRectifier(3)), - ]) - .provides_aircraft_variable("ACCELERATION BODY X", "feet per second squared", 0)? - .provides_aircraft_variable("ACCELERATION BODY Y", "feet per second squared", 0)? - .provides_aircraft_variable("ACCELERATION BODY Z", "feet per second squared", 0)? - .provides_aircraft_variable("AIRSPEED INDICATED", "Knots", 0)? - .provides_aircraft_variable("AIRSPEED MACH", "Mach", 0)? - .provides_aircraft_variable("AIRSPEED TRUE", "Knots", 0)? - .provides_aircraft_variable("AMBIENT PRESSURE", "inHg", 0)? - .provides_aircraft_variable("AMBIENT TEMPERATURE", "celsius", 0)? - .provides_aircraft_variable("AMBIENT WIND DIRECTION", "Degrees", 0)? - .provides_aircraft_variable("AMBIENT WIND VELOCITY", "Knots", 0)? - .provides_aircraft_variable("ANTISKID BRAKES ACTIVE", "Bool", 0)? - .provides_aircraft_variable_with_additional_names( - "APU GENERATOR SWITCH", - "Bool", - 0, - vec!["OVHD_ELEC_APU_GEN_PB_IS_ON"], - )? - .provides_aircraft_variable_with_additional_names( - "EXTERNAL POWER AVAILABLE", - "Bool", - 1, - vec!["OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"], - )? - .provides_aircraft_variable_with_additional_names( - "EXTERNAL POWER ON", - "Bool", - 1, - vec!["OVHD_ELEC_EXT_PWR_PB_IS_ON"], - )? - .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "Pounds", 0)? - .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 0)? - .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 1)? - .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 2)? - .provides_aircraft_variable("GEAR CENTER POSITION", "Percent", 0)? - .provides_aircraft_variable("GEAR HANDLE POSITION", "Bool", 0)? - .provides_aircraft_variable_with_additional_names( - "GENERAL ENG MASTER ALTERNATOR", - "Bool", - 1, - vec!["OVHD_ELEC_ENG_GEN_1_PB_IS_ON"], - )? - .provides_aircraft_variable_with_additional_names( - "GENERAL ENG MASTER ALTERNATOR", - "Bool", - 2, - vec!["OVHD_ELEC_ENG_GEN_2_PB_IS_ON"], - )? - .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 1)? - .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 2)? - .provides_aircraft_variable("GPS GROUND SPEED", "Knots", 0)? - .provides_aircraft_variable("GPS GROUND MAGNETIC TRACK", "Degrees", 0)? - .provides_aircraft_variable("INDICATED ALTITUDE", "Feet", 0)? - .provides_aircraft_variable("PLANE PITCH DEGREES", "Degrees", 0)? - .provides_aircraft_variable("PLANE BANK DEGREES", "Degrees", 0)? - .provides_aircraft_variable("PLANE HEADING DEGREES MAGNETIC", "Degrees", 0)? - .provides_aircraft_variable("PLANE LATITUDE", "degree latitude", 0)? - .provides_aircraft_variable("PLANE LONGITUDE", "degree longitude", 0)? - .provides_aircraft_variable("PUSHBACK ANGLE", "Radian", 0)? - .provides_aircraft_variable("PUSHBACK STATE", "Enum", 0)? - .provides_aircraft_variable("SEA LEVEL PRESSURE", "Millibars", 0)? - .provides_aircraft_variable("SIM ON GROUND", "Bool", 0)? - .provides_aircraft_variable("TOTAL AIR TEMPERATURE", "celsius", 0)? - .provides_aircraft_variable("TRAILING EDGE FLAPS LEFT PERCENT", "Percent", 0)? - .provides_aircraft_variable("TRAILING EDGE FLAPS RIGHT PERCENT", "Percent", 0)? - .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 1)? - .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 2)? - .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 1)? - .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 2)? - .provides_aircraft_variable("UNLIMITED FUEL", "Bool", 0)? - .provides_aircraft_variable("VELOCITY WORLD Y", "feet per minute", 0)? - .build(); - - let mut simulation = Simulation::new(|electricity| A320::new(electricity)); + let (mut simulation, mut handler) = + MsfsSimulationBuilder::new("A32NX_".to_owned(), sim_connect.as_mut()) + .with_electrical_buses(vec![ + ("AC_1", 2), + ("AC_1", 2), + ("AC_2", 3), + ("AC_ESS", 4), + ("AC_ESS_SHED", 5), + ("AC_STAT_INV", 6), + ("AC_GND_FLT_SVC", 14), + ("DC_1", 7), + ("DC_2", 8), + ("DC_ESS", 9), + ("DC_ESS_SHED", 10), + ("DC_BAT", 11), + ("DC_HOT_1", 12), + ("DC_HOT_2", 13), + ("DC_GND_FLT_SVC", 15), + ]) + .with_auxiliary_power_unit("OVHD_APU_START_PB_IS_AVAILABLE".to_owned(), 8)? + .with::()? + .with::()? + .with::()? + .with_failures(vec![ + (24_000, FailureType::TransformerRectifier(1)), + (24_001, FailureType::TransformerRectifier(2)), + (24_002, FailureType::TransformerRectifier(3)), + ]) + .provides_aircraft_variable("ACCELERATION BODY X", "feet per second squared", 0)? + .provides_aircraft_variable("ACCELERATION BODY Y", "feet per second squared", 0)? + .provides_aircraft_variable("ACCELERATION BODY Z", "feet per second squared", 0)? + .provides_aircraft_variable("AIRSPEED INDICATED", "Knots", 0)? + .provides_aircraft_variable("AIRSPEED MACH", "Mach", 0)? + .provides_aircraft_variable("AIRSPEED TRUE", "Knots", 0)? + .provides_aircraft_variable("AMBIENT PRESSURE", "inHg", 0)? + .provides_aircraft_variable("AMBIENT TEMPERATURE", "celsius", 0)? + .provides_aircraft_variable("AMBIENT WIND DIRECTION", "Degrees", 0)? + .provides_aircraft_variable("AMBIENT WIND VELOCITY", "Knots", 0)? + .provides_aircraft_variable("ANTISKID BRAKES ACTIVE", "Bool", 0)? + .provides_aircraft_variable_with_additional_names( + "APU GENERATOR SWITCH", + "Bool", + 0, + vec!["OVHD_ELEC_APU_GEN_PB_IS_ON".to_owned()], + )? + .provides_aircraft_variable_with_additional_names( + "EXTERNAL POWER AVAILABLE", + "Bool", + 1, + vec!["OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE".to_owned()], + )? + .provides_aircraft_variable_with_additional_names( + "EXTERNAL POWER ON", + "Bool", + 1, + vec!["OVHD_ELEC_EXT_PWR_PB_IS_ON".to_owned()], + )? + .provides_aircraft_variable("FUEL TANK LEFT MAIN QUANTITY", "Pounds", 0)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 0)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 1)? + .provides_aircraft_variable("GEAR ANIMATION POSITION", "Percent", 2)? + .provides_aircraft_variable("GEAR CENTER POSITION", "Percent", 0)? + .provides_aircraft_variable("GEAR HANDLE POSITION", "Bool", 0)? + .provides_aircraft_variable_with_additional_names( + "GENERAL ENG MASTER ALTERNATOR", + "Bool", + 1, + vec!["OVHD_ELEC_ENG_GEN_1_PB_IS_ON".to_owned()], + )? + .provides_aircraft_variable_with_additional_names( + "GENERAL ENG MASTER ALTERNATOR", + "Bool", + 2, + vec!["OVHD_ELEC_ENG_GEN_2_PB_IS_ON".to_owned()], + )? + .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 1)? + .provides_aircraft_variable("GENERAL ENG STARTER ACTIVE", "Bool", 2)? + .provides_aircraft_variable("GPS GROUND SPEED", "Knots", 0)? + .provides_aircraft_variable("GPS GROUND MAGNETIC TRACK", "Degrees", 0)? + .provides_aircraft_variable("INDICATED ALTITUDE", "Feet", 0)? + .provides_aircraft_variable("PLANE PITCH DEGREES", "Degrees", 0)? + .provides_aircraft_variable("PLANE BANK DEGREES", "Degrees", 0)? + .provides_aircraft_variable("PLANE HEADING DEGREES MAGNETIC", "Degrees", 0)? + .provides_aircraft_variable("PLANE LATITUDE", "degree latitude", 0)? + .provides_aircraft_variable("PLANE LONGITUDE", "degree longitude", 0)? + .provides_aircraft_variable("PUSHBACK ANGLE", "Radian", 0)? + .provides_aircraft_variable("PUSHBACK STATE", "Enum", 0)? + .provides_aircraft_variable("SEA LEVEL PRESSURE", "Millibars", 0)? + .provides_aircraft_variable("SIM ON GROUND", "Bool", 0)? + .provides_aircraft_variable("TOTAL AIR TEMPERATURE", "celsius", 0)? + .provides_aircraft_variable("TRAILING EDGE FLAPS LEFT PERCENT", "Percent", 0)? + .provides_aircraft_variable("TRAILING EDGE FLAPS RIGHT PERCENT", "Percent", 0)? + .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 1)? + .provides_aircraft_variable("TURB ENG CORRECTED N1", "Percent", 2)? + .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 1)? + .provides_aircraft_variable("TURB ENG CORRECTED N2", "Percent", 2)? + .provides_aircraft_variable("UNLIMITED FUEL", "Bool", 0)? + .provides_aircraft_variable("VELOCITY WORLD Y", "feet per minute", 0)? + .build(A320::new); + while let Some(event) = gauge.next_event().await { handler.handle(event, &mut simulation, sim_connect.as_mut())?; } @@ -131,6 +130,11 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box Result> { + fn new( + registry: &mut MsfsVariableRegistry, + sim_connect: &mut SimConnect, + ) -> Result> { Ok(Self { + autobrake_disarm_id: registry.get("AUTOBRAKE_DISARM".to_owned()), + ovhd_autobrk_low_on_is_pressed_id: registry + .get("OVHD_AUTOBRK_LOW_ON_IS_PRESSED".to_owned()), + ovhd_autobrk_med_on_is_pressed_id: registry + .get("OVHD_AUTOBRK_MED_ON_IS_PRESSED".to_owned()), + ovhd_autobrk_max_on_is_pressed_id: registry + .get("OVHD_AUTOBRK_MAX_ON_IS_PRESSED".to_owned()), + // SimConnect inputs masking id_mode_max: sim_connect.map_client_event_to_sim_event("AUTOBRAKE_HI_SET", false)?, id_mode_med: sim_connect.map_client_event_to_sim_event("AUTOBRAKE_MED_SET", false)?, @@ -227,13 +242,17 @@ impl Autobrakes { } } impl SimulatorAspect for Autobrakes { - fn read(&mut self, name: &str) -> Option { - match name { - "AUTOBRAKE_DISARM" => Some(self.disarm_requested as u8 as f64), - "OVHD_AUTOBRK_LOW_ON_IS_PRESSED" => Some(self.low_mode_requested as u8 as f64), - "OVHD_AUTOBRK_MED_ON_IS_PRESSED" => Some(self.med_mode_requested as u8 as f64), - "OVHD_AUTOBRK_MAX_ON_IS_PRESSED" => Some(self.max_mode_requested as u8 as f64), - _ => None, + fn read(&mut self, identifier: &VariableIdentifier) -> Option { + if identifier == &self.autobrake_disarm_id { + Some(self.disarm_requested as u8 as f64) + } else if identifier == &self.ovhd_autobrk_low_on_is_pressed_id { + Some(self.low_mode_requested as u8 as f64) + } else if identifier == &self.ovhd_autobrk_med_on_is_pressed_id { + Some(self.med_mode_requested as u8 as f64) + } else if identifier == &self.ovhd_autobrk_max_on_is_pressed_id { + Some(self.max_mode_requested as u8 as f64) + } else { + None } } @@ -272,6 +291,12 @@ impl SimulatorAspect for Autobrakes { } struct Brakes { + park_brak_lever_pos_id: VariableIdentifier, + left_brake_pedal_input_id: VariableIdentifier, + right_brake_pedal_input_id: VariableIdentifier, + brake_left_force_factor_id: VariableIdentifier, + brake_right_force_factor_id: VariableIdentifier, + park_brake_lever_masked_input: NamedVariable, left_pedal_brake_masked_input: NamedVariable, right_pedal_brake_masked_input: NamedVariable, @@ -299,8 +324,17 @@ struct Brakes { } impl MsfsAspectCtor for Brakes { - fn new(sim_connect: &mut SimConnect) -> Result> { + fn new( + registry: &mut MsfsVariableRegistry, + sim_connect: &mut SimConnect, + ) -> Result> { Ok(Self { + park_brak_lever_pos_id: registry.get("PARK_BRAKE_LEVER_POS".to_owned()), + left_brake_pedal_input_id: registry.get("LEFT_BRAKE_PEDAL_INPUT".to_owned()), + right_brake_pedal_input_id: registry.get("RIGHT_BRAKE_PEDAL_INPUT".to_owned()), + brake_left_force_factor_id: registry.get("BRAKE LEFT FORCE FACTOR".to_owned()), + brake_right_force_factor_id: registry.get("BRAKE RIGHT FORCE FACTOR".to_owned()), + park_brake_lever_masked_input: NamedVariable::from("A32NX_PARK_BRAKE_LEVER_POS"), left_pedal_brake_masked_input: NamedVariable::from("A32NX_LEFT_BRAKE_PEDAL_INPUT"), right_pedal_brake_masked_input: NamedVariable::from("A32NX_RIGHT_BRAKE_PEDAL_INPUT"), @@ -465,26 +499,27 @@ impl Brakes { } } impl SimulatorAspect for Brakes { - fn read(&mut self, name: &str) -> Option { - match name { - "PARK_BRAKE_LEVER_POS" => Some(self.is_park_brake_set()), - "LEFT_BRAKE_PEDAL_INPUT" => Some(self.brake_left()), - "RIGHT_BRAKE_PEDAL_INPUT" => Some(self.brake_right()), - _ => None, + fn read(&mut self, identifier: &VariableIdentifier) -> Option { + if identifier == &self.park_brak_lever_pos_id { + Some(self.is_park_brake_set()) + } else if identifier == &self.left_brake_pedal_input_id { + Some(self.brake_left()) + } else if identifier == &self.right_brake_pedal_input_id { + Some(self.brake_right()) + } else { + None } } - fn write(&mut self, name: &str, value: f64) -> bool { - match name { - "BRAKE LEFT FORCE FACTOR" => { - self.set_brake_left_output(value); - true - } - "BRAKE RIGHT FORCE FACTOR" => { - self.set_brake_right_output(value); - true - } - _ => false, + fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool { + if identifier == &self.brake_left_force_factor_id { + self.set_brake_left_output(value); + true + } else if identifier == &self.brake_right_force_factor_id { + self.set_brake_right_output(value); + true + } else { + false } } @@ -539,15 +574,23 @@ impl SimulatorAspect for Brakes { } struct CargoDoors { + fwd_door_cargo_position_id: VariableIdentifier, + fwd_door_cargo_open_req_id: VariableIdentifier, + forward_cargo_door_position: NamedVariable, forward_cargo_door_sim_position_request: AircraftVariable, fwd_position: f64, forward_cargo_door_open_req: f64, } - impl MsfsAspectCtor for CargoDoors { - fn new(_: &mut SimConnect) -> Result> { + fn new( + registry: &mut MsfsVariableRegistry, + _: &mut SimConnect, + ) -> Result> { Ok(Self { + fwd_door_cargo_position_id: registry.get("FWD_DOOR_CARGO_POSITION".to_owned()), + fwd_door_cargo_open_req_id: registry.get("FWD_DOOR_CARGO_OPEN_REQ".to_owned()), + forward_cargo_door_position: NamedVariable::from("A32NX_FWD_DOOR_CARGO_POSITION"), forward_cargo_door_sim_position_request: AircraftVariable::from( "INTERACTIVE POINT OPEN", @@ -574,21 +617,22 @@ impl CargoDoors { } } impl SimulatorAspect for CargoDoors { - fn write(&mut self, name: &str, value: f64) -> bool { - match name { - "FWD_DOOR_CARGO_POSITION" => { - self.set_forward_door_postition(value); - true - } - _ => false, + fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool { + if identifier == &self.fwd_door_cargo_position_id { + self.set_forward_door_postition(value); + true + } else { + false } } - fn read(&mut self, name: &str) -> Option { - match name { - "FWD_DOOR_CARGO_OPEN_REQ" => Some(self.forward_cargo_door_open_req), - "FWD_DOOR_CARGO_POSITION" => Some(self.fwd_position), - _ => None, + fn read(&mut self, identifier: &VariableIdentifier) -> Option { + if identifier == &self.fwd_door_cargo_open_req_id { + Some(self.forward_cargo_door_open_req) + } else if identifier == &self.fwd_door_cargo_position_id { + Some(self.fwd_position) + } else { + None } } diff --git a/src/systems/guidelines.md b/src/systems/guidelines.md index e5ed6229c39..f48d8ec6a81 100644 --- a/src/systems/guidelines.md +++ b/src/systems/guidelines.md @@ -22,6 +22,12 @@ When reading and writing `uom` types using `SimulatorReader` and `SimulatorWrite **Rationale**: This leads to shorter and easier to read code. The default unit works for the majority of cases. +## HashMap and HashSet + +Prefer using `fxhash::{FxHashMap, FxHashSet}` over using `std::collections::{HashMap, HashSet}`. + +**Rationale**: These use a significantly faster hasher. + ## Testing Automated tests are mandatory. @@ -165,6 +171,22 @@ In the scenario mentioned above, prefer `&impl SomeTrait` arguments over `&SomeS **Rationale:** Easier code reuse, more generic, avoids bidirectional dependencies, and allows for application of [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle). +Prefer taking ownership over borrowing when the function's internals would clone the value if borrowing were used. + +```rust +// INCORRECT +fn some_function(value: &str) -> Vec { + vec![value.to_owned()] +} + +// CORRECT +fn some_function(value: String) -> Vec { + vec![value] +} +``` + +**Rationale**: Allows for more efficient code when the caller already owns the value and doesn't use it after the call to `some_function`, as this avoids an unnecessary clone. + ## Do not re-read SimVars written by Rust code SimVars that are written by Rust code should not be read by Rust code. Instead, pass around the structs containing the value to the locations that need the value. diff --git a/src/systems/systems/Cargo.toml b/src/systems/systems/Cargo.toml index 54349d08dd4..0d0d8324fd7 100644 --- a/src/systems/systems/Cargo.toml +++ b/src/systems/systems/Cargo.toml @@ -11,6 +11,7 @@ ntest = "0.7.2" num-derive = "0.3.3" num-traits = "0.2.14" nalgebra = "0.25.0" +fxhash = "0.2.1" [dev-dependencies] rstest = "0.10.0" diff --git a/src/systems/systems/src/apu/air_intake_flap.rs b/src/systems/systems/src/apu/air_intake_flap.rs index 81f1a1d0d63..bbbf9837925 100644 --- a/src/systems/systems/src/apu/air_intake_flap.rs +++ b/src/systems/systems/src/apu/air_intake_flap.rs @@ -113,8 +113,8 @@ mod air_intake_flap_tests { use crate::electrical::Electricity; use crate::shared::{ElectricalBusType, PotentialOrigin, PowerConsumptionReport}; use crate::simulation::test::TestBed; - use crate::simulation::SimulationElementVisitor; use crate::simulation::{test::SimulationTestBed, Aircraft, SimulationElement}; + use crate::simulation::{InitContext, SimulationElementVisitor}; use ntest::assert_about_eq; use uom::si::power::watt; @@ -126,16 +126,13 @@ mod air_intake_flap_tests { power_consumption: Power, } impl TestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { electricity_source: TestElectricitySource::powered( + context, PotentialOrigin::Battery(1), - electricity, - ), - dc_bat_bus: ElectricalBus::new( - ElectricalBusType::DirectCurrentBattery, - electricity, ), + dc_bat_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentBattery), flap: AirIntakeFlap::new(ElectricalBusType::DirectCurrentBattery), controller: TestFlapController::new(), power_consumption: Power::new::(0.), @@ -243,7 +240,7 @@ mod air_intake_flap_tests { #[test] fn starts_opening_when_target_is_open() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs(5)); @@ -253,7 +250,7 @@ mod air_intake_flap_tests { #[test] fn does_not_instantly_open() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs( @@ -265,7 +262,7 @@ mod air_intake_flap_tests { #[test] fn closes_when_target_is_closed() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs(5)); @@ -280,7 +277,7 @@ mod air_intake_flap_tests { #[test] fn does_not_instantly_close() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs( @@ -297,7 +294,7 @@ mod air_intake_flap_tests { #[test] fn never_closes_beyond_0_percent() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_close()); test_bed.run_with_delta(Duration::from_secs(1_000)); @@ -310,7 +307,7 @@ mod air_intake_flap_tests { #[test] fn never_opens_beyond_100_percent() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs(1_000)); @@ -330,7 +327,7 @@ mod air_intake_flap_tests { #[test] fn is_fully_open_returns_true_when_open() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs(1_000)); @@ -340,7 +337,7 @@ mod air_intake_flap_tests { #[test] fn does_not_move_when_unpowered() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.command(|a| a.unpower_air_intake_flap()); @@ -354,7 +351,7 @@ mod air_intake_flap_tests { #[test] fn uses_power_when_moving() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.run_with_delta(Duration::from_secs(2)); @@ -364,7 +361,7 @@ mod air_intake_flap_tests { #[test] fn uses_no_power_when_not_moving() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run_with_delta(Duration::from_secs(2)); @@ -373,7 +370,7 @@ mod air_intake_flap_tests { #[test] fn does_not_move_when_controller_unpowered() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.command(|a| a.command_flap_open()); test_bed.command(|a| a.unpower_controller()); diff --git a/src/systems/systems/src/apu/aps3200.rs b/src/systems/systems/src/apu/aps3200.rs index df78502bc25..f57e3c94c3a 100644 --- a/src/systems/systems/src/apu/aps3200.rs +++ b/src/systems/systems/src/apu/aps3200.rs @@ -1,4 +1,10 @@ -use super::{ApuGenerator, ApuStartMotor, Turbine, TurbineSignal, TurbineState}; +use std::time::Duration; + +use uom::si::{ + electric_potential::volt, f64::*, frequency::hertz, power::watt, ratio::percent, + temperature_interval, thermodynamic_temperature::degree_celsius, +}; + use crate::{ electrical::{ ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, @@ -9,14 +15,11 @@ use crate::{ calculate_towards_target_temperature, random_number, ConsumePower, ControllerSignal, ElectricalBusType, ElectricalBuses, PotentialOrigin, PowerConsumptionReport, }, - simulation::{SimulationElement, SimulatorWriter, UpdateContext}, -}; -use std::time::Duration; -use uom::si::{ - electric_potential::volt, f64::*, frequency::hertz, power::watt, ratio::percent, - temperature_interval, thermodynamic_temperature::degree_celsius, + simulation::{InitContext, SimulationElement, SimulatorWriter, UpdateContext}, }; +use super::{ApuGenerator, ApuStartMotor, Turbine, TurbineSignal, TurbineState}; + pub struct ShutdownAps3200Turbine { egt: ThermodynamicTemperature, } @@ -552,15 +555,12 @@ pub struct Aps3200ApuGenerator { impl Aps3200ApuGenerator { pub(super) const APU_GEN_POWERED_N: f64 = 84.; - pub fn new( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Aps3200ApuGenerator { + pub fn new(context: &mut InitContext, number: usize) -> Aps3200ApuGenerator { Aps3200ApuGenerator { number, - identifier: identifier_provider.next(), + identifier: context.next_electrical_identifier(), n: Ratio::new::(0.), - writer: ElectricalStateWriter::new(&format!("APU_GEN_{}", number)), + writer: ElectricalStateWriter::new(context, &format!("APU_GEN_{}", number)), output_potential: ElectricPotential::new::(0.), output_frequency: Frequency::new::(0.), load: Ratio::new::(0.), @@ -764,6 +764,7 @@ mod apu_generator_tests { }; use super::*; + use crate::simulation::InitContext; #[test] fn starts_without_output() { @@ -951,18 +952,16 @@ mod apu_generator_tests { test_bed.run(); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_FREQUENCY_NORMAL")); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_LOAD")); - assert!(test_bed.contains_key("ELEC_APU_GEN_1_LOAD_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_LOAD")); + assert!(test_bed.contains_variable_with_name("ELEC_APU_GEN_1_LOAD_NORMAL")); } - fn apu_generator( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Aps3200ApuGenerator { - Aps3200ApuGenerator::new(1, identifier_provider) + fn apu_generator(context: &mut InitContext) -> Aps3200ApuGenerator { + Aps3200ApuGenerator::new(context, 1) } fn update_above_threshold(test_bed: &mut SimulationTestBed>) { diff --git a/src/systems/systems/src/apu/electronic_control_box.rs b/src/systems/systems/src/apu/electronic_control_box.rs index c9cf7526ceb..a675f30bf70 100644 --- a/src/systems/systems/src/apu/electronic_control_box.rs +++ b/src/systems/systems/src/apu/electronic_control_box.rs @@ -3,6 +3,7 @@ use super::{ AuxiliaryPowerUnitFireOverheadPanel, AuxiliaryPowerUnitOverheadPanel, FuelPressureSwitch, Turbine, TurbineSignal, TurbineState, }; +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{ arinc429::SignStatus, ApuMaster, ApuStart, ConsumePower, ContactorSignal, ControllerSignal, @@ -16,6 +17,17 @@ use uom::si::{ }; pub(super) struct ElectronicControlBox { + apu_n_raw_id: VariableIdentifier, + apu_n_id: VariableIdentifier, + apu_egt_id: VariableIdentifier, + apu_egt_caution_id: VariableIdentifier, + apu_egt_warning_id: VariableIdentifier, + apu_low_fuel_pressure_fault_id: VariableIdentifier, + apu_flap_fully_open_id: VariableIdentifier, + ecam_inop_sys_apu_id: VariableIdentifier, + apu_is_auto_shutdown_id: VariableIdentifier, + apu_is_emergency_shutdown_id: VariableIdentifier, + powered_by: ElectricalBusType, is_powered: bool, turbine_state: TurbineState, @@ -37,8 +49,21 @@ impl ElectronicControlBox { const START_MOTOR_POWERED_UNTIL_N: f64 = 55.; pub const BLEED_AIR_COOLDOWN_DURATION_MILLIS: u64 = 120000; - pub fn new(powered_by: ElectricalBusType) -> Self { + pub fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { ElectronicControlBox { + apu_n_raw_id: context.get_identifier("APU_N_RAW".to_owned()), + apu_n_id: context.get_identifier("APU_N".to_owned()), + apu_egt_id: context.get_identifier("APU_EGT".to_owned()), + apu_egt_caution_id: context.get_identifier("APU_EGT_CAUTION".to_owned()), + apu_egt_warning_id: context.get_identifier("APU_EGT_WARNING".to_owned()), + apu_low_fuel_pressure_fault_id: context + .get_identifier("APU_LOW_FUEL_PRESSURE_FAULT".to_owned()), + apu_flap_fully_open_id: context.get_identifier("APU_FLAP_FULLY_OPEN".to_owned()), + ecam_inop_sys_apu_id: context.get_identifier("ECAM_INOP_SYS_APU".to_owned()), + apu_is_auto_shutdown_id: context.get_identifier("APU_IS_AUTO_SHUTDOWN".to_owned()), + apu_is_emergency_shutdown_id: context + .get_identifier("APU_IS_EMERGENCY_SHUTDOWN".to_owned()), + powered_by, is_powered: false, turbine_state: TurbineState::Shutdown, @@ -322,27 +347,34 @@ impl SimulationElement for ElectronicControlBox { }; // For sound and effects. - writer.write("APU_N_RAW", self.n()); + writer.write(&self.apu_n_raw_id, self.n()); - writer.write_arinc429("APU_N", self.n(), ssm); - writer.write_arinc429("APU_EGT", self.egt, ssm); - writer.write_arinc429("APU_EGT_CAUTION", self.egt_caution_temperature(), ssm); - writer.write_arinc429("APU_EGT_WARNING", self.egt_warning_temperature, ssm); + writer.write_arinc429(&self.apu_n_id, self.n(), ssm); + writer.write_arinc429(&self.apu_egt_id, self.egt, ssm); + writer.write_arinc429( + &self.apu_egt_caution_id, + self.egt_caution_temperature(), + ssm, + ); + writer.write_arinc429(&self.apu_egt_warning_id, self.egt_warning_temperature, ssm); writer.write_arinc429( - "APU_LOW_FUEL_PRESSURE_FAULT", + &self.apu_low_fuel_pressure_fault_id, self.has_fuel_low_pressure_fault(), ssm, ); writer.write_arinc429( - "APU_FLAP_FULLY_OPEN", + &self.apu_flap_fully_open_id, self.air_intake_flap_is_fully_open(), ssm, ); // Flight Warning Computer related information. - writer.write("ECAM_INOP_SYS_APU", self.is_inoperable()); - writer.write("APU_IS_AUTO_SHUTDOWN", self.is_auto_shutdown()); - writer.write("APU_IS_EMERGENCY_SHUTDOWN", self.is_emergency_shutdown()); + writer.write(&self.ecam_inop_sys_apu_id, self.is_inoperable()); + writer.write(&self.apu_is_auto_shutdown_id, self.is_auto_shutdown()); + writer.write( + &self.apu_is_emergency_shutdown_id, + self.is_emergency_shutdown(), + ); } fn receive_power(&mut self, buses: &impl ElectricalBuses) { diff --git a/src/systems/systems/src/apu/mod.rs b/src/systems/systems/src/apu/mod.rs index 1a941b6b8a1..b0b9ae72ad4 100644 --- a/src/systems/systems/src/apu/mod.rs +++ b/src/systems/systems/src/apu/mod.rs @@ -4,8 +4,8 @@ use self::{ }; use crate::{ electrical::{ - ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, - ElectricitySource, Potential, ProvideFrequency, ProvidePotential, + ElectricalElement, ElectricalElementIdentifier, ElectricitySource, Potential, + ProvideFrequency, ProvidePotential, }, overhead::{FirePushButton, OnOffAvailablePushButton, OnOffFaultPushButton}, pneumatic::{BleedAirValve, BleedAirValveState}, @@ -23,21 +23,25 @@ use uom::si::f64::*; mod air_intake_flap; mod aps3200; +use crate::simulation::{InitContext, VariableIdentifier}; pub use aps3200::{Aps3200ApuGenerator, Aps3200StartMotor}; + mod electronic_control_box; pub struct AuxiliaryPowerUnitFactory {} impl AuxiliaryPowerUnitFactory { pub fn new_aps3200( + context: &mut InitContext, number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, start_motor_powered_by: ElectricalBusType, electronic_control_box_powered_by: ElectricalBusType, air_intake_flap_powered_by: ElectricalBusType, ) -> AuxiliaryPowerUnit { + let generator = Aps3200ApuGenerator::new(context, number); AuxiliaryPowerUnit::new( + context, Box::new(ShutdownAps3200Turbine::new()), - Aps3200ApuGenerator::new(number, identifier_provider), + generator, Aps3200StartMotor::new(start_motor_powered_by), electronic_control_box_powered_by, air_intake_flap_powered_by, @@ -79,6 +83,9 @@ pub enum TurbineSignal { } pub struct AuxiliaryPowerUnit { + apu_flap_open_percentage_id: VariableIdentifier, + apu_bleed_air_valve_open_id: VariableIdentifier, + turbine: Option>, generator: T, ecb: ElectronicControlBox, @@ -89,6 +96,7 @@ pub struct AuxiliaryPowerUnit { } impl AuxiliaryPowerUnit { pub fn new( + context: &mut InitContext, turbine: Box, generator: T, start_motor: U, @@ -96,9 +104,14 @@ impl AuxiliaryPowerUnit { air_intake_flap_powered_by: ElectricalBusType, ) -> Self { AuxiliaryPowerUnit { + apu_flap_open_percentage_id: context + .get_identifier("APU_FLAP_OPEN_PERCENTAGE".to_owned()), + apu_bleed_air_valve_open_id: context + .get_identifier("APU_BLEED_AIR_VALVE_OPEN".to_owned()), + turbine: Some(turbine), generator, - ecb: ElectronicControlBox::new(electronic_control_box_powered_by), + ecb: ElectronicControlBox::new(context, electronic_control_box_powered_by), start_motor, air_intake_flap: AirIntakeFlap::new(air_intake_flap_powered_by), bleed_air_valve: BleedAirValve::new(), @@ -224,10 +237,13 @@ impl SimulationElement for AuxiliaryPowerUnit fn write(&self, writer: &mut SimulatorWriter) { writer.write( - "APU_FLAP_OPEN_PERCENTAGE", + &self.apu_flap_open_percentage_id, self.air_intake_flap.open_amount(), ); - writer.write("APU_BLEED_AIR_VALVE_OPEN", self.bleed_air_valve_is_open()); + writer.write( + &self.apu_bleed_air_valve_open_id, + self.bleed_air_valve_is_open(), + ); } } impl BleedAirValveState for AuxiliaryPowerUnit { @@ -268,9 +284,9 @@ pub struct AuxiliaryPowerUnitFireOverheadPanel { apu_fire_button: FirePushButton, } impl AuxiliaryPowerUnitFireOverheadPanel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { AuxiliaryPowerUnitFireOverheadPanel { - apu_fire_button: FirePushButton::new("APU"), + apu_fire_button: FirePushButton::new(context, "APU"), } } @@ -285,21 +301,16 @@ impl SimulationElement for AuxiliaryPowerUnitFireOverheadPanel { visitor.visit(self); } } -impl Default for AuxiliaryPowerUnitFireOverheadPanel { - fn default() -> Self { - Self::new() - } -} pub struct AuxiliaryPowerUnitOverheadPanel { pub master: OnOffFaultPushButton, pub start: OnOffAvailablePushButton, } impl AuxiliaryPowerUnitOverheadPanel { - pub fn new() -> AuxiliaryPowerUnitOverheadPanel { + pub fn new(context: &mut InitContext) -> AuxiliaryPowerUnitOverheadPanel { AuxiliaryPowerUnitOverheadPanel { - master: OnOffFaultPushButton::new_off("APU_MASTER_SW"), - start: OnOffAvailablePushButton::new_off("APU_START"), + master: OnOffFaultPushButton::new_off(context, "APU_MASTER_SW"), + start: OnOffAvailablePushButton::new_off(context, "APU_START"), } } @@ -338,11 +349,6 @@ impl SimulationElement for AuxiliaryPowerUnitOverheadPanel { visitor.visit(self); } } -impl Default for AuxiliaryPowerUnitOverheadPanel { - fn default() -> Self { - Self::new() - } -} #[cfg(test)] pub mod tests { @@ -355,11 +361,13 @@ pub mod tests { }, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, + Aircraft, }, }; use super::*; + use crate::simulation::test::{ReadByName, WriteByName}; + use crate::simulation::InitContext; use std::time::Duration; use uom::si::{ length::foot, power::watt, ratio::percent, thermodynamic_temperature::degree_celsius, @@ -427,17 +435,17 @@ pub mod tests { const ECB_AND_AIR_INTAKE_FLAP_POWERED_BY: ElectricalBusType = ElectricalBusType::DirectCurrentBattery; - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - dc_bat_bus_electricity_source: TestElectricitySource::powered(PotentialOrigin::TransformerRectifier(1), electricity), - dc_bat_bus: ElectricalBus::new(Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY, electricity), - ac_1_bus: ElectricalBus::new(ElectricalBusType::AlternatingCurrent(1), electricity), + dc_bat_bus_electricity_source: TestElectricitySource::powered(context, PotentialOrigin::TransformerRectifier(1)), + dc_bat_bus: ElectricalBus::new(context, Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY), + ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), power_consumer: PowerConsumer::from(ElectricalBusType::AlternatingCurrent(1)), - apu_start_motor_bus: ElectricalBus::new(Self::START_MOTOR_POWERED_BY, electricity), - apu: AuxiliaryPowerUnitFactory::new_aps3200(1, electricity, Self::START_MOTOR_POWERED_BY, Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY, Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY), - apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(), - apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(), - apu_bleed: OnOffFaultPushButton::new_on("APU_BLEED"), + apu_start_motor_bus: ElectricalBus::new(context, Self::START_MOTOR_POWERED_BY), + apu: AuxiliaryPowerUnitFactory::new_aps3200(context, 1, Self::START_MOTOR_POWERED_BY, Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY, Self::ECB_AND_AIR_INTAKE_FLAP_POWERED_BY), + apu_fire_overhead: AuxiliaryPowerUnitFireOverheadPanel::new(context), + apu_overhead: AuxiliaryPowerUnitOverheadPanel::new(context), + apu_bleed: OnOffFaultPushButton::new_on(context, "APU_BLEED"), apu_gen_is_used: true, has_fuel_remaining: true, cut_start_motor_power: false, @@ -570,12 +578,10 @@ pub mod tests { let mut apu_test_bed = Self { ambient_temperature: ThermodynamicTemperature::new::(0.), indicated_altitude: Length::new::(5000.), - test_bed: SimulationTestBed::new(|electricity| { - AuxiliaryPowerUnitTestAircraft::new(electricity) - }), + test_bed: SimulationTestBed::new(AuxiliaryPowerUnitTestAircraft::new), }; - apu_test_bed.write("OVHD_APU_BLEED_PB_IS_ON", true); + apu_test_bed.write_by_name("OVHD_APU_BLEED_PB_IS_ON", true); apu_test_bed } @@ -591,27 +597,27 @@ pub mod tests { } fn master_on(mut self) -> Self { - self.write("OVHD_APU_MASTER_SW_PB_IS_ON", true); + self.write_by_name("OVHD_APU_MASTER_SW_PB_IS_ON", true); self } fn master_off(mut self) -> Self { - self.write("OVHD_APU_MASTER_SW_PB_IS_ON", false); + self.write_by_name("OVHD_APU_MASTER_SW_PB_IS_ON", false); self } fn start_on(mut self) -> Self { - self.write("OVHD_APU_START_PB_IS_ON", true); + self.write_by_name("OVHD_APU_START_PB_IS_ON", true); self } fn start_off(mut self) -> Self { - self.write("OVHD_APU_START_PB_IS_ON", false); + self.write_by_name("OVHD_APU_START_PB_IS_ON", false); self } fn bleed_air_off(mut self) -> Self { - self.write("OVHD_APU_BLEED_PB_IS_ON", false); + self.write_by_name("OVHD_APU_BLEED_PB_IS_ON", false); self } @@ -637,7 +643,7 @@ pub mod tests { } pub fn released_apu_fire_pb(mut self) -> Self { - self.write("FIRE_BUTTON_APU", true); + self.write_by_name("FIRE_BUTTON_APU", true); self } @@ -693,12 +699,12 @@ pub mod tests { } fn running_apu_with_bleed_air(mut self) -> Self { - self.write("OVHD_APU_BLEED_PB_IS_ON", true); + self.write_by_name("OVHD_APU_BLEED_PB_IS_ON", true); self.running_apu() } fn running_apu_without_bleed_air(mut self) -> Self { - self.write("OVHD_APU_BLEED_PB_IS_ON", false); + self.write_by_name("OVHD_APU_BLEED_PB_IS_ON", false); self.running_apu() } @@ -793,7 +799,7 @@ pub mod tests { } fn is_air_intake_flap_fully_open(&mut self) -> Arinc429Word { - self.read_arinc429("APU_FLAP_FULLY_OPEN") + self.read_arinc429_by_name("APU_FLAP_FULLY_OPEN") } fn is_air_intake_flap_fully_closed(&mut self) -> bool { @@ -801,17 +807,17 @@ pub mod tests { } fn air_intake_flap_open_amount(&mut self) -> Ratio { - self.read("APU_FLAP_OPEN_PERCENTAGE") + self.read_by_name("APU_FLAP_OPEN_PERCENTAGE") } pub fn n(&mut self) -> Arinc429Word { - self.read_arinc429("APU_N") + self.read_arinc429_by_name("APU_N") } /// The raw value should only be used for sounds and effects and therefore /// isn't wrapped in an Arinc 429 value. fn n_raw(&mut self) -> Ratio { - self.read("APU_N_RAW") + self.read_by_name("APU_N_RAW") } fn turbine_is_shutdown(&mut self) -> bool { @@ -820,15 +826,15 @@ pub mod tests { } fn egt(&mut self) -> Arinc429Word { - self.read_arinc429("APU_EGT") + self.read_arinc429_by_name("APU_EGT") } fn egt_warning_temperature(&mut self) -> Arinc429Word { - self.read_arinc429("APU_EGT_WARNING") + self.read_arinc429_by_name("APU_EGT_WARNING") } fn egt_caution_temperature(&mut self) -> Arinc429Word { - self.read_arinc429("APU_EGT_CAUTION") + self.read_arinc429_by_name("APU_EGT_CAUTION") } fn apu_is_available(&mut self) -> bool { @@ -836,15 +842,15 @@ pub mod tests { } fn start_is_on(&mut self) -> bool { - self.read("OVHD_APU_START_PB_IS_ON") + self.read_by_name("OVHD_APU_START_PB_IS_ON") } fn start_shows_available(&mut self) -> bool { - self.read("OVHD_APU_START_PB_IS_AVAILABLE") + self.read_by_name("OVHD_APU_START_PB_IS_AVAILABLE") } fn master_has_fault(&mut self) -> bool { - self.read("OVHD_APU_MASTER_SW_PB_HAS_FAULT") + self.read_by_name("OVHD_APU_MASTER_SW_PB_HAS_FAULT") } pub fn generator_is_unpowered(&self) -> bool { @@ -852,27 +858,27 @@ pub mod tests { } pub fn potential(&mut self) -> ElectricPotential { - self.read("ELEC_APU_GEN_1_POTENTIAL") + self.read_by_name("ELEC_APU_GEN_1_POTENTIAL") } pub fn potential_within_normal_range(&mut self) -> bool { - self.read("ELEC_APU_GEN_1_POTENTIAL_NORMAL") + self.read_by_name("ELEC_APU_GEN_1_POTENTIAL_NORMAL") } pub fn frequency(&mut self) -> Frequency { - self.read("ELEC_APU_GEN_1_FREQUENCY") + self.read_by_name("ELEC_APU_GEN_1_FREQUENCY") } pub fn frequency_within_normal_range(&mut self) -> bool { - self.read("ELEC_APU_GEN_1_FREQUENCY_NORMAL") + self.read_by_name("ELEC_APU_GEN_1_FREQUENCY_NORMAL") } pub fn load(&mut self) -> Ratio { - self.read("ELEC_APU_GEN_1_LOAD") + self.read_by_name("ELEC_APU_GEN_1_LOAD") } pub fn load_within_normal_range(&mut self) -> bool { - self.read("ELEC_APU_GEN_1_LOAD_NORMAL") + self.read_by_name("ELEC_APU_GEN_1_LOAD_NORMAL") } fn should_close_start_contactors_commanded(&self) -> bool { @@ -887,23 +893,23 @@ pub mod tests { } fn has_fuel_low_pressure_fault(&mut self) -> Arinc429Word { - self.read_arinc429("APU_LOW_FUEL_PRESSURE_FAULT") + self.read_arinc429_by_name("APU_LOW_FUEL_PRESSURE_FAULT") } fn is_auto_shutdown(&mut self) -> bool { - self.read("APU_IS_AUTO_SHUTDOWN") + self.read_by_name("APU_IS_AUTO_SHUTDOWN") } fn is_emergency_shutdown(&mut self) -> bool { - self.read("APU_IS_EMERGENCY_SHUTDOWN") + self.read_by_name("APU_IS_EMERGENCY_SHUTDOWN") } fn is_inoperable(&mut self) -> bool { - self.read("ECAM_INOP_SYS_APU") + self.read_by_name("ECAM_INOP_SYS_APU") } fn bleed_air_valve_is_open(&mut self) -> bool { - self.read("APU_BLEED_AIR_VALVE_OPEN") + self.read_by_name("APU_BLEED_AIR_VALVE_OPEN") } fn apu_generator_output_within_normal_parameters(&self) -> bool { diff --git a/src/systems/systems/src/electrical/battery.rs b/src/systems/systems/src/electrical/battery.rs index 5bba64f5312..348f2a79f18 100644 --- a/src/systems/systems/src/electrical/battery.rs +++ b/src/systems/systems/src/electrical/battery.rs @@ -1,15 +1,17 @@ -use super::{ - ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, - ElectricalStateWriter, ElectricitySource, Potential, PotentialOrigin, ProvideCurrent, - ProvidePotential, +use uom::si::{ + electric_charge::ampere_hour, electric_current::ampere, electric_potential::volt, + electrical_resistance::ohm, f64::*, time::second, }; + use crate::{ shared::{ConsumePower, PowerConsumptionReport}, - simulation::{SimulationElement, SimulatorWriter, UpdateContext}, + simulation::{InitContext, SimulationElement, SimulatorWriter, UpdateContext}, }; -use uom::si::{ - electric_charge::ampere_hour, electric_current::ampere, electric_potential::volt, - electrical_resistance::ohm, f64::*, time::second, + +use super::{ + ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, + ElectricalStateWriter, ElectricitySource, Potential, PotentialOrigin, ProvideCurrent, + ProvidePotential, }; pub struct Battery { @@ -24,48 +26,31 @@ pub struct Battery { impl Battery { const RATED_CAPACITY_AMPERE_HOURS: f64 = 23.; - pub fn full( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Battery { + pub fn full(context: &mut InitContext, number: usize) -> Battery { Battery::new( + context, number, ElectricCharge::new::(Battery::RATED_CAPACITY_AMPERE_HOURS), - identifier_provider, ) } - pub fn half( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Battery { + pub fn half(context: &mut InitContext, number: usize) -> Battery { Battery::new( + context, number, ElectricCharge::new::(Battery::RATED_CAPACITY_AMPERE_HOURS / 2.), - identifier_provider, ) } - pub fn empty( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Battery { - Battery::new( - number, - ElectricCharge::new::(0.), - identifier_provider, - ) + pub fn empty(context: &mut InitContext, number: usize) -> Battery { + Battery::new(context, number, ElectricCharge::new::(0.)) } - pub fn new( - number: usize, - charge: ElectricCharge, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Self { + pub fn new(context: &mut InitContext, number: usize, charge: ElectricCharge) -> Self { Self { number, - identifier: identifier_provider.next(), - writer: ElectricalStateWriter::new(&format!("BAT_{}", number)), + identifier: context.next_electrical_identifier(), + writer: ElectricalStateWriter::new(context, &format!("BAT_{}", number)), charge, input_potential: ElectricPotential::new::(0.), output_potential: Battery::calculate_output_potential_for_charge(charge), @@ -241,6 +226,8 @@ mod tests { #[cfg(test)] mod battery_tests { use super::*; + use crate::simulation::test::ReadByName; + use crate::simulation::InitContext; use crate::{ electrical::{ consumption::PowerConsumer, test::TestElectricitySource, Contactor, ElectricalBus, @@ -248,7 +235,7 @@ mod tests { }, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, SimulationElementVisitor, UpdateContext, + Aircraft, SimulationElementVisitor, UpdateContext, }, }; use std::time::Duration; @@ -260,11 +247,11 @@ mod tests { impl BatteryTestBed { fn with_full_batteries() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::full(1, electricity), - Battery::full(2, electricity), - electricity, + Battery::full(context, 1), + Battery::full(context, 2), + context, ) }), } @@ -272,11 +259,11 @@ mod tests { fn with_half_charged_batteries() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::half(1, electricity), - Battery::half(2, electricity), - electricity, + Battery::half(context, 1), + Battery::half(context, 2), + context, ) }), } @@ -284,11 +271,11 @@ mod tests { fn with_nearly_empty_batteries() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::new(1, ElectricCharge::new::(0.001), electricity), - Battery::new(2, ElectricCharge::new::(0.001), electricity), - electricity, + Battery::new(context, 1, ElectricCharge::new::(0.001)), + Battery::new(context, 2, ElectricCharge::new::(0.001)), + context, ) }), } @@ -296,11 +283,11 @@ mod tests { fn with_nearly_empty_dissimilarly_charged_batteries() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::new(1, ElectricCharge::new::(0.002), electricity), - Battery::new(2, ElectricCharge::new::(0.001), electricity), - electricity, + Battery::new(context, 1, ElectricCharge::new::(0.002)), + Battery::new(context, 2, ElectricCharge::new::(0.001)), + context, ) }), } @@ -308,11 +295,11 @@ mod tests { fn with_full_and_empty_battery() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::full(1, electricity), - Battery::empty(2, electricity), - electricity, + Battery::full(context, 1), + Battery::empty(context, 2), + context, ) }), } @@ -320,30 +307,30 @@ mod tests { fn with_empty_batteries() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { + test_bed: SimulationTestBed::new(|context| { TestAircraft::new( - Battery::empty(1, electricity), - Battery::empty(2, electricity), - electricity, + Battery::empty(context, 1), + Battery::empty(context, 2), + context, ) }), } } fn current_is_normal(&mut self, number: usize) -> bool { - self.read(&format!("ELEC_BAT_{}_CURRENT_NORMAL", number)) + self.read_by_name(&format!("ELEC_BAT_{}_CURRENT_NORMAL", number)) } fn current(&mut self, number: usize) -> ElectricCurrent { - self.read(&format!("ELEC_BAT_{}_CURRENT", number)) + self.read_by_name(&format!("ELEC_BAT_{}_CURRENT", number)) } fn potential_is_normal(&mut self, number: usize) -> bool { - self.read(&format!("ELEC_BAT_{}_POTENTIAL_NORMAL", number)) + self.read_by_name(&format!("ELEC_BAT_{}_POTENTIAL_NORMAL", number)) } fn potential(&mut self, number: usize) -> ElectricPotential { - self.read(&format!("ELEC_BAT_{}_POTENTIAL", number)) + self.read_by_name(&format!("ELEC_BAT_{}_POTENTIAL", number)) } } impl TestBed for BatteryTestBed { @@ -369,20 +356,17 @@ mod tests { battery_consumption: Power, } impl TestAircraft { - fn new(battery_1: Battery, battery_2: Battery, electricity: &mut Electricity) -> Self { + fn new(battery_1: Battery, battery_2: Battery, context: &mut InitContext) -> Self { let mut aircraft = Self { electricity_source: TestElectricitySource::unpowered( + context, PotentialOrigin::TransformerRectifier(1), - electricity, ), battery_1, battery_2, - bat_bus: ElectricalBus::new( - ElectricalBusType::DirectCurrentBattery, - electricity, - ), - battery_1_contactor: Contactor::new("BAT1", electricity), - battery_2_contactor: Contactor::new("BAT2", electricity), + bat_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentBattery), + battery_1_contactor: Contactor::new(context, "BAT1"), + battery_2_contactor: Contactor::new(context, "BAT2"), consumer: PowerConsumer::from(ElectricalBusType::DirectCurrentBattery), battery_consumption: Power::new::(0.), }; diff --git a/src/systems/systems/src/electrical/battery_charge_limiter.rs b/src/systems/systems/src/electrical/battery_charge_limiter.rs index ef20cc3e4c9..f6bde8060f0 100644 --- a/src/systems/systems/src/electrical/battery_charge_limiter.rs +++ b/src/systems/systems/src/electrical/battery_charge_limiter.rs @@ -2,6 +2,7 @@ use super::{ AlternatingCurrentElectricalSystem, BatteryPushButtons, ElectricalElement, Electricity, ElectricitySource, EmergencyElectrical, ProvideCurrent, ProvidePotential, }; +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{ApuAvailable, ApuMaster, ApuStart, DelayedTrueLogicGate, LandingGearRealPosition}, simulation::{SimulationElement, SimulatorWriter, UpdateContext, Write}, @@ -76,18 +77,18 @@ impl State { pub struct BatteryChargeLimiter { number: usize, - should_show_arrow_when_contactor_closed_id: String, + should_show_arrow_when_contactor_closed_id: VariableIdentifier, arrow: ArrowBetweenBatteryAndBatBus, observer: Option, } impl BatteryChargeLimiter { - pub fn new(number: usize, contactor_id: &str) -> Self { + pub fn new(context: &mut InitContext, number: usize, contactor_id: &str) -> Self { Self { number, - should_show_arrow_when_contactor_closed_id: format!( + should_show_arrow_when_contactor_closed_id: context.get_identifier(format!( "ELEC_CONTACTOR_{}_SHOW_ARROW_WHEN_CLOSED", contactor_id - ), + )), arrow: ArrowBetweenBatteryAndBatBus::new(), observer: Some(State::new()), } @@ -559,7 +560,10 @@ mod tests { #[cfg(test)] mod battery_charge_limiter_tests { - use super::*; + use std::time::Duration; + + use uom::si::{length::foot, power::watt}; + use crate::{ electrical::{ battery::Battery, consumption::PowerConsumer, test::TestElectricitySource, @@ -568,12 +572,12 @@ mod tests { Potential, PotentialOrigin, }, simulation::{ - test::{SimulationTestBed, TestBed}, - Aircraft, Read, SimulationElementVisitor, + test::{ReadByName, SimulationTestBed, TestBed}, + Aircraft, InitContext, SimulationElementVisitor, }, }; - use std::time::Duration; - use uom::si::{length::foot, power::watt}; + + use super::*; struct BatteryChargeLimiterTestBed { test_bed: SimulationTestBed, @@ -581,8 +585,9 @@ mod tests { impl BatteryChargeLimiterTestBed { fn new() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::new(Battery::half(1, electricity), electricity) + test_bed: SimulationTestBed::new(|context| { + let battery = Battery::half(context, 1); + TestAircraft::new(context, battery) }), } } @@ -754,7 +759,7 @@ mod tests { } fn current(&mut self) -> ElectricCurrent { - self.read(&format!("ELEC_BAT_{}_CURRENT", 1)) + self.read_by_name(&format!("ELEC_BAT_{}_CURRENT", 1)) } fn battery_contactor_is_closed(&self) -> bool { @@ -772,7 +777,7 @@ mod tests { } fn should_show_arrow_when_contactor_closed(&mut self) -> bool { - self.read("ELEC_CONTACTOR_TEST_SHOW_ARROW_WHEN_CLOSED") + self.read_by_name("ELEC_CONTACTOR_TEST_SHOW_ARROW_WHEN_CLOSED") } fn emergency_elec(mut self) -> Self { @@ -839,9 +844,9 @@ mod tests { is_available: bool, } impl TestEmergencyGenerator { - fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - identifier: identifier_provider.next(), + identifier: context.next_electrical_identifier(), is_available: false, } } @@ -944,20 +949,20 @@ mod tests { any_non_essential_bus_powered: bool, } impl TestAircraft { - fn new(battery: Battery, electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext, battery: Battery) -> Self { Self { battery_bus_electricity_source: TestElectricitySource::powered( + context, PotentialOrigin::TransformerRectifier(1), - electricity, ), battery, - battery_charge_limiter: BatteryChargeLimiter::new(1, "TEST"), + battery_charge_limiter: BatteryChargeLimiter::new(context, 1, "TEST"), battery_bus: ElectricalBus::new( + context, ElectricalBusType::DirectCurrentBattery, - electricity, ), - battery_contactor: Contactor::new("TEST", electricity), - emergency_generator: TestEmergencyGenerator::new(electricity), + battery_contactor: Contactor::new(context, "TEST"), + emergency_generator: TestEmergencyGenerator::new(context), consumer: PowerConsumer::from(ElectricalBusType::DirectCurrentBattery), apu_master_sw_pb_on: false, apu_start_pb_on: false, diff --git a/src/systems/systems/src/electrical/consumption.rs b/src/systems/systems/src/electrical/consumption.rs index e5a1e904b98..2228c93ddf3 100644 --- a/src/systems/systems/src/electrical/consumption.rs +++ b/src/systems/systems/src/electrical/consumption.rs @@ -17,6 +17,7 @@ //! load %, voltage, frequency and current. use super::ElectricalBusType; +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{random_number, ConsumePower, ElectricalBuses, FwcFlightPhase}, simulation::{ @@ -61,14 +62,18 @@ impl SimulationElement for PowerConsumer { /// A special type of power consumer which changes its consumption /// based on the phase of the flight. pub struct FlightPhasePowerConsumer { + fwc_flight_phase_id: VariableIdentifier, + consumer: PowerConsumer, base_demand: [Power; PowerConsumerFlightPhase::TaxiIn as usize + 1], current_flight_phase: PowerConsumerFlightPhase, update_after: Duration, } impl FlightPhasePowerConsumer { - pub fn from(bus_type: ElectricalBusType) -> Self { + pub fn new(context: &mut InitContext, bus_type: ElectricalBusType) -> Self { Self { + fwc_flight_phase_id: context.get_identifier("FWC_FLIGHT_PHASE".to_owned()), + consumer: PowerConsumer::from(bus_type), base_demand: Default::default(), current_flight_phase: PowerConsumerFlightPhase::BeforeStart, @@ -108,7 +113,7 @@ impl SimulationElement for FlightPhasePowerConsumer { fn read(&mut self, reader: &mut SimulatorReader) { let flight_phase: Option = - FromPrimitive::from_f64(reader.read("FWC_FLIGHT_PHASE")); + FromPrimitive::from_f64(reader.read(&self.fwc_flight_phase_id)); if let Some(phase) = flight_phase { self.current_flight_phase = PowerConsumerFlightPhase::from(phase); } @@ -149,14 +154,16 @@ mod tests { #[cfg(test)] mod flight_phase_power_consumer_tests { use crate::{ - electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, + electrical::{test::TestElectricitySource, ElectricalBus}, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Write, + Aircraft, }, }; use super::*; + use crate::simulation::test::WriteByName; + use crate::simulation::InitContext; struct FlightPhasePowerConsumerTestAircraft { electricity_source: TestElectricitySource, @@ -168,16 +175,16 @@ mod tests { fn new( consumer: FlightPhasePowerConsumer, bus_with_demand: ElectricalBusType, - electricity: &mut Electricity, + context: &mut InitContext, ) -> Self { Self { electricity_source: TestElectricitySource::unpowered( + context, PotentialOrigin::ApuGenerator(1), - electricity, ), apu_generator_consumption: None, consumer, - bus_with_demand: ElectricalBus::new(bus_with_demand, electricity), + bus_with_demand: ElectricalBus::new(context, bus_with_demand), } } @@ -234,27 +241,30 @@ mod tests { test_bed: &mut SimulationTestBed, phase: FwcFlightPhase, ) { - test_bed.write("FWC_FLIGHT_PHASE", phase as i32 as f64); + test_bed.write_by_name("FWC_FLIGHT_PHASE", phase as i32 as f64); } #[test] fn when_flight_phase_doesnt_have_demand_usage_is_zero() { - let mut test_bed = SimulationTestBed::new(|electricity| { + let mut test_bed = SimulationTestBed::new(|context| { FlightPhasePowerConsumerTestAircraft::new( - FlightPhasePowerConsumer::from(ElectricalBusType::AlternatingCurrent(1)) - .demand([ - ( - PowerConsumerFlightPhase::BeforeStart, - Power::new::(0.), - ), - (PowerConsumerFlightPhase::AfterStart, Power::new::(0.)), - (PowerConsumerFlightPhase::Takeoff, Power::new::(0.)), - (PowerConsumerFlightPhase::Flight, Power::new::(0.)), - (PowerConsumerFlightPhase::Landing, Power::new::(0.)), - (PowerConsumerFlightPhase::TaxiIn, Power::new::(0.)), - ]), + FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(0.), + ), + (PowerConsumerFlightPhase::AfterStart, Power::new::(0.)), + (PowerConsumerFlightPhase::Takeoff, Power::new::(0.)), + (PowerConsumerFlightPhase::Flight, Power::new::(0.)), + (PowerConsumerFlightPhase::Landing, Power::new::(0.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(0.)), + ]), ElectricalBusType::AlternatingCurrent(1), - electricity, + context, ) }); @@ -270,22 +280,25 @@ mod tests { #[test] fn when_flight_phase_does_have_demand_usage_is_close_to_demand() { let input = Power::new::(20000.); - let mut test_bed = SimulationTestBed::new(|electricity| { + let mut test_bed = SimulationTestBed::new(|context| { FlightPhasePowerConsumerTestAircraft::new( - FlightPhasePowerConsumer::from(ElectricalBusType::AlternatingCurrent(1)) - .demand([ - ( - PowerConsumerFlightPhase::BeforeStart, - Power::new::(0.), - ), - (PowerConsumerFlightPhase::AfterStart, Power::new::(0.)), - (PowerConsumerFlightPhase::Takeoff, Power::new::(0.)), - (PowerConsumerFlightPhase::Flight, input), - (PowerConsumerFlightPhase::Landing, Power::new::(0.)), - (PowerConsumerFlightPhase::TaxiIn, Power::new::(0.)), - ]), + FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(0.), + ), + (PowerConsumerFlightPhase::AfterStart, Power::new::(0.)), + (PowerConsumerFlightPhase::Takeoff, Power::new::(0.)), + (PowerConsumerFlightPhase::Flight, input), + (PowerConsumerFlightPhase::Landing, Power::new::(0.)), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(0.)), + ]), ElectricalBusType::AlternatingCurrent(1), - electricity, + context, ) }); @@ -300,31 +313,34 @@ mod tests { #[test] fn when_flight_phase_does_have_demand_but_consumer_unpowered_usage_is_zero() { - let mut test_bed = SimulationTestBed::new(|electricity| { + let mut test_bed = SimulationTestBed::new(|context| { FlightPhasePowerConsumerTestAircraft::new( - FlightPhasePowerConsumer::from(ElectricalBusType::AlternatingCurrent(1)) - .demand([ - ( - PowerConsumerFlightPhase::BeforeStart, - Power::new::(20000.), - ), - ( - PowerConsumerFlightPhase::AfterStart, - Power::new::(20000.), - ), - ( - PowerConsumerFlightPhase::Takeoff, - Power::new::(20000.), - ), - (PowerConsumerFlightPhase::Flight, Power::new::(20000.)), - ( - PowerConsumerFlightPhase::Landing, - Power::new::(20000.), - ), - (PowerConsumerFlightPhase::TaxiIn, Power::new::(20000.)), - ]), + FlightPhasePowerConsumer::new( + context, + ElectricalBusType::AlternatingCurrent(1), + ) + .demand([ + ( + PowerConsumerFlightPhase::BeforeStart, + Power::new::(20000.), + ), + ( + PowerConsumerFlightPhase::AfterStart, + Power::new::(20000.), + ), + ( + PowerConsumerFlightPhase::Takeoff, + Power::new::(20000.), + ), + (PowerConsumerFlightPhase::Flight, Power::new::(20000.)), + ( + PowerConsumerFlightPhase::Landing, + Power::new::(20000.), + ), + (PowerConsumerFlightPhase::TaxiIn, Power::new::(20000.)), + ]), ElectricalBusType::AlternatingCurrent(1), - electricity, + context, ) }); diff --git a/src/systems/systems/src/electrical/emergency_generator.rs b/src/systems/systems/src/electrical/emergency_generator.rs index 618fbe88ffd..b87565fb7ec 100644 --- a/src/systems/systems/src/electrical/emergency_generator.rs +++ b/src/systems/systems/src/electrical/emergency_generator.rs @@ -1,15 +1,17 @@ use std::time::Duration; +use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; + +use crate::{ + shared::{PowerConsumptionReport, RamAirTurbineHydraulicLoopPressurised}, + simulation::{InitContext, SimulationElement, SimulatorWriter, UpdateContext}, +}; + use super::{ ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, ElectricalStateWriter, ElectricitySource, Potential, PotentialOrigin, ProvideFrequency, ProvidePotential, }; -use crate::{ - shared::{PowerConsumptionReport, RamAirTurbineHydraulicLoopPressurised}, - simulation::{SimulationElement, SimulatorWriter, UpdateContext}, -}; -use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; pub struct EmergencyGenerator { identifier: ElectricalElementIdentifier, @@ -21,12 +23,10 @@ pub struct EmergencyGenerator { starting_or_started: bool, } impl EmergencyGenerator { - pub fn new( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> EmergencyGenerator { + pub fn new(context: &mut InitContext) -> EmergencyGenerator { EmergencyGenerator { - identifier: identifier_provider.next(), - writer: ElectricalStateWriter::new("EMER_GEN"), + identifier: context.next_electrical_identifier(), + writer: ElectricalStateWriter::new(context, "EMER_GEN"), supplying: false, output_frequency: Frequency::new::(0.), output_potential: ElectricPotential::new::(0.), @@ -122,11 +122,13 @@ impl SimulationElement for EmergencyGenerator { #[cfg(test)] mod emergency_generator_tests { use super::*; + use crate::simulation::test::ReadByName; + use crate::simulation::InitContext; use crate::{ electrical::Electricity, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, SimulationElementVisitor, UpdateContext, + Aircraft, SimulationElementVisitor, UpdateContext, }, }; @@ -136,16 +138,16 @@ mod emergency_generator_tests { impl EmergencyGeneratorTestBed { fn new() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| TestAircraft::new(electricity)), + test_bed: SimulationTestBed::new(TestAircraft::new), } } fn frequency_is_normal(&mut self) -> bool { - self.read("ELEC_EMER_GEN_FREQUENCY_NORMAL") + self.read_by_name("ELEC_EMER_GEN_FREQUENCY_NORMAL") } fn potential_is_normal(&mut self) -> bool { - self.read("ELEC_EMER_GEN_POTENTIAL_NORMAL") + self.read_by_name("ELEC_EMER_GEN_POTENTIAL_NORMAL") } fn emer_gen_is_powered(&self) -> bool { @@ -190,9 +192,9 @@ mod emergency_generator_tests { generator_output_within_normal_parameters_before_processing_power_consumption_report: bool, } impl TestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - emer_gen: EmergencyGenerator::new(electricity), + emer_gen: EmergencyGenerator::new(context), hydraulic: TestHydraulicSystem::new(), generator_output_within_normal_parameters_before_processing_power_consumption_report: false, } @@ -362,12 +364,12 @@ mod emergency_generator_tests { #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run(); - assert!(test_bed.contains_key("ELEC_EMER_GEN_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_EMER_GEN_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_EMER_GEN_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_EMER_GEN_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EMER_GEN_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EMER_GEN_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EMER_GEN_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_EMER_GEN_FREQUENCY_NORMAL")); } } diff --git a/src/systems/systems/src/electrical/engine_generator.rs b/src/systems/systems/src/electrical/engine_generator.rs index d671053d5a4..a0f7618eeef 100644 --- a/src/systems/systems/src/electrical/engine_generator.rs +++ b/src/systems/systems/src/electrical/engine_generator.rs @@ -1,21 +1,25 @@ -use super::{ - ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, - ElectricalStateWriter, ElectricitySource, EngineGeneratorPushButtons, Potential, - PotentialOrigin, ProvideFrequency, ProvideLoad, ProvidePotential, +use std::cmp::min; + +use uom::si::{ + electric_potential::volt, f64::*, frequency::hertz, power::watt, ratio::percent, + thermodynamic_temperature::degree_celsius, }; + use crate::{ shared::{ calculate_towards_target_temperature, EngineCorrectedN2, EngineFirePushButtons, PowerConsumptionReport, }, simulation::{ - SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, Write, + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + VariableIdentifier, Write, }, }; -use std::cmp::min; -use uom::si::{ - electric_potential::volt, f64::*, frequency::hertz, power::watt, ratio::percent, - thermodynamic_temperature::degree_celsius, + +use super::{ + ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, + ElectricalStateWriter, ElectricitySource, EngineGeneratorPushButtons, Potential, + PotentialOrigin, ProvideFrequency, ProvideLoad, ProvidePotential, }; pub const INTEGRATED_DRIVE_GENERATOR_STABILIZATION_TIME_IN_MILLISECONDS: u64 = 500; @@ -30,15 +34,12 @@ pub struct EngineGenerator { load: Ratio, } impl EngineGenerator { - pub fn new( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> EngineGenerator { + pub fn new(context: &mut InitContext, number: usize) -> EngineGenerator { EngineGenerator { - writer: ElectricalStateWriter::new(&format!("ENG_GEN_{}", number)), + writer: ElectricalStateWriter::new(context, &format!("ENG_GEN_{}", number)), number, - identifier: identifier_provider.next(), - idg: IntegratedDriveGenerator::new(number), + identifier: context.next_electrical_identifier(), + idg: IntegratedDriveGenerator::new(context, number), output_frequency: Frequency::new::(0.), output_potential: ElectricPotential::new::(0.), load: Ratio::new::(0.), @@ -138,9 +139,9 @@ impl SimulationElement for EngineGenerator { } struct IntegratedDriveGenerator { - oil_outlet_temperature_id: String, + oil_outlet_temperature_id: VariableIdentifier, oil_outlet_temperature: ThermodynamicTemperature, - is_connected_id: String, + is_connected_id: VariableIdentifier, connected: bool, activated: bool, number: usize, @@ -151,14 +152,15 @@ impl IntegratedDriveGenerator { pub const ENGINE_N2_POWER_UP_OUTPUT_THRESHOLD: f64 = 58.; pub const ENGINE_N2_POWER_DOWN_OUTPUT_THRESHOLD: f64 = 56.; - fn new(number: usize) -> IntegratedDriveGenerator { + fn new(context: &mut InitContext, number: usize) -> IntegratedDriveGenerator { IntegratedDriveGenerator { - oil_outlet_temperature_id: format!( + oil_outlet_temperature_id: context.get_identifier(format!( "ELEC_ENG_GEN_{}_IDG_OIL_OUTLET_TEMPERATURE", number - ), + )), oil_outlet_temperature: ThermodynamicTemperature::new::(0.), - is_connected_id: format!("ELEC_ENG_GEN_{}_IDG_IS_CONNECTED", number), + is_connected_id: context + .get_identifier(format!("ELEC_ENG_GEN_{}_IDG_IS_CONNECTED", number)), connected: true, activated: true, number, @@ -347,13 +349,15 @@ mod tests { #[cfg(test)] mod engine_generator_tests { use super::*; + use crate::simulation::test::ReadByName; + use crate::simulation::InitContext; use crate::{ electrical::{ consumption::PowerConsumer, ElectricalBus, ElectricalBusType, Electricity, }, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, + Aircraft, }, }; @@ -363,34 +367,30 @@ mod tests { impl EngineGeneratorTestBed { fn with_running_engine() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::with_running_engine(electricity) - }), + test_bed: SimulationTestBed::new(TestAircraft::with_running_engine), } } fn with_shutdown_engine() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::with_shutdown_engine(electricity) - }), + test_bed: SimulationTestBed::new(TestAircraft::with_shutdown_engine), } } fn frequency_is_normal(&mut self) -> bool { - self.read("ELEC_ENG_GEN_1_FREQUENCY_NORMAL") + self.read_by_name("ELEC_ENG_GEN_1_FREQUENCY_NORMAL") } fn potential_is_normal(&mut self) -> bool { - self.read("ELEC_ENG_GEN_1_POTENTIAL_NORMAL") + self.read_by_name("ELEC_ENG_GEN_1_POTENTIAL_NORMAL") } fn load_is_normal(&mut self) -> bool { - self.read("ELEC_ENG_GEN_1_LOAD_NORMAL") + self.read_by_name("ELEC_ENG_GEN_1_LOAD_NORMAL") } fn load(&mut self) -> Ratio { - self.read("ELEC_ENG_GEN_1_LOAD") + self.read_by_name("ELEC_ENG_GEN_1_LOAD") } fn generator_is_powered(&mut self) -> bool { @@ -421,10 +421,10 @@ mod tests { bool, } impl TestAircraft { - fn new(running: bool, electricity: &mut Electricity) -> Self { + fn new(running: bool, context: &mut InitContext) -> Self { Self { - engine_gen: EngineGenerator::new(1, electricity), - bus: ElectricalBus::new(ElectricalBusType::AlternatingCurrent(1), electricity), + engine_gen: EngineGenerator::new(context, 1), + bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), running, gen_push_button_on: true, idg_push_button_released: false, @@ -434,12 +434,12 @@ mod tests { } } - fn with_shutdown_engine(electricity: &mut Electricity) -> Self { - TestAircraft::new(false, electricity) + fn with_shutdown_engine(context: &mut InitContext) -> Self { + TestAircraft::new(false, context) } - fn with_running_engine(electricity: &mut Electricity) -> Self { - TestAircraft::new(true, electricity) + fn with_running_engine(context: &mut InitContext) -> Self { + TestAircraft::new(true, context) } fn disconnect_idg(&mut self) { @@ -770,52 +770,55 @@ mod tests { let mut test_bed = EngineGeneratorTestBed::with_running_engine(); test_bed.run(); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_FREQUENCY_NORMAL")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_LOAD")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_LOAD_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_LOAD")); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_LOAD_NORMAL")); } } #[cfg(test)] mod integrated_drive_generator_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; use std::time::Duration; - fn idg() -> IntegratedDriveGenerator { - IntegratedDriveGenerator::new(1) + fn idg(context: &mut InitContext) -> IntegratedDriveGenerator { + IntegratedDriveGenerator::new(context, 1) } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(idg()); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)); test_bed.run(); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_IDG_OIL_OUTLET_TEMPERATURE")); - assert!(test_bed.contains_key("ELEC_ENG_GEN_1_IDG_IS_CONNECTED")); + assert!( + test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_IDG_OIL_OUTLET_TEMPERATURE") + ); + assert!(test_bed.contains_variable_with_name("ELEC_ENG_GEN_1_IDG_IS_CONNECTED")); } #[test] fn starts_unstable() { - assert_eq!(idg().provides_stable_power_output(), false); + let test_bed = SimulationTestBed::from(ElementCtorFn(idg)); + + assert!(test_bed.query_element(|e| !e.provides_stable_power_output())); } #[test] fn becomes_stable_once_engine_above_threshold_for_500_milliseconds() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, false), &TestFireOverhead::new(false), ) - }, - ); + }); test_bed.run_with_delta(Duration::from_millis(500)); @@ -827,16 +830,15 @@ mod tests { #[test] fn does_not_become_stable_before_engine_above_threshold_for_500_milliseconds() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, false), &TestFireOverhead::new(false), ) - }, - ); + }); test_bed.run_with_delta(Duration::from_millis(499)); @@ -848,16 +850,15 @@ mod tests { #[test] fn cannot_reconnect_once_disconnected() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, true), &TestFireOverhead::new(false), ) - }, - ); + }); test_bed.run_with_delta(Duration::from_millis(500)); @@ -880,16 +881,15 @@ mod tests { #[test] fn running_engine_warms_up_idg() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, false), &TestFireOverhead::new(false), ) - }, - ); + }); let starting_temperature = test_bed.query_element(|e| e.oil_outlet_temperature); @@ -900,16 +900,15 @@ mod tests { #[test] fn running_engine_does_not_warm_up_idg_when_disconnected() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, true), &TestFireOverhead::new(false), ) - }, - ); + }); let starting_temperature = test_bed.query_element(|e| e.oil_outlet_temperature); @@ -923,16 +922,15 @@ mod tests { #[test] fn shutdown_engine_cools_down_idg() { - let mut test_bed = SimulationTestBed::from(idg()).with_update_after_power_distribution( - |idg, context| { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(idg)) + .with_update_after_power_distribution(|idg, context| { idg.update( context, &TestEngine::new(Ratio::new::(80.)), &TestOverhead::new(true, false), &TestFireOverhead::new(false), ) - }, - ); + }); test_bed.run_with_delta(Duration::from_secs(10)); diff --git a/src/systems/systems/src/electrical/external_power_source.rs b/src/systems/systems/src/electrical/external_power_source.rs index 8b15e6d9bf8..4fa0f7db160 100644 --- a/src/systems/systems/src/electrical/external_power_source.rs +++ b/src/systems/systems/src/electrical/external_power_source.rs @@ -1,8 +1,12 @@ +use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; + use crate::{ shared::PowerConsumptionReport, - simulation::{Read, SimulationElement, SimulatorReader, SimulatorWriter, UpdateContext}, + simulation::{ + InitContext, Read, SimulationElement, SimulatorReader, SimulatorWriter, UpdateContext, + VariableIdentifier, + }, }; -use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; use super::{ ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, @@ -11,6 +15,8 @@ use super::{ }; pub struct ExternalPowerSource { + external_power_available_id: VariableIdentifier, + identifier: ElectricalElementIdentifier, writer: ElectricalStateWriter, is_connected: bool, @@ -18,12 +24,12 @@ pub struct ExternalPowerSource { output_potential: ElectricPotential, } impl ExternalPowerSource { - pub fn new( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> ExternalPowerSource { + pub fn new(context: &mut InitContext) -> ExternalPowerSource { ExternalPowerSource { - identifier: identifier_provider.next(), - writer: ElectricalStateWriter::new("EXT_PWR"), + external_power_available_id: context + .get_identifier("EXTERNAL POWER AVAILABLE:1".to_owned()), + identifier: context.next_electrical_identifier(), + writer: ElectricalStateWriter::new(context, "EXT_PWR"), is_connected: false, output_frequency: Frequency::new::(0.), output_potential: ElectricPotential::new::(0.), @@ -69,7 +75,7 @@ provide_potential!(ExternalPowerSource, (110.0..=120.0)); provide_frequency!(ExternalPowerSource, (390.0..=410.0)); impl SimulationElement for ExternalPowerSource { fn read(&mut self, reader: &mut SimulatorReader) { - self.is_connected = reader.read("EXTERNAL POWER AVAILABLE:1"); + self.is_connected = reader.read(&self.external_power_available_id); } fn write(&self, writer: &mut SimulatorWriter) { @@ -98,11 +104,13 @@ impl SimulationElement for ExternalPowerSource { #[cfg(test)] mod external_power_source_tests { use super::*; + use crate::simulation::test::{ReadByName, WriteByName}; + use crate::simulation::InitContext; use crate::{ electrical::Electricity, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, SimulationElementVisitor, Write, + Aircraft, SimulationElementVisitor, }, }; @@ -112,7 +120,7 @@ mod external_power_source_tests { impl ExternalPowerTestBed { fn new() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| TestAircraft::new(electricity)), + test_bed: SimulationTestBed::new(TestAircraft::new), } } @@ -122,20 +130,20 @@ mod external_power_source_tests { } fn with_connected_external_power(mut self) -> Self { - self.write("EXTERNAL POWER AVAILABLE:1", true); + self.write_by_name("EXTERNAL POWER AVAILABLE:1", true); self } fn disconnect_external_power(&mut self) { - self.write("EXTERNAL POWER AVAILABLE:1", false); + self.write_by_name("EXTERNAL POWER AVAILABLE:1", false); } fn frequency_is_normal(&mut self) -> bool { - self.read("ELEC_EXT_PWR_FREQUENCY_NORMAL") + self.read_by_name("ELEC_EXT_PWR_FREQUENCY_NORMAL") } fn potential_is_normal(&mut self) -> bool { - self.read("ELEC_EXT_PWR_POTENTIAL_NORMAL") + self.read_by_name("ELEC_EXT_PWR_POTENTIAL_NORMAL") } fn ext_pwr_is_powered(&self) -> bool { @@ -159,9 +167,9 @@ mod external_power_source_tests { ext_pwr_output_within_normal_parameters_before_processing_power_consumption_report: bool, } impl TestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { - ext_pwr: ExternalPowerSource::new(electricity), + ext_pwr: ExternalPowerSource::new(context), ext_pwr_output_within_normal_parameters_before_processing_power_consumption_report: false, } } @@ -294,13 +302,13 @@ mod external_power_source_tests { #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run(); - assert!(test_bed.contains_key("ELEC_EXT_PWR_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_EXT_PWR_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_EXT_PWR_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_EXT_PWR_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EXT_PWR_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EXT_PWR_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_EXT_PWR_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_EXT_PWR_FREQUENCY_NORMAL")); } } diff --git a/src/systems/systems/src/electrical/mod.rs b/src/systems/systems/src/electrical/mod.rs index 97f3b758d27..ffcbf29eaa5 100644 --- a/src/systems/systems/src/electrical/mod.rs +++ b/src/systems/systems/src/electrical/mod.rs @@ -10,11 +10,11 @@ mod static_inverter; mod transformer_rectifier; use std::{ cell::{Ref, RefCell}, - collections::{HashMap, HashSet}, rc::Rc, time::Duration, }; +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{ ConsumePower, ElectricalBusType, ElectricalBuses, PotentialOrigin, PowerConsumptionReport, @@ -30,9 +30,11 @@ pub use engine_generator::{ EngineGenerator, INTEGRATED_DRIVE_GENERATOR_STABILIZATION_TIME_IN_MILLISECONDS, }; pub use external_power_source::ExternalPowerSource; +use fxhash::{FxHashMap, FxHashSet}; pub use static_inverter::StaticInverter; pub use transformer_rectifier::TransformerRectifier; use uom::si::{electric_potential::volt, f64::*, power::watt, velocity::knot}; + pub mod test; pub trait AlternatingCurrentElectricalSystem { @@ -53,17 +55,14 @@ pub trait BatteryPushButtons { #[derive(Debug)] pub struct Contactor { identifier: ElectricalElementIdentifier, - closed_id: String, + closed_id: VariableIdentifier, closed: bool, } impl Contactor { - pub fn new( - id: &str, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> Contactor { + pub fn new(context: &mut InitContext, id: &str) -> Contactor { Contactor { - identifier: identifier_provider.next(), - closed_id: format!("ELEC_CONTACTOR_{}_IS_CLOSED", id), + identifier: context.next_electrical_identifier(), + closed_id: context.get_identifier(format!("ELEC_CONTACTOR_{}_IS_CLOSED", id)), closed: false, } } @@ -101,20 +100,21 @@ impl SimulationElement for Contactor { pub struct ElectricalBus { identifier: ElectricalElementIdentifier, - bus_powered_id: String, - bus_potential_normal_id: String, + bus_powered_id: VariableIdentifier, + bus_potential_normal_id: VariableIdentifier, potential: ElectricPotential, bus_type: ElectricalBusType, } impl ElectricalBus { - pub fn new( - bus_type: ElectricalBusType, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> ElectricalBus { + pub fn new(context: &mut InitContext, bus_type: ElectricalBusType) -> ElectricalBus { ElectricalBus { - identifier: identifier_provider.next_for_bus(bus_type), - bus_powered_id: format!("ELEC_{}_BUS_IS_POWERED", bus_type.to_string()), - bus_potential_normal_id: format!("ELEC_{}_BUS_POTENTIAL_NORMAL", bus_type.to_string()), + identifier: context.next_electrical_identifier_for_bus(bus_type), + bus_powered_id: context + .get_identifier(format!("ELEC_{}_BUS_IS_POWERED", bus_type.to_string())), + bus_potential_normal_id: context.get_identifier(format!( + "ELEC_{}_BUS_POTENTIAL_NORMAL", + bus_type.to_string() + )), potential: ElectricPotential::new::(0.), bus_type, } @@ -167,26 +167,29 @@ impl SimulationElement for ElectricalBus { } pub struct ElectricalStateWriter { - current_id: String, - current_normal_id: String, - potential_id: String, - potential_normal_id: String, - frequency_id: String, - frequency_normal_id: String, - load_id: String, - load_normal_id: String, + current_id: VariableIdentifier, + current_normal_id: VariableIdentifier, + potential_id: VariableIdentifier, + potential_normal_id: VariableIdentifier, + frequency_id: VariableIdentifier, + frequency_normal_id: VariableIdentifier, + load_id: VariableIdentifier, + load_normal_id: VariableIdentifier, } impl ElectricalStateWriter { - pub fn new(element_id: &str) -> Self { + pub fn new(context: &mut InitContext, element_id: &str) -> Self { Self { - current_id: format!("ELEC_{}_CURRENT", element_id), - current_normal_id: format!("ELEC_{}_CURRENT_NORMAL", element_id), - potential_id: format!("ELEC_{}_POTENTIAL", element_id), - potential_normal_id: format!("ELEC_{}_POTENTIAL_NORMAL", element_id), - frequency_id: format!("ELEC_{}_FREQUENCY", element_id), - frequency_normal_id: format!("ELEC_{}_FREQUENCY_NORMAL", element_id), - load_id: format!("ELEC_{}_LOAD", element_id), - load_normal_id: format!("ELEC_{}_LOAD_NORMAL", element_id), + current_id: context.get_identifier(format!("ELEC_{}_CURRENT", element_id)), + current_normal_id: context + .get_identifier(format!("ELEC_{}_CURRENT_NORMAL", element_id)), + potential_id: context.get_identifier(format!("ELEC_{}_POTENTIAL", element_id)), + potential_normal_id: context + .get_identifier(format!("ELEC_{}_POTENTIAL_NORMAL", element_id)), + frequency_id: context.get_identifier(format!("ELEC_{}_FREQUENCY", element_id)), + frequency_normal_id: context + .get_identifier(format!("ELEC_{}_FREQUENCY_NORMAL", element_id)), + load_id: context.get_identifier(format!("ELEC_{}_LOAD", element_id)), + load_normal_id: context.get_identifier(format!("ELEC_{}_LOAD_NORMAL", element_id)), } } @@ -327,14 +330,17 @@ impl ElectricalElementIdentifier { } pub trait ElectricalElementIdentifierProvider { - fn next(&mut self) -> ElectricalElementIdentifier; - fn next_for_bus(&mut self, bus_type: ElectricalBusType) -> ElectricalElementIdentifier; + fn next_electrical_identifier(&mut self) -> ElectricalElementIdentifier; + fn next_electrical_identifier_for_bus( + &mut self, + bus_type: ElectricalBusType, + ) -> ElectricalElementIdentifier; } #[derive(Debug)] pub struct Electricity { next_identifier: ElectricalElementIdentifier, - buses: HashMap, + buses: FxHashMap, potential: PotentialCollection, none_potential: RefCell, } @@ -342,7 +348,7 @@ impl Electricity { pub fn new() -> Self { Self { next_identifier: ElectricalElementIdentifier::first(), - buses: HashMap::new(), + buses: Default::default(), potential: PotentialCollection::new(), none_potential: RefCell::new(Potential::none()), } @@ -361,10 +367,19 @@ impl Electricity { /// input and AC output. Thus for these specific element types, one has to make sure the /// argument order is correct. /// ```rust - /// # use systems::{shared::ElectricalBusType, electrical::{Contactor, ElectricalBus, Electricity}}; - /// let mut electricity = Electricity::new(); - /// let contactor = Contactor::new("TEST", &mut electricity); - /// let bus = ElectricalBus::new(ElectricalBusType::DirectCurrentBattery, &mut electricity); + /// # use systems::{shared::ElectricalBusType, electrical::{Contactor, ElectricalBus, Electricity}, + /// # simulation::{InitContext, VariableRegistry, VariableIdentifier}}; + /// # struct SomeVariableRegistry {} + /// # impl VariableRegistry for SomeVariableRegistry { + /// # fn get(&mut self, name: String) -> VariableIdentifier { + /// # VariableIdentifier::default() + /// # } + /// # } + /// # let mut registry = SomeVariableRegistry {}; + /// # let mut electricity = Electricity::new(); + /// # let mut context = InitContext::new(&mut electricity, &mut registry); + /// let contactor = Contactor::new(&mut context, "TEST"); + /// let bus = ElectricalBus::new(&mut context, ElectricalBusType::DirectCurrentBattery); /// /// electricity.flow(&contactor, &bus); /// ``` @@ -382,10 +397,19 @@ impl Electricity { /// Takes the output supplied by the given source of electricity, such that /// it can then [flow](`Self::flow()`) through the electrical system. /// ```rust - /// # use systems::electrical::{Contactor, Electricity, EngineGenerator}; - /// let mut electricity = Electricity::new(); - /// let generator = EngineGenerator::new(1, &mut electricity); - /// let contactor = Contactor::new("TEST", &mut electricity); + /// # use systems::{shared::ElectricalBusType, electrical::{Contactor, ElectricalBus, Electricity, EngineGenerator}, + /// # simulation::{InitContext, VariableRegistry, VariableIdentifier}}; + /// # struct SomeVariableRegistry {} + /// # impl VariableRegistry for SomeVariableRegistry { + /// # fn get(&mut self, name: String) -> VariableIdentifier { + /// # VariableIdentifier::default() + /// # } + /// # } + /// # let mut registry = SomeVariableRegistry {}; + /// # let mut electricity = Electricity::new(); + /// # let mut context = InitContext::new(&mut electricity, &mut registry); + /// let generator = EngineGenerator::new(&mut context, 1); + /// let contactor = Contactor::new(&mut context, "TEST"); /// /// electricity.supplied_by(&generator); /// electricity.flow(&generator, &contactor); @@ -400,11 +424,21 @@ impl Electricity { /// Transforms electricity within the given transformer. /// ```rust - /// # use systems::{shared::ElectricalBusType, electrical::{TransformerRectifier, ElectricalBus, Electricity}}; - /// let mut electricity = Electricity::new(); - /// let ac_bus = ElectricalBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); - /// let tr = TransformerRectifier::new(1, &mut electricity); - /// let dc_bus = ElectricalBus::new(ElectricalBusType::DirectCurrent(1), &mut electricity); + /// # use systems::{shared::ElectricalBusType, electrical::{Contactor, ElectricalBus, Electricity}, + /// # simulation::{InitContext, VariableRegistry, VariableIdentifier}}; + /// # use systems::electrical::TransformerRectifier; + /// # struct SomeVariableRegistry {} + /// # impl VariableRegistry for SomeVariableRegistry { + /// # fn get(&mut self, name: String) -> VariableIdentifier { + /// # VariableIdentifier::default() + /// # } + /// # } + /// # let mut registry = SomeVariableRegistry {}; + /// # let mut electricity = Electricity::new(); + /// # let mut context = InitContext::new(&mut electricity, &mut registry); + /// let ac_bus = ElectricalBus::new(&mut context, ElectricalBusType::AlternatingCurrent(1)); + /// let tr = TransformerRectifier::new(&mut context, 1); + /// let dc_bus = ElectricalBus::new(&mut context, ElectricalBusType::DirectCurrent(1)); /// /// electricity.flow(&ac_bus, &tr); /// electricity.transform_in(&tr); @@ -477,15 +511,18 @@ impl Electricity { } } impl ElectricalElementIdentifierProvider for Electricity { - fn next(&mut self) -> ElectricalElementIdentifier { + fn next_electrical_identifier(&mut self) -> ElectricalElementIdentifier { let identifier = self.next_identifier; self.next_identifier = identifier.next(); identifier } - fn next_for_bus(&mut self, bus_type: ElectricalBusType) -> ElectricalElementIdentifier { - let identifier = self.next(); + fn next_electrical_identifier_for_bus( + &mut self, + bus_type: ElectricalBusType, + ) -> ElectricalElementIdentifier { + let identifier = self.next_electrical_identifier(); self.buses.insert(bus_type, identifier); identifier @@ -631,26 +668,26 @@ impl<'a> SimulationElementVisitor for ProcessPowerConsumptionReportVisitor<'a> { /// when it is. #[derive(Debug)] pub struct Potential { - origins: HashSet, - elements: HashSet, + origins: FxHashSet, + elements: FxHashSet, raw: ElectricPotential, } impl Potential { pub fn new(origin: PotentialOrigin, raw: ElectricPotential) -> Self { - let mut origins = HashSet::new(); + let mut origins = FxHashSet::default(); origins.insert(origin); Self { origins, - elements: HashSet::new(), + elements: FxHashSet::default(), raw, } } pub fn none() -> Self { Self { - origins: HashSet::new(), - elements: HashSet::new(), + origins: FxHashSet::default(), + elements: FxHashSet::default(), raw: ElectricPotential::new::(0.), } } @@ -738,7 +775,7 @@ impl Potential { } pub fn is_pair(&self, x: PotentialOrigin, y: PotentialOrigin) -> bool { - let mut set = HashSet::new(); + let mut set = FxHashSet::default(); set.insert(x); set.insert(y); @@ -754,14 +791,14 @@ impl Default for Potential { /// Maintains the many to one relationship from electrical elements to their electric potential. #[derive(Debug)] struct PotentialCollection { - items: HashMap>>, - consumption_per_origin: HashMap, + items: FxHashMap>>, + consumption_per_origin: FxHashMap, } impl PotentialCollection { fn new() -> Self { Self { - items: HashMap::new(), - consumption_per_origin: HashMap::new(), + items: Default::default(), + consumption_per_origin: Default::default(), } } @@ -902,9 +939,10 @@ mod tests { #[cfg(test)] mod electrical_bus_tests { use super::*; + use crate::simulation::test::ReadByName; use crate::simulation::{ test::{ElementCtorFn, SimulationTestBed, TestAircraft, TestBed}, - Aircraft, Read, + Aircraft, InitContext, }; #[test] @@ -912,17 +950,17 @@ mod tests { let mut test_bed = SimulationTestBed::from(ElementCtorFn(electrical_bus)); test_bed.run(); - assert!(test_bed.contains_key("ELEC_AC_2_BUS_IS_POWERED")); + assert!(test_bed.contains_variable_with_name("ELEC_AC_2_BUS_IS_POWERED")); } #[test] fn sub_bus_does_not_write_its_state() { - let mut test_bed = SimulationTestBed::new(|electricity| { - ElectricalBusTestAircraft::new(ElectricalBusType::Sub("202PP"), electricity) + let mut test_bed = SimulationTestBed::new(|context| { + ElectricalBusTestAircraft::new(ElectricalBusType::Sub("202PP"), context) }); test_bed.run(); - assert!(!test_bed.contains_key("ELEC_SUB_202PP_BUS_IS_POWERED")); + assert!(!test_bed.contains_variable_with_name("ELEC_SUB_202PP_BUS_IS_POWERED")); } struct BatteryStub { @@ -930,11 +968,9 @@ mod tests { potential: ElectricPotential, } impl BatteryStub { - fn new( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> BatteryStub { + fn new(context: &mut InitContext) -> BatteryStub { BatteryStub { - identifier: identifier_provider.next(), + identifier: context.next_electrical_identifier(), potential: ElectricPotential::new::(0.), } } @@ -971,10 +1007,10 @@ mod tests { battery: BatteryStub, } impl ElectricalBusTestAircraft { - fn new(bus_type: ElectricalBusType, electricity: &mut Electricity) -> Self { + fn new(bus_type: ElectricalBusType, context: &mut InitContext) -> Self { Self { - bus: ElectricalBus::new(bus_type, electricity), - battery: BatteryStub::new(electricity), + bus: ElectricalBus::new(context, bus_type), + battery: BatteryStub::new(context), } } @@ -1001,204 +1037,221 @@ mod tests { #[test] fn bat_bus_at_25_volt_is_abnormal() { - let mut test_bed = SimulationTestBed::new(|electricity| { - ElectricalBusTestAircraft::new(ElectricalBusType::DirectCurrentBattery, electricity) + let mut test_bed = SimulationTestBed::new(|context| { + ElectricalBusTestAircraft::new(ElectricalBusType::DirectCurrentBattery, context) }); test_bed.command(|a| a.powered_by_battery_at(ElectricPotential::new::(25.))); test_bed.run(); assert_eq!( - Read::::read(&mut test_bed, "ELEC_DC_BAT_BUS_POTENTIAL_NORMAL"), + ReadByName::, bool>::read_by_name( + &mut test_bed, + "ELEC_DC_BAT_BUS_POTENTIAL_NORMAL" + ), false ); } #[test] fn bat_bus_above_25_volt_is_abnormal() { - let mut test_bed = SimulationTestBed::new(|electricity| { - ElectricalBusTestAircraft::new(ElectricalBusType::DirectCurrentBattery, electricity) + let mut test_bed = SimulationTestBed::new(|context| { + ElectricalBusTestAircraft::new(ElectricalBusType::DirectCurrentBattery, context) }); test_bed.command(|a| a.powered_by_battery_at(ElectricPotential::new::(25.01))); test_bed.run(); assert_eq!( - Read::::read(&mut test_bed, "ELEC_DC_BAT_BUS_POTENTIAL_NORMAL"), + ReadByName::, bool>::read_by_name( + &mut test_bed, + "ELEC_DC_BAT_BUS_POTENTIAL_NORMAL" + ), true ); } #[test] fn writes_potential_normal_when_bat_bus() { - let mut test_bed = SimulationTestBed::new(|electricity| { + let mut test_bed = SimulationTestBed::new(|context| { TestAircraft::new(ElectricalBus::new( + context, ElectricalBusType::DirectCurrentBattery, - electricity, )) }); test_bed.run(); - assert!(test_bed.contains_key("ELEC_DC_BAT_BUS_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_DC_BAT_BUS_POTENTIAL_NORMAL")); } #[test] fn does_not_write_potential_normal_when_not_bat_bus() { - let mut test_bed = SimulationTestBed::new(|electricity| { + let mut test_bed = SimulationTestBed::new(|context| { TestAircraft::new(ElectricalBus::new( + context, ElectricalBusType::AlternatingCurrentEssential, - electricity, )) }); test_bed.run(); - assert!(!test_bed.contains_key("ELEC_DC_BAT_BUS_POTENTIAL_NORMAL")); + assert!(!test_bed.contains_variable_with_name("ELEC_DC_BAT_BUS_POTENTIAL_NORMAL")); } - fn electrical_bus(electricity: &mut Electricity) -> ElectricalBus { - ElectricalBus::new(ElectricalBusType::AlternatingCurrent(2), electricity) + fn electrical_bus(context: &mut InitContext) -> ElectricalBus { + ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)) } } #[cfg(test)] mod contactor_tests { use super::*; + use crate::simulation::{Aircraft, InitContext}; use crate::{ electrical::test::TestElectricitySource, - simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}, + simulation::test::{SimulationTestBed, TestBed}, }; - #[test] - fn contactor_starts_open() { - assert!(contactor(&mut Electricity::new()).is_open()); + struct ContactorTestAircraft { + contactor: Contactor, + power_source: TestElectricitySource, } + impl ContactorTestAircraft { + fn new_closed(context: &mut InitContext) -> Self { + Self::new(context, true) + } - #[test] - fn open_contactor_when_toggled_open_stays_open() { - let mut contactor = open_contactor(&mut Electricity::new()); - contactor.close_when(false); + fn new_open(context: &mut InitContext) -> Self { + Self::new(context, false) + } - assert!(contactor.is_open()); - } + fn new(context: &mut InitContext, closed: bool) -> Self { + let mut contactor = Contactor::new(context, "TEST"); + contactor.closed = closed; + Self { + contactor, + power_source: TestElectricitySource::unpowered( + context, + PotentialOrigin::External, + ), + } + } - #[test] - fn open_contactor_when_toggled_closed_closes() { - let mut contactor = open_contactor(&mut Electricity::new()); - contactor.close_when(true); + fn open_contactor(&mut self) { + self.contactor.close_when(false); + } + + fn close_contactor(&mut self) { + self.contactor.close_when(true); + } + + fn contactor_is_open(&self) -> bool { + self.contactor.is_open() + } - assert!(contactor.is_closed()); + fn contactor_is_closed(&self) -> bool { + self.contactor.is_closed() + } + + fn contactor_is_powered(&self, electricity: &Electricity) -> bool { + electricity.is_powered(&self.contactor) + } + + fn provide_power(&mut self) { + self.power_source.power(); + } + } + impl Aircraft for ContactorTestAircraft { + fn update_before_power_distribution( + &mut self, + _: &UpdateContext, + electricity: &mut Electricity, + ) { + electricity.supplied_by(&self.power_source); + electricity.flow(&self.power_source, &self.contactor); + } + } + impl SimulationElement for ContactorTestAircraft { + fn accept(&mut self, visitor: &mut T) { + self.contactor.accept(visitor); + visitor.visit(self); + } } #[test] - fn closed_contactor_when_toggled_open_opens() { - let mut contactor = closed_contactor(&mut Electricity::new()); - contactor.close_when(false); + fn open_contactor_when_toggled_open_stays_open() { + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_open); + test_bed.command(|a| a.open_contactor()); + test_bed.run(); - assert!(contactor.is_open()); + assert!(test_bed.query(|a| a.contactor_is_open())); } #[test] - fn closed_contactor_when_toggled_closed_stays_closed() { - let mut contactor = closed_contactor(&mut Electricity::new()); - contactor.close_when(true); + fn open_contactor_when_toggled_closed_closes() { + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_open); + test_bed.command(|a| a.close_contactor()); - assert!(contactor.is_closed()); + assert!(test_bed.query(|a| a.contactor_is_closed())); } #[test] - fn open_contactor_has_no_output_when_powered_by_nothing() { - let mut electricity = Electricity::new(); - let contactor = open_contactor(&mut electricity); + fn closed_contactor_when_toggled_open_opens() { + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_closed); + test_bed.command(|a| a.open_contactor()); + test_bed.run(); - assert!(!electricity.is_powered(&contactor)); + assert!(test_bed.query(|a| a.contactor_is_open())); } #[test] - fn closed_contactor_has_no_output_when_powered_by_nothing() { - let mut electricity = Electricity::new(); - let contactor = closed_contactor(&mut electricity); + fn closed_contactor_when_toggled_closed_stays_closed() { + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_closed); + test_bed.command(|a| a.close_contactor()); + test_bed.run(); - assert!(!electricity.is_powered(&contactor)); + assert!(test_bed.query(|a| a.contactor_is_closed())); } #[test] fn open_contactor_has_no_output_when_powered_by_nothing_which_is_powered() { - let mut electricity = Electricity::new(); - contactor_has_no_output_when_powered_by_nothing_which_is_powered( - open_contactor(&mut electricity), - &mut electricity, - ); + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_open); + test_bed.run(); + + assert!(test_bed.query_elec(|a, elec| !a.contactor_is_powered(elec))); } #[test] fn closed_contactor_has_no_output_when_powered_by_nothing_which_is_powered() { - let mut electricity = Electricity::new(); - contactor_has_no_output_when_powered_by_nothing_which_is_powered( - closed_contactor(&mut electricity), - &mut electricity, - ); - } - - fn contactor_has_no_output_when_powered_by_nothing_which_is_powered( - contactor: Contactor, - electricity: &mut Electricity, - ) { - let unpowered = - TestElectricitySource::unpowered(PotentialOrigin::External, electricity); - electricity.supplied_by(&unpowered); - electricity.flow(&unpowered, &contactor); + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_closed); + test_bed.run(); - assert!(!electricity.is_powered(&contactor)); + assert!(test_bed.query_elec(|a, elec| !a.contactor_is_powered(elec))); } #[test] fn open_contactor_has_no_output_when_powered_by_something() { - let mut electricity = Electricity::new(); - let contactor = open_contactor(&mut electricity); - let powered = - TestElectricitySource::powered(PotentialOrigin::External, &mut electricity); - electricity.supplied_by(&powered); - electricity.flow(&powered, &contactor); + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_open); + test_bed.command(|a| a.provide_power()); + test_bed.run(); - assert!(!electricity.is_powered(&contactor)); + assert!(test_bed.query_elec(|a, elec| !a.contactor_is_powered(elec))); } #[test] fn closed_contactor_has_output_when_powered_by_something_which_is_powered() { - let mut electricity = Electricity::new(); - let contactor = closed_contactor(&mut electricity); - let powered = - TestElectricitySource::powered(PotentialOrigin::External, &mut electricity); - electricity.supplied_by(&powered); - electricity.flow(&powered, &contactor); + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_closed); + test_bed.command(|a| a.provide_power()); + test_bed.run(); - assert!(electricity.is_powered(&contactor)); + assert!(test_bed.query_elec(|a, elec| a.contactor_is_powered(elec))); } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(ElementCtorFn(contactor)); + let mut test_bed = SimulationTestBed::new(ContactorTestAircraft::new_open); test_bed.run(); - assert!(test_bed.contains_key("ELEC_CONTACTOR_TEST_IS_CLOSED")); - } - - fn contactor(electricity: &mut Electricity) -> Contactor { - Contactor::new("TEST", electricity) - } - - fn open_contactor(electricity: &mut Electricity) -> Contactor { - let mut contactor = contactor(electricity); - contactor.closed = false; - - contactor - } - - fn closed_contactor(electricity: &mut Electricity) -> Contactor { - let mut contactor = contactor(electricity); - contactor.closed = true; - - contactor + assert!(test_bed.contains_variable_with_name("ELEC_CONTACTOR_TEST_IS_CLOSED")); } } @@ -1221,10 +1274,10 @@ mod tests { writer: ElectricalStateWriter, } impl CurrentStateWriterTestAircraft { - fn new(write_type: WriteType) -> Self { + fn new(context: &mut InitContext, write_type: WriteType) -> Self { Self { write_type, - writer: ElectricalStateWriter::new("TEST"), + writer: ElectricalStateWriter::new(context, "TEST"), } } } @@ -1247,46 +1300,46 @@ mod tests { #[test] fn writes_direct_current_state() { - let mut test_bed = SimulationTestBed::new(|_| { - CurrentStateWriterTestAircraft::new(WriteType::DirectCurrent) + let mut test_bed = SimulationTestBed::new(|context| { + CurrentStateWriterTestAircraft::new(context, WriteType::DirectCurrent) }); test_bed.run(); - assert!(test_bed.contains_key("ELEC_TEST_CURRENT")); - assert!(test_bed.contains_key("ELEC_TEST_CURRENT_NORMAL")); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_CURRENT")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_CURRENT_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL_NORMAL")); } #[test] fn writes_alternating_current_state() { - let mut test_bed = SimulationTestBed::new(|_| { - CurrentStateWriterTestAircraft::new(WriteType::AlternatingCurrent) + let mut test_bed = SimulationTestBed::new(|context| { + CurrentStateWriterTestAircraft::new(context, WriteType::AlternatingCurrent) }); test_bed.run(); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_TEST_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_TEST_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_FREQUENCY_NORMAL")); } #[test] fn writes_alternating_current_with_load_state() { - let mut test_bed = SimulationTestBed::new(|_| { - CurrentStateWriterTestAircraft::new(WriteType::AlternatingCurrentWithLoad) + let mut test_bed = SimulationTestBed::new(|context| { + CurrentStateWriterTestAircraft::new(context, WriteType::AlternatingCurrentWithLoad) }); test_bed.run(); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_TEST_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_TEST_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_TEST_FREQUENCY_NORMAL")); - assert!(test_bed.contains_key("ELEC_TEST_LOAD")); - assert!(test_bed.contains_key("ELEC_TEST_LOAD_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_LOAD")); + assert!(test_bed.contains_variable_with_name("ELEC_TEST_LOAD_NORMAL")); } } @@ -1367,7 +1420,7 @@ mod tests { ElectricPotential::new::(115.), )); - let mut set = HashSet::new(); + let mut set = FxHashSet::default(); set.insert(PotentialOrigin::ApuGenerator(1)); set.insert(PotentialOrigin::EngineGenerator(1)); @@ -1401,7 +1454,7 @@ mod tests { ElectricPotential::new::(115.002), )); - let mut set = HashSet::new(); + let mut set = FxHashSet::default(); set.insert(PotentialOrigin::ApuGenerator(1)); set.insert(PotentialOrigin::EngineGenerator(1)); @@ -1522,7 +1575,7 @@ mod tests { fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { Self { number: 1, - identifier: identifier_provider.next(), + identifier: identifier_provider.next_electrical_identifier(), is_powered: false, is_conductive: true, } @@ -1583,11 +1636,11 @@ mod tests { } impl TestBus { fn new( - bus_type: ElectricalBusType, identifier_provider: &mut impl ElectricalElementIdentifierProvider, + bus_type: ElectricalBusType, ) -> Self { Self { - identifier: identifier_provider.next_for_bus(bus_type), + identifier: identifier_provider.next_electrical_identifier_for_bus(bus_type), } } } @@ -1612,8 +1665,8 @@ mod tests { impl TestTransformer { fn new(identifier_provider: &mut impl ElectricalElementIdentifierProvider) -> Self { Self { - input_identifier: identifier_provider.next(), - output_identifier: identifier_provider.next(), + input_identifier: identifier_provider.next_electrical_identifier(), + output_identifier: identifier_provider.next_electrical_identifier(), } } } @@ -1646,9 +1699,9 @@ mod tests { #[test] fn next_provides_increasing_identifiers() { let mut electricity = Electricity::new(); - let mut identifier = electricity.next(); + let mut identifier = electricity.next_electrical_identifier(); for _ in 1..=10 { - let next_identifier = electricity.next(); + let next_identifier = electricity.next_electrical_identifier(); assert!(identifier.0 < next_identifier.0); identifier = next_identifier; } @@ -1657,10 +1710,10 @@ mod tests { #[test] fn next_for_bus_provides_increasing_identifiers() { let mut electricity = Electricity::new(); - let mut identifier = electricity.next(); + let mut identifier = electricity.next_electrical_identifier(); for _ in 1..=10 { - let next_identifier = - electricity.next_for_bus(ElectricalBusType::DirectCurrentBattery); + let next_identifier = electricity + .next_electrical_identifier_for_bus(ElectricalBusType::DirectCurrentBattery); assert!(identifier.0 < next_identifier.0); identifier = next_identifier; } @@ -1669,12 +1722,13 @@ mod tests { #[test] fn next_and_next_for_bus_together_provide_increasing_identifiers() { let mut electricity = Electricity::new(); - let mut identifier = electricity.next(); + let mut identifier = electricity.next_electrical_identifier(); for n in 1..=10 { let next_identifier = if n % 2 == 0 { - electricity.next() + electricity.next_electrical_identifier() } else { - electricity.next_for_bus(ElectricalBusType::DirectCurrentBattery) + electricity + .next_electrical_identifier_for_bus(ElectricalBusType::DirectCurrentBattery) }; assert!(identifier.0 < next_identifier.0); identifier = next_identifier; @@ -1685,8 +1739,9 @@ mod tests { fn next_for_bus_identifier_matches_looked_up_identifier() { let mut electricity = Electricity::new(); - electricity.next_for_bus(ElectricalBusType::DirectCurrentBattery); - let identifier = electricity.next_for_bus(ElectricalBusType::DirectCurrentEssential); + electricity.next_electrical_identifier_for_bus(ElectricalBusType::DirectCurrentBattery); + let identifier = electricity + .next_electrical_identifier_for_bus(ElectricalBusType::DirectCurrentEssential); assert!( electricity.identifier_for(ElectricalBusType::DirectCurrentEssential) @@ -1776,7 +1831,7 @@ mod tests { fn a_known_but_unpowered_bus_isnt_powered() { let mut electricity = Electricity::new(); // Request an identifier, such that the bus type is registered. - TestBus::new(ElectricalBusType::DirectCurrentBattery, &mut electricity); + TestBus::new(&mut electricity, ElectricalBusType::DirectCurrentBattery); assert!(!electricity.bus_is_powered(ElectricalBusType::DirectCurrentBattery)); } @@ -1787,7 +1842,7 @@ mod tests { let element = TestElectricalElement::new(&mut electricity).power(); electricity.supplied_by(&element); - let bus = TestBus::new(ElectricalBusType::DirectCurrentBattery, &mut electricity); + let bus = TestBus::new(&mut electricity, ElectricalBusType::DirectCurrentBattery); electricity.flow(&element, &bus); assert!(electricity.bus_is_powered(ElectricalBusType::DirectCurrentBattery)); @@ -1916,8 +1971,8 @@ mod tests { let element = TestElectricalElement::new(&mut electricity).power(); electricity.supplied_by(&element); - let ac_bus = TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); - TestBus::new(ElectricalBusType::DirectCurrent(1), &mut electricity); + let ac_bus = TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); + TestBus::new(&mut electricity, ElectricalBusType::DirectCurrent(1)); electricity.flow(&element, &ac_bus); // Don't flow to DC BUS on purpose. @@ -1931,8 +1986,8 @@ mod tests { #[test] fn any_is_powered_returns_false_when_none_of_the_arguments_is_powered() { let mut electricity = Electricity::new(); - TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); - TestBus::new(ElectricalBusType::DirectCurrent(1), &mut electricity); + TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); + TestBus::new(&mut electricity, ElectricalBusType::DirectCurrent(1)); assert!(!electricity.any_is_powered(&[ ElectricalBusType::AlternatingCurrent(1), @@ -1943,7 +1998,7 @@ mod tests { #[test] fn potential_of_returns_a_potential_which_isnt_powered_when_bus_is_unpowered() { let mut electricity = Electricity::new(); - TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); + TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); let potential = electricity.potential_of(ElectricalBusType::AlternatingCurrent(1)); assert!(potential.is_unpowered()); @@ -1955,7 +2010,7 @@ mod tests { let element = TestElectricalElement::new(&mut electricity).power(); electricity.supplied_by(&element); - let bus = TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); + let bus = TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); electricity.flow(&element, &bus); let potential = electricity.potential_of(ElectricalBusType::AlternatingCurrent(1)); @@ -1969,7 +2024,7 @@ mod tests { let generator = TestElectricalElement::new(&mut electricity).power(); electricity.supplied_by(&generator); - let bus = TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); + let bus = TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); electricity.flow(&generator, &bus); electricity.consume_from_bus( @@ -1990,7 +2045,7 @@ mod tests { #[test] fn power_consumed_from_an_unpowered_bus_is_not_included_in_the_power_usage() { let mut electricity = Electricity::new(); - TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); + TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); electricity.consume_from_bus( ElectricalBusType::AlternatingCurrent(1), @@ -2013,7 +2068,7 @@ mod tests { electricity.supplied_by(&generator_1); electricity.supplied_by(&generator_2); - let bus = TestBus::new(ElectricalBusType::AlternatingCurrent(1), &mut electricity); + let bus = TestBus::new(&mut electricity, ElectricalBusType::AlternatingCurrent(1)); electricity.flow(&generator_1, &bus); electricity.flow(&generator_2, &bus); diff --git a/src/systems/systems/src/electrical/static_inverter.rs b/src/systems/systems/src/electrical/static_inverter.rs index d0aa3f9e978..396dc793924 100644 --- a/src/systems/systems/src/electrical/static_inverter.rs +++ b/src/systems/systems/src/electrical/static_inverter.rs @@ -1,5 +1,7 @@ use std::cell::Ref; +use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; + use super::{ ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, ElectricalStateWriter, ElectricityTransformer, Potential, PotentialOrigin, ProvideFrequency, @@ -7,9 +9,8 @@ use super::{ }; use crate::{ shared::{ConsumePower, PowerConsumptionReport}, - simulation::{SimulationElement, SimulatorWriter, UpdateContext}, + simulation::{InitContext, SimulationElement, SimulatorWriter, UpdateContext}, }; -use uom::si::{electric_potential::volt, f64::*, frequency::hertz}; pub struct StaticInverter { input_identifier: ElectricalElementIdentifier, @@ -19,13 +20,11 @@ pub struct StaticInverter { output_frequency: Frequency, } impl StaticInverter { - pub fn new( - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> StaticInverter { + pub fn new(context: &mut InitContext) -> StaticInverter { StaticInverter { - input_identifier: identifier_provider.next(), - output_identifier: identifier_provider.next(), - writer: ElectricalStateWriter::new("STAT_INV"), + input_identifier: context.next_electrical_identifier(), + output_identifier: context.next_electrical_identifier(), + writer: ElectricalStateWriter::new(context, "STAT_INV"), output_potential: ElectricPotential::new::(0.), output_frequency: Frequency::new::(0.), } @@ -99,6 +98,8 @@ mod static_inverter_tests { use uom::si::power::watt; use super::*; + use crate::simulation::test::ReadByName; + use crate::simulation::InitContext; use crate::{ electrical::{ consumption::PowerConsumer, test::TestElectricitySource, ElectricalBus, @@ -106,7 +107,7 @@ mod static_inverter_tests { }, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, SimulationElementVisitor, UpdateContext, + Aircraft, SimulationElementVisitor, UpdateContext, }, }; @@ -116,26 +117,26 @@ mod static_inverter_tests { impl StaticInverterTestBed { fn with_powered_static_inverter() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::new(electricity).with_powered_static_inverter() + test_bed: SimulationTestBed::new(|context| { + TestAircraft::new(context).with_powered_static_inverter() }), } } fn with_unpowered_static_inverter() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::new(electricity).with_unpowered_static_inverter() + test_bed: SimulationTestBed::new(|context| { + TestAircraft::new(context).with_unpowered_static_inverter() }), } } fn frequency_is_normal(&mut self) -> bool { - self.read("ELEC_STAT_INV_FREQUENCY_NORMAL") + self.read_by_name("ELEC_STAT_INV_FREQUENCY_NORMAL") } fn potential_is_normal(&mut self) -> bool { - self.read("ELEC_STAT_INV_POTENTIAL_NORMAL") + self.read_by_name("ELEC_STAT_INV_POTENTIAL_NORMAL") } fn static_inverter_is_powered(&self) -> bool { @@ -162,18 +163,15 @@ mod static_inverter_tests { static_inverter_consumption: Power, } impl TestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { electricity_source: TestElectricitySource::unpowered( + context, PotentialOrigin::Battery(1), - electricity, - ), - bus: ElectricalBus::new( - ElectricalBusType::AlternatingCurrentEssential, - electricity, ), + bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrentEssential), consumer: PowerConsumer::from(ElectricalBusType::AlternatingCurrentEssential), - static_inverter: StaticInverter::new(electricity), + static_inverter: StaticInverter::new(context), static_inverter_consumption: Power::new::(0.), } } @@ -324,13 +322,13 @@ mod static_inverter_tests { #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::new(|electricity| TestAircraft::new(electricity)); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run(); - assert!(test_bed.contains_key("ELEC_STAT_INV_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_STAT_INV_POTENTIAL_NORMAL")); - assert!(test_bed.contains_key("ELEC_STAT_INV_FREQUENCY")); - assert!(test_bed.contains_key("ELEC_STAT_INV_FREQUENCY_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_STAT_INV_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_STAT_INV_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_STAT_INV_FREQUENCY")); + assert!(test_bed.contains_variable_with_name("ELEC_STAT_INV_FREQUENCY_NORMAL")); } } diff --git a/src/systems/systems/src/electrical/test.rs b/src/systems/systems/src/electrical/test.rs index 55c9b276a2f..7d4fbfb79f9 100644 --- a/src/systems/systems/src/electrical/test.rs +++ b/src/systems/systems/src/electrical/test.rs @@ -1,9 +1,8 @@ use crate::shared::PotentialOrigin; -use super::{ - ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, - ElectricitySource, Potential, -}; +use super::{ElectricalElement, ElectricalElementIdentifier, ElectricitySource, Potential}; +use crate::electrical::ElectricalElementIdentifierProvider; + use uom::si::{electric_potential::volt, f64::*}; pub struct TestElectricitySource { @@ -13,22 +12,22 @@ pub struct TestElectricitySource { } impl TestElectricitySource { pub fn unpowered( - origin: PotentialOrigin, identifier_provider: &mut impl ElectricalElementIdentifierProvider, + origin: PotentialOrigin, ) -> Self { Self { - identifier: identifier_provider.next(), + identifier: identifier_provider.next_electrical_identifier(), origin, potential: ElectricPotential::new::(0.), } } pub fn powered( - origin: PotentialOrigin, identifier_provider: &mut impl ElectricalElementIdentifierProvider, + origin: PotentialOrigin, ) -> Self { Self { - identifier: identifier_provider.next(), + identifier: identifier_provider.next_electrical_identifier(), origin, potential: ElectricPotential::new::(28.), } diff --git a/src/systems/systems/src/electrical/transformer_rectifier.rs b/src/systems/systems/src/electrical/transformer_rectifier.rs index 52a87b3f6e9..f8ba32e9d56 100644 --- a/src/systems/systems/src/electrical/transformer_rectifier.rs +++ b/src/systems/systems/src/electrical/transformer_rectifier.rs @@ -1,5 +1,7 @@ use std::cell::Ref; +use uom::si::{electric_current::ampere, electric_potential::volt, f64::*}; + use super::{ ElectricalElement, ElectricalElementIdentifier, ElectricalElementIdentifierProvider, ElectricalStateWriter, ElectricityTransformer, Potential, PotentialOrigin, ProvideCurrent, @@ -8,9 +10,10 @@ use super::{ use crate::{ failures::{Failure, FailureType}, shared::{ConsumePower, PowerConsumptionReport}, - simulation::{SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext}, + simulation::{ + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + }, }; -use uom::si::{electric_current::ampere, electric_potential::volt, f64::*}; pub struct TransformerRectifier { writer: ElectricalStateWriter, @@ -22,15 +25,12 @@ pub struct TransformerRectifier { output_current: ElectricCurrent, } impl TransformerRectifier { - pub fn new( - number: usize, - identifier_provider: &mut impl ElectricalElementIdentifierProvider, - ) -> TransformerRectifier { + pub fn new(context: &mut InitContext, number: usize) -> TransformerRectifier { TransformerRectifier { - writer: ElectricalStateWriter::new(&format!("TR_{}", number)), + writer: ElectricalStateWriter::new(context, &format!("TR_{}", number)), number, - input_identifier: identifier_provider.next(), - output_identifier: identifier_provider.next(), + input_identifier: context.next_electrical_identifier(), + output_identifier: context.next_electrical_identifier(), failure: Failure::new(FailureType::TransformerRectifier(number)), output_potential: ElectricPotential::new::(0.), output_current: ElectricCurrent::new::(0.), @@ -124,6 +124,8 @@ mod transformer_rectifier_tests { use uom::si::power::watt; use super::*; + use crate::simulation::test::ReadByName; + use crate::simulation::InitContext; use crate::{ electrical::{ consumption::PowerConsumer, test::TestElectricitySource, ElectricalBus, @@ -131,7 +133,7 @@ mod transformer_rectifier_tests { }, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, Read, SimulationElementVisitor, UpdateContext, + Aircraft, SimulationElementVisitor, UpdateContext, }, }; @@ -141,30 +143,30 @@ mod transformer_rectifier_tests { impl TransformerRectifierTestBed { fn with_unpowered_transformer_rectifier() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::new(electricity).with_unpowered_transformer_rectifier() + test_bed: SimulationTestBed::new(|context| { + TestAircraft::new(context).with_unpowered_transformer_rectifier() }), } } fn with_powered_transformer_rectifier() -> Self { Self { - test_bed: SimulationTestBed::new(|electricity| { - TestAircraft::new(electricity).with_powered_transformer_rectifier() + test_bed: SimulationTestBed::new(|context| { + TestAircraft::new(context).with_powered_transformer_rectifier() }), } } fn current_is_normal(&mut self) -> bool { - self.read("ELEC_TR_1_CURRENT_NORMAL") + self.read_by_name("ELEC_TR_1_CURRENT_NORMAL") } fn potential_is_normal(&mut self) -> bool { - self.read("ELEC_TR_1_POTENTIAL_NORMAL") + self.read_by_name("ELEC_TR_1_POTENTIAL_NORMAL") } fn current(&mut self) -> ElectricCurrent { - self.read("ELEC_TR_1_CURRENT") + self.read_by_name("ELEC_TR_1_CURRENT") } fn transformer_rectifier_is_powered(&self) -> bool { @@ -191,14 +193,14 @@ mod transformer_rectifier_tests { transformer_rectifier_consumption: Power, } impl TestAircraft { - fn new(electricity: &mut Electricity) -> Self { + fn new(context: &mut InitContext) -> Self { Self { electricity_source: TestElectricitySource::unpowered( + context, PotentialOrigin::ApuGenerator(1), - electricity, ), - transformer_rectifier: TransformerRectifier::new(1, electricity), - bus: ElectricalBus::new(ElectricalBusType::DirectCurrent(1), electricity), + transformer_rectifier: TransformerRectifier::new(context, 1), + bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), consumer: PowerConsumer::from(ElectricalBusType::DirectCurrent(1)), transformer_rectifier_consumption: Power::new::(0.), } @@ -388,9 +390,9 @@ mod transformer_rectifier_tests { test_bed.run(); - assert!(test_bed.contains_key("ELEC_TR_1_CURRENT")); - assert!(test_bed.contains_key("ELEC_TR_1_CURRENT_NORMAL")); - assert!(test_bed.contains_key("ELEC_TR_1_POTENTIAL")); - assert!(test_bed.contains_key("ELEC_TR_1_POTENTIAL_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TR_1_CURRENT")); + assert!(test_bed.contains_variable_with_name("ELEC_TR_1_CURRENT_NORMAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TR_1_POTENTIAL")); + assert!(test_bed.contains_variable_with_name("ELEC_TR_1_POTENTIAL_NORMAL")); } } diff --git a/src/systems/systems/src/engine/leap_engine.rs b/src/systems/systems/src/engine/leap_engine.rs index 9337e28e7ad..fb893c1f810 100644 --- a/src/systems/systems/src/engine/leap_engine.rs +++ b/src/systems/systems/src/engine/leap_engine.rs @@ -6,13 +6,15 @@ use crate::{ }; use super::Engine; +use crate::simulation::{InitContext, VariableIdentifier}; + pub struct LeapEngine { - corrected_n1_id: String, + corrected_n1_id: VariableIdentifier, corrected_n1: Ratio, - corrected_n2_id: String, + corrected_n2_id: VariableIdentifier, corrected_n2: Ratio, - uncorrected_n2_id: String, + uncorrected_n2_id: VariableIdentifier, uncorrected_n2: Ratio, n2_speed: AngularVelocity, @@ -29,13 +31,13 @@ impl LeapEngine { const MIN_IDLE_N2_UNCORRECTED_THRESHOLD_PERCENT: f64 = 55.; - pub fn new(number: usize) -> LeapEngine { + pub fn new(context: &mut InitContext, number: usize) -> LeapEngine { LeapEngine { - corrected_n1_id: format!("TURB ENG CORRECTED N1:{}", number), + corrected_n1_id: context.get_identifier(format!("TURB ENG CORRECTED N1:{}", number)), corrected_n1: Ratio::new::(0.), - corrected_n2_id: format!("TURB ENG CORRECTED N2:{}", number), + corrected_n2_id: context.get_identifier(format!("TURB ENG CORRECTED N2:{}", number)), corrected_n2: Ratio::new::(0.), - uncorrected_n2_id: format!("ENGINE_N2:{}", number), + uncorrected_n2_id: context.get_identifier(format!("ENGINE_N2:{}", number)), uncorrected_n2: Ratio::new::(0.), n2_speed: AngularVelocity::new::(0.), hydraulic_pump_output_speed: AngularVelocity::new::(0.), diff --git a/src/systems/systems/src/engine/mod.rs b/src/systems/systems/src/engine/mod.rs index 1f7a1b16036..63cc159da78 100644 --- a/src/systems/systems/src/engine/mod.rs +++ b/src/systems/systems/src/engine/mod.rs @@ -1,5 +1,6 @@ use uom::si::f64::*; +use crate::simulation::InitContext; use crate::{ overhead::FirePushButton, shared::{EngineCorrectedN2, EngineFirePushButtons, EngineUncorrectedN2}, @@ -20,9 +21,12 @@ pub struct EngineFireOverheadPanel { engine_fire_push_buttons: [FirePushButton; 2], } impl EngineFireOverheadPanel { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { - engine_fire_push_buttons: [FirePushButton::new("ENG1"), FirePushButton::new("ENG2")], + engine_fire_push_buttons: [ + FirePushButton::new(context, "ENG1"), + FirePushButton::new(context, "ENG2"), + ], } } } @@ -38,33 +42,24 @@ impl SimulationElement for EngineFireOverheadPanel { visitor.visit(self); } } -impl Default for EngineFireOverheadPanel { - fn default() -> Self { - Self::new() - } -} #[cfg(test)] mod engine_fire_overhead_panel_tests { - use crate::simulation::{ - test::{SimulationTestBed, TestBed}, - Write, - }; - use super::*; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed, WriteByName}; #[test] fn after_construction_fire_push_buttons_are_not_released() { - let panel = EngineFireOverheadPanel::new(); + let test_bed = SimulationTestBed::from(ElementCtorFn(EngineFireOverheadPanel::new)); - assert_eq!(panel.is_released(1), false); - assert_eq!(panel.is_released(2), false); + assert_eq!(test_bed.query_element(|e| e.is_released(1)), false); + assert_eq!(test_bed.query_element(|e| e.is_released(2)), false); } #[test] fn fire_push_button_is_released_returns_false_when_not_released() { - let mut test_bed = SimulationTestBed::from(EngineFireOverheadPanel::new()); - test_bed.write("FIRE_BUTTON_ENG1", false); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(EngineFireOverheadPanel::new)); + test_bed.write_by_name("FIRE_BUTTON_ENG1", false); test_bed.run(); assert_eq!(test_bed.query_element(|e| e.is_released(1)), false); @@ -72,8 +67,8 @@ mod engine_fire_overhead_panel_tests { #[test] fn fire_push_button_is_released_returns_true_when_released() { - let mut test_bed = SimulationTestBed::from(EngineFireOverheadPanel::new()); - test_bed.write("FIRE_BUTTON_ENG1", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(EngineFireOverheadPanel::new)); + test_bed.write_by_name("FIRE_BUTTON_ENG1", true); test_bed.run(); assert_eq!(test_bed.query_element(|e| e.is_released(1)), true); diff --git a/src/systems/systems/src/hydraulic/brake_circuit.rs b/src/systems/systems/src/hydraulic/brake_circuit.rs index 0f310096a77..2c8d41acb1f 100644 --- a/src/systems/systems/src/hydraulic/brake_circuit.rs +++ b/src/systems/systems/src/hydraulic/brake_circuit.rs @@ -8,7 +8,6 @@ use crate::{ }; use std::f64::consts::E; -use std::string::String; use std::time::Duration; use uom::si::{ @@ -16,6 +15,7 @@ use uom::si::{ }; use super::Accumulator; +use crate::simulation::{InitContext, VariableIdentifier}; struct BrakeActuator { total_displacement: Volume, @@ -113,10 +113,9 @@ impl Actuator for BrakeActuator { /// Brake model is simplified as we just move brake actuator position from 0 to 1 and take corresponding fluid volume (vol = max_displacement * brake_position). /// So it's fairly simplified as we just end up with brake pressure = PRESSURE_FOR_MAX_BRAKE_DEFLECTION_PSI * current_position pub struct BrakeCircuit { - _id: String, - id_left_press: String, - id_right_press: String, - id_acc_press: String, + left_press_id: VariableIdentifier, + right_press_id: VariableIdentifier, + acc_press_id: VariableIdentifier, left_brake_actuator: BrakeActuator, right_brake_actuator: BrakeActuator, @@ -153,6 +152,7 @@ impl BrakeCircuit { const ACC_PRESSURE_SENSOR_FILTER_TIMECONST: f64 = 0.1; pub fn new( + context: &mut InitContext, id: &str, accumulator_volume: Volume, accumulator_fluid_volume_at_init: Volume, @@ -164,10 +164,9 @@ impl BrakeCircuit { } BrakeCircuit { - _id: String::from(id).to_uppercase(), - id_left_press: format!("HYD_BRAKE_{}_LEFT_PRESS", id), - id_right_press: format!("HYD_BRAKE_{}_RIGHT_PRESS", id), - id_acc_press: format!("HYD_BRAKE_{}_ACC_PRESS", id), + left_press_id: context.get_identifier(format!("HYD_BRAKE_{}_LEFT_PRESS", id)), + right_press_id: context.get_identifier(format!("HYD_BRAKE_{}_RIGHT_PRESS", id)), + acc_press_id: context.get_identifier(format!("HYD_BRAKE_{}_ACC_PRESS", id)), // We assume displacement is just split on left and right left_brake_actuator: BrakeActuator::new(total_displacement / 2.), @@ -311,10 +310,10 @@ impl Actuator for BrakeCircuit { } impl SimulationElement for BrakeCircuit { fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.id_left_press, self.left_brake_pressure()); - writer.write(&self.id_right_press, self.right_brake_pressure()); + writer.write(&self.left_press_id, self.left_brake_pressure()); + writer.write(&self.right_press_id, self.right_brake_pressure()); if self.has_accumulator { - writer.write(&self.id_acc_press, self.accumulator_pressure()); + writer.write(&self.acc_press_id, self.accumulator_pressure()); } } } @@ -350,11 +349,11 @@ pub struct AutobrakePanel { max_button: PressSingleSignalButton, } impl AutobrakePanel { - pub fn new() -> AutobrakePanel { + pub fn new(context: &mut InitContext) -> AutobrakePanel { AutobrakePanel { - lo_button: PressSingleSignalButton::new("AUTOBRK_LOW_ON"), - med_button: PressSingleSignalButton::new("AUTOBRK_MED_ON"), - max_button: PressSingleSignalButton::new("AUTOBRK_MAX_ON"), + lo_button: PressSingleSignalButton::new(context, "AUTOBRK_LOW_ON"), + med_button: PressSingleSignalButton::new(context, "AUTOBRK_MED_ON"), + max_button: PressSingleSignalButton::new(context, "AUTOBRK_MAX_ON"), } } @@ -391,11 +390,6 @@ impl SimulationElement for AutobrakePanel { visitor.visit(self); } } -impl Default for AutobrakePanel { - fn default() -> Self { - Self::new() - } -} /// Deceleration governor is the PI controller computing the expected brake force to reach the target /// it's been given by update caller @@ -493,6 +487,8 @@ impl Default for AutobrakeDecelerationGovernor { #[cfg(test)] mod tests { use super::*; + use crate::electrical::Electricity; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestVariableRegistry}; use crate::{ hydraulic::{Fluid, HydraulicLoop}, simulation::UpdateContext, @@ -510,6 +506,10 @@ mod tests { #[test] fn brake_actuator_movement() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut brake_actuator = BrakeActuator::new(Volume::new::(0.04)); assert!(brake_actuator.current_position == 0.); @@ -519,7 +519,7 @@ mod tests { for _loop_idx in 0..15 { brake_actuator.update( - &context(Duration::from_secs_f64(0.1)), + &context(&mut init_context, Duration::from_secs_f64(0.1)), Pressure::new::(BrakeActuator::PRESSURE_FOR_MAX_BRAKE_DEFLECTION_PSI), ); } @@ -538,7 +538,7 @@ mod tests { brake_actuator.set_position_demand(-2.); for _ in 0..15 { brake_actuator.update( - &context(Duration::from_secs_f64(0.1)), + &context(&mut init_context, Duration::from_secs_f64(0.1)), Pressure::new::(3000.), ); } @@ -556,7 +556,7 @@ mod tests { for _ in 0..15 { brake_actuator.update( - &context(Duration::from_secs_f64(0.1)), + &context(&mut init_context, Duration::from_secs_f64(0.1)), Pressure::new::(20.), ); } @@ -570,6 +570,10 @@ mod tests { #[test] fn brake_actuator_movement_medium_pressure() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut brake_actuator = BrakeActuator::new(Volume::new::(0.04)); brake_actuator.set_position_demand(1.2); @@ -577,7 +581,10 @@ mod tests { let medium_pressure = Pressure::new::(1500.); // Update position with 1500psi only: should not reach max displacement. for loop_idx in 0..15 { - brake_actuator.update(&context(Duration::from_secs_f64(0.1)), medium_pressure); + brake_actuator.update( + &context(&mut init_context, Duration::from_secs_f64(0.1)), + medium_pressure, + ); println!( "Loop {}, position: {}", loop_idx, brake_actuator.current_position @@ -595,7 +602,7 @@ mod tests { for _loop_idx in 0..15 { brake_actuator.update( - &context(Duration::from_secs_f64(0.1)), + &context(&mut init_context, Duration::from_secs_f64(0.1)), Pressure::new::(20.), ); println!( @@ -609,48 +616,62 @@ mod tests { } #[test] - fn brake_state_at_init() { + fn unprimed_brake_circuit_state_at_init() { let init_max_vol = Volume::new::(1.5); - let brake_circuit_unprimed = BrakeCircuit::new( - "altn", - init_max_vol, - Volume::new::(0.0), - Volume::new::(0.1), - ); - + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + BrakeCircuit::new( + context, + "altn", + init_max_vol, + Volume::new::(0.0), + Volume::new::(0.1), + ) + })); + + assert!(test_bed.query_element( + |e| e.left_brake_pressure() + e.right_brake_pressure() < Pressure::new::(10.0) + )); + + assert!(test_bed.query_element(|e| e.accumulator.total_volume == init_max_vol)); assert!( - brake_circuit_unprimed.left_brake_pressure() - + brake_circuit_unprimed.right_brake_pressure() - < Pressure::new::(10.0) + test_bed.query_element(|e| e.accumulator.fluid_volume() == Volume::new::(0.0)) ); - assert!(brake_circuit_unprimed.accumulator.total_volume == init_max_vol); - assert!(brake_circuit_unprimed.accumulator.fluid_volume() == Volume::new::(0.0)); - assert!(brake_circuit_unprimed.accumulator.gas_volume == init_max_vol); + assert!(test_bed.query_element(|e| e.accumulator.gas_volume == init_max_vol)); + } - let brake_circuit_primed = BrakeCircuit::new( - "altn", - init_max_vol, - init_max_vol / 2.0, - Volume::new::(0.1), - ); + #[test] + fn primed_brake_circuit_state_at_init() { + let init_max_vol = Volume::new::(1.5); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + BrakeCircuit::new( + context, + "altn", + init_max_vol, + init_max_vol / 2.0, + Volume::new::(0.1), + ) + })); - assert!( - brake_circuit_unprimed.left_brake_pressure() - + brake_circuit_unprimed.right_brake_pressure() - < Pressure::new::(10.0) - ); - assert!(brake_circuit_primed.accumulator.total_volume == init_max_vol); - assert!(brake_circuit_primed.accumulator.fluid_volume() == init_max_vol / 2.0); - assert!(brake_circuit_primed.accumulator.gas_volume < init_max_vol); + assert!(test_bed.query_element( + |e| e.left_brake_pressure() + e.right_brake_pressure() < Pressure::new::(10.0) + )); + assert!(test_bed.query_element(|e| e.accumulator.total_volume == init_max_vol)); + assert!(test_bed.query_element(|e| e.accumulator.fluid_volume() == init_max_vol / 2.0)); + assert!(test_bed.query_element(|e| e.accumulator.gas_volume < init_max_vol)); } #[test] - fn brake_pressure_rise() { + fn primed_circuit_brake_pressure_rise() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let init_max_vol = Volume::new::(1.5); - let mut hyd_loop = hydraulic_loop("YELLOW"); + let mut hyd_loop = hydraulic_loop(&mut init_context, "YELLOW"); hyd_loop.loop_pressure = Pressure::new::(2500.0); - let mut brake_circuit_primed = BrakeCircuit::new( + let mut brake_circuit = BrakeCircuit::new( + &mut init_context, "Altn", init_max_vol, init_max_vol / 2.0, @@ -658,41 +679,53 @@ mod tests { ); assert!( - brake_circuit_primed.left_brake_pressure() - + brake_circuit_primed.right_brake_pressure() + brake_circuit.left_brake_pressure() + brake_circuit.right_brake_pressure() < Pressure::new::(10.0) ); - brake_circuit_primed.update(&context(Duration::from_secs_f64(0.1)), &hyd_loop); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(0.1)), + &hyd_loop, + ); assert!( - brake_circuit_primed.left_brake_pressure() - + brake_circuit_primed.right_brake_pressure() + brake_circuit.left_brake_pressure() + brake_circuit.right_brake_pressure() < Pressure::new::(10.0) ); - brake_circuit_primed.set_brake_demand_left(Ratio::new::(1.0)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.)), &hyd_loop); + brake_circuit.set_brake_demand_left(Ratio::new::(1.0)); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.)), + &hyd_loop, + ); - assert!(brake_circuit_primed.left_brake_pressure() >= Pressure::new::(1000.)); - assert!(brake_circuit_primed.right_brake_pressure() <= Pressure::new::(50.)); - assert!(brake_circuit_primed.accumulator.fluid_volume() >= Volume::new::(0.1)); + assert!(brake_circuit.left_brake_pressure() >= Pressure::new::(1000.)); + assert!(brake_circuit.right_brake_pressure() <= Pressure::new::(50.)); + assert!(brake_circuit.accumulator.fluid_volume() >= Volume::new::(0.1)); - brake_circuit_primed.set_brake_demand_left(Ratio::new::(0.0)); - brake_circuit_primed.set_brake_demand_right(Ratio::new::(1.0)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.)), &hyd_loop); - assert!(brake_circuit_primed.right_brake_pressure() >= Pressure::new::(1000.)); - assert!(brake_circuit_primed.left_brake_pressure() <= Pressure::new::(50.)); - assert!(brake_circuit_primed.accumulator.fluid_volume() >= Volume::new::(0.1)); + brake_circuit.set_brake_demand_left(Ratio::new::(0.0)); + brake_circuit.set_brake_demand_right(Ratio::new::(1.0)); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.)), + &hyd_loop, + ); + assert!(brake_circuit.right_brake_pressure() >= Pressure::new::(1000.)); + assert!(brake_circuit.left_brake_pressure() <= Pressure::new::(50.)); + assert!(brake_circuit.accumulator.fluid_volume() >= Volume::new::(0.1)); } #[test] - fn brake_pressure_rise_no_accumulator() { + fn primed_circuit_brake_pressure_rise_no_accumulator() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let init_max_vol = Volume::new::(0.0); - let mut hyd_loop = hydraulic_loop("GREEN"); + let mut hyd_loop = hydraulic_loop(&mut init_context, "GREEN"); hyd_loop.loop_pressure = Pressure::new::(2500.0); - let mut brake_circuit_primed = BrakeCircuit::new( + let mut brake_circuit = BrakeCircuit::new( + &mut init_context, "norm", init_max_vol, init_max_vol / 2.0, @@ -700,79 +733,103 @@ mod tests { ); assert!( - brake_circuit_primed.left_brake_pressure() - + brake_circuit_primed.right_brake_pressure() + brake_circuit.left_brake_pressure() + brake_circuit.right_brake_pressure() < Pressure::new::(10.0) ); - brake_circuit_primed.update(&context(Duration::from_secs_f64(0.1)), &hyd_loop); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(0.1)), + &hyd_loop, + ); assert!( - brake_circuit_primed.left_brake_pressure() - + brake_circuit_primed.right_brake_pressure() + brake_circuit.left_brake_pressure() + brake_circuit.right_brake_pressure() < Pressure::new::(10.0) ); - brake_circuit_primed.set_brake_demand_left(Ratio::new::(1.0)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.5)), &hyd_loop); + brake_circuit.set_brake_demand_left(Ratio::new::(1.0)); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.5)), + &hyd_loop, + ); - assert!(brake_circuit_primed.left_brake_pressure() >= Pressure::new::(2500.)); - assert!(brake_circuit_primed.right_brake_pressure() <= Pressure::new::(50.)); + assert!(brake_circuit.left_brake_pressure() >= Pressure::new::(2500.)); + assert!(brake_circuit.right_brake_pressure() <= Pressure::new::(50.)); - brake_circuit_primed.set_brake_demand_left(Ratio::new::(0.0)); - brake_circuit_primed.set_brake_demand_right(Ratio::new::(1.0)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.5)), &hyd_loop); - assert!(brake_circuit_primed.right_brake_pressure() >= Pressure::new::(2500.)); - assert!(brake_circuit_primed.left_brake_pressure() <= Pressure::new::(50.)); - assert!(brake_circuit_primed.accumulator.fluid_volume() == Volume::new::(0.0)); + brake_circuit.set_brake_demand_left(Ratio::new::(0.0)); + brake_circuit.set_brake_demand_right(Ratio::new::(1.0)); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.5)), + &hyd_loop, + ); + assert!(brake_circuit.right_brake_pressure() >= Pressure::new::(2500.)); + assert!(brake_circuit.left_brake_pressure() <= Pressure::new::(50.)); + assert!(brake_circuit.accumulator.fluid_volume() == Volume::new::(0.0)); } #[test] fn brake_pressure_limitation() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let init_max_vol = Volume::new::(0.0); - let mut hyd_loop = hydraulic_loop("GREEN"); + let mut hyd_loop = hydraulic_loop(&mut init_context, "GREEN"); hyd_loop.loop_pressure = Pressure::new::(3100.0); - let mut brake_circuit_primed = BrakeCircuit::new( + let mut brake_circuit = BrakeCircuit::new( + &mut init_context, "norm", init_max_vol, init_max_vol / 2.0, Volume::new::(0.1), ); - brake_circuit_primed.update(&context(Duration::from_secs_f64(5.)), &hyd_loop); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(5.)), + &hyd_loop, + ); assert!( - brake_circuit_primed.left_brake_pressure() - + brake_circuit_primed.right_brake_pressure() + brake_circuit.left_brake_pressure() + brake_circuit.right_brake_pressure() < Pressure::new::(1.0) ); - brake_circuit_primed.set_brake_demand_left(Ratio::new::(1.0)); - brake_circuit_primed.set_brake_demand_right(Ratio::new::(1.0)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.5)), &hyd_loop); + brake_circuit.set_brake_demand_left(Ratio::new::(1.0)); + brake_circuit.set_brake_demand_right(Ratio::new::(1.0)); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.5)), + &hyd_loop, + ); - assert!(brake_circuit_primed.left_brake_pressure() >= Pressure::new::(2900.)); - assert!(brake_circuit_primed.right_brake_pressure() >= Pressure::new::(2900.)); + assert!(brake_circuit.left_brake_pressure() >= Pressure::new::(2900.)); + assert!(brake_circuit.right_brake_pressure() >= Pressure::new::(2900.)); let pressure_limit = Pressure::new::(1200.); - brake_circuit_primed.set_brake_press_limit(pressure_limit); - brake_circuit_primed.update(&context(Duration::from_secs_f64(0.1)), &hyd_loop); + brake_circuit.set_brake_press_limit(pressure_limit); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(0.1)), + &hyd_loop, + ); // Now we limit to 1200 but pressure shouldn't drop instantly - assert!(brake_circuit_primed.left_brake_pressure() >= Pressure::new::(2500.)); - assert!(brake_circuit_primed.right_brake_pressure() >= Pressure::new::(2500.)); + assert!(brake_circuit.left_brake_pressure() >= Pressure::new::(2500.)); + assert!(brake_circuit.right_brake_pressure() >= Pressure::new::(2500.)); - brake_circuit_primed.update(&context(Duration::from_secs_f64(1.)), &hyd_loop); + brake_circuit.update( + &context(&mut init_context, Duration::from_secs_f64(1.)), + &hyd_loop, + ); // After one second it should have reached the lower limit - assert!(brake_circuit_primed.left_brake_pressure() <= pressure_limit); - assert!(brake_circuit_primed.right_brake_pressure() <= pressure_limit); + assert!(brake_circuit.left_brake_pressure() <= pressure_limit); + assert!(brake_circuit.right_brake_pressure() <= pressure_limit); } - fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { + fn hydraulic_loop(context: &mut InitContext, loop_color: &str) -> HydraulicLoop { match loop_color { "GREEN" => HydraulicLoop::new( + context, loop_color, false, true, @@ -786,6 +843,7 @@ mod tests { Pressure::new::(1750.0), ), "YELLOW" => HydraulicLoop::new( + context, loop_color, true, false, @@ -799,6 +857,7 @@ mod tests { Pressure::new::(1750.0), ), _ => HydraulicLoop::new( + context, loop_color, false, false, @@ -814,8 +873,9 @@ mod tests { } } - fn context(delta_time: Duration) -> UpdateContext { + fn context(context: &mut InitContext, delta_time: Duration) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), diff --git a/src/systems/systems/src/hydraulic/linear_actuator.rs b/src/systems/systems/src/hydraulic/linear_actuator.rs index 5ed653e744f..ae51926bda9 100644 --- a/src/systems/systems/src/hydraulic/linear_actuator.rs +++ b/src/systems/systems/src/hydraulic/linear_actuator.rs @@ -836,6 +836,9 @@ mod tests { use super::*; + use crate::electrical::Electricity; + use crate::simulation::test::TestVariableRegistry; + use crate::simulation::InitContext; use std::time::Duration; use uom::si::{ acceleration::meter_per_second_squared, angle::degree, length::foot, mass::kilogram, @@ -844,6 +847,10 @@ mod tests { #[test] fn linear_actuator_not_moving_on_locked_rigid_body() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -859,6 +866,7 @@ mod tests { Pressure::new::(1500.), ); rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -866,6 +874,7 @@ mod tests { actuator.update_after_rigid_body( &rigid_body, &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -884,6 +893,10 @@ mod tests { #[test] fn linear_actuator_moving_on_unlocked_rigid_body() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -901,6 +914,7 @@ mod tests { Pressure::new::(1500.), ); rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -908,6 +922,7 @@ mod tests { actuator.update_after_rigid_body( &rigid_body, &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -940,6 +955,10 @@ mod tests { #[test] fn linear_actuator_can_move_rigid_body_up() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -959,6 +978,7 @@ mod tests { Pressure::new::(1500.), ); rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -966,6 +986,7 @@ mod tests { actuator.update_after_rigid_body( &rigid_body, &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -994,6 +1015,10 @@ mod tests { #[test] fn linear_actuator_resists_body_drop_when_valves_closed() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -1015,6 +1040,7 @@ mod tests { Pressure::new::(1500.), ); rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1022,6 +1048,7 @@ mod tests { actuator.update_after_rigid_body( &rigid_body, &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1055,6 +1082,10 @@ mod tests { #[test] fn linear_actuator_dampens_body_drop_when_damping_mode() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -1073,6 +1104,7 @@ mod tests { Pressure::new::(1500.), ); rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1080,6 +1112,7 @@ mod tests { actuator.update_after_rigid_body( &rigid_body, &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1109,6 +1142,10 @@ mod tests { #[test] fn linear_actuator_without_hyd_pressure_cant_move_body_up() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -1116,6 +1153,7 @@ mod tests { let dt = 0.05; let context = &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1149,6 +1187,10 @@ mod tests { #[test] fn linear_actuator_losing_hyd_pressure_half_way_cant_move_body_up() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let mut actuator = cargo_door_actuator(&rigid_body); @@ -1156,6 +1198,7 @@ mod tests { let dt = 0.05; let context = &context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(0.), @@ -1198,8 +1241,14 @@ mod tests { } } - fn context(delta_time: Duration, pitch: Angle, bank: Angle) -> UpdateContext { + fn context( + context: &mut InitContext, + delta_time: Duration, + pitch: Angle, + bank: Angle, + ) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), @@ -1228,6 +1277,10 @@ mod tests { #[test] fn body_gravity_movement() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(false); let dt = 0.05; @@ -1235,6 +1288,7 @@ mod tests { let mut time = 0.; for _ in 0..100 { rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(-45.), @@ -1246,6 +1300,10 @@ mod tests { #[test] fn not_locked_at_init_will_move() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(false); let init_pos = rigid_body.position; @@ -1254,6 +1312,7 @@ mod tests { let mut time = 0.; for _ in 0..100 { rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(-45.), @@ -1269,6 +1328,10 @@ mod tests { #[test] fn locked_at_init_wont_move() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let dt = 0.05; @@ -1278,6 +1341,7 @@ mod tests { let mut time = 0.; for _ in 0..100 { rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(-45.), @@ -1293,6 +1357,10 @@ mod tests { #[test] fn start_moving_once_unlocked() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(true); let dt = 0.05; @@ -1302,6 +1370,7 @@ mod tests { let mut time = 0.; for _ in 0..100 { rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(-45.), @@ -1337,6 +1406,10 @@ mod tests { #[test] fn locks_at_required_position() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut rigid_body = cargo_door_body(false); let dt = 0.05; @@ -1351,6 +1424,7 @@ mod tests { for _ in 0..100 { rigid_body.update(&context( + &mut init_context, Duration::from_secs_f64(dt), Angle::new::(0.), Angle::new::(-45.), diff --git a/src/systems/systems/src/hydraulic/mod.rs b/src/systems/systems/src/hydraulic/mod.rs index 9909623a962..c85330474a7 100644 --- a/src/systems/systems/src/hydraulic/mod.rs +++ b/src/systems/systems/src/hydraulic/mod.rs @@ -2,9 +2,9 @@ use self::linear_actuator::Actuator; use crate::shared::{interpolation, ElectricalBusType, ElectricalBuses}; use crate::simulation::{ - SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, Write, + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + VariableIdentifier, Write, }; -use std::string::String; use std::time::Duration; use uom::si::{ angular_velocity::revolution_per_minute, @@ -76,6 +76,11 @@ pub trait PowerTransferUnitController { } pub struct PowerTransferUnit { + active_l2r_id: VariableIdentifier, + active_r2l_id: VariableIdentifier, + motor_flow_id: VariableIdentifier, + valve_opened_id: VariableIdentifier, + is_enabled: bool, is_active_right: bool, is_active_left: bool, @@ -96,8 +101,13 @@ impl PowerTransferUnit { // set to 0.5 PTU will only use half of the flow that all pumps are able to generate const AGRESSIVENESS_FACTOR: f64 = 0.78; - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { + active_l2r_id: context.get_identifier("HYD_PTU_ACTIVE_L2R".to_owned()), + active_r2l_id: context.get_identifier("HYD_PTU_ACTIVE_R2L".to_owned()), + motor_flow_id: context.get_identifier("HYD_PTU_MOTOR_FLOW".to_owned()), + valve_opened_id: context.get_identifier("HYD_PTU_VALVE_OPENED".to_owned()), + is_enabled: false, is_active_right: false, is_active_left: false, @@ -192,15 +202,10 @@ impl PowerTransferUnit { } impl SimulationElement for PowerTransferUnit { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("HYD_PTU_ACTIVE_L2R", self.is_active_left); - writer.write("HYD_PTU_ACTIVE_R2L", self.is_active_right); - writer.write("HYD_PTU_MOTOR_FLOW", self.flow()); - writer.write("HYD_PTU_VALVE_OPENED", self.is_enabled()); - } -} -impl Default for PowerTransferUnit { - fn default() -> Self { - Self::new() + writer.write(&self.active_l2r_id, self.is_active_left); + writer.write(&self.active_r2l_id, self.is_active_right); + writer.write(&self.motor_flow_id, self.flow()); + writer.write(&self.valve_opened_id, self.is_enabled()); } } @@ -314,9 +319,10 @@ impl Accumulator { } } pub struct HydraulicLoop { - pressure_id: String, - reservoir_id: String, - fire_valve_id: String, + pressure_id: VariableIdentifier, + reservoir_id: VariableIdentifier, + fire_valve_id: VariableIdentifier, + fluid: Fluid, accumulator: Accumulator, connected_to_ptu_left_side: bool, @@ -357,6 +363,7 @@ impl HydraulicLoop { [0.0, 0.001, 0.005, 0.05, 0.08, 0.15, 0.25, 0.35, 0.5, 0.5]; pub fn new( + context: &mut InitContext, id: &str, connected_to_ptu_left_side: bool, // Is connected to PTU "left" side: non variable displacement side connected_to_ptu_right_side: bool, // Is connected to PTU "right" side: variable displacement side @@ -370,9 +377,9 @@ impl HydraulicLoop { min_pressure_pressurised_hi_hyst: Pressure, ) -> Self { Self { - pressure_id: format!("HYD_{}_PRESSURE", id), - reservoir_id: format!("HYD_{}_RESERVOIR", id), - fire_valve_id: format!("HYD_{}_FIRE_VALVE_OPENED", id), + pressure_id: context.get_identifier(format!("HYD_{}_PRESSURE", id)), + reservoir_id: context.get_identifier(format!("HYD_{}_RESERVOIR", id)), + fire_valve_id: context.get_identifier(format!("HYD_{}_FIRE_VALVE_OPENED", id)), connected_to_ptu_left_side, connected_to_ptu_right_side, @@ -725,7 +732,7 @@ impl PressureSource for Pump { } pub struct ElectricPump { - active_id: String, + active_id: VariableIdentifier, is_active: bool, bus_type: ElectricalBusType, @@ -744,9 +751,9 @@ impl ElectricPump { // 1 == No filtering const DISPLACEMENT_DYNAMICS: f64 = 1.0; - pub fn new(id: &str, bus_type: ElectricalBusType) -> Self { + pub fn new(context: &mut InitContext, id: &str, bus_type: ElectricalBusType) -> Self { Self { - active_id: format!("HYD_{}_EPUMP_ACTIVE", id), + active_id: context.get_identifier(format!("HYD_{}_EPUMP_ACTIVE", id)), is_active: false, bus_type, is_powered: false, @@ -820,7 +827,7 @@ impl SimulationElement for ElectricPump { } pub struct EngineDrivenPump { - active_id: String, + active_id: VariableIdentifier, is_active: bool, speed: AngularVelocity, @@ -835,9 +842,9 @@ impl EngineDrivenPump { // 0.1 == 90% filtering on max displacement transient const DISPLACEMENT_DYNAMICS: f64 = 0.95; - pub fn new(id: &str) -> Self { + pub fn new(context: &mut InitContext, id: &str) -> Self { Self { - active_id: format!("HYD_{}_EDPUMP_ACTIVE", id), + active_id: context.get_identifier(format!("HYD_{}_EDPUMP_ACTIVE", id)), is_active: false, speed: AngularVelocity::new::(0.), pump: Pump::new( @@ -884,6 +891,8 @@ impl SimulationElement for EngineDrivenPump { } struct WindTurbine { + rpm_id: VariableIdentifier, + position: f64, speed: f64, acceleration: f64, @@ -900,8 +909,10 @@ impl WindTurbine { ]; const PROP_ALPHA_MAP: [f64; 9] = [45., 45., 45., 45., 35., 25., 1., 1., 1.]; - fn new() -> Self { + fn new(context: &mut InitContext) -> Self { Self { + rpm_id: context.get_identifier("HYD_RAT_RPM".to_owned()), + position: Self::STOWED_ANGLE, speed: 0., acceleration: 0., @@ -971,12 +982,7 @@ impl WindTurbine { } impl SimulationElement for WindTurbine { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("HYD_RAT_RPM", self.rpm()); - } -} -impl Default for WindTurbine { - fn default() -> Self { - Self::new() + writer.write(&self.rpm_id, self.rpm()); } } @@ -1002,6 +1008,8 @@ pub trait RamAirTurbineController { } pub struct RamAirTurbine { + stow_position_id: VariableIdentifier, + deployment_commanded: bool, pump: Pump, pump_controller: AlwaysPressurisePumpController, @@ -1021,7 +1029,7 @@ impl RamAirTurbine { // Speed to go from 0 to 1 stow position per sec. 1 means full deploying in 1s const STOWING_SPEED: f64 = 1.; - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { let mut max_disp = 0.; for v in Self::DISPLACEMENT_MAP.iter() { if v > &max_disp { @@ -1030,6 +1038,8 @@ impl RamAirTurbine { } Self { + stow_position_id: context.get_identifier("HYD_RAT_STOW_POSITION".to_owned()), + deployment_commanded: false, pump: Pump::new( Self::DISPLACEMENT_BREAKPTS, @@ -1037,7 +1047,7 @@ impl RamAirTurbine { Self::DISPLACEMENT_DYNAMICS, ), pump_controller: AlwaysPressurisePumpController::new(), - wind_turbine: WindTurbine::new(), + wind_turbine: WindTurbine::new(context), position: 0., max_displacement: max_disp, } @@ -1109,18 +1119,13 @@ impl SimulationElement for RamAirTurbine { } fn write(&self, writer: &mut SimulatorWriter) { - writer.write("HYD_RAT_STOW_POSITION", self.position); - } -} -impl Default for RamAirTurbine { - fn default() -> Self { - Self::new() + writer.write(&self.stow_position_id, self.position); } } #[cfg(test)] mod tests { - use crate::simulation::UpdateContext; + use crate::simulation::{Aircraft, UpdateContext}; use uom::si::{ acceleration::foot_per_second_squared, angle::radian, @@ -1183,91 +1188,119 @@ mod tests { } } - use super::*; - #[test] - /// Runs electric pump, checks pressure OK, shut it down, check drop of pressure after 20s - fn blue_loop_rat_deploy_simulation() { - let mut rat = RamAirTurbine::new(); - let mut rat_controller = TestRamAirTurbineController::new(); - let mut blue_loop = hydraulic_loop("BLUE"); - let blue_loop_controller = - TestHydraulicLoopController::commanding_open_fire_shutoff_valve(); - - let timestep = 0.05; - let context = context(Duration::from_secs_f64(timestep)); - let mut indicated_airspeed = context.indicated_airspeed(); - - let mut time = 0.0; - for x in 0..1500 { - rat.update_position(&context.delta()); - if time >= 10. && time < 10. + timestep { - println!("ASSERT RAT STOWED"); - assert!(blue_loop.loop_pressure <= Pressure::new::(50.0)); - rat.deployment_commanded = false; - assert!(rat.position == 0.); - } + struct BlueLoopRatDeploySimulationAircraft { + rat: RamAirTurbine, + blue_loop: HydraulicLoop, + context: UpdateContext, + } + impl BlueLoopRatDeploySimulationAircraft { + const TIME_STEP_SECS: f64 = 0.05; - if time >= 20. && time < 20. + timestep { - println!("ASSERT RAT STOWED STILL NO PRESS"); - assert!(blue_loop.loop_pressure <= Pressure::new::(50.0)); - rat_controller.command_deployment(); + fn new(init_context: &mut InitContext) -> Self { + Self { + rat: RamAirTurbine::new(init_context), + blue_loop: hydraulic_loop(init_context, "BLUE"), + context: context(init_context, Duration::from_secs_f64(Self::TIME_STEP_SECS)), } + } - if time >= 30. && time < 30. + timestep { - println!("ASSERT RAT OUT AND SPINING"); - assert!(blue_loop.loop_pressure >= Pressure::new::(2000.0)); - assert!(rat.position >= 0.999); - assert!(rat.wind_turbine.rpm >= 1000.); - } - if time >= 60. && time < 60. + timestep { - println!("ASSERT RAT AT SPEED"); - assert!(blue_loop.loop_pressure >= Pressure::new::(2000.0)); - assert!(rat.wind_turbine.rpm >= 4500.); - } + fn update(&mut self) { + let mut rat_controller = TestRamAirTurbineController::new(); + let blue_loop_controller = + TestHydraulicLoopController::commanding_open_fire_shutoff_valve(); + + let mut indicated_airspeed = self.context.indicated_airspeed(); + + let mut time = 0.0; + for x in 0..1500 { + self.rat.update_position(&self.context.delta()); + if (10.0..10.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("ASSERT RAT STOWED"); + assert!(self.blue_loop.loop_pressure <= Pressure::new::(50.0)); + self.rat.deployment_commanded = false; + assert!(self.rat.position == 0.); + } - if time >= 70. && time < 70. + timestep { - println!("STOPING THE PLANE"); - indicated_airspeed = Velocity::new::(0.); - } + if (20.0..20.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("ASSERT RAT STOWED STILL NO PRESS"); + assert!(self.blue_loop.loop_pressure <= Pressure::new::(50.0)); + rat_controller.command_deployment(); + } - if time >= 120. && time < 120. + timestep { - println!("ASSERT RAT SLOWED DOWN"); - assert!(rat.wind_turbine.rpm <= 2500.); - } + if (30.0..30.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("ASSERT RAT OUT AND SPINING"); + assert!(self.blue_loop.loop_pressure >= Pressure::new::(2000.0)); + assert!(self.rat.position >= 0.999); + assert!(self.rat.wind_turbine.rpm >= 1000.); + } + if (60.0..60.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("ASSERT RAT AT SPEED"); + assert!(self.blue_loop.loop_pressure >= Pressure::new::(2000.0)); + assert!(self.rat.wind_turbine.rpm >= 4500.); + } - rat.update_physics(&context.delta(), indicated_airspeed); - rat.update(&context, &blue_loop, &rat_controller); - blue_loop.update( - &context, - Vec::new(), - Vec::new(), - vec![&rat], - Vec::new(), - &blue_loop_controller, - ); - if x % 20 == 0 { - println!("Iteration {} Time {}", x, time); - println!("-------------------------------------------"); - println!("---PSI: {}", blue_loop.loop_pressure.get::()); - println!("---RAT stow pos: {}", rat.position); - println!("---RAT RPM: {}", rat.wind_turbine.rpm); - println!("---RAT volMax: {}", rat.delta_vol_max().get::()); - println!( - "--------Reservoir Volume (g): {}", - blue_loop.reservoir_volume.get::() - ); - println!( - "--------Loop Volume (g): {}", - blue_loop.loop_volume.get::() + if (70.0..70.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("STOPING THE PLANE"); + indicated_airspeed = Velocity::new::(0.); + } + + if (120.0..120.0 + Self::TIME_STEP_SECS).contains(&time) { + println!("ASSERT RAT SLOWED DOWN"); + assert!(self.rat.wind_turbine.rpm <= 2500.); + } + + self.rat + .update_physics(&self.context.delta(), indicated_airspeed); + self.rat + .update(&self.context, &self.blue_loop, &rat_controller); + self.blue_loop.update( + &self.context, + Vec::new(), + Vec::new(), + vec![&self.rat], + Vec::new(), + &blue_loop_controller, ); + if x % 20 == 0 { + println!("Iteration {} Time {}", x, time); + println!("-------------------------------------------"); + println!("---PSI: {}", self.blue_loop.loop_pressure.get::()); + println!("---RAT stow pos: {}", self.rat.position); + println!("---RAT RPM: {}", self.rat.wind_turbine.rpm); + println!( + "---RAT volMax: {}", + self.rat.delta_vol_max().get::() + ); + println!( + "--------Reservoir Volume (g): {}", + self.blue_loop.reservoir_volume.get::() + ); + println!( + "--------Loop Volume (g): {}", + self.blue_loop.loop_volume.get::() + ); + } + time += Self::TIME_STEP_SECS; } - time += timestep; } } + impl Aircraft for BlueLoopRatDeploySimulationAircraft {} + impl SimulationElement for BlueLoopRatDeploySimulationAircraft {} - fn hydraulic_loop(loop_color: &str) -> HydraulicLoop { + use super::*; + use crate::simulation::test::{SimulationTestBed, TestBed}; + + #[test] + /// Runs electric pump, checks pressure OK, shut it down, check drop of pressure after 20s + fn blue_loop_rat_deploy_simulation() { + let mut test_bed = SimulationTestBed::new(BlueLoopRatDeploySimulationAircraft::new); + test_bed.command(|e| e.update()); + } + + fn hydraulic_loop(context: &mut InitContext, loop_color: &str) -> HydraulicLoop { match loop_color { "GREEN" => HydraulicLoop::new( + context, loop_color, true, false, @@ -1281,6 +1314,7 @@ mod tests { Pressure::new::(1750.0), ), "YELLOW" => HydraulicLoop::new( + context, loop_color, false, true, @@ -1294,6 +1328,7 @@ mod tests { Pressure::new::(1750.0), ), _ => HydraulicLoop::new( + context, loop_color, false, false, @@ -1309,12 +1344,13 @@ mod tests { } } - fn engine_driven_pump() -> EngineDrivenPump { - EngineDrivenPump::new("DEFAULT") + fn engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { + EngineDrivenPump::new(context, "DEFAULT") } - fn context(delta_time: Duration) -> UpdateContext { + fn context(context: &mut InitContext, delta_time: Duration) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), @@ -1332,22 +1368,27 @@ mod tests { mod edp_tests { use super::*; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; + use crate::simulation::Aircraft; + use uom::si::time::second; + #[test] fn starts_inactive() { - assert!(!engine_driven_pump().is_active); + let test_bed = SimulationTestBed::from(ElementCtorFn(engine_driven_pump)); + + assert!(test_bed.query_element(|e| !e.is_active)); } #[test] fn zero_flow_above_3000_psi_after_25ms() { let pump_rpm = 3300.; let pressure = Pressure::new::(3100.); - let context = context(Duration::from_millis(25)); let displacement = Volume::new::(0.); assert!(delta_vol_equality_check( pump_rpm, displacement, pressure, - &context + Duration::from_millis(25), )) } @@ -1355,10 +1396,10 @@ mod tests { pump_rpm: f64, displacement: Volume, pressure: Pressure, - context: &UpdateContext, + delta_time: Duration, ) -> bool { - let actual = get_edp_actual_delta_vol_when(pump_rpm, pressure, context); - let predicted = get_edp_predicted_delta_vol_when(pump_rpm, displacement, context); + let actual = get_edp_actual_delta_vol_when(pump_rpm, pressure, delta_time); + let predicted = get_edp_predicted_delta_vol_when(pump_rpm, displacement, delta_time); println!("Actual: {}", actual.get::()); println!("Predicted: {}", predicted.get::()); actual == predicted @@ -1367,36 +1408,62 @@ mod tests { fn get_edp_actual_delta_vol_when( pump_rpm: f64, pressure: Pressure, - context: &UpdateContext, + delta_time: Duration, ) -> Volume { - let mut edp = engine_driven_pump(); - let mut line = hydraulic_loop("GREEN"); - - let engine_driven_pump_controller = TestPumpController::commanding_pressurise(); - line.loop_pressure = pressure; - edp.update( - &context.with_delta(Duration::from_secs(1)), - &line, - AngularVelocity::new::(pump_rpm), - &engine_driven_pump_controller, - ); // Update 10 times to stabilize displacement - - edp.update( - context, - &line, - AngularVelocity::new::(pump_rpm), - &engine_driven_pump_controller, - ); - edp.delta_vol_max() + let mut test_bed = SimulationTestBed::new(|context| { + EdpActualDeltaVolAircraft::new(context, delta_time) + }); + test_bed.command(|e| e.update(pump_rpm, pressure)); + + test_bed.query(|e| e.delta_vol_max()) } fn get_edp_predicted_delta_vol_when( pump_rpm: f64, displacement: Volume, - context: &UpdateContext, + delta_time: Duration, ) -> Volume { let expected_flow = Pump::calculate_flow(pump_rpm, displacement); - expected_flow * context.delta_as_time() + expected_flow * Time::new::(delta_time.as_secs_f64()) + } + + struct EdpActualDeltaVolAircraft { + edp: EngineDrivenPump, + line: HydraulicLoop, + context: UpdateContext, + } + impl EdpActualDeltaVolAircraft { + fn new(init_context: &mut InitContext, delta_time: Duration) -> Self { + Self { + edp: engine_driven_pump(init_context), + line: hydraulic_loop(init_context, "GREEN"), + context: context(init_context, delta_time), + } + } + + fn update(&mut self, pump_rpm: f64, pressure: Pressure) { + let engine_driven_pump_controller = TestPumpController::commanding_pressurise(); + self.line.loop_pressure = pressure; + self.edp.update( + &self.context.with_delta(Duration::from_secs(1)), + &self.line, + AngularVelocity::new::(pump_rpm), + &engine_driven_pump_controller, + ); // Update 10 times to stabilize displacement + + self.edp.update( + &self.context, + &self.line, + AngularVelocity::new::(pump_rpm), + &engine_driven_pump_controller, + ); + } + + fn delta_vol_max(&self) -> Volume { + self.edp.delta_vol_max() + } } + impl Aircraft for EdpActualDeltaVolAircraft {} + impl SimulationElement for EdpActualDeltaVolAircraft {} } } diff --git a/src/systems/systems/src/hydraulic/update_iterator.rs b/src/systems/systems/src/hydraulic/update_iterator.rs index 574f1c4d5ee..e92a28e3af9 100644 --- a/src/systems/systems/src/hydraulic/update_iterator.rs +++ b/src/systems/systems/src/hydraulic/update_iterator.rs @@ -134,6 +134,9 @@ mod fixed_tests { use std::time::Duration; + use crate::electrical::Electricity; + use crate::simulation::test::TestVariableRegistry; + use crate::simulation::InitContext; use uom::si::{ acceleration::foot_per_second_squared, angle::radian, f64::*, length::foot, thermodynamic_temperature::degree_celsius, velocity::knot, @@ -148,24 +151,36 @@ mod fixed_tests { #[test] fn no_step_after_zero_time_update() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut fixed_step = FixedStepLoop::new(Duration::from_millis(100)); - fixed_step.update(&context(Duration::from_secs(0))); + fixed_step.update(&context(&mut init_context, Duration::from_secs(0))); assert!(fixed_step.into_iter().len() == 0); } #[test] fn one_step_after_exact_fixed_time_step_update() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut fixed_step = FixedStepLoop::new(Duration::from_millis(100)); - fixed_step.update(&context(Duration::from_millis(100))); + fixed_step.update(&context(&mut init_context, Duration::from_millis(100))); assert!(fixed_step.into_iter().len() == 1); } #[test] fn more_than_fixed_step_gives_correct_num_of_loops() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let timestep = Duration::from_millis(100); let mut fixed_step = FixedStepLoop::new(timestep); @@ -177,7 +192,7 @@ mod fixed_tests { let expected_remaining_time_at_end_of_loops = test_duration - expected_num_of_loops * timestep; - fixed_step.update(&context(test_duration)); + fixed_step.update(&context(&mut init_context, test_duration)); let mut actual_loop_num = 0; for cur_time_step in &mut fixed_step { @@ -189,8 +204,9 @@ mod fixed_tests { assert!(actual_loop_num == expected_num_of_loops); } - fn context(delta_time: Duration) -> UpdateContext { + fn context(context: &mut InitContext, delta_time: Duration) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), @@ -211,6 +227,9 @@ mod max_step_tests { use std::time::Duration; + use crate::electrical::Electricity; + use crate::simulation::test::TestVariableRegistry; + use crate::simulation::InitContext; use uom::si::{ acceleration::foot_per_second_squared, angle::radian, f64::*, length::foot, thermodynamic_temperature::degree_celsius, velocity::knot, @@ -225,30 +244,42 @@ mod max_step_tests { #[test] fn no_step_after_zero_time_update() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut max_step = MaxFixedStepLoop::new(Duration::from_millis(100)); - max_step.update(&context(Duration::from_secs(0))); + max_step.update(&context(&mut init_context, Duration::from_secs(0))); assert!(max_step.into_iter().len() == 0); } #[test] fn one_step_after_exact_fixed_time_step_update() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let mut max_step = MaxFixedStepLoop::new(Duration::from_millis(100)); - max_step.update(&context(Duration::from_millis(100))); + max_step.update(&context(&mut init_context, Duration::from_millis(100))); assert!(max_step.into_iter().len() == 1); } #[test] fn more_than_max_step_gives_correct_num_of_loops() { + let mut electricity = Electricity::new(); + let mut registry: TestVariableRegistry = Default::default(); + let mut init_context = InitContext::new(&mut electricity, &mut registry); + let timestep = Duration::from_millis(100); let mut max_step = MaxFixedStepLoop::new(timestep); let test_duration = Duration::from_secs_f64(0.320); - max_step.update(&context(test_duration)); + max_step.update(&context(&mut init_context, test_duration)); let mut actual_loop_num = 0; let mut time_simulated = Duration::from_secs(0); @@ -265,8 +296,9 @@ mod max_step_tests { ); } - fn context(delta_time: Duration) -> UpdateContext { + fn context(context: &mut InitContext, delta_time: Duration) -> UpdateContext { UpdateContext::new( + context, delta_time, Velocity::new::(250.), Length::new::(5000.), diff --git a/src/systems/systems/src/landing_gear/mod.rs b/src/systems/systems/src/landing_gear/mod.rs index cd16ec34642..5a938679c64 100644 --- a/src/systems/systems/src/landing_gear/mod.rs +++ b/src/systems/systems/src/landing_gear/mod.rs @@ -1,3 +1,4 @@ +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{ ElectricalBusType, ElectricalBuses, LandingGearRealPosition, LgciuGearExtension, @@ -21,6 +22,13 @@ pub enum GearWheel { /// locked or down and locked. No in between state. /// It provides as well the state of all weight on wheel sensors pub struct LandingGear { + center_position_id: VariableIdentifier, + left_position_id: VariableIdentifier, + right_position_id: VariableIdentifier, + center_compression_id: VariableIdentifier, + left_compression_id: VariableIdentifier, + right_compression_id: VariableIdentifier, + center_position: Ratio, left_position: Ratio, right_position: Ratio, @@ -41,8 +49,15 @@ impl LandingGear { // Is extended at 0.5, we set a super small margin of 0.02 from fully extended so 0.52 const COMPRESSION_THRESHOLD_FOR_WEIGHT_ON_WHEELS_RATIO: f64 = 0.52; - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { + center_position_id: context.get_identifier(Self::GEAR_CENTER_POSITION.to_owned()), + left_position_id: context.get_identifier(Self::GEAR_LEFT_POSITION.to_owned()), + right_position_id: context.get_identifier(Self::GEAR_RIGHT_POSITION.to_owned()), + center_compression_id: context.get_identifier(Self::GEAR_CENTER_COMPRESSION.to_owned()), + left_compression_id: context.get_identifier(Self::GEAR_LEFT_COMPRESSION.to_owned()), + right_compression_id: context.get_identifier(Self::GEAR_RIGHT_COMPRESSION.to_owned()), + center_position: Ratio::new::(0.), left_position: Ratio::new::(0.), right_position: Ratio::new::(0.), @@ -97,18 +112,13 @@ impl LandingGearRealPosition for LandingGear { } impl SimulationElement for LandingGear { fn read(&mut self, reader: &mut SimulatorReader) { - self.center_position = reader.read(LandingGear::GEAR_CENTER_POSITION); - self.left_position = reader.read(LandingGear::GEAR_LEFT_POSITION); - self.right_position = reader.read(LandingGear::GEAR_RIGHT_POSITION); + self.center_position = reader.read(&self.center_position_id); + self.left_position = reader.read(&self.left_position_id); + self.right_position = reader.read(&self.right_position_id); - self.center_compression = reader.read(LandingGear::GEAR_CENTER_COMPRESSION); - self.left_compression = reader.read(LandingGear::GEAR_LEFT_COMPRESSION); - self.right_compression = reader.read(LandingGear::GEAR_RIGHT_COMPRESSION); - } -} -impl Default for LandingGear { - fn default() -> Self { - Self::new() + self.center_compression = reader.read(&self.center_compression_id); + self.left_compression = reader.read(&self.left_compression_id); + self.right_compression = reader.read(&self.right_compression_id); } } @@ -235,10 +245,8 @@ impl LgciuInterface for LandingGearControlInterfaceUnit {} #[cfg(test)] mod tests { use super::*; - use crate::simulation::{ - test::{SimulationTestBed, TestAircraft, TestBed}, - Write, - }; + use crate::simulation::test::{ElementCtorFn, WriteByName}; + use crate::simulation::test::{SimulationTestBed, TestAircraft, TestBed}; #[test] fn is_up_and_locked_returns_false_when_fully_down() { @@ -324,10 +332,10 @@ mod tests { fn run_test_bed_on_with_position( position: Ratio, ) -> SimulationTestBed> { - let mut test_bed = SimulationTestBed::from(LandingGear::new()); - test_bed.write(LandingGear::GEAR_CENTER_POSITION, position); - test_bed.write(LandingGear::GEAR_LEFT_POSITION, position); - test_bed.write(LandingGear::GEAR_RIGHT_POSITION, position); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(LandingGear::new)); + test_bed.write_by_name(LandingGear::GEAR_CENTER_POSITION, position); + test_bed.write_by_name(LandingGear::GEAR_LEFT_POSITION, position); + test_bed.write_by_name(LandingGear::GEAR_RIGHT_POSITION, position); test_bed.run(); @@ -339,10 +347,10 @@ mod tests { center: Ratio, right: Ratio, ) -> SimulationTestBed> { - let mut test_bed = SimulationTestBed::from(LandingGear::new()); - test_bed.write(LandingGear::GEAR_LEFT_COMPRESSION, left); - test_bed.write(LandingGear::GEAR_CENTER_COMPRESSION, center); - test_bed.write(LandingGear::GEAR_RIGHT_COMPRESSION, right); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(LandingGear::new)); + test_bed.write_by_name(LandingGear::GEAR_LEFT_COMPRESSION, left); + test_bed.write_by_name(LandingGear::GEAR_CENTER_COMPRESSION, center); + test_bed.write_by_name(LandingGear::GEAR_RIGHT_COMPRESSION, right); test_bed.run(); diff --git a/src/systems/systems/src/navigation/adirs.rs b/src/systems/systems/src/navigation/adirs.rs index 5811b081844..ae0bcfca3bb 100644 --- a/src/systems/systems/src/navigation/adirs.rs +++ b/src/systems/systems/src/navigation/adirs.rs @@ -1,3 +1,4 @@ +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ overhead::{IndicationLight, OnOffFaultPushButton}, shared::{ @@ -29,24 +30,24 @@ impl AirDataInertialReferenceSystemOverheadPanel { const DURATION_AFTER_WHICH_ON_BAT_ILLUMINATES: Duration = Duration::from_millis(10500); const ON_BAT_ILLUMINATION_DURATION: Duration = Duration::from_millis(5500); - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { ir: [ - OnOffFaultPushButton::new_on("ADIRS_IR_1"), - OnOffFaultPushButton::new_on("ADIRS_IR_2"), - OnOffFaultPushButton::new_on("ADIRS_IR_3"), + OnOffFaultPushButton::new_on(context, "ADIRS_IR_1"), + OnOffFaultPushButton::new_on(context, "ADIRS_IR_2"), + OnOffFaultPushButton::new_on(context, "ADIRS_IR_3"), ], mode_selectors: [ - InertialReferenceModeSelector::new(1), - InertialReferenceModeSelector::new(2), - InertialReferenceModeSelector::new(3), + InertialReferenceModeSelector::new(context, 1), + InertialReferenceModeSelector::new(context, 2), + InertialReferenceModeSelector::new(context, 3), ], adr: [ - OnOffFaultPushButton::new_on("ADIRS_ADR_1"), - OnOffFaultPushButton::new_on("ADIRS_ADR_2"), - OnOffFaultPushButton::new_on("ADIRS_ADR_3"), + OnOffFaultPushButton::new_on(context, "ADIRS_ADR_1"), + OnOffFaultPushButton::new_on(context, "ADIRS_ADR_2"), + OnOffFaultPushButton::new_on(context, "ADIRS_ADR_3"), ], - on_bat: IndicationLight::new(Self::ADIRS_ON_BAT_NAME), + on_bat: IndicationLight::new(context, Self::ADIRS_ON_BAT_NAME), } } @@ -96,11 +97,6 @@ impl SimulationElement for AirDataInertialReferenceSystemOverheadPanel { visitor.visit(self); } } -impl Default for AirDataInertialReferenceSystemOverheadPanel { - fn default() -> Self { - Self::new() - } -} #[derive(Clone, Copy, PartialEq)] enum InertialReferenceMode { @@ -122,14 +118,14 @@ impl From for InertialReferenceMode { } struct InertialReferenceModeSelector { - mode_id: String, + mode_id: VariableIdentifier, mode: InertialReferenceMode, not_off_duration: Duration, } impl InertialReferenceModeSelector { - fn new(number: usize) -> Self { + fn new(context: &mut InitContext, number: usize) -> Self { Self { - mode_id: Self::mode_id(number), + mode_id: context.get_identifier(Self::mode_id(number)), // We start in an aligned state to support starting on the // runway or in the air. mode: InertialReferenceMode::Navigation, @@ -182,28 +178,53 @@ impl From for AlignState { } } -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy)] struct AdirsSimulatorData { + mach_id: VariableIdentifier, mach: MachNumber, + + vertical_speed_id: VariableIdentifier, vertical_speed: Velocity, + + true_airspeed_id: VariableIdentifier, true_airspeed: Velocity, + + latitude_id: VariableIdentifier, latitude: Angle, + + longitude_id: VariableIdentifier, longitude: Angle, + + pitch_id: VariableIdentifier, pitch: Angle, + + roll_id: VariableIdentifier, roll: Angle, + + heading_id: VariableIdentifier, heading: Angle, + + track_id: VariableIdentifier, track: Angle, + + ground_speed_id: VariableIdentifier, ground_speed: Velocity, + + wind_direction_id: VariableIdentifier, wind_direction: Angle, + + wind_velocity_id: VariableIdentifier, wind_velocity: Velocity, + + total_air_temperature_id: VariableIdentifier, total_air_temperature: ThermodynamicTemperature, } impl AdirsSimulatorData { - const LATITUDE: &'static str = "PLANE LATITUDE"; - const LONGITUDE: &'static str = "PLANE LONGITUDE"; const MACH: &'static str = "AIRSPEED MACH"; const VERTICAL_SPEED: &'static str = "VELOCITY WORLD Y"; const TRUE_AIRSPEED: &'static str = "AIRSPEED TRUE"; + const LATITUDE: &'static str = "PLANE LATITUDE"; + const LONGITUDE: &'static str = "PLANE LONGITUDE"; const PITCH: &'static str = "PLANE PITCH DEGREES"; const ROLL: &'static str = "PLANE BANK DEGREES"; const HEADING: &'static str = "PLANE HEADING DEGREES MAGNETIC"; @@ -212,28 +233,76 @@ impl AdirsSimulatorData { const WIND_DIRECTION: &'static str = "AMBIENT WIND DIRECTION"; const WIND_VELOCITY: &'static str = "AMBIENT WIND VELOCITY"; const TOTAL_AIR_TEMPERATURE: &'static str = "TOTAL AIR TEMPERATURE"; + + fn new(context: &mut InitContext) -> Self { + Self { + mach_id: context.get_identifier(Self::MACH.to_owned()), + mach: Default::default(), + + vertical_speed_id: context.get_identifier(Self::VERTICAL_SPEED.to_owned()), + vertical_speed: Default::default(), + + true_airspeed_id: context.get_identifier(Self::TRUE_AIRSPEED.to_owned()), + true_airspeed: Default::default(), + + latitude_id: context.get_identifier(Self::LATITUDE.to_owned()), + latitude: Default::default(), + + longitude_id: context.get_identifier(Self::LONGITUDE.to_owned()), + longitude: Default::default(), + + pitch_id: context.get_identifier(Self::PITCH.to_owned()), + pitch: Default::default(), + + roll_id: context.get_identifier(Self::ROLL.to_owned()), + roll: Default::default(), + + heading_id: context.get_identifier(Self::HEADING.to_owned()), + heading: Default::default(), + + track_id: context.get_identifier(Self::TRACK.to_owned()), + track: Default::default(), + + ground_speed_id: context.get_identifier(Self::GROUND_SPEED.to_owned()), + ground_speed: Default::default(), + + wind_direction_id: context.get_identifier(Self::WIND_DIRECTION.to_owned()), + wind_direction: Default::default(), + + wind_velocity_id: context.get_identifier(Self::WIND_VELOCITY.to_owned()), + wind_velocity: Default::default(), + + total_air_temperature_id: context + .get_identifier(Self::TOTAL_AIR_TEMPERATURE.to_owned()), + total_air_temperature: Default::default(), + } + } } impl SimulationElement for AdirsSimulatorData { fn read(&mut self, reader: &mut SimulatorReader) { // To reduce reads, we only read these values once and then share it with the underlying ADRs and IRs. - self.mach = reader.read(Self::MACH); - let vertical_speed: f64 = reader.read(Self::VERTICAL_SPEED); + self.mach = reader.read(&self.mach_id); + let vertical_speed: f64 = reader.read(&self.vertical_speed_id); self.vertical_speed = Velocity::new::(vertical_speed); - self.true_airspeed = reader.read(Self::TRUE_AIRSPEED); - self.latitude = reader.read(Self::LATITUDE); - self.longitude = reader.read(Self::LONGITUDE); - self.pitch = reader.read(Self::PITCH); - self.roll = reader.read(Self::ROLL); - self.heading = reader.read(Self::HEADING); - self.track = reader.read(Self::TRACK); - self.ground_speed = reader.read(Self::GROUND_SPEED); - self.wind_direction = reader.read(Self::WIND_DIRECTION); - self.wind_velocity = reader.read(Self::WIND_VELOCITY); - self.total_air_temperature = reader.read(Self::TOTAL_AIR_TEMPERATURE); + self.true_airspeed = reader.read(&self.true_airspeed_id); + self.latitude = reader.read(&self.latitude_id); + self.longitude = reader.read(&self.longitude_id); + self.pitch = reader.read(&self.pitch_id); + self.roll = reader.read(&self.roll_id); + self.heading = reader.read(&self.heading_id); + self.track = reader.read(&self.track_id); + self.ground_speed = reader.read(&self.ground_speed_id); + self.wind_direction = reader.read(&self.wind_direction_id); + self.wind_velocity = reader.read(&self.wind_velocity_id); + self.total_air_temperature = reader.read(&self.total_air_temperature_id); } } pub struct AirDataInertialReferenceSystem { + remaining_alignment_time_id: VariableIdentifier, + configured_align_time_id: VariableIdentifier, + uses_gps_as_primary_id: VariableIdentifier, + adirus: [AirDataInertialReferenceUnit; 3], configured_align_time: AlignTime, simulator_data: AdirsSimulatorData, @@ -243,15 +312,22 @@ impl AirDataInertialReferenceSystem { const CONFIGURED_ALIGN_TIME_KEY: &'static str = "CONFIG_ADIRS_IR_ALIGN_TIME"; const USES_GPS_AS_PRIMARY_KEY: &'static str = "ADIRS_USES_GPS_AS_PRIMARY"; - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { Self { + remaining_alignment_time_id: context + .get_identifier(Self::REMAINING_ALIGNMENT_TIME_KEY.to_owned()), + configured_align_time_id: context + .get_identifier(Self::CONFIGURED_ALIGN_TIME_KEY.to_owned()), + uses_gps_as_primary_id: context + .get_identifier(Self::USES_GPS_AS_PRIMARY_KEY.to_owned()), + adirus: [ - AirDataInertialReferenceUnit::new(1, true), - AirDataInertialReferenceUnit::new(2, false), - AirDataInertialReferenceUnit::new(3, true), + AirDataInertialReferenceUnit::new(context, 1, true), + AirDataInertialReferenceUnit::new(context, 2, false), + AirDataInertialReferenceUnit::new(context, 3, true), ], configured_align_time: AlignTime::Realistic, - simulator_data: Default::default(), + simulator_data: AdirsSimulatorData::new(context), } } @@ -298,38 +374,33 @@ impl SimulationElement for AirDataInertialReferenceSystem { } fn read(&mut self, reader: &mut SimulatorReader) { - self.configured_align_time = reader.read(Self::CONFIGURED_ALIGN_TIME_KEY); + self.configured_align_time = reader.read(&self.configured_align_time_id); } fn write(&self, writer: &mut SimulatorWriter) { writer.write( - Self::REMAINING_ALIGNMENT_TIME_KEY, + &self.remaining_alignment_time_id, self.remaining_align_duration(), ); writer.write( - Self::USES_GPS_AS_PRIMARY_KEY, + &self.uses_gps_as_primary_id, self.any_adiru_fully_aligned_with_ir_on(), ) } } -impl Default for AirDataInertialReferenceSystem { - fn default() -> Self { - Self::new() - } -} struct AirDataInertialReferenceUnit { - state_id: String, + state_id: VariableIdentifier, adr: AirDataReference, ir: InertialReference, } impl AirDataInertialReferenceUnit { - fn new(number: usize, outputs_temperatures: bool) -> Self { + fn new(context: &mut InitContext, number: usize, outputs_temperatures: bool) -> Self { Self { - state_id: Self::state_id(number), - adr: AirDataReference::new(number, outputs_temperatures), - ir: InertialReference::new(number), + state_id: context.get_identifier(Self::state_id(number)), + adr: AirDataReference::new(context, number, outputs_temperatures), + ir: InertialReference::new(context, number), } } @@ -389,22 +460,27 @@ impl SimulationElement for AirDataInertialReferenceUnit { } struct AdirsData { - id: String, + id: VariableIdentifier, value: T, ssm: SignStatus, } impl AdirsData { - fn new_adr(number: usize, name: &str) -> Self { - Self::new(OutputDataType::ADR, number, name) + fn new_adr(context: &mut InitContext, number: usize, name: &str) -> Self { + Self::new(context, OutputDataType::ADR, number, name) } - fn new_ir(number: usize, name: &str) -> Self { - Self::new(OutputDataType::IR, number, name) + fn new_ir(context: &mut InitContext, number: usize, name: &str) -> Self { + Self::new(context, OutputDataType::IR, number, name) } - fn new(data_type: OutputDataType, number: usize, name: &str) -> Self { + fn new( + context: &mut InitContext, + data_type: OutputDataType, + number: usize, + name: &str, + ) -> Self { Self { - id: output_data_id(data_type, number, name), + id: context.get_identifier(output_data_id(data_type, number, name)), value: Default::default(), ssm: SignStatus::NoComputedData, } @@ -479,20 +555,29 @@ impl AirDataReference { "INTERNATIONAL_STANDARD_ATMOSPHERE_DELTA"; const MINIMUM_COMPUTED_AIRSPEED_FOR_TRUE_AIRSPEED_DETERMINATION_KNOTS: f64 = 60.; - fn new(number: usize, outputs_temperatures: bool) -> Self { + fn new(context: &mut InitContext, number: usize, outputs_temperatures: bool) -> Self { Self { number, is_on: true, outputs_temperatures, - altitude: AdirsData::new_adr(number, Self::ALTITUDE), - computed_airspeed: AdirsData::new_adr(number, Self::COMPUTED_AIRSPEED), - mach: AdirsData::new_adr(number, Self::MACH), - barometric_vertical_speed: AdirsData::new_adr(number, Self::BAROMETRIC_VERTICAL_SPEED), - true_airspeed: AdirsData::new_adr(number, Self::TRUE_AIRSPEED), - static_air_temperature: AdirsData::new_adr(number, Self::STATIC_AIR_TEMPERATURE), - total_air_temperature: AdirsData::new_adr(number, Self::TOTAL_AIR_TEMPERATURE), + altitude: AdirsData::new_adr(context, number, Self::ALTITUDE), + computed_airspeed: AdirsData::new_adr(context, number, Self::COMPUTED_AIRSPEED), + mach: AdirsData::new_adr(context, number, Self::MACH), + barometric_vertical_speed: AdirsData::new_adr( + context, + number, + Self::BAROMETRIC_VERTICAL_SPEED, + ), + true_airspeed: AdirsData::new_adr(context, number, Self::TRUE_AIRSPEED), + static_air_temperature: AdirsData::new_adr( + context, + number, + Self::STATIC_AIR_TEMPERATURE, + ), + total_air_temperature: AdirsData::new_adr(context, number, Self::TOTAL_AIR_TEMPERATURE), international_standard_atmosphere_delta: AdirsData::new_adr( + context, number, Self::INTERNATIONAL_STANDARD_ATMOSPHERE_DELTA, ), @@ -674,7 +759,7 @@ impl InertialReference { const MINIMUM_TRUE_AIRSPEED_FOR_WIND_DETERMINATION_KNOTS: f64 = 100.; const MINIMUM_GROUND_SPEED_FOR_TRACK_KNOTS: f64 = 50.; - fn new(number: usize) -> Self { + fn new(context: &mut InitContext, number: usize) -> Self { Self { number, is_on: true, @@ -684,16 +769,16 @@ impl InertialReference { ir_fault_flash_duration: None, // Start fully initialised. remaining_attitude_initialisation_duration: Some(Duration::from_secs(0)), - pitch: AdirsData::new_ir(number, Self::PITCH), - roll: AdirsData::new_ir(number, Self::ROLL), - heading: AdirsData::new_ir(number, Self::HEADING), - track: AdirsData::new_ir(number, Self::TRACK), - vertical_speed: AdirsData::new_ir(number, Self::VERTICAL_SPEED), - ground_speed: AdirsData::new_ir(number, Self::GROUND_SPEED), - wind_direction: AdirsData::new_ir(number, Self::WIND_DIRECTION), - wind_velocity: AdirsData::new_ir(number, Self::WIND_VELOCITY), - latitude: AdirsData::new_ir(number, Self::LATITUDE), - longitude: AdirsData::new_ir(number, Self::LONGITUDE), + pitch: AdirsData::new_ir(context, number, Self::PITCH), + roll: AdirsData::new_ir(context, number, Self::ROLL), + heading: AdirsData::new_ir(context, number, Self::HEADING), + track: AdirsData::new_ir(context, number, Self::TRACK), + vertical_speed: AdirsData::new_ir(context, number, Self::VERTICAL_SPEED), + ground_speed: AdirsData::new_ir(context, number, Self::GROUND_SPEED), + wind_direction: AdirsData::new_ir(context, number, Self::WIND_DIRECTION), + wind_velocity: AdirsData::new_ir(context, number, Self::WIND_VELOCITY), + latitude: AdirsData::new_ir(context, number, Self::LATITUDE), + longitude: AdirsData::new_ir(context, number, Self::LONGITUDE), } } @@ -931,11 +1016,12 @@ fn subtract_delta_from_duration(context: &UpdateContext, duration: Duration) -> #[cfg(test)] mod tests { use super::*; + use crate::simulation::test::{ReadByName, WriteByName}; use crate::{ shared::arinc429::Arinc429Word, simulation::{ test::{SimulationTestBed, TestBed}, - Aircraft, SimulationElementVisitor, SimulatorWriter, UpdateContext, Write, + Aircraft, SimulationElementVisitor, SimulatorWriter, UpdateContext, }, }; use ntest::{assert_about_eq, timeout}; @@ -953,10 +1039,10 @@ mod tests { overhead: AirDataInertialReferenceSystemOverheadPanel, } impl TestAircraft { - fn new() -> Self { + fn new(context: &mut InitContext) -> Self { Self { - adirs: AirDataInertialReferenceSystem::new(), - overhead: AirDataInertialReferenceSystemOverheadPanel::new(), + adirs: AirDataInertialReferenceSystem::new(context), + overhead: AirDataInertialReferenceSystemOverheadPanel::new(context), } } } @@ -985,7 +1071,7 @@ mod tests { impl AdirsTestBed { fn new() -> Self { let mut adirs_test_bed = Self { - test_bed: SimulationTestBed::new(|_| TestAircraft::new()), + test_bed: SimulationTestBed::new(TestAircraft::new), }; adirs_test_bed.move_all_mode_selectors_to(InertialReferenceMode::Navigation); @@ -1009,22 +1095,22 @@ mod tests { } fn latitude_of(mut self, latitude: Angle) -> Self { - self.write(AdirsSimulatorData::LATITUDE, latitude); + self.write_by_name(AdirsSimulatorData::LATITUDE, latitude); self } fn longitude_of(mut self, longitude: Angle) -> Self { - self.write(AdirsSimulatorData::LONGITUDE, longitude); + self.write_by_name(AdirsSimulatorData::LONGITUDE, longitude); self } fn mach_of(mut self, mach: MachNumber) -> Self { - self.write(AdirsSimulatorData::MACH, mach); + self.write_by_name(AdirsSimulatorData::MACH, mach); self } fn vertical_speed_of(mut self, velocity: Velocity) -> Self { - self.write( + self.write_by_name( AdirsSimulatorData::VERTICAL_SPEED, velocity.get::(), ); @@ -1032,48 +1118,48 @@ mod tests { } fn true_airspeed_of(mut self, velocity: Velocity) -> Self { - self.write(AdirsSimulatorData::TRUE_AIRSPEED, velocity); + self.write_by_name(AdirsSimulatorData::TRUE_AIRSPEED, velocity); self } fn total_air_temperature_of(mut self, temperature: ThermodynamicTemperature) -> Self { - self.write(AdirsSimulatorData::TOTAL_AIR_TEMPERATURE, temperature); + self.write_by_name(AdirsSimulatorData::TOTAL_AIR_TEMPERATURE, temperature); self } fn pitch_of(mut self, angle: Angle) -> Self { - self.write(AdirsSimulatorData::PITCH, angle); + self.write_by_name(AdirsSimulatorData::PITCH, angle); self } fn roll_of(mut self, angle: Angle) -> Self { - self.write(AdirsSimulatorData::ROLL, angle); + self.write_by_name(AdirsSimulatorData::ROLL, angle); self } fn heading_of(mut self, angle: Angle) -> Self { - self.write(AdirsSimulatorData::HEADING, angle); + self.write_by_name(AdirsSimulatorData::HEADING, angle); self } fn track_of(mut self, angle: Angle) -> Self { - self.write(AdirsSimulatorData::TRACK, angle); + self.write_by_name(AdirsSimulatorData::TRACK, angle); self } fn ground_speed_of(mut self, velocity: Velocity) -> Self { - self.write(AdirsSimulatorData::GROUND_SPEED, velocity); + self.write_by_name(AdirsSimulatorData::GROUND_SPEED, velocity); self } fn wind_of(mut self, angle: Angle, velocity: Velocity) -> Self { - self.write(AdirsSimulatorData::WIND_DIRECTION, angle); - self.write(AdirsSimulatorData::WIND_VELOCITY, velocity); + self.write_by_name(AdirsSimulatorData::WIND_DIRECTION, angle); + self.write_by_name(AdirsSimulatorData::WIND_VELOCITY, velocity); self } fn align_time_configured_as(mut self, align_time: AlignTime) -> Self { - Write::::write( + WriteByName::::write_by_name( &mut self, AirDataInertialReferenceSystem::CONFIGURED_ALIGN_TIME_KEY, align_time.into(), @@ -1082,7 +1168,7 @@ mod tests { } fn ir_mode_selector_set_to(mut self, number: usize, mode: InertialReferenceMode) -> Self { - Write::::write( + WriteByName::::write_by_name( &mut self, &InertialReferenceModeSelector::mode_id(number), mode.into(), @@ -1091,7 +1177,7 @@ mod tests { } fn adr_push_button_off(mut self, number: usize) -> Self { - self.write( + self.write_by_name( &OnOffFaultPushButton::is_on_id(&format!("ADIRS_ADR_{}", number)), false, ); @@ -1100,7 +1186,7 @@ mod tests { } fn ir_push_button_off(mut self, number: usize) -> Self { - self.write( + self.write_by_name( &OnOffFaultPushButton::is_on_id(&format!("ADIRS_IR_{}", number)), false, ); @@ -1109,7 +1195,7 @@ mod tests { } fn ir_fault_light_illuminated(&mut self, number: usize) -> bool { - self.read(&OnOffFaultPushButton::has_fault_id(&format!( + self.read_by_name(&OnOffFaultPushButton::has_fault_id(&format!( "ADIRS_IR_{}", number ))) @@ -1124,11 +1210,11 @@ mod tests { } fn align_state(&mut self, adiru_number: usize) -> AlignState { - self.read(&AirDataInertialReferenceUnit::state_id(adiru_number)) + self.read_by_name(&AirDataInertialReferenceUnit::state_id(adiru_number)) } fn remaining_alignment_time(&mut self) -> Duration { - self.read(AirDataInertialReferenceSystem::REMAINING_ALIGNMENT_TIME_KEY) + self.read_by_name(AirDataInertialReferenceSystem::REMAINING_ALIGNMENT_TIME_KEY) } fn all_mode_selectors_off(mut self) -> Self { @@ -1139,18 +1225,18 @@ mod tests { fn move_all_mode_selectors_to(&mut self, mode: InertialReferenceMode) { for number in 1..=3 { - self.write(&InertialReferenceModeSelector::mode_id(number), mode); + self.write_by_name(&InertialReferenceModeSelector::mode_id(number), mode); } } fn on_bat_light_illuminated(&mut self) -> bool { - self.read(&IndicationLight::is_illuminated_id( + self.read_by_name(&IndicationLight::is_illuminated_id( AirDataInertialReferenceSystemOverheadPanel::ADIRS_ON_BAT_NAME, )) } fn altitude(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::ALTITUDE, @@ -1158,7 +1244,7 @@ mod tests { } fn computed_airspeed(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::COMPUTED_AIRSPEED, @@ -1166,7 +1252,7 @@ mod tests { } fn mach(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::MACH, @@ -1174,7 +1260,7 @@ mod tests { } fn barometric_vertical_speed(&mut self, adiru_number: usize) -> Arinc429Word { - let vertical_speed: Arinc429Word = self.read_arinc429(&output_data_id( + let vertical_speed: Arinc429Word = self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::BAROMETRIC_VERTICAL_SPEED, @@ -1186,7 +1272,7 @@ mod tests { } fn true_airspeed(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::TRUE_AIRSPEED, @@ -1197,7 +1283,7 @@ mod tests { &mut self, adiru_number: usize, ) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::STATIC_AIR_TEMPERATURE, @@ -1208,7 +1294,7 @@ mod tests { &mut self, adiru_number: usize, ) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::TOTAL_AIR_TEMPERATURE, @@ -1219,7 +1305,7 @@ mod tests { &mut self, adiru_number: usize, ) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::ADR, adiru_number, AirDataReference::INTERNATIONAL_STANDARD_ATMOSPHERE_DELTA, @@ -1227,7 +1313,7 @@ mod tests { } fn pitch(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::PITCH, @@ -1235,7 +1321,7 @@ mod tests { } fn roll(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::ROLL, @@ -1243,7 +1329,7 @@ mod tests { } fn heading(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::HEADING, @@ -1251,7 +1337,7 @@ mod tests { } fn track(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::TRACK, @@ -1259,7 +1345,7 @@ mod tests { } fn ground_speed(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::GROUND_SPEED, @@ -1267,7 +1353,7 @@ mod tests { } fn wind_direction(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::WIND_DIRECTION, @@ -1275,7 +1361,7 @@ mod tests { } fn wind_velocity(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::WIND_VELOCITY, @@ -1283,7 +1369,7 @@ mod tests { } fn inertial_vertical_speed(&mut self, adiru_number: usize) -> Arinc429Word { - let vertical_speed: Arinc429Word = self.read_arinc429(&output_data_id( + let vertical_speed: Arinc429Word = self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::VERTICAL_SPEED, @@ -1295,7 +1381,7 @@ mod tests { } fn longitude(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::LONGITUDE, @@ -1303,7 +1389,7 @@ mod tests { } fn latitude(&mut self, adiru_number: usize) -> Arinc429Word { - self.read_arinc429(&output_data_id( + self.read_arinc429_by_name(&output_data_id( OutputDataType::IR, adiru_number, InertialReference::LATITUDE, @@ -1311,7 +1397,7 @@ mod tests { } fn uses_gps_as_primary(&mut self) -> bool { - self.read(AirDataInertialReferenceSystem::USES_GPS_AS_PRIMARY_KEY) + self.read_by_name(AirDataInertialReferenceSystem::USES_GPS_AS_PRIMARY_KEY) } fn assert_adr_data_available(&mut self, available: bool, adiru_number: usize) { diff --git a/src/systems/systems/src/overhead/mod.rs b/src/systems/systems/src/overhead/mod.rs index 075ec25205e..74d9811aad7 100644 --- a/src/systems/systems/src/overhead/mod.rs +++ b/src/systems/systems/src/overhead/mod.rs @@ -1,25 +1,28 @@ -use crate::simulation::{Read, SimulationElement, SimulatorReader, SimulatorWriter, Write}; +use crate::simulation::{ + InitContext, Read, SimulationElement, SimulatorReader, SimulatorWriter, VariableIdentifier, + Write, +}; pub struct OnOffFaultPushButton { - is_on_id: String, - has_fault_id: String, + is_on_id: VariableIdentifier, + has_fault_id: VariableIdentifier, is_on: bool, has_fault: bool, } impl OnOffFaultPushButton { - pub fn new_on(name: &str) -> Self { - Self::new(name, true) + pub fn new_on(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_off(name: &str) -> Self { - Self::new(name, false) + pub fn new_off(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_on: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_on: bool) -> Self { Self { - is_on_id: format!("OVHD_{}_PB_IS_ON", name), - has_fault_id: format!("OVHD_{}_PB_HAS_FAULT", name), + is_on_id: context.get_identifier(format!("OVHD_{}_PB_IS_ON", name)), + has_fault_id: context.get_identifier(format!("OVHD_{}_PB_HAS_FAULT", name)), is_on, has_fault: false, } @@ -74,25 +77,25 @@ impl SimulationElement for OnOffFaultPushButton { } pub struct OnOffAvailablePushButton { - is_on_id: String, - is_available_id: String, + is_on_id: VariableIdentifier, + is_available_id: VariableIdentifier, is_on: bool, is_available: bool, } impl OnOffAvailablePushButton { - pub fn new_on(name: &str) -> Self { - Self::new(name, true) + pub fn new_on(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_off(name: &str) -> Self { - Self::new(name, false) + pub fn new_off(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_on: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_on: bool) -> Self { Self { - is_on_id: format!("OVHD_{}_PB_IS_ON", name), - is_available_id: format!("OVHD_{}_PB_IS_AVAILABLE", name), + is_on_id: context.get_identifier(format!("OVHD_{}_PB_IS_ON", name)), + is_available_id: context.get_identifier(format!("OVHD_{}_PB_IS_AVAILABLE", name)), is_on, is_available: false, } @@ -139,25 +142,25 @@ impl SimulationElement for OnOffAvailablePushButton { } pub struct NormalAltnFaultPushButton { - is_normal_id: String, - has_fault_id: String, + is_normal_id: VariableIdentifier, + has_fault_id: VariableIdentifier, is_normal: bool, has_fault: bool, } impl NormalAltnFaultPushButton { - pub fn new_normal(name: &str) -> Self { - Self::new(name, true) + pub fn new_normal(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_altn(name: &str) -> Self { - Self::new(name, false) + pub fn new_altn(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_normal: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_normal: bool) -> Self { Self { - is_normal_id: format!("OVHD_{}_PB_IS_NORMAL", name), - has_fault_id: format!("OVHD_{}_PB_HAS_FAULT", name), + is_normal_id: context.get_identifier(format!("OVHD_{}_PB_IS_NORMAL", name)), + has_fault_id: context.get_identifier(format!("OVHD_{}_PB_HAS_FAULT", name)), is_normal, has_fault: false, } @@ -200,25 +203,25 @@ impl SimulationElement for NormalAltnFaultPushButton { } pub struct AutoOffFaultPushButton { - is_auto_id: String, - has_fault_id: String, + is_auto_id: VariableIdentifier, + has_fault_id: VariableIdentifier, is_auto: bool, has_fault: bool, } impl AutoOffFaultPushButton { - pub fn new_auto(name: &str) -> Self { - Self::new(name, true) + pub fn new_auto(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_off(name: &str) -> Self { - Self::new(name, false) + pub fn new_off(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_auto: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_auto: bool) -> Self { Self { - is_auto_id: format!("OVHD_{}_PB_IS_AUTO", name), - has_fault_id: format!("OVHD_{}_PB_HAS_FAULT", name), + is_auto_id: context.get_identifier(format!("OVHD_{}_PB_IS_AUTO", name)), + has_fault_id: context.get_identifier(format!("OVHD_{}_PB_HAS_FAULT", name)), is_auto, has_fault: false, } @@ -265,25 +268,25 @@ impl SimulationElement for AutoOffFaultPushButton { } pub struct AutoOnFaultPushButton { - is_auto_id: String, - has_fault_id: String, + is_auto_id: VariableIdentifier, + has_fault_id: VariableIdentifier, is_auto: bool, has_fault: bool, } impl AutoOnFaultPushButton { - pub fn new_auto(name: &str) -> Self { - Self::new(name, true) + pub fn new_auto(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_on(name: &str) -> Self { - Self::new(name, false) + pub fn new_on(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_auto: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_auto: bool) -> Self { Self { - is_auto_id: format!("OVHD_{}_PB_IS_AUTO", name), - has_fault_id: format!("OVHD_{}_PB_HAS_FAULT", name), + is_auto_id: context.get_identifier(format!("OVHD_{}_PB_IS_AUTO", name)), + has_fault_id: context.get_identifier(format!("OVHD_{}_PB_HAS_FAULT", name)), is_auto, has_fault: false, } @@ -330,25 +333,25 @@ impl SimulationElement for AutoOnFaultPushButton { } pub struct FaultReleasePushButton { - is_released_id: String, - has_fault_id: String, + is_released_id: VariableIdentifier, + has_fault_id: VariableIdentifier, is_released: bool, has_fault: bool, } impl FaultReleasePushButton { #[cfg(test)] - pub fn new_released(name: &str) -> Self { - Self::new(name, true) + pub fn new_released(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, true) } - pub fn new_in(name: &str) -> Self { - Self::new(name, false) + pub fn new_in(context: &mut InitContext, name: &str) -> Self { + Self::new(context, name, false) } - fn new(name: &str, is_released: bool) -> Self { + fn new(context: &mut InitContext, name: &str, is_released: bool) -> Self { Self { - is_released_id: format!("OVHD_{}_PB_IS_RELEASED", name), - has_fault_id: format!("OVHD_{}_PB_HAS_FAULT", name), + is_released_id: context.get_identifier(format!("OVHD_{}_PB_IS_RELEASED", name)), + has_fault_id: context.get_identifier(format!("OVHD_{}_PB_HAS_FAULT", name)), is_released, has_fault: false, } @@ -383,18 +386,18 @@ impl SimulationElement for FaultReleasePushButton { } pub struct FirePushButton { - is_released_id: String, + is_released_id: VariableIdentifier, is_released: bool, } impl FirePushButton { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - is_released_id: format!("FIRE_BUTTON_{}", name), + is_released_id: context.get_identifier(format!("FIRE_BUTTON_{}", name)), is_released: false, } } - pub fn set(&mut self, released: bool) { + pub fn set_released(&mut self, released: bool) { self.is_released = self.is_released || released; } @@ -408,18 +411,18 @@ impl SimulationElement for FirePushButton { } fn read(&mut self, reader: &mut SimulatorReader) { - self.set(reader.read(&self.is_released_id)); + self.set_released(reader.read(&self.is_released_id)); } } pub struct FaultIndication { - has_fault_id: String, + has_fault_id: VariableIdentifier, has_fault: bool, } impl FaultIndication { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - has_fault_id: format!("OVHD_{}_HAS_FAULT", name), + has_fault_id: context.get_identifier(format!("OVHD_{}_HAS_FAULT", name)), has_fault: false, } } @@ -435,13 +438,13 @@ impl SimulationElement for FaultIndication { } pub struct MomentaryPushButton { - is_pressed_id: String, + is_pressed_id: VariableIdentifier, is_pressed: bool, } impl MomentaryPushButton { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - is_pressed_id: format!("OVHD_{}_IS_PRESSED", name), + is_pressed_id: context.get_identifier(format!("OVHD_{}_IS_PRESSED", name)), is_pressed: false, } } @@ -458,14 +461,14 @@ impl SimulationElement for MomentaryPushButton { /// Same implementation as MomentaryPushButton but is only "pressed" for one update even if kept pressed pub struct PressSingleSignalButton { - is_pressed_id: String, + is_pressed_id: VariableIdentifier, is_pressed: bool, last_pressed_state: bool, } impl PressSingleSignalButton { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - is_pressed_id: format!("OVHD_{}_IS_PRESSED", name), + is_pressed_id: context.get_identifier(format!("OVHD_{}_IS_PRESSED", name)), is_pressed: false, last_pressed_state: false, } @@ -484,17 +487,17 @@ impl SimulationElement for PressSingleSignalButton { } pub struct MomentaryOnPushButton { - is_pressed_id: String, - is_on_id: String, + is_pressed_id: VariableIdentifier, + is_on_id: VariableIdentifier, is_pressed: bool, last_pressed_state: bool, is_on: bool, } impl MomentaryOnPushButton { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - is_pressed_id: format!("OVHD_{}_IS_PRESSED", name), - is_on_id: format!("OVHD_{}_IS_ON", name), + is_pressed_id: context.get_identifier(format!("OVHD_{}_IS_PRESSED", name)), + is_on_id: context.get_identifier(format!("OVHD_{}_IS_ON", name)), is_pressed: false, last_pressed_state: false, is_on: false, @@ -535,13 +538,13 @@ impl SimulationElement for MomentaryOnPushButton { } pub struct IndicationLight { - is_illuminated_id: String, + is_illuminated_id: VariableIdentifier, is_illuminated: bool, } impl IndicationLight { - pub fn new(name: &str) -> Self { + pub fn new(context: &mut InitContext, name: &str) -> Self { Self { - is_illuminated_id: Self::is_illuminated_id(name), + is_illuminated_id: context.get_identifier(Self::is_illuminated_id(name)), is_illuminated: false, } } @@ -562,240 +565,310 @@ impl SimulationElement for IndicationLight { #[cfg(test)] mod on_off_fault_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_on_push_button_is_on() { - assert!(OnOffFaultPushButton::new_on("BUTTON").is_on()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffFaultPushButton::new_on(context, "BUTTON") + })); + + assert!(test_bed.query_element(|e| e.is_on())); } #[test] fn new_off_push_button_is_off() { - assert!(OnOffFaultPushButton::new_off("BUTTON").is_off()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffFaultPushButton::new_off(context, "BUTTON") + })); + + assert!(test_bed.query_element(|e| e.is_off())); } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(OnOffFaultPushButton::new_on("ELEC_GEN_1")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffFaultPushButton::new_on(context, "ELEC_GEN_1") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_ELEC_GEN_1_PB_IS_ON")); - assert!(test_bed.contains_key("OVHD_ELEC_GEN_1_PB_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_GEN_1_PB_IS_ON")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_GEN_1_PB_HAS_FAULT")); } } #[cfg(test)] mod on_off_available_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_on_push_button_is_on() { - assert!(OnOffAvailablePushButton::new_on("BUTTON").is_on()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffAvailablePushButton::new_on(context, "BUTTON") + })); + + assert!(test_bed.query_element(|e| e.is_on())); } #[test] fn new_off_push_button_is_off() { - assert!(OnOffAvailablePushButton::new_off("BUTTON").is_off()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffAvailablePushButton::new_off(context, "BUTTON") + })); + + assert!(test_bed.query_element(|e| e.is_off())); } #[test] fn writes_its_state() { - let mut test_bed = - SimulationTestBed::from(OnOffAvailablePushButton::new_on("ELEC_EXT_PWR")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + OnOffAvailablePushButton::new_on(context, "ELEC_EXT_PWR") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_ELEC_EXT_PWR_PB_IS_ON")); - assert!(test_bed.contains_key("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_EXT_PWR_PB_IS_ON")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE")); } } #[cfg(test)] mod normal_altn_fault_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_normal_push_button_is_normal() { - assert!(NormalAltnFaultPushButton::new_normal("TEST").is_normal()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + NormalAltnFaultPushButton::new_normal(context, "TEST") + })); + + assert!(test_bed.query_element(|e| e.is_normal())); } #[test] fn new_altn_push_button_is_altn() { - assert!(NormalAltnFaultPushButton::new_altn("TEST").is_altn()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + NormalAltnFaultPushButton::new_altn(context, "TEST") + })); + + assert!(test_bed.query_element(|e| e.is_altn())); } #[test] fn writes_its_state() { - let mut test_bed = - SimulationTestBed::from(NormalAltnFaultPushButton::new_normal("ELEC_AC_ESS_FEED")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + NormalAltnFaultPushButton::new_normal(context, "ELEC_AC_ESS_FEED") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_ELEC_AC_ESS_FEED_PB_IS_NORMAL")); - assert!(test_bed.contains_key("OVHD_ELEC_AC_ESS_FEED_PB_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_AC_ESS_FEED_PB_IS_NORMAL")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_AC_ESS_FEED_PB_HAS_FAULT")); } } #[cfg(test)] mod auto_off_fault_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_auto_push_button_is_auto() { - assert!(AutoOffFaultPushButton::new_auto("TEST").is_auto()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + AutoOffFaultPushButton::new_auto(context, "TEST") + })); + + assert!(test_bed.query_element(|e| e.is_auto())); } #[test] fn new_off_push_button_is_off() { - assert!(AutoOffFaultPushButton::new_off("TEST").is_off()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + AutoOffFaultPushButton::new_off(context, "TEST") + })); + + assert!(test_bed.query_element(|e| e.is_off())); } #[test] fn writes_its_state() { - let mut test_bed = - SimulationTestBed::from(AutoOffFaultPushButton::new_auto("ELEC_BUS_TIE")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + AutoOffFaultPushButton::new_auto(context, "ELEC_BUS_TIE") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_ELEC_BUS_TIE_PB_IS_AUTO")); - assert!(test_bed.contains_key("OVHD_ELEC_BUS_TIE_PB_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_BUS_TIE_PB_IS_AUTO")); + assert!(test_bed.contains_variable_with_name("OVHD_ELEC_BUS_TIE_PB_HAS_FAULT")); } } #[cfg(test)] mod fault_release_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_in_is_not_released() { - let pb = FaultReleasePushButton::new_in("TEST"); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultReleasePushButton::new_in(context, "TEST") + })); - assert_eq!(pb.is_released(), false); + assert!(test_bed.query_element(|e| !e.is_released())); } #[test] fn new_released_is_released() { - let pb = FaultReleasePushButton::new_released("TEST"); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultReleasePushButton::new_released(context, "TEST") + })); - assert_eq!(pb.is_released(), true); + assert!(test_bed.query_element(|e| e.is_released())); } #[test] fn when_set_as_released_is_released() { - let mut pb = FaultReleasePushButton::new_in("TEST"); - pb.set_released(true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultReleasePushButton::new_released(context, "TEST") + })); + + test_bed.command_element(|e| e.set_released(true)); - assert_eq!(pb.is_released(), true); + assert!(test_bed.query_element(|e| e.is_released())); } #[test] fn once_released_stays_released() { - let mut pb = FaultReleasePushButton::new_in("TEST"); - pb.set_released(true); - pb.set_released(false); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultReleasePushButton::new_released(context, "TEST") + })); + + test_bed.command_element(|e| e.set_released(true)); + test_bed.command_element(|e| e.set_released(false)); - assert_eq!(pb.is_released(), true); + assert!(test_bed.query_element(|e| e.is_released())); } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(FaultReleasePushButton::new_in("IDG_1")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultReleasePushButton::new_in(context, "IDG_1") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_IDG_1_PB_IS_RELEASED")); - assert!(test_bed.contains_key("OVHD_IDG_1_PB_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_IDG_1_PB_IS_RELEASED")); + assert!(test_bed.contains_variable_with_name("OVHD_IDG_1_PB_HAS_FAULT")); } } #[cfg(test)] mod fire_push_button_tests { - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; use super::*; #[test] fn new_fire_push_button_is_not_released() { - let pb = FirePushButton::new("TEST"); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FirePushButton::new(context, "TEST") + })); - assert_eq!(pb.is_released(), false); + assert!(test_bed.query_element(|e| !e.is_released())); } #[test] fn when_set_as_released_is_released() { - let mut pb = FirePushButton::new("TEST"); - pb.set(true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FirePushButton::new(context, "TEST") + })); - assert_eq!(pb.is_released(), true); + test_bed.command_element(|e| e.set_released(true)); + + assert!(test_bed.query_element(|e| e.is_released())); } #[test] fn once_released_stays_released() { - let mut pb = FirePushButton::new("TEST"); - pb.set(true); - pb.set(false); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FirePushButton::new(context, "TEST") + })); + + test_bed.command_element(|e| e.set_released(true)); + test_bed.command_element(|e| e.set_released(false)); - assert_eq!(pb.is_released(), true); + assert!(test_bed.query_element(|e| e.is_released())); } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(FirePushButton::new("APU")); + let mut test_bed = + SimulationTestBed::from(ElementCtorFn(|context| FirePushButton::new(context, "APU"))); test_bed.run(); - assert!(test_bed.contains_key("FIRE_BUTTON_APU")); + assert!(test_bed.contains_variable_with_name("FIRE_BUTTON_APU")); } } #[cfg(test)] mod fault_indication_tests { use super::*; - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed}; #[test] fn new_does_not_have_fault() { - assert!(!FaultIndication::new("TEST").has_fault); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultIndication::new(context, "TEST") + })); + + assert!(test_bed.query_element(|e| !e.has_fault)); } #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(FaultIndication::new("TEST")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + FaultIndication::new(context, "TEST") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_TEST_HAS_FAULT")); + assert!(test_bed.contains_variable_with_name("OVHD_TEST_HAS_FAULT")); } } #[cfg(test)] mod momentary_push_button_tests { use super::*; - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed, WriteByName}; #[test] fn new_is_not_pressed() { - assert!(!MomentaryPushButton::new("TEST").is_pressed()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryPushButton::new(context, "TEST") + })); + + assert!(test_bed.query_element(|e| !e.is_pressed())); } #[test] fn reads_its_state() { - let mut test_bed = SimulationTestBed::from(MomentaryPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryPushButton::new(context, "TEST") + })); - test_bed.run(); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); + test_bed.run(); assert!(test_bed.query_element(|e| e.is_pressed())); } } @@ -803,27 +876,36 @@ mod momentary_push_button_tests { #[cfg(test)] mod momentary_on_push_button_tests { use super::*; - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed, WriteByName}; #[test] fn new_is_not_pressed() { - assert!(!MomentaryOnPushButton::new("TEST").is_pressed()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); + + assert!(test_bed.query_element(|e| !e.is_pressed())); } #[test] fn reads_its_state() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); - test_bed.run(); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); - assert!(test_bed.query_element(|button| button.is_pressed())); + test_bed.run(); + assert!(test_bed.query_element(|e| e.is_pressed())); } #[test] fn stays_on_while_kept_pressed() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); + + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(test_bed.query_element(|button| button.is_on())); @@ -834,8 +916,11 @@ mod momentary_on_push_button_tests { #[test] fn can_be_forced_off() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); + + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(test_bed.query_element(|button| button.is_on())); @@ -848,18 +933,20 @@ mod momentary_on_push_button_tests { #[test] fn remains_off_when_forced_off() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); test_bed.command_element(|button| button.turn_off()); assert!(!test_bed.query_element(|button| button.is_on())); - test_bed.write("OVHD_TEST_IS_PRESSED", false); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", false); test_bed.run(); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); test_bed.command_element(|button| button.turn_off()); @@ -868,23 +955,25 @@ mod momentary_on_push_button_tests { #[test] fn can_press_on_and_off() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(test_bed.query_element(|button| button.is_on())); - test_bed.write("OVHD_TEST_IS_PRESSED", false); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", false); test_bed.run(); assert!(test_bed.query_element(|button| button.is_on())); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(!test_bed.query_element(|button| button.is_on())); - test_bed.write("OVHD_TEST_IS_PRESSED", false); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", false); test_bed.run(); assert!(!test_bed.query_element(|button| button.is_on())); @@ -892,28 +981,36 @@ mod momentary_on_push_button_tests { #[test] fn writes_its_state() { - let mut test_bed = SimulationTestBed::from(MomentaryOnPushButton::new("TEST")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + MomentaryOnPushButton::new(context, "TEST") + })); test_bed.run(); - assert!(test_bed.contains_key("OVHD_TEST_IS_ON")); + assert!(test_bed.contains_variable_with_name("OVHD_TEST_IS_ON")); } } #[cfg(test)] -mod momentary_rising_edge_push_button_tests { +mod press_single_signal_button_tests { use super::*; - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, SimulationTestBed, TestBed, WriteByName}; #[test] fn new_is_not_pressed() { - assert!(!PressSingleSignalButton::new("TEST").is_pressed()); + let test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + PressSingleSignalButton::new(context, "TEST") + })); + + assert!(test_bed.query_element(|e| !e.is_pressed())); } #[test] fn reads_its_state() { - let mut test_bed = SimulationTestBed::from(PressSingleSignalButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + PressSingleSignalButton::new(context, "TEST") + })); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); @@ -922,8 +1019,10 @@ mod momentary_rising_edge_push_button_tests { #[test] fn can_be_pressed() { - let mut test_bed = SimulationTestBed::from(PressSingleSignalButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + PressSingleSignalButton::new(context, "TEST") + })); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(test_bed.query_element(|button| button.is_pressed())); @@ -931,8 +1030,10 @@ mod momentary_rising_edge_push_button_tests { #[test] fn is_only_pressed_for_one_update() { - let mut test_bed = SimulationTestBed::from(PressSingleSignalButton::new("TEST")); - test_bed.write("OVHD_TEST_IS_PRESSED", true); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + PressSingleSignalButton::new(context, "TEST") + })); + test_bed.write_by_name("OVHD_TEST_IS_PRESSED", true); test_bed.run(); assert!(test_bed.query_element(|button| button.is_pressed())); @@ -948,15 +1049,18 @@ mod momentary_rising_edge_push_button_tests { #[cfg(test)] mod indication_light_tests { use super::*; - use crate::simulation::test::{SimulationTestBed, TestBed}; + use crate::simulation::test::{ElementCtorFn, ReadByName, SimulationTestBed, TestBed}; use rstest::rstest; #[test] fn new_is_not_illuminated() { - let mut test_bed = SimulationTestBed::from(IndicationLight::new("TEST")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + IndicationLight::new(context, "TEST") + })); test_bed.run(); - let is_illuminated: bool = test_bed.read(&IndicationLight::is_illuminated_id("TEST")); + let is_illuminated: bool = + test_bed.read_by_name(&IndicationLight::is_illuminated_id("TEST")); assert!(!is_illuminated); } @@ -967,11 +1071,14 @@ mod indication_light_tests { #[case] set_illuminated: bool, #[case] expected: bool, ) { - let mut test_bed = SimulationTestBed::from(IndicationLight::new("TEST")); + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + IndicationLight::new(context, "TEST") + })); test_bed.command_element(|light| light.set_illuminated(set_illuminated)); test_bed.run(); - let is_illuminated: bool = test_bed.read(&IndicationLight::is_illuminated_id("TEST")); + let is_illuminated: bool = + test_bed.read_by_name(&IndicationLight::is_illuminated_id("TEST")); assert_eq!(is_illuminated, expected); } } diff --git a/src/systems/systems/src/pressurization/mod.rs b/src/systems/systems/src/pressurization/mod.rs index 8bc0de42fac..b0a4226a5c7 100644 --- a/src/systems/systems/src/pressurization/mod.rs +++ b/src/systems/systems/src/pressurization/mod.rs @@ -1,4 +1,5 @@ use self::{cabin_pressure_controller::CabinPressureController, pressure_valve::PressureValve}; +use crate::simulation::{InitContext, VariableIdentifier}; use crate::{ shared::{random_number, EngineCorrectedN1}, simulation::{Read, SimulationElement, SimulatorReader, SimulatorWriter, UpdateContext, Write}, @@ -13,6 +14,15 @@ trait PressureValveActuator { } pub struct Pressurization { + active_cpc_sys_id: VariableIdentifier, + cabin_altitude_id: VariableIdentifier, + cabin_vs_id: VariableIdentifier, + cabin_delta_pressure_id: VariableIdentifier, + outflow_valve_open_percentage_id: VariableIdentifier, + auto_landing_elevation_id: VariableIdentifier, + sea_level_pressure_id: VariableIdentifier, + destination_qnh_id: VariableIdentifier, + cpc: [CabinPressureController; 2], outflow_valve: PressureValve, active_system: usize, @@ -22,7 +32,7 @@ pub struct Pressurization { } impl Pressurization { - pub fn new() -> Self { + pub fn new(context: &mut InitContext) -> Self { let random = random_number(); let mut active: usize = 1; if random % 2 == 0 { @@ -30,6 +40,18 @@ impl Pressurization { } Self { + active_cpc_sys_id: context.get_identifier("PRESS_ACTIVE_CPC_SYS".to_owned()), + cabin_altitude_id: context.get_identifier("PRESS_CABIN_ALTITUDE".to_owned()), + cabin_vs_id: context.get_identifier("PRESS_CABIN_VS".to_owned()), + cabin_delta_pressure_id: context + .get_identifier("PRESS_CABIN_DELTA_PRESSURE".to_owned()), + outflow_valve_open_percentage_id: context + .get_identifier("PRESS_OUTFLOW_VALVE_OPEN_PERCENTAGE".to_owned()), + auto_landing_elevation_id: context + .get_identifier("PRESS_AUTO_LANDING_ELEVATION".to_owned()), + sea_level_pressure_id: context.get_identifier("SEA LEVEL PRESSURE".to_owned()), + destination_qnh_id: context.get_identifier("DESTINATION_QNH".to_owned()), + cpc: [CabinPressureController::new(); 2], outflow_valve: PressureValve::new(), active_system: active, @@ -72,37 +94,32 @@ impl Pressurization { impl SimulationElement for Pressurization { fn write(&self, writer: &mut SimulatorWriter) { - writer.write("PRESS_ACTIVE_CPC_SYS", self.active_system); + writer.write(&self.active_cpc_sys_id, self.active_system); writer.write( - "PRESS_CABIN_ALTITUDE", + &self.cabin_altitude_id, self.cpc[self.active_system - 1].cabin_altitude(), ); writer.write( - "PRESS_CABIN_VS", + &self.cabin_vs_id, self.cpc[self.active_system - 1] .cabin_vs() .get::(), ); writer.write( - "PRESS_CABIN_DELTA_PRESSURE", + &self.cabin_delta_pressure_id, self.cpc[self.active_system - 1].cabin_delta_p(), ); writer.write( - "PRESS_OUTFLOW_VALVE_OPEN_PERCENTAGE", + &self.outflow_valve_open_percentage_id, self.outflow_valve.open_amount(), ); } fn read(&mut self, reader: &mut SimulatorReader) { - self.landing_elevation = reader.read("PRESS_AUTO_LANDING_ELEVATION"); - self.sea_level_pressure = Pressure::new::(reader.read("SEA LEVEL PRESSURE")); - self.destination_qnh = Pressure::new::(reader.read("DESTINATION_QNH")); - } -} - -impl Default for Pressurization { - fn default() -> Self { - Self::new() + self.landing_elevation = reader.read(&self.auto_landing_elevation_id); + self.sea_level_pressure = + Pressure::new::(reader.read(&self.sea_level_pressure_id)); + self.destination_qnh = Pressure::new::(reader.read(&self.destination_qnh_id)); } } @@ -146,8 +163,8 @@ mod tests { } impl TestAircraft { - fn new() -> Self { - let mut press = Pressurization::new(); + fn new(context: &mut InitContext) -> Self { + let mut press = Pressurization::new(context); press.active_system = 1; Self { @@ -173,7 +190,7 @@ mod tests { #[test] fn conversion_from_pressure_to_altitude_works() { - let mut test_bed = SimulationTestBed::new(|_| TestAircraft::new()); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); //Equivalent to FL340 from tables test_bed.set_ambient_pressure(Pressure::new::(250.)); @@ -191,7 +208,7 @@ mod tests { #[test] fn positive_cabin_vs_reduces_cabin_pressure() { - let mut test_bed = SimulationTestBed::new(|_| TestAircraft::new()); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.set_indicated_airspeed(Velocity::new::(101.)); test_bed.set_vertical_speed(Velocity::new::(1000.)); @@ -204,7 +221,7 @@ mod tests { #[test] fn seventy_seconds_after_landing_cpc_switches() { - let mut test_bed = SimulationTestBed::new(|_| TestAircraft::new()); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run(); test_bed.run_with_delta(Duration::from_secs_f64(31.)); @@ -238,7 +255,7 @@ mod tests { #[test] fn fifty_five_seconds_after_landing_outflow_valve_opens() { - let mut test_bed = SimulationTestBed::new(|_| TestAircraft::new()); + let mut test_bed = SimulationTestBed::new(TestAircraft::new); test_bed.run(); test_bed.run_with_delta(Duration::from_secs_f64(31.)); diff --git a/src/systems/systems/src/simulation/mod.rs b/src/systems/systems/src/simulation/mod.rs index d7e6967810f..96986dd860b 100644 --- a/src/systems/systems/src/simulation/mod.rs +++ b/src/systems/systems/src/simulation/mod.rs @@ -1,6 +1,8 @@ use std::time::Duration; mod update_context; +use crate::electrical::{ElectricalElementIdentifier, ElectricalElementIdentifierProvider}; +use crate::shared::ElectricalBusType; use crate::{ electrical::Electricity, failures::FailureType, @@ -14,6 +16,7 @@ use uom::si::{ volume_rate::gallon_per_second, }; pub use update_context::*; + pub mod test; /// Trait for a type which can read and write simulator data. @@ -21,10 +24,79 @@ pub mod test; /// interacts with the simulator. This separation of concerns is very important /// for keeping the majority of the code unit testable. pub trait SimulatorReaderWriter { - /// Reads a variable with the given name from the simulator. - fn read(&mut self, name: &str) -> f64; - /// Writes a variable with the given name to the simulator. - fn write(&mut self, name: &str, value: f64); + /// Reads a variable with the given identifier from the simulator. + fn read(&mut self, identifier: &VariableIdentifier) -> f64; + /// Writes a variable with the given identifier to the simulator. + fn write(&mut self, identifier: &VariableIdentifier, value: f64); +} + +pub trait VariableRegistry { + fn get(&mut self, name: String) -> VariableIdentifier; +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)] +pub struct VariableIdentifier(u8, usize); + +impl VariableIdentifier { + pub fn new(identifier_type: u8) -> Self { + Self { + 0: identifier_type, + 1: 0, + } + } + + pub fn identifier_type(&self) -> u8 { + self.0 + } + + pub fn identifier_index(&self) -> usize { + self.1 + } +} + +impl VariableIdentifier { + pub fn next(&self) -> Self { + Self { + 0: self.0, + 1: self.1 + 1, + } + } +} + +pub struct InitContext<'a> { + electrical_identifier_provider: &'a mut dyn ElectricalElementIdentifierProvider, + registry: &'a mut dyn VariableRegistry, +} + +impl<'a> InitContext<'a> { + pub fn new( + electricity: &'a mut impl ElectricalElementIdentifierProvider, + registry: &'a mut impl VariableRegistry, + ) -> Self { + Self { + electrical_identifier_provider: electricity, + registry, + } + } + + pub fn get_identifier(&mut self, name: String) -> VariableIdentifier { + self.registry.get(name) + } +} + +impl<'a> ElectricalElementIdentifierProvider for InitContext<'a> { + fn next_electrical_identifier(&mut self) -> ElectricalElementIdentifier { + self.electrical_identifier_provider + .next_electrical_identifier() + } + + fn next_electrical_identifier_for_bus( + &mut self, + bus_type: ElectricalBusType, + ) -> ElectricalElementIdentifier { + self.electrical_identifier_provider + .next_electrical_identifier_for_bus(bus_type) + } } /// An [`Aircraft`] that can be simulated by the [`Simulation`]. @@ -106,13 +178,14 @@ pub trait SimulationElement { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Read}; + /// # SimulatorReader, SimulatorWriter, Read, VariableIdentifier}; /// struct MySimulationElement { /// is_on: bool, /// } /// impl SimulationElement for MySimulationElement { /// fn read(&mut self, reader: &mut SimulatorReader) { - /// self.is_on = reader.read("MY_SIMULATOR_ELEMENT_IS_ON"); + /// // The identifier would ordinarily be retrieved from the registry. + /// self.is_on = reader.read(&VariableIdentifier::default()); /// } /// } /// ``` @@ -122,13 +195,14 @@ pub trait SimulationElement { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Write}; + /// # SimulatorReader, SimulatorWriter, Write, VariableIdentifier}; /// struct MySimulationElement { /// is_on: bool, /// } /// impl SimulationElement for MySimulationElement { /// fn write(&self, writer: &mut SimulatorWriter) { - /// writer.write("MY_SIMULATOR_ELEMENT_IS_ON", self.is_on); + /// // The identifier would ordinarily be retrieved from the registry. + /// writer.write(&VariableIdentifier::default(), self.is_on); /// } /// } /// ``` @@ -203,13 +277,20 @@ pub trait SimulationElementVisitor { pub struct Simulation { aircraft: T, electricity: Electricity, + update_context: UpdateContext, } impl Simulation { - pub fn new T>(aircraft_ctor_fn: U) -> Self { + pub fn new T>( + aircraft_ctor_fn: U, + registry: &mut impl VariableRegistry, + ) -> Self { let mut electricity = Electricity::new(); + let mut context = InitContext::new(&mut electricity, registry); + let update_context = UpdateContext::new_for_simulation(&mut context); Self { - aircraft: (aircraft_ctor_fn)(&mut electricity), + aircraft: (aircraft_ctor_fn)(&mut context), electricity, + update_context, } } @@ -225,11 +306,11 @@ impl Simulation { /// Basic usage is as follows: /// ```rust /// # use std::time::Duration; - /// # use systems::electrical::Electricity; - /// # use systems::simulation::{Aircraft, SimulationElement, SimulatorReaderWriter, Simulation, UpdateContext}; + /// # use systems::simulation::{Aircraft, SimulationElement, SimulatorReaderWriter, Simulation, + /// # UpdateContext, InitContext, VariableRegistry, VariableIdentifier}; /// # struct MyAircraft {} /// # impl MyAircraft { - /// # fn new(_: &mut Electricity) -> Self { + /// # fn new(_: &mut InitContext) -> Self { /// # Self {} /// # } /// # } @@ -243,10 +324,22 @@ impl Simulation { /// # } /// # } /// # impl SimulatorReaderWriter for MySimulatorReaderWriter { - /// # fn read(&mut self, name: &str) -> f64 { 0.0 } - /// # fn write(&mut self, name: &str, value: f64) { } + /// # fn read(&mut self, identifier: &VariableIdentifier) -> f64 { 0.0 } + /// # fn write(&mut self, identifier: &VariableIdentifier, value: f64) { } + /// # } + /// # struct MyVariableRegistry {} + /// # impl MyVariableRegistry { + /// # fn new() -> Self { + /// # Self {} + /// # } /// # } - /// let mut simulation = Simulation::new(|electricity| MyAircraft::new(electricity)); + /// # impl VariableRegistry for MyVariableRegistry { + /// # fn get(&mut self, name: String) -> VariableIdentifier { + /// # Default::default() + /// # } + /// # } + /// let mut registry = MyVariableRegistry::new(); + /// let mut simulation = Simulation::new(MyAircraft::new, &mut registry); /// let mut reader_writer = MySimulatorReaderWriter::new(); /// // For each frame, call the tick function. /// simulation.tick(Duration::from_millis(50), &mut reader_writer) @@ -256,22 +349,23 @@ impl Simulation { self.electricity.pre_tick(); let mut reader = SimulatorReader::new(reader_writer); - let context = UpdateContext::from_reader(&mut reader, delta); + self.update_context.update(&mut reader, delta); let mut visitor = SimulatorToSimulationVisitor::new(&mut reader); self.aircraft.accept(&mut visitor); self.aircraft - .update_before_power_distribution(&context, &mut self.electricity); + .update_before_power_distribution(&self.update_context, &mut self.electricity); self.aircraft - .distribute_electricity(&context, &self.electricity); + .distribute_electricity(&self.update_context, &self.electricity); - self.aircraft.update_after_power_distribution(&context); self.aircraft - .consume_electricity(&context, &mut self.electricity); + .update_after_power_distribution(&self.update_context); + self.aircraft + .consume_electricity(&self.update_context, &mut self.electricity); self.aircraft - .report_electricity_consumption(&context, &self.electricity); + .report_electricity_consumption(&self.update_context, &self.electricity); let mut writer = SimulatorWriter::new(reader_writer); let mut visitor = SimulationToSimulatorVisitor::new(&mut writer); @@ -365,7 +459,7 @@ impl<'a> SimulationElementVisitor for SimulationToSimulatorVisitor<'a> { } pub trait Reader { - fn read_f64(&mut self, name: &str) -> f64; + fn read_f64(&mut self, identifier: &VariableIdentifier) -> f64; } /// Reads data from the simulator into the aircraft system simulation. @@ -380,13 +474,13 @@ impl<'a> SimulatorReader<'a> { } } impl<'a> Reader for SimulatorReader<'a> { - fn read_f64(&mut self, name: &str) -> f64 { - self.simulator_read_writer.read(name) + fn read_f64(&mut self, identifier: &VariableIdentifier) -> f64 { + self.simulator_read_writer.read(identifier) } } pub trait Writer { - fn write_f64(&mut self, name: &str, value: f64); + fn write_f64(&mut self, identifier: &VariableIdentifier, value: f64); } /// Writes data from the aircraft system simulation into the the simulator. @@ -401,8 +495,8 @@ impl<'a> SimulatorWriter<'a> { } } impl<'a> Writer for SimulatorWriter<'a> { - fn write_f64(&mut self, name: &str, value: f64) { - self.simulator_read_writer.write(name, value); + fn write_f64(&mut self, identifier: &VariableIdentifier, value: f64) { + self.simulator_read_writer.write(identifier, value); } } @@ -420,21 +514,22 @@ pub trait Read { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Read}; + /// # SimulatorReader, SimulatorWriter, Read, VariableIdentifier}; /// struct MySimulationElement { /// is_on: bool, /// } /// impl SimulationElement for MySimulationElement { /// fn read(&mut self, reader: &mut SimulatorReader) { - /// self.is_on = reader.read("MY_SIMULATOR_ELEMENT_IS_ON"); + /// // The identifier would ordinarily be retrieved from the registry. + /// self.is_on = reader.read(&VariableIdentifier::default()); /// } /// } /// ``` - fn read(&mut self, name: &str) -> T + fn read(&mut self, identifier: &VariableIdentifier) -> T where Self: Sized + Reader, { - let value = self.read_f64(name); + let value = self.read_f64(identifier); self.convert(value) } @@ -442,22 +537,23 @@ pub trait Read { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Read}; + /// # SimulatorReader, SimulatorWriter, Read, VariableIdentifier}; /// # use systems::shared::arinc429::Arinc429Word; /// struct MySimulationElement { /// is_on: Arinc429Word, /// } /// impl SimulationElement for MySimulationElement { /// fn read(&mut self, reader: &mut SimulatorReader) { - /// self.is_on = reader.read_arinc429("MY_SIMULATOR_ELEMENT_IS_ON"); + /// // The identifier would ordinarily be retrieved from the registry. + /// self.is_on = reader.read_arinc429(&VariableIdentifier::default()); /// } /// } /// ``` - fn read_arinc429(&mut self, name: &str) -> Arinc429Word + fn read_arinc429(&mut self, identifier: &VariableIdentifier) -> Arinc429Word where Self: Sized + Reader, { - let value = from_arinc429(self.read_f64(name)); + let value = from_arinc429(self.read_f64(identifier)); Arinc429Word::new(self.convert(value.0), value.1) } @@ -469,22 +565,23 @@ pub trait Write { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Write}; + /// # SimulatorReader, SimulatorWriter, Write, VariableIdentifier}; /// struct MySimulationElement { /// n: f64, /// } /// impl SimulationElement for MySimulationElement { /// fn write(&self, writer: &mut SimulatorWriter) { - /// writer.write("MY_SIMULATOR_ELEMENT_N", self.n); + /// // The identifier would ordinarily be retrieved from the registry. + /// writer.write(&VariableIdentifier::default(), self.n); /// } /// } /// ``` - fn write(&mut self, name: &str, value: T) + fn write(&mut self, identifier: &VariableIdentifier, value: T) where Self: Sized + Writer, { let value = self.convert(value); - self.write_f64(name, value) + self.write_f64(identifier, value) } /// Write an ARINC 429 value to the simulator. @@ -493,23 +590,24 @@ pub trait Write { /// # Examples /// ```rust /// # use systems::simulation::{SimulationElement, SimulationElementVisitor, - /// # SimulatorReader, SimulatorWriter, Write}; + /// # SimulatorReader, SimulatorWriter, Write, VariableIdentifier}; /// # use systems::shared::arinc429::SignStatus; /// struct MySimulationElement { /// n: f64, /// } /// impl SimulationElement for MySimulationElement { /// fn write(&self, writer: &mut SimulatorWriter) { - /// writer.write_arinc429("MY_SIMULATOR_ELEMENT_N", self.n, SignStatus::NormalOperation); + /// // The identifier would ordinarily be retrieved from the registry. + /// writer.write_arinc429(&VariableIdentifier::default(), self.n, SignStatus::NormalOperation); /// } /// } /// ``` - fn write_arinc429(&mut self, name: &str, value: T, ssm: SignStatus) + fn write_arinc429(&mut self, identifier: &VariableIdentifier, value: T, ssm: SignStatus) where Self: Sized + Writer, { let value = self.convert(value); - self.write_f64(name, to_arinc429(value, ssm)); + self.write_f64(identifier, to_arinc429(value, ssm)); } fn convert(&mut self, value: T) -> f64; diff --git a/src/systems/systems/src/simulation/test.rs b/src/systems/systems/src/simulation/test.rs index d92e76906ff..7c42b4c0757 100644 --- a/src/systems/systems/src/simulation/test.rs +++ b/src/systems/systems/src/simulation/test.rs @@ -1,5 +1,6 @@ +use fxhash::FxHashMap; use rand::Rng; -use std::{cell::Ref, collections::HashMap, time::Duration}; +use std::{cell::Ref, time::Duration}; use uom::si::{ acceleration::foot_per_second_squared, f64::*, @@ -21,6 +22,8 @@ use super::{ Writer, }; use crate::landing_gear::LandingGear; +use crate::shared::arinc429::{from_arinc429, to_arinc429, Arinc429Word, SignStatus}; +use crate::simulation::{InitContext, VariableIdentifier, VariableRegistry}; pub trait TestBed { type Aircraft: Aircraft; @@ -94,21 +97,93 @@ pub trait TestBed { self.test_bed_mut().set_vertical_speed(vertical_speed); } - fn contains_key(&self, name: &str) -> bool { - self.test_bed().contains_key(name) + fn contains_variable_with_name(&self, name: &str) -> bool { + self.test_bed().contains_variable_with_name(name) + } + + fn get_variable_identifier(&mut self, name: &str) -> Option<&VariableIdentifier> { + self.test_bed_mut().get_variable_identifier(name) } } impl Writer for T { - fn write_f64(&mut self, name: &str, value: f64) { - self.test_bed_mut().write_f64(name, value); + fn write_f64(&mut self, identifier: &VariableIdentifier, value: f64) { + self.test_bed_mut().write_f64(identifier, value); } } impl Reader for T { - fn read_f64(&mut self, name: &str) -> f64 { - self.test_bed_mut().read_f64(name) + fn read_f64(&mut self, identifier: &VariableIdentifier) -> f64 { + self.test_bed_mut().read_f64(identifier) } } +impl WriteByName for T +where + T: Write, +{ + fn write_by_name(&mut self, name: &str, value: U) { + if let Some(identifier) = self.get_variable_identifier(name).copied() { + let value = self.convert(value); + self.write_f64(&identifier, value) + } + } + + fn write_arinc429_by_name(&mut self, name: &str, value: U, ssm: SignStatus) { + if let Some(identifier) = self.get_variable_identifier(name).copied() { + let value = self.convert(value); + self.write_f64(&identifier, to_arinc429(value, ssm)); + } + } +} + +impl ReadByName for T +where + T: Read, +{ + fn read_by_name(&mut self, name: &str) -> U { + let value = match self.get_variable_identifier(name).copied() { + Some(identifier) => self.read_f64(&identifier), + None => 0., + }; + + self.convert(value) + } + + fn read_arinc429_by_name(&mut self, name: &str) -> Arinc429Word { + let value = from_arinc429(match self.get_variable_identifier(name).copied() { + Some(identifier) => self.read_f64(&identifier), + None => 0., + }); + + Arinc429Word::new(self.convert(value.0), value.1) + } +} + +pub trait WriteByName +where + T: Write, +{ + fn write_by_name(&mut self, name: &str, value: U) + where + Self: Sized + Writer; + + fn write_arinc429_by_name(&mut self, name: &str, value: U, ssm: SignStatus) + where + Self: Sized + Writer; +} + +pub trait ReadByName +where + T: Read, +{ + fn read_by_name(&mut self, name: &str) -> U + where + Self: Sized + Reader; + + fn read_arinc429_by_name(&mut self, name: &str) -> Arinc429Word + where + Self: Sized + Reader; +} + /// The simulation test bed handles the testing of [`Aircraft`] and [`SimulationElement`] /// by running a full simulation tick on them. /// @@ -117,12 +192,15 @@ impl Reader for T { pub struct SimulationTestBed { reader_writer: TestReaderWriter, simulation: Simulation, + variable_registry: TestVariableRegistry, } impl SimulationTestBed { - pub fn new T>(aircraft_ctor_fn: U) -> Self { + pub fn new T>(aircraft_ctor_fn: U) -> Self { + let mut variable_registry = TestVariableRegistry::default(); let mut test_bed = Self { reader_writer: TestReaderWriter::new(), - simulation: Simulation::new(aircraft_ctor_fn), + simulation: Simulation::new(aircraft_ctor_fn, &mut variable_registry), + variable_registry, }; test_bed.set_indicated_airspeed(Velocity::new::(250.)); @@ -218,65 +296,72 @@ impl SimulationTestBed { } fn set_indicated_airspeed(&mut self, indicated_airspeed: Velocity) { - self.write(UpdateContext::INDICATED_AIRSPEED_KEY, indicated_airspeed); + self.write_by_name(UpdateContext::INDICATED_AIRSPEED_KEY, indicated_airspeed); } fn indicated_airspeed(&mut self) -> Velocity { - self.read(UpdateContext::INDICATED_AIRSPEED_KEY) + self.read_by_name(UpdateContext::INDICATED_AIRSPEED_KEY) } fn set_indicated_altitude(&mut self, indicated_altitude: Length) { - self.write(UpdateContext::INDICATED_ALTITUDE_KEY, indicated_altitude); + self.write_by_name(UpdateContext::INDICATED_ALTITUDE_KEY, indicated_altitude); } fn set_ambient_temperature(&mut self, ambient_temperature: ThermodynamicTemperature) { - self.write(UpdateContext::AMBIENT_TEMPERATURE_KEY, ambient_temperature); + self.write_by_name(UpdateContext::AMBIENT_TEMPERATURE_KEY, ambient_temperature); } fn set_on_ground(&mut self, on_ground: bool) { - self.write(UpdateContext::IS_ON_GROUND_KEY, on_ground); + self.write_by_name(UpdateContext::IS_ON_GROUND_KEY, on_ground); let mut gear_compression = Ratio::new::(0.5); if on_ground { gear_compression = Ratio::new::(0.8); } - self.write(LandingGear::GEAR_CENTER_COMPRESSION, gear_compression); - self.write(LandingGear::GEAR_LEFT_COMPRESSION, gear_compression); - self.write(LandingGear::GEAR_RIGHT_COMPRESSION, gear_compression); + self.write_by_name(LandingGear::GEAR_CENTER_COMPRESSION, gear_compression); + self.write_by_name(LandingGear::GEAR_LEFT_COMPRESSION, gear_compression); + self.write_by_name(LandingGear::GEAR_RIGHT_COMPRESSION, gear_compression); } fn set_ambient_pressure(&mut self, ambient_pressure: Pressure) { - self.write( + self.write_by_name( UpdateContext::AMBIENT_PRESSURE_KEY, ambient_pressure.get::(), ); } fn set_vertical_speed(&mut self, vertical_speed: Velocity) { - self.write( + self.write_by_name( UpdateContext::VERTICAL_SPEED_KEY, vertical_speed.get::(), ); } pub fn set_long_acceleration(&mut self, accel: Acceleration) { - self.reader_writer.write_f64( + self.write_by_name( UpdateContext::ACCEL_BODY_Z_KEY, accel.get::(), ); } - fn write_f64(&mut self, name: &str, value: f64) { - self.reader_writer.write_f64(name, value); + fn write_f64(&mut self, identifier: &VariableIdentifier, value: f64) { + self.reader_writer.write_f64(identifier, value); } - fn read_f64(&mut self, name: &str) -> f64 { - self.reader_writer.read_f64(name) + fn read_f64(&mut self, identifier: &VariableIdentifier) -> f64 { + self.reader_writer.read_f64(identifier) } - fn contains_key(&self, name: &str) -> bool { - self.reader_writer.contains_key(name) + fn contains_variable_with_name(&self, name: &str) -> bool { + match self.variable_registry.find(name) { + Some(identifier) => self.reader_writer.contains(identifier), + None => false, + } + } + + fn get_variable_identifier(&mut self, name: &str) -> Option<&VariableIdentifier> { + self.variable_registry.find(name) } } impl TestBed for SimulationTestBed { @@ -346,12 +431,12 @@ impl SimulationTestBed> { /// Wrapper for converting the given constructor function to /// a [`SimulationTestBed>`] instance. -pub struct ElementCtorFn T>(pub U); -impl T> From> +pub struct ElementCtorFn T>(pub U); +impl T> From> for SimulationTestBed> { fn from(func: ElementCtorFn) -> Self { - Self::new(|electricity| TestAircraft::new((func.0)(electricity))) + Self::new(|context| TestAircraft::new((func.0)(context))) } } impl From for SimulationTestBed> { @@ -421,34 +506,34 @@ impl SimulationElement for TestAircraft { } struct TestReaderWriter { - variables: HashMap, + variables: FxHashMap, } impl TestReaderWriter { fn new() -> Self { Self { - variables: HashMap::new(), + variables: FxHashMap::default(), } } - fn contains_key(&self, name: &str) -> bool { - self.variables.contains_key(name) + fn contains(&self, identifier: &VariableIdentifier) -> bool { + self.variables.contains_key(identifier) } - fn write_f64(&mut self, name: &str, value: f64) { - self.write(name, value); + fn write_f64(&mut self, identifier: &VariableIdentifier, value: f64) { + self.write(identifier, value); } - fn read_f64(&mut self, name: &str) -> f64 { - self.read(name) + fn read_f64(&mut self, identifier: &VariableIdentifier) -> f64 { + self.read(identifier) } } impl SimulatorReaderWriter for TestReaderWriter { - fn read(&mut self, name: &str) -> f64 { - *self.variables.get(name).unwrap_or(&0.) + fn read(&mut self, identifier: &VariableIdentifier) -> f64 { + *self.variables.get(identifier).unwrap_or(&0.) } - fn write(&mut self, name: &str, value: f64) { - self.variables.insert(name.to_owned(), value); + fn write(&mut self, identifier: &VariableIdentifier, value: f64) { + self.variables.insert(*identifier, value); } } @@ -458,6 +543,34 @@ impl Default for TestReaderWriter { } } +#[derive(Default)] +// TODO Make private once HYD tests are modified to use SimulationTestBed. +pub struct TestVariableRegistry { + name_to_identifier: FxHashMap, + next_identifier: VariableIdentifier, +} + +impl TestVariableRegistry { + fn find(&self, name: &str) -> Option<&VariableIdentifier> { + self.name_to_identifier.get(name) + } +} + +impl VariableRegistry for TestVariableRegistry { + fn get(&mut self, name: String) -> VariableIdentifier { + match self.name_to_identifier.get(&name).copied() { + Some(identifier) => identifier, + None => { + let identifier = self.next_identifier; + self.name_to_identifier.insert(name, identifier); + self.next_identifier = identifier.next(); + + identifier + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/systems/systems/src/simulation/update_context.rs b/src/systems/systems/src/simulation/update_context.rs index 783ea4ef068..9113b37e954 100644 --- a/src/systems/systems/src/simulation/update_context.rs +++ b/src/systems/systems/src/simulation/update_context.rs @@ -5,9 +5,10 @@ use uom::si::{ }; use super::{Read, SimulatorReader}; +use crate::simulation::{InitContext, VariableIdentifier}; use nalgebra::{Rotation3, Vector3}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Attitude { pitch: Angle, bank: Angle, @@ -33,7 +34,7 @@ impl Attitude { self.bank } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct LocalAcceleration { acceleration: [Acceleration; 3], } @@ -80,6 +81,18 @@ impl LocalAcceleration { /// for the purpose of handling a simulation tick. #[derive(Clone, Copy, Debug)] pub struct UpdateContext { + ambient_temperature_id: VariableIdentifier, + indicated_airspeed_id: VariableIdentifier, + indicated_altitude_id: VariableIdentifier, + is_on_ground_id: VariableIdentifier, + ambient_pressure_id: VariableIdentifier, + vertical_speed_id: VariableIdentifier, + accel_body_x_id: VariableIdentifier, + accel_body_y_id: VariableIdentifier, + accel_body_z_id: VariableIdentifier, + plane_pitch_id: VariableIdentifier, + plane_bank_id: VariableIdentifier, + delta: Duration, indicated_airspeed: Velocity, indicated_altitude: Length, @@ -103,7 +116,11 @@ impl UpdateContext { pub(crate) const PLANE_PITCH_KEY: &'static str = "PLANE PITCH DEGREES"; pub(crate) const PLANE_BANK_KEY: &'static str = "PLANE BANK DEGREES"; + #[deprecated( + note = "Do not create UpdateContext directly. Instead use the SimulationTestBed or your own custom test bed." + )] pub fn new( + context: &mut InitContext, delta: Duration, indicated_airspeed: Velocity, indicated_altitude: Length, @@ -116,6 +133,19 @@ impl UpdateContext { bank: Angle, ) -> UpdateContext { UpdateContext { + ambient_temperature_id: context + .get_identifier(Self::AMBIENT_TEMPERATURE_KEY.to_owned()), + indicated_airspeed_id: context.get_identifier(Self::INDICATED_AIRSPEED_KEY.to_owned()), + indicated_altitude_id: context.get_identifier(Self::INDICATED_ALTITUDE_KEY.to_owned()), + is_on_ground_id: context.get_identifier(Self::IS_ON_GROUND_KEY.to_owned()), + ambient_pressure_id: context.get_identifier(Self::AMBIENT_PRESSURE_KEY.to_owned()), + vertical_speed_id: context.get_identifier(Self::VERTICAL_SPEED_KEY.to_owned()), + accel_body_x_id: context.get_identifier(Self::ACCEL_BODY_X_KEY.to_owned()), + accel_body_y_id: context.get_identifier(Self::ACCEL_BODY_Y_KEY.to_owned()), + accel_body_z_id: context.get_identifier(Self::ACCEL_BODY_Z_KEY.to_owned()), + plane_pitch_id: context.get_identifier(Self::PLANE_PITCH_KEY.to_owned()), + plane_bank_id: context.get_identifier(Self::PLANE_BANK_KEY.to_owned()), + delta, indicated_airspeed, indicated_altitude, @@ -132,33 +162,55 @@ impl UpdateContext { } } - /// Creates a context based on the data that was read from the simulator. - pub(super) fn from_reader(reader: &mut SimulatorReader, delta_time: Duration) -> UpdateContext { + pub(super) fn new_for_simulation(context: &mut InitContext) -> UpdateContext { UpdateContext { - ambient_temperature: reader.read(UpdateContext::AMBIENT_TEMPERATURE_KEY), - indicated_airspeed: reader.read(UpdateContext::INDICATED_AIRSPEED_KEY), - indicated_altitude: reader.read(UpdateContext::INDICATED_ALTITUDE_KEY), - is_on_ground: reader.read(UpdateContext::IS_ON_GROUND_KEY), - ambient_pressure: Pressure::new::( - reader.read(UpdateContext::AMBIENT_PRESSURE_KEY), - ), - vertical_speed: Velocity::new::( - reader.read(UpdateContext::VERTICAL_SPEED_KEY), - ), - delta: delta_time, - local_acceleration: LocalAcceleration::new( - reader.read(UpdateContext::ACCEL_BODY_X_KEY), - reader.read(UpdateContext::ACCEL_BODY_Y_KEY), - reader.read(UpdateContext::ACCEL_BODY_Z_KEY), - ), - - attitude: Attitude::new( - reader.read(UpdateContext::PLANE_PITCH_KEY), - reader.read(UpdateContext::PLANE_BANK_KEY), - ), + ambient_temperature_id: context.get_identifier("AMBIENT TEMPERATURE".to_owned()), + indicated_airspeed_id: context.get_identifier("AIRSPEED INDICATED".to_owned()), + indicated_altitude_id: context.get_identifier("INDICATED ALTITUDE".to_owned()), + is_on_ground_id: context.get_identifier("SIM ON GROUND".to_owned()), + ambient_pressure_id: context.get_identifier("AMBIENT PRESSURE".to_owned()), + vertical_speed_id: context.get_identifier("VELOCITY WORLD Y".to_owned()), + accel_body_x_id: context.get_identifier("ACCELERATION BODY X".to_owned()), + accel_body_y_id: context.get_identifier("ACCELERATION BODY Y".to_owned()), + accel_body_z_id: context.get_identifier("ACCELERATION BODY Z".to_owned()), + plane_pitch_id: context.get_identifier("PLANE PITCH DEGREES".to_owned()), + plane_bank_id: context.get_identifier("PLANE BANK DEGREES".to_owned()), + + delta: Default::default(), + indicated_airspeed: Default::default(), + indicated_altitude: Default::default(), + ambient_temperature: Default::default(), + ambient_pressure: Default::default(), + is_on_ground: Default::default(), + vertical_speed: Default::default(), + local_acceleration: Default::default(), + attitude: Default::default(), } } + /// Updates a context based on the data that was read from the simulator. + pub(super) fn update(&mut self, reader: &mut SimulatorReader, delta_time: Duration) { + self.ambient_temperature = reader.read(&self.ambient_temperature_id); + self.indicated_airspeed = reader.read(&self.indicated_airspeed_id); + self.indicated_altitude = reader.read(&self.indicated_altitude_id); + self.is_on_ground = reader.read(&self.is_on_ground_id); + self.ambient_pressure = + Pressure::new::(reader.read(&self.ambient_pressure_id)); + self.vertical_speed = + Velocity::new::(reader.read(&self.vertical_speed_id)); + self.delta = delta_time; + self.local_acceleration = LocalAcceleration::new( + reader.read(&self.accel_body_x_id), + reader.read(&self.accel_body_y_id), + reader.read(&self.accel_body_z_id), + ); + + self.attitude = Attitude::new( + reader.read(&self.plane_pitch_id), + reader.read(&self.plane_bank_id), + ); + } + pub fn is_in_flight(&self) -> bool { !self.is_on_ground } diff --git a/src/systems/systems_wasm/Cargo.toml b/src/systems/systems_wasm/Cargo.toml index c74e427b10a..1530c5ca714 100644 --- a/src/systems/systems_wasm/Cargo.toml +++ b/src/systems/systems_wasm/Cargo.toml @@ -13,3 +13,4 @@ doc = false uom = "0.30.0" systems = { path = "../systems" } msfs = { git = "https://github.com/flybywiresim/msfs-rs", branch = "main" } +fxhash = "0.2.1" diff --git a/src/systems/systems_wasm/src/electrical.rs b/src/systems/systems_wasm/src/electrical.rs index 3c1b943e196..6b57c679525 100644 --- a/src/systems/systems_wasm/src/electrical.rs +++ b/src/systems/systems_wasm/src/electrical.rs @@ -1,29 +1,34 @@ -use std::collections::HashMap; +use fxhash::FxHashMap; use std::error::Error; use crate::SimulatorAspect; use msfs::legacy::execute_calculator_code; use msfs::legacy::AircraftVariable; use systems::shared::to_bool; +use systems::simulation::{VariableIdentifier, VariableRegistry}; #[derive(Default)] pub(super) struct MsfsElectricalBuses { - connections: HashMap, + connections: FxHashMap, } impl MsfsElectricalBuses { - pub(super) fn add(&mut self, name: &str, from: usize, to: usize) { - self.connections.insert( - format!("ELEC_{}_BUS_IS_POWERED", name), - ElectricalBusConnection::new(from, to), - ); + pub(super) fn add( + &mut self, + registry: &mut impl VariableRegistry, + name: &str, + from: usize, + to: usize, + ) { + let identifier = registry.get(format!("ELEC_{}_BUS_IS_POWERED", name)); + self.connections + .insert(identifier, ElectricalBusConnection::new(from, to)); } } impl SimulatorAspect for MsfsElectricalBuses { - fn write(&mut self, name: &str, value: f64) -> bool { - if name.starts_with("ELEC_") && name.ends_with("_BUS_IS_POWERED") { - if let Some(connection) = self.connections.get_mut(name) { - connection.update(value); - } + fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool { + match self.connections.get_mut(identifier) { + Some(connection) => connection.update(value), + None => {} } // The powered state of a bus isn't just updated here, but should also be set as a named @@ -34,25 +39,23 @@ impl SimulatorAspect for MsfsElectricalBuses { struct ElectricalBusConnection { connected: bool, - from: usize, - to: usize, + toggle_code: String, } impl ElectricalBusConnection { fn new(from: usize, to: usize) -> Self { Self { connected: true, - from, - to, + toggle_code: format!( + "{} {} (>K:2:ELECTRICAL_BUS_TO_BUS_CONNECTION_TOGGLE)", + from, to + ), } } fn update(&mut self, value: f64) { let should_be_connected = (value - 1.).abs() < f64::EPSILON; if should_be_connected != self.connected { - execute_calculator_code::<()>(&format!( - "{} {} (>K:2:ELECTRICAL_BUS_TO_BUS_CONNECTION_TOGGLE)", - self.from, self.to - )); + execute_calculator_code::<()>(&self.toggle_code); self.connected = !self.connected; } } @@ -63,17 +66,18 @@ impl ElectricalBusConnection { /// Once pneumatics and the engine model are completed, this /// type can probably be removed. pub(super) struct MsfsAuxiliaryPowerUnit { - is_available_variable_name: String, + is_available_id: VariableIdentifier, msfs_apu_is_on: AircraftVariable, fuel_valve_number: u8, } impl MsfsAuxiliaryPowerUnit { - pub(super) fn new( - is_available_variable_name: &str, + pub fn new( + registry: &mut impl VariableRegistry, + is_available_variable_name: String, fuel_valve_number: u8, ) -> Result> { Ok(Self { - is_available_variable_name: is_available_variable_name.to_owned(), + is_available_id: registry.get(is_available_variable_name), msfs_apu_is_on: AircraftVariable::from("APU SWITCH", "Bool", 0)?, fuel_valve_number, }) @@ -97,8 +101,8 @@ impl MsfsAuxiliaryPowerUnit { } } impl SimulatorAspect for MsfsAuxiliaryPowerUnit { - fn write(&mut self, name: &str, value: f64) -> bool { - if name == self.is_available_variable_name { + fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool { + if identifier == &self.is_available_id { let is_available = to_bool(value); let msfs_apu_is_on = to_bool(self.msfs_apu_is_on.get()); diff --git a/src/systems/systems_wasm/src/failures.rs b/src/systems/systems_wasm/src/failures.rs index 42bc6e210a9..51d18da8c9d 100644 --- a/src/systems/systems_wasm/src/failures.rs +++ b/src/systems/systems_wasm/src/failures.rs @@ -1,19 +1,19 @@ -use std::collections::HashMap; - +use fxhash::FxHashMap; use msfs::legacy::NamedVariable; + use systems::failures::FailureType; pub(super) struct Failures { activate_sim_var: NamedVariable, deactivate_sim_var: NamedVariable, - identifier_to_failure_type: HashMap, + identifier_to_failure_type: FxHashMap, } impl Failures { pub(super) fn new(activate_sim_var: NamedVariable, deactivate_sim_var: NamedVariable) -> Self { Self { activate_sim_var, deactivate_sim_var, - identifier_to_failure_type: HashMap::new(), + identifier_to_failure_type: FxHashMap::default(), } } diff --git a/src/systems/systems_wasm/src/lib.rs b/src/systems/systems_wasm/src/lib.rs index 372b5f0815b..bf8e4725752 100644 --- a/src/systems/systems_wasm/src/lib.rs +++ b/src/systems/systems_wasm/src/lib.rs @@ -2,8 +2,9 @@ mod electrical; mod failures; -use std::{collections::HashMap, error::Error, pin::Pin, time::Duration}; +use std::{error::Error, pin::Pin, time::Duration}; +use fxhash::FxHashMap; use msfs::{ legacy::{AircraftVariable, NamedVariable}, sim_connect::{SimConnect, SimConnectRecv}, @@ -12,27 +13,30 @@ use msfs::{ use systems::{ failures::FailureType, - simulation::{Aircraft, Simulation, SimulatorReaderWriter}, + simulation::{ + Aircraft, Simulation, SimulatorReaderWriter, VariableIdentifier, VariableRegistry, + }, }; use electrical::{MsfsAuxiliaryPowerUnit, MsfsElectricalBuses}; use failures::Failures; +use systems::simulation::InitContext; /// An aspect to inject into events in the simulation. pub trait SimulatorAspect { - /// Attempts to read data with the given name. + /// Attempts to read data with the given identifier. /// Returns `Some` when reading was successful, `None` otherwise. - fn read(&mut self, _name: &str) -> Option { + fn read(&mut self, _identifier: &VariableIdentifier) -> Option { None } - /// Attempts to write the value with the given name. + /// Attempts to write the value with the given identifier. /// Returns true when the writing was successful and deemed sufficient, /// false otherwise. /// /// Note that there may be cases where multiple types write the same data. /// For such situations, after a successful write the function can return false. - fn write(&mut self, _name: &str, _value: f64) -> bool { + fn write(&mut self, _identifier: &VariableIdentifier, _value: f64) -> bool { false } @@ -52,29 +56,32 @@ pub trait SimulatorAspect { } pub trait MsfsAspectCtor { - fn new(sim_connect: &mut SimConnect) -> Result> + fn new( + registry: &mut MsfsVariableRegistry, + sim_connect: &mut SimConnect, + ) -> Result> where Self: Sized; } -pub struct MsfsHandlerBuilder<'a, 'b> { +pub struct MsfsSimulationBuilder<'a, 'b> { + variable_registry: Option, key_prefix: String, electrical_buses: Option, - aircraft_variable_reader: Option, sim_connect: Pin<&'a mut SimConnect<'b>>, apu: Option, failures: Option, additional_aspects: Vec>, } -impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { +impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> { const MSFS_INFINITELY_POWERED_BUS_IDENTIFIER: usize = 1; - pub fn new(key_prefix: &str, sim_connect: Pin<&'a mut SimConnect<'b>>) -> Self { + pub fn new(key_prefix: String, sim_connect: Pin<&'a mut SimConnect<'b>>) -> Self { Self { - key_prefix: key_prefix.to_owned(), + variable_registry: Some(MsfsVariableRegistry::new(key_prefix.clone())), + key_prefix, electrical_buses: Some(Default::default()), - aircraft_variable_reader: Some(Default::default()), sim_connect, apu: None, failures: None, @@ -82,7 +89,10 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { } } - pub fn build(mut self) -> MsfsHandler { + pub fn build T>( + mut self, + aircraft_ctor_fn: U, + ) -> (Simulation, MsfsHandler) { let mut aspects: Vec> = vec![Box::new(self.electrical_buses.unwrap())]; if self.apu.is_some() { @@ -90,19 +100,23 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { } aspects.append(&mut self.additional_aspects); - aspects.push(Box::new(self.aircraft_variable_reader.unwrap())); - aspects.push(Box::new(MsfsNamedVariableReaderWriter::new( - &self.key_prefix, - ))); - MsfsHandler::new(aspects, self.failures) + let mut registry = self.variable_registry.unwrap(); + let simulation = Simulation::new(aircraft_ctor_fn, &mut registry); + aspects.push(Box::new(registry)); + + (simulation, MsfsHandler::new(aspects, self.failures)) } pub fn with( mut self, ) -> Result> { - self.additional_aspects - .push(Box::new(T::new(self.sim_connect.as_mut().get_mut())?)); + if let Some(registry) = &mut self.variable_registry { + self.additional_aspects.push(Box::new(T::new( + registry, + self.sim_connect.as_mut().get_mut(), + )?)); + } Ok(self) } @@ -113,9 +127,16 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { /// This function assumes that `bus.1` is a bus which is infinitely powered, and thus can act /// as a power source for the other buses which will all be connected to it. pub fn with_electrical_buses(mut self, buses_to_add: Vec<(&'static str, usize)>) -> Self { - if let Some(buses) = &mut self.electrical_buses { - for bus in buses_to_add { - buses.add(bus.0, Self::MSFS_INFINITELY_POWERED_BUS_IDENTIFIER, bus.1) + if let Some(registry) = &mut self.variable_registry { + if let Some(buses) = &mut self.electrical_buses { + for bus in buses_to_add { + buses.add( + registry, + bus.0, + Self::MSFS_INFINITELY_POWERED_BUS_IDENTIFIER, + bus.1, + ) + } } } @@ -124,13 +145,16 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { pub fn with_auxiliary_power_unit( mut self, - is_available_variable_name: &str, + is_available_variable_name: String, fuel_valve_number: u8, ) -> Result> { - self.apu = Some(MsfsAuxiliaryPowerUnit::new( - is_available_variable_name, - fuel_valve_number, - )?); + if let Some(registry) = &mut self.variable_registry { + self.apu = Some(MsfsAuxiliaryPowerUnit::new( + registry, + is_available_variable_name, + fuel_valve_number, + )?); + } Ok(self) } @@ -155,8 +179,8 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { units: &str, index: usize, ) -> Result> { - if let Some(reader) = &mut self.aircraft_variable_reader { - reader.add(name, units, index)?; + if let Some(registry) = &mut self.variable_registry { + registry.add_aircraft_variable(name, units, index)?; } Ok(self) @@ -167,10 +191,15 @@ impl<'a, 'b> MsfsHandlerBuilder<'a, 'b> { name: &str, units: &str, index: usize, - additional_names: Vec<&str>, + additional_names: Vec, ) -> Result> { - if let Some(reader) = &mut self.aircraft_variable_reader { - reader.add_with_additional_names(name, units, index, Some(additional_names))?; + if let Some(registry) = &mut self.variable_registry { + registry.add_aircraft_variable_with_additional_names( + name, + units, + index, + Some(additional_names), + )?; } Ok(self) @@ -253,98 +282,71 @@ impl MsfsHandler { } } impl SimulatorReaderWriter for MsfsHandler { - fn read(&mut self, name: &str) -> f64 { + fn read(&mut self, identifier: &VariableIdentifier) -> f64 { self.aspects .iter_mut() - .find_map(|aspect| aspect.read(name)) + .find_map(|aspect| aspect.read(identifier)) .unwrap_or(0.) } - fn write(&mut self, name: &str, value: f64) { + fn write(&mut self, identifier: &VariableIdentifier, value: f64) { for aspect in self.aspects.iter_mut() { - if aspect.write(name, value) { + if aspect.write(identifier, value) { break; } } } } -/// Reads and writes named variables (LVar). -pub struct MsfsNamedVariableReaderWriter { - name_prefix: String, - variables: HashMap, +pub struct MsfsVariableRegistry { + name_to_identifier: FxHashMap, + aircraft_variables: Vec, + named_variables: Vec, + named_variable_prefix: String, + next_aircraft_variable_identifier: VariableIdentifier, + next_named_variable_identifier: VariableIdentifier, } -impl MsfsNamedVariableReaderWriter { - pub fn new(key_prefix: &str) -> Self { - Self { - name_prefix: key_prefix.to_owned(), - variables: HashMap::::new(), - } - } - fn lookup_named_variable(&mut self, name: &str) -> &mut NamedVariable { - let name = format!("{}{}", self.name_prefix, name); +impl MsfsVariableRegistry { + const AIRCRAFT_VARIABLE_IDENTIFIER_TYPE: u8 = 0; + const NAMED_VARIABLE_IDENTIFIER_TYPE: u8 = 1; - self.variables - .entry(name.clone()) - .or_insert_with(|| NamedVariable::from(&name)) - } -} -impl SimulatorAspect for MsfsNamedVariableReaderWriter { - fn read(&mut self, name: &str) -> Option { - Some(self.lookup_named_variable(name).get_value()) - } - - fn write(&mut self, name: &str, value: f64) -> bool { - self.lookup_named_variable(name).set_value(value); - - true + pub fn new(named_variable_prefix: String) -> Self { + Self { + name_to_identifier: FxHashMap::default(), + aircraft_variables: Default::default(), + named_variables: Default::default(), + named_variable_prefix, + next_aircraft_variable_identifier: VariableIdentifier::new( + Self::AIRCRAFT_VARIABLE_IDENTIFIER_TYPE, + ), + next_named_variable_identifier: VariableIdentifier::new( + Self::NAMED_VARIABLE_IDENTIFIER_TYPE, + ), + } } -} -/// Reads aircraft variables (AVar). -#[derive(Default)] -pub struct MsfsAircraftVariableReader { - variables: HashMap, - mapping: HashMap, -} -impl MsfsAircraftVariableReader { /// Add an aircraft variable definition. Once added, the aircraft variable - /// can be read through the [`read`] function. - /// - /// Indexed variables are read by suffixing the index, for the example variable `"TURB ENG CORRECTED N2"`: - /// - When index `0` is passed, the variable can be read as: `"TURB ENG CORRECTED N2"`. - /// - When index `n` is passed, the variable can be read as: `"TURB ENG CORRECTED N2:n"`. - /// - /// [`read`]: trait.ReadWrite.html#method.read - pub fn add( + /// can be read through the `MsfsVariableRegistry.read` function. + pub fn add_aircraft_variable( &mut self, name: &str, units: &str, index: usize, ) -> Result<(), Box> { - self.add_with_additional_names(name, units, index, None) + self.add_aircraft_variable_with_additional_names(name, units, index, None) } /// Add an aircraft variable definition. Once added, the aircraft variable - /// can be read through the [`read`] function. - /// - /// Indexed variables are read by suffixing the index, for the example variable `"TURB ENG CORRECTED N2"`: - /// - When index `0` is passed, the variable can be read as: `"TURB ENG CORRECTED N2"`. - /// - When index `n` is passed, the variable can be read as: `"TURB ENG CORRECTED N2:n"`. + /// can be read through the `MsfsVariableRegistry.read` function. /// - /// When reading a variable, the additional names are mapped to the underlying variable. - /// Thus, when `"EXTERNAL POWER AVAILABLE"` is the underlying variable and you pass - /// `"OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"` as an additional name, the variable can be accessed - /// via `"EXTERNAL POWER AVAILABLE"` and `"OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"`. - /// - /// [`read`]: trait.ReadWrite.html#method.read - pub fn add_with_additional_names( + /// The additional names map to the same variable. + pub fn add_aircraft_variable_with_additional_names( &mut self, name: &str, units: &str, index: usize, - additional_names: Option>, + additional_names: Option>, ) -> Result<(), Box> { match AircraftVariable::from(&name, units, index) { Ok(var) => { @@ -354,29 +356,69 @@ impl MsfsAircraftVariableReader { name.to_owned() }; + let identifier = self.next_aircraft_variable_identifier; + + self.aircraft_variables + .insert(identifier.identifier_index(), var); + self.name_to_identifier.insert(name, identifier); + if let Some(additional_names) = additional_names { - additional_names.iter().for_each(|&el| { - self.mapping.insert(el.to_owned(), name.clone()); + additional_names.into_iter().for_each(|el| { + self.name_to_identifier.insert(el, identifier); }); } - self.variables.insert(name, var); + self.next_aircraft_variable_identifier = identifier.next(); + Ok(()) } Err(x) => Err(x), } } + + fn add_named_variable(&mut self, name: String) -> VariableIdentifier { + let identifier = self.next_named_variable_identifier; + self.named_variables.insert( + identifier.identifier_index(), + NamedVariable::from(&format!("{}{}", self.named_variable_prefix, name)), + ); + self.name_to_identifier.insert(name, identifier); + + self.next_named_variable_identifier = identifier.next(); + + identifier + } +} + +impl VariableRegistry for MsfsVariableRegistry { + fn get(&mut self, name: String) -> VariableIdentifier { + match self.name_to_identifier.get(&name) { + Some(identifier) => *identifier, + None => self.add_named_variable(name), + } + } } -impl SimulatorAspect for MsfsAircraftVariableReader { - fn read(&mut self, name: &str) -> Option { - let name = match self.mapping.get(name) { - Some(x) => x, - None => name, - }; - - match self.variables.get(name) { - Some(variable) => Some(variable.get()), - None => None, + +impl SimulatorAspect for MsfsVariableRegistry { + fn read(&mut self, identifier: &VariableIdentifier) -> Option { + match identifier.identifier_type() { + Self::AIRCRAFT_VARIABLE_IDENTIFIER_TYPE => { + Some(self.aircraft_variables[identifier.identifier_index()].get()) + } + Self::NAMED_VARIABLE_IDENTIFIER_TYPE => { + Some(self.named_variables[identifier.identifier_index()].get_value()) + } + _ => None, + } + } + + fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool { + match identifier.identifier_type() { + Self::NAMED_VARIABLE_IDENTIFIER_TYPE => { + self.named_variables[identifier.identifier_index()].set_value(value); + true + } + _ => false, } } }