From b6efc1c8073b2efd7cc605ddb7d2f13e59db731d Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Sat, 12 Nov 2022 20:37:27 -0500 Subject: [PATCH] autoformatter uses tabs now --- autoupdater/src/main.rs | 254 ++++++++--------- autoupdater/src/parsing/install_path.rs | 54 ++-- autoupdater/src/parsing/mcp.rs | 46 ++-- autoupdater/src/parsing/mod.rs | 204 +++++++------- firmware/crates/panic_defmt/src/lib.rs | 32 +-- firmware/src/aliases.rs | 22 +- firmware/src/globals.rs | 28 +- firmware/src/imu/mod.rs | 38 +-- firmware/src/imu/mpu6050.rs | 104 +++---- firmware/src/main.rs | 40 +-- firmware/src/peripherals/esp32c3.rs | 68 ++--- firmware/src/peripherals/mod.rs | 4 +- firmware/src/utils.rs | 54 ++-- overlay/app/src/client/data.rs | 66 ++--- overlay/app/src/client/mod.rs | 170 ++++++------ overlay/app/src/client/state_machine.rs | 348 ++++++++++++------------ overlay/app/src/color.rs | 58 ++-- overlay/app/src/lib.rs | 256 ++++++++--------- overlay/app/src/main.rs | 6 +- overlay/app/src/model/bone.rs | 340 +++++++++++------------ overlay/app/src/model/bone_kind.rs | 244 ++++++++--------- overlay/app/src/model/bone_map.rs | 110 ++++---- overlay/app/src/model/skeleton.rs | 196 ++++++------- overlay/tokio_shutdown/src/lib.rs | 188 ++++++------- rustfmt.toml | 1 + skeletal_model/src/bone/bone_kind.rs | 236 ++++++++-------- skeletal_model/src/bone/bone_map.rs | 190 ++++++------- skeletal_model/src/conventions.rs | 238 ++++++++-------- skeletal_model/src/lib.rs | 10 +- skeletal_model/src/newtypes.rs | 32 +-- skeletal_model/src/skeleton/edge.rs | 100 +++---- skeletal_model/src/skeleton/mod.rs | 148 +++++----- skeletal_model/src/skeleton/node.rs | 44 +-- 33 files changed, 1965 insertions(+), 1964 deletions(-) diff --git a/autoupdater/src/main.rs b/autoupdater/src/main.rs index b5b27bdc..bdc0461c 100644 --- a/autoupdater/src/main.rs +++ b/autoupdater/src/main.rs @@ -11,146 +11,146 @@ use reqwest::Url; use std::path::PathBuf; use tempfile::tempdir; use tokio::{ - fs::{File, OpenOptions}, - io::AsyncWriteExt, - task::JoinHandle, + fs::{File, OpenOptions}, + io::AsyncWriteExt, + task::JoinHandle, }; lazy_static! { - static ref VERSIONING_URL: Url = Url::parse( - "https://github.com/SlimeVR/SlimeVR-Overlay/releases/download/autoupdater-latest/versioning.json" - ).unwrap(); + static ref VERSIONING_URL: Url = Url::parse( + "https://github.com/SlimeVR/SlimeVR-Overlay/releases/download/autoupdater-latest/versioning.json" + ).unwrap(); } #[derive(Parser)] struct Args { - /// The url to fetch the versioning.yaml from. - #[clap(long, default_value_t = VERSIONING_URL.clone())] - url: Url, - #[clap(long)] - /// The path to a versioning.yaml file, to use instead of the URL. - path: Option, + /// The url to fetch the versioning.yaml from. + #[clap(long, default_value_t = VERSIONING_URL.clone())] + url: Url, + #[clap(long)] + /// The path to a versioning.yaml file, to use instead of the URL. + path: Option, } /// Helper macro to unpack Option and continue with a helpful error if None. macro_rules! try_get { - ($arg:expr) => { - if let Some(v) = $arg { - v - } else { - println!("Component not supported on this platform. Skipping."); - continue; - } - }; + ($arg:expr) => { + if let Some(v) = $arg { + v + } else { + println!("Component not supported on this platform. Skipping."); + continue; + } + }; } #[tokio::main] async fn main() -> Result<()> { - let args = Args::parse(); - color_eyre::install()?; // pwetty errors UwU 👉👈 - - // Read yaml file from url or path - let versioning = if let Some(p) = args.path { - std::fs::read_to_string(p) - .wrap_err("Failed to read versioning.yaml from file")? - } else { - reqwest::get(args.url.clone()) - .await - .wrap_err_with(move || { - format!( - "Failed to download `versioning.json` from URL: {}", - &args.url - ) - })? - .text() - .await - .wrap_err("Failed to decode response body")? - }; - - // Parse/deserialize yaml - let components: Components = - serde_yaml::from_str(&versioning).wrap_err_with(|| { - format!("Could not deserialize YAML, whose contents was:\n{versioning}") - })?; - - // We will delete the temporary dir upon drop. - let tmp_dir = tempdir().wrap_err("Failed to create temporary directory")?; - - // Download each component, storing the async tasks in `download_tasks` - let mut download_tasks = Vec::new(); - for (comp_name, comp_info) in components.0.into_iter() { - println!("Downloading component: {comp_name}..."); - let url = try_get!(comp_info.download_url.get_owned()); - let install_path = try_get!(comp_info.install_dir.get_owned()); - - let mut response = reqwest::get(url.clone()).await.wrap_err_with(|| { - format!("Failed to download `{comp_name}` from URL: {url}") - })?; - - // TODO: This should use snake_case names since that is what the components in the - // file use. - let filename = url - .path_segments() - .map_or(comp_name.to_string(), |v| v.last().unwrap().to_string()); - let download_path = tmp_dir.path().join(filename); - let mut download_file = File::create(&download_path) - .await - .wrap_err("Failed to create temporary file")?; - - // We spawn a task so that the file can be downloaded concurrently - let task: JoinHandle> = tokio::spawn(async move { - while let Some(mut b) = response - .chunk() - .await - .wrap_err("error while slurping chunks")? - { - download_file - .write_all_buf(&mut b) - .await - .wrap_err("Error while writing to file")? - } - Ok((download_file, download_path, install_path.to_path()?)) - }); - download_tasks.push(task); - } - - // Check that all components downloaded successfully - let downloads: Result> = join_all(download_tasks) - .await - .into_iter() - .map(|t| { - Ok(t.wrap_err("couldn't join task")? - .wrap_err("failed download of component")?) - }) - .collect(); - let downloads = downloads?; - - // Check that all files are writeable *before* attempting to move. - join_all(downloads.iter().map(|(_, _, install_path)| async move { - OpenOptions::new() - .create(true) - .write(true) - .open(install_path) - .await - .wrap_err_with(|| format!("{install_path:?} was not writeable")) - })) - .await - .into_iter() - .collect::>>()?; // returns if errors - - // Move all the components to overwrite the old ones. - // This is suceptible to race conditions after the writable check, but its fine 😅 - for (download_file, download_path, ref install_path) in downloads { - drop(download_file); - // Truncates if the file already exists. - tokio::fs::rename(download_path, install_path) - .await - .wrap_err_with(|| { - format!("Failed to move temporary file to {install_path:?}") - })?; - } - - println!("Press enter to quit..."); - std::io::stdin().read_line(&mut String::new()).unwrap(); - Ok(()) + let args = Args::parse(); + color_eyre::install()?; // pwetty errors UwU 👉👈 + + // Read yaml file from url or path + let versioning = if let Some(p) = args.path { + std::fs::read_to_string(p) + .wrap_err("Failed to read versioning.yaml from file")? + } else { + reqwest::get(args.url.clone()) + .await + .wrap_err_with(move || { + format!( + "Failed to download `versioning.json` from URL: {}", + &args.url + ) + })? + .text() + .await + .wrap_err("Failed to decode response body")? + }; + + // Parse/deserialize yaml + let components: Components = + serde_yaml::from_str(&versioning).wrap_err_with(|| { + format!("Could not deserialize YAML, whose contents was:\n{versioning}") + })?; + + // We will delete the temporary dir upon drop. + let tmp_dir = tempdir().wrap_err("Failed to create temporary directory")?; + + // Download each component, storing the async tasks in `download_tasks` + let mut download_tasks = Vec::new(); + for (comp_name, comp_info) in components.0.into_iter() { + println!("Downloading component: {comp_name}..."); + let url = try_get!(comp_info.download_url.get_owned()); + let install_path = try_get!(comp_info.install_dir.get_owned()); + + let mut response = reqwest::get(url.clone()).await.wrap_err_with(|| { + format!("Failed to download `{comp_name}` from URL: {url}") + })?; + + // TODO: This should use snake_case names since that is what the components in the + // file use. + let filename = url + .path_segments() + .map_or(comp_name.to_string(), |v| v.last().unwrap().to_string()); + let download_path = tmp_dir.path().join(filename); + let mut download_file = File::create(&download_path) + .await + .wrap_err("Failed to create temporary file")?; + + // We spawn a task so that the file can be downloaded concurrently + let task: JoinHandle> = tokio::spawn(async move { + while let Some(mut b) = response + .chunk() + .await + .wrap_err("error while slurping chunks")? + { + download_file + .write_all_buf(&mut b) + .await + .wrap_err("Error while writing to file")? + } + Ok((download_file, download_path, install_path.to_path()?)) + }); + download_tasks.push(task); + } + + // Check that all components downloaded successfully + let downloads: Result> = join_all(download_tasks) + .await + .into_iter() + .map(|t| { + Ok(t.wrap_err("couldn't join task")? + .wrap_err("failed download of component")?) + }) + .collect(); + let downloads = downloads?; + + // Check that all files are writeable *before* attempting to move. + join_all(downloads.iter().map(|(_, _, install_path)| async move { + OpenOptions::new() + .create(true) + .write(true) + .open(install_path) + .await + .wrap_err_with(|| format!("{install_path:?} was not writeable")) + })) + .await + .into_iter() + .collect::>>()?; // returns if errors + + // Move all the components to overwrite the old ones. + // This is suceptible to race conditions after the writable check, but its fine 😅 + for (download_file, download_path, ref install_path) in downloads { + drop(download_file); + // Truncates if the file already exists. + tokio::fs::rename(download_path, install_path) + .await + .wrap_err_with(|| { + format!("Failed to move temporary file to {install_path:?}") + })?; + } + + println!("Press enter to quit..."); + std::io::stdin().read_line(&mut String::new()).unwrap(); + Ok(()) } diff --git a/autoupdater/src/parsing/install_path.rs b/autoupdater/src/parsing/install_path.rs index fdc4874f..9201bc1d 100644 --- a/autoupdater/src/parsing/install_path.rs +++ b/autoupdater/src/parsing/install_path.rs @@ -1,6 +1,6 @@ use color_eyre::{ - eyre::{ContextCompat, WrapErr}, - Result, + eyre::{ContextCompat, WrapErr}, + Result, }; use lazy_static::lazy_static; use path_absolutize::Absolutize; @@ -10,8 +10,8 @@ use std::path::PathBuf; use crate::parsing::MCP; lazy_static! { - static ref SLIME_DIR: MCP = MCP::Cross(PathBuf::from("todo")); - static ref STEAM_DIR: MCP = MCP::Cross(PathBuf::from("todo")); + static ref SLIME_DIR: MCP = MCP::Cross(PathBuf::from("todo")); + static ref STEAM_DIR: MCP = MCP::Cross(PathBuf::from("todo")); } /// The location to install a component. @@ -20,29 +20,29 @@ lazy_static! { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)] #[serde(rename_all = "snake_case")] pub enum InstallPath { - /// Just a regular path - Normal(PathBuf), - /// A path relative to the SlimeVR installation dir - RelativeToSlime(PathBuf), - /// A path relative to the SteamVR installation dir - RelativeToSteam(PathBuf), + /// Just a regular path + Normal(PathBuf), + /// A path relative to the SlimeVR installation dir + RelativeToSlime(PathBuf), + /// A path relative to the SteamVR installation dir + RelativeToSteam(PathBuf), } impl InstallPath { - /// Converts to the full absolute path of the file - pub fn to_path(&self) -> Result { - let p = match self { - InstallPath::Normal(p) => p.to_owned(), - InstallPath::RelativeToSlime(p) => SLIME_DIR - .get() - .wrap_err("No slime install directory for current platform")? - .join(p), - InstallPath::RelativeToSteam(p) => STEAM_DIR - .get() - .wrap_err("No steam install directory for current platform")? - .join(p), - }; - p.absolutize() - .map(|p| p.to_path_buf()) - .wrap_err("Failed to canonicalize install path") - } + /// Converts to the full absolute path of the file + pub fn to_path(&self) -> Result { + let p = match self { + InstallPath::Normal(p) => p.to_owned(), + InstallPath::RelativeToSlime(p) => SLIME_DIR + .get() + .wrap_err("No slime install directory for current platform")? + .join(p), + InstallPath::RelativeToSteam(p) => STEAM_DIR + .get() + .wrap_err("No steam install directory for current platform")? + .join(p), + }; + p.absolutize() + .map(|p| p.to_path_buf()) + .wrap_err("Failed to canonicalize install path") + } } diff --git a/autoupdater/src/parsing/mcp.rs b/autoupdater/src/parsing/mcp.rs index 97ddadd9..14179b5d 100644 --- a/autoupdater/src/parsing/mcp.rs +++ b/autoupdater/src/parsing/mcp.rs @@ -10,33 +10,33 @@ use super::Platform; #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, From)] #[serde(untagged)] pub enum MaybeCrossPlatform { - /// This `T` is the same across all platforms. - Cross(T), - /// This `T` depends on the `Platform` - NotCross(HashMap), + /// This `T` is the same across all platforms. + Cross(T), + /// This `T` depends on the `Platform` + NotCross(HashMap), } impl MaybeCrossPlatform { - /// Gets the `T` for the current platform. - pub fn get(&self) -> Option<&T> { - match self { - MaybeCrossPlatform::Cross(inner) => Some(inner), - MaybeCrossPlatform::NotCross(map) => map.get(Platform::current()), - } - } + /// Gets the `T` for the current platform. + pub fn get(&self) -> Option<&T> { + match self { + MaybeCrossPlatform::Cross(inner) => Some(inner), + MaybeCrossPlatform::NotCross(map) => map.get(Platform::current()), + } + } - pub fn get_mut(&mut self) -> Option<&mut T> { - match self { - MaybeCrossPlatform::Cross(inner) => Some(inner), - MaybeCrossPlatform::NotCross(map) => map.get_mut(Platform::current()), - } - } + pub fn get_mut(&mut self) -> Option<&mut T> { + match self { + MaybeCrossPlatform::Cross(inner) => Some(inner), + MaybeCrossPlatform::NotCross(map) => map.get_mut(Platform::current()), + } + } - pub fn get_owned(self) -> Option { - match self { - MaybeCrossPlatform::Cross(inner) => Some(inner), - MaybeCrossPlatform::NotCross(mut map) => map.remove(Platform::current()), - } - } + pub fn get_owned(self) -> Option { + match self { + MaybeCrossPlatform::Cross(inner) => Some(inner), + MaybeCrossPlatform::NotCross(mut map) => map.remove(Platform::current()), + } + } } /// Type alias so we don't have long ass names pub type MCP = MaybeCrossPlatform; diff --git a/autoupdater/src/parsing/mod.rs b/autoupdater/src/parsing/mod.rs index 1ad806e2..8d2c33e2 100644 --- a/autoupdater/src/parsing/mod.rs +++ b/autoupdater/src/parsing/mod.rs @@ -21,57 +21,57 @@ use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str}; /// Represents a target platform for SlimeVR #[derive( - Deserialize_enum_str, Serialize_enum_str, Clone, Debug, Eq, PartialEq, Hash, + Deserialize_enum_str, Serialize_enum_str, Clone, Debug, Eq, PartialEq, Hash, )] #[serde(rename_all = "snake_case")] pub enum Platform { - Windows64, - Linux64, - /// A platform we don't understand. - /// - /// We might get this variant if we added a new platform that we want to support, - /// but the updater hasn't updated to understand it yet. - #[serde(other)] - Unknown(String), + Windows64, + Linux64, + /// A platform we don't understand. + /// + /// We might get this variant if we added a new platform that we want to support, + /// but the updater hasn't updated to understand it yet. + #[serde(other)] + Unknown(String), } impl Platform { - pub fn current() -> &'static Platform { - #[cfg(all(target_os = "windows", target_arch = "x86_64"))] - return &Platform::Windows64; - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - return &Platform::Linux64; - lazy_static! { - static ref PLATFORM: Platform = Platform::Unknown("unknown".to_string()); - } - #[allow(unused)] - &PLATFORM - } + pub fn current() -> &'static Platform { + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + return &Platform::Windows64; + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + return &Platform::Linux64; + lazy_static! { + static ref PLATFORM: Platform = Platform::Unknown("unknown".to_string()); + } + #[allow(unused)] + &PLATFORM + } } /// Describes all the information about a component and how to install it. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct ComponentInfo { - /// The URL from which this component is downloaded. - pub download_url: MCP, - /// The dir to which this component is installed. - pub install_dir: MCP, + /// The URL from which this component is downloaded. + pub download_url: MCP, + /// The dir to which this component is installed. + pub install_dir: MCP, } #[derive(Deserialize_enum_str, Serialize_enum_str, Debug, Hash, Eq, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ComponentName { - Overlay, - Server, - Feeder, - Driver, - Gui, - AutoUpdater, - /// The name of a component we don't understand. - /// - /// This might happen if the autoupdater has not yet updated to know about this - /// component. - #[serde(other)] - Unknown(String), + Overlay, + Server, + Feeder, + Driver, + Gui, + AutoUpdater, + /// The name of a component we don't understand. + /// + /// This might happen if the autoupdater has not yet updated to know about this + /// component. + #[serde(other)] + Unknown(String), } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] @@ -79,66 +79,66 @@ pub struct Components(pub HashMap); #[cfg(test)] mod tests { - use std::path::PathBuf; + use std::path::PathBuf; - use super::*; + use super::*; - use lazy_static::lazy_static; + use lazy_static::lazy_static; - // These have been split out because rustfmt shits the bed when lines exceed the - // max_width and can't be wrapped for it - const SERVER_WINDOWS: &str = "https://github.com/SlimeVR/SlimeVR-Server/releases/download/v0.2.0/slimevr.jar"; - const OVERLAY_WINDOWS: &str = "https://github.com/SlimeVR/SlimeVR-Rust/releases/download/overlay-latest/windows-x64.zip"; - const OVERLAY_LINUX: &str = "https://github.com/SlimeVR/SlimeVR-Rust/releases/download/overlay-latest/linux-x64.zip"; + // These have been split out because rustfmt shits the bed when lines exceed the + // max_width and can't be wrapped for it + const SERVER_WINDOWS: &str = "https://github.com/SlimeVR/SlimeVR-Server/releases/download/v0.2.0/slimevr.jar"; + const OVERLAY_WINDOWS: &str = "https://github.com/SlimeVR/SlimeVR-Rust/releases/download/overlay-latest/windows-x64.zip"; + const OVERLAY_LINUX: &str = "https://github.com/SlimeVR/SlimeVR-Rust/releases/download/overlay-latest/linux-x64.zip"; - lazy_static! { - static ref EXAMPLE_STRUCT: Components = { - let mut components = HashMap::new(); - components.insert( - ComponentName::Server, - ComponentInfo { - download_url: Url::parse(SERVER_WINDOWS).unwrap().into(), - install_dir: InstallPath::RelativeToSlime(PathBuf::from("")).into(), - }, - ); - components.insert( - ComponentName::Overlay, - ComponentInfo { - download_url: MCP::NotCross(HashMap::from([ - (Platform::Windows64, Url::parse(OVERLAY_WINDOWS).unwrap()), - (Platform::Linux64, Url::parse(OVERLAY_LINUX).unwrap()), - ])), - install_dir: InstallPath::RelativeToSlime(PathBuf::from("overlay")) - .into(), - }, - ); - components.insert( - ComponentName::Unknown("a_new_component".to_string()), - ComponentInfo { - download_url: Url::parse("https://github.com/SlimeVR/whatever") - .unwrap() - .into(), - install_dir: InstallPath::Normal(PathBuf::from(r"D:\s\nuts")) - .into(), - }, - ); - components.insert( - ComponentName::Unknown("another_component".to_string()), - ComponentInfo { - download_url: Url::parse("https://github.com/slimeVR/another") - .unwrap() - .into(), - install_dir: MCP::NotCross(HashMap::from([ - (Platform::Windows64, InstallPath::Normal("".into())), - (Platform::Linux64, InstallPath::Normal("".into())), - ])), - }, - ); - Components(components) - }; - } + lazy_static! { + static ref EXAMPLE_STRUCT: Components = { + let mut components = HashMap::new(); + components.insert( + ComponentName::Server, + ComponentInfo { + download_url: Url::parse(SERVER_WINDOWS).unwrap().into(), + install_dir: InstallPath::RelativeToSlime(PathBuf::from("")).into(), + }, + ); + components.insert( + ComponentName::Overlay, + ComponentInfo { + download_url: MCP::NotCross(HashMap::from([ + (Platform::Windows64, Url::parse(OVERLAY_WINDOWS).unwrap()), + (Platform::Linux64, Url::parse(OVERLAY_LINUX).unwrap()), + ])), + install_dir: InstallPath::RelativeToSlime(PathBuf::from("overlay")) + .into(), + }, + ); + components.insert( + ComponentName::Unknown("a_new_component".to_string()), + ComponentInfo { + download_url: Url::parse("https://github.com/SlimeVR/whatever") + .unwrap() + .into(), + install_dir: InstallPath::Normal(PathBuf::from(r"D:\s\nuts")) + .into(), + }, + ); + components.insert( + ComponentName::Unknown("another_component".to_string()), + ComponentInfo { + download_url: Url::parse("https://github.com/slimeVR/another") + .unwrap() + .into(), + install_dir: MCP::NotCross(HashMap::from([ + (Platform::Windows64, InstallPath::Normal("".into())), + (Platform::Linux64, InstallPath::Normal("".into())), + ])), + }, + ); + Components(components) + }; + } - const EXAMPLE_STR: &str = r#" + const EXAMPLE_STR: &str = r#" server: download_url: https://github.com/SlimeVR/SlimeVR-Server/releases/download/v0.2.0/slimevr.jar install_dir: @@ -162,16 +162,16 @@ mod tests { normal: "" "#; - #[test] - fn test_round_trip() -> color_eyre::Result<()> { - let deserialized: Components = serde_yaml::from_str(EXAMPLE_STR)?; - let round_tripped: Components = - serde_yaml::from_str(&serde_yaml::to_string(&deserialized)?)?; + #[test] + fn test_round_trip() -> color_eyre::Result<()> { + let deserialized: Components = serde_yaml::from_str(EXAMPLE_STR)?; + let round_tripped: Components = + serde_yaml::from_str(&serde_yaml::to_string(&deserialized)?)?; - assert_eq!(deserialized, round_tripped); - println!("Example:\n{:#?}", *EXAMPLE_STRUCT); - println!("Deserialized:\n{:#?}", deserialized); - assert_eq!(deserialized, *EXAMPLE_STRUCT); - Ok(()) - } + assert_eq!(deserialized, round_tripped); + println!("Example:\n{:#?}", *EXAMPLE_STRUCT); + println!("Deserialized:\n{:#?}", deserialized); + assert_eq!(deserialized, *EXAMPLE_STRUCT); + Ok(()) + } } diff --git a/firmware/crates/panic_defmt/src/lib.rs b/firmware/crates/panic_defmt/src/lib.rs index 6a991d65..43c7410f 100644 --- a/firmware/crates/panic_defmt/src/lib.rs +++ b/firmware/crates/panic_defmt/src/lib.rs @@ -6,22 +6,22 @@ use defmt::error; #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - static PANICKED: AtomicBool = AtomicBool::new(false); + static PANICKED: AtomicBool = AtomicBool::new(false); - // TODO: What `Ordering` should this use? - if !PANICKED.swap(true, Ordering::SeqCst) { - if let Some(location) = info.location() { - let (file, line, column) = - (location.file(), location.line(), location.column()); - error!( - "A panic occured in '{}', at line {}, column {}", - file, line, column - ); - } else { - error!("A panic occured at an unknown location"); - } - } - error!("{:#?}", defmt::Debug2Format(info)); + // TODO: What `Ordering` should this use? + if !PANICKED.swap(true, Ordering::SeqCst) { + if let Some(location) = info.location() { + let (file, line, column) = + (location.file(), location.line(), location.column()); + error!( + "A panic occured in '{}', at line {}, column {}", + file, line, column + ); + } else { + error!("A panic occured at an unknown location"); + } + } + error!("{:#?}", defmt::Debug2Format(info)); - loop {} + loop {} } diff --git a/firmware/src/aliases.rs b/firmware/src/aliases.rs index 5983ea9e..0ed252f3 100644 --- a/firmware/src/aliases.rs +++ b/firmware/src/aliases.rs @@ -1,23 +1,23 @@ #[cfg(feature = "mcu-esp32c3")] mod āļž { - pub use esp32c3_hal::ehal; - pub type I2cConcrete = esp32c3_hal::i2c::I2C; - pub use esp32c3_hal::Delay as DelayConcrete; + pub use esp32c3_hal::ehal; + pub type I2cConcrete = esp32c3_hal::i2c::I2C; + pub use esp32c3_hal::Delay as DelayConcrete; } pub use āļž::{DelayConcrete, I2cConcrete}; pub trait I2c: - embedded_hal::blocking::i2c::Write::Error> - + embedded_hal::blocking::i2c::WriteRead::Error> + embedded_hal::blocking::i2c::Write::Error> + + embedded_hal::blocking::i2c::WriteRead::Error> { - type Error: core::fmt::Debug; + type Error: core::fmt::Debug; } impl< - T: embedded_hal::blocking::i2c::Write - + embedded_hal::blocking::i2c::WriteRead, - E: core::fmt::Debug, - > I2c for T + T: embedded_hal::blocking::i2c::Write + + embedded_hal::blocking::i2c::WriteRead, + E: core::fmt::Debug, + > I2c for T { - type Error = E; + type Error = E; } diff --git a/firmware/src/globals.rs b/firmware/src/globals.rs index a1eeb59d..204fcb33 100644 --- a/firmware/src/globals.rs +++ b/firmware/src/globals.rs @@ -16,23 +16,23 @@ pub use esp32c3_hal as ehal; /// Sets up any global state pub fn setup() { - // Initialize the global allocator BEFORE you use it - { - const HEAP_SIZE: usize = 10 * 1024; - static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE]; - unsafe { ALLOCATOR.init(HEAP.as_mut_ptr(), HEAP_SIZE) } - } + // Initialize the global allocator BEFORE you use it + { + const HEAP_SIZE: usize = 10 * 1024; + static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE]; + unsafe { ALLOCATOR.init(HEAP.as_mut_ptr(), HEAP_SIZE) } + } } /// This will be called when a hardware exception occurs #[export_name = "ExceptionHandler"] pub fn custom_exception_handler(trap_frame: &riscv_rt::TrapFrame) { - let mepc = riscv::register::mepc::read(); - let mcause = riscv::register::mcause::read(); - panic!( - "Unexpected hardware exception. MCAUSE: {:?}, RA: {:#x}, MEPC: {:#b}", - mcause.cause(), - trap_frame.ra, - mepc, - ) + let mepc = riscv::register::mepc::read(); + let mcause = riscv::register::mcause::read(); + panic!( + "Unexpected hardware exception. MCAUSE: {:?}, RA: {:#x}, MEPC: {:#b}", + mcause.cause(), + trap_frame.ra, + mepc, + ) } diff --git a/firmware/src/imu/mod.rs b/firmware/src/imu/mod.rs index f1d508c9..94a25916 100644 --- a/firmware/src/imu/mod.rs +++ b/firmware/src/imu/mod.rs @@ -12,31 +12,31 @@ pub type Quat = nalgebra::UnitQuaternion; #[derive(Debug, Eq, PartialEq)] pub enum ImuKind { - Mpu6050, + Mpu6050, } pub trait Imu { - type Error: core::fmt::Debug; + type Error: core::fmt::Debug; - const IMU_KIND: ImuKind; - fn quat(&mut self) -> nb::Result; + const IMU_KIND: ImuKind; + fn quat(&mut self) -> nb::Result; } /// Gets data from the IMU pub async fn imu_task(i2c: impl crate::aliases::I2c, mut delay: impl DelayMs) { - debug!("Started sensor_task"); - let mut imu = āļž::new_imu(i2c, &mut delay); - info!("Initialized IMU!"); - - loop { - let q = nb2a(|| imu.quat()).await.expect("Fatal IMU Error"); - trace!( - "Quat values: x: {}, y: {}, z: {}, w: {}", - q.coords.x, - q.coords.y, - q.coords.z, - q.coords.w - ); - yield_now().await // Yield to ensure fairness - } + debug!("Started sensor_task"); + let mut imu = āļž::new_imu(i2c, &mut delay); + info!("Initialized IMU!"); + + loop { + let q = nb2a(|| imu.quat()).await.expect("Fatal IMU Error"); + trace!( + "Quat values: x: {}, y: {}, z: {}, w: {}", + q.coords.x, + q.coords.y, + q.coords.z, + q.coords.w + ); + yield_now().await // Yield to ensure fairness + } } diff --git a/firmware/src/imu/mpu6050.rs b/firmware/src/imu/mpu6050.rs index 149e7363..a6236a05 100644 --- a/firmware/src/imu/mpu6050.rs +++ b/firmware/src/imu/mpu6050.rs @@ -9,68 +9,68 @@ use mpu6050_dmp::error::InitError; use mpu6050_dmp::sensor::Mpu6050 as LibMpu; pub struct Mpu6050 { - mpu: LibMpu, - fifo_buf: [u8; 28], + mpu: LibMpu, + fifo_buf: [u8; 28], } impl Mpu6050 { - pub fn new(i2c: I, delay: &mut impl DelayMs) -> Result> { - debug!("Constructing MPU..."); - let addr = Address::from(0x68); - debug!("I2C address: {:x}", addr.0); + pub fn new(i2c: I, delay: &mut impl DelayMs) -> Result> { + debug!("Constructing MPU..."); + let addr = Address::from(0x68); + debug!("I2C address: {:x}", addr.0); - utils::retry( - 4, - i2c, - |mut i2c| { - delay.delay_ms(100); - trace!("Flushing I2C with bogus data"); - let _ = i2c.write(addr.0, &[0]); - delay.delay_ms(100); - trace!("Constructing IMU"); - let mut mpu = LibMpu::new(i2c, addr) - // Map converts from struct -> tuple - .map_err(|InitError { i2c, error }| (i2c, error))?; - debug!("Constructed MPU"); - delay.delay_ms(100); - if let Err(error) = mpu.initialize_dmp(delay) { - return Err((mpu.release(), error)); - } - debug!("Initialized DMP"); - Ok(Self { - mpu, - fifo_buf: [0; 28], - }) - }, - |i| debug!("Retrying IMU connection (attempts so far: {})", i + 1), - ) - // Map converts from tuple -> struct - .map_err(|(i2c, error)| InitError { i2c, error }) - } + utils::retry( + 4, + i2c, + |mut i2c| { + delay.delay_ms(100); + trace!("Flushing I2C with bogus data"); + let _ = i2c.write(addr.0, &[0]); + delay.delay_ms(100); + trace!("Constructing IMU"); + let mut mpu = LibMpu::new(i2c, addr) + // Map converts from struct -> tuple + .map_err(|InitError { i2c, error }| (i2c, error))?; + debug!("Constructed MPU"); + delay.delay_ms(100); + if let Err(error) = mpu.initialize_dmp(delay) { + return Err((mpu.release(), error)); + } + debug!("Initialized DMP"); + Ok(Self { + mpu, + fifo_buf: [0; 28], + }) + }, + |i| debug!("Retrying IMU connection (attempts so far: {})", i + 1), + ) + // Map converts from tuple -> struct + .map_err(|(i2c, error)| InitError { i2c, error }) + } } impl Imu for Mpu6050 { - type Error = mpu6050_dmp::error::Error; + type Error = mpu6050_dmp::error::Error; - const IMU_KIND: super::ImuKind = ImuKind::Mpu6050; + const IMU_KIND: super::ImuKind = ImuKind::Mpu6050; - fn quat(&mut self) -> nb::Result { - if self.mpu.get_fifo_count()? >= 28 { - let data = self.mpu.read_fifo(&mut self.fifo_buf)?; - let data = &data[..16]; - let q = mpu6050_dmp::quaternion::Quaternion::from_bytes(data).unwrap(); - let q = nalgebra::Quaternion { - coords: nalgebra::vector![q.x, q.y, q.z, q.w], - }; - Ok(Quat::from_quaternion(q)) - } else { - Err(nb::Error::WouldBlock) - } - } + fn quat(&mut self) -> nb::Result { + if self.mpu.get_fifo_count()? >= 28 { + let data = self.mpu.read_fifo(&mut self.fifo_buf)?; + let data = &data[..16]; + let q = mpu6050_dmp::quaternion::Quaternion::from_bytes(data).unwrap(); + let q = nalgebra::Quaternion { + coords: nalgebra::vector![q.x, q.y, q.z, q.w], + }; + Ok(Quat::from_quaternion(q)) + } else { + Err(nb::Error::WouldBlock) + } + } } pub fn new_imu( - i2c: impl crate::aliases::I2c, - delay: &mut impl DelayMs, + i2c: impl crate::aliases::I2c, + delay: &mut impl DelayMs, ) -> impl crate::imu::Imu { - Mpu6050::new(i2c, delay).expect("Failed to initialize MPU6050") + Mpu6050::new(i2c, delay).expect("Failed to initialize MPU6050") } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index d407de9b..ac96bf54 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -17,35 +17,35 @@ use static_cell::StaticCell; #[entry] fn main() -> ! { - self::globals::setup(); - debug!("Booted"); + self::globals::setup(); + debug!("Booted"); - let p = self::peripherals::get_peripherals(); - debug!("Initialized peripherals"); - p.delay.delay(1000); + let p = self::peripherals::get_peripherals(); + debug!("Initialized peripherals"); + p.delay.delay(1000); - static EXECUTOR: StaticCell = StaticCell::new(); - EXECUTOR.init(Executor::new()).run(move |spawner| { - spawner.spawn(network_task()).unwrap(); - spawner.spawn(imu_task(p.i2c, p.delay)).unwrap(); - }); + static EXECUTOR: StaticCell = StaticCell::new(); + EXECUTOR.init(Executor::new()).run(move |spawner| { + spawner.spawn(network_task()).unwrap(); + spawner.spawn(imu_task(p.i2c, p.delay)).unwrap(); + }); } #[task] async fn network_task() { - debug!("Started network_task"); - let mut i = 0; - loop { - trace!("In main(), i was {}", i); - i += 1; - yield_now().await // Yield to ensure fairness - } + debug!("Started network_task"); + let mut i = 0; + loop { + trace!("In main(), i was {}", i); + i += 1; + yield_now().await // Yield to ensure fairness + } } #[task] async fn imu_task( - i2c: crate::aliases::I2cConcrete, - delay: crate::aliases::DelayConcrete, + i2c: crate::aliases::I2cConcrete, + delay: crate::aliases::DelayConcrete, ) { - crate::imu::imu_task(i2c, delay).await + crate::imu::imu_task(i2c, delay).await } diff --git a/firmware/src/peripherals/esp32c3.rs b/firmware/src/peripherals/esp32c3.rs index 49da46f2..280b076a 100644 --- a/firmware/src/peripherals/esp32c3.rs +++ b/firmware/src/peripherals/esp32c3.rs @@ -6,38 +6,38 @@ use fugit::RateExtU32; use esp32c3_hal::{clock::ClockControl, prelude::*, timer::TimerGroup, Rtc}; pub fn get_peripherals() -> Peripherals { - let p = esp32c3_hal::pac::Peripherals::take().unwrap(); - - let mut system = p.SYSTEM.split(); - let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); - - // Disable the RTC and TIMG watchdog timers - { - let mut rtc = Rtc::new(p.RTC_CNTL); - let timer_group0 = TimerGroup::new(p.TIMG0, &clocks); - let mut wdt0 = timer_group0.wdt; - let timer_group1 = TimerGroup::new(p.TIMG1, &clocks); - let mut wdt1 = timer_group1.wdt; - - rtc.rwdt.disable(); - rtc.swd.disable(); - wdt0.disable(); - wdt1.disable(); - } - - let io = esp32c3_hal::IO::new(p.GPIO, p.IO_MUX); - // let hz = - let i2c = esp32c3_hal::i2c::I2C::new( - p.I2C0, - io.pins.gpio10, - io.pins.gpio8, - 400u32.kHz(), - &mut system.peripheral_clock_control, - &clocks, - ) - .expect("Failed to set up i2c"); - - let delay = esp32c3_hal::Delay::new(&clocks); - - Peripherals { i2c, delay } + let p = esp32c3_hal::pac::Peripherals::take().unwrap(); + + let mut system = p.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the RTC and TIMG watchdog timers + { + let mut rtc = Rtc::new(p.RTC_CNTL); + let timer_group0 = TimerGroup::new(p.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(p.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.rwdt.disable(); + rtc.swd.disable(); + wdt0.disable(); + wdt1.disable(); + } + + let io = esp32c3_hal::IO::new(p.GPIO, p.IO_MUX); + // let hz = + let i2c = esp32c3_hal::i2c::I2C::new( + p.I2C0, + io.pins.gpio10, + io.pins.gpio8, + 400u32.kHz(), + &mut system.peripheral_clock_control, + &clocks, + ) + .expect("Failed to set up i2c"); + + let delay = esp32c3_hal::Delay::new(&clocks); + + Peripherals { i2c, delay } } diff --git a/firmware/src/peripherals/mod.rs b/firmware/src/peripherals/mod.rs index fa861406..9ec80662 100644 --- a/firmware/src/peripherals/mod.rs +++ b/firmware/src/peripherals/mod.rs @@ -9,6 +9,6 @@ pub use āļž::get_peripherals; use crate::aliases::I2c; pub struct Peripherals> { - pub i2c: I, - pub delay: D, + pub i2c: I, + pub delay: D, } diff --git a/firmware/src/utils.rs b/firmware/src/utils.rs index 97c0fc8a..a071f095 100644 --- a/firmware/src/utils.rs +++ b/firmware/src/utils.rs @@ -13,37 +13,37 @@ use embassy_futures::yield_now; /// again on a retry. `retry_num` goes from `0..n`. Often useful for logging or /// controlling a delay between invocations of `f`. pub fn retry( - n: u8, - acc: A, - mut f: impl FnMut(A) -> Result, - mut before_retry: impl FnMut(u8), + n: u8, + acc: A, + mut f: impl FnMut(A) -> Result, + mut before_retry: impl FnMut(u8), ) -> Result { - // First attempt - let mut last_result = f(acc); - // Any additional attempts, up to `n` times. Each time we update `last_result`. - for i in 0..n { - let acc = match last_result { - Ok(t) => { - last_result = Ok(t); - break; - } - Err((acc, _err)) => acc, - }; - before_retry(i); - last_result = f(acc); - } - last_result + // First attempt + let mut last_result = f(acc); + // Any additional attempts, up to `n` times. Each time we update `last_result`. + for i in 0..n { + let acc = match last_result { + Ok(t) => { + last_result = Ok(t); + break; + } + Err((acc, _err)) => acc, + }; + before_retry(i); + last_result = f(acc); + } + last_result } /// Converts a nb::Result to an async function by looping and yielding to the async /// executor. pub async fn nb2a(mut f: impl FnMut() -> nb::Result) -> Result { - loop { - let v = f(); - match v { - Ok(t) => return Ok(t), - Err(nb::Error::Other(e)) => return Err(e), - Err(nb::Error::WouldBlock) => yield_now().await, - } - } + loop { + let v = f(); + match v { + Ok(t) => return Ok(t), + Err(nb::Error::Other(e)) => return Err(e), + Err(nb::Error::WouldBlock) => yield_now().await, + } + } } diff --git a/overlay/app/src/client/data.rs b/overlay/app/src/client/data.rs index c6970277..2f09787d 100644 --- a/overlay/app/src/client/data.rs +++ b/overlay/app/src/client/data.rs @@ -7,49 +7,49 @@ use std::fmt::Debug; #[derive(thiserror::Error, Debug)] pub enum DecodeError { - #[error("Flatbuffer failed verification: {0}")] - FbVerification(#[from] InvalidFlatbuffer), - // #[error("Io error: {0}")] - // Io(#[from] std::io::Error), + #[error("Flatbuffer failed verification: {0}")] + FbVerification(#[from] InvalidFlatbuffer), + // #[error("Io error: {0}")] + // Io(#[from] std::io::Error), } /// Root flatbuffer type, after verification #[self_referencing] pub struct Data { - data: Vec, - #[borrows(data)] - #[covariant] - table: MessageBundle<'this>, + data: Vec, + #[borrows(data)] + #[covariant] + table: MessageBundle<'this>, } impl Data { - pub unsafe fn from_vec_unchecked(data: Vec) -> Self { - Self::new(data, |v| flatbuffers::root_unchecked::(v)) - } - - pub fn from_vec(data: Vec) -> Result, DecodeError)> { - Self::try_new_or_recover(data, |v| flatbuffers::root::(v)) - .map_err(|(e, data)| (data.data, e.into())) - } - - pub fn into_vec(self) -> Vec { - self.into_heads().data - } - - pub fn as_slice(&self) -> &[u8] { - self.with_data(|v| v.as_slice()) - } - - pub fn table(&self) -> MessageBundle { - *self.borrow_table() - } + pub unsafe fn from_vec_unchecked(data: Vec) -> Self { + Self::new(data, |v| flatbuffers::root_unchecked::(v)) + } + + pub fn from_vec(data: Vec) -> Result, DecodeError)> { + Self::try_new_or_recover(data, |v| flatbuffers::root::(v)) + .map_err(|(e, data)| (data.data, e.into())) + } + + pub fn into_vec(self) -> Vec { + self.into_heads().data + } + + pub fn as_slice(&self) -> &[u8] { + self.with_data(|v| v.as_slice()) + } + + pub fn table(&self) -> MessageBundle { + *self.borrow_table() + } } impl Debug for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Data") - .field("table", self.borrow_table()) - .finish() - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Data") + .field("table", self.borrow_table()) + .finish() + } } #[derive(Debug)] diff --git a/overlay/app/src/client/mod.rs b/overlay/app/src/client/mod.rs index 9c8d9373..ecbc61b0 100644 --- a/overlay/app/src/client/mod.rs +++ b/overlay/app/src/client/mod.rs @@ -19,95 +19,95 @@ use tokio_tungstenite::WebSocketStream; type Wss = WebSocketStream>; pub struct Client { - socket_task: JoinHandle>, + socket_task: JoinHandle>, } impl Client { - pub fn new( - connect_to: String, - sub: SubsystemHandle, - ) -> Result<(Self, watch::Receiver>)> { - let (data_send, data_recv) = watch::channel(None); + pub fn new( + connect_to: String, + sub: SubsystemHandle, + ) -> Result<(Self, watch::Receiver>)> { + let (data_send, data_recv) = watch::channel(None); - let socket_task = task::spawn(async move { - tokio::select! { - _ = sub.on_shutdown_requested() => {} - result = Self::run(connect_to, data_send) => {result?} - }; - Ok(()) - }); + let socket_task = task::spawn(async move { + tokio::select! { + _ = sub.on_shutdown_requested() => {} + result = Self::run(connect_to, data_send) => {result?} + }; + Ok(()) + }); - Ok((Self { socket_task }, data_recv)) - } + Ok((Self { socket_task }, data_recv)) + } - async fn run( - connect_to: String, - data_send: watch::Sender>, - ) -> Result<()> { - let mut disconnected = Some(ClientStateMachine::new(connect_to)); - loop { - let ready = match disconnected.take().unwrap().connect().await { - Ok(ready) => ready, - Err((d, err)) => { - log::error!("Error while connecting: {}", err); - disconnected = Some(d); - continue; - } - }; - let active = match ready.request_feed().await { - Ok(active) => active, - Err((d, err)) => { - log::error!( - "{:?}", - eyre::Report::new(err).wrap_err("Failed to request feed") - ); - disconnected = Some(d); - continue; - } - }; - let mut active = Some(active); - loop { - use RecvError as E; - match active.take().unwrap().recv().await { - Ok((a, update)) => { - log::trace!("Sending data to watchers: {:#?}", update); - active = Some(a); - data_send.send_replace(Some(update)); - } - Err(err) => { - let display = format!("{}", &err); - match err { - E::CriticalWs(d, _) => { - log::error!("Critical websocket error: {}", display); - disconnected = Some(d); - break; - } - E::None(d) => { - log::error!("Critical websocket error: {}", display); - disconnected = Some(d); - break; - } - E::Deserialize(a, DeserializeError::PayloadType(_)) => { - active = Some(a) - } - E::Deserialize(a, d_err) => { - match d_err { - DeserializeError::PayloadType(_) => { - log::trace!("{}", d_err) - } - _ => { - log::warn!("Deserialization error: {}", display) - } - } - active = Some(a); - } - } - } - } - } - } - } + async fn run( + connect_to: String, + data_send: watch::Sender>, + ) -> Result<()> { + let mut disconnected = Some(ClientStateMachine::new(connect_to)); + loop { + let ready = match disconnected.take().unwrap().connect().await { + Ok(ready) => ready, + Err((d, err)) => { + log::error!("Error while connecting: {}", err); + disconnected = Some(d); + continue; + } + }; + let active = match ready.request_feed().await { + Ok(active) => active, + Err((d, err)) => { + log::error!( + "{:?}", + eyre::Report::new(err).wrap_err("Failed to request feed") + ); + disconnected = Some(d); + continue; + } + }; + let mut active = Some(active); + loop { + use RecvError as E; + match active.take().unwrap().recv().await { + Ok((a, update)) => { + log::trace!("Sending data to watchers: {:#?}", update); + active = Some(a); + data_send.send_replace(Some(update)); + } + Err(err) => { + let display = format!("{}", &err); + match err { + E::CriticalWs(d, _) => { + log::error!("Critical websocket error: {}", display); + disconnected = Some(d); + break; + } + E::None(d) => { + log::error!("Critical websocket error: {}", display); + disconnected = Some(d); + break; + } + E::Deserialize(a, DeserializeError::PayloadType(_)) => { + active = Some(a) + } + E::Deserialize(a, d_err) => { + match d_err { + DeserializeError::PayloadType(_) => { + log::trace!("{}", d_err) + } + _ => { + log::warn!("Deserialization error: {}", display) + } + } + active = Some(a); + } + } + } + } + } + } + } - pub async fn join(self) -> Result<()> { - self.socket_task.await.wrap_err("Failed to join!")? - } + pub async fn join(self) -> Result<()> { + self.socket_task.await.wrap_err("Failed to join!")? + } } diff --git a/overlay/app/src/client/state_machine.rs b/overlay/app/src/client/state_machine.rs index c629469e..407f439d 100644 --- a/overlay/app/src/client/state_machine.rs +++ b/overlay/app/src/client/state_machine.rs @@ -24,30 +24,30 @@ impl SlimeSinkT for T where T: Sink + Send + Debug {} /// Data common to all states goes here #[derive(Debug)] struct Common { - connect_to: String, + connect_to: String, } #[derive(Debug)] pub struct ClientStateMachine { - state: State, - common: Common, + state: State, + common: Common, } impl ClientStateMachine { - /// Creates a new `NetworkStateMachine`. This starts in the [`Disconnected`] state. - pub fn new(connect_to: String) -> Self { - Self { - state: Disconnected, - common: Common { connect_to }, - } - } + /// Creates a new `NetworkStateMachine`. This starts in the [`Disconnected`] state. + pub fn new(connect_to: String) -> Self { + Self { + state: Disconnected, + common: Common { connect_to }, + } + } } impl ClientStateMachine { - /// Helper function to transition to next state while preserving all common data - fn into_state(self, state: Next) -> ClientStateMachine { - ClientStateMachine { - common: self.common, - state, - } - } + /// Helper function to transition to next state while preserving all common data + fn into_state(self, state: Next) -> ClientStateMachine { + ClientStateMachine { + common: self.common, + state, + } + } } // Makes things easier to type @@ -57,187 +57,187 @@ type M = ClientStateMachine; #[derive(thiserror::Error, Debug)] pub enum DeserializeError { - #[error("Decoding error: {1}")] - DecodeError(Vec, #[source] DecodeError), - #[error("Payload was an invalid type")] - PayloadType(Message), - #[error(transparent)] - Ws(#[from] WsError), + #[error("Decoding error: {1}")] + DecodeError(Vec, #[source] DecodeError), + #[error("Payload was an invalid type")] + PayloadType(Message), + #[error(transparent)] + Ws(#[from] WsError), } /// Client is fully disconnected from the server. #[derive(Debug)] pub struct Disconnected; impl M { - pub async fn connect(self) -> Result, (Self, WsError)> { - match connect_async(&self.common.connect_to).await { - Ok((socket, _)) => { - let (sink, stream) = socket.split(); - - // We never actually error, but this signature satisfies `sink.with()` - // `Ready` is required because otherwise our `Future` won't implement `Debug` - fn serialize(data: Data) -> future::Ready> { - let v = data.into_vec(); - log::trace!("Sending serialized data: {v:?}"); - future::ready(Ok(Message::Binary(v))) - } - - fn deserialize( - msg: Result, - ) -> Result { - log::trace!("Received websocket message"); - use DeserializeError::*; - match msg { - Ok(Message::Binary(v)) => { - Data::from_vec(v).map_err(|e| DecodeError(e.0, e.1)) - } - Ok(m) => Err(PayloadType(m)), - Err(err) => Err(Ws(err)), - } - } - - let sink = sink.with(serialize); - let sink: Pin> = Box::pin(sink); - let stream: SlimeStream = stream.map(deserialize); - Ok(self.into_state(Connected { - sink, - stream, - fbb: FlatBufferBuilder::new(), - })) - } - Err(e) => Err((self.into_state(Disconnected), e)), - } - } + pub async fn connect(self) -> Result, (Self, WsError)> { + match connect_async(&self.common.connect_to).await { + Ok((socket, _)) => { + let (sink, stream) = socket.split(); + + // We never actually error, but this signature satisfies `sink.with()` + // `Ready` is required because otherwise our `Future` won't implement `Debug` + fn serialize(data: Data) -> future::Ready> { + let v = data.into_vec(); + log::trace!("Sending serialized data: {v:?}"); + future::ready(Ok(Message::Binary(v))) + } + + fn deserialize( + msg: Result, + ) -> Result { + log::trace!("Received websocket message"); + use DeserializeError::*; + match msg { + Ok(Message::Binary(v)) => { + Data::from_vec(v).map_err(|e| DecodeError(e.0, e.1)) + } + Ok(m) => Err(PayloadType(m)), + Err(err) => Err(Ws(err)), + } + } + + let sink = sink.with(serialize); + let sink: Pin> = Box::pin(sink); + let stream: SlimeStream = stream.map(deserialize); + Ok(self.into_state(Connected { + sink, + stream, + fbb: FlatBufferBuilder::new(), + })) + } + Err(e) => Err((self.into_state(Disconnected), e)), + } + } } /// Client is connected over websocket #[derive(Debug)] pub struct Connected { - sink: Pin, - stream: SlimeStream, - fbb: FlatBufferBuilder<'static>, + sink: Pin, + stream: SlimeStream, + fbb: FlatBufferBuilder<'static>, } impl M { - pub async fn request_feed( - mut self, - ) -> Result, (M, WsError)> { - use solarxr_protocol::{ - data_feed::{DataFeedMessageHeader, DataFeedMessageHeaderArgs}, - MessageBundleArgs, - }; - let fbb = &mut self.state.fbb; - let data = { - use solarxr_protocol::data_feed::tracker::{ - TrackerDataMask, TrackerDataMaskArgs, - }; - use solarxr_protocol::data_feed::{ - DataFeedConfig, DataFeedConfigArgs, DataFeedMessage, StartDataFeed, - StartDataFeedArgs, - }; - - let _tracker_mask = TrackerDataMask::create( - fbb, - &TrackerDataMaskArgs { - // TODO: We only need the body part here, not the whole TrackerInfo - info: true, - rotation: true, - position: true, - ..Default::default() - }, - ); - - let data_feed_config = DataFeedConfig::create( - fbb, - &DataFeedConfigArgs { - minimum_time_since_last: 10, - // We don't care about anything but bones - bone_mask: true, - ..Default::default() - }, - ); - let data_feed_config = fbb.create_vector(&[data_feed_config]); - - let start_data_feed = StartDataFeed::create( - fbb, - &StartDataFeedArgs { - data_feeds: Some(data_feed_config), - }, - ); - let header = DataFeedMessageHeader::create( - fbb, - &DataFeedMessageHeaderArgs { - message_type: DataFeedMessage::StartDataFeed, - message: Some(start_data_feed.as_union_value()), - ..Default::default() - }, - ); - let header = fbb.create_vector(&[header]); - let root = MessageBundle::create( - fbb, - &MessageBundleArgs { - data_feed_msgs: Some(header), - ..Default::default() - }, - ); - fbb.finish(root, None); - let v = fbb.finished_data().to_vec(); - - #[cfg(not(debug_assertions))] - unsafe { - Data::from_vec_unchecked(v) - } - #[cfg(debug_assertions)] - Data::from_vec(v).unwrap() - }; - - let mut sink = self.state.sink.as_mut(); - match sink.send(data).await { - Ok(()) => Ok(M { - common: self.common, - state: Active { - _sink: self.state.sink, - stream: self.state.stream, - }, - }), - Err(err) => Err(( - M { - common: self.common, - state: Disconnected, - }, - err, - )), - } - } + pub async fn request_feed( + mut self, + ) -> Result, (M, WsError)> { + use solarxr_protocol::{ + data_feed::{DataFeedMessageHeader, DataFeedMessageHeaderArgs}, + MessageBundleArgs, + }; + let fbb = &mut self.state.fbb; + let data = { + use solarxr_protocol::data_feed::tracker::{ + TrackerDataMask, TrackerDataMaskArgs, + }; + use solarxr_protocol::data_feed::{ + DataFeedConfig, DataFeedConfigArgs, DataFeedMessage, StartDataFeed, + StartDataFeedArgs, + }; + + let _tracker_mask = TrackerDataMask::create( + fbb, + &TrackerDataMaskArgs { + // TODO: We only need the body part here, not the whole TrackerInfo + info: true, + rotation: true, + position: true, + ..Default::default() + }, + ); + + let data_feed_config = DataFeedConfig::create( + fbb, + &DataFeedConfigArgs { + minimum_time_since_last: 10, + // We don't care about anything but bones + bone_mask: true, + ..Default::default() + }, + ); + let data_feed_config = fbb.create_vector(&[data_feed_config]); + + let start_data_feed = StartDataFeed::create( + fbb, + &StartDataFeedArgs { + data_feeds: Some(data_feed_config), + }, + ); + let header = DataFeedMessageHeader::create( + fbb, + &DataFeedMessageHeaderArgs { + message_type: DataFeedMessage::StartDataFeed, + message: Some(start_data_feed.as_union_value()), + ..Default::default() + }, + ); + let header = fbb.create_vector(&[header]); + let root = MessageBundle::create( + fbb, + &MessageBundleArgs { + data_feed_msgs: Some(header), + ..Default::default() + }, + ); + fbb.finish(root, None); + let v = fbb.finished_data().to_vec(); + + #[cfg(not(debug_assertions))] + unsafe { + Data::from_vec_unchecked(v) + } + #[cfg(debug_assertions)] + Data::from_vec(v).unwrap() + }; + + let mut sink = self.state.sink.as_mut(); + match sink.send(data).await { + Ok(()) => Ok(M { + common: self.common, + state: Active { + _sink: self.state.sink, + stream: self.state.stream, + }, + }), + Err(err) => Err(( + M { + common: self.common, + state: Disconnected, + }, + err, + )), + } + } } /// Datafeed is active #[derive(Debug)] pub struct Active { - _sink: Pin, - stream: SlimeStream, + _sink: Pin, + stream: SlimeStream, } impl M { - pub async fn recv(mut self) -> RecvResult { - use RecvError as E; - match self.state.stream.next().await { - Some(Ok(v)) => Ok((self, FeedUpdate(v))), - Some(Err(DeserializeError::Ws(ws_err))) => { - Err(E::CriticalWs(self.into_state(Disconnected), ws_err)) - } - Some(Err(err)) => Err(E::Deserialize(self, err)), - None => Err(E::None(self.into_state(Disconnected))), - } - } + pub async fn recv(mut self) -> RecvResult { + use RecvError as E; + match self.state.stream.next().await { + Some(Ok(v)) => Ok((self, FeedUpdate(v))), + Some(Err(DeserializeError::Ws(ws_err))) => { + Err(E::CriticalWs(self.into_state(Disconnected), ws_err)) + } + Some(Err(err)) => Err(E::Deserialize(self, err)), + None => Err(E::None(self.into_state(Disconnected))), + } + } } #[derive(thiserror::Error, Debug)] pub enum RecvError { - #[error("Critical websocket error: {1}")] - CriticalWs(M, WsError), - #[error("Error while deserializing: {1}")] - Deserialize(M, DeserializeError), - #[error("Stream produced `None`")] - None(M), + #[error("Critical websocket error: {1}")] + CriticalWs(M, WsError), + #[error("Error while deserializing: {1}")] + Deserialize(M, DeserializeError), + #[error("Stream produced `None`")] + None(M), } pub type RecvResult = Result<(M, FeedUpdate), RecvError>; diff --git a/overlay/app/src/color.rs b/overlay/app/src/color.rs index 637f740f..1afb3a9e 100644 --- a/overlay/app/src/color.rs +++ b/overlay/app/src/color.rs @@ -1,37 +1,37 @@ macro_rules! def_color { - ($name:ident, $r:literal, $g:literal, $b: literal, $a: literal) => { - pub const $name: RGBA = RGBA::new($r, $g, $b, $a); - }; - ($name:ident, $r:literal, $g:literal, $b:literal) => { - def_color!($name, $r, $g, $b, 255); - }; + ($name:ident, $r:literal, $g:literal, $b: literal, $a: literal) => { + pub const $name: RGBA = RGBA::new($r, $g, $b, $a); + }; + ($name:ident, $r:literal, $g:literal, $b:literal) => { + def_color!($name, $r, $g, $b, 255); + }; } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct RGBA { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, } impl RGBA { - pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - def_color!(WHITE, 255, 255, 255); - def_color!(SILVER, 192, 192, 192); - def_color!(GRAY, 128, 128, 128); - def_color!(BLACK, 0, 0, 0); - def_color!(RED, 255, 0, 0); - def_color!(MAROON, 128, 0, 0); - def_color!(YELLOW, 255, 255, 0); - def_color!(OLIVE, 128, 128, 0); - def_color!(LIME, 0, 255, 0); - def_color!(GREEN, 0, 128, 0); - def_color!(AQUA, 0, 255, 255); - def_color!(TEAL, 0, 128, 128); - def_color!(BLUE, 0, 0, 255); - def_color!(NAVY, 0, 0, 128); - def_color!(FUCHSIA, 255, 0, 255); - def_color!(PURPLE, 128, 0, 128); + pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } + def_color!(WHITE, 255, 255, 255); + def_color!(SILVER, 192, 192, 192); + def_color!(GRAY, 128, 128, 128); + def_color!(BLACK, 0, 0, 0); + def_color!(RED, 255, 0, 0); + def_color!(MAROON, 128, 0, 0); + def_color!(YELLOW, 255, 255, 0); + def_color!(OLIVE, 128, 128, 0); + def_color!(LIME, 0, 255, 0); + def_color!(GREEN, 0, 128, 0); + def_color!(AQUA, 0, 255, 255); + def_color!(TEAL, 0, 128, 128); + def_color!(BLUE, 0, 0, 255); + def_color!(NAVY, 0, 0, 128); + def_color!(FUCHSIA, 255, 0, 255); + def_color!(PURPLE, 128, 0, 128); } diff --git a/overlay/app/src/lib.rs b/overlay/app/src/lib.rs index 63f63a0a..98281665 100644 --- a/overlay/app/src/lib.rs +++ b/overlay/app/src/lib.rs @@ -20,146 +20,146 @@ const CONNECT_STR: &str = "ws://localhost:21110"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ShutdownReason { - CtrlC, + CtrlC, } macro_rules! unwrap_or_continue { - ($e:expr) => {{ - if let Some(inner) = $e { - inner - } else { - continue; - } - }}; + ($e:expr) => {{ + if let Some(inner) = $e { + inner + } else { + continue; + } + }}; } #[tokio::main] pub async fn main() -> Result<()> { - Toplevel::new() - .start("Networking", networking) - .catch_signals() - .handle_shutdown_requests(Duration::from_millis(1000)) - .await + Toplevel::new() + .start("Networking", networking) + .catch_signals() + .handle_shutdown_requests(Duration::from_millis(1000)) + .await } async fn overlay( - mut recv: watch::Receiver>, - subsys: SubsystemHandle, + mut recv: watch::Receiver>, + subsys: SubsystemHandle, ) -> Result<()> { - log::info!("Initializing OpenVR context"); - let context = ovr::Context::init().wrap_err("Failed to initialize OpenVR")?; - let mngr = &mut context.overlay_mngr(); - - let mut skeleton = SkeletonBuilder::default() - .build(mngr) - .wrap_err("Could not create skeleton")?; - - log::info!("Overlay Loop"); - - let loop_ = async { - let mut hidden_bones: HashSet = HashSet::new(); - loop { - recv.changed() - .await - .wrap_err("Error while attempting to watch for feed update")?; - - log::trace!("Got a feed update"); - - // Mark all bones as "need to hide" - hidden_bones.extend(BoneKind::iter()); - - let bones: Vec<_> = { - let guard = recv.borrow_and_update(); - let table = guard.as_ref().unwrap().0.table(); - log::trace!("update: {:#?}", table); - - let m = unwrap_or_continue!(table.data_feed_msgs()); - let m = m.get(0); - let m = unwrap_or_continue!(m.message_as_data_feed_update()); - let bones = unwrap_or_continue!(m.bones()); - log::debug!("Got {} bones before filtering", bones.len()); - - bones - .iter() - .filter_map(|b| { - let part = b.body_part(); - log::trace!("body_part: {part:?}"); - let bone_kind = BoneKind::try_from(part) - .map_err(|e| { - log::trace!("Filtering out {e:?}"); - e - }) - .ok()?; - let pos = if let Some(p) = b.head_position_g() { - p - } else { - log::warn!("No position"); - return None; - }; - let rot = if let Some(r) = b.rotation_g() { - r - } else { - log::warn!("No rotation"); - return None; - }; - let length = b.bone_length(); - - let pos = Translation3::new(pos.x(), pos.y(), pos.z()); - let rot = UnitQuaternion::from_quaternion( - [rot.x(), rot.y(), rot.z(), rot.w()].into(), - ); - hidden_bones.remove(&bone_kind); - Some((bone_kind, pos, rot, length)) - }) - .collect() - }; - log::debug!( - "Bones after filtering: {:?}", - bones.iter().map(|t| t.0).collect::>() - ); - log::trace!("Bone data: {bones:?}"); - for (bone_kind, pos, rot, length) in bones { - let iso = Isometry { - rotation: rot, - translation: pos, - }; - skeleton.set_isometry(bone_kind, iso); - skeleton.set_length(bone_kind, length); - skeleton.set_visibility(bone_kind, true); - if let Err(e) = skeleton.update_render(bone_kind, mngr) { - log::error!( - "Error updating render for bone {bone_kind:?}: {:?}", - e - ); - } - } - for bone_kind in hidden_bones.iter() { - skeleton.set_visibility(*bone_kind, false); - if let Err(e) = skeleton.update_render(*bone_kind, mngr) { - log::error!( - "Error updating render for bone {bone_kind:?}: {:?}", - e - ); - } - } - } - }; - tokio::select! { - _ = subsys.on_shutdown_requested() => { - log::debug!("overlay shutdown requested"); - Ok::<_, eyre::Report>(()) - }, - r = loop_ => r, - }?; - - log::info!("Shutting down OpenVR context"); - unsafe { context.shutdown() }; - Ok(()) + log::info!("Initializing OpenVR context"); + let context = ovr::Context::init().wrap_err("Failed to initialize OpenVR")?; + let mngr = &mut context.overlay_mngr(); + + let mut skeleton = SkeletonBuilder::default() + .build(mngr) + .wrap_err("Could not create skeleton")?; + + log::info!("Overlay Loop"); + + let loop_ = async { + let mut hidden_bones: HashSet = HashSet::new(); + loop { + recv.changed() + .await + .wrap_err("Error while attempting to watch for feed update")?; + + log::trace!("Got a feed update"); + + // Mark all bones as "need to hide" + hidden_bones.extend(BoneKind::iter()); + + let bones: Vec<_> = { + let guard = recv.borrow_and_update(); + let table = guard.as_ref().unwrap().0.table(); + log::trace!("update: {:#?}", table); + + let m = unwrap_or_continue!(table.data_feed_msgs()); + let m = m.get(0); + let m = unwrap_or_continue!(m.message_as_data_feed_update()); + let bones = unwrap_or_continue!(m.bones()); + log::debug!("Got {} bones before filtering", bones.len()); + + bones + .iter() + .filter_map(|b| { + let part = b.body_part(); + log::trace!("body_part: {part:?}"); + let bone_kind = BoneKind::try_from(part) + .map_err(|e| { + log::trace!("Filtering out {e:?}"); + e + }) + .ok()?; + let pos = if let Some(p) = b.head_position_g() { + p + } else { + log::warn!("No position"); + return None; + }; + let rot = if let Some(r) = b.rotation_g() { + r + } else { + log::warn!("No rotation"); + return None; + }; + let length = b.bone_length(); + + let pos = Translation3::new(pos.x(), pos.y(), pos.z()); + let rot = UnitQuaternion::from_quaternion( + [rot.x(), rot.y(), rot.z(), rot.w()].into(), + ); + hidden_bones.remove(&bone_kind); + Some((bone_kind, pos, rot, length)) + }) + .collect() + }; + log::debug!( + "Bones after filtering: {:?}", + bones.iter().map(|t| t.0).collect::>() + ); + log::trace!("Bone data: {bones:?}"); + for (bone_kind, pos, rot, length) in bones { + let iso = Isometry { + rotation: rot, + translation: pos, + }; + skeleton.set_isometry(bone_kind, iso); + skeleton.set_length(bone_kind, length); + skeleton.set_visibility(bone_kind, true); + if let Err(e) = skeleton.update_render(bone_kind, mngr) { + log::error!( + "Error updating render for bone {bone_kind:?}: {:?}", + e + ); + } + } + for bone_kind in hidden_bones.iter() { + skeleton.set_visibility(*bone_kind, false); + if let Err(e) = skeleton.update_render(*bone_kind, mngr) { + log::error!( + "Error updating render for bone {bone_kind:?}: {:?}", + e + ); + } + } + } + }; + tokio::select! { + _ = subsys.on_shutdown_requested() => { + log::debug!("overlay shutdown requested"); + Ok::<_, eyre::Report>(()) + }, + r = loop_ => r, + }?; + + log::info!("Shutting down OpenVR context"); + unsafe { context.shutdown() }; + Ok(()) } async fn networking(subsys: SubsystemHandle) -> Result<()> { - let (client, recv) = Client::new(CONNECT_STR.to_string(), subsys.clone()) - .wrap_err("Failed to start client")?; - subsys.start("Overlay", |s| overlay(recv, s)); - client.join().await + let (client, recv) = Client::new(CONNECT_STR.to_string(), subsys.clone()) + .wrap_err("Failed to start client")?; + subsys.start("Overlay", |s| overlay(recv, s)); + client.join().await } diff --git a/overlay/app/src/main.rs b/overlay/app/src/main.rs index ce1052b7..46c04222 100644 --- a/overlay/app/src/main.rs +++ b/overlay/app/src/main.rs @@ -1,8 +1,8 @@ use eyre::Result; fn main() -> Result<()> { - pretty_env_logger::init(); - color_eyre::install()?; + pretty_env_logger::init(); + color_eyre::install()?; - slimevr_overlay::main() + slimevr_overlay::main() } diff --git a/overlay/app/src/model/bone.rs b/overlay/app/src/model/bone.rs index 8660bc4f..5bec3cad 100644 --- a/overlay/app/src/model/bone.rs +++ b/overlay/app/src/model/bone.rs @@ -10,176 +10,176 @@ pub type Isometry = nalgebra::Isometry3; #[derive(Debug)] pub struct Bone { - overlays: (OverlayHandle, OverlayHandle), - iso: Isometry, - color: RGBA, - radius: f32, - length: f32, - is_visible: bool, + overlays: (OverlayHandle, OverlayHandle), + iso: Isometry, + color: RGBA, + radius: f32, + length: f32, + is_visible: bool, } impl Bone { - pub fn new( - mngr: &mut OverlayManager, - color: RGBA, - isometry: Isometry, - key: String, - radius: f32, // meters - length: f32, // meters - ) -> Result { - let keys = (format!("{key}_0"), format!("{key}_1")); - - let mut init_overlay = |key: &str| -> Result { - let overlay = mngr - .create_overlay(key, key) - .wrap_err("Failed to create overlay")?; - mngr.set_curvature(overlay, 1.) - .wrap_err("Failed to set curvature")?; - mngr.set_raw_data(overlay, &[255u8; 4], 1, 1, 4) - .wrap_err("Failed to set raw data")?; - - Ok(overlay) - }; - - let overlays = (init_overlay(&keys.0)?, init_overlay(&keys.1)?); - - Ok(Self { - overlays, - iso: isometry, - radius, - length, - color, - is_visible: false, - }) - } - - pub fn update_render(&self, mngr: &mut OverlayManager<'_>) -> Result<()> { - // Set Color - { - fn f(color: u8) -> f32 { - color as f32 / 255. - } - let tint = ColorTint { - r: f(self.color.r), - g: f(self.color.g), - b: f(self.color.b), - a: f(self.color.a), - }; - mngr.set_tint(self.overlays.0, tint) - .and_then(|_| mngr.set_tint(self.overlays.1, tint)) - .wrap_err("Failed to set color")?; - } - - // Set width and height - { - let mut f = |overlay| -> Result<()> { - mngr.set_width(overlay, self.circumference()) - .wrap_err("Failed to set radius")?; - let aspect = self.circumference() / self.length; - mngr.set_texel_aspect(overlay, aspect) - .wrap_err("Failed to set texture aspect ratio")?; - - Ok(()) - }; - - f(self.overlays.0)?; - f(self.overlays.1)?; - } - - mngr.set_visibility(self.overlays.0, self.is_visible) - .and_then(|_| mngr.set_visibility(self.overlays.1, self.is_visible)) - .wrap_err("Failed to show overlay")?; - - // Set transform - { - // Adjusts the rotation of `iso` to have only roll and not pitch, to avoid - // distortion due to the curvature that SteamVR causes. Also adjusts the - // translation to place the overlay in the center of the tube instead of - // the edge - let mut f = |overlay, mut iso: Isometry, flip: f32| -> Result<()> { - // Offset for skeleton debugging - // iso.translation *= Translation3::new(0., 0., -1.); - - // The direction the overlay's y axis points after the transform. - let y_direction = iso.rotation.transform_vector(&Vector3::y_axis()); - let transform = if y_direction == Vector3::y_axis().into_inner() - || y_direction == -Vector3::y_axis().into_inner() - { - // just use the existing rotation, there won't be any distortion - iso.translation.vector += iso.rotation.transform_vector( - &Vector3::new(0., -self.length / 2.0, -self.radius), - ); - iso.to_homogeneous().remove_fixed_rows::<1>(3) - } else { - // We can freely rotate around `y_direction`, but to avoid - // distortion we want to have zero pitch, and only roll/yaw. A plane - // with zero pitch would be the plane between two vectors - one - // with the global y axis, and one with `y_direction`. Crossing - // those two vectors gives us the normal of the plane, called - // `z_direction`. - let z_direction = - flip * Vector3::::y_axis().cross(&y_direction).normalize(); - - // Now that we have the y direction and the z direction, we can - // form the rotation corresponding to this orientation. - iso.rotation = - UnitQuaternion::face_towards(&z_direction, &y_direction); - // let x_basis = y_basis.cross(&z_basis).normalize(); - - // Fixes the "center of tube" issue and the "center of overlay" - // issue - iso.translation.vector += z_direction * -self.radius; - iso.translation.vector -= y_direction * self.length / 2.0; - - iso.to_homogeneous().remove_fixed_rows::<1>(3) - }; - - let col_major_3x4 = Matrix3x4::from(&transform); - mngr.set_transform_absolute( - overlay, - TrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated, - &col_major_3x4, - ) - .wrap_err("Failed to set transform")?; - Ok(()) - }; - - let flipped = { - let mut rotation = UnitQuaternion::from_axis_angle( - &Vector3::y_axis(), - std::f32::consts::PI, - ); - rotation = self.iso.rotation * rotation; - Isometry3 { - rotation, - translation: self.iso.translation, - } - }; - - f(self.overlays.0, self.iso, 1.0)?; - f(self.overlays.1, flipped, -1.0)?; - } - - Ok(()) - } - - pub fn set_isometry(&mut self, isometry: Isometry) { - self.iso = isometry; - } - - pub fn set_length(&mut self, length: f32) { - assert!(length >= 0., "Length must be positive"); - self.length = length; - } - - pub fn set_radius(&mut self, radius: f32) { - self.radius = radius; - } - - pub fn circumference(&self) -> f32 { - 2. * std::f32::consts::PI * self.radius - } - - pub fn set_visibility(&mut self, is_visible: bool) { - self.is_visible = is_visible; - } + pub fn new( + mngr: &mut OverlayManager, + color: RGBA, + isometry: Isometry, + key: String, + radius: f32, // meters + length: f32, // meters + ) -> Result { + let keys = (format!("{key}_0"), format!("{key}_1")); + + let mut init_overlay = |key: &str| -> Result { + let overlay = mngr + .create_overlay(key, key) + .wrap_err("Failed to create overlay")?; + mngr.set_curvature(overlay, 1.) + .wrap_err("Failed to set curvature")?; + mngr.set_raw_data(overlay, &[255u8; 4], 1, 1, 4) + .wrap_err("Failed to set raw data")?; + + Ok(overlay) + }; + + let overlays = (init_overlay(&keys.0)?, init_overlay(&keys.1)?); + + Ok(Self { + overlays, + iso: isometry, + radius, + length, + color, + is_visible: false, + }) + } + + pub fn update_render(&self, mngr: &mut OverlayManager<'_>) -> Result<()> { + // Set Color + { + fn f(color: u8) -> f32 { + color as f32 / 255. + } + let tint = ColorTint { + r: f(self.color.r), + g: f(self.color.g), + b: f(self.color.b), + a: f(self.color.a), + }; + mngr.set_tint(self.overlays.0, tint) + .and_then(|_| mngr.set_tint(self.overlays.1, tint)) + .wrap_err("Failed to set color")?; + } + + // Set width and height + { + let mut f = |overlay| -> Result<()> { + mngr.set_width(overlay, self.circumference()) + .wrap_err("Failed to set radius")?; + let aspect = self.circumference() / self.length; + mngr.set_texel_aspect(overlay, aspect) + .wrap_err("Failed to set texture aspect ratio")?; + + Ok(()) + }; + + f(self.overlays.0)?; + f(self.overlays.1)?; + } + + mngr.set_visibility(self.overlays.0, self.is_visible) + .and_then(|_| mngr.set_visibility(self.overlays.1, self.is_visible)) + .wrap_err("Failed to show overlay")?; + + // Set transform + { + // Adjusts the rotation of `iso` to have only roll and not pitch, to avoid + // distortion due to the curvature that SteamVR causes. Also adjusts the + // translation to place the overlay in the center of the tube instead of + // the edge + let mut f = |overlay, mut iso: Isometry, flip: f32| -> Result<()> { + // Offset for skeleton debugging + // iso.translation *= Translation3::new(0., 0., -1.); + + // The direction the overlay's y axis points after the transform. + let y_direction = iso.rotation.transform_vector(&Vector3::y_axis()); + let transform = if y_direction == Vector3::y_axis().into_inner() + || y_direction == -Vector3::y_axis().into_inner() + { + // just use the existing rotation, there won't be any distortion + iso.translation.vector += iso.rotation.transform_vector( + &Vector3::new(0., -self.length / 2.0, -self.radius), + ); + iso.to_homogeneous().remove_fixed_rows::<1>(3) + } else { + // We can freely rotate around `y_direction`, but to avoid + // distortion we want to have zero pitch, and only roll/yaw. A plane + // with zero pitch would be the plane between two vectors - one + // with the global y axis, and one with `y_direction`. Crossing + // those two vectors gives us the normal of the plane, called + // `z_direction`. + let z_direction = + flip * Vector3::::y_axis().cross(&y_direction).normalize(); + + // Now that we have the y direction and the z direction, we can + // form the rotation corresponding to this orientation. + iso.rotation = + UnitQuaternion::face_towards(&z_direction, &y_direction); + // let x_basis = y_basis.cross(&z_basis).normalize(); + + // Fixes the "center of tube" issue and the "center of overlay" + // issue + iso.translation.vector += z_direction * -self.radius; + iso.translation.vector -= y_direction * self.length / 2.0; + + iso.to_homogeneous().remove_fixed_rows::<1>(3) + }; + + let col_major_3x4 = Matrix3x4::from(&transform); + mngr.set_transform_absolute( + overlay, + TrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated, + &col_major_3x4, + ) + .wrap_err("Failed to set transform")?; + Ok(()) + }; + + let flipped = { + let mut rotation = UnitQuaternion::from_axis_angle( + &Vector3::y_axis(), + std::f32::consts::PI, + ); + rotation = self.iso.rotation * rotation; + Isometry3 { + rotation, + translation: self.iso.translation, + } + }; + + f(self.overlays.0, self.iso, 1.0)?; + f(self.overlays.1, flipped, -1.0)?; + } + + Ok(()) + } + + pub fn set_isometry(&mut self, isometry: Isometry) { + self.iso = isometry; + } + + pub fn set_length(&mut self, length: f32) { + assert!(length >= 0., "Length must be positive"); + self.length = length; + } + + pub fn set_radius(&mut self, radius: f32) { + self.radius = radius; + } + + pub fn circumference(&self) -> f32 { + 2. * std::f32::consts::PI * self.radius + } + + pub fn set_visibility(&mut self, is_visible: bool) { + self.is_visible = is_visible; + } } diff --git a/overlay/app/src/model/bone_kind.rs b/overlay/app/src/model/bone_kind.rs index ded55249..4949b27b 100644 --- a/overlay/app/src/model/bone_kind.rs +++ b/overlay/app/src/model/bone_kind.rs @@ -6,151 +6,151 @@ use solarxr_protocol::datatypes::BodyPart; #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, FromPrimitive, ToPrimitive)] #[allow(dead_code)] pub enum BoneKind { - Head = 0, - Neck, - Chest, - Waist, - Hip, - ThighL, - ThighR, - AnkleL, - AnkleR, - FootL, - FootR, + Head = 0, + Neck, + Chest, + Waist, + Hip, + ThighL, + ThighR, + AnkleL, + AnkleR, + FootL, + FootR, - UpperArmL, - UpperArmR, - ForearmL, - ForearmR, - WristL, - WristR, + UpperArmL, + UpperArmR, + ForearmL, + ForearmR, + WristL, + WristR, } impl BoneKind { - /// The `BoneKind` with the largest integer value - pub const fn max() -> BoneKind { - BoneKind::WristR - } - pub const MAX: BoneKind = Self::max(); + /// The `BoneKind` with the largest integer value + pub const fn max() -> BoneKind { + BoneKind::WristR + } + pub const MAX: BoneKind = Self::max(); - /// The `BoneKind` with the smallest integer value - pub const fn min() -> BoneKind { - BoneKind::root() - } - pub const MIN: BoneKind = Self::min(); + /// The `BoneKind` with the smallest integer value + pub const fn min() -> BoneKind { + BoneKind::root() + } + pub const MIN: BoneKind = Self::min(); - /// The root bone type. - pub const fn root() -> Self { - Self::Head - } - pub const ROOT: BoneKind = Self::root(); + /// The root bone type. + pub const fn root() -> Self { + Self::Head + } + pub const ROOT: BoneKind = Self::root(); - /// Returns the number of different types of bones. - pub const fn num_types() -> usize { - BoneKind::max() as usize + 1 - } - pub const NUM_TYPES: usize = Self::num_types(); + /// Returns the number of different types of bones. + pub const fn num_types() -> usize { + BoneKind::max() as usize + 1 + } + pub const NUM_TYPES: usize = Self::num_types(); - pub const fn children(&self) -> &'static [Self] { - use BoneKind::*; - match self { - Head => &[Neck], - Neck => &[Chest, UpperArmL, UpperArmR], - Chest => &[Waist], - Waist => &[Hip], - Hip => &[ThighL, ThighR], - ThighL => &[AnkleL], - ThighR => &[AnkleR], - AnkleL => &[FootL], - AnkleR => &[FootR], - FootL => &[], - FootR => &[], + pub const fn children(&self) -> &'static [Self] { + use BoneKind::*; + match self { + Head => &[Neck], + Neck => &[Chest, UpperArmL, UpperArmR], + Chest => &[Waist], + Waist => &[Hip], + Hip => &[ThighL, ThighR], + ThighL => &[AnkleL], + ThighR => &[AnkleR], + AnkleL => &[FootL], + AnkleR => &[FootR], + FootL => &[], + FootR => &[], - UpperArmL => &[ForearmL], - UpperArmR => &[ForearmR], - ForearmL => &[WristL], - ForearmR => &[WristR], - WristR => &[], - WristL => &[], - } - } + UpperArmL => &[ForearmL], + UpperArmR => &[ForearmR], + ForearmL => &[WristL], + ForearmR => &[WristR], + WristR => &[], + WristL => &[], + } + } - pub const fn parent(&self) -> Option { - use BoneKind::*; - Some(match self { - Head => return None, - Neck => Head, - Chest => Neck, - Waist => Chest, - Hip => Waist, - ThighL => Hip, - ThighR => Hip, - AnkleL => ThighL, - AnkleR => ThighR, - FootL => AnkleL, - FootR => AnkleR, + pub const fn parent(&self) -> Option { + use BoneKind::*; + Some(match self { + Head => return None, + Neck => Head, + Chest => Neck, + Waist => Chest, + Hip => Waist, + ThighL => Hip, + ThighR => Hip, + AnkleL => ThighL, + AnkleR => ThighR, + FootL => AnkleL, + FootR => AnkleR, - UpperArmL => Neck, - UpperArmR => Neck, - ForearmL => UpperArmL, - ForearmR => UpperArmR, - WristL => ForearmL, - WristR => ForearmR, - }) - } + UpperArmL => Neck, + UpperArmR => Neck, + ForearmL => UpperArmL, + ForearmR => UpperArmR, + WristL => ForearmL, + WristR => ForearmR, + }) + } - pub fn iter() -> std::iter::Map, fn(u8) -> BoneKind> { - (Self::MIN as u8..=Self::MAX as u8).map(|x| x.try_into().unwrap()) - } + pub fn iter() -> std::iter::Map, fn(u8) -> BoneKind> { + (Self::MIN as u8..=Self::MAX as u8).map(|x| x.try_into().unwrap()) + } } impl TryFrom for BoneKind { - type Error = (); + type Error = (); - fn try_from(value: u8) -> Result { - FromPrimitive::from_u8(value).ok_or(()) - } + fn try_from(value: u8) -> Result { + FromPrimitive::from_u8(value).ok_or(()) + } } impl TryFrom for BoneKind { - type Error = (); - fn try_from(value: usize) -> Result { - FromPrimitive::from_usize(value).ok_or(()) - } + type Error = (); + fn try_from(value: usize) -> Result { + FromPrimitive::from_usize(value).ok_or(()) + } } impl From for u8 { - fn from(other: BoneKind) -> Self { - other as _ - } + fn from(other: BoneKind) -> Self { + other as _ + } } impl From for usize { - fn from(other: BoneKind) -> Self { - other as _ - } + fn from(other: BoneKind) -> Self { + other as _ + } } impl TryFrom for BoneKind { - type Error = BodyPart; - fn try_from(other: BodyPart) -> Result { - use BodyPart as O; - Ok(match other { - O::NONE | O::LEFT_CONTROLLER | O::RIGHT_CONTROLLER => return Err(other), + type Error = BodyPart; + fn try_from(other: BodyPart) -> Result { + use BodyPart as O; + Ok(match other { + O::NONE | O::LEFT_CONTROLLER | O::RIGHT_CONTROLLER => return Err(other), - O::NECK => Self::Neck, - O::CHEST => Self::Chest, - O::WAIST => Self::Waist, - O::HIP => Self::Hip, - O::LEFT_UPPER_LEG => Self::ThighL, - O::RIGHT_UPPER_LEG => Self::ThighR, - O::LEFT_LOWER_LEG => Self::AnkleL, - O::RIGHT_LOWER_LEG => Self::AnkleR, - O::LEFT_FOOT => Self::FootL, - O::RIGHT_FOOT => Self::FootR, + O::NECK => Self::Neck, + O::CHEST => Self::Chest, + O::WAIST => Self::Waist, + O::HIP => Self::Hip, + O::LEFT_UPPER_LEG => Self::ThighL, + O::RIGHT_UPPER_LEG => Self::ThighR, + O::LEFT_LOWER_LEG => Self::AnkleL, + O::RIGHT_LOWER_LEG => Self::AnkleR, + O::LEFT_FOOT => Self::FootL, + O::RIGHT_FOOT => Self::FootR, - O::LEFT_UPPER_ARM => Self::UpperArmL, - O::RIGHT_UPPER_ARM => Self::UpperArmR, - O::LEFT_LOWER_ARM => Self::ForearmL, - O::RIGHT_LOWER_ARM => Self::ForearmR, - O::LEFT_HAND => Self::WristL, - O::RIGHT_HAND => Self::WristR, + O::LEFT_UPPER_ARM => Self::UpperArmL, + O::RIGHT_UPPER_ARM => Self::UpperArmR, + O::LEFT_LOWER_ARM => Self::ForearmL, + O::RIGHT_LOWER_ARM => Self::ForearmR, + O::LEFT_HAND => Self::WristL, + O::RIGHT_HAND => Self::WristR, - O(_) => return Err(other), - }) - } + O(_) => return Err(other), + }) + } } diff --git a/overlay/app/src/model/bone_map.rs b/overlay/app/src/model/bone_map.rs index 95cab985..db69ae35 100644 --- a/overlay/app/src/model/bone_map.rs +++ b/overlay/app/src/model/bone_map.rs @@ -9,60 +9,60 @@ use std::iter::{Enumerate, Map}; #[derive(Debug, Default, Clone, Copy)] pub struct BoneMap([T; BoneKind::num_types()]); impl BoneMap { - pub fn new(map: [T; BoneKind::num_types()]) -> Self { - Self(map) - } + pub fn new(map: [T; BoneKind::num_types()]) -> Self { + Self(map) + } - pub fn iter(&self) -> Iter<'_, T> { - self.into_iter() - } + pub fn iter(&self) -> Iter<'_, T> { + self.into_iter() + } - pub fn iter_mut(&mut self) -> IterMut<'_, T> { - self.into_iter() - } + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.into_iter() + } } // ---- Type conversion stuff ---- impl TryFrom> for BoneMap { - type Error = IncompleteArrayError; + type Error = IncompleteArrayError; - fn try_from(other: HashMap) -> Result { - other.into_iter().try_collect() - } + fn try_from(other: HashMap) -> Result { + other.into_iter().try_collect() + } } impl TryFromIterator<(BoneKind, T)> for BoneMap { - type Error = IncompleteArrayError; - - fn try_from_iter(iter: I) -> Result - where - I: IntoIterator, - { - let mut bmap: BoneMap> = BoneMap::default(); - for (kind, item) in iter.into_iter().take(BoneKind::NUM_TYPES) { - bmap[kind] = Some(item); - } - if bmap.iter().any(|(_kind, item)| item.is_none()) { - return Err(IncompleteArrayError); - } - Ok(BoneMap::new(bmap.0.map(|item| item.unwrap()))) - } + type Error = IncompleteArrayError; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator, + { + let mut bmap: BoneMap> = BoneMap::default(); + for (kind, item) in iter.into_iter().take(BoneKind::NUM_TYPES) { + bmap[kind] = Some(item); + } + if bmap.iter().any(|(_kind, item)| item.is_none()) { + return Err(IncompleteArrayError); + } + Ok(BoneMap::new(bmap.0.map(|item| item.unwrap()))) + } } // ---- Index stuff ---- impl Index for BoneMap { - type Output = T; + type Output = T; - fn index(&self, index: BoneKind) -> &Self::Output { - // I *could* do get_unchecked, but meh, why introduce more unsafe. Maybe the - // compiler will optimize it. - &self.0[usize::from(index)] - } + fn index(&self, index: BoneKind) -> &Self::Output { + // I *could* do get_unchecked, but meh, why introduce more unsafe. Maybe the + // compiler will optimize it. + &self.0[usize::from(index)] + } } impl IndexMut for BoneMap { - fn index_mut(&mut self, index: BoneKind) -> &mut Self::Output { - &mut self.0[usize::from(index)] - } + fn index_mut(&mut self, index: BoneKind) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } } // ---- Iterator stuff ---- @@ -72,41 +72,41 @@ type MapIdxFnType = fn((usize, T)) -> (BoneKind, T); pub type Iter<'a, T> = Map>, MapIdxFnType<&'a T>>; pub type IterMut<'a, T> = - Map>, MapIdxFnType<&'a mut T>>; + Map>, MapIdxFnType<&'a mut T>>; pub type IntoIter = - Map>, MapIdxFnType>; + Map>, MapIdxFnType>; impl IntoIterator for BoneMap { - type Item = (BoneKind, T); + type Item = (BoneKind, T); - type IntoIter = IntoIter; + type IntoIter = IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter().enumerate().map(map_idx) + } } impl<'a, T> IntoIterator for &'a BoneMap { - type Item = (BoneKind, &'a T); + type Item = (BoneKind, &'a T); - type IntoIter = Iter<'a, T>; + type IntoIter = Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.iter().enumerate().map(map_idx) + } } impl<'a, T> IntoIterator for &'a mut BoneMap { - type Item = (BoneKind, &'a mut T); + type Item = (BoneKind, &'a mut T); - type IntoIter = IterMut<'a, T>; + type IntoIter = IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut().enumerate().map(map_idx) + } } fn map_idx(item: (usize, T)) -> (BoneKind, T) { - (BoneKind::try_from(item.0).unwrap(), item.1) + (BoneKind::try_from(item.0).unwrap(), item.1) } diff --git a/overlay/app/src/model/skeleton.rs b/overlay/app/src/model/skeleton.rs index a254ab44..952b0e9a 100644 --- a/overlay/app/src/model/skeleton.rs +++ b/overlay/app/src/model/skeleton.rs @@ -16,124 +16,124 @@ use super::bone::Isometry; pub type BoneArena = BoneMap; lazy_static! { - static ref DEFAULT_COLORS: BoneMap = { - use BoneKind::*; - HashMap::from([ - (Head, RGBA::BLACK), - (Neck, RGBA::SILVER), - (Chest, RGBA::OLIVE), - (Waist, RGBA::LIME), - (Hip, RGBA::MAROON), - (ThighL, RGBA::BLUE), - (ThighR, RGBA::BLUE), - (AnkleL, RGBA::TEAL), - (AnkleR, RGBA::TEAL), - (FootL, RGBA::PURPLE), - (FootR, RGBA::PURPLE), - (UpperArmL, RGBA::RED), - (UpperArmR, RGBA::RED), - (ForearmL, RGBA::PURPLE), - (ForearmR, RGBA::PURPLE), - (WristL, RGBA::FUCHSIA), - (WristR, RGBA::FUCHSIA), - ]) - .try_into() - .unwrap() - }; + static ref DEFAULT_COLORS: BoneMap = { + use BoneKind::*; + HashMap::from([ + (Head, RGBA::BLACK), + (Neck, RGBA::SILVER), + (Chest, RGBA::OLIVE), + (Waist, RGBA::LIME), + (Hip, RGBA::MAROON), + (ThighL, RGBA::BLUE), + (ThighR, RGBA::BLUE), + (AnkleL, RGBA::TEAL), + (AnkleR, RGBA::TEAL), + (FootL, RGBA::PURPLE), + (FootR, RGBA::PURPLE), + (UpperArmL, RGBA::RED), + (UpperArmR, RGBA::RED), + (ForearmL, RGBA::PURPLE), + (ForearmR, RGBA::PURPLE), + (WristL, RGBA::FUCHSIA), + (WristR, RGBA::FUCHSIA), + ]) + .try_into() + .unwrap() + }; } const BONE_RADIUS: f32 = 0.002; /// Builder for the [`Skeleton`]. pub struct SkeletonBuilder { - colors: Option>>, - key: String, - bone_radius: f32, - bone_lengths: Option>, + colors: Option>>, + key: String, + bone_radius: f32, + bone_lengths: Option>, } impl SkeletonBuilder { - #[allow(dead_code)] - pub fn build(self, overlay_manager: &mut OverlayManager) -> Result { - let colors = if let Some(colors) = self.colors { - colors - } else { - Default::default() - }; - let colors: BoneMap = colors - .into_iter() - .map(|(kind, maybe_color)| { - (kind, maybe_color.unwrap_or_else(|| DEFAULT_COLORS[kind])) - }) - .try_collect() - .unwrap(); + #[allow(dead_code)] + pub fn build(self, overlay_manager: &mut OverlayManager) -> Result { + let colors = if let Some(colors) = self.colors { + colors + } else { + Default::default() + }; + let colors: BoneMap = colors + .into_iter() + .map(|(kind, maybe_color)| { + (kind, maybe_color.unwrap_or_else(|| DEFAULT_COLORS[kind])) + }) + .try_collect() + .unwrap(); - let bone_lengths = self - .bone_lengths - .unwrap_or_else(|| BoneMap::new([0.1; BoneKind::NUM_TYPES])); + let bone_lengths = self + .bone_lengths + .unwrap_or_else(|| BoneMap::new([0.1; BoneKind::NUM_TYPES])); - let mut bones = Vec::new(); - for (kind, color) in colors { - let bone = Bone::new( - overlay_manager, - color, - Default::default(), - format!("{}: {kind:?}", self.key), - self.bone_radius, - bone_lengths[kind], - )?; - bones.push((kind, bone)); - } - let bones: BoneArena = bones.into_iter().try_collect().unwrap(); - Ok(Skeleton::new(bones)) - } + let mut bones = Vec::new(); + for (kind, color) in colors { + let bone = Bone::new( + overlay_manager, + color, + Default::default(), + format!("{}: {kind:?}", self.key), + self.bone_radius, + bone_lengths[kind], + )?; + bones.push((kind, bone)); + } + let bones: BoneArena = bones.into_iter().try_collect().unwrap(); + Ok(Skeleton::new(bones)) + } } impl Default for SkeletonBuilder { - fn default() -> Self { - Self { - colors: None, - key: String::from("slimevr"), - bone_radius: BONE_RADIUS, - bone_lengths: None, - } - } + fn default() -> Self { + Self { + colors: None, + key: String::from("slimevr"), + bone_radius: BONE_RADIUS, + bone_lengths: None, + } + } } pub struct Skeleton { - pub bones: BoneArena, + pub bones: BoneArena, } #[allow(dead_code)] impl Skeleton { - pub fn new(bones: BoneArena) -> Self { - let mut result = Self { bones }; - // We explicitly set all bones to invisible, to reduce code brittleness. - for b in BoneKind::iter() { - result.set_visibility(b, false); - } - result - } + pub fn new(bones: BoneArena) -> Self { + let mut result = Self { bones }; + // We explicitly set all bones to invisible, to reduce code brittleness. + for b in BoneKind::iter() { + result.set_visibility(b, false); + } + result + } - pub fn set_isometry(&mut self, bone: BoneKind, iso: Isometry) { - let bone = &mut self.bones[bone]; - bone.set_isometry(iso); - } + pub fn set_isometry(&mut self, bone: BoneKind, iso: Isometry) { + let bone = &mut self.bones[bone]; + bone.set_isometry(iso); + } - pub fn set_length(&mut self, bone: BoneKind, len: f32) { - let bone = &mut self.bones[bone]; - bone.set_length(len); - } + pub fn set_length(&mut self, bone: BoneKind, len: f32) { + let bone = &mut self.bones[bone]; + bone.set_length(len); + } - pub fn update_render( - &mut self, - bone: BoneKind, - mngr: &mut OverlayManager, - ) -> eyre::Result<()> { - let bone = &mut self.bones[bone]; - bone.update_render(mngr) - .wrap_err("could not update render for bone") - } + pub fn update_render( + &mut self, + bone: BoneKind, + mngr: &mut OverlayManager, + ) -> eyre::Result<()> { + let bone = &mut self.bones[bone]; + bone.update_render(mngr) + .wrap_err("could not update render for bone") + } - pub fn set_visibility(&mut self, bone: BoneKind, is_visible: bool) { - let bone = &mut self.bones[bone]; - bone.set_visibility(is_visible); - } + pub fn set_visibility(&mut self, bone: BoneKind, is_visible: bool) { + let bone = &mut self.bones[bone]; + bone.set_visibility(is_visible); + } } diff --git a/overlay/tokio_shutdown/src/lib.rs b/overlay/tokio_shutdown/src/lib.rs index 2f84a62d..7ef50ef2 100644 --- a/overlay/tokio_shutdown/src/lib.rs +++ b/overlay/tokio_shutdown/src/lib.rs @@ -14,121 +14,121 @@ use tokio::sync::{broadcast, mpsc}; /// - `A`: An acknowledgement of the shutdown, possibly containing any useful /// information that can be sent back to the `ShutdownBroadcaster`. pub struct Broadcaster { - broadcaster: broadcast::Sender, - shutdown_watcher: mpsc::UnboundedReceiver, - // we hang on to this to be able to create new listeners. - mpsc_copy: mpsc::UnboundedSender, + broadcaster: broadcast::Sender, + shutdown_watcher: mpsc::UnboundedReceiver, + // we hang on to this to be able to create new listeners. + mpsc_copy: mpsc::UnboundedSender, } impl Broadcaster { - pub fn new() -> Self { - let (b_sender, _) = broadcast::channel(1); - let (mpsc_sender, mpsc_receiver) = mpsc::unbounded_channel(); + pub fn new() -> Self { + let (b_sender, _) = broadcast::channel(1); + let (mpsc_sender, mpsc_receiver) = mpsc::unbounded_channel(); - Self { - broadcaster: b_sender, - shutdown_watcher: mpsc_receiver, - mpsc_copy: mpsc_sender, - } - } + Self { + broadcaster: b_sender, + shutdown_watcher: mpsc_receiver, + mpsc_copy: mpsc_sender, + } + } - /// Creates a new `Listener` to the `Broadcaster`. - pub fn new_listener(&self) -> Listener { - let b_receiver = self.broadcaster.subscribe(); - let mpsc_sender = self.mpsc_copy.clone(); + /// Creates a new `Listener` to the `Broadcaster`. + pub fn new_listener(&self) -> Listener { + let b_receiver = self.broadcaster.subscribe(); + let mpsc_sender = self.mpsc_copy.clone(); - Listener { - b_receiver, - mpsc_sender, - shutdown_reason: None, - } - } + Listener { + b_receiver, + mpsc_sender, + shutdown_reason: None, + } + } - pub fn num_listeners(&self) -> usize { - self.broadcaster.receiver_count() - } + pub fn num_listeners(&self) -> usize { + self.broadcaster.receiver_count() + } - /// Signals shutdown, with an optional reason. If no reason is provided, the - /// listeners will get [`ShutdownReason::BroadcasterClosed`]. - /// - /// # Returns - /// Returns a channel to be used for receiving the shutdown acknowledgements. - pub fn signal_shutdown(self, reason: Option) -> mpsc::UnboundedReceiver { - if let Some(r) = reason { - // We don't care if all recipients are closed - self.broadcaster.send(r).ok(); - } - self.shutdown_watcher - } + /// Signals shutdown, with an optional reason. If no reason is provided, the + /// listeners will get [`ShutdownReason::BroadcasterClosed`]. + /// + /// # Returns + /// Returns a channel to be used for receiving the shutdown acknowledgements. + pub fn signal_shutdown(self, reason: Option) -> mpsc::UnboundedReceiver { + if let Some(r) = reason { + // We don't care if all recipients are closed + self.broadcaster.send(r).ok(); + } + self.shutdown_watcher + } } impl Default for Broadcaster { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } /// # Generics /// See [`Broadcaster`] for documentation on the generic args. pub struct Listener { - b_receiver: broadcast::Receiver, - mpsc_sender: mpsc::UnboundedSender, - shutdown_reason: Option>, + b_receiver: broadcast::Receiver, + mpsc_sender: mpsc::UnboundedSender, + shutdown_reason: Option>, } impl Listener { - /// Doesn't return until a shutdown occurs. - pub async fn recv(&mut self) -> &ShutdownReason { - if let Some(ref r) = self.shutdown_reason { - return r; - } - let reason = match self.b_receiver.recv().await { - Ok(r) => ShutdownReason::Reason(r), - Err(broadcast::error::RecvError::Closed) => { - ShutdownReason::BroadcasterClosed - } - Err(_) => unreachable!( - "we shouldn't be able to lag, only 1 shutdown is ever sent." - ), - }; - self.shutdown_reason = Some(reason); - self.shutdown_reason.as_ref().unwrap() - } + /// Doesn't return until a shutdown occurs. + pub async fn recv(&mut self) -> &ShutdownReason { + if let Some(ref r) = self.shutdown_reason { + return r; + } + let reason = match self.b_receiver.recv().await { + Ok(r) => ShutdownReason::Reason(r), + Err(broadcast::error::RecvError::Closed) => { + ShutdownReason::BroadcasterClosed + } + Err(_) => unreachable!( + "we shouldn't be able to lag, only 1 shutdown is ever sent." + ), + }; + self.shutdown_reason = Some(reason); + self.shutdown_reason.as_ref().unwrap() + } - /// Returns `None` if no shutdown has occurred, otherwise returns the shutdown - /// reason. - pub fn try_recv(&mut self) -> Option<&ShutdownReason> { - let reason = match self.b_receiver.try_recv() { - Ok(r) => ShutdownReason::Reason(r), - Err(broadcast::error::TryRecvError::Closed) => { - ShutdownReason::BroadcasterClosed - } - Err(broadcast::error::TryRecvError::Empty) => return None, - Err(broadcast::error::TryRecvError::Lagged(_)) => { - unreachable!( - "we shouldn't be able to lag, only 1 shutdown is ever sent." - ) - } - }; - self.shutdown_reason = Some(reason); - self.shutdown_reason.as_ref() - } + /// Returns `None` if no shutdown has occurred, otherwise returns the shutdown + /// reason. + pub fn try_recv(&mut self) -> Option<&ShutdownReason> { + let reason = match self.b_receiver.try_recv() { + Ok(r) => ShutdownReason::Reason(r), + Err(broadcast::error::TryRecvError::Closed) => { + ShutdownReason::BroadcasterClosed + } + Err(broadcast::error::TryRecvError::Empty) => return None, + Err(broadcast::error::TryRecvError::Lagged(_)) => { + unreachable!( + "we shouldn't be able to lag, only 1 shutdown is ever sent." + ) + } + }; + self.shutdown_reason = Some(reason); + self.shutdown_reason.as_ref() + } - /// Get the underlying shutdown reason, if present. Does not send any shut - pub fn into_reason(self) -> Option> { - self.shutdown_reason - } + /// Get the underlying shutdown reason, if present. Does not send any shut + pub fn into_reason(self) -> Option> { + self.shutdown_reason + } - /// Block until an acknowledgement of the shutdown is sent. It is possible - /// for the `Broadcaster` to have already closed. - /// - /// # Returns - /// Returns the original shutdown reason, if any. - pub fn acknowledge(self, info: I) -> Option> { - self.mpsc_sender.send(info).ok(); - self.shutdown_reason - } + /// Block until an acknowledgement of the shutdown is sent. It is possible + /// for the `Broadcaster` to have already closed. + /// + /// # Returns + /// Returns the original shutdown reason, if any. + pub fn acknowledge(self, info: I) -> Option> { + self.mpsc_sender.send(info).ok(); + self.shutdown_reason + } } #[derive(Clone)] pub enum ShutdownReason { - BroadcasterClosed, - Reason(R), + BroadcasterClosed, + Reason(R), } diff --git a/rustfmt.toml b/rustfmt.toml index 1ee26f6f..58f1a9d0 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,3 @@ # Adopt python's `black` col limit for easier side-by-side and cli editing max_width = 88 +hard_tabs = true diff --git a/skeletal_model/src/bone/bone_kind.rs b/skeletal_model/src/bone/bone_kind.rs index 45e93ae7..1080cf64 100644 --- a/skeletal_model/src/bone/bone_kind.rs +++ b/skeletal_model/src/bone/bone_kind.rs @@ -33,144 +33,144 @@ use num_traits::FromPrimitive; #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, FromPrimitive, ToPrimitive)] #[allow(dead_code)] pub enum BoneKind { - Neck = 0, - Chest, - Waist, - Hip, - ThighL, - ThighR, - AnkleL, - AnkleR, - FootL, - FootR, + Neck = 0, + Chest, + Waist, + Hip, + ThighL, + ThighR, + AnkleL, + AnkleR, + FootL, + FootR, - UpperArmL, - UpperArmR, - ForearmL, - ForearmR, - WristL, - WristR, + UpperArmL, + UpperArmR, + ForearmL, + ForearmR, + WristL, + WristR, } impl BoneKind { - /// The bone with the largest integer value - pub const fn max() -> BoneKind { - BoneKind::WristR - } - pub const MAX: BoneKind = Self::max(); + /// The bone with the largest integer value + pub const fn max() -> BoneKind { + BoneKind::WristR + } + pub const MAX: BoneKind = Self::max(); - /// The bone with the smallest integer value - pub const fn min() -> BoneKind { - BoneKind::root() - } - pub const MIN: BoneKind = Self::min(); + /// The bone with the smallest integer value + pub const fn min() -> BoneKind { + BoneKind::root() + } + pub const MIN: BoneKind = Self::min(); - /// The root bone of the skeletal graph/tree. - pub const fn root() -> Self { - Self::Neck - } - pub const ROOT: BoneKind = Self::root(); + /// The root bone of the skeletal graph/tree. + pub const fn root() -> Self { + Self::Neck + } + pub const ROOT: BoneKind = Self::root(); - /// Returns the number of unique kinds of bones. This is equivalent to the number - /// of variants in `BoneKind` - pub const fn num_types() -> usize { - BoneKind::max() as usize + 1 - } - pub const NUM_TYPES: usize = Self::num_types(); + /// Returns the number of unique kinds of bones. This is equivalent to the number + /// of variants in `BoneKind` + pub const fn num_types() -> usize { + BoneKind::max() as usize + 1 + } + pub const NUM_TYPES: usize = Self::num_types(); - /// Returns the children of any particular bone. - /// - /// The slice is `'static`, which means the lifetime of the returned slice lives - /// for the entire duration of the program. This is because the parent/child - /// relationship of bones is known at compile-time. - pub const fn children(&self) -> &'static [Self] { - use BoneKind::*; - match self { - Neck => &[Chest, UpperArmL, UpperArmR], - Chest => &[Waist], - Waist => &[Hip], - Hip => &[ThighL, ThighR], - ThighL => &[AnkleL], - ThighR => &[AnkleR], - AnkleL => &[FootL], - AnkleR => &[FootR], - FootL => &[], - FootR => &[], + /// Returns the children of any particular bone. + /// + /// The slice is `'static`, which means the lifetime of the returned slice lives + /// for the entire duration of the program. This is because the parent/child + /// relationship of bones is known at compile-time. + pub const fn children(&self) -> &'static [Self] { + use BoneKind::*; + match self { + Neck => &[Chest, UpperArmL, UpperArmR], + Chest => &[Waist], + Waist => &[Hip], + Hip => &[ThighL, ThighR], + ThighL => &[AnkleL], + ThighR => &[AnkleR], + AnkleL => &[FootL], + AnkleR => &[FootR], + FootL => &[], + FootR => &[], - UpperArmL => &[ForearmL], - UpperArmR => &[ForearmR], - ForearmL => &[WristL], - ForearmR => &[WristR], - WristR => &[], - WristL => &[], - } - } + UpperArmL => &[ForearmL], + UpperArmR => &[ForearmR], + ForearmL => &[WristL], + ForearmR => &[WristR], + WristR => &[], + WristL => &[], + } + } - /// The parent of a bone. - pub const fn parent(&self) -> Option { - use BoneKind::*; - Some(match self { - Neck => return None, - Chest => Neck, - Waist => Chest, - Hip => Waist, - ThighL => Hip, - ThighR => Hip, - AnkleL => ThighL, - AnkleR => ThighR, - FootL => AnkleL, - FootR => AnkleR, + /// The parent of a bone. + pub const fn parent(&self) -> Option { + use BoneKind::*; + Some(match self { + Neck => return None, + Chest => Neck, + Waist => Chest, + Hip => Waist, + ThighL => Hip, + ThighR => Hip, + AnkleL => ThighL, + AnkleR => ThighR, + FootL => AnkleL, + FootR => AnkleR, - UpperArmL => Neck, - UpperArmR => Neck, - ForearmL => UpperArmL, - ForearmR => UpperArmR, - WristL => ForearmL, - WristR => ForearmR, - }) - } + UpperArmL => Neck, + UpperArmR => Neck, + ForearmL => UpperArmL, + ForearmR => UpperArmR, + WristL => ForearmL, + WristR => ForearmR, + }) + } - pub fn iter() -> std::iter::Map, fn(u8) -> BoneKind> { - (Self::MIN as u8..=Self::MAX as u8).map(|x| x.try_into().unwrap()) - } + pub fn iter() -> std::iter::Map, fn(u8) -> BoneKind> { + (Self::MIN as u8..=Self::MAX as u8).map(|x| x.try_into().unwrap()) + } - /// Returns the initial calibration pose of the bone. Rotating the up vector by - /// this rotation would cause it to point in the same target direction as the bone. - pub fn calibration_rotation(self) -> Global { - use BoneKind::*; - Global(match self { - FootL | FootR => UnitQuat::look_at_rh(&-up_vec(), &forward_vec()), - _ => UnitQuat::default(), - }) - } + /// Returns the initial calibration pose of the bone. Rotating the up vector by + /// this rotation would cause it to point in the same target direction as the bone. + pub fn calibration_rotation(self) -> Global { + use BoneKind::*; + Global(match self { + FootL | FootR => UnitQuat::look_at_rh(&-up_vec(), &forward_vec()), + _ => UnitQuat::default(), + }) + } - /// Returns the initial calibration pose of the bone, as a rotation relative to the - /// parent bone. See also: [`Self::calibration_rotation`] - pub fn calibration_rotation_local(self) -> Local { - let child_rot_g = self.calibration_rotation(); - let parent_rot_g = self.parent().unwrap_or(self).calibration_rotation(); - Local(parent_rot_g.0.rotation_to(&child_rot_g.0)) - } + /// Returns the initial calibration pose of the bone, as a rotation relative to the + /// parent bone. See also: [`Self::calibration_rotation`] + pub fn calibration_rotation_local(self) -> Local { + let child_rot_g = self.calibration_rotation(); + let parent_rot_g = self.parent().unwrap_or(self).calibration_rotation(); + Local(parent_rot_g.0.rotation_to(&child_rot_g.0)) + } } impl TryFrom for BoneKind { - type Error = (); + type Error = (); - fn try_from(value: u8) -> Result { - FromPrimitive::from_u8(value).ok_or(()) - } + fn try_from(value: u8) -> Result { + FromPrimitive::from_u8(value).ok_or(()) + } } impl TryFrom for BoneKind { - type Error = (); - fn try_from(value: usize) -> Result { - FromPrimitive::from_usize(value).ok_or(()) - } + type Error = (); + fn try_from(value: usize) -> Result { + FromPrimitive::from_usize(value).ok_or(()) + } } impl From for u8 { - fn from(other: BoneKind) -> Self { - other as _ - } + fn from(other: BoneKind) -> Self { + other as _ + } } impl From for usize { - fn from(other: BoneKind) -> Self { - other as _ - } + fn from(other: BoneKind) -> Self { + other as _ + } } diff --git a/skeletal_model/src/bone/bone_map.rs b/skeletal_model/src/bone/bone_map.rs index 20690ead..1be098d1 100644 --- a/skeletal_model/src/bone/bone_map.rs +++ b/skeletal_model/src/bone/bone_map.rs @@ -26,74 +26,74 @@ use std::iter::{Enumerate, Map}; #[derive(Debug, Default, Clone, Copy, From, Eq, PartialEq)] pub struct BoneMap([T; BoneKind::num_types()]); impl BoneMap { - pub fn new(map: [T; BoneKind::num_types()]) -> Self { - Self(map) - } - - /// Gets an iterator over the (key, value) pairs of the `BoneMap`. - /// - /// Iteration is guaranteed to start at [`BoneKind::root()`] but beyond that, - /// iteration order is not guaranteed. However, iteration *is* exhaustive over - /// the various kinds of bones. - pub fn iter(&self) -> Iter<'_, T> { - self.into_iter() - } - - /// Gets a mutable iterator over the `(key, value)` pairs of the `BoneMap`. - /// - /// See also: [`Self::iter()`] - pub fn iter_mut(&mut self) -> IterMut<'_, T> { - self.into_iter() - } - - /// Applies a function to each element of the `BoneMap`, mapping it from `T` to `U`. - pub fn map(self, mut f: impl FnMut(BoneKind, T) -> U) -> BoneMap { - let it = self.into_iter().map(|(kind, item)| (kind, f(kind, item))); - it.try_collect().unwrap() - } + pub fn new(map: [T; BoneKind::num_types()]) -> Self { + Self(map) + } + + /// Gets an iterator over the (key, value) pairs of the `BoneMap`. + /// + /// Iteration is guaranteed to start at [`BoneKind::root()`] but beyond that, + /// iteration order is not guaranteed. However, iteration *is* exhaustive over + /// the various kinds of bones. + pub fn iter(&self) -> Iter<'_, T> { + self.into_iter() + } + + /// Gets a mutable iterator over the `(key, value)` pairs of the `BoneMap`. + /// + /// See also: [`Self::iter()`] + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.into_iter() + } + + /// Applies a function to each element of the `BoneMap`, mapping it from `T` to `U`. + pub fn map(self, mut f: impl FnMut(BoneKind, T) -> U) -> BoneMap { + let it = self.into_iter().map(|(kind, item)| (kind, f(kind, item))); + it.try_collect().unwrap() + } } // ---- Type conversion stuff ---- impl TryFrom> for BoneMap { - type Error = IncompleteArrayError; + type Error = IncompleteArrayError; - fn try_from(other: HashMap) -> Result { - other.into_iter().try_collect() - } + fn try_from(other: HashMap) -> Result { + other.into_iter().try_collect() + } } impl TryFromIterator<(BoneKind, T)> for BoneMap { - type Error = IncompleteArrayError; - - fn try_from_iter(iter: I) -> Result - where - I: IntoIterator, - { - let mut bmap: BoneMap> = BoneMap::default(); - for (kind, item) in iter.into_iter().take(BoneKind::NUM_TYPES) { - bmap[kind] = Some(item); - } - if bmap.iter().any(|(_kind, item)| item.is_none()) { - return Err(IncompleteArrayError); - } - Ok(BoneMap::new(bmap.0.map(|item| item.unwrap()))) - } + type Error = IncompleteArrayError; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator, + { + let mut bmap: BoneMap> = BoneMap::default(); + for (kind, item) in iter.into_iter().take(BoneKind::NUM_TYPES) { + bmap[kind] = Some(item); + } + if bmap.iter().any(|(_kind, item)| item.is_none()) { + return Err(IncompleteArrayError); + } + Ok(BoneMap::new(bmap.0.map(|item| item.unwrap()))) + } } // ---- Index stuff ---- impl Index for BoneMap { - type Output = T; + type Output = T; - fn index(&self, index: BoneKind) -> &Self::Output { - // I *could* do get_unchecked, but meh, why introduce more unsafe. Maybe the - // compiler will optimize it. - &self.0[usize::from(index)] - } + fn index(&self, index: BoneKind) -> &Self::Output { + // I *could* do get_unchecked, but meh, why introduce more unsafe. Maybe the + // compiler will optimize it. + &self.0[usize::from(index)] + } } impl IndexMut for BoneMap { - fn index_mut(&mut self, index: BoneKind) -> &mut Self::Output { - &mut self.0[usize::from(index)] - } + fn index_mut(&mut self, index: BoneKind) -> &mut Self::Output { + &mut self.0[usize::from(index)] + } } // ---- Iterator stuff ---- @@ -103,69 +103,69 @@ type MapIdxFnType = fn((usize, T)) -> (BoneKind, T); pub type Iter<'a, T> = Map>, MapIdxFnType<&'a T>>; pub type IterMut<'a, T> = - Map>, MapIdxFnType<&'a mut T>>; + Map>, MapIdxFnType<&'a mut T>>; pub type IntoIter = - Map>, MapIdxFnType>; + Map>, MapIdxFnType>; impl IntoIterator for BoneMap { - type Item = (BoneKind, T); + type Item = (BoneKind, T); - type IntoIter = IntoIter; + type IntoIter = IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter().enumerate().map(map_idx) + } } impl<'a, T> IntoIterator for &'a BoneMap { - type Item = (BoneKind, &'a T); + type Item = (BoneKind, &'a T); - type IntoIter = Iter<'a, T>; + type IntoIter = Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.iter().enumerate().map(map_idx) + } } impl<'a, T> IntoIterator for &'a mut BoneMap { - type Item = (BoneKind, &'a mut T); + type Item = (BoneKind, &'a mut T); - type IntoIter = IterMut<'a, T>; + type IntoIter = IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut().enumerate().map(map_idx) - } + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut().enumerate().map(map_idx) + } } fn map_idx(item: (usize, T)) -> (BoneKind, T) { - (BoneKind::try_from(item.0).unwrap(), item.1) + (BoneKind::try_from(item.0).unwrap(), item.1) } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_default() { - let zeros = BoneMap::new([0u8; BoneKind::NUM_TYPES]); - let ones = BoneMap::new([1u8; BoneKind::NUM_TYPES]); - assert_eq!(zeros, BoneMap::default()); - assert_ne!(ones, BoneMap::default()); - - #[derive(Copy, Clone, Eq, PartialEq, Debug)] - struct Foo; - let nones: BoneMap> = BoneMap::new([None; BoneKind::NUM_TYPES]); - assert_eq!(nones, BoneMap::default()) - } - - #[test] - fn test_map() { - let zeros = BoneMap::new([0u8; BoneKind::NUM_TYPES]); - - let as_u8 = zeros.map(|kind, _| kind as u8); - for (kind, _) in zeros.iter() { - assert_eq!(kind as u8, as_u8[kind]); - } - } + use super::*; + + #[test] + fn test_default() { + let zeros = BoneMap::new([0u8; BoneKind::NUM_TYPES]); + let ones = BoneMap::new([1u8; BoneKind::NUM_TYPES]); + assert_eq!(zeros, BoneMap::default()); + assert_ne!(ones, BoneMap::default()); + + #[derive(Copy, Clone, Eq, PartialEq, Debug)] + struct Foo; + let nones: BoneMap> = BoneMap::new([None; BoneKind::NUM_TYPES]); + assert_eq!(nones, BoneMap::default()) + } + + #[test] + fn test_map() { + let zeros = BoneMap::new([0u8; BoneKind::NUM_TYPES]); + + let as_u8 = zeros.map(|kind, _| kind as u8); + for (kind, _) in zeros.iter() { + assert_eq!(kind as u8, as_u8[kind]); + } + } } diff --git a/skeletal_model/src/conventions.rs b/skeletal_model/src/conventions.rs index 57d766fc..3550526f 100644 --- a/skeletal_model/src/conventions.rs +++ b/skeletal_model/src/conventions.rs @@ -22,19 +22,19 @@ use num_traits::Zero; /// A vector in the "up" or `+Y` direction #[inline] pub fn up_vec() -> Unit> { - Vector3::y_axis() + Vector3::y_axis() } /// A vector in the "forward" or `-Z` direction #[inline] pub fn forward_vec() -> Unit> { - -Vector3::z_axis() + -Vector3::z_axis() } /// A vector in the "right" or `+X` direction #[inline] pub fn right_vec() -> Unit> { - Vector3::x_axis() + Vector3::x_axis() } /// Creates a [`UnitQuat`] that corresponds to the local frame of an observer standing @@ -64,130 +64,130 @@ pub fn right_vec() -> Unit> { /// ``` #[inline] pub fn look_towards(dir: &Vector3, up: &Vector3) -> UnitQuat { - debug_assert!( - approx::relative_ne!(dir.cross(up), Vector3::zero()), - "`dir` and `up` were collinear!", - ); - UnitQuat::face_towards(&-dir, up) + debug_assert!( + approx::relative_ne!(dir.cross(up), Vector3::zero()), + "`dir` and `up` were collinear!", + ); + UnitQuat::face_towards(&-dir, up) } #[cfg(test)] mod tests { - use crate::prelude::*; + use crate::prelude::*; - use approx::assert_relative_eq; - use nalgebra::Vector3; - use std::f32::consts::FRAC_PI_2; + use approx::assert_relative_eq; + use nalgebra::Vector3; + use std::f32::consts::FRAC_PI_2; - use super::look_towards; + use super::look_towards; - /// Example and sanity check of how to use various functions from `nalgebra` to - /// describe rotations. - #[test] - fn check_rotation_builders() { - /// Contains a group of rotations generated by various functions. Each should be - /// identical. - struct Rotations { - /// The description of the rotation - desc: &'static str, - /// The result of [`look_towards()`] - look_towards: UnitQuat, - /// The result of [`UnitQuat::look_at_rh()`] - look_at: UnitQuat, - /// The result of [`UnitQuat::face_towards()`] - face_towards: UnitQuat, - /// The result of [`UnitQuat::from_axis_angle`] - axis_angle: UnitQuat, - /// The result of [`UnitQuat::from_euler_angles`] - euler: UnitQuat, - } + /// Example and sanity check of how to use various functions from `nalgebra` to + /// describe rotations. + #[test] + fn check_rotation_builders() { + /// Contains a group of rotations generated by various functions. Each should be + /// identical. + struct Rotations { + /// The description of the rotation + desc: &'static str, + /// The result of [`look_towards()`] + look_towards: UnitQuat, + /// The result of [`UnitQuat::look_at_rh()`] + look_at: UnitQuat, + /// The result of [`UnitQuat::face_towards()`] + face_towards: UnitQuat, + /// The result of [`UnitQuat::from_axis_angle`] + axis_angle: UnitQuat, + /// The result of [`UnitQuat::from_euler_angles`] + euler: UnitQuat, + } - // Build the set of rotations to check - let rotations = vec![ - Rotations { - desc: "Pitch up 90 degrees", - look_towards: look_towards(&up_vec(), &-forward_vec()), - look_at: UnitQuat::look_at_rh(&-Vector3::y_axis(), &-Vector3::z_axis()), - face_towards: UnitQuat::face_towards( - &-Vector3::y_axis(), - &Vector3::z_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::x_axis(), FRAC_PI_2), - euler: UnitQuat::from_euler_angles(FRAC_PI_2, 0., 0.), - }, - Rotations { - desc: "Pitch down 90 degrees", - look_towards: look_towards(&-up_vec(), &forward_vec()), - look_at: UnitQuat::look_at_rh(&Vector3::y_axis(), &Vector3::z_axis()), - face_towards: UnitQuat::face_towards( - &Vector3::y_axis(), - &-Vector3::z_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::x_axis(), -FRAC_PI_2), - euler: UnitQuat::from_euler_angles(-FRAC_PI_2, 0., 0.), - }, - Rotations { - desc: "Yaw right 90 degrees", - look_towards: look_towards(&right_vec(), &up_vec()), - look_at: UnitQuat::look_at_rh(&-Vector3::x_axis(), &Vector3::y_axis()), - face_towards: UnitQuat::face_towards( - &-Vector3::x_axis(), - &Vector3::y_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::y_axis(), -FRAC_PI_2), - euler: UnitQuat::from_euler_angles(0., -FRAC_PI_2, 0.), - }, - Rotations { - desc: "Yaw left 90 degrees", - look_towards: look_towards(&-right_vec(), &up_vec()), - look_at: UnitQuat::look_at_rh(&Vector3::x_axis(), &Vector3::y_axis()), - face_towards: UnitQuat::face_towards( - &Vector3::x_axis(), - &Vector3::y_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::y_axis(), FRAC_PI_2), - euler: UnitQuat::from_euler_angles(0., FRAC_PI_2, 0.), - }, - Rotations { - desc: "Roll clockwise 90 degrees", - look_towards: look_towards(&forward_vec(), &right_vec()), - look_at: UnitQuat::look_at_rh(&-Vector3::z_axis(), &-Vector3::x_axis()), - face_towards: UnitQuat::face_towards( - &Vector3::z_axis(), - &Vector3::x_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::z_axis(), -FRAC_PI_2), - euler: UnitQuat::from_euler_angles(0., 0., -FRAC_PI_2), - }, - Rotations { - desc: "Roll counter-clockwise 90 degrees", - look_towards: look_towards(&forward_vec(), &-right_vec()), - look_at: UnitQuat::look_at_rh(&-Vector3::z_axis(), &Vector3::x_axis()), - face_towards: UnitQuat::face_towards( - &Vector3::z_axis(), - &-Vector3::x_axis(), - ), - axis_angle: UnitQuat::from_axis_angle(&Vector3::z_axis(), FRAC_PI_2), - euler: UnitQuat::from_euler_angles(0., 0., FRAC_PI_2), - }, - ]; + // Build the set of rotations to check + let rotations = vec![ + Rotations { + desc: "Pitch up 90 degrees", + look_towards: look_towards(&up_vec(), &-forward_vec()), + look_at: UnitQuat::look_at_rh(&-Vector3::y_axis(), &-Vector3::z_axis()), + face_towards: UnitQuat::face_towards( + &-Vector3::y_axis(), + &Vector3::z_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::x_axis(), FRAC_PI_2), + euler: UnitQuat::from_euler_angles(FRAC_PI_2, 0., 0.), + }, + Rotations { + desc: "Pitch down 90 degrees", + look_towards: look_towards(&-up_vec(), &forward_vec()), + look_at: UnitQuat::look_at_rh(&Vector3::y_axis(), &Vector3::z_axis()), + face_towards: UnitQuat::face_towards( + &Vector3::y_axis(), + &-Vector3::z_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::x_axis(), -FRAC_PI_2), + euler: UnitQuat::from_euler_angles(-FRAC_PI_2, 0., 0.), + }, + Rotations { + desc: "Yaw right 90 degrees", + look_towards: look_towards(&right_vec(), &up_vec()), + look_at: UnitQuat::look_at_rh(&-Vector3::x_axis(), &Vector3::y_axis()), + face_towards: UnitQuat::face_towards( + &-Vector3::x_axis(), + &Vector3::y_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::y_axis(), -FRAC_PI_2), + euler: UnitQuat::from_euler_angles(0., -FRAC_PI_2, 0.), + }, + Rotations { + desc: "Yaw left 90 degrees", + look_towards: look_towards(&-right_vec(), &up_vec()), + look_at: UnitQuat::look_at_rh(&Vector3::x_axis(), &Vector3::y_axis()), + face_towards: UnitQuat::face_towards( + &Vector3::x_axis(), + &Vector3::y_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::y_axis(), FRAC_PI_2), + euler: UnitQuat::from_euler_angles(0., FRAC_PI_2, 0.), + }, + Rotations { + desc: "Roll clockwise 90 degrees", + look_towards: look_towards(&forward_vec(), &right_vec()), + look_at: UnitQuat::look_at_rh(&-Vector3::z_axis(), &-Vector3::x_axis()), + face_towards: UnitQuat::face_towards( + &Vector3::z_axis(), + &Vector3::x_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::z_axis(), -FRAC_PI_2), + euler: UnitQuat::from_euler_angles(0., 0., -FRAC_PI_2), + }, + Rotations { + desc: "Roll counter-clockwise 90 degrees", + look_towards: look_towards(&forward_vec(), &-right_vec()), + look_at: UnitQuat::look_at_rh(&-Vector3::z_axis(), &Vector3::x_axis()), + face_towards: UnitQuat::face_towards( + &Vector3::z_axis(), + &-Vector3::x_axis(), + ), + axis_angle: UnitQuat::from_axis_angle(&Vector3::z_axis(), FRAC_PI_2), + euler: UnitQuat::from_euler_angles(0., 0., FRAC_PI_2), + }, + ]; - for r in rotations { - // Check that all 3 coordinate axes are rotated to same direction - println!("Checking rotation: {}", r.desc); - for axis in [Vector3::x_axis(), Vector3::y_axis(), Vector3::z_axis()] { - println!("Testing axis: {axis:?}"); - assert_relative_eq!(r.axis_angle * axis, r.look_at * axis); - assert_relative_eq!(r.axis_angle * axis, r.euler * axis); - assert_relative_eq!(r.axis_angle * axis, r.face_towards * axis); - assert_relative_eq!(r.axis_angle * axis, r.look_towards * axis); - } + for r in rotations { + // Check that all 3 coordinate axes are rotated to same direction + println!("Checking rotation: {}", r.desc); + for axis in [Vector3::x_axis(), Vector3::y_axis(), Vector3::z_axis()] { + println!("Testing axis: {axis:?}"); + assert_relative_eq!(r.axis_angle * axis, r.look_at * axis); + assert_relative_eq!(r.axis_angle * axis, r.euler * axis); + assert_relative_eq!(r.axis_angle * axis, r.face_towards * axis); + assert_relative_eq!(r.axis_angle * axis, r.look_towards * axis); + } - // Check that the angles between rotations are zero - assert_relative_eq!(r.axis_angle.angle_to(&r.look_at), 0.0); - assert_relative_eq!(r.axis_angle.angle_to(&r.euler), 0.0); - assert_relative_eq!(r.axis_angle.angle_to(&r.face_towards), 0.0); - assert_relative_eq!(r.axis_angle.angle_to(&r.look_towards), 0.0); - } - } + // Check that the angles between rotations are zero + assert_relative_eq!(r.axis_angle.angle_to(&r.look_at), 0.0); + assert_relative_eq!(r.axis_angle.angle_to(&r.euler), 0.0); + assert_relative_eq!(r.axis_angle.angle_to(&r.face_towards), 0.0); + assert_relative_eq!(r.axis_angle.angle_to(&r.look_towards), 0.0); + } + } } diff --git a/skeletal_model/src/lib.rs b/skeletal_model/src/lib.rs index a76bd581..8aa99d66 100644 --- a/skeletal_model/src/lib.rs +++ b/skeletal_model/src/lib.rs @@ -61,11 +61,11 @@ // These set linter options #![deny( - invalid_doc_attributes, - rustdoc::broken_intra_doc_links, - rustdoc::private_intra_doc_links, - unused_import_braces, - unused + invalid_doc_attributes, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + unused_import_braces, + unused )] pub mod bone; diff --git a/skeletal_model/src/newtypes.rs b/skeletal_model/src/newtypes.rs index 7cb8fd0b..11e21b08 100644 --- a/skeletal_model/src/newtypes.rs +++ b/skeletal_model/src/newtypes.rs @@ -8,13 +8,13 @@ pub struct Global(pub T); /// Implements `From for $ident` macro_rules! impl_helper { - ($ident:ident) => { - impl From for $ident { - fn from(other: T) -> Self { - Self(other) - } - } - }; + ($ident:ident) => { + impl From for $ident { + fn from(other: T) -> Self { + Self(other) + } + } + }; } impl_helper!(Global); impl_helper!(Local); @@ -24,14 +24,14 @@ impl_helper!(Local); pub struct Local(pub T); mod private { - use super::*; + use super::*; - /// Private helper trait to limit the types that can go in [`Global`] or [`Local`]. - /// - /// For more info about this pattern, see - /// [here](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed) - pub trait Sealed {} - impl Sealed for Translation {} - impl Sealed for UnitQuat {} - impl Sealed for Point {} + /// Private helper trait to limit the types that can go in [`Global`] or [`Local`]. + /// + /// For more info about this pattern, see + /// [here](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed) + pub trait Sealed {} + impl Sealed for Translation {} + impl Sealed for UnitQuat {} + impl Sealed for Point {} } diff --git a/skeletal_model/src/skeleton/edge.rs b/skeletal_model/src/skeleton/edge.rs index c2ac8984..2ca3f97f 100644 --- a/skeletal_model/src/skeleton/edge.rs +++ b/skeletal_model/src/skeleton/edge.rs @@ -5,14 +5,14 @@ use derive_more::From; /// The different kinds of edges. #[derive(Debug, Eq, PartialEq, Hash, From, Copy, Clone)] pub enum EdgeKind { - /// Represents a regular bone in the skeleton. - Bone(BoneKind), - /// Represents a tracker that is providing pose information as an input to the - /// skeleton. - InputTracker, - /// Represents a computed/synthetic tracker that will act as an output tracker for - /// the skeleton. - OutputTracker, + /// Represents a regular bone in the skeleton. + Bone(BoneKind), + /// Represents a tracker that is providing pose information as an input to the + /// skeleton. + InputTracker, + /// Represents a computed/synthetic tracker that will act as an output tracker for + /// the skeleton. + OutputTracker, } /// `Edge`s represent the connections between the [`Node`]s of the @@ -31,54 +31,54 @@ pub enum EdgeKind { /// /// [`Node`]: crate::skeleton::Node pub struct Edge { - kind: EdgeKind, - /// Input rotation in global space. If it is unconstrained, it is `None`. - input_rot_g: Option>, - /// Local rotation of the edge with respect to the parent edge at calibration time. - /// Maps from parent frame to child frame. - calib_rot_l: Local, - /// Length of the edge. May be set by the user, or may be computed at calibration. - length: f32, - /// The output rotation of the edge. Solving the skeleton updates this. - output_rot_g: Global, + kind: EdgeKind, + /// Input rotation in global space. If it is unconstrained, it is `None`. + input_rot_g: Option>, + /// Local rotation of the edge with respect to the parent edge at calibration time. + /// Maps from parent frame to child frame. + calib_rot_l: Local, + /// Length of the edge. May be set by the user, or may be computed at calibration. + length: f32, + /// The output rotation of the edge. Solving the skeleton updates this. + output_rot_g: Global, } impl Edge { - pub fn new(kind: impl Into, length: f32) -> Self { - let kind = kind.into(); - let calib_rot_l = match kind { - EdgeKind::Bone(k) => k.calibration_rotation_local(), - _ => UnitQuat::identity().into(), - }; - Self { - kind, - input_rot_g: None, - calib_rot_l, - length, - output_rot_g: Default::default(), - } - } + pub fn new(kind: impl Into, length: f32) -> Self { + let kind = kind.into(); + let calib_rot_l = match kind { + EdgeKind::Bone(k) => k.calibration_rotation_local(), + _ => UnitQuat::identity().into(), + }; + Self { + kind, + input_rot_g: None, + calib_rot_l, + length, + output_rot_g: Default::default(), + } + } - pub fn input_rotation_mut(&mut self) -> Option<&mut Global> { - self.input_rot_g.as_mut() - } + pub fn input_rotation_mut(&mut self) -> Option<&mut Global> { + self.input_rot_g.as_mut() + } - pub fn output_rotation(&self) -> &Global { - &self.output_rot_g - } + pub fn output_rotation(&self) -> &Global { + &self.output_rot_g + } - pub fn length(&self) -> f32 { - self.length - } + pub fn length(&self) -> f32 { + self.length + } - pub fn length_mut(&mut self) -> &mut f32 { - &mut self.length - } + pub fn length_mut(&mut self) -> &mut f32 { + &mut self.length + } - pub fn calibration_rotation(&self) -> &Local { - &self.calib_rot_l - } + pub fn calibration_rotation(&self) -> &Local { + &self.calib_rot_l + } - pub fn kind(&self) -> EdgeKind { - self.kind - } + pub fn kind(&self) -> EdgeKind { + self.kind + } } diff --git a/skeletal_model/src/skeleton/mod.rs b/skeletal_model/src/skeleton/mod.rs index 96a5bc21..4dd01741 100644 --- a/skeletal_model/src/skeleton/mod.rs +++ b/skeletal_model/src/skeleton/mod.rs @@ -110,12 +110,12 @@ use daggy::{Dag, EdgeIndex}; /// Used to initialize the [`Skeleton`] with its initial parameters pub struct SkeletonConfig { - bone_lengths: BoneMap, + bone_lengths: BoneMap, } impl SkeletonConfig { - pub fn new(bone_lengths: BoneMap) -> Self { - SkeletonConfig { bone_lengths } - } + pub fn new(bone_lengths: BoneMap) -> Self { + SkeletonConfig { bone_lengths } + } } /// The `Skeleton` provides a way of reading, writing, and solving for the pose of @@ -123,88 +123,88 @@ impl SkeletonConfig { /// /// See the [`crate::skeleton`] module for more information. pub struct Skeleton { - bone_map: BoneMap, - graph: Dag, + bone_map: BoneMap, + graph: Dag, } impl Skeleton { - /// Creates a new `Skeleton` from [`SkeletonConfig`]. Initially, the skeleton will - /// not have any input trackers or output trackers. - pub fn new(config: &SkeletonConfig) -> Self { - let mut g = Dag::new(); - - // Option is used for resiliance against bugs while the map is being built - let mut bone_map: BoneMap> = BoneMap::default(); - - // Create root skeletal bone: edge (bone) connects to nodes (joints) - { - let head = g.add_node(Node::new()); - let (edge, _tail) = g.add_child( - head, - Edge::new(BoneKind::Neck, config.bone_lengths[BoneKind::Neck]), - Node::new(), - ); - bone_map[BoneKind::Neck] = Some(edge); - } - - // This closure adds all the immediate children of `parent_bone` to the graph - let mut add_child_bones = |parent_bone: BoneKind| { - let parent_edge = - bone_map[parent_bone].expect("Bone was not yet added to graph"); - let head = g.edge_endpoints(parent_edge).unwrap().1; // Get child node of edge - for child_kind in parent_bone.children() { - // No need to work with a ref, `child_kind` is `Copy` - let child_kind = *child_kind; - - let (edge, _tail) = g.add_child( - head, - Edge::new(child_kind, config.bone_lengths[child_kind]), - Node::new(), - ); - - bone_map[child_kind] = Some(edge); - } - }; - - // Call `add_child_bones` in a depth-first traversal to build the actual graph. - let mut bone_stack = vec![BoneKind::Neck]; - while !bone_stack.is_empty() { - let parent_bone = bone_stack.pop().unwrap(); - add_child_bones(parent_bone); - bone_stack.extend(parent_bone.children()); - } - - // Map is populated, get rid of the `Optional` - let bone_map: BoneMap = bone_map.map(|_kind, bone| bone.unwrap()); - - Self { graph: g, bone_map } - } + /// Creates a new `Skeleton` from [`SkeletonConfig`]. Initially, the skeleton will + /// not have any input trackers or output trackers. + pub fn new(config: &SkeletonConfig) -> Self { + let mut g = Dag::new(); + + // Option is used for resiliance against bugs while the map is being built + let mut bone_map: BoneMap> = BoneMap::default(); + + // Create root skeletal bone: edge (bone) connects to nodes (joints) + { + let head = g.add_node(Node::new()); + let (edge, _tail) = g.add_child( + head, + Edge::new(BoneKind::Neck, config.bone_lengths[BoneKind::Neck]), + Node::new(), + ); + bone_map[BoneKind::Neck] = Some(edge); + } + + // This closure adds all the immediate children of `parent_bone` to the graph + let mut add_child_bones = |parent_bone: BoneKind| { + let parent_edge = + bone_map[parent_bone].expect("Bone was not yet added to graph"); + let head = g.edge_endpoints(parent_edge).unwrap().1; // Get child node of edge + for child_kind in parent_bone.children() { + // No need to work with a ref, `child_kind` is `Copy` + let child_kind = *child_kind; + + let (edge, _tail) = g.add_child( + head, + Edge::new(child_kind, config.bone_lengths[child_kind]), + Node::new(), + ); + + bone_map[child_kind] = Some(edge); + } + }; + + // Call `add_child_bones` in a depth-first traversal to build the actual graph. + let mut bone_stack = vec![BoneKind::Neck]; + while !bone_stack.is_empty() { + let parent_bone = bone_stack.pop().unwrap(); + add_child_bones(parent_bone); + bone_stack.extend(parent_bone.children()); + } + + // Map is populated, get rid of the `Optional` + let bone_map: BoneMap = bone_map.map(|_kind, bone| bone.unwrap()); + + Self { graph: g, bone_map } + } } impl Index for Skeleton { - type Output = Edge; + type Output = Edge; - fn index(&self, index: BoneKind) -> &Self::Output { - let edge = self.bone_map[index]; - &self.graph[edge] - } + fn index(&self, index: BoneKind) -> &Self::Output { + let edge = self.bone_map[index]; + &self.graph[edge] + } } #[cfg(test)] mod test { - use super::*; + use super::*; - /// Tests that all lengths of the skeleton are properly initialized based on `SkeletonConfig` - #[test] - fn test_lengths() { - let mut bone_lengths = BoneMap::new([0.; BoneKind::num_types()]); + /// Tests that all lengths of the skeleton are properly initialized based on `SkeletonConfig` + #[test] + fn test_lengths() { + let mut bone_lengths = BoneMap::new([0.; BoneKind::num_types()]); - bone_lengths[BoneKind::FootL] = 4.0; + bone_lengths[BoneKind::FootL] = 4.0; - let config = SkeletonConfig::new(bone_lengths); + let config = SkeletonConfig::new(bone_lengths); - let skeleton = Skeleton::new(&config); + let skeleton = Skeleton::new(&config); - for (bone, length) in bone_lengths.iter() { - assert_eq!(&skeleton[bone].length(), length); - } - } + for (bone, length) in bone_lengths.iter() { + assert_eq!(&skeleton[bone].length(), length); + } + } } diff --git a/skeletal_model/src/skeleton/node.rs b/skeletal_model/src/skeleton/node.rs index 8baebcbe..b8ed6683 100644 --- a/skeletal_model/src/skeleton/node.rs +++ b/skeletal_model/src/skeleton/node.rs @@ -9,33 +9,33 @@ use crate::prelude::*; #[derive(Debug, Default)] #[allow(dead_code)] pub struct Node { - /// Input position in global space. If it is unconstrained, it is `None`. - input_pos_g: Option>, - /// The output position of the `Node`. Solving the skeleton updates this. - output_pos_g: Global, + /// Input position in global space. If it is unconstrained, it is `None`. + input_pos_g: Option>, + /// The output position of the `Node`. Solving the skeleton updates this. + output_pos_g: Global, } #[allow(dead_code)] impl Node { - pub fn new() -> Self { - Self { - input_pos_g: None, - output_pos_g: Default::default(), - } - } + pub fn new() -> Self { + Self { + input_pos_g: None, + output_pos_g: Default::default(), + } + } - pub fn input_position(&self) -> Option<&Global> { - self.input_pos_g.as_ref() - } + pub fn input_position(&self) -> Option<&Global> { + self.input_pos_g.as_ref() + } - pub fn input_position_mut(&mut self) -> Option<&mut Global> { - self.input_pos_g.as_mut() - } + pub fn input_position_mut(&mut self) -> Option<&mut Global> { + self.input_pos_g.as_mut() + } - pub fn output_position(&self) -> &Global { - &self.output_pos_g - } + pub fn output_position(&self) -> &Global { + &self.output_pos_g + } - pub fn output_position_mut(&mut self) -> &mut Global { - &mut self.output_pos_g - } + pub fn output_position_mut(&mut self) -> &mut Global { + &mut self.output_pos_g + } }