diff --git a/Cargo.lock b/Cargo.lock
index 2d852b96a69..985cd4c9575 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -214,6 +214,18 @@ dependencies = [
"vec_map",
]
+[[package]]
+name = "enum_dispatch"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd53b3fde38a39a06b2e66dc282f3e86191e53bd04cc499929c15742beae3df8"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "env_logger"
version = "0.8.4"
@@ -621,6 +633,12 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
+[[package]]
+name = "once_cell"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
+
[[package]]
name = "paste"
version = "1.0.5"
@@ -934,6 +952,7 @@ dependencies = [
name = "systems_wasm"
version = "0.1.0"
dependencies = [
+ "enum_dispatch",
"fxhash",
"msfs",
"systems",
diff --git a/docs/a320-simvars.md b/docs/a320-simvars.md
index ea5c7ca690d..cfa7b048240 100644
--- a/docs/a320-simvars.md
+++ b/docs/a320-simvars.md
@@ -821,18 +821,13 @@
- Bool
- NW STRG DISC memo indication should show on ecam if true
-- A32NX_TILLER_PEDAL_DISCONNECT
- - Bool
- - True when tiller disconnect button is pressed
- Tiller button to be binded on "TOGGLE WATER RUDDER"
-
- A32NX_NOSE_WHEEL_POSITION
- Percent over 100
- Position of nose steering wheel animation [0;1] 0 left, 0.5 middle
- A32NX_TILLER_HANDLE_POSITION
- Percent over 100
- - Position of tiller steering handle animation [0;1] 0 left, 0.5 middle
+ - Position of tiller steering handle animation [-1;1] -1 left, 0 middle, 1 right
- A32NX_AUTOPILOT_NOSEWHEEL_DEMAND
- Percent over 100
diff --git a/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO_INTERIOR.xml b/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO_INTERIOR.xml
index cd780824c68..e8e88e31851 100644
--- a/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO_INTERIOR.xml
+++ b/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/model/A320_NEO_INTERIOR.xml
@@ -461,7 +461,8 @@
HANDLE_LEFT_YOKE
HANDLE_LEFT_YOKE
- (L:A32NX_TILLER_HANDLE_POSITION)
+
+ (L:A32NX_TILLER_HANDLE_POSITION) 1 + 2 /
1
diff --git a/src/systems/a320_systems/src/hydraulic/mod.rs b/src/systems/a320_systems/src/hydraulic/mod.rs
index 2d9d3507f46..0dbaee5849c 100644
--- a/src/systems/a320_systems/src/hydraulic/mod.rs
+++ b/src/systems/a320_systems/src/hydraulic/mod.rs
@@ -1702,7 +1702,7 @@ impl A320HydraulicBrakeSteerComputerUnit {
.get_identifier("RIGHT_BRAKE_PEDAL_INPUT".to_owned()),
ground_speed_id: context.get_identifier("GPS GROUND SPEED".to_owned()),
- rudder_pedal_input_id: context.get_identifier("RUDDER_PEDAL_POSITION".to_owned()),
+ rudder_pedal_input_id: context.get_identifier("RUDDER_PEDAL_POSITION_RATIO".to_owned()),
tiller_handle_input_id: context.get_identifier("TILLER_HANDLE_POSITION".to_owned()),
tiller_pedal_disconnect_id: context
.get_identifier("TILLER_PEDAL_DISCONNECT".to_owned()),
@@ -1963,9 +1963,9 @@ impl SimulationElement for A320HydraulicBrakeSteerComputerUnit {
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));
+ 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));
+ Ratio::new::(reader.read(&self.right_brake_pedal_input_id));
self.tiller_handle_position =
Ratio::new::(reader.read(&self.tiller_handle_input_id));
@@ -3567,17 +3567,13 @@ mod tests {
.air_press_nominal()
}
- fn set_left_brake(self, position_percent: Ratio) -> Self {
- self.set_brake("LEFT_BRAKE_PEDAL_INPUT", position_percent)
- }
-
- fn set_right_brake(self, position_percent: Ratio) -> Self {
- self.set_brake("RIGHT_BRAKE_PEDAL_INPUT", position_percent)
+ fn set_left_brake(mut self, position: Ratio) -> Self {
+ self.write_by_name("LEFT_BRAKE_PEDAL_INPUT", position);
+ self
}
- fn set_brake(mut self, name: &str, position_percent: Ratio) -> Self {
- let scaled_value = position_percent.get::();
- self.write_by_name(name, scaled_value.min(1.).max(0.));
+ fn set_right_brake(mut self, position: Ratio) -> Self {
+ self.write_by_name("RIGHT_BRAKE_PEDAL_INPUT", position);
self
}
diff --git a/src/systems/a320_systems_wasm/src/autobrakes.rs b/src/systems/a320_systems_wasm/src/autobrakes.rs
new file mode 100644
index 00000000000..c0f1e56ac15
--- /dev/null
+++ b/src/systems/a320_systems_wasm/src/autobrakes.rs
@@ -0,0 +1,39 @@
+use std::error::Error;
+use std::time::Duration;
+use systems_wasm::aspects::{EventToVariableMapping, EventToVariableOptions, MsfsAspectBuilder};
+use systems_wasm::Variable;
+
+pub(super) fn autobrakes(builder: &mut MsfsAspectBuilder) -> Result<(), Box> {
+ let options = |options: EventToVariableOptions| {
+ options
+ .leading_debounce(Duration::from_millis(1500))
+ .afterwards_reset_to(0.)
+ };
+
+ builder.event_to_variable(
+ "AUTOBRAKE_LO_SET",
+ EventToVariableMapping::Value(1.),
+ Variable::named("OVHD_AUTOBRK_LOW_ON_IS_PRESSED"),
+ options,
+ )?;
+ builder.event_to_variable(
+ "AUTOBRAKE_MED_SET",
+ EventToVariableMapping::Value(1.),
+ Variable::named("OVHD_AUTOBRK_MED_ON_IS_PRESSED"),
+ options,
+ )?;
+ builder.event_to_variable(
+ "AUTOBRAKE_HI_SET",
+ EventToVariableMapping::Value(1.),
+ Variable::named("OVHD_AUTOBRK_MAX_ON_IS_PRESSED"),
+ options,
+ )?;
+ builder.event_to_variable(
+ "AUTOBRAKE_DISARM",
+ EventToVariableMapping::Value(1.),
+ Variable::named("AUTOBRAKE_DISARM"),
+ |options| options.afterwards_reset_to(0.),
+ )?;
+
+ Ok(())
+}
diff --git a/src/systems/a320_systems_wasm/src/brakes.rs b/src/systems/a320_systems_wasm/src/brakes.rs
new file mode 100644
index 00000000000..3b220e8665f
--- /dev/null
+++ b/src/systems/a320_systems_wasm/src/brakes.rs
@@ -0,0 +1,107 @@
+use std::error::Error;
+use systems::shared::{from_bool, to_bool};
+use systems_wasm::aspects::{
+ max, EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariableToEventMapping,
+ VariableToEventWriteOn,
+};
+use systems_wasm::Variable;
+
+pub(super) fn brakes(builder: &mut MsfsAspectBuilder) -> Result<(), Box> {
+ builder.event_to_variable(
+ "PARKING_BRAKES",
+ EventToVariableMapping::CurrentValueToValue(|current_value| {
+ from_bool(!to_bool(current_value))
+ }),
+ Variable::named("PARK_BRAKE_LEVER_POS"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "PARKING_BRAKE_SET",
+ EventToVariableMapping::EventDataToValue(|event_data| from_bool(event_data == 1)),
+ Variable::named("PARK_BRAKE_LEVER_POS"),
+ |options| options.mask(),
+ )?;
+
+ // Controller inputs for the left and right brakes are captured and translated
+ // to a variable so that it can be used by the simulation.
+ // After running the simulation, the variable value is written back to the simulator
+ // through the event.
+ let axis_left_brake_set_event_id = builder.event_to_variable(
+ "AXIS_LEFT_BRAKE_SET",
+ EventToVariableMapping::EventData32kPosition,
+ Variable::aspect("BRAKES_LEFT_EVENT"),
+ |options| options.mask(),
+ )?;
+ builder.variable_to_event_id(
+ Variable::aspect("BRAKE LEFT FORCE FACTOR"),
+ VariableToEventMapping::EventData32kPosition,
+ VariableToEventWriteOn::EveryTick,
+ axis_left_brake_set_event_id,
+ );
+ let axis_right_brake_set_event_id = builder.event_to_variable(
+ "AXIS_RIGHT_BRAKE_SET",
+ EventToVariableMapping::EventData32kPosition,
+ Variable::aspect("BRAKES_RIGHT_EVENT"),
+ |options| options.mask(),
+ )?;
+ builder.variable_to_event_id(
+ Variable::aspect("BRAKE RIGHT FORCE FACTOR"),
+ VariableToEventMapping::EventData32kPosition,
+ VariableToEventWriteOn::EveryTick,
+ axis_right_brake_set_event_id,
+ );
+
+ // Inputs for both brakes, left brake, and right brake are captured and
+ // translated via a smooth press function into a ratio which is written to variables.
+ const KEYBOARD_PRESS_SPEED: f64 = 0.6;
+ const KEYBOARD_RELEASE_SPEED: f64 = 0.3;
+ builder.event_to_variable(
+ "BRAKES",
+ EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED),
+ Variable::aspect("BRAKES"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "BRAKES_LEFT",
+ EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED),
+ Variable::aspect("BRAKES_LEFT"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "BRAKES_RIGHT",
+ EventToVariableMapping::SmoothPress(KEYBOARD_PRESS_SPEED, KEYBOARD_RELEASE_SPEED),
+ Variable::aspect("BRAKES_RIGHT"),
+ |options| options.mask(),
+ )?;
+
+ // The maximum braking demand of all controller inputs
+ // is calculated and made available as a percentage.
+ builder.reduce(
+ ExecuteOn::PreTick,
+ vec![
+ Variable::aspect("BRAKES"),
+ Variable::aspect("BRAKES_LEFT"),
+ Variable::aspect("BRAKES_LEFT_EVENT"),
+ ],
+ 0.,
+ to_percent_max,
+ Variable::named("LEFT_BRAKE_PEDAL_INPUT"),
+ );
+ builder.reduce(
+ ExecuteOn::PreTick,
+ vec![
+ Variable::aspect("BRAKES"),
+ Variable::aspect("BRAKES_RIGHT"),
+ Variable::aspect("BRAKES_RIGHT_EVENT"),
+ ],
+ 0.,
+ to_percent_max,
+ Variable::named("RIGHT_BRAKE_PEDAL_INPUT"),
+ );
+
+ Ok(())
+}
+
+fn to_percent_max(accumulator: f64, item: f64) -> f64 {
+ max(accumulator, item * 100.)
+}
diff --git a/src/systems/a320_systems_wasm/src/flaps.rs b/src/systems/a320_systems_wasm/src/flaps.rs
new file mode 100644
index 00000000000..a037fa24f94
--- /dev/null
+++ b/src/systems/a320_systems_wasm/src/flaps.rs
@@ -0,0 +1,206 @@
+use msfs::sim_connect;
+use msfs::{sim_connect::SimConnect, sim_connect::SIMCONNECT_OBJECT_ID_USER};
+use std::error::Error;
+use systems_wasm::aspects::{
+ EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariablesToObject,
+};
+use systems_wasm::{set_data_on_sim_object, Variable};
+
+pub(super) fn flaps(builder: &mut MsfsAspectBuilder) -> Result<(), Box> {
+ builder.event_to_variable(
+ "FLAPS_INCR",
+ EventToVariableMapping::CurrentValueToValue(|current_value| (current_value + 1.).min(4.)),
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "FLAPS_DECR",
+ EventToVariableMapping::CurrentValueToValue(|current_value| (current_value - 1.).max(0.)),
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |options| options.mask(),
+ )?;
+ flaps_event_to_value(builder, "FLAPS_UP", 0.)?;
+ flaps_event_to_value(builder, "FLAPS_1", 1.)?;
+ flaps_event_to_value(builder, "FLAPS_2", 2.)?;
+ flaps_event_to_value(builder, "FLAPS_3", 3.)?;
+ flaps_event_to_value(builder, "FLAPS_DOWN", 4.)?;
+ builder.event_to_variable(
+ "FLAPS_SET",
+ EventToVariableMapping::EventDataAndCurrentValueToValue(|event_data, current_value| {
+ let normalized_input: f64 = (event_data as i32 as f64) / 8192. - 1.;
+ get_handle_pos_from_0_1(normalized_input, current_value)
+ }),
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "AXIS_FLAPS_SET",
+ EventToVariableMapping::EventDataAndCurrentValueToValue(|event_data, current_value| {
+ let normalized_input: f64 = (event_data as i32 as f64) / 16384.;
+ get_handle_pos_from_0_1(normalized_input, current_value)
+ }),
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |options| options.mask(),
+ )?;
+
+ builder.map(
+ ExecuteOn::PreTick,
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |value| value / 4.,
+ Variable::named("FLAPS_HANDLE_PERCENT"),
+ );
+
+ builder.variables_to_object(Box::new(FlapsSurface {
+ left_flap: 0.,
+ right_flap: 0.,
+ }));
+ builder.variables_to_object(Box::new(SlatsSurface {
+ left_slat: 0.,
+ right_slat: 0.,
+ }));
+ builder.variables_to_object(Box::new(FlapsHandleIndex { index: 0. }));
+
+ Ok(())
+}
+
+fn flaps_event_to_value(
+ builder: &mut MsfsAspectBuilder,
+ event_name: &str,
+ value: f64,
+) -> Result<(), Box> {
+ builder.event_to_variable(
+ event_name,
+ EventToVariableMapping::Value(value),
+ Variable::named("FLAPS_HANDLE_INDEX"),
+ |options| options.mask(),
+ )?;
+
+ Ok(())
+}
+
+fn get_handle_pos_from_0_1(input: f64, current_value: f64) -> f64 {
+ if input < -0.8 {
+ 0.
+ } else if input > -0.7 && input < -0.3 {
+ 1.
+ } else if input > -0.2 && input < 0.2 {
+ 2.
+ } else if input > 0.3 && input < 0.7 {
+ 3.
+ } else if input > 0.8 {
+ 4.
+ } else {
+ current_value
+ }
+}
+
+#[sim_connect::data_definition]
+struct FlapsSurface {
+ #[name = "TRAILING EDGE FLAPS LEFT PERCENT"]
+ #[unit = "Percent"]
+ left_flap: f64,
+
+ #[name = "TRAILING EDGE FLAPS RIGHT PERCENT"]
+ #[unit = "Percent"]
+ right_flap: f64,
+}
+
+impl VariablesToObject for FlapsSurface {
+ fn variables(&self) -> Vec {
+ vec![
+ Variable::named("LEFT_FLAPS_POSITION_PERCENT"),
+ Variable::named("RIGHT_FLAPS_POSITION_PERCENT"),
+ ]
+ }
+
+ fn write(&mut self, values: Vec) {
+ self.left_flap = values[0];
+ self.right_flap = values[1];
+ }
+
+ set_data_on_sim_object!();
+}
+
+#[sim_connect::data_definition]
+struct SlatsSurface {
+ #[name = "LEADING EDGE FLAPS LEFT PERCENT"]
+ #[unit = "Percent"]
+ left_slat: f64,
+
+ #[name = "LEADING EDGE FLAPS RIGHT PERCENT"]
+ #[unit = "Percent"]
+ right_slat: f64,
+}
+
+impl VariablesToObject for SlatsSurface {
+ fn variables(&self) -> Vec {
+ vec![
+ Variable::named("LEFT_SLATS_POSITION_PERCENT"),
+ Variable::named("RIGHT_SLATS_POSITION_PERCENT"),
+ ]
+ }
+
+ fn write(&mut self, values: Vec) {
+ self.left_slat = values[0];
+ self.right_slat = values[1];
+ }
+
+ set_data_on_sim_object!();
+}
+
+#[sim_connect::data_definition]
+struct FlapsHandleIndex {
+ #[name = "FLAPS HANDLE INDEX"]
+ #[unit = "Number"]
+ index: f64,
+}
+
+impl VariablesToObject for FlapsHandleIndex {
+ fn variables(&self) -> Vec {
+ vec![
+ Variable::named("LEFT_FLAPS_POSITION_PERCENT"),
+ Variable::named("RIGHT_FLAPS_POSITION_PERCENT"),
+ Variable::named("LEFT_SLATS_POSITION_PERCENT"),
+ Variable::named("RIGHT_SLATS_POSITION_PERCENT"),
+ ]
+ }
+
+ fn write(&mut self, values: Vec) {
+ self.index = Self::msfs_flap_index_from_surfaces_positions_percent(values);
+ }
+
+ set_data_on_sim_object!();
+}
+
+impl FlapsHandleIndex {
+ /// Tries to take actual surfaces position PERCENTS and convert it into flight model FLAP HANDLE INDEX
+ /// This index is used by MSFS to select correct aerodynamic properties
+ /// There is no index available for flaps but no slats configurations (possible plane failure case)
+ /// The percent thresholds can be tuned to change the timing of aerodynamic impact versus surface actual position
+ fn msfs_flap_index_from_surfaces_positions_percent(values: Vec) -> f64 {
+ let left_flaps_position = values[0];
+ let right_flaps_position = values[1];
+ let left_slats_position = values[2];
+ let right_slats_position = values[3];
+ let flap_mean_position = (left_flaps_position + right_flaps_position) / 2.;
+ let slat_mean_position = (left_slats_position + right_slats_position) / 2.;
+
+ if flap_mean_position < 2. && slat_mean_position < 2. {
+ // Clean configuration no flaps no slats
+ 0.
+ } else if flap_mean_position < 12. && slat_mean_position > 15. {
+ // Almost no flaps but some slats -> CONF 1
+ 1.
+ } else if flap_mean_position > 80. {
+ 5.
+ } else if flap_mean_position > 49. {
+ 4.
+ } else if flap_mean_position > 30. {
+ 3.
+ } else if flap_mean_position > 12. {
+ 2.
+ } else {
+ 0.
+ }
+ }
+}
diff --git a/src/systems/a320_systems_wasm/src/lib.rs b/src/systems/a320_systems_wasm/src/lib.rs
index 26a2dae383b..537e5982fa8 100644
--- a/src/systems/a320_systems_wasm/src/lib.rs
+++ b/src/systems/a320_systems_wasm/src/lib.rs
@@ -1,36 +1,26 @@
#![cfg(any(target_arch = "wasm32", doc))]
-use std::{
- error::Error,
- time::{Duration, Instant},
-};
+mod autobrakes;
+mod brakes;
+mod flaps;
+mod nose_wheel_steering;
use a320_systems::A320;
-use msfs::sim_connect;
-use msfs::{
- legacy::{AircraftVariable, NamedVariable},
- sim_connect::SimConnect,
- sim_connect::{SimConnectRecv, SIMCONNECT_OBJECT_ID_USER},
- sys,
-};
-
-use systems::{
- failures::FailureType,
- shared::HydraulicColor,
- simulation::{VariableIdentifier, VariableRegistry},
-};
-use systems_wasm::{
- f64_to_sim_connect_32k_pos, sim_connect_32k_pos_to_f64, MsfsAspectCtor, MsfsSimulationBuilder,
- MsfsVariableRegistry, SimulatorAspect,
-};
+use autobrakes::autobrakes;
+use brakes::brakes;
+use flaps::flaps;
+use nose_wheel_steering::nose_wheel_steering;
+use std::error::Error;
+use systems::{failures::FailureType, shared::HydraulicColor};
+use systems_wasm::aspects::ExecuteOn;
+use systems_wasm::{MsfsSimulationBuilder, Variable};
#[msfs::gauge(name=systems)]
async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
let mut sim_connect = gauge.open_simconnect("systems")?;
let (mut simulation, mut handler) =
- MsfsSimulationBuilder::new("A32NX_".to_owned(), sim_connect.as_mut().get_mut())
+ MsfsSimulationBuilder::new("A32NX_", sim_connect.as_mut().get_mut())
.with_electrical_buses(vec![
- ("AC_1", 2),
("AC_1", 2),
("AC_2", 3),
("AC_ESS", 4),
@@ -46,12 +36,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
("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::()?
- .with::()?
+ .with_auxiliary_power_unit("OVHD_APU_START_PB_IS_AVAILABLE", 8)?
.with_failures(vec![
(24_000, FailureType::TransformerRectifier(1)),
(24_001, FailureType::TransformerRectifier(2)),
@@ -89,36 +74,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
.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(
- "BLEED AIR ENGINE",
- "Bool",
- 1,
- vec!["OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO".to_owned()],
- )?
- .provides_aircraft_variable_with_additional_names(
- "BLEED AIR ENGINE",
- "Bool",
- 2,
- vec!["OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO".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("EXTERNAL POWER AVAILABLE", "Bool", 1)?
.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)?
@@ -127,18 +83,6 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
.provides_aircraft_variable("GEAR LEFT POSITION", "Percent", 0)?
.provides_aircraft_variable("GEAR RIGHT 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)?
@@ -162,6 +106,52 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
.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)?
+ .with_aspect(|builder| {
+ builder.copy(
+ Variable::aircraft("APU GENERATOR SWITCH", "Bool", 0),
+ Variable::aspect("OVHD_ELEC_APU_GEN_PB_IS_ON"),
+ );
+
+ builder.copy(
+ Variable::aircraft("BLEED AIR ENGINE", "Bool", 1),
+ Variable::aspect("OVHD_PNEU_ENG_1_BLEED_PB_IS_AUTO"),
+ );
+ builder.copy(
+ Variable::aircraft("BLEED AIR ENGINE", "Bool", 2),
+ Variable::aspect("OVHD_PNEU_ENG_2_BLEED_PB_IS_AUTO"),
+ );
+
+ builder.copy(
+ Variable::aircraft("EXTERNAL POWER AVAILABLE", "Bool", 1),
+ Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_AVAILABLE"),
+ );
+ builder.copy(
+ Variable::aircraft("EXTERNAL POWER ON", "Bool", 1),
+ Variable::aspect("OVHD_ELEC_EXT_PWR_PB_IS_ON"),
+ );
+
+ builder.copy(
+ Variable::aircraft("GENERAL ENG MASTER ALTERNATOR", "Bool", 1),
+ Variable::aspect("OVHD_ELEC_ENG_GEN_1_PB_IS_ON"),
+ );
+ builder.copy(
+ Variable::aircraft("GENERAL ENG MASTER ALTERNATOR", "Bool", 2),
+ Variable::aspect("OVHD_ELEC_ENG_GEN_2_PB_IS_ON"),
+ );
+
+ builder.map(
+ ExecuteOn::PreTick,
+ Variable::aircraft("INTERACTIVE POINT OPEN", "Position", 5),
+ |value| if value > 0. { 1. } else { 0. },
+ Variable::aspect("FWD_DOOR_CARGO_OPEN_REQ"),
+ );
+
+ Ok(())
+ })?
+ .with_aspect(brakes)?
+ .with_aspect(autobrakes)?
+ .with_aspect(nose_wheel_steering)?
+ .with_aspect(flaps)?
.build(A320::new)?;
while let Some(event) = gauge.next_event().await {
@@ -170,1061 +160,3 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> {
Ok(())
}
-
-#[sim_connect::data_definition]
-struct FlapsSurface {
- #[name = "TRAILING EDGE FLAPS LEFT PERCENT"]
- #[unit = "Percent"]
- left_flap: f64,
-
- #[name = "TRAILING EDGE FLAPS RIGHT PERCENT"]
- #[unit = "Percent"]
- right_flap: f64,
-}
-
-#[sim_connect::data_definition]
-struct SlatsSurface {
- #[name = "LEADING EDGE FLAPS LEFT PERCENT"]
- #[unit = "Percent"]
- left_slat: f64,
-
- #[name = "LEADING EDGE FLAPS RIGHT PERCENT"]
- #[unit = "Percent"]
- right_slat: f64,
-}
-
-#[sim_connect::data_definition]
-struct FlapsHandleIndex {
- #[name = "FLAPS HANDLE INDEX"]
- #[unit = "Number"]
- index: f64,
-}
-
-struct Flaps {
- flaps_left_position_id: VariableIdentifier,
- flaps_right_position_id: VariableIdentifier,
- slats_left_position_id: VariableIdentifier,
- slats_right_position_id: VariableIdentifier,
-
- //IDs of the flaps handle events
- id_flaps_incr: sys::DWORD,
- id_flaps_decr: sys::DWORD,
- id_flaps_1: sys::DWORD,
- id_flaps_2: sys::DWORD,
- id_flaps_3: sys::DWORD,
- id_flaps_set: sys::DWORD,
- id_axis_flaps_set: sys::DWORD,
- id_flaps_down: sys::DWORD,
- id_flaps_up: sys::DWORD,
-
- //LVars to communicate between the flap movement logic
- //and the simulation animation
- flaps_handle_index_sim_var: NamedVariable,
- flaps_handle_percent_sim_var: NamedVariable,
- left_flaps_position_sim_var: NamedVariable,
- right_flaps_position_sim_var: NamedVariable,
- left_slats_position_sim_var: NamedVariable,
- right_slats_position_sim_var: NamedVariable,
-
- msfs_flaps_handle_index: FlapsHandleIndex,
- flaps_surface_sim_object: FlapsSurface,
- slats_surface_sim_object: SlatsSurface,
- flaps_handle_position: u8,
- left_flaps_position: f64,
- right_flaps_position: f64,
-
- left_slats_position: f64,
- right_slats_position: f64,
-}
-impl MsfsAspectCtor for Flaps {
- fn new(
- registry: &mut MsfsVariableRegistry,
- sim_connect: &mut SimConnect,
- ) -> Result> {
- Ok(Self {
- flaps_left_position_id: registry.get("LEFT_FLAPS_POSITION_PERCENT".to_owned()),
- flaps_right_position_id: registry.get("RIGHT_FLAPS_POSITION_PERCENT".to_owned()),
- slats_left_position_id: registry.get("LEFT_SLATS_POSITION_PERCENT".to_owned()),
- slats_right_position_id: registry.get("RIGHT_SLATS_POSITION_PERCENT".to_owned()),
-
- id_flaps_incr: sim_connect.map_client_event_to_sim_event("FLAPS_INCR", true)?,
- id_flaps_decr: sim_connect.map_client_event_to_sim_event("FLAPS_DECR", true)?,
- id_flaps_1: sim_connect.map_client_event_to_sim_event("FLAPS_1", true)?,
- id_flaps_2: sim_connect.map_client_event_to_sim_event("FLAPS_2", true)?,
- id_flaps_3: sim_connect.map_client_event_to_sim_event("FLAPS_3", true)?,
- id_flaps_set: sim_connect.map_client_event_to_sim_event("FLAPS_SET", true)?,
- id_axis_flaps_set: sim_connect.map_client_event_to_sim_event("AXIS_FLAPS_SET", true)?,
- id_flaps_down: sim_connect.map_client_event_to_sim_event("FLAPS_DOWN", true)?,
- id_flaps_up: sim_connect.map_client_event_to_sim_event("FLAPS_UP", true)?,
-
- flaps_handle_index_sim_var: NamedVariable::from("A32NX_FLAPS_HANDLE_INDEX"),
- flaps_handle_percent_sim_var: NamedVariable::from("A32NX_FLAPS_HANDLE_PERCENT"),
- left_flaps_position_sim_var: NamedVariable::from("A32NX_LEFT_FLAPS_POSITION_PERCENT"),
- right_flaps_position_sim_var: NamedVariable::from("A32NX_RIGHT_FLAPS_POSITION_PERCENT"),
- left_slats_position_sim_var: NamedVariable::from("A32NX_LEFT_SLATS_POSITION_PERCENT"),
- right_slats_position_sim_var: NamedVariable::from("A32NX_RIGHT_SLATS_POSITION_PERCENT"),
-
- msfs_flaps_handle_index: FlapsHandleIndex { index: 0. },
- flaps_surface_sim_object: FlapsSurface {
- left_flap: 0.,
- right_flap: 0.,
- },
- slats_surface_sim_object: SlatsSurface {
- left_slat: 0.,
- right_slat: 0.,
- },
-
- flaps_handle_position: 0,
- left_flaps_position: 0.,
- right_flaps_position: 0.,
-
- left_slats_position: 0.,
- right_slats_position: 0.,
- })
- }
-}
-
-impl Flaps {
- fn flaps_handle_position_f64(&self) -> f64 {
- self.flaps_handle_position as f64
- }
-
- /// Tries to take actual surfaces position PERCENTS and convert it into flight model FLAP HANDLE INDEX
- /// This index is used by MSFS to select correct aerodynamic properties
- /// There is no index available for flaps but no slats configurations (possible plane failure case)
- /// The percent thresholds can be tuned to change the timing of aerodynamic impact versus surface actual position
- fn msfs_flap_index_from_surfaces_positions_percent(&self) -> u8 {
- let flap_mean_position = (self.left_flaps_position + self.right_flaps_position) / 2.;
- let slat_mean_position = (self.left_slats_position + self.right_slats_position) / 2.;
-
- // Clean configuration no flaps no slats
- if flap_mean_position < 2. && slat_mean_position < 2. {
- return 0;
- }
-
- // Almost no flaps but some slats -> CONF 1
- if flap_mean_position < 12. && slat_mean_position > 15. {
- return 1;
- }
-
- if flap_mean_position > 80. {
- 5
- } else if flap_mean_position > 49. {
- 4
- } else if flap_mean_position > 30. {
- 3
- } else if flap_mean_position > 12. {
- 2
- } else {
- 0
- }
- }
-
- fn get_handle_pos_from_0_1(&self, input: f64) -> u8 {
- if input < -0.8 {
- 0
- } else if input > -0.7 && input < -0.3 {
- 1
- } else if input > -0.2 && input < 0.2 {
- 2
- } else if input > 0.3 && input < 0.7 {
- 3
- } else if input > 0.8 {
- 4
- } else {
- self.flaps_handle_position
- }
- }
-
- fn get_handle_pos_flaps_set(&self, input: i32) -> u8 {
- let normalized_input: f64 = (input as f64) / 8192. - 1.;
- return self.get_handle_pos_from_0_1(normalized_input);
- }
-
- fn get_handle_pos_axis_flaps_set(&self, input: i32) -> u8 {
- let normalized_input: f64 = (input as f64) / 16384.;
- return self.get_handle_pos_from_0_1(normalized_input);
- }
-
- fn catch_event(&mut self, event_id: sys::DWORD, event_data: u32) -> bool {
- if event_id == self.id_flaps_incr {
- self.flaps_handle_position += 1;
- self.flaps_handle_position = self.flaps_handle_position.min(4);
- } else if event_id == self.id_flaps_decr {
- if self.flaps_handle_position > 0 {
- self.flaps_handle_position -= 1;
- }
- } else if event_id == self.id_flaps_1 {
- self.flaps_handle_position = 1;
- } else if event_id == self.id_flaps_2 {
- self.flaps_handle_position = 2;
- } else if event_id == self.id_flaps_3 {
- self.flaps_handle_position = 3;
- } else if event_id == self.id_flaps_set {
- self.flaps_handle_position = self.get_handle_pos_flaps_set(event_data as i32);
- } else if event_id == self.id_axis_flaps_set {
- self.flaps_handle_position = self.get_handle_pos_axis_flaps_set(event_data as i32);
- } else if event_id == self.id_flaps_down {
- self.flaps_handle_position = 4;
- } else if event_id == self.id_flaps_up {
- self.flaps_handle_position = 0;
- } else {
- return false;
- }
- self.flaps_handle_index_sim_var
- .set_value(self.flaps_handle_position_f64());
- self.flaps_handle_percent_sim_var
- .set_value(self.flaps_handle_position_f64() / 4.);
- true
- }
-
- fn write_sim_vars(&mut self) {
- self.left_flaps_position_sim_var
- .set_value(self.left_flaps_position);
- self.right_flaps_position_sim_var
- .set_value(self.right_flaps_position);
- self.left_slats_position_sim_var
- .set_value(self.left_slats_position);
- self.right_slats_position_sim_var
- .set_value(self.right_slats_position);
- }
-}
-
-impl SimulatorAspect for Flaps {
- fn read(&mut self, identifier: &VariableIdentifier) -> Option {
- if identifier == &self.flaps_left_position_id {
- Some(self.left_flaps_position)
- } else if identifier == &self.flaps_right_position_id {
- Some(self.right_flaps_position)
- } else if identifier == &self.slats_left_position_id {
- Some(self.left_slats_position)
- } else if identifier == &self.slats_right_position_id {
- Some(self.right_slats_position)
- } else {
- None
- }
- }
-
- fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool {
- if identifier == &self.flaps_left_position_id {
- self.left_flaps_position = value;
- true
- } else if identifier == &self.flaps_right_position_id {
- self.right_flaps_position = value;
- true
- } else if identifier == &self.slats_left_position_id {
- self.left_slats_position = value;
- true
- } else if identifier == &self.slats_right_position_id {
- self.right_slats_position = value;
- true
- } else {
- false
- }
- }
-
- fn handle_message(&mut self, message: &SimConnectRecv) -> bool {
- if let SimConnectRecv::Event(e) = message {
- self.catch_event(e.id(), e.data())
- } else {
- false
- }
- }
-
- fn post_tick(&mut self, sim_connect: &mut SimConnect) -> Result<(), Box> {
- self.flaps_surface_sim_object.left_flap = self.left_flaps_position;
- self.flaps_surface_sim_object.right_flap = self.right_flaps_position;
- self.slats_surface_sim_object.left_slat = self.left_slats_position;
- self.slats_surface_sim_object.right_slat = self.right_slats_position;
- self.msfs_flaps_handle_index.index =
- self.msfs_flap_index_from_surfaces_positions_percent() as f64;
-
- self.write_sim_vars();
-
- sim_connect
- .set_data_on_sim_object(SIMCONNECT_OBJECT_ID_USER, &self.msfs_flaps_handle_index)?;
- sim_connect
- .set_data_on_sim_object(SIMCONNECT_OBJECT_ID_USER, &self.flaps_surface_sim_object)?;
- sim_connect
- .set_data_on_sim_object(SIMCONNECT_OBJECT_ID_USER, &self.slats_surface_sim_object)?;
-
- Ok(())
- }
-}
-
-struct Autobrakes {
- autobrake_disarm_id: VariableIdentifier,
- ovhd_autobrk_low_on_is_pressed_id: VariableIdentifier,
- ovhd_autobrk_med_on_is_pressed_id: VariableIdentifier,
- ovhd_autobrk_max_on_is_pressed_id: VariableIdentifier,
-
- id_mode_max: sys::DWORD,
- id_mode_med: sys::DWORD,
- id_mode_low: sys::DWORD,
- id_disarm: sys::DWORD,
-
- low_mode_panel_pushbutton: NamedVariable,
- med_mode_panel_pushbutton: NamedVariable,
- max_mode_panel_pushbutton: NamedVariable,
-
- low_mode_requested: bool,
- med_mode_requested: bool,
- max_mode_requested: bool,
- disarm_requested: bool,
-
- last_button_press: Instant,
-}
-
-impl MsfsAspectCtor for Autobrakes {
- 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)?,
- id_mode_low: sim_connect.map_client_event_to_sim_event("AUTOBRAKE_LO_SET", false)?,
- id_disarm: sim_connect.map_client_event_to_sim_event("AUTOBRAKE_DISARM", false)?,
-
- low_mode_panel_pushbutton: NamedVariable::from("A32NX_OVHD_AUTOBRK_LOW_ON_IS_PRESSED"),
- med_mode_panel_pushbutton: NamedVariable::from("A32NX_OVHD_AUTOBRK_MED_ON_IS_PRESSED"),
- max_mode_panel_pushbutton: NamedVariable::from("A32NX_OVHD_AUTOBRK_MAX_ON_IS_PRESSED"),
-
- low_mode_requested: false,
- med_mode_requested: false,
- max_mode_requested: false,
- disarm_requested: false,
-
- last_button_press: Instant::now(),
- })
- }
-}
-
-impl Autobrakes {
- // Time to freeze keyboard events once key is released. This will keep key_pressed to TRUE internally when key is actually staying pressed
- // but keyboard events wrongly goes to false then back to true for a short period of time due to poor key event handling
- const DEFAULT_REARMING_DURATION: Duration = Duration::from_millis(1500);
-
- fn synchronise_with_sim(&mut self) {
- if self.low_mode_panel_pushbutton.get_value() {
- self.set_mode_low();
- }
- if self.med_mode_panel_pushbutton.get_value() {
- self.set_mode_med();
- }
- if self.max_mode_panel_pushbutton.get_value() {
- self.set_mode_max();
- }
- }
-
- fn reset_events(&mut self) {
- if self.last_button_press.elapsed() > Self::DEFAULT_REARMING_DURATION {
- self.max_mode_requested = false;
- self.med_mode_requested = false;
- self.low_mode_requested = false;
- }
- self.disarm_requested = false;
- }
-
- fn on_receive_pushbutton_event(&mut self) {
- self.last_button_press = Instant::now();
- }
-
- fn set_mode_max(&mut self) {
- self.max_mode_requested = true;
- self.med_mode_requested = false;
- self.low_mode_requested = false;
- self.on_receive_pushbutton_event();
- }
-
- fn set_mode_med(&mut self) {
- self.med_mode_requested = true;
- self.max_mode_requested = false;
- self.low_mode_requested = false;
- self.on_receive_pushbutton_event();
- }
-
- fn set_mode_low(&mut self) {
- self.low_mode_requested = true;
- self.med_mode_requested = false;
- self.max_mode_requested = false;
- self.on_receive_pushbutton_event();
- }
-
- fn set_disarm(&mut self) {
- self.disarm_requested = true;
- }
-}
-impl SimulatorAspect for Autobrakes {
- 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
- }
- }
-
- fn handle_message(&mut self, message: &SimConnectRecv) -> bool {
- match message {
- SimConnectRecv::Event(e) => {
- if e.id() == self.id_mode_low {
- self.set_mode_low();
- true
- } else if e.id() == self.id_mode_med {
- self.set_mode_med();
- true
- } else if e.id() == self.id_mode_max {
- self.set_mode_max();
- true
- } else if e.id() == self.id_disarm {
- self.set_disarm();
- true
- } else {
- false
- }
- }
- _ => false,
- }
- }
-
- fn pre_tick(&mut self, _: Duration) {
- self.synchronise_with_sim();
- }
-
- fn post_tick(&mut self, _: &mut SimConnect) -> Result<(), Box> {
- self.reset_events();
-
- Ok(())
- }
-}
-
-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,
-
- id_brake_left: sys::DWORD,
- id_brake_right: sys::DWORD,
- id_brake_keyboard: sys::DWORD,
- id_brake_left_keyboard: sys::DWORD,
- id_brake_right_keyboard: sys::DWORD,
- id_parking_brake: sys::DWORD,
- id_parking_brake_set: sys::DWORD,
-
- brake_left_sim_input: f64,
- brake_right_sim_input: f64,
- brake_left_sim_input_keyboard: f64,
- brake_right_sim_input_keyboard: f64,
- left_key_pressed: bool,
- right_key_pressed: bool,
-
- brake_left_output_to_sim: f64,
- brake_right_output_to_sim: f64,
-
- parking_brake_lever_is_set: bool,
- last_transmitted_park_brake_lever_position: f64,
-}
-
-impl MsfsAspectCtor for Brakes {
- 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"),
-
- // SimConnect inputs masking
- id_brake_left: sim_connect
- .map_client_event_to_sim_event("AXIS_LEFT_BRAKE_SET", true)?,
- id_brake_right: sim_connect
- .map_client_event_to_sim_event("AXIS_RIGHT_BRAKE_SET", true)?,
-
- id_brake_keyboard: sim_connect.map_client_event_to_sim_event("BRAKES", true)?,
- id_brake_left_keyboard: sim_connect
- .map_client_event_to_sim_event("BRAKES_LEFT", true)?,
- id_brake_right_keyboard: sim_connect
- .map_client_event_to_sim_event("BRAKES_RIGHT", true)?,
-
- id_parking_brake: sim_connect.map_client_event_to_sim_event("PARKING_BRAKES", true)?,
- id_parking_brake_set: sim_connect
- .map_client_event_to_sim_event("PARKING_BRAKE_SET", true)?,
-
- brake_left_sim_input: 0.,
- brake_right_sim_input: 0.,
- brake_left_sim_input_keyboard: 0.,
- brake_right_sim_input_keyboard: 0.,
- left_key_pressed: false,
- right_key_pressed: false,
-
- brake_left_output_to_sim: 0.,
- brake_right_output_to_sim: 0.,
-
- parking_brake_lever_is_set: true,
-
- last_transmitted_park_brake_lever_position: 1.,
- })
- }
-}
-
-impl Brakes {
- const KEYBOARD_PRESS_SPEED: f64 = 0.6;
- const KEYBOARD_RELEASE_SPEED: f64 = 0.3;
-
- fn set_brake_left(&mut self, simconnect_value: u32) {
- self.brake_left_sim_input = sim_connect_32k_pos_to_f64(simconnect_value);
- }
-
- fn set_brake_left_key_pressed(&mut self) {
- self.left_key_pressed = true;
- }
-
- fn set_brake_right_key_pressed(&mut self) {
- self.right_key_pressed = true;
- }
-
- fn synchronise_with_sim(&mut self) {
- // Synchronising WASM park brake state with simulator park brake lever variable
- let current_in_sim_park_brake: f64 = self.park_brake_lever_masked_input.get_value();
-
- if current_in_sim_park_brake != self.last_transmitted_park_brake_lever_position {
- self.receive_a_park_brake_set_event(current_in_sim_park_brake as u32);
- }
- }
-
- fn update_keyboard_inputs(&mut self, delta: Duration) {
- if self.left_key_pressed {
- self.brake_left_sim_input_keyboard += delta.as_secs_f64() * Self::KEYBOARD_PRESS_SPEED;
- } else {
- self.brake_left_sim_input_keyboard -=
- delta.as_secs_f64() * Self::KEYBOARD_RELEASE_SPEED;
- }
-
- if self.right_key_pressed {
- self.brake_right_sim_input_keyboard += delta.as_secs_f64() * Self::KEYBOARD_PRESS_SPEED;
- } else {
- self.brake_right_sim_input_keyboard -=
- delta.as_secs_f64() * Self::KEYBOARD_RELEASE_SPEED;
- }
-
- self.brake_right_sim_input_keyboard = self.brake_right_sim_input_keyboard.min(1.).max(0.);
- self.brake_left_sim_input_keyboard = self.brake_left_sim_input_keyboard.min(1.).max(0.);
- }
-
- fn reset_keyboard_events(&mut self) {
- self.left_key_pressed = false;
- self.right_key_pressed = false;
- }
-
- fn transmit_masked_inputs(&mut self) {
- let park_is_set = self.parking_brake_lever_is_set as u32 as f64;
- self.last_transmitted_park_brake_lever_position = park_is_set;
- self.park_brake_lever_masked_input.set_value(park_is_set);
-
- let brake_right = self.brake_right() * 100.;
- let brake_left = self.brake_left() * 100.;
- self.right_pedal_brake_masked_input.set_value(brake_right);
- self.left_pedal_brake_masked_input.set_value(brake_left);
- }
-
- fn transmit_client_events(
- &mut self,
- sim_connect: &mut SimConnect,
- ) -> Result<(), Box> {
- // We want to send our brake commands once per refresh event, thus doing it after a draw event
- sim_connect.transmit_client_event(
- SIMCONNECT_OBJECT_ID_USER,
- self.id_brake_left,
- self.get_brake_left_output_converted_in_simconnect_format(),
- )?;
-
- sim_connect.transmit_client_event(
- SIMCONNECT_OBJECT_ID_USER,
- self.id_brake_right,
- self.get_brake_right_output_converted_in_simconnect_format(),
- )?;
-
- Ok(())
- }
-
- fn set_brake_right(&mut self, simconnect_value: u32) {
- self.brake_right_sim_input = sim_connect_32k_pos_to_f64(simconnect_value);
- }
-
- fn brake_left(&mut self) -> f64 {
- self.brake_left_sim_input
- .max(self.brake_left_sim_input_keyboard)
- }
-
- fn brake_right(&mut self) -> f64 {
- self.brake_right_sim_input
- .max(self.brake_right_sim_input_keyboard)
- }
-
- fn set_brake_right_output(&mut self, brake_force_factor: f64) {
- self.brake_right_output_to_sim = brake_force_factor;
- }
-
- fn set_brake_left_output(&mut self, brake_force_factor: f64) {
- self.brake_left_output_to_sim = brake_force_factor;
- }
-
- fn receive_a_park_brake_event(&mut self) {
- self.parking_brake_lever_is_set = !self.parking_brake_lever_is_set;
- }
-
- fn receive_a_park_brake_set_event(&mut self, data: u32) {
- self.parking_brake_lever_is_set = data == 1;
- }
-
- fn get_brake_right_output_converted_in_simconnect_format(&mut self) -> u32 {
- f64_to_sim_connect_32k_pos(self.brake_right_output_to_sim)
- }
-
- fn get_brake_left_output_converted_in_simconnect_format(&mut self) -> u32 {
- f64_to_sim_connect_32k_pos(self.brake_left_output_to_sim)
- }
-
- fn is_park_brake_set(&self) -> f64 {
- if self.parking_brake_lever_is_set {
- 1.
- } else {
- 0.
- }
- }
-}
-impl SimulatorAspect for Brakes {
- 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, 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
- }
- }
-
- fn handle_message(&mut self, message: &SimConnectRecv) -> bool {
- match message {
- SimConnectRecv::Event(e) => {
- if e.id() == self.id_brake_left {
- self.set_brake_left(e.data());
- true
- } else if e.id() == self.id_brake_right {
- self.set_brake_right(e.data());
- true
- } else if e.id() == self.id_parking_brake {
- self.receive_a_park_brake_event();
- true
- } else if e.id() == self.id_parking_brake_set {
- self.receive_a_park_brake_set_event(e.data());
- true
- } else if e.id() == self.id_brake_keyboard {
- self.set_brake_left_key_pressed();
- self.set_brake_right_key_pressed();
- true
- } else if e.id() == self.id_brake_left_keyboard {
- self.set_brake_left_key_pressed();
- true
- } else if e.id() == self.id_brake_right_keyboard {
- self.set_brake_right_key_pressed();
- true
- } else {
- false
- }
- }
- _ => false,
- }
- }
-
- fn pre_tick(&mut self, delta: Duration) {
- self.synchronise_with_sim();
- self.update_keyboard_inputs(delta);
- }
-
- fn post_tick(&mut self, sim_connect: &mut SimConnect) -> Result<(), Box> {
- self.reset_keyboard_events();
- self.transmit_client_events(sim_connect)?;
- self.transmit_masked_inputs();
-
- Ok(())
- }
-}
-
-struct NoseWheelSteering {
- realistic_tiller_axis_var: NamedVariable,
- is_realistic_tiller_mode: bool,
-
- tiller_handle_position_id: VariableIdentifier,
- tiller_handle_position_var: NamedVariable,
-
- rudder_pedal_position_id: VariableIdentifier,
-
- rudder_position_var: AircraftVariable,
- rudder_position: f64,
-
- nose_wheel_position_id: VariableIdentifier,
- nose_wheel_position_var: NamedVariable,
- nose_wheel_position: f64,
-
- rudder_pedal_position_var: NamedVariable,
- rudder_pedal_position: f64,
-
- tiller_handle_position_event: sys::DWORD,
- tiller_handle_position: f64,
-
- nose_wheel_angle_event: sys::DWORD,
- nose_wheel_angle_inc_event: sys::DWORD,
- nose_wheel_angle_dec_event: sys::DWORD,
-
- pedal_disconnect_event: sys::DWORD,
- pedal_disconnect_id: VariableIdentifier,
- pedal_disconnect: bool,
-}
-
-impl MsfsAspectCtor for NoseWheelSteering {
- fn new(
- registry: &mut MsfsVariableRegistry,
- sim_connect: &mut SimConnect,
- ) -> Result> {
- Ok(Self {
- realistic_tiller_axis_var: NamedVariable::from("A32NX_REALISTIC_TILLER_ENABLED"),
- is_realistic_tiller_mode: false,
-
- tiller_handle_position_id: registry.get("TILLER_HANDLE_POSITION".to_owned()),
- tiller_handle_position_var: NamedVariable::from("A32NX_TILLER_HANDLE_POSITION"),
-
- rudder_pedal_position_id: registry.get("RUDDER_PEDAL_POSITION".to_owned()),
-
- rudder_position_var: AircraftVariable::from("RUDDER POSITION", "Position", 0)?,
- rudder_position: 0.5,
-
- nose_wheel_position_id: registry.get("NOSE_WHEEL_POSITION".to_owned()),
- nose_wheel_position_var: NamedVariable::from("A32NX_NOSE_WHEEL_POSITION"),
- nose_wheel_position: 0.,
-
- rudder_pedal_position_var: NamedVariable::from("A32NX_RUDDER_PEDAL_POSITION"),
- rudder_pedal_position: 0.5,
-
- tiller_handle_position_event: sim_connect
- .map_client_event_to_sim_event("AXIS_MIXTURE4_SET", true)?,
- tiller_handle_position: 0.5,
-
- nose_wheel_angle_event: sim_connect
- .map_client_event_to_sim_event("STEERING_SET", true)?,
- nose_wheel_angle_inc_event: sim_connect
- .map_client_event_to_sim_event("STEERING_INC", true)?,
- nose_wheel_angle_dec_event: sim_connect
- .map_client_event_to_sim_event("STEERING_DEC", true)?,
-
- pedal_disconnect_event: sim_connect
- .map_client_event_to_sim_event("TOGGLE_WATER_RUDDER", true)?,
- pedal_disconnect_id: registry.get("TILLER_PEDAL_DISCONNECT".to_owned()),
- pedal_disconnect: false,
- })
- }
-}
-impl NoseWheelSteering {
- const MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES: f64 = 75.;
- const MAX_MSFS_STEERING_ANGLE_DEGREES: f64 = 90.;
- const STEERING_ANIMATION_TOTAL_RANGE_DEGREES: f64 = 360.;
-
- const TILLER_KEYBOARD_INCREMENTS: f64 = 0.05;
-
- fn set_tiller_handle(&mut self, simconnect_value: u32) {
- self.tiller_handle_position = sim_connect_32k_pos_to_f64(simconnect_value);
- }
-
- fn decrement_tiller(&mut self) {
- self.tiller_handle_position -= Self::TILLER_KEYBOARD_INCREMENTS;
- self.tiller_handle_position = self.tiller_handle_position.min(1.).max(0.);
-
- self.tiller_key_event_centering();
- }
-
- fn increment_tiller(&mut self) {
- self.tiller_handle_position += Self::TILLER_KEYBOARD_INCREMENTS;
- self.tiller_handle_position = self.tiller_handle_position.min(1.).max(0.);
-
- self.tiller_key_event_centering();
- }
-
- fn tiller_key_event_centering(&mut self) {
- if self.tiller_handle_position < 0.5 + Self::TILLER_KEYBOARD_INCREMENTS
- && self.tiller_handle_position > 0.5 - Self::TILLER_KEYBOARD_INCREMENTS
- {
- self.tiller_handle_position = 0.5;
- }
- }
-
- fn set_pedal_disconnect(&mut self, is_disconnected: bool) {
- self.pedal_disconnect = is_disconnected;
- }
-
- /// Steering position is [-1;1] -1 is left, 0 is straight
- fn set_steering_position(&mut self, steering_position: f64) {
- self.nose_wheel_position = steering_position;
- }
-
- /// Tiller position in [-1;1] range, -1 is left
- fn tiller_handle_position(&self) -> f64 {
- self.tiller_handle_position * 2. - 1.
- }
-
- /// Rudder pedal position in [-1;1] range, -1 is left
- fn rudder_pedal_position(&self) -> f64 {
- self.rudder_pedal_position * 2. - 1.
- }
-
- fn set_realistic_tiller_mode(&mut self, is_active: bool) {
- self.is_realistic_tiller_mode = is_active;
- }
-
- fn synchronise_with_sim(&mut self) {
- let rudder_percent: f64 = self.rudder_pedal_position_var.get_value();
- self.rudder_pedal_position = (rudder_percent + 100.) / 200.;
-
- let rudder_position: f64 = self.rudder_position_var.get();
- self.rudder_position = (rudder_position + 1.) / 2.;
-
- let realistic_mode: f64 = self.realistic_tiller_axis_var.get_value();
- self.set_realistic_tiller_mode(realistic_mode > 0.);
- }
-
- fn final_tiller_position_sent_to_systems(&self) -> f64 {
- if self.is_realistic_tiller_mode {
- self.tiller_handle_position()
- } else {
- if !self.pedal_disconnect {
- self.rudder_pedal_position()
- } else {
- 0.
- }
- }
- }
-
- fn final_rudder_pedal_position_sent_to_systems(&self) -> f64 {
- if self.is_realistic_tiller_mode {
- self.rudder_pedal_position()
- } else {
- 0.
- }
- }
-
- fn steering_demand_to_msfs_from_steering_angle(&self) -> f64 {
- // Steering in msfs is the max we want rescaled to the max in msfs
- let steering_ratio_converted = self.nose_wheel_position
- * Self::MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES
- / Self::MAX_MSFS_STEERING_ANGLE_DEGREES
- / 2.
- + 0.5;
-
- // Steering demand is reverted in msfs so we do 1 - angle.
- // Then we hack msfs by adding the rudder value that it will always substract internally
- // This way we end up with actual angle we required
- (1. - steering_ratio_converted) + (self.rudder_position - 0.5)
- }
-
- fn steering_animation_to_msfs_from_steering_angle(&self) -> f64 {
- ((self.nose_wheel_position * Self::MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES
- / (Self::STEERING_ANIMATION_TOTAL_RANGE_DEGREES / 2.))
- / 2.)
- + 0.5
- }
-
- fn write_animation_position_to_sim(&self) {
- self.tiller_handle_position_var
- .set_value((self.final_tiller_position_sent_to_systems() + 1.) / 2.);
-
- self.nose_wheel_position_var
- .set_value(self.steering_animation_to_msfs_from_steering_angle());
- }
-
- fn transmit_client_events(
- &mut self,
- sim_connect: &mut SimConnect,
- ) -> Result<(), Box> {
- sim_connect.transmit_client_event(
- SIMCONNECT_OBJECT_ID_USER,
- self.nose_wheel_angle_event,
- f64_to_sim_connect_32k_pos(self.steering_demand_to_msfs_from_steering_angle()),
- )?;
-
- Ok(())
- }
-}
-impl SimulatorAspect for NoseWheelSteering {
- fn read(&mut self, identifier: &VariableIdentifier) -> Option {
- if identifier == &self.tiller_handle_position_id {
- Some(self.final_tiller_position_sent_to_systems())
- } else if identifier == &self.rudder_pedal_position_id {
- Some(self.final_rudder_pedal_position_sent_to_systems())
- } else if identifier == &self.pedal_disconnect_id {
- Some(self.pedal_disconnect as u8 as f64)
- } else {
- None
- }
- }
-
- fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool {
- if identifier == &self.nose_wheel_position_id {
- self.set_steering_position(value);
- true
- } else {
- false
- }
- }
-
- fn handle_message(&mut self, message: &SimConnectRecv) -> bool {
- match message {
- SimConnectRecv::Event(e) => {
- if e.id() == self.tiller_handle_position_event {
- self.set_tiller_handle(e.data());
- true
- } else if e.id() == self.pedal_disconnect_event {
- self.set_pedal_disconnect(true);
- true
- } else if e.id() == self.nose_wheel_angle_dec_event {
- self.decrement_tiller();
- true
- } else if e.id() == self.nose_wheel_angle_inc_event {
- self.increment_tiller();
- true
- } else {
- false
- }
- }
- _ => false,
- }
- }
-
- fn pre_tick(&mut self, _: Duration) {
- self.synchronise_with_sim();
- }
-
- fn post_tick(&mut self, sim_connect: &mut SimConnect) -> Result<(), Box> {
- self.transmit_client_events(sim_connect)?;
- self.write_animation_position_to_sim();
- self.set_pedal_disconnect(false);
-
- Ok(())
- }
-}
-
-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(
- 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",
- "Position",
- 5,
- )?,
- fwd_position: 0.,
- forward_cargo_door_open_req: 0.,
- })
- }
-}
-
-impl CargoDoors {
- fn set_forward_door_postition(&mut self, value: f64) {
- self.fwd_position = value;
- }
-
- fn set_in_sim_position_request(&mut self, position_requested: f64) {
- if position_requested > 0. {
- self.forward_cargo_door_open_req = 1.;
- } else {
- self.forward_cargo_door_open_req = 0.;
- }
- }
-}
-impl SimulatorAspect for CargoDoors {
- 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, 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
- }
- }
-
- fn pre_tick(&mut self, _: Duration) {
- let read_val = self.forward_cargo_door_sim_position_request.get();
- self.set_in_sim_position_request(read_val);
- }
-
- fn post_tick(&mut self, _: &mut SimConnect) -> Result<(), Box> {
- self.forward_cargo_door_position
- .set_value(self.fwd_position);
-
- Ok(())
- }
-}
diff --git a/src/systems/a320_systems_wasm/src/nose_wheel_steering.rs b/src/systems/a320_systems_wasm/src/nose_wheel_steering.rs
new file mode 100644
index 00000000000..79c950c7d2f
--- /dev/null
+++ b/src/systems/a320_systems_wasm/src/nose_wheel_steering.rs
@@ -0,0 +1,179 @@
+use std::error::Error;
+use systems::shared::to_bool;
+use systems_wasm::aspects::{
+ EventToVariableMapping, ExecuteOn, MsfsAspectBuilder, VariableToEventMapping,
+ VariableToEventWriteOn,
+};
+use systems_wasm::Variable;
+
+pub(super) fn nose_wheel_steering(builder: &mut MsfsAspectBuilder) -> Result<(), Box> {
+ // The rudder pedals should start in a centered position.
+ builder.init_variable(Variable::aspect("RAW_RUDDER_PEDAL_POSITION"), 0.5);
+
+ builder.map(
+ ExecuteOn::PreTick,
+ Variable::named("RUDDER_PEDAL_POSITION"),
+ // Convert rudder pedal position to [-1;1], -1 is left
+ |value| ((value + 100.) / 200.) * 2. - 1.,
+ Variable::aspect("RAW_RUDDER_PEDAL_POSITION"),
+ );
+
+ builder.map_many(
+ ExecuteOn::PostTick,
+ vec![
+ Variable::named("REALISTIC_TILLER_ENABLED"),
+ Variable::aspect("RAW_RUDDER_PEDAL_POSITION"),
+ ],
+ |values| {
+ let realistic_tiller_enabled = to_bool(values[0]);
+ let rudder_pedal_position = values[1];
+ if realistic_tiller_enabled {
+ rudder_pedal_position
+ } else {
+ 0.
+ }
+ },
+ Variable::aspect("RUDDER_PEDAL_POSITION_RATIO"),
+ );
+
+ // The tiller handle should start in a centered position.
+ builder.init_variable(Variable::aspect("RAW_TILLER_HANDLE_POSITION"), 0.5);
+
+ // Lacking a better event to bind to, we've picked a mixture axis for setting the
+ // tiller handle position.
+ builder.event_to_variable(
+ "AXIS_MIXTURE4_SET",
+ EventToVariableMapping::EventData32kPosition,
+ Variable::aspect("RAW_TILLER_HANDLE_POSITION"),
+ |options| options.mask(),
+ )?;
+
+ const TILLER_KEYBOARD_INCREMENTS: f64 = 0.05;
+ builder.event_to_variable(
+ "STEERING_INC",
+ EventToVariableMapping::CurrentValueToValue(|current_value| {
+ recenter_when_close_to_center(
+ (current_value + TILLER_KEYBOARD_INCREMENTS).min(1.),
+ TILLER_KEYBOARD_INCREMENTS,
+ )
+ }),
+ Variable::aspect("RAW_TILLER_HANDLE_POSITION"),
+ |options| options.mask(),
+ )?;
+ builder.event_to_variable(
+ "STEERING_DEC",
+ EventToVariableMapping::CurrentValueToValue(|current_value| {
+ recenter_when_close_to_center(
+ (current_value - TILLER_KEYBOARD_INCREMENTS).max(0.),
+ TILLER_KEYBOARD_INCREMENTS,
+ )
+ }),
+ Variable::aspect("RAW_TILLER_HANDLE_POSITION"),
+ |options| options.mask(),
+ )?;
+
+ // Lacking a better event to bind to, we've picked the toggle water rudder event for
+ // disconnecting the rudder pedals via the PEDALS DISC button on the tiller.
+ builder.event_to_variable(
+ "TOGGLE_WATER_RUDDER",
+ EventToVariableMapping::Value(1.),
+ Variable::aspect("TILLER_PEDAL_DISCONNECT"),
+ |options| options.mask().afterwards_reset_to(0.),
+ )?;
+
+ builder.map_many(
+ ExecuteOn::PostTick,
+ vec![
+ Variable::named("REALISTIC_TILLER_ENABLED"),
+ Variable::aspect("RAW_RUDDER_PEDAL_POSITION"),
+ Variable::aspect("RAW_TILLER_HANDLE_POSITION"),
+ Variable::aspect("TILLER_PEDAL_DISCONNECT"),
+ ],
+ |values| {
+ let realistic_tiller_enabled = to_bool(values[0]);
+ let rudder_pedal_position = values[1];
+ let tiller_handle_position = values[2];
+ let tiller_pedal_disconnect = to_bool(values[3]);
+
+ if realistic_tiller_enabled {
+ // Convert tiller handle position to [-1;1], -1 is left
+ tiller_handle_position * 2. - 1.
+ } else {
+ if !tiller_pedal_disconnect {
+ rudder_pedal_position
+ } else {
+ 0.
+ }
+ }
+ },
+ Variable::named("TILLER_HANDLE_POSITION"),
+ );
+
+ builder.map(
+ ExecuteOn::PostTick,
+ Variable::aspect("NOSE_WHEEL_POSITION_RATIO"),
+ steering_animation_to_msfs_from_steering_angle,
+ Variable::named("NOSE_WHEEL_POSITION"),
+ );
+
+ builder.map_many(
+ ExecuteOn::PostTick,
+ vec![
+ Variable::aspect("NOSE_WHEEL_POSITION_RATIO"),
+ Variable::aircraft("RUDDER POSITION", "Position", 0),
+ ],
+ |values| {
+ let nose_wheel_position = values[0];
+ let rudder_position = (values[1] + 1.) / 2.;
+
+ steering_demand_to_msfs_from_steering_angle(nose_wheel_position, rudder_position)
+ },
+ Variable::aspect("STEERING_ANGLE"),
+ );
+
+ builder.variable_to_event(
+ Variable::aspect("STEERING_ANGLE"),
+ VariableToEventMapping::EventData32kPosition,
+ VariableToEventWriteOn::EveryTick,
+ "STEERING_SET",
+ )?;
+
+ Ok(())
+}
+
+fn recenter_when_close_to_center(value: f64, increment: f64) -> f64 {
+ if value < 0.5 + increment && value > 0.5 - increment {
+ 0.5
+ } else {
+ value
+ }
+}
+
+const MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES: f64 = 75.;
+
+fn steering_animation_to_msfs_from_steering_angle(nose_wheel_position: f64) -> f64 {
+ const STEERING_ANIMATION_TOTAL_RANGE_DEGREES: f64 = 360.;
+
+ ((nose_wheel_position * MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES
+ / (STEERING_ANIMATION_TOTAL_RANGE_DEGREES / 2.))
+ / 2.)
+ + 0.5
+}
+
+fn steering_demand_to_msfs_from_steering_angle(
+ nose_wheel_position: f64,
+ rudder_position: f64,
+) -> f64 {
+ const MAX_MSFS_STEERING_ANGLE_DEGREES: f64 = 90.;
+
+ // Steering in msfs is the max we want rescaled to the max in msfs
+ let steering_ratio_converted = nose_wheel_position * MAX_CONTROLLABLE_STEERING_ANGLE_DEGREES
+ / MAX_MSFS_STEERING_ANGLE_DEGREES
+ / 2.
+ + 0.5;
+
+ // Steering demand is reverted in msfs so we do 1 - angle.
+ // Then we hack msfs by adding the rudder value that it will always substract internally
+ // This way we end up with actual angle we required
+ (1. - steering_ratio_converted) + (rudder_position - 0.5)
+}
diff --git a/src/systems/systems/src/hydraulic/nose_steering.rs b/src/systems/systems/src/hydraulic/nose_steering.rs
index 17ff7aaf8ec..5bb3c8e7075 100644
--- a/src/systems/systems/src/hydraulic/nose_steering.rs
+++ b/src/systems/systems/src/hydraulic/nose_steering.rs
@@ -125,7 +125,7 @@ impl SteeringActuator {
angular_to_linear_ratio: Ratio,
) -> Self {
Self {
- position_id: context.get_identifier("NOSE_WHEEL_POSITION".to_owned()),
+ position_id: context.get_identifier("NOSE_WHEEL_POSITION_RATIO".to_owned()),
current_speed: LowPassFilter::::new(
Self::CURRENT_SPEED_FILTER_TIMECONST,
@@ -402,7 +402,7 @@ mod tests {
test_bed.run();
- assert!(test_bed.contains_variable_with_name("NOSE_WHEEL_POSITION"));
+ assert!(test_bed.contains_variable_with_name("NOSE_WHEEL_POSITION_RATIO"));
}
#[test]
@@ -419,7 +419,7 @@ mod tests {
actuator_position_init
));
- let normalized_position: f64 = test_bed.read_by_name("NOSE_WHEEL_POSITION");
+ let normalized_position: f64 = test_bed.read_by_name("NOSE_WHEEL_POSITION_RATIO");
assert!(normalized_position == 0.);
}
diff --git a/src/systems/systems/src/shared/mod.rs b/src/systems/systems/src/shared/mod.rs
index 54f156c03f7..8d0b2296db9 100644
--- a/src/systems/systems/src/shared/mod.rs
+++ b/src/systems/systems/src/shared/mod.rs
@@ -408,6 +408,15 @@ pub fn interpolation(xs: &[f64], ys: &[f64], intermediate_x: f64) -> f64 {
}
}
+/// Converts a given `bool` value into an `f64` representing that boolean value in the simulator.
+pub fn from_bool(value: bool) -> f64 {
+ if value {
+ 1.0
+ } else {
+ 0.0
+ }
+}
+
pub fn to_bool(value: f64) -> bool {
(value - 1.).abs() < f64::EPSILON
}
diff --git a/src/systems/systems/src/simulation/mod.rs b/src/systems/systems/src/simulation/mod.rs
index 302469882af..124c23d0f8b 100644
--- a/src/systems/systems/src/simulation/mod.rs
+++ b/src/systems/systems/src/simulation/mod.rs
@@ -2,7 +2,7 @@ use std::time::Duration;
mod update_context;
use crate::electrical::{ElectricalElementIdentifier, ElectricalElementIdentifierProvider};
-use crate::shared::ElectricalBusType;
+use crate::shared::{from_bool, ElectricalBusType};
use crate::{
electrical::Electricity,
failures::FailureType,
@@ -39,9 +39,9 @@ pub trait VariableRegistry {
pub struct VariableIdentifier(u8, usize);
impl VariableIdentifier {
- pub fn new(identifier_type: u8) -> Self {
+ pub fn new>(variable_type: T) -> Self {
Self {
- 0: identifier_type,
+ 0: variable_type.into(),
1: 0,
}
}
@@ -501,15 +501,6 @@ impl<'a> Writer for SimulatorWriter<'a> {
}
}
-/// Converts a given `bool` value into an `f64` representing that boolean value in the simulator.
-fn from_bool(value: bool) -> f64 {
- if value {
- 1.0
- } else {
- 0.0
- }
-}
-
pub trait Read {
/// Reads a value from the simulator.
/// # Examples
diff --git a/src/systems/systems_wasm/Cargo.toml b/src/systems/systems_wasm/Cargo.toml
index 1530c5ca714..f07069fa474 100644
--- a/src/systems/systems_wasm/Cargo.toml
+++ b/src/systems/systems_wasm/Cargo.toml
@@ -14,3 +14,4 @@ uom = "0.30.0"
systems = { path = "../systems" }
msfs = { git = "https://github.com/flybywiresim/msfs-rs", branch = "main" }
fxhash = "0.2.1"
+enum_dispatch = "0.3.7"
\ No newline at end of file
diff --git a/src/systems/systems_wasm/src/aspects.rs b/src/systems/systems_wasm/src/aspects.rs
new file mode 100644
index 00000000000..d7a8e2aa087
--- /dev/null
+++ b/src/systems/systems_wasm/src/aspects.rs
@@ -0,0 +1,852 @@
+use crate::{
+ f64_to_sim_connect_32k_pos, sim_connect_32k_pos_to_f64, Aspect, MsfsVariableRegistry, Variable,
+};
+use enum_dispatch::enum_dispatch;
+use msfs::sim_connect::{SimConnect, SimConnectRecv, SIMCONNECT_OBJECT_ID_USER};
+use std::error::Error;
+use std::time::{Duration, Instant};
+use systems::simulation::VariableIdentifier;
+
+/// Type used to configure and build an [Aspect].
+///
+/// It should be noted that the resulting [Aspect] executes its tasks in the order in which they
+/// were declared. Declaration order is important when one action depends on another action.
+/// When e.g. a variable that is mapped should then be written to an event, be sure to
+/// declare the mapping before the event writing.
+pub struct MsfsAspectBuilder<'a, 'b> {
+ sim_connect: &'a mut SimConnect<'b>,
+ variables: &'a mut MsfsVariableRegistry,
+ message_handlers: Vec,
+ actions: Vec<(VariableAction, ExecuteOn)>,
+}
+
+impl<'a, 'b> MsfsAspectBuilder<'a, 'b> {
+ pub fn new(
+ sim_connect: &'a mut SimConnect<'b>,
+ variables: &'a mut MsfsVariableRegistry,
+ ) -> Self {
+ Self {
+ sim_connect,
+ variables,
+ message_handlers: Default::default(),
+ actions: Default::default(),
+ }
+ }
+
+ pub fn build(self) -> MsfsAspect {
+ let aspect = MsfsAspect::new(self.message_handlers, self.actions);
+
+ aspect
+ }
+
+ /// Initialise the variable with the given value.
+ pub fn init_variable(&mut self, variable: Variable, value: f64) {
+ Self::precondition_not_aircraft_variable(&variable);
+
+ let identifier = self.variables.register(&variable);
+ self.variables.write(&identifier, value);
+ }
+
+ /// Copy a variable's value to another variable.
+ pub fn copy(&mut self, input: Variable, output: Variable) {
+ Self::precondition_not_aircraft_variable(&output);
+
+ let input = self.variables.register(&input);
+ let output = self.variables.register(&output);
+
+ self.actions.push((
+ Map::new(input, |value| value, output).into(),
+ ExecuteOn::PreTick,
+ ));
+ }
+
+ /// Map a variable's value to another variable, applying the given function in the process.
+ pub fn map(
+ &mut self,
+ execute_on: ExecuteOn,
+ input: Variable,
+ func: fn(f64) -> f64,
+ output: Variable,
+ ) {
+ Self::precondition_not_aircraft_variable(&output);
+
+ let inputs = self.variables.register(&input);
+ let output = self.variables.register(&output);
+
+ self.actions
+ .push((Map::new(inputs, func, output).into(), execute_on));
+ }
+
+ /// Map a set of variable values to another variable.
+ pub fn map_many(
+ &mut self,
+ execute_on: ExecuteOn,
+ inputs: Vec,
+ func: fn(&[f64]) -> f64,
+ output: Variable,
+ ) {
+ Self::precondition_not_aircraft_variable(&output);
+
+ let inputs = self.variables.register_many(&inputs);
+ let output = self.variables.register(&output);
+
+ self.actions
+ .push((MapMany::new(inputs, func, output).into(), execute_on));
+ }
+
+ /// Reduce a set of variable values into one output value and write it to a variable.
+ pub fn reduce(
+ &mut self,
+ execute_on: ExecuteOn,
+ inputs: Vec,
+ init: f64,
+ func: fn(f64, f64) -> f64,
+ output: Variable,
+ ) {
+ Self::precondition_not_aircraft_variable(&output);
+
+ let inputs = self.variables.register_many(&inputs);
+ let output = self.variables.register(&output);
+
+ self.actions
+ .push((Reduce::new(inputs, init, func, output).into(), execute_on));
+ }
+
+ /// Write a set of variables to an object.
+ pub fn variables_to_object(&mut self, instance: Box) {
+ let variables = self.variables.register_many(&instance.variables());
+
+ self.actions.push((
+ ToObject::new(instance, variables).into(),
+ ExecuteOn::PostTick,
+ ));
+ }
+
+ /// Convert event occurrences to a variable.
+ ///
+ /// If you want to write a variable back to the same event, then use the
+ /// event id returned by this method and pass it to [Self::variable_to_event_id].
+ pub fn event_to_variable(
+ &mut self,
+ event_name: &str,
+ mapping: EventToVariableMapping,
+ target: Variable,
+ configure_options: fn(EventToVariableOptions) -> EventToVariableOptions,
+ ) -> Result> {
+ Self::precondition_not_aircraft_variable(&target);
+
+ let target = self.variables.register(&target);
+
+ let event_to_variable = EventToVariable::new(
+ &mut self.sim_connect,
+ event_name,
+ mapping,
+ target,
+ configure_options(EventToVariableOptions::default()),
+ )?;
+
+ let event_id = event_to_variable.event_id;
+
+ self.message_handlers.push(event_to_variable.into());
+
+ Ok(event_id)
+ }
+
+ /// Write the variable's value to an event. If you use [Self::event_to_variable] for the same
+ /// event, then you should use [Self::variable_to_event_id] instead.
+ pub fn variable_to_event(
+ &mut self,
+ input: Variable,
+ mapping: VariableToEventMapping,
+ write_on: VariableToEventWriteOn,
+ event_name: &str,
+ ) -> Result<(), Box> {
+ let input = self.variables.register(&input);
+
+ self.actions.push((
+ ToEvent::new(&mut self.sim_connect, input, mapping, write_on, event_name)?.into(),
+ ExecuteOn::PostTick,
+ ));
+
+ Ok(())
+ }
+
+ /// Write the variable's value to an event with the given event id. This function should be used
+ /// when you previously acquired an event id using [Self::event_to_variable].
+ pub fn variable_to_event_id(
+ &mut self,
+ input: Variable,
+ mapping: VariableToEventMapping,
+ write_on: VariableToEventWriteOn,
+ event_id: u32,
+ ) {
+ let input = self.variables.register(&input);
+
+ self.actions.push((
+ ToEvent::new_with_event_id(input, mapping, write_on, event_id).into(),
+ ExecuteOn::PostTick,
+ ));
+ }
+
+ fn precondition_not_aircraft_variable(variable: &Variable) {
+ if matches!(variable, Variable::Aircraft(..)) {
+ eprintln!("Writing to variable '{}' is unsupported.", variable);
+ }
+ }
+}
+
+pub struct MsfsAspect {
+ message_handlers: Vec,
+ actions: Vec<(VariableAction, ExecuteOn)>,
+}
+
+impl MsfsAspect {
+ fn new(
+ message_handlers: Vec,
+ actions: Vec<(VariableAction, ExecuteOn)>,
+ ) -> Self {
+ Self {
+ message_handlers,
+ actions,
+ }
+ }
+
+ fn execute_actions(
+ &mut self,
+ sim_connect: &mut SimConnect,
+ execute_moment: ExecuteOn,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ self.actions
+ .iter_mut()
+ .try_for_each(|(action, execute_on)| {
+ if *execute_on == execute_moment {
+ action.execute(sim_connect, variables)?;
+ }
+
+ Ok(())
+ })
+ }
+}
+
+impl Aspect for MsfsAspect {
+ fn handle_message(
+ &mut self,
+ message: &SimConnectRecv,
+ variables: &mut MsfsVariableRegistry,
+ ) -> bool {
+ self.message_handlers
+ .iter_mut()
+ .any(|handler| handler.handle(message, variables))
+ }
+
+ fn pre_tick(
+ &mut self,
+ variables: &mut MsfsVariableRegistry,
+ sim_connect: &mut SimConnect,
+ delta: Duration,
+ ) -> Result<(), Box> {
+ self.message_handlers
+ .iter_mut()
+ .for_each(|ev| ev.pre_tick(variables, delta));
+
+ self.execute_actions(sim_connect, ExecuteOn::PreTick, variables)?;
+
+ Ok(())
+ }
+
+ fn post_tick(
+ &mut self,
+ variables: &mut MsfsVariableRegistry,
+ sim_connect: &mut SimConnect,
+ ) -> Result<(), Box> {
+ self.execute_actions(sim_connect, ExecuteOn::PostTick, variables)?;
+
+ self.message_handlers
+ .iter_mut()
+ .for_each(|ev| ev.post_tick(variables));
+
+ Ok(())
+ }
+}
+
+#[derive(Clone, Copy, PartialEq)]
+/// Declares when to execute the action.
+pub enum ExecuteOn {
+ PreTick,
+ PostTick,
+}
+
+#[enum_dispatch]
+enum VariableAction {
+ Map,
+ MapMany,
+ Reduce,
+ ToObject,
+ ToEvent,
+}
+
+#[enum_dispatch(VariableAction)]
+trait ExecutableVariableAction {
+ fn execute(
+ &mut self,
+ sim_connect: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box>;
+}
+
+struct Map {
+ input_variable_identifier: VariableIdentifier,
+ func: fn(f64) -> f64,
+ output_variable_identifier: VariableIdentifier,
+}
+
+impl Map {
+ fn new(
+ input_variable_identifier: VariableIdentifier,
+ func: fn(f64) -> f64,
+ output_variable_identifier: VariableIdentifier,
+ ) -> Self {
+ Self {
+ input_variable_identifier,
+ func,
+ output_variable_identifier,
+ }
+ }
+}
+
+impl ExecutableVariableAction for Map {
+ fn execute(
+ &mut self,
+ _: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ let value = match variables.read(&self.input_variable_identifier) {
+ Some(value) => value,
+ None => panic!("Attempted to map a variable which is unavailable."),
+ };
+
+ variables.write(&self.output_variable_identifier, (self.func)(value));
+
+ Ok(())
+ }
+}
+
+fn precondition_multiple_identifiers(action_name: &str, identifiers: &[VariableIdentifier]) {
+ if identifiers.len() < 2 {
+ eprintln!(
+ "{} requires at least 2 input variables. {} {} provided.",
+ action_name,
+ identifiers.len(),
+ if identifiers.len() == 1 {
+ "was"
+ } else {
+ "were"
+ }
+ );
+ }
+}
+
+struct MapMany {
+ input_variable_identifiers: Vec,
+ func: fn(&[f64]) -> f64,
+ output_variable_identifier: VariableIdentifier,
+}
+
+impl MapMany {
+ fn new(
+ input_variable_identifiers: Vec,
+ func: fn(&[f64]) -> f64,
+ output_variable_identifier: VariableIdentifier,
+ ) -> Self {
+ precondition_multiple_identifiers("MapMany", &input_variable_identifiers);
+
+ Self {
+ input_variable_identifiers,
+ func,
+ output_variable_identifier,
+ }
+ }
+}
+
+impl ExecutableVariableAction for MapMany {
+ fn execute(
+ &mut self,
+ _: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ let values: Vec = variables
+ .read_many(&self.input_variable_identifiers)
+ .iter()
+ .map(|&x| x.unwrap())
+ .collect();
+ let result = (self.func)(&values);
+ variables.write(&self.output_variable_identifier, result);
+
+ Ok(())
+ }
+}
+
+struct Reduce {
+ input_variable_identifiers: Vec,
+ init: f64,
+ func: fn(f64, f64) -> f64,
+ output_variable_identifier: VariableIdentifier,
+}
+
+impl Reduce {
+ fn new(
+ input_variable_identifiers: Vec,
+ init: f64,
+ func: fn(f64, f64) -> f64,
+ output_variable_identifier: VariableIdentifier,
+ ) -> Self {
+ precondition_multiple_identifiers("Reduce", &input_variable_identifiers);
+
+ Self {
+ input_variable_identifiers,
+ init,
+ func,
+ output_variable_identifier,
+ }
+ }
+}
+
+impl ExecutableVariableAction for Reduce {
+ fn execute(
+ &mut self,
+ _: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ let values: Vec = variables
+ .read_many(&self.input_variable_identifiers)
+ .iter()
+ .map(|&x| x.unwrap())
+ .collect();
+ let result = values.into_iter().fold(self.init, self.func);
+ variables.write(&self.output_variable_identifier, result);
+
+ Ok(())
+ }
+}
+
+pub fn max(accumulator: f64, item: f64) -> f64 {
+ accumulator.max(item)
+}
+
+pub fn min(accumulator: f64, item: f64) -> f64 {
+ accumulator.min(item)
+}
+
+pub trait VariablesToObject {
+ fn variables(&self) -> Vec;
+ fn write(&mut self, values: Vec);
+ fn set_data_on_sim_object(&self, sim_connect: &mut SimConnect) -> Result<(), Box>;
+}
+
+struct ToObject {
+ target_object: Box,
+ variables: Vec,
+}
+
+impl ToObject {
+ fn new(target_object: Box, variables: Vec) -> Self {
+ Self {
+ target_object,
+ variables,
+ }
+ }
+}
+
+impl ExecutableVariableAction for ToObject {
+ fn execute(
+ &mut self,
+ sim_connect: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ let values: Vec = self
+ .variables
+ .iter()
+ .map(
+ |variable_identifier| match variables.read(variable_identifier) {
+ Some(value) => value,
+ None => {
+ panic!("Attempted to access variables which are unavailable.")
+ }
+ },
+ )
+ .collect();
+
+ self.target_object.write(values);
+ self.target_object.set_data_on_sim_object(sim_connect)?;
+
+ Ok(())
+ }
+}
+
+#[macro_export]
+macro_rules! set_data_on_sim_object {
+ () => {
+ fn set_data_on_sim_object(
+ &self,
+ sim_connect: &mut SimConnect,
+ ) -> Result<(), Box> {
+ sim_connect.set_data_on_sim_object(SIMCONNECT_OBJECT_ID_USER, self)?;
+ Ok(())
+ }
+ };
+}
+
+#[enum_dispatch]
+enum Debounce {
+ None(NoDebounce),
+ Leading(LeadingDebounce),
+}
+
+#[enum_dispatch(Debounce)]
+trait Debouncer {
+ fn should_handle(&self) -> bool;
+ fn notify_handled(&mut self);
+ fn post_tick(&mut self, variables: &mut MsfsVariableRegistry);
+}
+
+#[derive(Default)]
+struct NoDebounce {
+ event_handled_before_tick: bool,
+ reset_to: Option<(VariableIdentifier, f64)>,
+}
+
+impl NoDebounce {
+ fn new(reset_to: Option<(VariableIdentifier, f64)>) -> Self {
+ Self {
+ event_handled_before_tick: false,
+ reset_to,
+ }
+ }
+}
+
+impl Debouncer for NoDebounce {
+ fn should_handle(&self) -> bool {
+ true
+ }
+
+ fn notify_handled(&mut self) {
+ self.event_handled_before_tick = true;
+ }
+
+ fn post_tick(&mut self, variables: &mut MsfsVariableRegistry) {
+ if let Some((variable, value)) = &self.reset_to {
+ variables.write(variable, *value);
+ }
+
+ self.event_handled_before_tick = false;
+ }
+}
+
+struct LeadingDebounce {
+ duration: Duration,
+ handled_at: Option,
+ reset_to: Option<(VariableIdentifier, f64)>,
+}
+
+impl LeadingDebounce {
+ fn new(duration: Duration, reset_to: Option<(VariableIdentifier, f64)>) -> Self {
+ Self {
+ duration,
+ handled_at: None,
+ reset_to,
+ }
+ }
+
+ fn exceeded_debounce_duration(&self) -> bool {
+ self.handled_at
+ .map(|instant| instant.elapsed() > self.duration)
+ .unwrap_or(true)
+ }
+}
+
+impl Debouncer for LeadingDebounce {
+ fn should_handle(&self) -> bool {
+ self.exceeded_debounce_duration()
+ }
+
+ fn notify_handled(&mut self) {
+ self.handled_at = Some(Instant::now());
+ }
+
+ fn post_tick(&mut self, variables: &mut MsfsVariableRegistry) {
+ if self.handled_at.is_some() && self.exceeded_debounce_duration() {
+ if let Some((variable, value)) = &self.reset_to {
+ variables.write(variable, *value);
+ }
+
+ self.handled_at = None;
+ }
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+/// Configurable options for event to variable handling.
+pub struct EventToVariableOptions {
+ mask: bool,
+ leading_debounce_duration: Option,
+ reset_to: Option,
+}
+
+impl EventToVariableOptions {
+ /// Masks the event, causing the simulator to ignore it, and only this module to receive it.
+ pub fn mask(mut self) -> Self {
+ self.mask = true;
+ self
+ }
+
+ /// Apply a leading debounce to the event handling. Leading debounce immediately executes
+ /// the action associated with the event, but ignores any subsequent event until no event
+ /// triggered for the given duration.
+ ///
+ /// This is useful to deal with poor MSFS event handling, e.g. events being triggered
+ /// repeatedly despite only one press occurring.
+ pub fn leading_debounce(mut self, duration: Duration) -> Self {
+ self.leading_debounce_duration = Some(duration);
+ self
+ }
+
+ /// Sets the value to which the variable should be reset after the event occurred and any
+ /// debounce duration has passed.
+ pub fn afterwards_reset_to(mut self, value: f64) -> Self {
+ self.reset_to = Some(value);
+ self
+ }
+}
+
+/// Declares how to map the given event to a variable value.
+pub enum EventToVariableMapping {
+ /// When the event occurs, sets the variable to the given value.
+ Value(f64),
+
+ /// Maps the event data from a u32 to an f64 without any further processing.
+ EventDataRaw,
+
+ /// Maps the event data from a 32k position to an f64.
+ EventData32kPosition,
+
+ /// When the event occurs, calls the function with event data and sets
+ /// the variable to the returned value.
+ EventDataToValue(fn(u32) -> f64),
+
+ /// When the event occurs, calls the function with the current variable value and
+ /// sets the variable to the returned value.
+ CurrentValueToValue(fn(f64) -> f64),
+
+ /// When the event occurs, calls the function with event data and the current
+ /// variable value and sets the variable to the returned value.
+ EventDataAndCurrentValueToValue(fn(u32, f64) -> f64),
+
+ /// Converts the event occurrence to a value which increases and decreases
+ /// by the given factors.
+ SmoothPress(f64, f64),
+}
+
+#[enum_dispatch]
+enum MessageHandler {
+ EventToVariable,
+}
+
+#[enum_dispatch(MessageHandler)]
+trait HandleMessages {
+ fn handle(&mut self, message: &SimConnectRecv, variables: &mut MsfsVariableRegistry) -> bool;
+ fn pre_tick(&mut self, variables: &mut MsfsVariableRegistry, delta: Duration);
+ fn post_tick(&mut self, variables: &mut MsfsVariableRegistry);
+}
+
+struct EventToVariable {
+ event_id: u32,
+ event_handled_before_tick: bool,
+ target: VariableIdentifier,
+ mapping: EventToVariableMapping,
+ debounce: Debounce,
+}
+
+impl EventToVariable {
+ fn new(
+ sim_connect: &mut SimConnect,
+ event_name: &str,
+ mapping: EventToVariableMapping,
+ target: VariableIdentifier,
+ options: EventToVariableOptions,
+ ) -> Result> {
+ let reset_to = options.reset_to.map(|value| (target, value));
+ let debounce = if let Some(duration) = options.leading_debounce_duration {
+ LeadingDebounce::new(duration, reset_to).into()
+ } else {
+ NoDebounce::new(reset_to).into()
+ };
+
+ Ok(Self {
+ event_id: sim_connect.map_client_event_to_sim_event(event_name, options.mask)?,
+ event_handled_before_tick: false,
+ target,
+ mapping,
+ debounce,
+ })
+ }
+
+ fn map_to_value(
+ &self,
+ e: &msfs::sys::SIMCONNECT_RECV_EVENT,
+ variables: &mut MsfsVariableRegistry,
+ ) -> f64 {
+ match self.mapping {
+ EventToVariableMapping::Value(value) => value,
+ EventToVariableMapping::EventDataRaw => e.data() as f64,
+ EventToVariableMapping::EventData32kPosition => sim_connect_32k_pos_to_f64(e.data()),
+ EventToVariableMapping::EventDataToValue(func) => func(e.data()),
+ EventToVariableMapping::CurrentValueToValue(func) => {
+ func(variables.read(&self.target).unwrap_or(0.))
+ }
+ EventToVariableMapping::EventDataAndCurrentValueToValue(func) => {
+ func(e.data(), variables.read(&self.target).unwrap_or(0.))
+ }
+ EventToVariableMapping::SmoothPress(..) => variables.read(&self.target).unwrap_or(0.),
+ }
+ }
+
+ fn adjust_smooth_pressed_value(
+ &mut self,
+ delta: Duration,
+ variables: &mut MsfsVariableRegistry,
+ ) {
+ if let EventToVariableMapping::SmoothPress(press_factor, release_factor) = self.mapping {
+ let mut value = variables.read(&self.target).unwrap_or(0.);
+ if self.event_handled_before_tick {
+ value += delta.as_secs_f64() * press_factor;
+ } else {
+ value -= delta.as_secs_f64() * release_factor;
+ }
+
+ variables.write(&self.target, value.min(1.).max(0.));
+ }
+ }
+}
+
+impl HandleMessages for EventToVariable {
+ fn handle(&mut self, message: &SimConnectRecv, variables: &mut MsfsVariableRegistry) -> bool {
+ match message {
+ SimConnectRecv::Event(e) if e.id() == self.event_id => {
+ if self.debounce.should_handle() {
+ let mapped_value = self.map_to_value(e, variables);
+ variables.write(&self.target, mapped_value);
+
+ self.debounce.notify_handled();
+ self.event_handled_before_tick = true;
+ }
+
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn pre_tick(&mut self, variables: &mut MsfsVariableRegistry, delta: Duration) {
+ self.adjust_smooth_pressed_value(delta, variables);
+ }
+
+ fn post_tick(&mut self, variables: &mut MsfsVariableRegistry) {
+ self.debounce.post_tick(variables);
+ self.event_handled_before_tick = false;
+ }
+}
+
+#[derive(Clone, Copy)]
+/// Declares how to map the given variable value to an event value.
+pub enum VariableToEventMapping {
+ /// Maps the variable from an f64 to a u32 without any further processing.
+ EventDataRaw,
+
+ /// Maps the variable from an f64 to a 32k position.
+ EventData32kPosition,
+}
+
+/// Declares when to write the variable to the event.
+#[derive(Clone, Copy)]
+pub enum VariableToEventWriteOn {
+ /// Writes the variable to the event after every tick.
+ EveryTick,
+
+ /// Writes the variable to the event when the variable's value has changed.
+ Change,
+}
+
+struct ToEvent {
+ input: VariableIdentifier,
+ mapping: VariableToEventMapping,
+ write_on: VariableToEventWriteOn,
+ event_id: u32,
+ last_written_value: Option,
+}
+
+impl ToEvent {
+ fn new(
+ sim_connect: &mut SimConnect,
+ input: VariableIdentifier,
+ mapping: VariableToEventMapping,
+ write_on: VariableToEventWriteOn,
+ event_name: &str,
+ ) -> Result> {
+ Ok(Self {
+ input,
+ mapping,
+ write_on,
+ event_id: sim_connect.map_client_event_to_sim_event(event_name, false)?,
+ last_written_value: None,
+ })
+ }
+
+ fn new_with_event_id(
+ input: VariableIdentifier,
+ mapping: VariableToEventMapping,
+ write_on: VariableToEventWriteOn,
+ event_id: u32,
+ ) -> Self {
+ Self {
+ input,
+ mapping,
+ write_on,
+ event_id,
+ last_written_value: None,
+ }
+ }
+}
+
+impl ExecutableVariableAction for ToEvent {
+ fn execute(
+ &mut self,
+ sim_connect: &mut SimConnect,
+ variables: &mut MsfsVariableRegistry,
+ ) -> Result<(), Box> {
+ let value = variables.read(&self.input).unwrap_or(0.);
+ let should_write = match self.write_on {
+ VariableToEventWriteOn::EveryTick => true,
+ VariableToEventWriteOn::Change => match self.last_written_value {
+ Some(last_written_value) => value != last_written_value,
+ None => true,
+ },
+ };
+
+ if should_write {
+ sim_connect.transmit_client_event(
+ SIMCONNECT_OBJECT_ID_USER,
+ self.event_id,
+ match self.mapping {
+ VariableToEventMapping::EventDataRaw => value as u32,
+ VariableToEventMapping::EventData32kPosition => {
+ f64_to_sim_connect_32k_pos(value)
+ }
+ },
+ )?;
+ self.last_written_value = Some(value);
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/systems/systems_wasm/src/electrical.rs b/src/systems/systems_wasm/src/electrical.rs
index 6b57c679525..1fe65de5c0e 100644
--- a/src/systems/systems_wasm/src/electrical.rs
+++ b/src/systems/systems_wasm/src/electrical.rs
@@ -1,7 +1,7 @@
use fxhash::FxHashMap;
use std::error::Error;
-use crate::SimulatorAspect;
+use crate::Aspect;
use msfs::legacy::execute_calculator_code;
use msfs::legacy::AircraftVariable;
use systems::shared::to_bool;
@@ -24,7 +24,7 @@ impl MsfsElectricalBuses {
.insert(identifier, ElectricalBusConnection::new(from, to));
}
}
-impl SimulatorAspect for MsfsElectricalBuses {
+impl Aspect for MsfsElectricalBuses {
fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool {
match self.connections.get_mut(identifier) {
Some(connection) => connection.update(value),
@@ -100,7 +100,7 @@ impl MsfsAuxiliaryPowerUnit {
execute_calculator_code::<()>("1 (>K:APU_OFF_SWITCH, Number)");
}
}
-impl SimulatorAspect for MsfsAuxiliaryPowerUnit {
+impl Aspect for MsfsAuxiliaryPowerUnit {
fn write(&mut self, identifier: &VariableIdentifier, value: f64) -> bool {
if identifier == &self.is_available_id {
let is_available = to_bool(value);
diff --git a/src/systems/systems_wasm/src/lib.rs b/src/systems/systems_wasm/src/lib.rs
index 6daabeb0c86..503aa448e3c 100644
--- a/src/systems/systems_wasm/src/lib.rs
+++ b/src/systems/systems_wasm/src/lib.rs
@@ -1,16 +1,21 @@
#![cfg(any(target_arch = "wasm32", doc))]
+#[macro_use]
+pub mod aspects;
mod electrical;
mod failures;
-use std::{error::Error, time::Duration};
-
+use crate::aspects::MsfsAspectBuilder;
+use electrical::{MsfsAuxiliaryPowerUnit, MsfsElectricalBuses};
+use failures::Failures;
use fxhash::FxHashMap;
use msfs::{
legacy::{AircraftVariable, NamedVariable},
sim_connect::{data_definition, Period, SimConnect, SimConnectRecv, SIMCONNECT_OBJECT_ID_USER},
MSFSEvent,
};
-
+use std::fmt::{Display, Formatter};
+use std::{error::Error, time::Duration};
+use systems::simulation::InitContext;
use systems::{
failures::FailureType,
simulation::{
@@ -18,12 +23,9 @@ use systems::{
},
};
-use electrical::{MsfsAuxiliaryPowerUnit, MsfsElectricalBuses};
-use failures::Failures;
-use systems::simulation::InitContext;
-
-/// An aspect to inject into events in the simulation.
-pub trait SimulatorAspect {
+/// A concern that should be handled by the bridging layer. Examples are
+/// the handling of events which move flaps up and down, triggering of brakes, etc.
+pub trait Aspect {
/// Attempts to read data with the given identifier.
/// Returns `Some` when reading was successful, `None` otherwise.
fn read(&mut self, _identifier: &VariableIdentifier) -> Option {
@@ -40,30 +42,38 @@ pub trait SimulatorAspect {
false
}
- /// Attempts to handle the given SimConnect message, returning true
+ /// Attempts to handle the given message, returning true
/// when the message was handled and false otherwise.
- fn handle_message(&mut self, _message: &SimConnectRecv) -> bool {
+ fn handle_message(
+ &mut self,
+ _message: &SimConnectRecv,
+ _variables: &mut MsfsVariableRegistry,
+ ) -> bool {
false
}
/// Executes before a simulation tick runs.
- fn pre_tick(&mut self, _delta: Duration) {}
+ fn pre_tick(
+ &mut self,
+ _variables: &mut MsfsVariableRegistry,
+ _sim_connect: &mut SimConnect,
+ _delta: Duration,
+ ) -> Result<(), Box> {
+ Ok(())
+ }
/// Executes after a simulation tick ran.
- fn post_tick(&mut self, _sim_connect: &mut SimConnect) -> Result<(), Box> {
+ fn post_tick(
+ &mut self,
+ _variables: &mut MsfsVariableRegistry,
+ _sim_connect: &mut SimConnect,
+ ) -> Result<(), Box> {
Ok(())
}
}
-pub trait MsfsAspectCtor {
- fn new(
- registry: &mut MsfsVariableRegistry,
- sim_connect: &mut SimConnect,
- ) -> Result>
- where
- Self: Sized;
-}
-
+/// Type used to configure and build a simulation and a handler which acts as a bridging layer
+/// between the simulation and Microsoft Flight Simulator.
pub struct MsfsSimulationBuilder<'a, 'b> {
variable_registry: Option,
key_prefix: String,
@@ -71,16 +81,16 @@ pub struct MsfsSimulationBuilder<'a, 'b> {
sim_connect: &'a mut SimConnect<'b>,
apu: Option,
failures: Option,
- additional_aspects: Vec>,
+ additional_aspects: Vec>,
}
impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> {
const MSFS_INFINITELY_POWERED_BUS_IDENTIFIER: usize = 1;
- pub fn new(key_prefix: String, sim_connect: &'a mut SimConnect<'b>) -> Self {
+ pub fn new(key_prefix: &str, sim_connect: &'a mut SimConnect<'b>) -> Self {
Self {
- variable_registry: Some(MsfsVariableRegistry::new(key_prefix.clone())),
- key_prefix,
+ variable_registry: Some(MsfsVariableRegistry::new(key_prefix.into())),
+ key_prefix: key_prefix.into(),
electrical_buses: Some(Default::default()),
sim_connect,
apu: None,
@@ -93,8 +103,7 @@ impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> {
mut self,
aircraft_ctor_fn: U,
) -> Result<(Simulation, MsfsHandler), Box> {
- let mut aspects: Vec> =
- vec![Box::new(self.electrical_buses.unwrap())];
+ let mut aspects: Vec> = vec![Box::new(self.electrical_buses.unwrap())];
if self.apu.is_some() {
aspects.push(Box::new(self.apu.unwrap()));
}
@@ -103,21 +112,23 @@ impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> {
let mut registry = self.variable_registry.unwrap();
let simulation = Simulation::new(aircraft_ctor_fn, &mut registry);
- aspects.push(Box::new(registry));
Ok((
simulation,
- MsfsHandler::new(aspects, self.failures, self.sim_connect)?,
+ MsfsHandler::new(registry, aspects, self.failures, self.sim_connect)?,
))
}
- pub fn with(
+ /// Adds an aspect. An aspect is a concern that should be handled by the bridging layer.
+ /// The function passed to this method is used to configure the aspect.
+ pub fn with_aspect Result<(), Box>>(
mut self,
+ builder_func: T,
) -> Result> {
- if let Some(registry) = &mut self.variable_registry {
- self.additional_aspects
- .push(Box::new(T::new(registry, self.sim_connect)?));
- }
+ let variable_registry = &mut self.variable_registry.as_mut().unwrap();
+ let mut builder = MsfsAspectBuilder::new(&mut self.sim_connect, variable_registry);
+ (builder_func)(&mut builder)?;
+ self.additional_aspects.push(Box::new(builder.build()));
Ok(self)
}
@@ -146,13 +157,13 @@ impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> {
pub fn with_auxiliary_power_unit(
mut self,
- is_available_variable_name: String,
+ is_available_variable_name: &str,
fuel_valve_number: u8,
) -> Result> {
if let Some(registry) = &mut self.variable_registry {
self.apu = Some(MsfsAuxiliaryPowerUnit::new(
registry,
- is_available_variable_name,
+ is_available_variable_name.into(),
fuel_valve_number,
)?);
}
@@ -181,45 +192,33 @@ impl<'a, 'b> MsfsSimulationBuilder<'a, 'b> {
index: usize,
) -> Result> {
if let Some(registry) = &mut self.variable_registry {
- registry.add_aircraft_variable(name, units, index)?;
- }
-
- Ok(self)
- }
-
- pub fn provides_aircraft_variable_with_additional_names(
- mut self,
- name: &str,
- units: &str,
- index: usize,
- additional_names: Vec,
- ) -> Result> {
- if let Some(registry) = &mut self.variable_registry {
- registry.add_aircraft_variable_with_additional_names(
- name,
- units,
+ registry.register(&Variable::Aircraft(
+ name.to_owned(),
+ units.to_owned(),
index,
- Some(additional_names),
- )?;
+ ));
}
Ok(self)
}
}
-/// Used to orchestrate the simulation combined with Microsoft Flight Simulator.
+/// Used to bridge between the simulation and Microsoft Flight Simulator.
pub struct MsfsHandler {
- aspects: Vec>,
+ variables: Option,
+ aspects: Vec>,
failures: Option,
time: Time,
}
impl MsfsHandler {
fn new(
- aspects: Vec>,
+ variables: MsfsVariableRegistry,
+ aspects: Vec>,
failures: Option,
sim_connect: &mut SimConnect,
) -> Result> {
Ok(Self {
+ variables: Some(variables),
aspects,
failures,
time: Time::new(sim_connect)?,
@@ -236,7 +235,7 @@ impl MsfsHandler {
MSFSEvent::PreDraw(_) => {
if !self.time.is_pausing() {
let delta_time = self.time.take();
- self.pre_tick(delta_time);
+ self.pre_tick(sim_connect, delta_time)?;
if let Some(failures) = &self.failures {
Self::read_failures_into_simulation(failures, simulation);
}
@@ -260,22 +259,43 @@ impl MsfsHandler {
}
fn handle_message(&mut self, message: &SimConnectRecv) {
- for aspect in self.aspects.iter_mut() {
- if aspect.handle_message(message) {
- break;
+ if let Some(mut variables) = self.variables.take() {
+ for aspect in self.aspects.iter_mut() {
+ if aspect.handle_message(message, &mut variables) {
+ break;
+ }
}
+
+ self.variables = Some(variables);
}
}
- fn pre_tick(&mut self, delta: Duration) {
- self.aspects.iter_mut().for_each(|aspect| {
- aspect.pre_tick(delta);
- });
+ fn pre_tick(
+ &mut self,
+ sim_connect: &mut SimConnect,
+ delta: Duration,
+ ) -> Result<(), Box> {
+ if let Some(mut variables) = self.variables.take() {
+ let result = self
+ .aspects
+ .iter_mut()
+ .try_for_each(|aspect| aspect.pre_tick(&mut variables, sim_connect, delta));
+
+ self.variables = Some(variables);
+
+ result
+ } else {
+ Ok(())
+ }
}
fn post_tick(&mut self, sim_connect: &mut SimConnect) -> Result<(), Box> {
- for aspect in self.aspects.iter_mut() {
- aspect.post_tick(sim_connect)?;
+ if let Some(mut variables) = self.variables.take() {
+ for aspect in self.aspects.iter_mut() {
+ aspect.post_tick(&mut variables, sim_connect)?;
+ }
+
+ self.variables = Some(variables);
}
Ok(())
@@ -299,107 +319,265 @@ impl SimulatorReaderWriter for MsfsHandler {
self.aspects
.iter_mut()
.find_map(|aspect| aspect.read(identifier))
- .unwrap_or(0.)
+ .unwrap_or_else(|| {
+ self.variables
+ .as_ref()
+ .map(|registry| registry.read(identifier).unwrap_or(0.))
+ .unwrap_or(0.)
+ })
}
fn write(&mut self, identifier: &VariableIdentifier, value: f64) {
for aspect in self.aspects.iter_mut() {
if aspect.write(identifier, value) {
- break;
+ return;
}
}
+
+ if let Some(variable_registry) = &mut self.variables {
+ variable_registry.write(identifier, value);
+ }
+ }
+}
+
+/// Declares a variable of a given type with a given name.
+#[derive(Clone)]
+pub enum Variable {
+ /// An aircraft variable accessible within the aspect, simulation and simulator.
+ Aircraft(String, String, usize),
+
+ /// A named variable accessible within the aspect, simulation and simulator.
+ Named(String),
+
+ /// A variable accessible within all aspects and the simulation.
+ ///
+ /// Note that even though aspect variables are accessible from all aspects, no assumptions
+ /// should be made about their update order outside of a single aspect. Thus it is best not
+ /// to use the same aspect variable within different aspects.
+ Aspect(String),
+}
+
+impl Display for Variable {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let name = match self {
+ Self::Aircraft(name, _, index) => {
+ format!("Aircraft({})", Self::indexed_name(name, *index))
+ }
+ Self::Named(name, ..) => format!("Named({})", name),
+ Self::Aspect(name, ..) => format!("Aspect({})", name),
+ };
+
+ write!(f, "{}", name)
+ }
+}
+
+impl Variable {
+ pub fn aircraft(name: &str, units: &str, index: usize) -> Self {
+ Self::Aircraft(name.into(), units.into(), index)
+ }
+
+ pub fn named(name: &str) -> Self {
+ Self::Named(name.into())
+ }
+
+ pub fn aspect(name: &str) -> Self {
+ Self::Aspect(name.into())
+ }
+
+ /// Provides the name that should be used for storing and looking up a [VariableIdentifier].
+ fn lookup_name(&self) -> String {
+ match self {
+ Self::Aircraft(name, _, index, ..) => Self::indexed_name(name, *index),
+ Self::Named(name, ..) | Self::Aspect(name, ..) => name.into(),
+ }
+ }
+
+ fn add_prefix(&mut self, prefix: &str) {
+ match self {
+ Self::Aircraft(name, ..) | Self::Named(name, ..) | Self::Aspect(name, ..) => {
+ *name = format!("{}{}", prefix, name);
+ }
+ }
+ }
+
+ fn indexed_name(name: &str, index: usize) -> String {
+ if index > 0 {
+ format!("{}:{}", name, index)
+ } else {
+ name.into()
+ }
+ }
+}
+
+impl From<&Variable> for VariableValue {
+ fn from(value: &Variable) -> Self {
+ match value {
+ Variable::Aircraft(name, units, index, ..) => {
+ let index = *index;
+ VariableValue::Aircraft(match AircraftVariable::from(&name, units, index) {
+ Ok(aircraft_variable) => aircraft_variable,
+ Err(error) => panic!(
+ "Error while trying to create aircraft variable named '{}': {}",
+ name, error
+ ),
+ })
+ }
+ Variable::Named(name, ..) => VariableValue::Named(NamedVariable::from(name)),
+ Variable::Aspect(..) => VariableValue::Aspect(0.),
+ }
+ }
+}
+
+impl From<&Variable> for VariableType {
+ fn from(value: &Variable) -> Self {
+ match value {
+ Variable::Aircraft(..) => Self::Aircraft,
+ Variable::Named(..) => Self::Named,
+ Variable::Aspect(..) => Self::Aspect,
+ }
+ }
+}
+
+#[derive(Debug, Eq, Hash, PartialEq)]
+pub enum VariableType {
+ Aircraft = 0,
+ Named = 1,
+ Aspect = 2,
+}
+
+impl From for u8 {
+ fn from(value: VariableType) -> Self {
+ value as u8
+ }
+}
+
+impl From for VariableType {
+ fn from(value: u8) -> Self {
+ match value {
+ 0 => Self::Aircraft,
+ 1 => Self::Named,
+ 2 => Self::Aspect,
+ _ => panic!("Cannot convert {} to identifier type", value),
+ }
+ }
+}
+
+impl From<&VariableIdentifier> for VariableType {
+ fn from(value: &VariableIdentifier) -> Self {
+ value.identifier_type().into()
+ }
+}
+
+pub enum VariableValue {
+ Aircraft(AircraftVariable),
+ Named(NamedVariable),
+ Aspect(f64),
+}
+
+impl VariableValue {
+ fn read(&self) -> f64 {
+ match self {
+ Self::Aircraft(underlying) => underlying.get(),
+ Self::Named(underlying) => underlying.get_value(),
+ Self::Aspect(underlying) => *underlying,
+ }
+ }
+
+ fn write(&mut self, value: f64) {
+ match self {
+ Self::Aircraft(_) => panic!("Cannot write to an aircraft variable."),
+ Self::Named(underlying) => underlying.set_value(value),
+ Self::Aspect(underlying) => *underlying = value,
+ }
}
}
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,
+ name_to_identifier: FxHashMap,
+ next_variable_identifier: FxHashMap,
+ variables: FxHashMap,
}
impl MsfsVariableRegistry {
- const AIRCRAFT_VARIABLE_IDENTIFIER_TYPE: u8 = 0;
- const NAMED_VARIABLE_IDENTIFIER_TYPE: u8 = 1;
-
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,
- ),
+ name_to_identifier: FxHashMap::default(),
+ next_variable_identifier: FxHashMap::default(),
+ variables: FxHashMap::default(),
}
}
- /// Add an aircraft variable definition. Once added, the aircraft variable
+ /// Registers a variable definition. Once added, the variable
/// 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_aircraft_variable_with_additional_names(name, units, index, None)
+ pub fn register(&mut self, variable: &Variable) -> VariableIdentifier {
+ let identifier = self.get_identifier_or_create_variable(variable);
+
+ let registered_type: VariableType = (&identifier).into();
+ let target_type: VariableType = variable.into();
+ if registered_type != target_type {
+ eprintln!("Attempted to re-register a variable \"{}\" which was previously registered with type {:?}.",
+ variable, registered_type);
+ }
+
+ identifier
}
- /// Add an aircraft variable definition. Once added, the aircraft variable
- /// can be read through the `MsfsVariableRegistry.read` function.
- ///
- /// 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>,
- ) -> Result<(), Box> {
- match AircraftVariable::from(&name, units, index) {
- Ok(var) => {
- let name = if index > 0 {
- format!("{}:{}", name, index)
- } else {
- 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.into_iter().for_each(|el| {
- self.name_to_identifier.insert(el, identifier);
- });
+ pub fn register_many(&mut self, variables: &[Variable]) -> Vec {
+ variables
+ .into_iter()
+ .map(|variable| self.register(variable))
+ .collect()
+ }
+
+ fn get_identifier_or_create_variable(&mut self, variable: &Variable) -> VariableIdentifier {
+ match self.name_to_identifier.get(&variable.lookup_name()) {
+ Some(identifier) => *identifier,
+ None => {
+ let identifier = *self
+ .next_variable_identifier
+ .entry(variable.into())
+ .or_insert_with(|| VariableIdentifier::new::(variable.into()));
+
+ self.next_variable_identifier
+ .insert(variable.into(), identifier.next());
+
+ self.name_to_identifier
+ .insert(variable.lookup_name(), identifier);
+
+ let mut variable = variable.clone();
+ if matches!(variable, Variable::Named(..)) {
+ variable.add_prefix(&self.named_variable_prefix);
}
- self.next_aircraft_variable_identifier = identifier.next();
+ let value: VariableValue = (&variable).into();
+ self.variables.insert(identifier, value);
- Ok(())
+ identifier
}
- 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);
+ fn read(&self, identifier: &VariableIdentifier) -> Option {
+ match self.variables.get(identifier) {
+ Some(variable_value) => Some(variable_value.read()),
+ None => None,
+ }
+ }
- self.next_named_variable_identifier = identifier.next();
+ fn read_many(&self, identifiers: &[VariableIdentifier]) -> Vec