diff --git a/Cargo.lock b/Cargo.lock index cb6652ccf9..5fb20dc629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "approx" @@ -262,18 +262,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "audio" -version = "0.1.0" -dependencies = [ - "color-eyre", - "context_attribute", - "filtering", - "framework", - "rustfft", - "types", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -450,15 +438,6 @@ dependencies = [ "serde", ] -[[package]] -name = "build_script_helpers" -version = "0.1.0" -dependencies = [ - "color-eyre", - "proc-macro2", - "source_analyzer", -] - [[package]] name = "bumpalo" version = "3.12.0" @@ -726,12 +705,12 @@ dependencies = [ name = "code_generation" version = "0.1.0" dependencies = [ - "color-eyre", "convert_case", "itertools", "proc-macro2", "quote", "source_analyzer", + "thiserror", ] [[package]] @@ -897,28 +876,6 @@ dependencies = [ "syn", ] -[[package]] -name = "control" -version = "0.1.0" -dependencies = [ - "approx", - "color-eyre", - "context_attribute", - "filtering", - "framework", - "itertools", - "kinematics", - "log", - "nalgebra", - "ordered-float", - "serde", - "serde_json", - "serialize_hierarchy", - "smallvec", - "spl_network_messages", - "types", -] - [[package]] name = "convert_case" version = "0.6.0" @@ -1141,30 +1098,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cyclers" -version = "0.1.0" -dependencies = [ - "audio", - "build_script_helpers", - "code_generation", - "color-eyre", - "communication", - "control", - "framework", - "ittapi", - "quote", - "serde", - "serialize_hierarchy", - "source_analyzer", - "spl_network", - "structs", - "tokio", - "tokio-util", - "types", - "vision", -] - [[package]] name = "darling" version = "0.13.4" @@ -1743,14 +1676,12 @@ dependencies = [ name = "framework" version = "0.1.0" dependencies = [ - "build_script_helpers", "color-eyre", "convert_case", "parking_lot", "proc-macro2", "quote", "source_analyzer", - "structs", "types", ] @@ -2116,24 +2047,42 @@ name = "hulk" version = "0.1.0" dependencies = [ "alsa", + "approx", "chrono", + "code_generation", "color-eyre", + "communication", + "compiled-nn", "constants", + "context_attribute", + "convert_case", "ctrlc", - "cyclers", "fern", + "filtering", + "framework", "i2cdev", + "itertools", + "ittapi", + "kinematics", "log", "nalgebra", "nao_camera", + "ordered-float", "parking_lot", + "proc-macro2", + "quote", "rand", + "rustfft", "serde", "serde_json", - "spl_network", - "structs", + "serialize_hierarchy", + "smallvec", + "source_analyzer", + "spl_network_messages", + "thiserror", "tokio", "tokio-util", + "toml", "types", "v4l", "webots", @@ -3033,6 +2982,7 @@ dependencies = [ "spl_network_messages", "thiserror", "tokio", + "toml", ] [[package]] @@ -3742,12 +3692,16 @@ dependencies = [ name = "source_analyzer" version = "0.1.0" dependencies = [ - "color-eyre", "convert_case", "glob", + "itertools", "proc-macro2", "quote", + "serde", "syn", + "thiserror", + "threadbound", + "toml", "topological-sort", ] @@ -3760,20 +3714,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spl_network" -version = "0.1.0" -dependencies = [ - "color-eyre", - "context_attribute", - "framework", - "log", - "serde", - "thiserror", - "tokio", - "types", -] - [[package]] name = "spl_network_messages" version = "0.1.0" @@ -3847,25 +3787,6 @@ dependencies = [ "syn", ] -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "build_script_helpers", - "color-eyre", - "convert_case", - "filtering", - "nalgebra", - "proc-macro2", - "quote", - "serde", - "serde_json", - "serialize_hierarchy", - "source_analyzer", - "spl_network_messages", - "types", -] - [[package]] name = "syn" version = "1.0.107" @@ -3959,6 +3880,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadbound" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b61d8ab119a378ae1221abf5a3d77fd9686ebaeda630f52cc00e2a50cfb061a" + [[package]] name = "threadpool" version = "1.8.1" @@ -4351,23 +4278,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vision" -version = "0.1.0" -dependencies = [ - "approx", - "color-eyre", - "compiled-nn", - "context_attribute", - "filtering", - "framework", - "itertools", - "nalgebra", - "ordered-float", - "rand", - "types", -] - [[package]] name = "waker-fn" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 45d43f7ea2..d1ed310702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,10 @@ [workspace] members = [ "crates/aliveness", - "crates/audio", - "crates/build_script_helpers", "crates/code_generation", "crates/communication", "crates/constants", "crates/context_attribute", - "crates/control", - "crates/cyclers", "crates/filtering", "crates/framework", "crates/hulk", @@ -19,11 +15,8 @@ members = [ "crates/serialize_hierarchy", "crates/serialize_hierarchy_derive", "crates/source_analyzer", - "crates/spl_network", "crates/spl_network_messages", - "crates/structs", "crates/types", - "crates/vision", "tools/camera_matrix_extractor", "tools/depp", "tools/fanta", @@ -40,13 +33,11 @@ exclude = [ alsa = "0.7.0" aliveness = { path = "crates/aliveness" } approx = "0.5.1" -audio = { path = "crates/audio" } awaitgroup = "0.6.0" base64 = "0.13.0" bat = { version = "0.21.0", default-features = false, features = ["regex-onig", "paging"] } bincode = "1.3.3" bindgen = "0.61.0" -build_script_helpers = { path = "crates/build_script_helpers" } byteorder = "1.4.3" chrono = "0.4.23" clap = { version = "4.0.22", features = ["derive"] } @@ -58,10 +49,8 @@ communication = { path = "crates/communication" } compiled-nn = "0.12.0" constants = { path = "crates/constants" } context_attribute = { path = "crates/context_attribute" } -control = { path = "crates/control" } convert_case = "0.6.0" ctrlc = { version = "3.2.3", features = ["termination"] } -cyclers = { path = "crates/cyclers" } eframe = { version = "0.19.0", features = ["persistence"] } egui_dock = { version = "0.2.1", git = "https://github.com/knoellle/egui_dock/", features = [ "serde", @@ -108,13 +97,12 @@ serialize_hierarchy = { path = "crates/serialize_hierarchy" } serialize_hierarchy_derive = { path = "crates/serialize_hierarchy_derive" } smallvec = "1.9.0" source_analyzer = { path = "crates/source_analyzer" } -spl_network = { path = "crates/spl_network" } spl_network_messages = { path = "crates/spl_network_messages" } structopt = "0.3.26" -structs = { path = "crates/structs" } syn = { version = "1.0.101", features = ["full", "extra-traits"] } tempfile = "3.3.0" thiserror = "1.0.37" +threadbound = "0.1.6" tokio = { version = "1.21.2", features = ["full"] } tokio-tungstenite = "0.17.2" tokio-util = "0.7.4" @@ -125,7 +113,6 @@ uuid = { version = "1.1.2", features = ["v4"] } v4l = { version = "0.12.1", git = "https://github.com/HULKs/libv4l-rs", branch = "hulksChanges" } walkdir = "2.3.2" webots = { version = "0.6.0" } -vision = { path = "crates/vision" } zbus = { version = "3.7.0", features = ["tokio"] } [profile.incremental] diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml deleted file mode 100644 index 464125ef97..0000000000 --- a/crates/audio/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "audio" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -color-eyre = { workspace = true } -context_attribute = { workspace = true } -filtering = { workspace = true } -framework = { workspace = true } -rustfft = { workspace = true } -types = { workspace = true } diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs deleted file mode 100644 index e0a2af0050..0000000000 --- a/crates/audio/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod microphone_recorder; -pub mod whistle_detection; - -#[derive(Clone, Copy, Debug)] -pub enum CyclerInstance { - Audio, -} diff --git a/crates/build_script_helpers/Cargo.toml b/crates/build_script_helpers/Cargo.toml deleted file mode 100644 index 11f2906220..0000000000 --- a/crates/build_script_helpers/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "build_script_helpers" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -color-eyre = { workspace = true } -proc-macro2 = { workspace = true } -source_analyzer = { workspace = true } diff --git a/crates/build_script_helpers/src/lib.rs b/crates/build_script_helpers/src/lib.rs deleted file mode 100644 index ace29309c6..0000000000 --- a/crates/build_script_helpers/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{env::var, fs::File, io::Write, path::PathBuf, process::Command}; - -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; -use proc_macro2::TokenStream; - -pub fn write_token_stream(file_name: &str, token_stream: TokenStream) -> Result<()> { - let file_path = - PathBuf::from(var("OUT_DIR").wrap_err("failed to get environment variable OUT_DIR")?) - .join(file_name); - - { - let mut file = File::create(&file_path) - .wrap_err_with(|| format!("failed create file {file_path:?}"))?; - write!(file, "{token_stream}") - .wrap_err_with(|| format!("failed to write to file {file_path:?}"))?; - } - - let status = Command::new("rustfmt") - .arg(file_path) - .status() - .wrap_err("failed to execute rustfmt")?; - if !status.success() { - bail!("rustfmt did not exit with success"); - } - - Ok(()) -} diff --git a/crates/code_generation/Cargo.toml b/crates/code_generation/Cargo.toml index 4296875a2c..7692b3349a 100644 --- a/crates/code_generation/Cargo.toml +++ b/crates/code_generation/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0-only" homepage = "https://github.com/hulks/hulk" [dependencies] -color-eyre = { workspace = true } +thiserror = { workspace = true } convert_case = { workspace = true } itertools = { workspace = true } proc-macro2 = { workspace = true } diff --git a/crates/code_generation/src/accessor.rs b/crates/code_generation/src/accessor.rs index 88eefccb02..647feca8ed 100644 --- a/crates/code_generation/src/accessor.rs +++ b/crates/code_generation/src/accessor.rs @@ -1,177 +1,86 @@ use convert_case::{Case, Casing}; -use proc_macro2::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree}; -use quote::{format_ident, TokenStreamExt}; -use source_analyzer::PathSegment; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use source_analyzer::{ + cycler::{Cycler, InstanceName}, + path::Path, +}; -use super::reference_type::ReferenceType; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ReferenceKind { + Immutable, + Mutable, +} pub fn path_to_accessor_token_stream( - prefix_token_stream: TokenStream, - path: &[PathSegment], - reference_type: ReferenceType, - instance: TokenStream, - cycler_instance_prefix: TokenStream, - cycler_instances: &[String], + prefix: TokenStream, + path: &Path, + reference_type: ReferenceKind, + cycler: &Cycler, ) -> TokenStream { - for segment in path.iter() { - if segment.is_variable && segment.name != "cycler_instance" { - unimplemented!("only $cycler_instance is implemented"); - } + if let Some(segment) = path + .segments + .iter() + .find(|segment| segment.is_variable && segment.name != "cycler_instance") + { + let name = &segment.name; + unimplemented!("unexpected `${name}` only $cycler_instance can be used as variable in path",) } - let path_contains_variable = path.iter().any(|segment| segment.is_variable); - if path_contains_variable { - let mut token_stream = TokenStream::default(); - token_stream.append(TokenTree::Ident(format_ident!("match"))); - token_stream.extend(instance); - let mut token_stream_within_match = TokenStream::default(); - for cycler_instance in cycler_instances { - token_stream_within_match.extend(cycler_instance_prefix.clone()); - token_stream_within_match.append(format_ident!("{}", cycler_instance)); - token_stream_within_match.append(TokenTree::Punct(Punct::new('=', Spacing::Joint))); - token_stream_within_match.append(TokenTree::Punct(Punct::new('>', Spacing::Alone))); - token_stream_within_match.extend(path_to_accessor_token_stream_with_cycler_instance( - prefix_token_stream.clone(), + if path.contains_variable() { + let variants = cycler.instances.iter().map(|instance| { + let instance_name = format_ident!("{}", instance.name); + let accessor_path = path_to_accessor_token_stream_with_cycler_instance( + prefix.clone(), path, reference_type, - Some(cycler_instance), - )); - token_stream_within_match.append(TokenTree::Punct(Punct::new(',', Spacing::Alone))); + Some(&instance.name), + ); + quote! { + CyclerInstance::#instance_name => #accessor_path, + } + }); + quote! { + match instance { + #(#variants)* + } } - token_stream.append(TokenTree::Group(Group::new( - Delimiter::Brace, - token_stream_within_match, - ))); - token_stream } else { - path_to_accessor_token_stream_with_cycler_instance( - prefix_token_stream, - path, - reference_type, - None, - ) + path_to_accessor_token_stream_with_cycler_instance(prefix, path, reference_type, None) } } fn path_to_accessor_token_stream_with_cycler_instance( - prefix_token_stream: TokenStream, - path: &[PathSegment], - reference_type: ReferenceType, - cycler_instance: Option<&str>, + prefix: TokenStream, + path: &Path, + reference_type: ReferenceKind, + cycler_instance: Option<&InstanceName>, ) -> TokenStream { - let mut token_stream = TokenStream::default(); - let mut token_stream_within_method = None; - - let path_contains_optional = path.iter().any(|segment| segment.is_optional); - if !path_contains_optional { - token_stream.append(TokenTree::Punct(Punct::new('&', Spacing::Alone))); - if ReferenceType::Mutable == reference_type { - token_stream.append(TokenTree::Ident(format_ident!("mut"))); + let segments = path.segments.iter().map(|segment| { + let field = match segment.is_variable { + true => format_ident!("{}", cycler_instance.unwrap().to_case(Case::Snake)), + false => format_ident!("{}", segment.name), + }; + match segment.is_optional { + true => match reference_type { + ReferenceKind::Immutable => quote! { #field.as_ref()? }, + ReferenceKind::Mutable => quote! { #field.as_mut()? }, + }, + false => quote! { #field }, } - } - - token_stream.extend(prefix_token_stream); - - for (index, segment) in path.iter().enumerate() { - { - let token_stream = token_stream_within_method - .as_mut() - .unwrap_or(&mut token_stream); - - token_stream.append(TokenTree::Punct(Punct::new('.', Spacing::Alone))); - match (segment.is_variable, cycler_instance) { - (true, Some(cycler_instance)) => { - token_stream.append(TokenTree::Ident(format_ident!( - "{}", - cycler_instance.to_case(Case::Snake) - ))); - } - _ => { - token_stream.append(TokenTree::Ident(format_ident!("{}", segment.name))); - } - } + }); + if path.contains_optional() { + quote! { + (|| Some(#prefix . #(#segments).*))() } - - let is_last_segment = index == path.len() - 1; - if segment.is_optional { - match token_stream_within_method.take() { - Some(mut token_stream_within_method) => { - token_stream_within_method - .append(TokenTree::Punct(Punct::new('.', Spacing::Alone))); - match reference_type { - ReferenceType::Immutable => token_stream_within_method - .append(TokenTree::Ident(format_ident!("as_ref"))), - ReferenceType::Mutable => token_stream_within_method - .append(TokenTree::Ident(format_ident!("as_mut"))), - } - token_stream_within_method.append(TokenTree::Group(Group::new( - Delimiter::Parenthesis, - TokenStream::default(), - ))); - - token_stream.append(TokenTree::Group(Group::new( - Delimiter::Parenthesis, - token_stream_within_method, - ))); - } - None => { - token_stream.append(TokenTree::Punct(Punct::new('.', Spacing::Alone))); - match reference_type { - ReferenceType::Immutable => { - token_stream.append(TokenTree::Ident(format_ident!("as_ref"))) - } - ReferenceType::Mutable => { - token_stream.append(TokenTree::Ident(format_ident!("as_mut"))) - } - } - token_stream.append(TokenTree::Group(Group::new( - Delimiter::Parenthesis, - TokenStream::default(), - ))); - } - } - - if !is_last_segment { - token_stream.append(TokenTree::Punct(Punct::new('.', Spacing::Alone))); - let next_segments_contain_optional = path - .iter() - .skip(index + 1) - .any(|segment| segment.is_optional); - let method_name = match next_segments_contain_optional { - true => "and_then", - false => "map", - }; - token_stream.append(TokenTree::Ident(format_ident!("{}", method_name))); - - let mut new_token_stream_within_method = TokenStream::default(); - new_token_stream_within_method - .append(TokenTree::Punct(Punct::new('|', Spacing::Alone))); - new_token_stream_within_method - .append(TokenTree::Ident(format_ident!("{}", segment.name))); - new_token_stream_within_method - .append(TokenTree::Punct(Punct::new('|', Spacing::Alone))); - if !next_segments_contain_optional { - new_token_stream_within_method - .append(TokenTree::Punct(Punct::new('&', Spacing::Alone))); - if ReferenceType::Mutable == reference_type { - new_token_stream_within_method - .append(TokenTree::Ident(format_ident!("mut"))); - } - } - new_token_stream_within_method - .append(TokenTree::Ident(format_ident!("{}", segment.name))); - token_stream_within_method = Some(new_token_stream_within_method); - } + } else { + let reference = match reference_type { + ReferenceKind::Immutable => quote! {& }, + ReferenceKind::Mutable => quote! {&mut }, + }; + quote! { + #reference #prefix . #(#segments).* } } - - if let Some(token_stream_within_method) = token_stream_within_method.take() { - token_stream.append(TokenTree::Group(Group::new( - Delimiter::Parenthesis, - token_stream_within_method, - ))); - } - - token_stream } #[cfg(test)] @@ -179,205 +88,223 @@ mod tests { use super::*; use quote::quote; + use source_analyzer::cycler::{CyclerKind, Instance}; #[test] fn paths_with_optionals_result_in_correct_accessor_token_streams() { let cases = [ - ("a", ReferenceType::Immutable, quote! { &prefix.a }), + ("a", ReferenceKind::Immutable, quote! { &prefix.a }), ( "$cycler_instance", - ReferenceType::Immutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => &prefix.instance_a, CyclerInstance::InstanceB => &prefix.instance_b, } }, + ReferenceKind::Immutable, + quote! { match instance { CyclerInstance::InstanceA => &prefix.instance_a, CyclerInstance::InstanceB => &prefix.instance_b, } }, ), - ("a", ReferenceType::Mutable, quote! { &mut prefix.a }), + ("a", ReferenceKind::Mutable, quote! { &mut prefix.a }), ( "$cycler_instance", - ReferenceType::Mutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => &mut prefix.instance_a, CyclerInstance::InstanceB => &mut prefix.instance_b, } }, + ReferenceKind::Mutable, + quote! { match instance { CyclerInstance::InstanceA => &mut prefix.instance_a, CyclerInstance::InstanceB => &mut prefix.instance_b, } }, ), - ("a/b", ReferenceType::Immutable, quote! { &prefix.a.b }), + ("a.b", ReferenceKind::Immutable, quote! { &prefix.a.b }), ( - "a/$cycler_instance", - ReferenceType::Immutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => &prefix.a.instance_a, CyclerInstance::InstanceB => &prefix.a.instance_b, } }, + "a.$cycler_instance", + ReferenceKind::Immutable, + quote! { match instance { CyclerInstance::InstanceA => &prefix.a.instance_a, CyclerInstance::InstanceB => &prefix.a.instance_b, } }, ), - ("a/b", ReferenceType::Mutable, quote! { &mut prefix.a.b }), + ("a.b", ReferenceKind::Mutable, quote! { &mut prefix.a.b }), ( - "a/$cycler_instance", - ReferenceType::Mutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => &mut prefix.a.instance_a, CyclerInstance::InstanceB => &mut prefix.a.instance_b, } }, + "a.$cycler_instance", + ReferenceKind::Mutable, + quote! { match instance { CyclerInstance::InstanceA => &mut prefix.a.instance_a, CyclerInstance::InstanceB => &mut prefix.a.instance_b, } }, ), - ("a/b/c", ReferenceType::Immutable, quote! { &prefix.a.b.c }), + ("a.b.c", ReferenceKind::Immutable, quote! { &prefix.a.b.c }), ( - "a/b/c", - ReferenceType::Mutable, + "a.b.c", + ReferenceKind::Mutable, quote! { &mut prefix.a.b.c }, ), ( - "a?/b/c", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().map(|a| &a.b.c) }, + "a?.b.c", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.c)) () }, ), ( - "a?/b/c", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().map(|a| &mut a.b.c) }, + "a?.b.c", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.c)) () }, + ), + ( + "a?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?)) () }, ), - ("a?", ReferenceType::Immutable, quote! { prefix.a.as_ref() }), ( "$cycler_instance?", - ReferenceType::Immutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => prefix.instance_a.as_ref(), CyclerInstance::InstanceB => prefix.instance_b.as_ref(), } }, + ReferenceKind::Immutable, + quote! { match instance { CyclerInstance::InstanceA => (|| Some(prefix.instance_a.as_ref()?)) (), CyclerInstance::InstanceB => (|| Some(prefix.instance_b.as_ref()?)) (), } }, + ), + ( + "a?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?)) () }, ), - ("a?", ReferenceType::Mutable, quote! { prefix.a.as_mut() }), ( "$cycler_instance?", - ReferenceType::Mutable, - quote! { match self.instance_name { CyclerInstance::InstanceA => prefix.instance_a.as_mut(), CyclerInstance::InstanceB => prefix.instance_b.as_mut(), } }, + ReferenceKind::Mutable, + quote! { match instance { CyclerInstance::InstanceA => (|| Some(prefix.instance_a.as_mut()?)) (), CyclerInstance::InstanceB => (|| Some(prefix.instance_b.as_mut()?)) (), } }, ), ( - "a?/b?/c", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.as_ref()).map(|b| &b.c) }, + "a?.b?.c", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.as_ref()?.c)) () }, ), ( - "a?/b?/c", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.as_mut()).map(|b| &mut b.c) }, + "a?.b?.c", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.as_mut()?.c)) () }, ), ( - "a?/b?/c?", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.as_ref()).and_then(|b| b.c.as_ref()) }, + "a?.b?.c?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.as_ref()?.c.as_ref()?)) () }, ), ( - "a?/b?/c?", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.as_mut()).and_then(|b| b.c.as_mut()) }, + "a?.b?.c?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.as_mut()?.c.as_mut()?)) () }, ), ( - "a?/b?/c?/d", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.as_ref()).and_then(|b| b.c.as_ref()).map(|c| &c.d) }, + "a?.b?.c?.d", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.as_ref()?.c.as_ref()?.d)) () }, ), ( - "a?/b?/c?/d", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.as_mut()).and_then(|b| b.c.as_mut()).map(|c| &mut c.d) }, + "a?.b?.c?.d", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.as_mut()?.c.as_mut()?.d)) () }, ), ( - "a?/b?/c?/d?", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.as_ref()).and_then(|b| b.c.as_ref()).and_then(|c| c.d.as_ref()) }, + "a?.b?.c?.d?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.as_ref()?.c.as_ref()?.d.as_ref()?)) () }, ), ( - "a?/b?/c?/d?", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.as_mut()).and_then(|b| b.c.as_mut()).and_then(|c| c.d.as_mut()) }, + "a?.b?.c?.d?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.as_mut()?.c.as_mut()?.d.as_mut()?)) () }, ), ( - "a?/b/c/d?", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.c.d.as_ref()) }, + "a?.b.c.d?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.c.d.as_ref()?)) () }, ), ( - "a?/b/c/d?", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.c.d.as_mut()) }, + "a?.b.c.d?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.c.d.as_mut()?)) () }, ), ( - "a?/b/c/d", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().map(|a| &a.b.c.d) }, + "a?.b.c.d", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.c.d)) () }, ), ( - "a?/b/c/d", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().map(|a| &mut a.b.c.d) }, + "a?.b.c.d", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.c.d)) () }, ), ( - "a?/b/c?/d", - ReferenceType::Immutable, - quote! { prefix.a.as_ref().and_then(|a| a.b.c.as_ref()).map(|c| &c.d) }, + "a?.b.c?.d", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.as_ref()?.b.c.as_ref()?.d)) () }, ), ( - "a?/b/c?/d", - ReferenceType::Mutable, - quote! { prefix.a.as_mut().and_then(|a| a.b.c.as_mut()).map(|c| &mut c.d) }, + "a?.b.c?.d", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.as_mut()?.b.c.as_mut()?.d)) () }, ), ( - "a/b/c?/d", - ReferenceType::Immutable, - quote! { prefix.a.b.c.as_ref().map(|c| &c.d) }, + "a.b.c?.d", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.b.c.as_ref()?.d)) () }, ), ( - "a/b/c?/d", - ReferenceType::Mutable, - quote! { prefix.a.b.c.as_mut().map(|c| &mut c.d) }, + "a.b.c?.d", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.b.c.as_mut()?.d)) () }, ), ( - "a/b/c/d", - ReferenceType::Immutable, + "a.b.c.d", + ReferenceKind::Immutable, quote! { &prefix.a.b.c.d }, ), ( - "a/b/c/d", - ReferenceType::Mutable, + "a.b.c.d", + ReferenceKind::Mutable, quote! { &mut prefix.a.b.c.d }, ), ( - "a/b?/c?/d", - ReferenceType::Immutable, - quote! { prefix.a.b.as_ref().and_then(|b| b.c.as_ref()).map(|c| &c.d) }, + "a.b?.c?.d", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.b.as_ref()?.c.as_ref()?.d)) () }, ), ( - "a/b?/c?/d", - ReferenceType::Mutable, - quote! { prefix.a.b.as_mut().and_then(|b| b.c.as_mut()).map(|c| &mut c.d) }, + "a.b?.c?.d", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.b.as_mut()?.c.as_mut()?.d)) () }, ), ( - "a/b?/c?/d?", - ReferenceType::Immutable, - quote! { prefix.a.b.as_ref().and_then(|b| b.c.as_ref()).and_then(|c| c.d.as_ref()) }, + "a.b?.c?.d?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.b.as_ref()?.c.as_ref()?.d.as_ref()?)) () }, ), ( - "a/b?/c?/d?", - ReferenceType::Mutable, - quote! { prefix.a.b.as_mut().and_then(|b| b.c.as_mut()).and_then(|c| c.d.as_mut()) }, + "a.b?.c?.d?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.b.as_mut()?.c.as_mut()?.d.as_mut()?)) () }, ), ( - "a/b/c/d/e/f?/g/i/j/k/l/m/n", - ReferenceType::Immutable, - quote! { prefix.a.b.c.d.e.f.as_ref().map(|f| &f.g.i.j.k.l.m.n) }, + "a.b.c.d.e.f?.g.i.j.k.l.m.n", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.b.c.d.e.f.as_ref()?.g.i.j.k.l.m.n)) () }, ), ( - "a/b/c/d/e/f?/g/i/j/k/l/m/n", - ReferenceType::Mutable, - quote! { prefix.a.b.c.d.e.f.as_mut().map(|f| &mut f.g.i.j.k.l.m.n) }, + "a.b.c.d.e.f?.g.i.j.k.l.m.n", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.b.c.d.e.f.as_mut()?.g.i.j.k.l.m.n)) () }, ), ( - "a/b/c/d/e/f?/g/i/j/k/l/m/n?", - ReferenceType::Immutable, - quote! { prefix.a.b.c.d.e.f.as_ref().and_then(|f| f.g.i.j.k.l.m.n.as_ref()) }, + "a.b.c.d.e.f?.g.i.j.k.l.m.n?", + ReferenceKind::Immutable, + quote! { (|| Some(prefix.a.b.c.d.e.f.as_ref()?.g.i.j.k.l.m.n.as_ref()?)) () }, ), ( - "a/b/c/d/e/f?/g/i/j/k/l/m/n?", - ReferenceType::Mutable, - quote! { prefix.a.b.c.d.e.f.as_mut().and_then(|f| f.g.i.j.k.l.m.n.as_mut()) }, + "a.b.c.d.e.f?.g.i.j.k.l.m.n?", + ReferenceKind::Mutable, + quote! { (|| Some(prefix.a.b.c.d.e.f.as_mut()?.g.i.j.k.l.m.n.as_mut()?)) () }, ), ]; + let cycler = Cycler { + name: "TestCycler".to_string(), + kind: CyclerKind::RealTime, + instances: vec![ + Instance { + name: "InstanceA".to_string(), + }, + Instance { + name: "InstanceB".to_string(), + }, + ], + module: "test_cycler".to_string(), + nodes: vec![], + }; for (path, reference_type, expected_token_stream) in cases { - let path_segments: Vec<_> = path.split('/').map(PathSegment::from).collect(); + let path = Path::from(path); + + let token_stream = + path_to_accessor_token_stream(quote! { prefix }, &path, reference_type, &cycler); - let token_stream = path_to_accessor_token_stream( - quote! { prefix }, - &path_segments, - reference_type, - quote! { self.instance_name }, - quote! { CyclerInstance:: }, - &["InstanceA".to_string(), "InstanceB".to_string()], - ); assert_eq!( token_stream.to_string(), expected_token_stream.to_string(), diff --git a/crates/code_generation/src/cycler.rs b/crates/code_generation/src/cycler.rs index 22b89b9303..08d0df587c 100644 --- a/crates/code_generation/src/cycler.rs +++ b/crates/code_generation/src/cycler.rs @@ -1,577 +1,752 @@ -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; +use std::iter::once; + use convert_case::{Case, Casing}; +use itertools::Itertools; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use source_analyzer::{CyclerInstances, CyclerType, CyclerTypes, Nodes}; - -use super::{node::Node, other_cycler::OtherCycler}; - -pub fn get_cyclers<'a>( - cycler_instances: &'a CyclerInstances, - nodes: &'a Nodes, - cycler_types: &'a CyclerTypes, -) -> Vec> { - cycler_instances - .modules_to_instances - .keys() - .map(|cycler_module_name| { - match cycler_types.cycler_modules_to_cycler_types[cycler_module_name] { - CyclerType::Perception => Cycler::Perception { - cycler_instances, - nodes, - cycler_types, - cycler_module_name, - }, - CyclerType::RealTime => Cycler::RealTime { - cycler_instances, - nodes, - cycler_types, - cycler_module_name, - }, - } - }) - .collect() -} +use source_analyzer::{ + contexts::Field, + cycler::{Cycler, CyclerKind, Cyclers}, + node::Node, +}; +use thiserror::Error; + +use crate::accessor::{path_to_accessor_token_stream, ReferenceKind}; -pub fn generate_cyclers(cyclers: &[Cycler]) -> Result { - let cyclers: Vec<_> = cyclers +#[derive(Debug, Error)] +pub enum Error {} + +pub fn generate_cyclers(cyclers: &Cyclers) -> Result { + let cyclers = cyclers + .cyclers .iter() - .map(|cycler| { - cycler.get_module().wrap_err_with(|| { - format!("failed to get cycler `{}`", cycler.get_cycler_module_name()) - }) - }) - .collect::>() - .wrap_err("failed to get cyclers")?; + .map(|cycler| generate_module(cycler, cyclers)) + .collect::, _>>()?; Ok(quote! { #(#cyclers)* }) } -#[derive(Debug)] -pub enum Cycler<'a> { - Perception { - cycler_instances: &'a CyclerInstances, - nodes: &'a Nodes, - cycler_types: &'a CyclerTypes, - cycler_module_name: &'a str, - }, - RealTime { - cycler_instances: &'a CyclerInstances, - nodes: &'a Nodes, - cycler_types: &'a CyclerTypes, - cycler_module_name: &'a str, - }, -} +fn generate_module(cycler: &Cycler, cyclers: &Cyclers) -> Result { + let module_name = format_ident!("{}", cycler.module); + let cycler_instance = generate_cycler_instance(cycler); + let database_struct = generate_database_struct(); + let cycler_struct = generate_struct(cycler, cyclers); + let cycler_implementation = generate_implementation(cycler, cyclers); -impl Cycler<'_> { - pub fn get_cycler_instances(&self) -> &CyclerInstances { - match self { - Cycler::Perception { - cycler_instances, .. - } => cycler_instances, - Cycler::RealTime { - cycler_instances, .. - } => cycler_instances, - } - } + Ok(quote! { + #[allow(dead_code, unused_mut, unused_variables)] + pub mod #module_name { + use color_eyre::eyre::WrapErr; + use crate::structs::#module_name::{MainOutputs, AdditionalOutputs}; - pub fn get_nodes(&self) -> &Nodes { - match self { - Cycler::Perception { nodes, .. } => nodes, - Cycler::RealTime { nodes, .. } => nodes, + #cycler_instance + #database_struct + #cycler_struct + #cycler_implementation } - } + }) +} - fn get_cycler_types(&self) -> &CyclerTypes { - match self { - Cycler::Perception { cycler_types, .. } => cycler_types, - Cycler::RealTime { cycler_types, .. } => cycler_types, +fn generate_cycler_instance(cycler: &Cycler) -> TokenStream { + let instances = cycler + .instances + .iter() + .map(|instance| format_ident!("{}", instance.name)); + quote! { + #[derive(Clone, Copy, Debug)] + pub enum CyclerInstance { + #(#instances,)* } } +} - pub fn get_cycler_module_name(&self) -> &str { - match self { - Cycler::Perception { - cycler_module_name, .. - } => cycler_module_name, - Cycler::RealTime { - cycler_module_name, .. - } => cycler_module_name, +fn generate_database_struct() -> TokenStream { + quote! { + #[derive(Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] + pub struct Database { + pub main_outputs: MainOutputs, + pub additional_outputs: AdditionalOutputs, } } +} - pub fn get_cycler_module_name_identifier(&self) -> Ident { - format_ident!("{}", self.get_cycler_module_name()) - } - - pub fn get_database_struct(&self) -> TokenStream { - let cycler_module_name_identifier = self.get_cycler_module_name_identifier(); - quote! { - #[derive(Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] - pub struct Database { - pub main_outputs: structs::#cycler_module_name_identifier::MainOutputs, - pub additional_outputs: structs::#cycler_module_name_identifier::AdditionalOutputs, +fn generate_struct(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + let module_name = format_ident!("{}", cycler.module); + let input_output_fields = generate_input_output_fields(cycler, cyclers); + let realtime_inputs = match cycler.kind { + CyclerKind::Perception => quote! {}, + CyclerKind::RealTime => { + quote! { + historic_databases: framework::HistoricDatabases, + perception_databases: framework::PerceptionDatabases, } } - } - - pub fn get_own_producer_identifier(&self) -> TokenStream { - match self { - Cycler::Perception { .. } => quote! { own_producer, }, - Cycler::RealTime { .. } => Default::default(), - } - } - - pub fn get_own_producer_type(&self) -> TokenStream { - let cycler_module_name_identifier = self.get_cycler_module_name_identifier(); - quote! { - framework::Producer< - structs::#cycler_module_name_identifier::MainOutputs, - > + }; + let node_fields = generate_node_fields(cycler); + + quote! { + pub struct Cycler { + instance: CyclerInstance, + hardware_interface: std::sync::Arc, + own_writer: framework::Writer, + own_changed: std::sync::Arc, + own_subscribed_outputs_reader: framework::Reader>, + configuration_reader: framework::Reader, + persistent_state: crate::structs::#module_name::PersistentState, + #realtime_inputs + #input_output_fields + #node_fields } } +} - pub fn get_own_producer_field(&self) -> TokenStream { - let own_producer_type = self.get_own_producer_type(); - match self { - Cycler::Perception { .. } => quote! { own_producer: #own_producer_type, }, - Cycler::RealTime { .. } => Default::default(), +fn generate_input_output_fields(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + match cycler.kind { + CyclerKind::Perception => { + let readers = generate_reader_fields(cyclers); + quote! { + own_producer: framework::Producer, + #readers + } } - } - - pub fn get_other_cyclers(&self) -> Vec { - match self { - Cycler::Perception { - cycler_instances, .. - } => self - .get_cycler_types() - .cycler_modules_to_cycler_types - .iter() - .filter_map( - |(other_cycler_module_name, other_cycler_type)| match other_cycler_type { - CyclerType::RealTime => Some( - cycler_instances.modules_to_instances[other_cycler_module_name] - .iter() - .map(|other_cycler_instance_name| OtherCycler::Reader { - cycler_instance_name: other_cycler_instance_name, - cycler_module_name: other_cycler_module_name, - }), - ), - CyclerType::Perception => None, - }, - ) - .flatten() - .collect(), - Cycler::RealTime { - cycler_instances, .. - } => self - .get_cycler_types() - .cycler_modules_to_cycler_types - .iter() - .filter_map( - |(other_cycler_module_name, other_cycler_type)| match other_cycler_type { - CyclerType::Perception => Some( - cycler_instances.modules_to_instances[other_cycler_module_name] - .iter() - .map(|other_cycler_instance_name| OtherCycler::Consumer { - cycler_instance_name: other_cycler_instance_name, - cycler_module_name: other_cycler_module_name, - }), - ), - CyclerType::RealTime => None, - }, - ) - .flatten() - .collect(), + CyclerKind::RealTime => { + let consumers = generate_consumer_fields(cyclers); + quote! { + #consumers + } } } +} - pub fn get_other_cycler_identifiers(&self) -> Vec { - self.get_other_cyclers() - .into_iter() - .map(|other_cycler| match other_cycler { - OtherCycler::Consumer { - cycler_instance_name, - .. - } => format_ident!("{}_consumer", cycler_instance_name.to_case(Case::Snake)), - OtherCycler::Reader { - cycler_instance_name, - .. - } => format_ident!("{}_reader", cycler_instance_name.to_case(Case::Snake)), - }) - .collect() - } - - pub fn get_other_cycler_fields(&self) -> Vec { - self.get_other_cyclers() - .into_iter() - .map(|other_cycler| { - let (field_name, field_type) = match other_cycler { - OtherCycler::Consumer { - cycler_instance_name, - cycler_module_name, - } => { - let cycler_module_name_identifier = format_ident!("{}", cycler_module_name); - ( - format_ident!("{}_consumer", cycler_instance_name.to_case(Case::Snake)), - quote! { - framework::Consumer< - structs::#cycler_module_name_identifier::MainOutputs, - > - }, - ) - } - OtherCycler::Reader { - cycler_instance_name, - cycler_module_name, - } => { - let cycler_module_name_identifier = format_ident!("{}", cycler_module_name); - ( - format_ident!("{}_reader", cycler_instance_name.to_case(Case::Snake)), - quote! { - framework::Reader - }, - ) - } - }; - quote! { - #field_name: #field_type - } - }) - .collect() - } +fn generate_reader_fields(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances_with(CyclerKind::RealTime) + .map(|(cycler, instance)| { + let field_name = format_ident!("{}_reader", instance.name.to_case(Case::Snake)); + let cycler_module_name = format_ident!("{}", cycler.module); - pub fn get_perception_cycler_updates(&self) -> Vec { - self.get_other_cyclers() - .into_iter() - .filter_map(|other_cycler| match other_cycler { - OtherCycler::Consumer { - cycler_instance_name, - .. - } => { - let update_name_identifier = - format_ident!("{}", cycler_instance_name.to_case(Case::Snake)); - let consumer_identifier = - format_ident!("{}_consumer", cycler_instance_name.to_case(Case::Snake)); - - Some(quote! { - #update_name_identifier: self.#consumer_identifier.consume(now) - }) - } - OtherCycler::Reader { .. } => None, - }) - .collect() - } + quote! { + #field_name: framework::Reader, + } + }) + .collect() +} - pub fn get_perception_cycler_databases(&self) -> Vec { - self.get_other_cyclers() - .into_iter() - .filter_map(|other_cycler| match other_cycler { - OtherCycler::Reader { - cycler_instance_name, - .. - } => { - let reader_identifier = - format_ident!("{}_reader", cycler_instance_name.to_case(Case::Snake)); - let database_identifier = - format_ident!("{}_database", cycler_instance_name.to_case(Case::Snake)); - - Some(quote! { - let #database_identifier = self.#reader_identifier.next(); - }) - } - OtherCycler::Consumer { .. } => None, - }) - .collect() - } +fn generate_consumer_fields(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances_with(CyclerKind::Perception) + .map(|(cycler, instance)| { + let field_name = format_ident!("{}_consumer", instance.name.to_case(Case::Snake)); + let cycler_module_name = format_ident!("{}", cycler.module); - pub fn get_interpreted_nodes(&self) -> Vec { - let nodes = self.get_nodes(); - nodes.cycler_modules_to_nodes[self.get_cycler_module_name()] - .iter() - .map(|node_name| Node { - cycler_instances: self.get_cycler_instances(), - node_name, - node: nodes.nodes.get(node_name).expect("missing node"), - }) - .collect() - } + quote! { + #field_name: framework::Consumer, + } + }) + .collect() +} - pub fn get_node_identifiers(&self) -> Vec { - self.get_interpreted_nodes() - .into_iter() - .map(|node| node.get_identifier_snake_case()) - .collect() +fn generate_node_fields(cycler: &Cycler) -> TokenStream { + let fields: Vec<_> = cycler + .nodes + .iter() + .map(|node| { + let node_name_snake_case = format_ident!("{}", node.name.to_case(Case::Snake)); + let cycler_module_name = format_ident!("{}", cycler.module); + let node_module = &node.module; + let node_name = format_ident!("{}", node.name); + quote! { + #node_name_snake_case: crate::#cycler_module_name::#node_module::#node_name + } + }) + .collect(); + quote! { + #(#fields,)* } +} - pub fn get_node_fields(&self) -> Vec { - self.get_interpreted_nodes() - .into_iter() - .map(|node| node.get_field()) - .collect() +fn generate_implementation(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + let new_method = generate_new_method(cycler, cyclers); + let start_method = generate_start_method(); + let cycle_method = generate_cycle_method(cycler, cyclers); + + quote! { + impl Cycler + where + Interface: types::hardware::Interface + std::marker::Send + std::marker::Sync + 'static, + { + #new_method + #start_method + #cycle_method + } } +} - pub fn get_node_initializers(&self) -> Result> { - self.get_interpreted_nodes() - .into_iter() - .map(|node| node.get_initializer()) - .collect() +fn generate_new_method(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + let input_output_fields = generate_input_output_fields(cycler, cyclers); + let cycler_module_name = format_ident!("{}", cycler.module); + let node_initializers = generate_node_initializers(cycler); + let node_identifiers = cycler + .nodes + .iter() + .map(|node| format_ident!("{}", node.name.to_case(Case::Snake))); + let input_output_identifiers = generate_input_output_identifiers(cycler, cyclers); + + quote! { + pub fn new( + instance: CyclerInstance, + hardware_interface: std::sync::Arc, + own_writer: framework::Writer, + own_changed: std::sync::Arc, + own_subscribed_outputs_reader: framework::Reader>, + configuration_reader: framework::Reader, + #input_output_fields + ) -> color_eyre::Result { + let configuration = configuration_reader.next().clone(); + let mut persistent_state = crate::structs::#cycler_module_name::PersistentState::default(); + #node_initializers + Ok(Self { + instance, + hardware_interface, + own_writer, + own_changed, + own_subscribed_outputs_reader, + configuration_reader, + persistent_state, + #input_output_identifiers + #(#node_identifiers,)* + }) + } } +} - pub fn get_node_executions(&self) -> Result> { - self.get_interpreted_nodes() - .into_iter() - .map(|node| node.get_execution()) - .collect() +fn generate_node_initializers(cycler: &Cycler) -> TokenStream { + let initializers = cycler.nodes.iter().map(|node| { + let node_name_snake_case = format_ident!("{}", node.name.to_case(Case::Snake)); + let cycler_module_name = format_ident!("{}", cycler.module); + let node_module = &node.module; + let node_name = format_ident!("{}", node.name); + let field_initializers = generate_node_field_initializers(node, cycler); + let error_message = format!("failed to create node `{}`", node.name); + quote! { + let #node_name_snake_case = crate::#cycler_module_name::#node_module::#node_name::new( + crate::#cycler_module_name::#node_module::CreationContext { + #field_initializers + } + ) + .wrap_err(#error_message)?; + } + }); + quote! { + #(#initializers)* } +} - pub fn get_struct_definition(&self) -> TokenStream { - let database_struct = self.get_database_struct(); - let own_producer_field = self.get_own_producer_field(); - let other_cycler_fields = self.get_other_cycler_fields(); - let cycler_module_name_identifier = self.get_cycler_module_name_identifier(); - let real_time_fields = match self { - Cycler::Perception { .. } => Default::default(), - Cycler::RealTime { - cycler_module_name, .. - } => { - let cycler_module_name_identifier = format_ident!("{}", cycler_module_name); - +fn generate_node_field_initializers(node: &Node, cycler: &Cycler) -> TokenStream { + node.contexts + .creation_context + .iter() + .map(|field| match field { + Field::AdditionalOutput { name, .. } => { + panic!("unexpected additional output field `{name}` in CreationContext") + } + Field::CyclerInstance { name } => quote! { + #name: instance, + }, + Field::HardwareInterface { name } => quote! { + #name: &hardware_interface, + }, + Field::HistoricInput { name, .. } => { + panic!("unexpected historic input field `{name}` in new context") + } + Field::Input { name, .. } => { + panic!("unexpected optional input field `{name}` in new context") + } + Field::MainOutput { name, .. } => { + panic!("unexpected main output field `{name}` in new context") + } + Field::Parameter { name, path, .. } => { + let accessor = path_to_accessor_token_stream( + quote! { configuration }, + path, + ReferenceKind::Immutable, + cycler, + ); quote! { - historic_databases: framework::HistoricDatabases< - structs::#cycler_module_name_identifier::MainOutputs, - >, - perception_databases: framework::PerceptionDatabases, + #name: #accessor, } } - }; - let node_fields = self.get_node_fields(); - - quote! { - #database_struct + Field::PerceptionInput { name, .. } => { + panic!("unexpected perception input field `{name}` in new context") + } + Field::PersistentState { name, path, .. } => { + let accessor = path_to_accessor_token_stream( + quote! { persistent_state }, + path, + ReferenceKind::Mutable, + cycler, + ); + quote! { + #name: #accessor, + } + } + Field::RequiredInput { name, .. } => { + panic!("unexpected required input field `{name}` in new context") + } + }) + .collect() +} - pub struct Cycler { - instance: #cycler_module_name_identifier::CyclerInstance, - hardware_interface: std::sync::Arc, - own_writer: framework::Writer, - #own_producer_field - #(#other_cycler_fields,)* - own_changed: std::sync::Arc, - own_subscribed_outputs_reader: framework::Reader>, - configuration_reader: framework::Reader, - #real_time_fields - persistent_state: structs::#cycler_module_name_identifier::PersistentState, - #(#node_fields,)* +fn generate_input_output_identifiers(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + match cycler.kind { + CyclerKind::Perception => { + let readers = generate_reader_identifiers(cyclers); + quote! { + own_producer, + #(#readers,)* } } - } - - pub fn get_new_method(&self) -> Result { - let own_producer_field = self.get_own_producer_field(); - let other_cycler_fields = self.get_other_cycler_fields(); - let cycler_module_name_identifier = self.get_cycler_module_name_identifier(); - let node_initializers = self - .get_node_initializers() - .wrap_err("failed to get node initializers")?; - let own_producer_identifier = self.get_own_producer_identifier(); - let other_cycler_identifiers = self.get_other_cycler_identifiers(); - let real_time_initializers = match self { - Cycler::Perception { .. } => Default::default(), - Cycler::RealTime { .. } => quote! { + CyclerKind::RealTime => { + let consumers = generate_consumer_identifiers(cyclers); + quote! { historic_databases: Default::default(), perception_databases: Default::default(), - }, - }; - let node_identifiers = self.get_node_identifiers(); - - Ok(quote! { - pub fn new( - instance: #cycler_module_name_identifier::CyclerInstance, - hardware_interface: std::sync::Arc, - own_writer: framework::Writer, - #own_producer_field - #(#other_cycler_fields,)* - own_changed: std::sync::Arc, - own_subscribed_outputs_reader: framework::Reader>, - configuration_reader: framework::Reader, - ) -> color_eyre::Result { - use color_eyre::eyre::WrapErr; - let configuration = configuration_reader.next().clone(); - let mut persistent_state = structs::#cycler_module_name_identifier::PersistentState::default(); - #(#node_initializers)* - Ok(Self { - instance, - hardware_interface, - own_writer, - #own_producer_identifier - #(#other_cycler_identifiers,)* - own_changed, - own_subscribed_outputs_reader, - configuration_reader, - #real_time_initializers - persistent_state, - #(#node_identifiers,)* - }) + #(#consumers,)* } - }) + } } +} - pub fn get_start_method(&self) -> TokenStream { - quote! { - pub fn start( - mut self, - keep_running: tokio_util::sync::CancellationToken, - ) -> color_eyre::Result>> { - use color_eyre::eyre::WrapErr; - let instance_name = format!("{:?}", self.instance); - std::thread::Builder::new() - .name(instance_name.clone()) - .spawn(move || { - while !keep_running.is_cancelled() { - if let Err(error) = self.cycle() { - keep_running.cancel(); - return Err(error).wrap_err_with(|| { - format!("failed to execute cycle of cycler `{:?}`", self.instance) - }); - } +fn generate_reader_identifiers(cyclers: &Cyclers) -> Vec { + cyclers + .instances_with(CyclerKind::RealTime) + .map(|(_cycler, instance)| format_ident!("{}_reader", instance.name.to_case(Case::Snake))) + .collect() +} + +fn generate_consumer_identifiers(cyclers: &Cyclers) -> Vec { + cyclers + .instances_with(CyclerKind::Perception) + .map(|(_cycler, instance)| format_ident!("{}_consumer", instance.name.to_case(Case::Snake))) + .collect() +} + +fn generate_start_method() -> TokenStream { + quote! { + pub fn start( + mut self, + keep_running: tokio_util::sync::CancellationToken, + ) -> color_eyre::Result>> { + let instance_name = format!("{:?}", self.instance); + std::thread::Builder::new() + .name(instance_name.clone()) + .spawn(move || { + while !keep_running.is_cancelled() { + if let Err(error) = self.cycle() { + keep_running.cancel(); + return Err(error).wrap_err_with(|| { + format!("failed to execute cycle of cycler `{:?}`", self.instance) + }); } - Ok(()) - }) - .wrap_err_with(|| { - format!("failed to spawn thread for `{instance_name}`") - }) - } + } + Ok(()) + }) + .wrap_err_with(|| { + format!("failed to spawn thread for `{instance_name}`") + }) } } +} - pub fn get_cycle_method(&self) -> Result { - let node_executions = self - .get_node_executions() - .wrap_err("failed to get node executions")?; +fn generate_cycle_method(cycler: &Cycler, cyclers: &Cyclers) -> TokenStream { + let (setup_nodes, remaining_nodes): (Vec<_>, Vec<_>) = + cycler.nodes.iter().partition(|node| node.is_setup); + let setup_node_executions = setup_nodes + .iter() + .map(|node| generate_node_execution(node, cycler)); + let remaining_node_executions = remaining_nodes + .iter() + .map(|node| generate_node_execution(node, cycler)); - if node_executions.is_empty() { - bail!("expected at least one node"); - } + let post_setup = match cycler.kind { + CyclerKind::Perception => quote! { + self.own_producer.announce(); + }, + CyclerKind::RealTime => { + let perception_cycler_updates = generate_perception_cycler_updates(cyclers); - let before_first_node = quote! { - let mut own_database = self.own_writer.next(); - let own_database_reference = { - use std::ops::DerefMut; - own_database.deref_mut() - }; - }; - let (first_node, remaining_nodes) = node_executions.split_at(1); - let first_node = { - let first_node = &first_node[0]; quote! { + let now = self.hardware_interface.get_now(); + self.perception_databases.update(now, crate::perception_databases::Updates { + #perception_cycler_updates + }); + } + } + }; + let lock_readers = match cycler.kind { + CyclerKind::Perception => cyclers + .instances_with(CyclerKind::RealTime) + .map(|(_cycler, instance)| { + let reader = format_ident!("{}_reader", instance.name.to_case(Case::Snake)); + let database = format_ident!("{}_database", instance.name.to_case(Case::Snake)); + quote! { + let #database = self.#reader.next(); + } + }) + .collect(), + CyclerKind::RealTime => quote! {}, + }; + let after_remaining_nodes = match cycler.kind { + CyclerKind::Perception => quote! { + self.own_producer.finalize(own_database_reference.main_outputs.clone()); + }, + CyclerKind::RealTime => quote! { + self.historic_databases.update( + now, + self.perception_databases + .get_first_timestamp_of_temporary_databases(), + &own_database_reference.main_outputs, + ); + }, + }; + + quote! { + pub fn cycle(&mut self) -> color_eyre::Result<()> { + { + let instance = self.instance; + let instance_name = format!("{instance:?}"); + let itt_domain = ittapi::Domain::new(&instance_name); + + let mut own_database = self.own_writer.next(); + let own_database_reference = { + use std::ops::DerefMut; + own_database.deref_mut() + }; + { let own_subscribed_outputs = self.own_subscribed_outputs_reader.next(); let configuration = self.configuration_reader.next(); - #first_node + #(#setup_node_executions)* } - } - }; - let after_first_node = match self { - Cycler::Perception { .. } => quote! { - self.own_producer.announce(); - }, - Cycler::RealTime { .. } => { - let perception_cycler_updates = self.get_perception_cycler_updates(); - quote! { - let now = self.hardware_interface.get_now(); - self.perception_databases.update(now, framework::Updates { - #(#perception_cycler_updates,)* - }); - } - } - }; - let other_cycler_databases = self.get_perception_cycler_databases(); - let remaining_nodes = match remaining_nodes.is_empty() { - true => Default::default(), - false => quote! { + #post_setup { let own_subscribed_outputs = self.own_subscribed_outputs_reader.next(); let configuration = self.configuration_reader.next(); - #(#other_cycler_databases)* - #(#remaining_nodes)* + #lock_readers + #(#remaining_node_executions)* } - }, - }; - let after_remaining_nodes = match self { - Cycler::Perception { .. } => quote! { - self.own_producer.finalize(own_database_reference.main_outputs.clone()); - }, - Cycler::RealTime { .. } => quote! { - self.historic_databases.update( - now, - self.perception_databases - .get_first_timestamp_of_temporary_databases(), - &own_database_reference.main_outputs, - ); - }, - }; - let after_dropping_database_writer_guard = quote! { + #after_remaining_nodes + } self.own_changed.notify_one(); - }; + Ok(()) + } + } +} - Ok(quote! { - pub fn cycle(&mut self) -> color_eyre::Result<()> { - use color_eyre::eyre::WrapErr; - { - let instance_name = format!("{:?}", self.instance); - let itt_domain = ittapi::Domain::new(&instance_name); - #before_first_node - #first_node - #after_first_node - #remaining_nodes - #after_remaining_nodes - } - #after_dropping_database_writer_guard - Ok(()) +fn generate_perception_cycler_updates(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances_with(CyclerKind::Perception) + .map(|(_cycler, instance)| { + let identifier = format_ident!("{}", instance.name.to_case(Case::Snake)); + let consumer = format_ident!("{}_consumer", identifier); + quote! { + #identifier: self.#consumer.consume(now), } }) + .collect() +} + +fn generate_node_execution(node: &Node, cycler: &Cycler) -> TokenStream { + let cycler_module_name = format_ident!("{}", cycler.module); + let are_required_inputs_some = generate_required_input_condition(node, cycler); + let node_name = &node.name; + let node_module = &node.module; + let node_member = format_ident!("{}", node.name.to_case(Case::Snake)); + let context_initializers = generate_context_initializers(node, cycler); + let error_message = format!("failed to execute cycle of `{}`", node.name); + let database_updates = generate_database_updates(node); + let database_updates_from_defaults = generate_database_updates_from_defaults(node); + quote! { + { + if #are_required_inputs_some { + let main_outputs = { + let _task = ittapi::Task::begin(&itt_domain, #node_name); + self.#node_member.cycle( + crate::#cycler_module_name::#node_module::CycleContext { + #context_initializers + }, + ) + .wrap_err(#error_message)? + }; + #database_updates + } + else { + #database_updates_from_defaults + } + } } +} - pub fn get_struct_implementation(&self) -> Result { - let new_method = self - .get_new_method() - .wrap_err("failed to get `new` method")?; - let start_method = self.get_start_method(); - let cycle_method = self - .get_cycle_method() - .wrap_err("failed to get `cycle` method")?; - - Ok(quote! { - impl Cycler - where - Interface: types::hardware::Interface + std::marker::Send + std::marker::Sync + 'static, - { - #new_method - #start_method - #cycle_method +fn generate_required_input_condition(node: &Node, cycler: &Cycler) -> TokenStream { + let conditions = node + .contexts + .cycle_context + .iter() + .filter_map(|field| match field { + Field::RequiredInput { + cycler_instance, + path, + .. + } => { + let database_prefix = match cycler_instance { + Some(cycler_instance) => { + let identifier = + format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); + quote! { #identifier.main_outputs } + } + None => { + quote! { own_database_reference.main_outputs } + } + }; + let accessor = path_to_accessor_token_stream( + database_prefix, + path, + ReferenceKind::Immutable, + cycler, + ); + Some(quote! { + #accessor .is_some() + }) } + _ => None, }) + .chain(once(quote! {true})); + quote! { + #(#conditions)&&* } +} - pub fn get_module(&self) -> Result { - let cycler_module_name_identifier = self.get_cycler_module_name_identifier(); - let struct_definition = self.get_struct_definition(); - let struct_implementation = self - .get_struct_implementation() - .wrap_err("failed to get struct implementation")?; - - Ok(quote! { - #[allow(dead_code, unused_mut, unused_variables)] - pub mod #cycler_module_name_identifier { - #struct_definition - #struct_implementation +fn generate_context_initializers(node: &Node, cycler: &Cycler) -> TokenStream { + let initializers = node + .contexts + .cycle_context + .iter() + .map(|field| match field { + Field::AdditionalOutput { name, path, .. } => { + let accessor = path_to_accessor_token_stream( + quote!{ own_database_reference.additional_outputs }, + path, + ReferenceKind::Mutable, + cycler, + ); + let path_string = once("additional_outputs").chain( + path.segments.iter().map(|segment| segment.name.as_str()) + ).join("."); + quote! { + #name: framework::AdditionalOutput::new( + own_subscribed_outputs + .iter() + .any(|subscribed_output| framework::should_be_filled(subscribed_output, #path_string)), + #accessor, + ) + } + } + Field::CyclerInstance { name } => quote! { + #name: self.instance + }, + Field::HardwareInterface { name } => quote! { + #name: &self.hardware_interface + }, + Field::HistoricInput { name, path, .. } => { + let now_accessor = path_to_accessor_token_stream( + quote!{ own_database_reference.main_outputs }, + path, + ReferenceKind::Immutable, + cycler, + ); + let historic_accessor = path_to_accessor_token_stream( + quote!{ database }, + path, + ReferenceKind::Immutable, + cycler, + ); + quote! { + #name: [(now, #now_accessor)] + .into_iter() + .chain( + self + .historic_databases + .databases + .iter() + .map(|(system_time, database)| ( + *system_time, + #historic_accessor, + )) + ) + .collect::>() + .into() + } + } + Field::Input { + cycler_instance, + name, + path, + .. + } => { + let database_prefix = match cycler_instance { + Some(cycler_instance) => { + let identifier = + format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); + quote! { #identifier.main_outputs } + } + None => { + quote! { own_database_reference.main_outputs } + } + }; + let accessor = path_to_accessor_token_stream( + database_prefix, + path, + ReferenceKind::Immutable, + cycler, + ); + quote! { + #name: #accessor + } + } + Field::MainOutput { name, .. } => { + panic!("unexpected MainOutput `{name}` in cycle context") + } + Field::Parameter { name, path, .. } => { + let accessor = path_to_accessor_token_stream( + quote! { configuration }, + path, + ReferenceKind::Immutable, + cycler, + ); + quote! { + #name: #accessor + } + } + Field::PerceptionInput { + cycler_instance, + name, + path, + .. + } => { + let cycler_instance_identifier = + format_ident!("{}", cycler_instance.to_case(Case::Snake)); + let accessor = path_to_accessor_token_stream( + quote! { database }, + path, + ReferenceKind::Immutable, + cycler, + ); + quote! { + #name: framework::PerceptionInput { + persistent: self + .perception_databases + .persistent() + .map(|(system_time, databases)| ( + *system_time, + databases + .#cycler_instance_identifier + .iter() + .map(|database| #accessor) + .collect() + , + )) + .collect(), + temporary: self + .perception_databases + .temporary() + .map(|(system_time, databases)| ( + *system_time, + databases + .#cycler_instance_identifier + .iter() + .map(|database| #accessor) + .collect() + , + )) + .collect(), + } + } + } + Field::PersistentState { name, path, .. } => { + let accessor = path_to_accessor_token_stream( + quote! { self.persistent_state }, + path, + ReferenceKind::Mutable, + cycler, + ); + quote! { + #name: #accessor + } + } + Field::RequiredInput { + cycler_instance, + name, + path, + .. + } => { + let database_prefix = match cycler_instance { + Some(cycler_instance) => { + let identifier = + format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); + quote! { #identifier.main_outputs } + } + None => { + quote! { own_database_reference.main_outputs } + } + }; + let accessor = path_to_accessor_token_stream( + database_prefix, + path, + ReferenceKind::Immutable, + cycler, + ); + quote! { + #name: #accessor .unwrap() + } + } + }) + ; + quote! { + #(#initializers,)* + } +} + +fn generate_database_updates(node: &Node) -> TokenStream { + node.contexts + .main_outputs + .iter() + .filter_map(|field| match field { + Field::MainOutput { name, .. } => { + let setter = quote! { + own_database_reference.main_outputs.#name = main_outputs.#name.value; + }; + Some(setter) } + _ => None, }) - } + .collect() +} + +fn generate_database_updates_from_defaults(node: &Node) -> TokenStream { + node.contexts + .main_outputs + .iter() + .filter_map(|field| match field { + Field::MainOutput { name, .. } => { + let setter = quote! { + own_database_reference.main_outputs.#name = Default::default(); + }; + Some(setter) + } + _ => None, + }) + .collect() } diff --git a/crates/code_generation/src/lib.rs b/crates/code_generation/src/lib.rs index 4de32856f5..0921e3da05 100644 --- a/crates/code_generation/src/lib.rs +++ b/crates/code_generation/src/lib.rs @@ -1,7 +1,4 @@ -pub mod accessor; +mod accessor; pub mod cycler; -pub mod node; -pub mod other_cycler; -pub mod path; -pub mod reference_type; pub mod run; +pub mod structs; diff --git a/crates/code_generation/src/node.rs b/crates/code_generation/src/node.rs deleted file mode 100644 index 0a090ea75b..0000000000 --- a/crates/code_generation/src/node.rs +++ /dev/null @@ -1,453 +0,0 @@ -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; -use convert_case::{Case, Casing}; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote}; -use source_analyzer::{CyclerInstances, Field}; - -use crate::path::path_to_path_string_token_stream; - -use super::{accessor::path_to_accessor_token_stream, reference_type::ReferenceType}; - -pub struct Node<'a> { - pub cycler_instances: &'a CyclerInstances, - pub node_name: &'a str, - pub node: &'a source_analyzer::Node, -} - -impl Node<'_> { - pub fn get_identifier(&self) -> Ident { - format_ident!("{}", self.node_name) - } - - pub fn get_identifier_snake_case(&self) -> Ident { - format_ident!("{}", self.node_name.to_case(Case::Snake)) - } - - pub fn get_path_segments(&self) -> Vec { - self.node - .path_segments - .iter() - .map(|segment| format_ident!("{}", segment)) - .collect() - } - - pub fn get_field(&self) -> TokenStream { - let node_name_identifier_snake_case = self.get_identifier_snake_case(); - let node_name_identifier = self.get_identifier(); - let path_segments = self.get_path_segments(); - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - - quote! { - #node_name_identifier_snake_case: - #cycler_module_name_identifier::#(#path_segments::)*#node_name_identifier - } - } - - pub fn get_initializer_field_initializers(&self) -> Result> { - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - self.node - .contexts - .creation_context - .iter() - .map(|field| match field { - Field::AdditionalOutput { name, .. } => { - bail!("unexpected additional output field `{name}` in new context") - } - Field::CyclerInstance { name } => Ok(quote! { - #name: instance - }), - Field::HardwareInterface { name } => Ok(quote! { - #name: &hardware_interface - }), - Field::HistoricInput { name, .. } => { - bail!("unexpected historic input field `{name}` in new context") - } - Field::Input { name, .. } => { - bail!("unexpected optional input field `{name}` in new context") - } - Field::MainOutput { name, .. } => { - bail!("unexpected main output field `{name}` in new context") - } - Field::Parameter { name, path, .. } => { - let accessor = path_to_accessor_token_stream( - quote! { configuration }, - path, - ReferenceType::Immutable, - quote! { instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor - }) - } - Field::PerceptionInput { name, .. } => { - bail!("unexpected perception input field `{name}` in new context") - } - Field::PersistentState { name, path, .. } => { - let accessor = path_to_accessor_token_stream( - quote! { persistent_state }, - path, - ReferenceType::Mutable, - quote! { instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor - }) - } - Field::RequiredInput { name, .. } => { - bail!("unexpected required input field `{name}` in new context") - } - }) - .collect() - } - - pub fn get_initializer(&self) -> Result { - let node_name_identifier_snake_case = self.get_identifier_snake_case(); - let node_name_identifier = self.get_identifier(); - let path_segments = self.get_path_segments(); - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - let field_initializers = self - .get_initializer_field_initializers() - .wrap_err("failed to generate field initializers")?; - let error_message = format!("failed to create node `{}`", self.node_name); - - Ok(quote! { - let #node_name_identifier_snake_case = #cycler_module_name_identifier::#(#path_segments::)*#node_name_identifier::new( - #cycler_module_name_identifier::#(#path_segments::)*CreationContext { - #(#field_initializers,)* - }, - ) - .wrap_err(#error_message)?; - }) - } - - pub fn get_required_inputs_are_some(&self) -> Option { - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - let required_inputs_are_some: Vec<_> = self - .node - .contexts - .cycle_context - .iter() - .filter_map(|field| match field { - Field::RequiredInput { - path, - cycler_instance, - .. - } => { - let database_prefix = match cycler_instance { - Some(cycler_instance) => { - let identifier = - format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); - quote! { #identifier.main_outputs } - } - None => { - quote! { own_database_reference.main_outputs } - } - }; - let accessor = path_to_accessor_token_stream( - database_prefix, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Some(quote! { - #accessor .is_some() - }) - } - _ => None, - }) - .collect(); - match required_inputs_are_some.is_empty() { - true => None, - false => Some(quote! { - #(#required_inputs_are_some)&&* - }), - } - } - - pub fn get_execution_field_initializers(&self) -> Result> { - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - self.node - .contexts - .cycle_context - .iter() - .map(|field| match field { - Field::AdditionalOutput { name, path, .. } => { - let accessor = path_to_accessor_token_stream( - quote! { own_database_reference.additional_outputs }, - path, - ReferenceType::Mutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - let path_string = path_to_path_string_token_stream( - path, - "additional_outputs", - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: framework::AdditionalOutput::new( - own_subscribed_outputs - .iter() - .any(|subscribed_output| framework::should_be_filled(subscribed_output, #path_string)), - #accessor, - ) - }) - } - Field::CyclerInstance { name } => Ok(quote! { - #name: self.instance - }), - Field::HardwareInterface { name } => Ok(quote! { - #name: &self.hardware_interface - }), - Field::HistoricInput { name, path, .. } => { - let now_accessor = path_to_accessor_token_stream( - quote! { own_database_reference.main_outputs }, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - let historic_accessor = path_to_accessor_token_stream( - quote! { database }, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: [(now, #now_accessor)] - .into_iter() - .chain( - self - .historic_databases - .databases - .iter() - .map(|(system_time, database)| ( - *system_time, - #historic_accessor, - )) - ) - .collect::>() - .into() - }) - } - Field::Input { - cycler_instance, - name, - path, - .. - } => { - let database_prefix = match cycler_instance { - Some(cycler_instance) => { - let identifier = - format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); - quote! { #identifier.main_outputs } - } - None => { - quote! { own_database_reference.main_outputs } - } - }; - let accessor = path_to_accessor_token_stream( - database_prefix, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor - }) - } - Field::MainOutput { name, .. } => { - bail!("unexpected main output field `{name}` in cycle context") - } - Field::Parameter { name, path, .. } => { - let accessor = path_to_accessor_token_stream( - quote! { configuration }, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor - }) - } - Field::PerceptionInput { - cycler_instance, - name, - path, - .. - } => { - let cycler_instance_identifier = - format_ident!("{}", cycler_instance.to_case(Case::Snake)); - let accessor = path_to_accessor_token_stream( - quote! { database }, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: framework::PerceptionInput { - persistent: self - .perception_databases - .persistent() - .map(|(system_time, databases)| ( - *system_time, - databases - .#cycler_instance_identifier - .iter() - .map(|database| #accessor) - .collect() - , - )) - .collect(), - temporary: self - .perception_databases - .temporary() - .map(|(system_time, databases)| ( - *system_time, - databases - .#cycler_instance_identifier - .iter() - .map(|database| #accessor) - .collect() - , - )) - .collect(), - } - }) - } - Field::PersistentState { name, path, .. } => { - let accessor = path_to_accessor_token_stream( - quote! { self.persistent_state }, - path, - ReferenceType::Mutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor - }) - } - Field::RequiredInput { - cycler_instance, - name, - path, - .. - } => { - let database_prefix = match cycler_instance { - Some(cycler_instance) => { - let identifier = - format_ident!("{}_database", cycler_instance.to_case(Case::Snake)); - quote! { #identifier.main_outputs } - } - None => { - quote! { own_database_reference.main_outputs } - } - }; - let accessor = path_to_accessor_token_stream( - database_prefix, - path, - ReferenceType::Immutable, - quote! { self.instance }, - quote! { #cycler_module_name_identifier::CyclerInstance:: }, - &self.cycler_instances.modules_to_instances[&self.node.cycler_module], - ); - Ok(quote! { - #name: #accessor .unwrap() - }) - } - }) - .collect() - } - - pub fn get_main_output_setters_from_cycle_result(&self) -> Vec { - self.node - .contexts - .main_outputs - .iter() - .filter_map(|field| match field { - Field::MainOutput { name, .. } => Some(quote! { - own_database_reference.main_outputs.#name = main_outputs.#name.value; - }), - _ => None, - }) - .collect() - } - - pub fn get_main_output_setters_from_default(&self) -> Vec { - self.node - .contexts - .main_outputs - .iter() - .filter_map(|field| match field { - Field::MainOutput { name, .. } => Some(quote! { - own_database_reference.main_outputs.#name = Default::default(); - }), - _ => None, - }) - .collect() - } - - pub fn get_execution(&self) -> Result { - let node_name_identifier_snake_case = self.get_identifier_snake_case(); - let path_segments = self.get_path_segments(); - let cycler_module_name_identifier = format_ident!("{}", self.node.cycler_module); - let required_inputs_are_some = self.get_required_inputs_are_some(); - let field_initializers = self - .get_execution_field_initializers() - .wrap_err("failed to generate field initializers")?; - let main_output_setters_from_cycle_result = - self.get_main_output_setters_from_cycle_result(); - let main_output_setters_from_default = self.get_main_output_setters_from_default(); - let error_message = format!("failed to execute cycle of node `{}`", self.node_name); - let node_name = self.node_name; - let node_execution = quote! { - let main_outputs = { - let _task = ittapi::Task::begin(&itt_domain, #node_name); - self.#node_name_identifier_snake_case.cycle( - #cycler_module_name_identifier::#(#path_segments::)*CycleContext { - #(#field_initializers,)* - }, - ) - .wrap_err(#error_message)? - }; - #(#main_output_setters_from_cycle_result)* - }; - - match required_inputs_are_some { - Some(required_inputs_are_some) => Ok(quote! { - if #required_inputs_are_some { - #node_execution - } else { - #(#main_output_setters_from_default)* - } - }), - None => Ok(quote! { - { - #node_execution - } - }), - } - } -} diff --git a/crates/code_generation/src/other_cycler.rs b/crates/code_generation/src/other_cycler.rs deleted file mode 100644 index c0d2c6b6f8..0000000000 --- a/crates/code_generation/src/other_cycler.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub enum OtherCycler<'a> { - Consumer { - cycler_instance_name: &'a str, - cycler_module_name: &'a str, - }, - Reader { - cycler_instance_name: &'a str, - cycler_module_name: &'a str, - }, -} diff --git a/crates/code_generation/src/path.rs b/crates/code_generation/src/path.rs deleted file mode 100644 index ccb8af0375..0000000000 --- a/crates/code_generation/src/path.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::iter::once; - -use itertools::intersperse; -use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree}; -use quote::{format_ident, TokenStreamExt}; -use source_analyzer::PathSegment; - -pub fn path_to_path_string_token_stream( - path: &[PathSegment], - path_prefix: &str, - instance: TokenStream, - cycler_instance_prefix: TokenStream, - cycler_instances: &[String], -) -> TokenStream { - for segment in path.iter() { - if segment.is_variable && segment.name != "cycler_instance" { - unimplemented!("only $cycler_instance is implemented"); - } - } - let path_contains_variable = path.iter().any(|segment| segment.is_variable); - if path_contains_variable { - let mut token_stream = TokenStream::default(); - token_stream.append(TokenTree::Ident(format_ident!("match"))); - token_stream.extend(instance); - let mut token_stream_within_match = TokenStream::default(); - for cycler_instance in cycler_instances { - token_stream_within_match.extend(cycler_instance_prefix.clone()); - token_stream_within_match.append(format_ident!("{}", cycler_instance)); - token_stream_within_match.append(TokenTree::Punct(Punct::new('=', Spacing::Joint))); - token_stream_within_match.append(TokenTree::Punct(Punct::new('>', Spacing::Alone))); - token_stream_within_match.extend( - path_to_path_string_token_stream_with_cycler_instance( - path, - path_prefix, - Some(cycler_instance), - ), - ); - token_stream_within_match.append(TokenTree::Punct(Punct::new(',', Spacing::Alone))); - } - token_stream.append(TokenTree::Group(Group::new( - Delimiter::Brace, - token_stream_within_match, - ))); - token_stream - } else { - path_to_path_string_token_stream_with_cycler_instance(path, path_prefix, None) - } -} - -fn path_to_path_string_token_stream_with_cycler_instance( - path: &[PathSegment], - path_prefix: &str, - cycler_instance: Option<&str>, -) -> TokenStream { - let path_string: String = intersperse( - once(path_prefix.to_string()).chain(path.iter().map(|segment| match segment.is_variable { - true => cycler_instance.unwrap().to_string(), - false => segment.name.clone(), - })), - ".".to_string(), - ) - .collect(); - TokenTree::Literal(Literal::string(&path_string)).into() -} diff --git a/crates/code_generation/src/reference_type.rs b/crates/code_generation/src/reference_type.rs deleted file mode 100644 index 0a5e563fca..0000000000 --- a/crates/code_generation/src/reference_type.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ReferenceType { - Immutable, - Mutable, -} diff --git a/crates/code_generation/src/run.rs b/crates/code_generation/src/run.rs index e1e52c33b2..edf6d28588 100644 --- a/crates/code_generation/src/run.rs +++ b/crates/code_generation/src/run.rs @@ -3,182 +3,17 @@ use std::iter::repeat; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use source_analyzer::cycler::{CyclerKind, Cyclers}; -use super::{cycler::Cycler, other_cycler::OtherCycler}; +pub fn generate_run_function(cyclers: &Cyclers) -> TokenStream { + let construct_multiple_buffers = generate_multiple_buffers(cyclers); + let construct_future_queues = generate_future_queues(cyclers); + // 2 communication writer slots + n reader slots for other cyclers + let number_of_parameter_slots = 2 + cyclers.number_of_instances(); + let construct_cyclers = generate_cycler_constructors(cyclers); + let start_cyclers = generate_cycler_starts(cyclers); + let join_cyclers = generate_cycler_joins(cyclers); -pub fn generate_run(cyclers: &[Cycler]) -> TokenStream { - let cycler_initializations: Vec<_> = cyclers - .iter() - .flat_map(|cycler| { - cycler.get_cycler_instances().modules_to_instances[cycler.get_cycler_module_name()] - .iter() - .map(|cycler_instance| { - let cycler_instance_snake_case = cycler_instance.to_case(Case::Snake); - let cycler_database_changed_identifier = format_ident!("{}_changed", cycler_instance_snake_case); - let cycler_variable_identifier = format_ident!("{}_cycler", cycler_instance_snake_case); - let cycler_module_name_identifier = cycler.get_cycler_module_name_identifier(); - let cycler_instance_identifier = format_ident!("{}", cycler_instance); - let own_writer_identifier = format_ident!("{}_writer", cycler_instance_snake_case); - let own_reader_identifier = format_ident!("{}_reader", cycler_instance_snake_case); - let own_subscribed_outputs_writer_identifier = format_ident!("{}_subscribed_outputs_writer", cycler_instance_snake_case); - let own_subscribed_outputs_reader_identifier = format_ident!("{}_subscribed_outputs_reader", cycler_instance_snake_case); - let own_producer_identifier = match cycler { - Cycler::Perception { .. } => { - let own_producer_identifier = format_ident!("{}_producer", cycler_instance_snake_case); - quote! { #own_producer_identifier, } - }, - Cycler::RealTime { .. } => Default::default(), - }; - let other_cycler_identifiers: Vec<_> = cycler - .get_other_cyclers() - .into_iter() - .map(|other_cycler| match other_cycler { - OtherCycler::Consumer { - cycler_instance_name, - .. - } => { - let identifier = format_ident!("{}_consumer", cycler_instance_name.to_case(Case::Snake)); - quote! { #identifier } - }, - OtherCycler::Reader { - cycler_instance_name, - .. - } => { - let identifier = format_ident!("{}_reader", cycler_instance_name.to_case(Case::Snake)); - quote! { #identifier.clone() } - }, - }) - .collect(); - let error_message = format!("failed to create cycler `{cycler_instance}`"); - quote! { - let #cycler_database_changed_identifier = std::sync::Arc::new(tokio::sync::Notify::new()); - let (#own_subscribed_outputs_writer_identifier, #own_subscribed_outputs_reader_identifier) = framework::multiple_buffer_with_slots([ - Default::default(), - Default::default(), - Default::default(), - ]); - let #cycler_variable_identifier = #cycler_module_name_identifier::Cycler::new( - ::#cycler_module_name_identifier::CyclerInstance::#cycler_instance_identifier, - hardware_interface.clone(), - #own_writer_identifier, - #own_producer_identifier - #(#other_cycler_identifiers,)* - #cycler_database_changed_identifier.clone(), - #own_subscribed_outputs_reader_identifier, - communication_server.get_parameters_reader(), - ) - .wrap_err(#error_message)?; - communication_server.register_cycler_instance( - #cycler_instance, - #cycler_database_changed_identifier, - #own_reader_identifier.clone(), - #own_subscribed_outputs_writer_identifier, - ); - } - }) - .collect::>() - }) - .collect(); - let amount_of_parameters_slots = 2 + cycler_initializations.len() /* 2 communication writer slots + n reader slots for other cyclers */; - let default_slot_initializers_for_all_cyclers: Vec<_> = repeat(quote! { Default::default() }) - .take(2 + cycler_initializations.len() /* 2 writer slots + n-1 reader slots for other cyclers + 1 reader slot for communication */) - .collect(); - let default_slot_initializers_for_communication: Vec<_> = repeat(quote! { Default::default() }) - .take( - 2 + 1, /* 2 writer slots + 1 reader slot for communication */ - ) - .collect(); - let multiple_buffer_initializers: Vec<_> = cyclers - .iter() - .flat_map(|cycler| { - cycler.get_cycler_instances().modules_to_instances[cycler.get_cycler_module_name()] - .iter() - .map(|cycler_instance| { - let cycler_instance_snake_case = cycler_instance.to_case(Case::Snake); - let writer_identifier = format_ident!("{}_writer", cycler_instance_snake_case); - let reader_identifier = format_ident!("{}_reader", cycler_instance_snake_case); - let slot_initializers = match cycler { - Cycler::Perception { .. } => &default_slot_initializers_for_communication, - Cycler::RealTime { .. } => &default_slot_initializers_for_all_cyclers, - }; - quote! { - let (#writer_identifier, #reader_identifier) = framework::multiple_buffer_with_slots([ - #(#slot_initializers,)* - ]); - } - }) - .collect::>() - }) - .collect(); - let future_queue_initializers: Vec<_> = cyclers - .iter() - .filter_map(|cycler| { - if let Cycler::Perception {..} = cycler { - Some(cycler.get_cycler_instances().modules_to_instances[cycler.get_cycler_module_name()] - .iter() - .map(|cycler_instance| { - let cycler_instance_snake_case = cycler_instance.to_case(Case::Snake); - let producer_identifier = format_ident!("{}_producer", cycler_instance_snake_case); - let consumer_identifier = format_ident!("{}_consumer", cycler_instance_snake_case); - quote! { - let (#producer_identifier, #consumer_identifier) = framework::future_queue(); - } - }) - .collect::>(), - ) - } else { - None - } - }) - .flatten() - .collect(); - let cycler_starts: Vec<_> = cyclers - .iter() - .flat_map(|cycler| { - cycler.get_cycler_instances().modules_to_instances[cycler.get_cycler_module_name()] - .iter() - .map(|cycler_instance| { - let cycler_instance_snake_case = cycler_instance.to_case(Case::Snake); - let cycler_variable_identifier = - format_ident!("{}_cycler", cycler_instance_snake_case); - let cycler_handle_identifier = - format_ident!("{}_handle", cycler_instance_snake_case); - let error_message = format!("failed to start cycler `{cycler_instance}`"); - quote! { - let #cycler_handle_identifier = #cycler_variable_identifier - .start(keep_running.clone()) - .wrap_err(#error_message)?; - } - }) - .collect::>() - }) - .collect(); - let cycler_joins: Vec<_> = cyclers - .iter() - .flat_map(|cycler| { - cycler.get_cycler_instances().modules_to_instances[cycler.get_cycler_module_name()] - .iter() - .map(|cycler_instance| { - let cycler_instance_snake_case = cycler_instance.to_case(Case::Snake); - let cycler_handle_identifier = - format_ident!("{}_handle", cycler_instance_snake_case); - quote! { - match #cycler_handle_identifier.join() { - Ok(Err(error)) => { - encountered_error = true; - println!("{error:?}"); - }, - Err(error) => { - encountered_error = true; - println!("{error:?}"); - }, - _ => {}, - } - } - }) - .collect::>() - }) - .collect(); quote! { #[allow(unused_imports, unused_variables)] pub fn run( @@ -194,19 +29,19 @@ pub fn generate_run(cyclers: &[Cycler]) -> TokenStream { { use color_eyre::eyre::WrapErr; - #(#multiple_buffer_initializers)* - #(#future_queue_initializers)* + #construct_multiple_buffers + #construct_future_queues let communication_server = communication::server::Runtime::start( - addresses, parameters_directory, body_id, head_id, #amount_of_parameters_slots, keep_running.clone()) + addresses, parameters_directory, body_id, head_id, #number_of_parameter_slots, keep_running.clone()) .wrap_err("failed to start communication server")?; - #(#cycler_initializations)* + #construct_cyclers - #(#cycler_starts)* + #start_cyclers let mut encountered_error = false; - #(#cycler_joins)* + #join_cyclers match communication_server.join() { Ok(Err(error)) => { encountered_error = true; @@ -226,3 +61,146 @@ pub fn generate_run(cyclers: &[Cycler]) -> TokenStream { } } } + +fn generate_multiple_buffers(cyclers: &Cyclers) -> TokenStream { + // 2 writer slots + n-1 reader slots for other cyclers + 1 reader slot for communication + let slots_for_real_time_cyclers: TokenStream = repeat(quote! { Default::default(), }) + .take(2 + cyclers.number_of_instances()) + .collect(); + // 2 writer slots + 1 reader slot for communication + let slots_for_perception_cyclers: TokenStream = + repeat(quote! { Default::default(), }).take(2 + 1).collect(); + + cyclers.instances().map(|(cycler, instance)| { + let writer_identifier = format_ident!("{}_writer", instance.name.to_case(Case::Snake)); + let reader_identifier = format_ident!("{}_reader", instance.name.to_case(Case::Snake)); + let slot_initializers = match cycler.kind { + CyclerKind::Perception => &slots_for_perception_cyclers, + CyclerKind::RealTime => &slots_for_real_time_cyclers, + }; + quote! { + let (#writer_identifier, #reader_identifier) = framework::multiple_buffer_with_slots([ + #slot_initializers + ]); + } + }).collect() +} + +fn generate_future_queues(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances_with(CyclerKind::Perception) + .map(|(_cycler, instance)| { + let producer_identifier = + format_ident!("{}_producer", instance.name.to_case(Case::Snake)); + let consumer_identifier = + format_ident!("{}_consumer", instance.name.to_case(Case::Snake)); + quote! { + let (#producer_identifier, #consumer_identifier) = framework::future_queue(); + } + }) + .collect() +} + +fn generate_cycler_constructors(cyclers: &Cyclers) -> TokenStream { + cyclers.instances().map(|(cycler, instance)| { + let instance_name_snake_case = instance.name.to_case(Case::Snake); + let cycler_database_changed_identifier = format_ident!("{instance_name_snake_case}_changed"); + let cycler_variable_identifier = format_ident!("{instance_name_snake_case}_cycler"); + let cycler_module_name_identifier = format_ident!("{}", cycler.module); + let cycler_instance_name = &instance.name; + let cycler_instance_name_identifier = format_ident!("{cycler_instance_name}"); + let own_writer_identifier = format_ident!("{instance_name_snake_case}_writer"); + let own_reader_identifier = format_ident!("{instance_name_snake_case}_reader"); + let own_subscribed_outputs_writer_identifier = format_ident!("{instance_name_snake_case}_subscribed_outputs_writer"); + let own_subscribed_outputs_reader_identifier = format_ident!("{instance_name_snake_case}_subscribed_outputs_reader"); + let own_producer_identifier = match cycler.kind { + CyclerKind::Perception => { + let own_producer_identifier = format_ident!("{instance_name_snake_case}_producer"); + quote! { #own_producer_identifier, } + }, + CyclerKind::RealTime => quote!{}, + }; + let other_cycler_inputs = cyclers.instances_with(match cycler.kind { + CyclerKind::Perception => CyclerKind::RealTime, + CyclerKind::RealTime => CyclerKind::Perception, + }) + .map(|(cycler, instance)| match cycler.kind { + CyclerKind::Perception => { + let identifier = format_ident!("{}_consumer", instance.name.to_case(Case::Snake)); + quote! { #identifier } + }, + CyclerKind::RealTime => { + let identifier = format_ident!("{}_reader", instance.name.to_case(Case::Snake)); + quote! { #identifier.clone() } + }, + }); + let error_message = format!("failed to create cycler `{}`", instance.name); + quote! { + let #cycler_database_changed_identifier = std::sync::Arc::new(tokio::sync::Notify::new()); + let (#own_subscribed_outputs_writer_identifier, #own_subscribed_outputs_reader_identifier) = framework::multiple_buffer_with_slots([ + Default::default(), + Default::default(), + Default::default(), + ]); + let #cycler_variable_identifier = #cycler_module_name_identifier::Cycler::new( + crate::#cycler_module_name_identifier::CyclerInstance::#cycler_instance_name_identifier, + hardware_interface.clone(), + #own_writer_identifier, + #cycler_database_changed_identifier.clone(), + #own_subscribed_outputs_reader_identifier, + communication_server.get_parameters_reader(), + #own_producer_identifier + #(#other_cycler_inputs,)* + ) + .wrap_err(#error_message)?; + communication_server.register_cycler_instance( + #cycler_instance_name, + #cycler_database_changed_identifier, + #own_reader_identifier.clone(), + #own_subscribed_outputs_writer_identifier, + ); + } + }) + .collect() +} + +fn generate_cycler_starts(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances() + .map(|(_cycler, instance)| { + let cycler_variable_identifier = + format_ident!("{}_cycler", instance.name.to_case(Case::Snake)); + let cycler_handle_identifier = + format_ident!("{}_handle", instance.name.to_case(Case::Snake)); + let error_message = format!("failed to start cycler `{}`", instance.name); + quote! { + let #cycler_handle_identifier = #cycler_variable_identifier + .start(keep_running.clone()) + .wrap_err(#error_message)?; + } + }) + .collect() +} + +fn generate_cycler_joins(cyclers: &Cyclers) -> TokenStream { + cyclers + .instances() + .map(|(_cycler, instance)| { + let cycler_handle_identifier = + format_ident!("{}_handle", instance.name.to_case(Case::Snake)); + quote! { + match #cycler_handle_identifier.join() { + Ok(Err(error)) => { + encountered_error = true; + println!("{error:?}"); + }, + Err(error) => { + encountered_error = true; + println!("{error:?}"); + }, + _ => {}, + } + } + }) + .collect() +} diff --git a/crates/code_generation/src/structs.rs b/crates/code_generation/src/structs.rs new file mode 100644 index 0000000000..c83d3e9164 --- /dev/null +++ b/crates/code_generation/src/structs.rs @@ -0,0 +1,67 @@ +use convert_case::{Case, Casing}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use source_analyzer::struct_hierarchy::StructHierarchy; + +pub fn hierarchy_to_token_stream( + hierarchy: &StructHierarchy, + struct_name: Ident, + derives: TokenStream, +) -> TokenStream { + let fields = match hierarchy { + StructHierarchy::Struct { fields } => fields, + StructHierarchy::Optional { .. } => panic!("option instead of struct"), + StructHierarchy::Field { .. } => panic!("field instead of struct"), + }; + let struct_fields = fields.iter().map(|(name, struct_hierarchy)| { + let name_identifier = format_ident!("{}", name); + match struct_hierarchy { + StructHierarchy::Struct { .. } => { + let struct_name_identifier = + format_ident!("{}{}", struct_name, name.to_case(Case::Pascal)); + quote! { pub #name_identifier: #struct_name_identifier } + } + StructHierarchy::Optional { child } => match &**child { + StructHierarchy::Struct { .. } => { + let struct_name_identifier = + format_ident!("{}{}", struct_name, name.to_case(Case::Pascal)); + quote! { pub #name_identifier: Option<#struct_name_identifier> } + } + StructHierarchy::Optional { .. } => { + panic!("unexpected optional in an optional struct") + } + StructHierarchy::Field { data_type } => { + quote! { pub #name_identifier: Option<#data_type> } + } + }, + StructHierarchy::Field { data_type } => { + quote! { pub #name_identifier: #data_type } + } + } + }); + let child_structs = fields.iter().map(|(name, struct_hierarchy)| { + let struct_name = format_ident!("{}{}", struct_name, name.to_case(Case::Pascal)); + match struct_hierarchy { + StructHierarchy::Struct { .. } => { + hierarchy_to_token_stream(struct_hierarchy, struct_name, derives.clone()) + } + StructHierarchy::Optional { child } => match &**child { + StructHierarchy::Struct { .. } => { + hierarchy_to_token_stream(struct_hierarchy, struct_name, derives.clone()) + } + StructHierarchy::Optional { .. } => { + panic!("unexpected optional in an optional struct") + } + StructHierarchy::Field { .. } => quote! {}, + }, + StructHierarchy::Field { .. } => quote! {}, + } + }); + quote! { + #derives + pub struct #struct_name { + #(#struct_fields,)* + } + #(#child_structs)* + } +} diff --git a/crates/control/Cargo.toml b/crates/control/Cargo.toml deleted file mode 100644 index a00ba5584c..0000000000 --- a/crates/control/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "control" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -approx = { workspace = true } -color-eyre = { workspace = true } -context_attribute = { workspace = true } -itertools = { workspace = true } -filtering = { workspace = true } -framework = { workspace = true } -kinematics = { workspace = true } -log = { workspace = true } -nalgebra = { workspace = true } -ordered-float = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serialize_hierarchy = { workspace = true } -smallvec = { workspace = true } -spl_network_messages = { workspace = true } -types = { workspace = true } diff --git a/crates/cyclers/Cargo.toml b/crates/cyclers/Cargo.toml deleted file mode 100644 index 090bc5ecd8..0000000000 --- a/crates/cyclers/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cyclers" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -audio = { workspace = true } -color-eyre = { workspace = true } -control = { workspace = true } -communication = { workspace = true } -framework = { workspace = true } -ittapi = { workspace = true } -spl_network = { workspace = true } -serde = { workspace = true } -serialize_hierarchy = { workspace = true } -structs = { workspace = true } -tokio = { workspace = true } -tokio-util = { workspace = true } -types = { workspace = true } -vision = { workspace = true } - -[build-dependencies] -build_script_helpers = { workspace = true } -code_generation = { workspace = true } -color-eyre = { workspace = true } -quote = { workspace = true } -source_analyzer = { workspace = true } diff --git a/crates/cyclers/build.rs b/crates/cyclers/build.rs deleted file mode 100644 index 69a3bbfc3b..0000000000 --- a/crates/cyclers/build.rs +++ /dev/null @@ -1,73 +0,0 @@ -use build_script_helpers::write_token_stream; -use code_generation::{ - cycler::{generate_cyclers, get_cyclers}, - run::generate_run, -}; -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; -use quote::quote; -use source_analyzer::{ - cycler_crates_from_crates_directory, CyclerInstances, CyclerTypes, Field, Nodes, -}; - -fn main() -> Result<()> { - for crate_directory in cycler_crates_from_crates_directory("..") - .wrap_err("failed to get cycler crate directories from crates directory")? - { - println!("cargo:rerun-if-changed={}", crate_directory.display()); - } - - let cycler_instances = CyclerInstances::try_from_crates_directory("..") - .wrap_err("failed to get cycler instances from crates directory")?; - let mut nodes = Nodes::try_from_crates_directory("..") - .wrap_err("failed to get nodes from crates directory")?; - nodes.sort().wrap_err("failed to sort nodes")?; - let cycler_types = CyclerTypes::try_from_crates_directory("..") - .wrap_err("failed to get perception cycler instances from crates directory")?; - - for node_names in nodes.cycler_modules_to_nodes.values() { - let first_node_name = match node_names.first() { - Some(first_node_name) => first_node_name, - None => continue, - }; - for field in nodes.nodes[first_node_name].contexts.cycle_context.iter() { - match field { - Field::HistoricInput { name, .. } => bail!( - "unexpected historic input for first node `{first_node_name}` in `{}` for `{name}` in cycle context", - nodes.nodes[first_node_name].cycler_module - ), - Field::Input { name, .. } => bail!( - "unexpected optional input for first node `{first_node_name}` in `{}` for `{name}` in cycle context", - nodes.nodes[first_node_name].cycler_module - ), - Field::PerceptionInput { name, .. } => bail!( - "unexpected perception input for first node `{first_node_name}` in `{}` for `{name}` in cycle context", - nodes.nodes[first_node_name].cycler_module - ), - Field::RequiredInput { name, .. } => bail!( - "unexpected required input for first node `{first_node_name}` in `{}` for `{name}` in cycle context", - nodes.nodes[first_node_name].cycler_module - ), - _ => {} - } - } - } - - let cyclers = get_cyclers(&cycler_instances, &nodes, &cycler_types); - - let cyclers_token_stream = generate_cyclers(&cyclers).wrap_err("failed to generate cyclers")?; - let runtime_token_stream = generate_run(&cyclers); - - write_token_stream( - "cyclers.rs", - quote! { - #cyclers_token_stream - #runtime_token_stream - }, - ) - .wrap_err("failed to write cyclers")?; - - Ok(()) -} diff --git a/crates/framework/Cargo.toml b/crates/framework/Cargo.toml index 45cd86dc6c..ff59e4be47 100644 --- a/crates/framework/Cargo.toml +++ b/crates/framework/Cargo.toml @@ -7,11 +7,9 @@ homepage = "https://github.com/hulks/hulk" [dependencies] parking_lot = { workspace = true } -structs = { workspace = true } types = { workspace = true } [build-dependencies] -build_script_helpers = { workspace = true } color-eyre = { workspace = true } convert_case = { workspace = true } proc-macro2 = { workspace = true } diff --git a/crates/framework/build.rs b/crates/framework/build.rs deleted file mode 100644 index 4a2c787711..0000000000 --- a/crates/framework/build.rs +++ /dev/null @@ -1,113 +0,0 @@ -use build_script_helpers::write_token_stream; -use color_eyre::{eyre::WrapErr, Result}; -use convert_case::{Case, Casing}; -use quote::{format_ident, quote}; -use source_analyzer::{ - cycler_crates_from_crates_directory, CyclerInstances, CyclerType, CyclerTypes, -}; - -fn main() -> Result<()> { - for crate_directory in cycler_crates_from_crates_directory("..") - .wrap_err("failed to get cycler crate directories from crates directory")? - { - println!("cargo:rerun-if-changed={}", crate_directory.display()); - } - - let cycler_instances = CyclerInstances::try_from_crates_directory("..") - .wrap_err("failed to get cycler instances from crates directory")?; - let cycler_types = CyclerTypes::try_from_crates_directory("..") - .wrap_err("failed to get perception cycler instances from crates directory")?; - - let updates_fields = cycler_instances.instances_to_modules.iter().filter_map(|(instance_name, module_name)| { - match cycler_types.cycler_modules_to_cycler_types[module_name] { - CyclerType::Perception => { - let field_name_identifier = format_ident!("{}", instance_name.to_case(Case::Snake)); - let module_name_identifier = format_ident!("{}", module_name); - Some(quote! { pub #field_name_identifier: Update }) - }, - CyclerType::RealTime => None, - } - }); - let timestamp_array_items = cycler_instances - .instances_to_modules - .iter() - .filter_map(|(instance_name, module_name)| { - match cycler_types.cycler_modules_to_cycler_types[module_name] { - CyclerType::Perception => { - let field_name_identifier = format_ident!("{}", instance_name.to_case(Case::Snake)); - Some(quote! { self.#field_name_identifier.first_timestamp_of_non_finalized_database }) - }, - CyclerType::RealTime => None, - } - }); - let push_loops = - cycler_instances - .instances_to_modules - .iter() - .filter_map(|(instance_name, module_name)| { - match cycler_types.cycler_modules_to_cycler_types[module_name] { - CyclerType::Perception => { - let field_name_identifier = - format_ident!("{}", instance_name.to_case(Case::Snake)); - Some(quote! { - for timestamped_database in self.#field_name_identifier.items { - databases - .get_mut(×tamped_database.timestamp) - .unwrap() - .#field_name_identifier - .push(timestamped_database.data); - } - }) - } - CyclerType::RealTime => None, - } - }); - let databases_fields = cycler_instances.instances_to_modules.iter().filter_map(|(instance_name, module_name)| { - match cycler_types.cycler_modules_to_cycler_types[module_name] { - CyclerType::Perception => { - let field_name_identifier = format_ident!("{}", instance_name.to_case(Case::Snake)); - let module_name_identifier = format_ident!("{}", module_name); - Some(quote! { pub #field_name_identifier: Vec }) - }, - CyclerType::RealTime => None, - } - }); - - write_token_stream( - "perception_databases_structs.rs", - quote! { - pub struct Updates { - #(#updates_fields,)* - } - - impl Updates { - fn first_timestamp_of_temporary_databases(&self) -> Option { - [ - #(#timestamp_array_items,)* - ] - .iter() - .copied() - .flatten() - .min() - } - - fn push_to_databases(self, databases: &mut BTreeMap) { - #(#push_loops)* - } - } - - pub struct Update { - pub items: Vec>, - pub first_timestamp_of_non_finalized_database: Option, - } - - #[derive(Default)] - pub struct Databases { - #(#databases_fields,)* - } - }, - ) - .wrap_err("failed to write perception databases structs")?; - - Ok(()) -} diff --git a/crates/framework/src/future_queue.rs b/crates/framework/src/future_queue.rs index a0501f5ddf..0fbae165a0 100644 --- a/crates/framework/src/future_queue.rs +++ b/crates/framework/src/future_queue.rs @@ -1,9 +1,7 @@ -use std::{sync::Arc, time::SystemTime}; +use std::{collections::BTreeMap, sync::Arc, time::SystemTime}; use parking_lot::Mutex; -use crate::Update; - struct Slot { timestamp: Option, data: Option, @@ -58,6 +56,16 @@ impl Producer { } } +pub struct Update { + pub items: Vec>, + pub first_timestamp_of_non_finalized_database: Option, +} + +pub trait Updates { + fn first_timestamp_of_temporary_databases(&self) -> Option; + fn push_to_databases(self, databases: &mut BTreeMap); +} + pub struct Consumer { slots: Arc>>>, } diff --git a/crates/framework/src/historic_input.rs b/crates/framework/src/historic_input.rs index 3fbd5473a8..02b0a7e6c7 100644 --- a/crates/framework/src/historic_input.rs +++ b/crates/framework/src/historic_input.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, time::SystemTime}; +#[derive(Debug)] pub struct HistoricInput { historic: BTreeMap, } diff --git a/crates/framework/src/lib.rs b/crates/framework/src/lib.rs index dc648a1629..905fd4a947 100644 --- a/crates/framework/src/lib.rs +++ b/crates/framework/src/lib.rs @@ -8,10 +8,10 @@ mod perception_databases; mod perception_input; pub use additional_output::{should_be_filled, AdditionalOutput}; -pub use future_queue::{future_queue, Consumer, Item, Producer}; +pub use future_queue::{future_queue, Consumer, Item, Producer, Update, Updates}; pub use historic_databases::HistoricDatabases; pub use historic_input::HistoricInput; pub use main_output::MainOutput; pub use multiple_buffer::{multiple_buffer_with_slots, Reader, ReaderGuard, Writer, WriterGuard}; -pub use perception_databases::{Databases, PerceptionDatabases, Update, Updates}; +pub use perception_databases::PerceptionDatabases; pub use perception_input::PerceptionInput; diff --git a/crates/framework/src/perception_databases.rs b/crates/framework/src/perception_databases.rs index b194ad3a5a..c0a753ef62 100644 --- a/crates/framework/src/perception_databases.rs +++ b/crates/framework/src/perception_databases.rs @@ -3,18 +3,19 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use crate::Item; - -include!(concat!(env!("OUT_DIR"), "/perception_databases_structs.rs")); +use crate::future_queue::Updates; #[derive(Default)] -pub struct PerceptionDatabases { +pub struct PerceptionDatabases { databases: BTreeMap, first_timestamp_of_temporary_databases: Option, } -impl PerceptionDatabases { - pub fn update(&mut self, now: SystemTime, updates: Updates) { +impl PerceptionDatabases +where + Databases: Default, +{ + pub fn update(&mut self, now: SystemTime, updates: impl Updates) { if let Some(first_timestamp_of_temporary_databases) = self.first_timestamp_of_temporary_databases { @@ -62,8 +63,76 @@ impl PerceptionDatabases { #[cfg(test)] mod tests { + use crate::{Item, Update}; + use super::*; + #[derive(Default)] + struct MainOutputs {} + + struct Updates { + audio: Update, + spl_network: Update, + vision_top: Update, + vision_bottom: Update, + } + + #[derive(Default)] + struct Databases { + pub vision_top: Vec, + pub vision_bottom: Vec, + pub spl_network: Vec, + pub audio: Vec, + } + + impl crate::Updates for Updates { + fn first_timestamp_of_temporary_databases(&self) -> Option { + [ + self.vision_top.first_timestamp_of_non_finalized_database, + self.vision_bottom.first_timestamp_of_non_finalized_database, + self.spl_network.first_timestamp_of_non_finalized_database, + self.audio.first_timestamp_of_non_finalized_database, + ] + .iter() + .copied() + .flatten() + .min() + } + fn push_to_databases( + self, + databases: &mut std::collections::BTreeMap, + ) { + for timestamped_database in self.vision_top.items { + databases + .get_mut(×tamped_database.timestamp) + .unwrap() + .vision_top + .push(timestamped_database.data); + } + for timestamped_database in self.vision_bottom.items { + databases + .get_mut(×tamped_database.timestamp) + .unwrap() + .vision_bottom + .push(timestamped_database.data); + } + for timestamped_database in self.spl_network.items { + databases + .get_mut(×tamped_database.timestamp) + .unwrap() + .spl_network + .push(timestamped_database.data); + } + for timestamped_database in self.audio.items { + databases + .get_mut(×tamped_database.timestamp) + .unwrap() + .audio + .push(timestamped_database.data); + } + } + } + #[test] fn empty_updates_creates_single_persistent_item() { let mut databases = PerceptionDatabases::default(); @@ -138,7 +207,7 @@ mod tests { first_timestamp_of_non_finalized_database: None, }, vision_top: Update { - items: vec![Item:: { + items: vec![Item:: { timestamp: instant, data: Default::default(), }], diff --git a/crates/hulk/Cargo.toml b/crates/hulk/Cargo.toml index 43cc1390a0..626b84c7b1 100644 --- a/crates/hulk/Cargo.toml +++ b/crates/hulk/Cargo.toml @@ -14,7 +14,6 @@ color-eyre = { workspace = true } constants = { workspace = true } chrono = { workspace = true } ctrlc = { workspace = true } -cyclers = { workspace = true } fern = { workspace = true } i2cdev = { workspace = true } log = { workspace = true } @@ -24,10 +23,33 @@ parking_lot = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -spl_network = { workspace = true } -structs = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } types = { workspace = true } v4l = { optional = true, workspace = true } webots = { optional = true, workspace = true } +kinematics = { workspace = true } +framework = { workspace = true } +context_attribute = { workspace = true } +spl_network_messages = { workspace = true } +filtering = { workspace = true } +thiserror = { workspace = true } +compiled-nn = { workspace = true } +smallvec = { workspace = true } +ordered-float = { workspace = true } +itertools = { workspace = true } +approx = { workspace = true } +serialize_hierarchy = { workspace = true } +rustfft = { workspace = true } +ittapi = { workspace = true } +communication = { workspace = true } + +[build-dependencies] +code_generation = { workspace = true } +color-eyre = { workspace = true } +convert_case = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +source_analyzer = { workspace = true } +toml = { workspace = true } +serde = { workspace = true } diff --git a/crates/hulk/build.rs b/crates/hulk/build.rs new file mode 100644 index 0000000000..6d388cfd4d --- /dev/null +++ b/crates/hulk/build.rs @@ -0,0 +1,193 @@ +use std::{ + env::var, + fs::File, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +use code_generation::{ + cycler::generate_cyclers, run::generate_run_function, structs::hierarchy_to_token_stream, +}; +use color_eyre::{ + eyre::{bail, WrapErr}, + Result, +}; +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use source_analyzer::{ + cycler::{CyclerKind, Cyclers}, + structs::Structs, +}; + +fn main() -> Result<()> { + let cyclers = Cyclers::try_from_directory(".")?; + code_cyclers(&cyclers)?; + code_structs(&cyclers)?; + code_perception_databases_structs(&cyclers)?; + Ok(()) +} + +pub fn write_token_stream(file_name: impl AsRef, token_stream: TokenStream) -> Result<()> { + let file_path = + PathBuf::from(var("OUT_DIR").wrap_err("failed to get environment variable OUT_DIR")?) + .join(file_name); + + { + let mut file = File::create(&file_path) + .wrap_err_with(|| format!("failed create file {file_path:?}"))?; + write!(file, "{token_stream}") + .wrap_err_with(|| format!("failed to write to file {file_path:?}"))?; + } + + let status = Command::new("rustfmt") + .arg(file_path) + .status() + .wrap_err("failed to execute rustfmt")?; + if !status.success() { + bail!("rustfmt did not exit with success"); + } + + Ok(()) +} + +fn code_cyclers(cyclers: &Cyclers) -> Result<()> { + let cyclers_token_stream = generate_cyclers(cyclers).wrap_err("failed to generate cyclers")?; + let runtime_token_stream = generate_run_function(cyclers); + + write_token_stream( + "cyclers.rs", + quote! { + #cyclers_token_stream + #runtime_token_stream + }, + ) + .wrap_err("failed to write cyclers")?; + + Ok(()) +} + +fn code_structs(cyclers: &Cyclers) -> Result<()> { + let structs = Structs::try_from_cyclers(cyclers)?; + + let configuration = hierarchy_to_token_stream( + &structs.configuration, + format_ident!("Configuration"), + quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, + ); + let cyclers = structs + .cyclers + .iter() + .map(|(cycler_module, cycler_structs)| { + let cycler_module_identifier = format_ident!("{}", cycler_module); + let main_outputs = hierarchy_to_token_stream(&cycler_structs.main_outputs,format_ident!("MainOutputs"), quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, ) ; + let additional_outputs = hierarchy_to_token_stream( + &cycler_structs.additional_outputs, + format_ident!("AdditionalOutputs"), + quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, + ) + ; + let persistent_state = hierarchy_to_token_stream(&cycler_structs.persistent_state, + format_ident!("PersistentState"), + quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, + ) + ; + + quote! { + pub mod #cycler_module_identifier { + #main_outputs + #additional_outputs + #persistent_state + } + } + }) + ; + + let token_stream = quote! { + #configuration + #(#cyclers)* + }; + + write_token_stream("structs.rs", token_stream).wrap_err("failed to write structs")?; + + Ok(()) +} + +fn code_perception_databases_structs(cyclers: &Cyclers) -> Result<()> { + let updates_fields = cyclers.instances_with(CyclerKind::Perception).map( + |(cycler, instance)| { + let field_name_identifier = format_ident!("{}", instance.name.to_case(Case::Snake)); + let module_name_identifier = format_ident!("{}", cycler.module); + quote! { + pub #field_name_identifier: framework::Update + } + }, + ); + let timestamp_array_items = + cyclers + .instances_with(CyclerKind::Perception) + .map(|(_cycler, instance)| { + let field_name_identifier = format_ident!("{}", instance.name.to_case(Case::Snake)); + quote! { + self.#field_name_identifier.first_timestamp_of_non_finalized_database + } + }); + let push_loops = cyclers + .instances_with(CyclerKind::Perception) + .map(|(_cycler, instance)| { + let field_name_identifier = format_ident!("{}", instance.name.to_case(Case::Snake)); + quote! { + for timestamped_database in self.#field_name_identifier.items { + databases + .get_mut(×tamped_database.timestamp) + .unwrap() + .#field_name_identifier + .push(timestamped_database.data); + } + } + }); + let databases_fields = + cyclers + .instances_with(CyclerKind::Perception) + .map(|(cycler, instance)| { + let field_name_identifier = format_ident!("{}", instance.name.to_case(Case::Snake)); + let module_name_identifier = format_ident!("{}", cycler.module); + quote! { + pub #field_name_identifier: Vec + } + }); + + write_token_stream( + "perception_databases.rs", + quote! { + pub struct Updates { + #(#updates_fields,)* + } + + impl framework::Updates for Updates { + fn first_timestamp_of_temporary_databases(&self) -> Option { + [ + #(#timestamp_array_items,)* + ] + .iter() + .copied() + .flatten() + .min() + } + + fn push_to_databases(self, databases: &mut std::collections::BTreeMap) { + #(#push_loops)* + } + } + + #[derive(Default)] + pub struct Databases { + #(#databases_fields,)* + } + }, + ) + .wrap_err("failed to write perception databases structs")?; + + Ok(()) +} diff --git a/crates/hulk/framework.toml b/crates/hulk/framework.toml new file mode 100644 index 0000000000..9079c2fc35 --- /dev/null +++ b/crates/hulk/framework.toml @@ -0,0 +1,84 @@ +[[cyclers]] +name = "Vision" +kind = "Perception" +instances = ["Top", "Bottom"] +module = "vision" +nodes = [ + { module = "image_receiver", is_setup = true }, + { module = "ball_detection" }, + { module = "field_border_detection" }, + { module = "line_detection" }, + { module = "perspective_grid_candidates_provider" }, + { module = "robot_detection" }, + { module = "camera_matrix_extractor" }, + { module = "field_color_detection" }, + { module = "image_segmenter" }, + { module = "segment_filter" }, +] + +[[cyclers]] +name = "Control" +kind = "RealTime" +module = "control" +nodes = [ + { module = "sensor_data_receiver", is_setup = true }, + { module = "behavior::node" }, + { module = "ball_filter" }, + { module = "button_filter" }, + { module = "camera_matrix_calculator" }, + { module = "center_of_mass_provider" }, + { module = "fall_state_estimation" }, + { module = "game_controller_filter" }, + { module = "game_state_filter" }, + { module = "ground_contact_detector" }, + { module = "ground_provider" }, + { module = "kinematics_provider" }, + { module = "led_status" }, + { module = "limb_projector" }, + { module = "localization" }, + { module = "obstacle_filter" }, + { module = "odometry" }, + { module = "orientation_filter" }, + { module = "penalty_shot_direction_estimation" }, + { module = "primary_state_filter" }, + { module = "role_assignment" }, + { module = "sole_pressure_filter" }, + { module = "sonar_filter" }, + { module = "support_foot_estimation" }, + { module = "whistle_filter" }, + { module = "world_state_composer" }, + + { module = "motion::arms_up_squat" }, + { module = "motion::dispatching_interpolator" }, + { module = "motion::fall_protector" }, + { module = "motion::head_motion" }, + { module = "motion::joint_command_sender" }, + { module = "motion::jump_left" }, + { module = "motion::jump_right" }, + { module = "motion::look_around" }, + { module = "motion::look_at" }, + { module = "motion::motion_selector" }, + { module = "motion::sit_down" }, + { module = "motion::stand_up_back" }, + { module = "motion::stand_up_front" }, + { module = "motion::step_planner" }, + { module = "motion::walk_manager" }, + { module = "motion::walking_engine" }, +] + +[[cyclers]] +name = "SplNetwork" +kind = "Perception" +module = "spl_network" +nodes = [ + { module = "message_receiver", is_setup = true }, +] + +[[cyclers]] +name = "Audio" +kind = "Perception" +module = "audio" +nodes = [ + { module = "microphone_recorder", is_setup = true }, + { module = "whistle_detection" }, +] diff --git a/crates/audio/src/microphone_recorder.rs b/crates/hulk/src/audio/microphone_recorder.rs similarity index 100% rename from crates/audio/src/microphone_recorder.rs rename to crates/hulk/src/audio/microphone_recorder.rs diff --git a/crates/hulk/src/audio/mod.rs b/crates/hulk/src/audio/mod.rs new file mode 100644 index 0000000000..521bbfadae --- /dev/null +++ b/crates/hulk/src/audio/mod.rs @@ -0,0 +1,4 @@ +pub mod microphone_recorder; +pub mod whistle_detection; + +pub use super::cyclers::audio::CyclerInstance; diff --git a/crates/audio/src/whistle_detection.rs b/crates/hulk/src/audio/whistle_detection.rs similarity index 99% rename from crates/audio/src/whistle_detection.rs rename to crates/hulk/src/audio/whistle_detection.rs index 7adef812b5..f1cff1bf8c 100644 --- a/crates/audio/src/whistle_detection.rs +++ b/crates/hulk/src/audio/whistle_detection.rs @@ -15,7 +15,6 @@ use types::{ }; pub const AUDIO_SAMPLE_RATE: u32 = 44100; -pub const NUMBER_OF_AUDIO_CHANNELS: usize = 4; pub const NUMBER_OF_AUDIO_SAMPLES: usize = 2048; const NUMBER_OF_FREQUENCY_SAMPLES: usize = NUMBER_OF_AUDIO_SAMPLES / 2; diff --git a/crates/hulk/src/bin/nao.rs b/crates/hulk/src/bin/nao.rs index 470c2adc75..e0f40fdb82 100644 --- a/crates/hulk/src/bin/nao.rs +++ b/crates/hulk/src/bin/nao.rs @@ -4,8 +4,7 @@ use color_eyre::{ eyre::{Result, WrapErr}, install, }; -use cyclers::run; -use hulk::{nao, setup_logger}; +use hulk::{cyclers::run, nao, setup_logger}; use serde_json::from_reader; use tokio_util::sync::CancellationToken; use types::hardware::Interface; diff --git a/crates/hulk/src/bin/webots.rs b/crates/hulk/src/bin/webots.rs index 28376eb578..c031df71a2 100644 --- a/crates/hulk/src/bin/webots.rs +++ b/crates/hulk/src/bin/webots.rs @@ -4,8 +4,7 @@ use color_eyre::{ eyre::{Result, WrapErr}, install, }; -use cyclers::run; -use hulk::{setup_logger, webots}; +use hulk::{cyclers::run, setup_logger, webots}; use serde_json::from_reader; use tokio_util::sync::CancellationToken; use types::hardware::Interface; diff --git a/crates/control/src/a_star.rs b/crates/hulk/src/control/a_star.rs similarity index 100% rename from crates/control/src/a_star.rs rename to crates/hulk/src/control/a_star.rs diff --git a/crates/control/src/ball_filter.rs b/crates/hulk/src/control/ball_filter.rs similarity index 100% rename from crates/control/src/ball_filter.rs rename to crates/hulk/src/control/ball_filter.rs diff --git a/crates/control/src/behavior/action.rs b/crates/hulk/src/control/behavior/action.rs similarity index 100% rename from crates/control/src/behavior/action.rs rename to crates/hulk/src/control/behavior/action.rs diff --git a/crates/control/src/behavior/defend.rs b/crates/hulk/src/control/behavior/defend.rs similarity index 100% rename from crates/control/src/behavior/defend.rs rename to crates/hulk/src/control/behavior/defend.rs diff --git a/crates/control/src/behavior/dribble.rs b/crates/hulk/src/control/behavior/dribble.rs similarity index 100% rename from crates/control/src/behavior/dribble.rs rename to crates/hulk/src/control/behavior/dribble.rs diff --git a/crates/control/src/behavior/fall_safely.rs b/crates/hulk/src/control/behavior/fall_safely.rs similarity index 100% rename from crates/control/src/behavior/fall_safely.rs rename to crates/hulk/src/control/behavior/fall_safely.rs diff --git a/crates/control/src/behavior/head.rs b/crates/hulk/src/control/behavior/head.rs similarity index 100% rename from crates/control/src/behavior/head.rs rename to crates/hulk/src/control/behavior/head.rs diff --git a/crates/control/src/behavior/jump.rs b/crates/hulk/src/control/behavior/jump.rs similarity index 100% rename from crates/control/src/behavior/jump.rs rename to crates/hulk/src/control/behavior/jump.rs diff --git a/crates/control/src/behavior/lost_ball.rs b/crates/hulk/src/control/behavior/lost_ball.rs similarity index 100% rename from crates/control/src/behavior/lost_ball.rs rename to crates/hulk/src/control/behavior/lost_ball.rs diff --git a/crates/control/src/behavior/mod.rs b/crates/hulk/src/control/behavior/mod.rs similarity index 100% rename from crates/control/src/behavior/mod.rs rename to crates/hulk/src/control/behavior/mod.rs diff --git a/crates/control/src/behavior/node.rs b/crates/hulk/src/control/behavior/node.rs similarity index 100% rename from crates/control/src/behavior/node.rs rename to crates/hulk/src/control/behavior/node.rs diff --git a/crates/control/src/behavior/penalize.rs b/crates/hulk/src/control/behavior/penalize.rs similarity index 100% rename from crates/control/src/behavior/penalize.rs rename to crates/hulk/src/control/behavior/penalize.rs diff --git a/crates/control/src/behavior/prepare_jump.rs b/crates/hulk/src/control/behavior/prepare_jump.rs similarity index 100% rename from crates/control/src/behavior/prepare_jump.rs rename to crates/hulk/src/control/behavior/prepare_jump.rs diff --git a/crates/control/src/behavior/search.rs b/crates/hulk/src/control/behavior/search.rs similarity index 100% rename from crates/control/src/behavior/search.rs rename to crates/hulk/src/control/behavior/search.rs diff --git a/crates/control/src/behavior/sit_down.rs b/crates/hulk/src/control/behavior/sit_down.rs similarity index 100% rename from crates/control/src/behavior/sit_down.rs rename to crates/hulk/src/control/behavior/sit_down.rs diff --git a/crates/control/src/behavior/stand.rs b/crates/hulk/src/control/behavior/stand.rs similarity index 100% rename from crates/control/src/behavior/stand.rs rename to crates/hulk/src/control/behavior/stand.rs diff --git a/crates/control/src/behavior/stand_up.rs b/crates/hulk/src/control/behavior/stand_up.rs similarity index 100% rename from crates/control/src/behavior/stand_up.rs rename to crates/hulk/src/control/behavior/stand_up.rs diff --git a/crates/control/src/behavior/support_striker.rs b/crates/hulk/src/control/behavior/support_striker.rs similarity index 100% rename from crates/control/src/behavior/support_striker.rs rename to crates/hulk/src/control/behavior/support_striker.rs diff --git a/crates/control/src/behavior/unstiff.rs b/crates/hulk/src/control/behavior/unstiff.rs similarity index 100% rename from crates/control/src/behavior/unstiff.rs rename to crates/hulk/src/control/behavior/unstiff.rs diff --git a/crates/control/src/behavior/walk_to_kick_off.rs b/crates/hulk/src/control/behavior/walk_to_kick_off.rs similarity index 100% rename from crates/control/src/behavior/walk_to_kick_off.rs rename to crates/hulk/src/control/behavior/walk_to_kick_off.rs diff --git a/crates/control/src/behavior/walk_to_pose.rs b/crates/hulk/src/control/behavior/walk_to_pose.rs similarity index 99% rename from crates/control/src/behavior/walk_to_pose.rs rename to crates/hulk/src/control/behavior/walk_to_pose.rs index 7b35a9d832..39974c600c 100644 --- a/crates/control/src/behavior/walk_to_pose.rs +++ b/crates/hulk/src/control/behavior/walk_to_pose.rs @@ -9,7 +9,7 @@ use types::{ PathObstacle, PathSegment, Side, WorldState, }; -use crate::path_planner::PathPlanner; +use crate::control::path_planner::PathPlanner; pub struct WalkPathPlanner<'cycle> { field_dimensions: &'cycle FieldDimensions, diff --git a/crates/control/src/button_filter.rs b/crates/hulk/src/control/button_filter.rs similarity index 100% rename from crates/control/src/button_filter.rs rename to crates/hulk/src/control/button_filter.rs diff --git a/crates/control/src/camera_matrix_calculator.rs b/crates/hulk/src/control/camera_matrix_calculator.rs similarity index 100% rename from crates/control/src/camera_matrix_calculator.rs rename to crates/hulk/src/control/camera_matrix_calculator.rs diff --git a/crates/control/src/center_of_mass_provider.rs b/crates/hulk/src/control/center_of_mass_provider.rs similarity index 100% rename from crates/control/src/center_of_mass_provider.rs rename to crates/hulk/src/control/center_of_mass_provider.rs diff --git a/crates/control/src/fall_state_estimation.rs b/crates/hulk/src/control/fall_state_estimation.rs similarity index 100% rename from crates/control/src/fall_state_estimation.rs rename to crates/hulk/src/control/fall_state_estimation.rs diff --git a/crates/control/src/game_controller_filter.rs b/crates/hulk/src/control/game_controller_filter.rs similarity index 100% rename from crates/control/src/game_controller_filter.rs rename to crates/hulk/src/control/game_controller_filter.rs diff --git a/crates/control/src/game_state_filter.rs b/crates/hulk/src/control/game_state_filter.rs similarity index 100% rename from crates/control/src/game_state_filter.rs rename to crates/hulk/src/control/game_state_filter.rs diff --git a/crates/control/src/ground_contact_detector.rs b/crates/hulk/src/control/ground_contact_detector.rs similarity index 100% rename from crates/control/src/ground_contact_detector.rs rename to crates/hulk/src/control/ground_contact_detector.rs diff --git a/crates/control/src/ground_provider.rs b/crates/hulk/src/control/ground_provider.rs similarity index 100% rename from crates/control/src/ground_provider.rs rename to crates/hulk/src/control/ground_provider.rs diff --git a/crates/control/src/kinematics_provider.rs b/crates/hulk/src/control/kinematics_provider.rs similarity index 100% rename from crates/control/src/kinematics_provider.rs rename to crates/hulk/src/control/kinematics_provider.rs diff --git a/crates/control/src/led_status.rs b/crates/hulk/src/control/led_status.rs similarity index 100% rename from crates/control/src/led_status.rs rename to crates/hulk/src/control/led_status.rs diff --git a/crates/control/src/limb_projector.rs b/crates/hulk/src/control/limb_projector.rs similarity index 100% rename from crates/control/src/limb_projector.rs rename to crates/hulk/src/control/limb_projector.rs diff --git a/crates/control/src/localization.rs b/crates/hulk/src/control/localization.rs similarity index 100% rename from crates/control/src/localization.rs rename to crates/hulk/src/control/localization.rs diff --git a/crates/control/src/lib.rs b/crates/hulk/src/control/mod.rs similarity index 91% rename from crates/control/src/lib.rs rename to crates/hulk/src/control/mod.rs index 0bb395f357..f2dee396a6 100644 --- a/crates/control/src/lib.rs +++ b/crates/hulk/src/control/mod.rs @@ -28,7 +28,6 @@ pub mod support_foot_estimation; pub mod whistle_filter; pub mod world_state_composer; -#[derive(Clone, Copy, Debug)] -pub enum CyclerInstance { - Control, -} +pub use motion::*; + +pub use super::cyclers::control::CyclerInstance; diff --git a/crates/control/src/motion/arms_up_squat.rs b/crates/hulk/src/control/motion/arms_up_squat.rs similarity index 100% rename from crates/control/src/motion/arms_up_squat.rs rename to crates/hulk/src/control/motion/arms_up_squat.rs diff --git a/crates/control/src/motion/dispatching_interpolator.rs b/crates/hulk/src/control/motion/dispatching_interpolator.rs similarity index 100% rename from crates/control/src/motion/dispatching_interpolator.rs rename to crates/hulk/src/control/motion/dispatching_interpolator.rs diff --git a/crates/control/src/motion/fall_protector.rs b/crates/hulk/src/control/motion/fall_protector.rs similarity index 100% rename from crates/control/src/motion/fall_protector.rs rename to crates/hulk/src/control/motion/fall_protector.rs diff --git a/crates/control/src/motion/head_motion.rs b/crates/hulk/src/control/motion/head_motion.rs similarity index 100% rename from crates/control/src/motion/head_motion.rs rename to crates/hulk/src/control/motion/head_motion.rs diff --git a/crates/control/src/motion/joint_command_sender.rs b/crates/hulk/src/control/motion/joint_command_sender.rs similarity index 100% rename from crates/control/src/motion/joint_command_sender.rs rename to crates/hulk/src/control/motion/joint_command_sender.rs diff --git a/crates/control/src/motion/jump_left.rs b/crates/hulk/src/control/motion/jump_left.rs similarity index 100% rename from crates/control/src/motion/jump_left.rs rename to crates/hulk/src/control/motion/jump_left.rs diff --git a/crates/control/src/motion/jump_right.rs b/crates/hulk/src/control/motion/jump_right.rs similarity index 100% rename from crates/control/src/motion/jump_right.rs rename to crates/hulk/src/control/motion/jump_right.rs diff --git a/crates/control/src/motion/look_around.rs b/crates/hulk/src/control/motion/look_around.rs similarity index 100% rename from crates/control/src/motion/look_around.rs rename to crates/hulk/src/control/motion/look_around.rs diff --git a/crates/control/src/motion/look_at.rs b/crates/hulk/src/control/motion/look_at.rs similarity index 100% rename from crates/control/src/motion/look_at.rs rename to crates/hulk/src/control/motion/look_at.rs diff --git a/crates/control/src/motion/mod.rs b/crates/hulk/src/control/motion/mod.rs similarity index 100% rename from crates/control/src/motion/mod.rs rename to crates/hulk/src/control/motion/mod.rs diff --git a/crates/control/src/motion/motion_selector.rs b/crates/hulk/src/control/motion/motion_selector.rs similarity index 100% rename from crates/control/src/motion/motion_selector.rs rename to crates/hulk/src/control/motion/motion_selector.rs diff --git a/crates/control/src/motion/sit_down.rs b/crates/hulk/src/control/motion/sit_down.rs similarity index 100% rename from crates/control/src/motion/sit_down.rs rename to crates/hulk/src/control/motion/sit_down.rs diff --git a/crates/control/src/motion/stand_up_back.rs b/crates/hulk/src/control/motion/stand_up_back.rs similarity index 100% rename from crates/control/src/motion/stand_up_back.rs rename to crates/hulk/src/control/motion/stand_up_back.rs diff --git a/crates/control/src/motion/stand_up_front.rs b/crates/hulk/src/control/motion/stand_up_front.rs similarity index 100% rename from crates/control/src/motion/stand_up_front.rs rename to crates/hulk/src/control/motion/stand_up_front.rs diff --git a/crates/control/src/motion/step_planner.rs b/crates/hulk/src/control/motion/step_planner.rs similarity index 100% rename from crates/control/src/motion/step_planner.rs rename to crates/hulk/src/control/motion/step_planner.rs diff --git a/crates/control/src/motion/walk_manager.rs b/crates/hulk/src/control/motion/walk_manager.rs similarity index 100% rename from crates/control/src/motion/walk_manager.rs rename to crates/hulk/src/control/motion/walk_manager.rs diff --git a/crates/control/src/motion/walking_engine.rs b/crates/hulk/src/control/motion/walking_engine.rs similarity index 100% rename from crates/control/src/motion/walking_engine.rs rename to crates/hulk/src/control/motion/walking_engine.rs diff --git a/crates/control/src/motion/walking_engine/arms.rs b/crates/hulk/src/control/motion/walking_engine/arms.rs similarity index 100% rename from crates/control/src/motion/walking_engine/arms.rs rename to crates/hulk/src/control/motion/walking_engine/arms.rs diff --git a/crates/control/src/motion/walking_engine/balancing.rs b/crates/hulk/src/control/motion/walking_engine/balancing.rs similarity index 100% rename from crates/control/src/motion/walking_engine/balancing.rs rename to crates/hulk/src/control/motion/walking_engine/balancing.rs diff --git a/crates/control/src/motion/walking_engine/engine.rs b/crates/hulk/src/control/motion/walking_engine/engine.rs similarity index 100% rename from crates/control/src/motion/walking_engine/engine.rs rename to crates/hulk/src/control/motion/walking_engine/engine.rs diff --git a/crates/control/src/motion/walking_engine/foot_offsets.rs b/crates/hulk/src/control/motion/walking_engine/foot_offsets.rs similarity index 100% rename from crates/control/src/motion/walking_engine/foot_offsets.rs rename to crates/hulk/src/control/motion/walking_engine/foot_offsets.rs diff --git a/crates/control/src/motion/walking_engine/kicking.rs b/crates/hulk/src/control/motion/walking_engine/kicking.rs similarity index 100% rename from crates/control/src/motion/walking_engine/kicking.rs rename to crates/hulk/src/control/motion/walking_engine/kicking.rs diff --git a/crates/control/src/motion/walking_engine/walk_state.rs b/crates/hulk/src/control/motion/walking_engine/walk_state.rs similarity index 100% rename from crates/control/src/motion/walking_engine/walk_state.rs rename to crates/hulk/src/control/motion/walking_engine/walk_state.rs diff --git a/crates/control/src/obstacle_filter.rs b/crates/hulk/src/control/obstacle_filter.rs similarity index 99% rename from crates/control/src/obstacle_filter.rs rename to crates/hulk/src/control/obstacle_filter.rs index 6536d56446..635103e5ac 100644 --- a/crates/control/src/obstacle_filter.rs +++ b/crates/hulk/src/control/obstacle_filter.rs @@ -29,7 +29,7 @@ pub struct CycleContext { pub current_odometry_to_last_odometry: HistoricInput>, "current_odometry_to_last_odometry?">, - pub network_robot_obstacles: HistoricInput>, "network_robot_obstacles?">, + pub network_robot_obstacles: HistoricInput>, "network_robot_obstacles">, pub robot_to_field: HistoricInput>, "robot_to_field?">, pub sonar_obstacles: HistoricInput, "sonar_obstacles">, diff --git a/crates/control/src/odometry.rs b/crates/hulk/src/control/odometry.rs similarity index 100% rename from crates/control/src/odometry.rs rename to crates/hulk/src/control/odometry.rs diff --git a/crates/control/src/orientation_filter.rs b/crates/hulk/src/control/orientation_filter.rs similarity index 100% rename from crates/control/src/orientation_filter.rs rename to crates/hulk/src/control/orientation_filter.rs diff --git a/crates/control/src/path_planner.rs b/crates/hulk/src/control/path_planner.rs similarity index 99% rename from crates/control/src/path_planner.rs rename to crates/hulk/src/control/path_planner.rs index ebecfcf535..31f64658ad 100644 --- a/crates/control/src/path_planner.rs +++ b/crates/hulk/src/control/path_planner.rs @@ -8,7 +8,7 @@ use types::{ PathObstacleShape, PathSegment, }; -use crate::a_star::{a_star_search, DynamicMap}; +use super::a_star::{a_star_search, DynamicMap}; #[derive(Debug, Clone)] pub struct PathNode { diff --git a/crates/control/src/penalty_shot_direction_estimation.rs b/crates/hulk/src/control/penalty_shot_direction_estimation.rs similarity index 100% rename from crates/control/src/penalty_shot_direction_estimation.rs rename to crates/hulk/src/control/penalty_shot_direction_estimation.rs diff --git a/crates/control/src/primary_state_filter.rs b/crates/hulk/src/control/primary_state_filter.rs similarity index 100% rename from crates/control/src/primary_state_filter.rs rename to crates/hulk/src/control/primary_state_filter.rs diff --git a/crates/control/src/role_assignment.rs b/crates/hulk/src/control/role_assignment.rs similarity index 100% rename from crates/control/src/role_assignment.rs rename to crates/hulk/src/control/role_assignment.rs diff --git a/crates/control/src/sensor_data_receiver.rs b/crates/hulk/src/control/sensor_data_receiver.rs similarity index 100% rename from crates/control/src/sensor_data_receiver.rs rename to crates/hulk/src/control/sensor_data_receiver.rs diff --git a/crates/control/src/sole_pressure_filter.rs b/crates/hulk/src/control/sole_pressure_filter.rs similarity index 100% rename from crates/control/src/sole_pressure_filter.rs rename to crates/hulk/src/control/sole_pressure_filter.rs diff --git a/crates/control/src/sonar_filter.rs b/crates/hulk/src/control/sonar_filter.rs similarity index 100% rename from crates/control/src/sonar_filter.rs rename to crates/hulk/src/control/sonar_filter.rs diff --git a/crates/control/src/support_foot_estimation.rs b/crates/hulk/src/control/support_foot_estimation.rs similarity index 100% rename from crates/control/src/support_foot_estimation.rs rename to crates/hulk/src/control/support_foot_estimation.rs diff --git a/crates/control/src/whistle_filter.rs b/crates/hulk/src/control/whistle_filter.rs similarity index 100% rename from crates/control/src/whistle_filter.rs rename to crates/hulk/src/control/whistle_filter.rs diff --git a/crates/control/src/world_state_composer.rs b/crates/hulk/src/control/world_state_composer.rs similarity index 100% rename from crates/control/src/world_state_composer.rs rename to crates/hulk/src/control/world_state_composer.rs diff --git a/crates/cyclers/src/lib.rs b/crates/hulk/src/cyclers.rs similarity index 61% rename from crates/cyclers/src/lib.rs rename to crates/hulk/src/cyclers.rs index 2c2522d82d..5395de6810 100644 --- a/crates/cyclers/src/lib.rs +++ b/crates/hulk/src/cyclers.rs @@ -1,4 +1,5 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::redundant_clone)] - +#![allow(clippy::needless_question_mark)] +#![allow(clippy::nonminimal_bool)] include!(concat!(env!("OUT_DIR"), "/cyclers.rs")); diff --git a/crates/hulk/src/lib.rs b/crates/hulk/src/lib.rs index 8196817fda..1c06563490 100644 --- a/crates/hulk/src/lib.rs +++ b/crates/hulk/src/lib.rs @@ -1,9 +1,17 @@ +#![recursion_limit = "256"] use std::io::stdout; use color_eyre::eyre::Result; +mod audio; +mod control; +pub mod cyclers; #[cfg(feature = "nao")] pub mod nao; +mod perception_databases; +mod spl_network; +mod structs; +mod vision; #[cfg(feature = "webots")] pub mod webots; diff --git a/crates/hulk/src/nao/interface.rs b/crates/hulk/src/nao/interface.rs index 34313d4495..9e95d3ac4b 100644 --- a/crates/hulk/src/nao/interface.rs +++ b/crates/hulk/src/nao/interface.rs @@ -6,7 +6,6 @@ use color_eyre::{ }; use parking_lot::Mutex; use serde::Deserialize; -use spl_network::endpoint::{Endpoint, Ports}; use tokio::{ runtime::{Builder, Runtime}, select, @@ -20,6 +19,8 @@ use types::{ CameraPosition, Joints, Leds, SensorData, }; +use crate::spl_network::endpoint::{Endpoint, Ports}; + use super::{ camera::Camera, hula_wrapper::HulaWrapper, diff --git a/crates/hulk/src/perception_databases.rs b/crates/hulk/src/perception_databases.rs new file mode 100644 index 0000000000..3357291673 --- /dev/null +++ b/crates/hulk/src/perception_databases.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/perception_databases.rs")); diff --git a/crates/spl_network/src/endpoint.rs b/crates/hulk/src/spl_network/endpoint.rs similarity index 98% rename from crates/spl_network/src/endpoint.rs rename to crates/hulk/src/spl_network/endpoint.rs index ed8324475e..5cf6dbada6 100644 --- a/crates/spl_network/src/endpoint.rs +++ b/crates/hulk/src/spl_network/endpoint.rs @@ -23,7 +23,7 @@ pub enum Error { #[error("failed to enable broadcast socket option")] EnableBroadcast(io::Error), #[error("failed to read from socket")] - ReadError(io::Error), + Read(io::Error), } impl Endpoint { @@ -54,7 +54,7 @@ impl Endpoint { let mut spl_buffer = [0; 1024]; select! { result = self.game_controller_state_socket.recv_from(&mut game_controller_state_buffer) => { - let (received_bytes, address) = result.map_err(Error::ReadError)?; + let (received_bytes, address) = result.map_err(Error::Read)?; match game_controller_state_buffer[0..received_bytes].try_into() { Ok(parsed_message) => { *self.last_game_controller_address.lock().await = Some(address); @@ -67,7 +67,7 @@ impl Endpoint { } }, result = self.spl_socket.recv_from(&mut spl_buffer) => { - let (received_bytes, _address) = result.map_err(Error::ReadError)?; + let (received_bytes, _address) = result.map_err(Error::Read)?; match spl_buffer[0..received_bytes].try_into() { Ok(parsed_message) => { break Ok(IncomingMessage::Spl(parsed_message)); diff --git a/crates/spl_network/src/message_receiver.rs b/crates/hulk/src/spl_network/message_receiver.rs similarity index 100% rename from crates/spl_network/src/message_receiver.rs rename to crates/hulk/src/spl_network/message_receiver.rs diff --git a/crates/hulk/src/spl_network/mod.rs b/crates/hulk/src/spl_network/mod.rs new file mode 100644 index 0000000000..8966b40647 --- /dev/null +++ b/crates/hulk/src/spl_network/mod.rs @@ -0,0 +1,4 @@ +pub mod endpoint; +pub mod message_receiver; + +pub use super::cyclers::spl_network::CyclerInstance; diff --git a/crates/structs/src/lib.rs b/crates/hulk/src/structs.rs similarity index 63% rename from crates/structs/src/lib.rs rename to crates/hulk/src/structs.rs index aaf36bd514..961a9751f4 100644 --- a/crates/structs/src/lib.rs +++ b/crates/hulk/src/structs.rs @@ -1,3 +1 @@ -#![recursion_limit = "256"] - include!(concat!(env!("OUT_DIR"), "/structs.rs")); diff --git a/crates/vision/src/ball_detection.rs b/crates/hulk/src/vision/ball_detection.rs similarity index 100% rename from crates/vision/src/ball_detection.rs rename to crates/hulk/src/vision/ball_detection.rs diff --git a/crates/vision/src/camera_matrix_extractor.rs b/crates/hulk/src/vision/camera_matrix_extractor.rs similarity index 97% rename from crates/vision/src/camera_matrix_extractor.rs rename to crates/hulk/src/vision/camera_matrix_extractor.rs index 79324d2a31..7d075c014d 100644 --- a/crates/vision/src/camera_matrix_extractor.rs +++ b/crates/hulk/src/vision/camera_matrix_extractor.rs @@ -3,7 +3,7 @@ use context_attribute::context; use framework::MainOutput; use types::{image::Image, CameraMatrices, CameraMatrix}; -use crate::CyclerInstance; +use super::CyclerInstance; pub struct CameraMatrixExtractor {} diff --git a/crates/vision/src/field_border_detection.rs b/crates/hulk/src/vision/field_border_detection.rs similarity index 99% rename from crates/vision/src/field_border_detection.rs rename to crates/hulk/src/vision/field_border_detection.rs index 5efecf2688..90b8a75a6a 100644 --- a/crates/vision/src/field_border_detection.rs +++ b/crates/hulk/src/vision/field_border_detection.rs @@ -4,7 +4,7 @@ use framework::{AdditionalOutput, MainOutput}; use nalgebra::{point, Point2, Vector2}; use types::{CameraMatrix, FieldBorder, Horizon, ImageSegments, Intensity, Line, Line2, Segment}; -use crate::{ransac::Ransac, CyclerInstance}; +use super::{ransac::Ransac, CyclerInstance}; pub struct FieldBorderDetection {} diff --git a/crates/vision/src/field_color_detection.rs b/crates/hulk/src/vision/field_color_detection.rs similarity index 100% rename from crates/vision/src/field_color_detection.rs rename to crates/hulk/src/vision/field_color_detection.rs diff --git a/crates/vision/src/image_receiver.rs b/crates/hulk/src/vision/image_receiver.rs similarity index 97% rename from crates/vision/src/image_receiver.rs rename to crates/hulk/src/vision/image_receiver.rs index b5db871c99..ebfb3e26ca 100644 --- a/crates/vision/src/image_receiver.rs +++ b/crates/hulk/src/vision/image_receiver.rs @@ -3,7 +3,7 @@ use context_attribute::context; use framework::MainOutput; use types::{hardware::Interface, image::Image, CameraPosition}; -use crate::CyclerInstance; +use super::CyclerInstance; pub struct ImageReceiver {} diff --git a/crates/vision/src/image_segmenter.rs b/crates/hulk/src/vision/image_segmenter.rs similarity index 99% rename from crates/vision/src/image_segmenter.rs rename to crates/hulk/src/vision/image_segmenter.rs index 1a9a833dc7..9e6ad10e35 100644 --- a/crates/vision/src/image_segmenter.rs +++ b/crates/hulk/src/vision/image_segmenter.rs @@ -11,7 +11,7 @@ use types::{ ProjectedLimbs, Rgb, RgbChannel, ScanGrid, ScanLine, Segment, YCbCr444, }; -use crate::CyclerInstance; +use super::CyclerInstance; pub struct ImageSegmenter {} diff --git a/crates/vision/src/line_detection.rs b/crates/hulk/src/vision/line_detection.rs similarity index 99% rename from crates/vision/src/line_detection.rs rename to crates/hulk/src/vision/line_detection.rs index 07a625b879..cfd36ed515 100644 --- a/crates/vision/src/line_detection.rs +++ b/crates/hulk/src/vision/line_detection.rs @@ -9,7 +9,7 @@ use types::{ image::Image, CameraMatrix, EdgeType, FilteredSegments, ImageLines, Line, LineData, Segment, }; -use crate::ransac::{Ransac, RansacResult}; +use super::ransac::{Ransac, RansacResult}; pub struct LineDetection {} diff --git a/crates/vision/src/lib.rs b/crates/hulk/src/vision/mod.rs similarity index 76% rename from crates/vision/src/lib.rs rename to crates/hulk/src/vision/mod.rs index 509524d7bf..a67915a34b 100644 --- a/crates/vision/src/lib.rs +++ b/crates/hulk/src/vision/mod.rs @@ -10,8 +10,4 @@ mod ransac; pub mod robot_detection; pub mod segment_filter; -#[derive(Clone, Copy, Debug)] -pub enum CyclerInstance { - VisionTop, - VisionBottom, -} +pub use super::cyclers::vision::CyclerInstance; diff --git a/crates/vision/src/perspective_grid_candidates_provider.rs b/crates/hulk/src/vision/perspective_grid_candidates_provider.rs similarity index 100% rename from crates/vision/src/perspective_grid_candidates_provider.rs rename to crates/hulk/src/vision/perspective_grid_candidates_provider.rs diff --git a/crates/vision/src/ransac.rs b/crates/hulk/src/vision/ransac.rs similarity index 100% rename from crates/vision/src/ransac.rs rename to crates/hulk/src/vision/ransac.rs diff --git a/crates/vision/src/robot_detection.rs b/crates/hulk/src/vision/robot_detection.rs similarity index 100% rename from crates/vision/src/robot_detection.rs rename to crates/hulk/src/vision/robot_detection.rs diff --git a/crates/vision/src/segment_filter.rs b/crates/hulk/src/vision/segment_filter.rs similarity index 100% rename from crates/vision/src/segment_filter.rs rename to crates/hulk/src/vision/segment_filter.rs diff --git a/crates/hulk/src/webots/interface.rs b/crates/hulk/src/webots/interface.rs index 0107f98057..9193a5c64a 100644 --- a/crates/hulk/src/webots/interface.rs +++ b/crates/hulk/src/webots/interface.rs @@ -12,7 +12,6 @@ use color_eyre::{ Result, }; use serde::Deserialize; -use spl_network::endpoint::{Endpoint, Ports}; use tokio::{ runtime::{Builder, Runtime}, select, @@ -27,6 +26,8 @@ use types::{ }; use webots::Robot; +use crate::spl_network::endpoint::{Endpoint, Ports}; + use super::{ camera::Camera, force_sensitive_resistor_devices::ForceSensitiveResistorDevices, intertial_measurement_unit_devices::InertialMeasurementUnitDevices, diff --git a/crates/source_analyzer/Cargo.toml b/crates/source_analyzer/Cargo.toml index 03a2a1a38f..9c675b907d 100644 --- a/crates/source_analyzer/Cargo.toml +++ b/crates/source_analyzer/Cargo.toml @@ -6,10 +6,14 @@ license = "GPL-3.0-only" homepage = "https://github.com/hulks/hulk" [dependencies] -color-eyre = { workspace = true } convert_case = { workspace = true } glob = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } topological-sort = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +itertools = { workspace = true } +threadbound = { workspace = true } +toml = { workspace = true } diff --git a/crates/source_analyzer/src/configuration.rs b/crates/source_analyzer/src/configuration.rs new file mode 100644 index 0000000000..dec72ddf16 --- /dev/null +++ b/crates/source_analyzer/src/configuration.rs @@ -0,0 +1,34 @@ +use serde::{de, Deserialize, Deserializer}; +use syn::Path; + +use crate::cycler::CyclerKind; + +#[derive(Deserialize, Debug)] +pub struct FrameworkConfiguration { + pub cyclers: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct CyclerConfiguration { + pub name: String, + pub kind: CyclerKind, + pub instances: Option>, + pub module: String, + pub nodes: Vec, +} + +fn module_path<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let path = <&str>::deserialize(deserializer)?; + syn::parse_str(path).map_err(de::Error::custom) +} + +#[derive(Deserialize, Debug)] +pub struct NodeConfiguration { + #[serde(deserialize_with = "module_path")] + pub module: Path, + #[serde(default)] + pub is_setup: bool, +} diff --git a/crates/source_analyzer/src/contexts.rs b/crates/source_analyzer/src/contexts.rs index ca34e42f18..73c3c1a95b 100644 --- a/crates/source_analyzer/src/contexts.rs +++ b/crates/source_analyzer/src/contexts.rs @@ -1,69 +1,75 @@ -use std::{collections::BTreeMap, path::Path}; +use std::fmt::{Display, Formatter}; -use color_eyre::{ - eyre::{bail, eyre, WrapErr}, - Result, -}; -use syn::{ - spanned::Spanned, Expr, ExprLit, File, GenericArgument, Ident, Item, Lit, PathArguments, Type, -}; +use syn::{Expr, ExprLit, File, GenericArgument, Ident, Item, Lit, PathArguments, Type}; use crate::{ - into_eyre_result::new_syn_error_as_eyre_result, + error::ParseError, + path::Path, to_absolute::ToAbsolute, - uses::{uses_from_items, Uses}, + uses::{uses_from_file, Uses}, }; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Contexts { pub creation_context: Vec, pub cycle_context: Vec, pub main_outputs: Vec, } +impl Display for Contexts { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "CreationContext")?; + for field in &self.creation_context { + writeln!(f, " {field}")?; + } + writeln!(f, "CycleContext")?; + for field in &self.cycle_context { + writeln!(f, " {field}")?; + } + writeln!(f, "MainOutputs")?; + for field in &self.main_outputs { + writeln!(f, " {field}")?; + } + Ok(()) + } +} + impl Contexts { - pub fn try_from_file(file_path: impl AsRef, file: &File) -> Result { - let uses = uses_from_items(&file.items); + pub fn try_from_file(file: &File) -> Result { + let uses = uses_from_file(file); let mut creation_context = vec![]; let mut cycle_context = vec![]; let mut main_outputs = vec![]; - for item in file.items.iter() { - match item { - Item::Struct(struct_item) - if struct_item.attrs.iter().any(|attribute| { - attribute - .path - .get_ident() - .map(|attribute_name| attribute_name == "context") - .unwrap_or(false) - }) => - { - let mut fields = struct_item - .fields - .iter() - .map(|field| Field::try_from_field(&file_path, field, &uses)) - .collect::>() - .wrap_err("failed to gather context fields")?; - match struct_item.ident.to_string().as_str() { - "CreationContext" => { - creation_context.append(&mut fields); - } - "CycleContext" => { - cycle_context.append(&mut fields); - } - "MainOutputs" => { - main_outputs.append(&mut fields); - } - _ => { - return new_syn_error_as_eyre_result( - struct_item.ident.span(), - "expected `CreationContext`, `CycleContext`, or `MainOutputs`", - file_path, - ); - } - } + for item in file.items.iter().filter_map(|item| match item { + Item::Struct(item) + if item + .attrs + .iter() + .filter_map(|attribute| attribute.path.get_ident()) + .any(|identifier| identifier == "context") => + { + Some(item) + } + _ => None, + }) { + let mut fields = item + .fields + .iter() + .map(|field| Field::try_from_field(field, &uses)) + .collect::>()?; + match item.ident.to_string().as_str() { + "CreationContext" => { + creation_context.append(&mut fields); + } + "CycleContext" => { + cycle_context.append(&mut fields); + } + "MainOutputs" => { + main_outputs.append(&mut fields); + } + _ => { + return Err(ParseError:: new_spanned(&item.ident, format!("expected `CreationContext`, `CycleContext`, or `MainOutputs`, found `{}`", item.ident))); } - _ => {} } } @@ -75,12 +81,12 @@ impl Contexts { } } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Field { AdditionalOutput { data_type: Type, name: Ident, - path: Vec, + path: Path, }, CyclerInstance { name: Ident, @@ -91,13 +97,13 @@ pub enum Field { HistoricInput { data_type: Type, name: Ident, - path: Vec, + path: Path, }, Input { cycler_instance: Option, data_type: Type, name: Ident, - path: Vec, + path: Path, }, MainOutput { data_type: Type, @@ -106,283 +112,210 @@ pub enum Field { Parameter { data_type: Type, name: Ident, - path: Vec, + path: Path, }, PerceptionInput { cycler_instance: String, data_type: Type, name: Ident, - path: Vec, + path: Path, }, PersistentState { data_type: Type, name: Ident, - path: Vec, + path: Path, }, RequiredInput { cycler_instance: Option, data_type: Type, name: Ident, - path: Vec, + path: Path, }, } +impl Display for Field { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Field::AdditionalOutput { name, .. } => write!(f, "{name}: AdditionalOutput"), + Field::CyclerInstance { name, .. } => write!(f, "{name}: CyclerInstance"), + Field::HardwareInterface { name, .. } => write!(f, "{name}: HardwareInterface"), + Field::HistoricInput { name, .. } => write!(f, "{name}: HistoricInput"), + Field::Input { name, .. } => write!(f, "{name}: Input"), + Field::MainOutput { name, .. } => write!(f, "{name}: MainOutput"), + Field::Parameter { name, .. } => write!(f, "{name}: Parameter"), + Field::PerceptionInput { name, .. } => write!(f, "{name}: PerceptionInput"), + Field::PersistentState { name, .. } => write!(f, "{name}: PersistentState"), + Field::RequiredInput { name, .. } => write!(f, "{name}: RequiredInput"), + } + } +} + impl Field { - pub fn try_from_field( - file_path: impl AsRef, - field: &syn::Field, - uses: &Uses, - ) -> Result { + pub fn try_from_field(field: &syn::Field, uses: &Uses) -> Result { let field_name = field .ident .as_ref() - .ok_or_else(|| eyre!("field must have be named"))?; - match &field.ty { - Type::Path(path) => { - if path.path.segments.len() != 1 { - return new_syn_error_as_eyre_result( - path.span(), - "expected type path with exactly one segment", - file_path, - ); - } - let first_segment = &path.path.segments[0]; - match first_segment.ident.to_string().as_str() { - "AdditionalOutput" => { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - let path_contains_optional = path.iter().any(|segment| segment.is_optional); - if path_contains_optional { - bail!("unexpected optional segments in path of additional output `{field_name}`"); - } - Ok(Field::AdditionalOutput { - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) - } - "CyclerInstance" => Ok(Field::CyclerInstance { - name: field_name.clone(), - }), - "HardwareInterface" => Ok(Field::HardwareInterface { - name: field_name.clone(), - }), - "HistoricInput" => { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - Ok(Field::HistoricInput { - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) + .ok_or(ParseError::new_spanned(field, "must be named"))?; + let type_path = match &field.ty { + Type::Path(path) => path, + _ => return Err(ParseError::new_spanned(&field.ty, "unexpected type")), + }; + if type_path.path.segments.len() != 1 { + return Err(ParseError::new_spanned( + &type_path.path, + "type must be single segment", + )); + } + let first_segment = &type_path.path.segments[0]; + match first_segment.ident.to_string().as_str() { + "AdditionalOutput" => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + Ok(Field::AdditionalOutput { + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "CyclerInstance" => Ok(Field::CyclerInstance { + name: field_name.clone(), + }), + "HardwareInterface" => Ok(Field::HardwareInterface { + name: field_name.clone(), + }), + "HistoricInput" => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + Ok(Field::HistoricInput { + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "Input" => { + let (data_type, cycler_instance, path) = match &first_segment.arguments { + PathArguments::AngleBracketed(arguments) if arguments.args.len() == 2 => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + (data_type, None, path) } - "Input" => { - let (data_type, cycler_instance, path) = match &first_segment.arguments { - PathArguments::AngleBracketed(arguments) - if arguments.args.len() == 2 => - { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - (data_type, None, path) - } - PathArguments::AngleBracketed(arguments) - if arguments.args.len() == 3 => - { - let (data_type, cycler_instance, path) = - extract_three_arguments(file_path, &first_segment.arguments)?; - (data_type, Some(cycler_instance), path) - } - _ => new_syn_error_as_eyre_result( - first_segment.arguments.span(), - "expected exactly two or three generic parameters", - file_path, - )?, - }; - Ok(Field::Input { - cycler_instance, - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) + PathArguments::AngleBracketed(arguments) if arguments.args.len() == 3 => { + let (data_type, cycler_instance, path) = + extract_three_arguments(&first_segment.arguments)?; + (data_type, Some(cycler_instance), path) } - "MainOutput" => { - let data_type = extract_one_argument(file_path, &first_segment.arguments)?; - Ok(Field::MainOutput { - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - }) + _ => { + return Err(ParseError::new_spanned( + &first_segment.arguments, + "invalid generics", + )) } - "Parameter" => { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - Ok(Field::Parameter { - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) + }; + Ok(Field::Input { + cycler_instance, + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "MainOutput" => { + let data_type = extract_one_argument(&first_segment.arguments)?; + Ok(Field::MainOutput { + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + }) + } + "Parameter" => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + Ok(Field::Parameter { + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "PerceptionInput" => { + let (data_type, cycler_instance, path) = + extract_three_arguments(&first_segment.arguments)?; + Ok(Field::PerceptionInput { + cycler_instance, + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "PersistentState" => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + Ok(Field::PersistentState { + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, + }) + } + "RequiredInput" => { + let (data_type, cycler_instance, path) = match &first_segment.arguments { + PathArguments::AngleBracketed(arguments) if arguments.args.len() == 2 => { + let (data_type, path) = extract_two_arguments(&first_segment.arguments)?; + (data_type, None, path) } - "PerceptionInput" => { + PathArguments::AngleBracketed(arguments) if arguments.args.len() == 3 => { let (data_type, cycler_instance, path) = - extract_three_arguments(file_path, &first_segment.arguments)?; - Ok(Field::PerceptionInput { - cycler_instance, - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) + extract_three_arguments(&first_segment.arguments)?; + (data_type, Some(cycler_instance), path) } - "PersistentState" => { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - Ok(Field::PersistentState { - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) + _ => { + return Err(ParseError::new_spanned( + &first_segment.arguments, + "expected exactly two or three generic parameters", + )) } - "RequiredInput" => { - let (data_type, cycler_instance, path) = match &first_segment.arguments { - PathArguments::AngleBracketed(arguments) - if arguments.args.len() == 2 => - { - let (data_type, path) = - extract_two_arguments(file_path, &first_segment.arguments)?; - (data_type, None, path) - } - PathArguments::AngleBracketed(arguments) - if arguments.args.len() == 3 => - { - let (data_type, cycler_instance, path) = - extract_three_arguments(file_path, &first_segment.arguments)?; - (data_type, Some(cycler_instance), path) - } - _ => new_syn_error_as_eyre_result( - first_segment.arguments.span(), - "expected exactly two or three generic parameters", - file_path, - )?, - }; - let path_contains_optional = path.iter().any(|segment| segment.is_optional); - if !path_contains_optional { - bail!("expected optional segments in path of required input `{field_name}`"); - } - Ok(Field::RequiredInput { - cycler_instance, - data_type: data_type.to_absolute(uses), - name: field_name.clone(), - path, - }) - } - _ => new_syn_error_as_eyre_result( - first_segment.ident.span(), - "unexpected identifier", - file_path, - ), + }; + if !path.contains_optional() { + return Err(ParseError::new_spanned( + field_name, + "expected optional segments in path", + )); } - } - _ => new_syn_error_as_eyre_result(field.ty.span(), "expected type path", file_path), - } - } -} - -#[derive(Clone, Debug)] -pub struct PathSegment { - pub name: String, - pub is_optional: bool, - pub is_variable: bool, -} - -impl From<&str> for PathSegment { - fn from(segment: &str) -> Self { - let (is_variable, start_index) = match segment.starts_with('$') { - true => (true, 1), - false => (false, 0), - }; - let (is_optional, end_index) = match segment.ends_with('?') { - true => (true, segment.chars().count() - 1), - false => (false, segment.chars().count()), - }; - - Self { - name: segment[start_index..end_index].to_string(), - is_optional, - is_variable, - } - } -} - -pub fn expand_variables_from_path( - path: &[PathSegment], - variables: &BTreeMap>, -) -> Result>> { - let mut paths = vec![vec![]]; - for path_segment in path { - if path_segment.is_variable { - let cases = match variables.get(&path_segment.name) { - Some(cases) => cases, - None => bail!("unexpected variable `{}` in path", path_segment.name), - }; - paths = cases - .iter() - .flat_map(|case| { - paths.iter().cloned().map(|mut path| { - path.push(PathSegment { - name: case.clone(), - is_optional: path_segment.is_optional, - is_variable: false, - }); - path - }) + Ok(Field::RequiredInput { + cycler_instance, + data_type: data_type.to_absolute(uses), + name: field_name.clone(), + path, }) - .collect(); - } else { - for path in paths.iter_mut() { - path.push(path_segment.clone()); } + _ => Err(ParseError::new_spanned(field_name, "unexpected identifier")), } } - Ok(paths) } -fn extract_one_argument(file_path: impl AsRef, arguments: &PathArguments) -> Result { +fn extract_one_argument(arguments: &PathArguments) -> Result { match arguments { PathArguments::AngleBracketed(arguments) => { if arguments.args.len() != 1 { - return new_syn_error_as_eyre_result( - arguments.span(), - "expected exactly one generic parameter", - file_path, - ); + return Err(ParseError::new_spanned( + &arguments.args, + "expected exactly one generic parameters", + )); } match &arguments.args[0] { GenericArgument::Type(type_argument) => Ok(type_argument.clone()), - _ => new_syn_error_as_eyre_result( - arguments.span(), + argument => Err(ParseError::new_spanned( + argument, "expected type in first generic parameter", - file_path, - ), + )), } } - _ => new_syn_error_as_eyre_result( - arguments.span(), + _ => Err(ParseError::new_spanned( + arguments, "expected exactly one generic parameter", - file_path, - ), + )), } } -fn extract_two_arguments( - file_path: impl AsRef, - arguments: &PathArguments, -) -> Result<(Type, Vec)> { +fn extract_two_arguments(arguments: &PathArguments) -> Result<(Type, Path), ParseError> { match arguments { PathArguments::AngleBracketed(arguments) => { if arguments.args.len() != 2 { - return new_syn_error_as_eyre_result( - arguments.span(), + return Err(ParseError::new_spanned( + &arguments.args, "expected exactly two generic parameters", - file_path, - ); + )); } match (&arguments.args[0], &arguments.args[1]) { (GenericArgument::Type(type_argument), GenericArgument::Const(Expr::Lit( @@ -391,35 +324,26 @@ fn extract_two_arguments( }, ))) => Ok(( type_argument.clone(), - literal_argument.token().to_string().trim_matches('"').split('.').map(PathSegment::from).collect(), + Path::from(literal_argument.token().to_string().trim_matches('"')), )), - _ => new_syn_error_as_eyre_result( - arguments.span(), - "expected type in first generic parameter and string literal in second generic parameter", - file_path, - ), + _ => Err(ParseError::new_spanned(&arguments.args,"expected type in first generic parameter and string literal in second generic parameter")), } } - _ => new_syn_error_as_eyre_result( - arguments.span(), + _ => Err(ParseError::new_spanned( + arguments, "expected exactly two generic parameters", - file_path, - ), + )), } } -fn extract_three_arguments( - file_path: impl AsRef, - arguments: &PathArguments, -) -> Result<(Type, String, Vec)> { +fn extract_three_arguments(arguments: &PathArguments) -> Result<(Type, String, Path), ParseError> { match arguments { PathArguments::AngleBracketed(arguments) => { if arguments.args.len() != 3 { - return new_syn_error_as_eyre_result( - arguments.span(), + return Err(ParseError::new_spanned( + &arguments.args, "expected exactly three generic parameters", - file_path, - ); + )); } match (&arguments.args[0], &arguments.args[1], &arguments.args[2]) { (GenericArgument::Type(type_argument), GenericArgument::Const(Expr::Lit( @@ -433,571 +357,16 @@ fn extract_three_arguments( ))) => Ok(( type_argument.clone(), first_literal_argument.token().to_string().trim_matches('"').to_string(), - second_literal_argument.token().to_string().trim_matches('"').split('.').map(PathSegment::from).collect(), + Path::from(second_literal_argument.token().to_string().trim_matches('"')), )), - _ => new_syn_error_as_eyre_result( - arguments.span(), - "expected type in first generic parameter and string literals in second and third generic parameters", - file_path, + _ => Err( + ParseError::new_spanned(&arguments.args,"expected type in first generic parameter and string literals in second and third generic parameters") ), } } - _ => new_syn_error_as_eyre_result( - arguments.span(), + _ => Err(ParseError::new_spanned( + arguments, "expected exactly three generic parameters", - file_path, - ), - } -} - -#[cfg(test)] -mod tests { - use std::convert::identity; - - use syn::{parse_str, FieldsNamed}; - - use super::*; - - #[test] - fn fields_parsing_is_correct() { - let empty_uses = Uses::new(); - let type_usize: Type = parse_str("usize").unwrap(); - let type_option_usize: Type = parse_str("Option").unwrap(); - - // without optionals - let field = "AdditionalOutput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::AdditionalOutput { - data_type, - name, - path, - } if data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && !path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // optionals are not supported - let field = "AdditionalOutput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - assert!(Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses - ) - .is_err()); - - // without optionals - let field = "HistoricInput, \"a.b.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::HistoricInput { - data_type, - name, - path, - } if data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && !path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // with optionals - let field = "HistoricInput, \"a.b?.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::HistoricInput { - data_type, - name, - path, - } if data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // optional output - let field = "MainOutput>"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::MainOutput { data_type, name } - if data_type == type_option_usize && name == "name" => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // required output - let field = "MainOutput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::MainOutput { data_type, name } if data_type == type_usize && name == "name" => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // from own cycler - let field = "Input, \"a.b?.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::Input { - cycler_instance: None, - data_type, - name, - path, - } if data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // from foreign cycler - let field = "Input, \"Control\", \"a.b?.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::Input { - cycler_instance: Some(cycler_instance), - data_type, - name, - path, - } if cycler_instance == "Control" - && data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // optionals are supported - let field = "Input, \"a.b.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - assert!(Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses - ) - .is_ok()); - - // without optionals - let field = "Parameter"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::Parameter { - data_type, - name, - path, - } if data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && !path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // with optionals and Option data type - let field = "Parameter, \"a.b?.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::Parameter { - data_type, - name, - path, - } if data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // without optionals - let field = "PerceptionInput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::PerceptionInput { - cycler_instance, - data_type, - name, - path, - } if cycler_instance == "Control" - && data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && !path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // with optionals and Option data type - let field = "PerceptionInput, \"Control\", \"a.b?.c\">"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::PerceptionInput { - cycler_instance, - data_type, - name, - path, - } if cycler_instance == "Control" - && data_type == type_option_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // without optionals - let field = "PersistentState"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::PersistentState { - data_type, - name, - path, - } if data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && !path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // optionals are supported - let field = "PersistentState"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - assert!(Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses - ) - .is_ok()); - - // from own cycler, without optionals - let field = "RequiredInput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - assert!(Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .is_err()); - - // from own cycler, with optionals but without Option data type - let field = "RequiredInput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::RequiredInput { - cycler_instance: None, - data_type, - name, - path, - } if data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - - // from foreign cycler, without optionals - let field = "RequiredInput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - assert!(Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .is_err()); - - // from foreign cycler, with optionals but without Option data type - let field = "RequiredInput"; - let fields = format!("{{ name: {field} }}"); - let named_fields: FieldsNamed = parse_str(&fields).unwrap(); - let parsed_field = Field::try_from_field( - "file_path", - named_fields.named.first().unwrap(), - &empty_uses, - ) - .unwrap(); - match parsed_field { - Field::RequiredInput { - cycler_instance: Some(cycler_instance), - data_type, - name, - path, - } if cycler_instance == "Control" - && data_type == type_usize - && name == "name" - && path.len() == 3 - && path[0].name == "a" - && !path[0].is_optional - && !path[0].is_variable - && path[1].name == "b" - && path[1].is_optional - && !path[1].is_variable - && path[2].name == "c" - && !path[2].is_optional - && !path[2].is_variable => {} - _ => panic!("Unexpected parsed field from {field:?}: {parsed_field:?}"), - } - } - - #[test] - fn multiple_variables_result_in_cartesian_product() { - let path = [ - PathSegment { - name: "a".to_string(), - is_optional: false, - is_variable: false, - }, - PathSegment { - name: "b".to_string(), - is_optional: false, - is_variable: true, - }, - PathSegment { - name: "c".to_string(), - is_optional: true, - is_variable: false, - }, - PathSegment { - name: "d".to_string(), - is_optional: true, - is_variable: true, - }, - PathSegment { - name: "e".to_string(), - is_optional: false, - is_variable: false, - }, - ]; - let variables = BTreeMap::from_iter([ - ("b".to_string(), vec!["b0".to_string(), "b1".to_string()]), - ("d".to_string(), vec!["d0".to_string(), "d1".to_string()]), - ]); - let paths = expand_variables_from_path(&path, &variables).unwrap(); - - assert_eq!(paths.len(), 4); - - let mut matched_cases = [false; 4]; - for path in paths.iter() { - assert_eq!(path.len(), 5); - - assert!(!path[0].is_optional); - assert!(!path[1].is_optional); - assert!(path[2].is_optional); - assert!(path[3].is_optional); - assert!(!path[4].is_optional); - - assert!(!path[0].is_variable); - assert!(!path[1].is_variable); - assert!(!path[2].is_variable); - assert!(!path[3].is_variable); - assert!(!path[4].is_variable); - - assert_eq!(path[0].name, "a"); - assert_eq!(path[2].name, "c"); - assert_eq!(path[4].name, "e"); - - match (path[1].name.as_str(), path[3].name.as_str()) { - ("b0", "d0") => { - matched_cases[0] = true; - } - ("b1", "d0") => { - matched_cases[1] = true; - } - ("b0", "d1") => { - matched_cases[2] = true; - } - ("b1", "d1") => { - matched_cases[3] = true; - } - _ => panic!( - "Unexpected path segment case: path[1] = {}, path[3] = {}", - path[1].name, path[3].name - ), - } - } - - assert!(matched_cases.into_iter().all(identity)); + )), } } diff --git a/crates/source_analyzer/src/cycler.rs b/crates/source_analyzer/src/cycler.rs new file mode 100644 index 0000000000..7c7ccad954 --- /dev/null +++ b/crates/source_analyzer/src/cycler.rs @@ -0,0 +1,224 @@ +use std::{ + collections::HashMap, + fmt::{Display, Formatter}, + fs::read_to_string, + path::Path, +}; + +use itertools::Itertools; +use serde::Deserialize; +use topological_sort::TopologicalSort; + +use crate::{ + configuration::{CyclerConfiguration, FrameworkConfiguration}, + contexts::Field, + error::Error, + node::Node, +}; + +pub type CyclerName = String; +pub type InstanceName = String; +pub type ModulePath = String; + +#[derive(Debug)] +pub struct Instance { + pub name: InstanceName, +} + +impl Display for Instance { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +#[derive(Deserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum CyclerKind { + Perception, + RealTime, +} + +#[derive(Debug)] +pub struct Cycler { + pub name: CyclerName, + pub kind: CyclerKind, + pub instances: Vec, + pub module: ModulePath, + pub nodes: Vec, +} + +impl Display for Cycler { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let instances = self.instances.iter().map(ToString::to_string).join(", "); + writeln!(f, "{} ({:?}) [{instances}]", self.name, self.kind)?; + for node in &self.nodes { + writeln!(f, " {node}")?; + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Cyclers { + pub cyclers: Vec, +} + +impl Display for Cyclers { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for cycler in &self.cyclers { + writeln!(f, "{cycler}")?; + } + Ok(()) + } +} + +impl Cyclers { + pub fn try_from_directory(root: impl AsRef) -> Result { + let path_to_configuration = root.as_ref().join("framework.toml"); + let configuration: FrameworkConfiguration = toml::from_str( + &read_to_string(&path_to_configuration).map_err(|error| Error::Io { + source: error, + path: path_to_configuration.clone(), + })?, + ) + .map_err(|error| Error::ConfigurationParsing { + source: error, + path: path_to_configuration.clone(), + })?; + Cyclers::try_from_configurations(&configuration.cyclers, &root) + } + + pub fn try_from_configurations<'config>( + values: impl IntoIterator, + root: &impl AsRef, + ) -> Result { + let cyclers = values + .into_iter() + .map(move |configuration| Self::try_from_configuration(configuration, root)) + .collect::>()?; + Ok(Self { cyclers }) + } + + fn try_from_configuration( + cycler_config: &CyclerConfiguration, + root: &impl AsRef, + ) -> Result { + let instance_names = cycler_config + .instances + .clone() + .unwrap_or_else(|| vec![String::new()]); + let instances = instance_names + .iter() + .map(|instance_name| Instance { + name: format!("{}{}", cycler_config.name, instance_name), + }) + .collect(); + let nodes = cycler_config + .nodes + .iter() + .map(|node_config| { + Node::try_from_configuration(&cycler_config.module, node_config, root) + }) + .collect::, _>>()?; + let output_to_node: HashMap<_, _> = nodes + .iter() + .flat_map(|node| { + node.contexts + .main_outputs + .iter() + .filter_map(move |field| match field { + Field::MainOutput { name, .. } => Some((name.to_string(), node)), + _ => None, + }) + }) + .collect(); + let mut topological_sort = TopologicalSort::<&Node>::new(); + for node in nodes.iter() { + topological_sort.insert(node); + for dependency in node + .contexts + .cycle_context + .iter() + .filter_map(|field| match field { + Field::HistoricInput { path, .. } + | Field::Input { + path, + cycler_instance: None, + .. + } + | Field::RequiredInput { + path, + cycler_instance: None, + .. + } => { + let first_segment = path.segments.first()?; + Some(first_segment.name.as_str()) + } + _ => None, + }) + { + let producing_node = match output_to_node.get(dependency) { + Some(node) => node, + None => { + return Err(Error::MissingOutput { + node: node.name.clone(), + output: dependency.to_string(), + }) + } + }; + if node.is_setup && !producing_node.is_setup { + return Err(Error::SetupNodeDependency { + depending_node: node.name.clone(), + output: dependency.to_string(), + producing_node: producing_node.name.clone(), + }); + } + topological_sort.add_dependency(*producing_node, node); + } + } + + let mut sorted_nodes = Vec::new(); + while let Some(node) = topological_sort.pop() { + sorted_nodes.push(node.clone()); + } + if !topological_sort.is_empty() { + return Err(Error::CircularDependency); + } + + let cycler = Cycler { + name: cycler_config.name.clone(), + kind: cycler_config.kind, + instances, + module: cycler_config.module.clone(), + nodes: sorted_nodes, + }; + Ok(cycler) + } + + pub fn number_of_instances(&self) -> usize { + self.cyclers + .iter() + .map(|cycler| cycler.instances.len()) + .sum() + } + + pub fn instances(&self) -> impl Iterator { + self.cyclers.iter().flat_map(move |cycler| { + cycler + .instances + .iter() + .map(move |instance| (cycler, instance)) + }) + } + + pub fn instances_with(&self, kind: CyclerKind) -> impl Iterator { + self.cyclers + .iter() + .filter(move |cycler| cycler.kind == kind) + .flat_map(move |cycler| { + cycler + .instances + .iter() + .map(move |instance| (cycler, instance)) + }) + } +} diff --git a/crates/source_analyzer/src/cycler_crates.rs b/crates/source_analyzer/src/cycler_crates.rs deleted file mode 100644 index 8c5ecd53ca..0000000000 --- a/crates/source_analyzer/src/cycler_crates.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::path::{Path, PathBuf}; - -use color_eyre::{eyre::WrapErr, Result}; -use glob::glob; -use syn::Item; - -use crate::parse::parse_rust_file; - -pub fn cycler_crates_from_crates_directory( - crates_directory: impl AsRef, -) -> Result> { - glob( - crates_directory - .as_ref() - .join("*/src/lib.rs") - .to_str() - .unwrap(), - ) - .wrap_err_with(|| { - format!( - "failed to find lib.rs files from crates directory {:?}", - crates_directory.as_ref() - ) - })? - .filter_map(|file_path| { - let file_path = match file_path { - Ok(file_path) => file_path, - Err(error) => return Some(Err(error.into())), - }; - let file = match parse_rust_file(&file_path) { - Ok(file) => file, - Err(error) => return Some(Err(error)), - }; - let has_cycler_instance = file.items.into_iter().any(|item| match item { - Item::Enum(enum_item) => enum_item.ident == "CyclerInstance", - _ => false, - }); - has_cycler_instance - .then(|| { - file_path - .parent() - .and_then(|source_directory| source_directory.parent()) - .map(|crate_directory| Ok(crate_directory.to_path_buf())) - }) - .flatten() - }) - .collect() -} diff --git a/crates/source_analyzer/src/cycler_instances.rs b/crates/source_analyzer/src/cycler_instances.rs deleted file mode 100644 index 3f1abe7b2e..0000000000 --- a/crates/source_analyzer/src/cycler_instances.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{collections::BTreeMap, path::Path}; - -use color_eyre::{ - eyre::{eyre, WrapErr}, - Result, -}; -use proc_macro2::Span; -use syn::{Error, Item}; - -use crate::{ - cycler_crates::cycler_crates_from_crates_directory, into_eyre_result::SynContext, - parse::parse_rust_file, -}; - -#[derive(Debug)] -pub struct CyclerInstances { - pub instances_to_modules: BTreeMap, - pub modules_to_instances: BTreeMap>, -} - -impl CyclerInstances { - pub fn try_from_crates_directory(crates_directory: impl AsRef) -> Result { - let mut instances_to_modules = BTreeMap::new(); - let mut modules_to_instances: BTreeMap<_, Vec<_>> = BTreeMap::new(); - for crate_directory in cycler_crates_from_crates_directory(&crates_directory) - .wrap_err_with(|| { - format!( - "Failed to get cycler crates from crates directory {:?}", - crates_directory.as_ref() - ) - })? - { - let module = crate_directory - .file_name() - .ok_or_else(|| eyre!("failed to get file name from crate directory"))? - .to_str() - .ok_or_else(|| { - eyre!("failed to interpret file name of crate directory as Unicode") - })?; - let rust_file_path = crate_directory.join("src/lib.rs"); - let rust_file = parse_rust_file(&rust_file_path) - .wrap_err_with(|| format!("failed to parse file {rust_file_path:?}"))?; - let enum_item = rust_file.items.iter().find_map(|item| match item { - Item::Enum(enum_item) if enum_item.ident == "CyclerInstance" => Some(enum_item), - _ => None, - }); - let enum_item = enum_item - .ok_or_else(|| Error::new(Span::call_site(), "expected `CyclerInstances` enum")) - .syn_context(rust_file_path)?; - for variant in enum_item.variants.iter() { - instances_to_modules.insert(variant.ident.to_string(), module.to_string()); - modules_to_instances - .entry(module.to_string()) - .or_default() - .push(variant.ident.to_string()); - } - } - - Ok(Self { - instances_to_modules, - modules_to_instances, - }) - } -} diff --git a/crates/source_analyzer/src/cycler_types.rs b/crates/source_analyzer/src/cycler_types.rs deleted file mode 100644 index 5ee44181a3..0000000000 --- a/crates/source_analyzer/src/cycler_types.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{collections::HashMap, path::Path}; - -use color_eyre::{eyre::WrapErr, Result}; - -use crate::{CyclerInstances, Field, Nodes}; - -#[derive(Debug)] -pub struct CyclerTypes { - pub cycler_modules_to_cycler_types: HashMap, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CyclerType { - Perception, - RealTime, -} - -impl CyclerTypes { - pub fn try_from_crates_directory(crates_directory: impl AsRef) -> Result { - let cycler_instances = CyclerInstances::try_from_crates_directory(&crates_directory) - .wrap_err("failed to get cycler_instances")?; - let nodes = - Nodes::try_from_crates_directory(&crates_directory).wrap_err("failed to get nodes")?; - - Ok(Self { - cycler_modules_to_cycler_types: cycler_instances - .modules_to_instances - .keys() - .map(|cycler_module_name| { - let at_least_one_node_uses_this_cycler_module_via_perception_input = - nodes.nodes.values().any(|node| { - node.contexts - .creation_context - .iter() - .chain(node.contexts.cycle_context.iter()) - .any(|field| matches!( - field, - Field::PerceptionInput {cycler_instance, ..} - if &cycler_instances.instances_to_modules[cycler_instance] == cycler_module_name - )) - }); - ( - cycler_module_name.clone(), - match at_least_one_node_uses_this_cycler_module_via_perception_input { - true => CyclerType::Perception, - false => CyclerType::RealTime, - }, - ) - }) - .collect(), - }) - } -} diff --git a/crates/source_analyzer/src/error.rs b/crates/source_analyzer/src/error.rs new file mode 100644 index 0000000000..8e3d62fd20 --- /dev/null +++ b/crates/source_analyzer/src/error.rs @@ -0,0 +1,71 @@ +use std::{fmt::Display, io, path::PathBuf}; + +use proc_macro2::Span; +use quote::ToTokens; +use thiserror::Error; +use threadbound::ThreadBound; + +#[derive(Debug, Error)] +pub enum Error { + #[error("failed to perform io on `{path}`")] + Io { source: io::Error, path: PathBuf }, + #[error("cannot parse configuration file at `{path}`")] + ConfigurationParsing { + source: toml::de::Error, + path: PathBuf, + }, + #[error("failed to parse rust at {path}:{caused_by}")] + RustParse { + caused_by: ParseError, + path: PathBuf, + }, + #[error("failed to read node `{node}` at {path}:{caused_by}")] + Node { + caused_by: ParseError, + node: String, + path: PathBuf, + }, + #[error("`{node}` requires output `{output}`, but it is never produced")] + MissingOutput { node: String, output: String }, + #[error("`{depending_node}` depends on output `{output}` from `{producing_node}`, but setup nodes cannot depend on non-setup nodes")] + SetupNodeDependency { + depending_node: String, + output: String, + producing_node: String, + }, + #[error("circular dependency")] + CircularDependency, +} + +#[derive(Debug, Error)] +#[error("{}:{}, {message}", + span.get_ref().cloned().unwrap_or_else(Span::call_site).start().line, + span.get_ref().cloned().unwrap_or_else(Span::call_site).start().column, + )] +pub struct ParseError { + span: ThreadBound, + message: String, +} + +impl ParseError { + pub fn new_spanned(tokens: impl ToTokens, message: impl Display) -> Self { + let span = tokens + .into_token_stream() + .into_iter() + .next() + .map_or_else(Span::call_site, |t| t.span()); + Self { + span: ThreadBound::new(span), + message: message.to_string(), + } + } +} + +impl From for ParseError { + fn from(value: syn::Error) -> Self { + Self { + span: ThreadBound::new(value.span()), + message: value.to_string(), + } + } +} diff --git a/crates/source_analyzer/src/into_eyre_result.rs b/crates/source_analyzer/src/into_eyre_result.rs deleted file mode 100644 index d4631e4d2e..0000000000 --- a/crates/source_analyzer/src/into_eyre_result.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{fmt::Display, path::Path}; - -use color_eyre::{eyre::eyre, Result}; -use proc_macro2::Span; -use syn::Error; - -pub trait SynContext { - fn syn_context(self, file_path: impl AsRef) -> Result; -} - -impl SynContext for syn::Result { - fn syn_context(self, file_path: impl AsRef) -> Result { - self.map_err(|error| { - let start = error.span().start(); - eyre!( - "{error} at {}:{}:{}", - file_path.as_ref().display(), - start.line, - start.column - ) - }) - } -} - -pub fn new_syn_error_as_eyre_result( - span: Span, - message: impl Display, - file_path: impl AsRef, -) -> Result { - Err(Error::new(span, message)).syn_context(file_path) -} diff --git a/crates/source_analyzer/src/lib.rs b/crates/source_analyzer/src/lib.rs index 60c5b50624..523969c4e7 100644 --- a/crates/source_analyzer/src/lib.rs +++ b/crates/source_analyzer/src/lib.rs @@ -1,18 +1,10 @@ -mod contexts; -mod cycler_crates; -mod cycler_instances; -mod cycler_types; -mod into_eyre_result; -mod nodes; -mod parse; -mod structs; +pub mod configuration; +pub mod contexts; +pub mod cycler; +pub mod error; +pub mod node; +pub mod path; +pub mod struct_hierarchy; +pub mod structs; mod to_absolute; mod uses; - -pub use contexts::{expand_variables_from_path, Contexts, Field, PathSegment}; -pub use cycler_crates::cycler_crates_from_crates_directory; -pub use cycler_instances::CyclerInstances; -pub use cycler_types::{CyclerType, CyclerTypes}; -pub use nodes::{Node, Nodes}; -pub use parse::parse_rust_file; -pub use structs::{CyclerStructs, StructHierarchy, Structs}; diff --git a/crates/source_analyzer/src/node.rs b/crates/source_analyzer/src/node.rs new file mode 100644 index 0000000000..5528964c22 --- /dev/null +++ b/crates/source_analyzer/src/node.rs @@ -0,0 +1,106 @@ +use std::{ + fmt::{self, Display, Formatter}, + fs::read_to_string, + hash::{Hash, Hasher}, + path::Path, +}; + +use itertools::Itertools; +use quote::ToTokens; +use syn::{parse_file, ImplItem, Item, ItemImpl, Type}; + +use crate::{ + configuration::NodeConfiguration, + contexts::Contexts, + error::{Error, ParseError}, +}; + +pub type NodeName = String; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Node { + pub name: NodeName, + pub module: syn::Path, + pub is_setup: bool, + pub contexts: Contexts, +} + +impl Display for Node { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + let name = &self.name; + write!(formatter, "{name}") + } +} + +impl Hash for Node { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +pub fn parse_rust_file(file_path: impl AsRef) -> Result { + let buffer = read_to_string(&file_path).map_err(|error| Error::Io { + source: error, + path: file_path.as_ref().to_path_buf(), + })?; + parse_file(&buffer).map_err(|error| Error::RustParse { + caused_by: error.into(), + path: file_path.as_ref().to_path_buf(), + }) +} + +impl Node { + pub fn try_from_configuration( + cycler_module: &str, + node_config: &NodeConfiguration, + root: &impl AsRef, + ) -> Result { + let path_to_module = node_config + .module + .segments + .iter() + .map(|segment| segment.ident.to_string()) + .join("/"); + let file_path = root + .as_ref() + .join(format!("src/{cycler_module}/{path_to_module}.rs")); + let wrap_error = |error| Error::Node { + caused_by: error, + node: node_config.module.to_token_stream().to_string(), + path: file_path.clone(), + }; + let rust_file = parse_rust_file(&file_path)?; + let name = rust_file + .items + .iter() + .find_map(|item| match item { + Item::Impl(implementation) if has_new_and_cycle_method(implementation) => { + match *implementation.self_ty { + Type::Path(ref path) => path.path.get_ident(), + _ => None, + } + } + _ => None, + }) + .ok_or_else(|| wrap_error(ParseError::new_spanned(&rust_file, "cannot find node declaration, expected a type with new(...) and cycle(...) method")))? + .to_string(); + let contexts = Contexts::try_from_file(&rust_file).map_err(wrap_error)?; + Ok(Self { + name, + module: node_config.module.clone(), + is_setup: node_config.is_setup, + contexts, + }) + } +} + +fn has_new_and_cycle_method(implementation: &ItemImpl) -> bool { + implementation + .items + .iter() + .any(|item| matches!(item, ImplItem::Method(method) if method.sig.ident == "new")) + && implementation + .items + .iter() + .any(|item| matches!(item, ImplItem::Method(method) if method.sig.ident == "cycle")) +} diff --git a/crates/source_analyzer/src/nodes.rs b/crates/source_analyzer/src/nodes.rs deleted file mode 100644 index d444035a71..0000000000 --- a/crates/source_analyzer/src/nodes.rs +++ /dev/null @@ -1,220 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - path::{Component, Path}, -}; - -use color_eyre::{ - eyre::{bail, eyre, WrapErr}, - Result, -}; -use glob::glob; -use quote::ToTokens; -use syn::{ImplItem, Item, Type}; -use topological_sort::TopologicalSort; - -use crate::{ - cycler_crates::cycler_crates_from_crates_directory, parse::parse_rust_file, Contexts, Field, - PathSegment, -}; - -#[derive(Debug)] -pub struct Nodes { - pub nodes: BTreeMap, - pub cycler_modules_to_nodes: BTreeMap>, -} - -impl Nodes { - pub fn try_from_crates_directory(crates_directory: impl AsRef) -> Result { - let mut nodes = BTreeMap::new(); - let mut cycler_modules_to_nodes: BTreeMap<_, Vec<_>> = BTreeMap::new(); - for crate_directory in cycler_crates_from_crates_directory(&crates_directory) - .wrap_err_with(|| { - format!( - "failed to get cycler crates from crates directory {:?}", - crates_directory.as_ref() - ) - })? - { - for rust_file_path in glob(crate_directory.join("src/**/*.rs").to_str().unwrap()) - .wrap_err_with(|| { - format!("failed to find rust files from crate directory {crate_directory:?}") - })? - { - let cycler_module = crate_directory - .file_name() - .ok_or_else(|| eyre!("failed to get file name from crate directory"))? - .to_str() - .ok_or_else(|| { - eyre!("failed to interpret file name of crate directory as Unicode") - })?; - let rust_file_path = rust_file_path.wrap_err("failed to get rust file path")?; - let rust_file = parse_rust_file(&rust_file_path) - .wrap_err_with(|| format!("failed to parse rust file {rust_file_path:?}"))?; - let has_at_least_one_struct_with_context_attribute = - rust_file.items.iter().any(|item| match item { - Item::Struct(struct_item) => struct_item.attrs.iter().any(|attribute| { - attribute - .path - .get_ident() - .map(|attribute_name| attribute_name == "context") - .unwrap_or(false) - }), - _ => false, - }); - if !has_at_least_one_struct_with_context_attribute { - continue; - } - let node_name = rust_file - .items - .iter() - .find_map(|item| match item { - Item::Impl(implementation) - if implementation.items.iter().any(|item| { - matches!(item, - ImplItem::Method(method) if method.sig.ident == "new") - }) && implementation.items.iter().any(|item| { - matches!(item, - ImplItem::Method(method) if method.sig.ident == "cycle") - }) => - { - match &*implementation.self_ty { - Type::Path(path) => path.path.get_ident(), - _ => None, - } - } - _ => None, - }) - .ok_or_else(|| eyre!("failed to find node name in {rust_file_path:?}"))?; - let contexts = Contexts::try_from_file(&rust_file_path, &rust_file) - .wrap_err_with(|| format!("failed to get contexts in {rust_file_path:?}"))?; - let path_segments: Vec<_> = rust_file_path - .strip_prefix(crate_directory.join("src")) - .wrap_err("failed to strip prefix of node's rust file path")? - .with_extension("") - .components() - .map(|component| match component { - Component::Normal(component) => component - .to_str() - .ok_or_else(|| eyre!("failed to interpret path component as Unicode")) - .map(ToString::to_string), - _ => bail!("unexpected path component"), - }) - .collect::>() - .wrap_err("failed to generate node's path")?; - let node = Node { - cycler_module: cycler_module.to_string(), - path_segments, - contexts, - }; - if let Some(overwritten_node) = nodes.insert(node_name.to_string(), node) { - bail!( - "node `{}` is not allowed to exist in multiple cyclers `{}`, `{}`, and maybe more", - node_name.to_string(), - cycler_module.to_string(), - overwritten_node.cycler_module, - ); - } - cycler_modules_to_nodes - .entry(cycler_module.to_string()) - .or_default() - .push(node_name.to_string()); - } - } - - Ok(Self { - nodes, - cycler_modules_to_nodes, - }) - } - - pub fn sort(&mut self) -> Result<()> { - for node_names in self.cycler_modules_to_nodes.values_mut() { - if node_names.len() == 1 { - continue; - } - - let mut main_outputs_to_nodes = HashMap::new(); - let mut topological_sort: TopologicalSort = TopologicalSort::new(); - - for node_name in node_names.iter() { - for field in self.nodes[node_name].contexts.main_outputs.iter() { - if let Field::MainOutput { data_type, name } = field { - main_outputs_to_nodes - .insert(name.to_string(), (node_name.clone(), data_type.clone())); - } - } - } - - for consuming_node_name in node_names.iter() { - for field in self.nodes[consuming_node_name] - .contexts - .creation_context - .iter() - .chain( - self.nodes[consuming_node_name] - .contexts - .cycle_context - .iter(), - ) - { - match field { - Field::HistoricInput { - data_type, - name, - path, - } - | Field::Input { - cycler_instance: None, - data_type, - name, - path, - .. - } - | Field::RequiredInput { - cycler_instance: None, - data_type, - name, - path, - .. - } => { - let first_segment = match path.first() { - Some(PathSegment { name, is_variable: false, .. }) => name, - Some(..) => bail!("unexpected variable segment as first segment for `{name}` in node `{consuming_node_name}` (not implemented)"), - None => bail!("expected at least one path segment for `{name}` in node `{consuming_node_name}`"), - }; - let (producing_node_name, main_output_data_type) = match main_outputs_to_nodes.get(first_segment) { - Some(producing_node) => producing_node, - None => bail!("failed to find producing node for `{name}` in node `{consuming_node_name}`"), - }; - if main_output_data_type != data_type { - bail!("expected data type `{}` but `{name}` has `{}` in node `{consuming_node_name}`", main_output_data_type.to_token_stream(), data_type.to_token_stream()); - } - topological_sort.add_dependency( - producing_node_name.clone(), - consuming_node_name.clone(), - ); - } - _ => {} - } - } - } - - let unsorted_node_names: Vec<_> = node_names.drain(..).collect(); - node_names.extend(topological_sort); - for node_name in unsorted_node_names { - if !node_names.contains(&node_name) { - node_names.push(node_name); - } - } - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct Node { - pub cycler_module: String, - pub path_segments: Vec, - pub contexts: Contexts, -} diff --git a/crates/source_analyzer/src/parse.rs b/crates/source_analyzer/src/parse.rs deleted file mode 100644 index ca415ae37f..0000000000 --- a/crates/source_analyzer/src/parse.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::{fs, io::Read, path::Path}; - -use color_eyre::{eyre::WrapErr, Result}; -use syn::{self, parse_file}; - -use crate::into_eyre_result::SynContext; - -pub fn parse_rust_file(file_path: impl AsRef) -> Result { - let mut file = fs::File::open(&file_path) - .wrap_err_with(|| format!("failed to open file {:?}", file_path.as_ref()))?; - let mut buffer = String::new(); - file.read_to_string(&mut buffer) - .wrap_err("failed to read file to string")?; - parse_file(&buffer) - .syn_context(&file_path) - .wrap_err("failed to parse file") -} diff --git a/crates/source_analyzer/src/path.rs b/crates/source_analyzer/src/path.rs new file mode 100644 index 0000000000..5cd5ba026f --- /dev/null +++ b/crates/source_analyzer/src/path.rs @@ -0,0 +1,79 @@ +use convert_case::{Case, Casing}; + +use crate::cycler::Cycler; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Path { + pub segments: Vec, +} + +impl From<&str> for Path { + fn from(path: &str) -> Self { + let segments = path.split('.').map(PathSegment::from).collect(); + Self { segments } + } +} + +impl Path { + pub fn contains_variable(&self) -> bool { + self.segments.iter().any(|segment| segment.is_variable) + } + + pub fn contains_optional(&self) -> bool { + self.segments.iter().any(|segment| segment.is_optional) + } + + pub fn expand_variables(&self, cycler: &Cycler) -> Vec { + if !self.contains_variable() { + return vec![self.clone()]; + } + cycler + .instances + .iter() + .map(|instance| { + let segments = self + .segments + .iter() + .map(|segment| { + if segment.is_variable { + PathSegment { + name: instance.name.to_case(Case::Snake), + is_optional: segment.is_optional, + is_variable: false, + } + } else { + segment.clone() + } + }) + .collect(); + Path { segments } + }) + .collect() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PathSegment { + pub name: String, + pub is_optional: bool, + pub is_variable: bool, +} + +impl From<&str> for PathSegment { + fn from(segment: &str) -> Self { + let (is_variable, start_index) = match segment.starts_with('$') { + true => (true, 1), + false => (false, 0), + }; + let (is_optional, end_index) = match segment.ends_with('?') { + true => (true, segment.chars().count() - 1), + false => (false, segment.chars().count()), + }; + + Self { + name: segment[start_index..end_index].to_string(), + is_optional, + is_variable, + } + } +} diff --git a/crates/source_analyzer/src/struct_hierarchy.rs b/crates/source_analyzer/src/struct_hierarchy.rs new file mode 100644 index 0000000000..c118cf7305 --- /dev/null +++ b/crates/source_analyzer/src/struct_hierarchy.rs @@ -0,0 +1,113 @@ +use std::collections::BTreeMap; + +use syn::Type; +use thiserror::Error; + +#[derive(Debug, PartialEq)] +pub enum StructHierarchy { + Struct { + fields: BTreeMap, + }, + Optional { + child: Box, + }, + Field { + data_type: Type, + }, +} + +impl StructHierarchy { + pub fn new_struct() -> Self { + Self::Struct { + fields: Default::default(), + } + } +} + +#[derive(Clone, Debug)] +pub enum InsertionRule { + InsertField { name: String }, + BeginOptional, + BeginStruct, + AppendDataType { data_type: Type }, +} + +#[derive(Debug, Error)] +pub enum HierarchyError { + #[error("failed to insert an optional in-place of non-empty struct")] + OptionalForStruct, + #[error("failed to insert field with name `{name}` to optional")] + FieldInOptional { name: String }, + #[error("failed to begin struct in-place of optional")] + StructInOptional, + #[error("failed to append data type in-place of optional")] + TypeForOptional, + #[error("unmatching data types: previous data type {old} does not match data type {new} to be inserted")] + MismatchingTypes { old: String, new: String }, +} + +impl StructHierarchy { + pub fn insert( + &mut self, + insertion_rules: impl IntoIterator, + ) -> Result<(), HierarchyError> { + let mut insertion_rules = insertion_rules.into_iter(); + let rule = match insertion_rules.next() { + Some(rule) => rule, + None => return Ok(()), + }; + + match self { + StructHierarchy::Struct { fields } => match rule { + InsertionRule::InsertField { name } => { + let field = fields + .entry(name) + .or_insert_with(StructHierarchy::new_struct); + field.insert(insertion_rules)?; + } + InsertionRule::BeginOptional => { + if !fields.is_empty() { + return Err(HierarchyError::OptionalForStruct); + } + let mut child = StructHierarchy::new_struct(); + child.insert(insertion_rules)?; + *self = StructHierarchy::Optional { + child: Box::new(child), + }; + } + InsertionRule::BeginStruct => { + self.insert(insertion_rules)?; + } + InsertionRule::AppendDataType { data_type } => { + *self = StructHierarchy::Field { data_type }; + } + }, + StructHierarchy::Optional { child } => match rule { + InsertionRule::InsertField { name } => { + return Err(HierarchyError::FieldInOptional { name }); + } + InsertionRule::BeginOptional => { + child.insert(insertion_rules)?; + } + InsertionRule::BeginStruct => { + return Err(HierarchyError::StructInOptional); + } + InsertionRule::AppendDataType { .. } => { + return Err(HierarchyError::TypeForOptional); + } + }, + StructHierarchy::Field { data_type } => match rule { + InsertionRule::AppendDataType { + data_type: data_type_to_be_inserted, + } if *data_type != data_type_to_be_inserted => { + return Err(HierarchyError::MismatchingTypes { + old: format!("{data_type:?}"), + new: format!("{data_type_to_be_inserted:?}"), + }); + } + _ => (), + }, + } + Ok(()) + } +} diff --git a/crates/source_analyzer/src/structs.rs b/crates/source_analyzer/src/structs.rs index eeb863f936..069d1687d2 100644 --- a/crates/source_analyzer/src/structs.rs +++ b/crates/source_analyzer/src/structs.rs @@ -1,85 +1,63 @@ -use std::{collections::BTreeMap, iter::once, path::Path}; +use std::{collections::BTreeMap, iter::once}; -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; -use convert_case::{Case, Casing}; -use quote::{format_ident, ToTokens}; +use quote::format_ident; use syn::{ punctuated::Punctuated, AngleBracketedGenericArguments, GenericArgument, PathArguments, Type, TypePath, }; +use thiserror::Error; -use crate::{expand_variables_from_path, CyclerInstances, Field, Nodes, PathSegment}; +use crate::{ + contexts::Field, + cycler::{CyclerName, Cyclers}, + path::Path, + struct_hierarchy::{HierarchyError, InsertionRule, StructHierarchy}, +}; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Structs { pub configuration: StructHierarchy, - pub cycler_structs: BTreeMap, + pub cyclers: BTreeMap, } -impl Structs { - pub fn try_from_crates_directory(crates_directory: impl AsRef) -> Result { - let mut structs = Self::default(); +impl Default for Structs { + fn default() -> Self { + Self { + configuration: StructHierarchy::new_struct(), + cyclers: Default::default(), + } + } +} - let cycler_instances = CyclerInstances::try_from_crates_directory(&crates_directory) - .wrap_err("failed to get cycler instances")?; - let nodes = - Nodes::try_from_crates_directory(&crates_directory).wrap_err("failed to get nodes")?; +#[derive(Debug, Error)] +pub enum Error { + #[error("cannot resolve struct hierarchy")] + Hierarchy(#[from] HierarchyError), + #[error("unexpected field {0} in `CreationContext` or `CycleContext`")] + UnexpectedField(String), +} - for (cycler_module, node_names) in nodes.cycler_modules_to_nodes.iter() { - let cycler_structs = structs - .cycler_structs - .entry(cycler_module.clone()) - .or_default(); - let cycler_instances = &cycler_instances.modules_to_instances[cycler_module]; +impl Structs { + pub fn try_from_cyclers(cyclers: &Cyclers) -> Result { + let mut structs = Self::default(); - for node_name in node_names.iter() { - let contexts = &nodes.nodes[node_name].contexts; + for cycler in cyclers.cyclers.iter() { + let cycler_structs = structs.cyclers.entry(cycler.module.clone()).or_default(); - for field in contexts.main_outputs.iter() { - match field { - Field::MainOutput { data_type, name } => { - match &mut cycler_structs.main_outputs { - StructHierarchy::Struct { fields } => { - fields.insert( - name.to_string(), - StructHierarchy::Field { - data_type: data_type.clone(), - }, - ); - } - _ => bail!("unexpected non-struct hierarchy in main outputs"), - } - } - _ => { - bail!("unexpected field {field:?} in MainOutputs"); - } - } + for node in cycler.nodes.iter() { + for field in node.contexts.main_outputs.iter() { + add_main_outputs(field, cycler_structs); } - for field in contexts + for field in node + .contexts .creation_context .iter() - .chain(contexts.cycle_context.iter()) + .chain(node.contexts.cycle_context.iter()) { match field { Field::AdditionalOutput { - data_type, - name, - path, + data_type, path, .. } => { - let expanded_paths = expand_variables_from_path( - path, - &BTreeMap::from_iter([( - "cycler_instance".to_string(), - cycler_instances.iter().map(|instance| instance.to_case(Case::Snake)).collect(), - )]), - ) - .wrap_err_with(|| { - format!("failed to expand path variables for additional output `{name}`") - })?; - let data_type_wrapped_in_option = Type::Path(TypePath { qself: None, path: syn::Path { @@ -99,190 +77,86 @@ impl Structs { }]), }, }); - for path in expanded_paths { + for path in path.expand_variables(cycler) { let insertion_rules = path_to_insertion_rules(&path, &data_type_wrapped_in_option); - cycler_structs - .additional_outputs - .insert(insertion_rules) - .wrap_err_with(|| { - format!("failed to insert expanded path into additional outputs for additional output `{name}`") - })?; + cycler_structs.additional_outputs.insert(insertion_rules)?; } } Field::Parameter { - data_type, - name, - path, + data_type, path, .. } => { - let expanded_paths = expand_variables_from_path( - path, - &BTreeMap::from_iter([( - "cycler_instance".to_string(), - cycler_instances - .iter() - .map(|instance| instance.to_case(Case::Snake)) - .collect(), - )]), - ) - .wrap_err_with(|| { - format!("failed to expand path variables for parameter `{name}`") - })?; - dbg!(&expanded_paths); + let expanded_paths = path.expand_variables(cycler); for path in expanded_paths { - let path_contains_optional = - path.iter().any(|segment| segment.is_optional); - let data_type = match path_contains_optional { - true => unwrap_option_data_type(data_type.clone()) - .wrap_err_with(|| { - format!("failed to unwrap Option from data type for parameter `{name}`") - })?, + let data_type = match path.contains_optional() { + true => unwrap_option_type(data_type.clone()), false => data_type.clone(), }; let insertion_rules = path_to_insertion_rules(&path, &data_type); - structs - .configuration - .insert(insertion_rules) - .wrap_err_with(|| { - format!("failed to insert expanded path into configuration for parameter `{name}`") - })?; + structs.configuration.insert(insertion_rules)?; } } Field::PersistentState { - data_type, - name, - path, + data_type, path, .. } => { let insertion_rules = path_to_insertion_rules(path, data_type); - cycler_structs - .persistent_state - .insert(insertion_rules) - .wrap_err_with(|| { - format!("failed to insert expanded path into persistent state for persistent state `{name}`") - })?; + cycler_structs.persistent_state.insert(insertion_rules)?; } - Field::CyclerInstance { .. } - | Field::HardwareInterface { .. } - | Field::HistoricInput { .. } - | Field::Input { .. } - | Field::PerceptionInput { .. } - | Field::RequiredInput { .. } => {} Field::MainOutput { .. } => { - bail!( - "unexpected field {field:?} in `CreationContext` or `CycleContext`" - ); + return Err(Error::UnexpectedField(format!("{field:?}"))); } + _ => (), } } } } - Ok(structs) } } -#[derive(Debug, Default)] +fn add_main_outputs(field: &Field, cycler_structs: &mut CyclerStructs) { + match field { + Field::MainOutput { data_type, name } => match &mut cycler_structs.main_outputs { + StructHierarchy::Struct { fields } => { + fields.insert( + name.to_string(), + StructHierarchy::Field { + data_type: data_type.clone(), + }, + ); + } + _ => panic!("unexpected non-struct hierarchy in main outputs"), + }, + _ => { + panic!("unexpected field {field:?} in MainOutputs"); + } + } +} + +#[derive(Debug)] pub struct CyclerStructs { pub main_outputs: StructHierarchy, pub additional_outputs: StructHierarchy, pub persistent_state: StructHierarchy, } -#[derive(Debug, PartialEq)] -pub enum StructHierarchy { - Struct { - fields: BTreeMap, - }, - Optional { - child: Box, - }, - Field { - data_type: Type, - }, -} - -impl Default for StructHierarchy { +impl Default for CyclerStructs { fn default() -> Self { - Self::Struct { - fields: Default::default(), + Self { + main_outputs: StructHierarchy::new_struct(), + additional_outputs: StructHierarchy::new_struct(), + persistent_state: StructHierarchy::new_struct(), } } } -impl StructHierarchy { - fn insert(&mut self, mut insertion_rules: Vec) -> Result<()> { - let first_rule = match insertion_rules.first() { - Some(first_rule) => first_rule, - None => return Ok(()), - }; - - match self { - StructHierarchy::Struct { fields } => match first_rule { - InsertionRule::InsertField { name } => fields - .entry(name.clone()) - .or_default() - .insert(insertion_rules.split_off(1)), - InsertionRule::BeginOptional => { - if !fields.is_empty() { - bail!("failed to begin optional in-place of non-empty struct"); - } - let mut child = StructHierarchy::default(); - child.insert(insertion_rules.split_off(1))?; - *self = StructHierarchy::Optional { - child: Box::new(child), - }; - Ok(()) - } - InsertionRule::BeginStruct => self.insert(insertion_rules.split_off(1)), - InsertionRule::AppendDataType { data_type } => { - *self = StructHierarchy::Field { - data_type: data_type.clone(), - }; - Ok(()) - } - }, - StructHierarchy::Optional { child } => match first_rule { - InsertionRule::InsertField { name } => { - bail!("failed to insert field with name `{name}` to optional") - } - InsertionRule::BeginOptional => child.insert(insertion_rules.split_off(1)), - InsertionRule::BeginStruct => bail!("failed to begin struct in-place of optional"), - InsertionRule::AppendDataType { .. } => { - bail!("failed to append data type in-place of optional") - } - }, - StructHierarchy::Field { data_type } => match first_rule { - InsertionRule::InsertField { .. } => Ok(()), - InsertionRule::BeginOptional => Ok(()), - InsertionRule::BeginStruct => Ok(()), - InsertionRule::AppendDataType { - data_type: data_type_to_be_appended, - } => { - if data_type != data_type_to_be_appended { - bail!( - "unmatching data types: previous data type {} does not match data type {} to be appended", - data_type.to_token_stream(), - data_type_to_be_appended.to_token_stream(), - ); - } - Ok(()) - } - }, - } - } -} - -#[derive(Clone, Debug)] -enum InsertionRule { - InsertField { name: String }, - BeginOptional, - BeginStruct, - AppendDataType { data_type: Type }, -} - -fn path_to_insertion_rules(path: &[PathSegment], data_type: &Type) -> Vec { - path.iter() +fn path_to_insertion_rules<'a>( + path: &'a Path, + data_type: &Type, +) -> impl 'a + Iterator { + path.segments + .iter() .flat_map(|segment| { assert!(!segment.is_variable); match segment.is_optional { @@ -304,30 +178,29 @@ fn path_to_insertion_rules(path: &[PathSegment], data_type: &Type) -> Vec Result { +fn unwrap_option_type(data_type: Type) -> Type { match data_type { Type::Path(TypePath { path: syn::Path { segments, .. }, .. }) if segments.len() == 1 && segments.first().unwrap().ident == "Option" => { - match &segments.first().unwrap().arguments { + match segments.into_iter().next().unwrap().arguments { PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) if args.len() == 1 => { - match args.first().unwrap() { - GenericArgument::Type(nested_data_type) => Ok(nested_data_type.clone()), - _ => bail!( + match args.into_iter().next().unwrap() { + GenericArgument::Type(nested_data_type) => nested_data_type, + _ => panic!( "unexpected generic argument, expected type argument in data type" ), } } - _ => bail!("expected exactly one generic type argument in data type"), + _ => panic!("expected exactly one generic type argument in data type"), } } - _ => bail!("execpted Option as data type"), + _ => panic!("execpted Option as data type"), } } @@ -340,7 +213,7 @@ mod tests { let data_type = Type::Verbatim(Default::default()); let cases = [ ( - "a/b/c", + "a.b.c", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -360,7 +233,7 @@ mod tests { ], ), ( - "a?/b/c", + "a?.b.c", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -381,7 +254,7 @@ mod tests { ], ), ( - "a?/b?/c", + "a?.b?.c", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -403,7 +276,7 @@ mod tests { ], ), ( - "a?/b?/c?", + "a?.b?.c?", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -426,7 +299,7 @@ mod tests { ], ), ( - "a/b?/c?", + "a.b?.c?", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -448,7 +321,7 @@ mod tests { ], ), ( - "a/b/c?", + "a.b.c?", vec![ InsertionRule::BeginStruct, InsertionRule::InsertField { @@ -471,9 +344,8 @@ mod tests { ]; for case in cases { - let path = case.0; - let path_segments: Vec<_> = path.split('/').map(PathSegment::from).collect(); - let insertion_rules = path_to_insertion_rules(&path_segments, &data_type); + let path = Path::from(case.0); + let insertion_rules = path_to_insertion_rules(&path, &data_type).collect::>(); let expected_insertion_rules = case.1; assert_eq!(insertion_rules.len(), expected_insertion_rules.len(), "path: {path:?}, insertion_rules: {insertion_rules:?}, expected_insertion_rules: {expected_insertion_rules:?}"); @@ -513,7 +385,7 @@ mod tests { data_type: data_type.clone(), }, ]; - let mut hierarchy = StructHierarchy::default(); + let mut hierarchy = StructHierarchy::new_struct(); hierarchy.insert(insertion_rules).unwrap(); let StructHierarchy::Struct { fields } = &hierarchy else { @@ -565,7 +437,7 @@ mod tests { data_type: data_type.clone(), }, ]; - let mut hierarchy = StructHierarchy::default(); + let mut hierarchy = StructHierarchy::new_struct(); hierarchy.insert(insertion_rules).unwrap(); let StructHierarchy::Struct { fields } = &hierarchy else { @@ -621,7 +493,7 @@ mod tests { data_type: data_type.clone(), }, ]; - let mut hierarchy = StructHierarchy::default(); + let mut hierarchy = StructHierarchy::new_struct(); hierarchy.insert(insertion_rules).unwrap(); let StructHierarchy::Struct { fields } = &hierarchy else { @@ -681,7 +553,7 @@ mod tests { data_type: data_type.clone(), }, ]; - let mut hierarchy = StructHierarchy::default(); + let mut hierarchy = StructHierarchy::new_struct(); hierarchy.insert(insertion_rules).unwrap(); let StructHierarchy::Struct { fields } = &hierarchy else { @@ -743,7 +615,7 @@ mod tests { }, InsertionRule::AppendDataType { data_type }, ]; - let mut hierarchy_less_specific_first = StructHierarchy::default(); + let mut hierarchy_less_specific_first = StructHierarchy::new_struct(); hierarchy_less_specific_first .insert(less_specific_insertion_rules.clone()) .unwrap(); @@ -751,7 +623,7 @@ mod tests { .insert(more_specific_insertion_rules.clone()) .unwrap(); - let mut hierarchy_more_specific_first = StructHierarchy::default(); + let mut hierarchy_more_specific_first = StructHierarchy::new_struct(); dbg!(&hierarchy_more_specific_first); hierarchy_more_specific_first .insert(more_specific_insertion_rules) diff --git a/crates/source_analyzer/src/uses.rs b/crates/source_analyzer/src/uses.rs index bd2ef61f33..4dda941791 100644 --- a/crates/source_analyzer/src/uses.rs +++ b/crates/source_analyzer/src/uses.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; -use syn::{Ident, Item, UseTree}; +use syn::{File, Ident, Item, UseTree}; pub type Uses = HashMap>; -pub fn uses_from_items(items: &[Item]) -> Uses { - items +pub fn uses_from_file(file: &File) -> Uses { + file.items .iter() .filter_map(|item| match item { Item::Use(use_item) => Some(use_item.tree.extract_uses(vec![])), diff --git a/crates/spl_network/Cargo.toml b/crates/spl_network/Cargo.toml deleted file mode 100644 index 8ef9ea7ce4..0000000000 --- a/crates/spl_network/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "spl_network" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -color-eyre = { workspace = true } -context_attribute = { workspace = true } -framework = { workspace = true } -log = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -types = { workspace = true } diff --git a/crates/spl_network/src/lib.rs b/crates/spl_network/src/lib.rs deleted file mode 100644 index 612b7b2bd4..0000000000 --- a/crates/spl_network/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod endpoint; -pub mod message_receiver; - -#[derive(Clone, Copy, Debug)] -pub enum CyclerInstance { - SplNetwork, -} diff --git a/crates/structs/Cargo.toml b/crates/structs/Cargo.toml deleted file mode 100644 index 3a6b1fdb03..0000000000 --- a/crates/structs/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "structs" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -color-eyre = { workspace = true } -filtering = { workspace = true } -nalgebra = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serialize_hierarchy = { workspace = true } -spl_network_messages = { workspace = true } -types = { workspace = true } - -[build-dependencies] -build_script_helpers = { workspace = true } -color-eyre = { workspace = true } -convert_case = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -source_analyzer = { workspace = true } diff --git a/crates/structs/build.rs b/crates/structs/build.rs deleted file mode 100644 index 8d7ae7fbab..0000000000 --- a/crates/structs/build.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::collections::BTreeMap; - -use build_script_helpers::write_token_stream; -use color_eyre::{ - eyre::{bail, WrapErr}, - Result, -}; -use convert_case::{Case, Casing}; -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; -use source_analyzer::{cycler_crates_from_crates_directory, StructHierarchy, Structs}; - -fn main() -> Result<()> { - for crate_directory in cycler_crates_from_crates_directory("..") - .wrap_err("failed to get cycler crate directories from crates directory")? - { - println!("cargo:rerun-if-changed={}", crate_directory.display()); - } - - let structs = Structs::try_from_crates_directory("..") - .wrap_err("failed to get structs from crates directory")?; - - let configuration = match &structs.configuration { - StructHierarchy::Struct { fields } => { - let structs = struct_hierarchy_to_token_stream( - "Configuration", - fields, - quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, - ) - .wrap_err("failed to generate struct `Configuration`")?; - quote! { - #structs - } - } - StructHierarchy::Optional { .. } => bail!("unexpected optional variant as root-struct"), - StructHierarchy::Field { .. } => bail!("unexpected field variant as root-struct"), - }; - let cyclers = structs - .cycler_structs - .iter() - .map(|(cycler_module, cycler_structs)| { - let cycler_module_identifier = format_ident!("{}", cycler_module); - let main_outputs = match &cycler_structs.main_outputs { - StructHierarchy::Struct { fields } => struct_hierarchy_to_token_stream( - "MainOutputs", - fields, - quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, - ) - .wrap_err("failed to generate struct `MainOutputs`")?, - StructHierarchy::Optional { .. } => { - bail!("unexpected optional variant as root-struct") - } - StructHierarchy::Field { .. } => bail!("unexpected field variant as root-struct"), - }; - let additional_outputs = match &cycler_structs.additional_outputs { - StructHierarchy::Struct { fields } => struct_hierarchy_to_token_stream( - "AdditionalOutputs", - fields, - quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, - ) - .wrap_err("failed to generate struct `AdditionalOutputs`")?, - StructHierarchy::Optional { .. } => { - bail!("unexpected optional variant as root-struct") - } - StructHierarchy::Field { .. } => bail!("unexpected field variant as root-struct"), - }; - let persistent_state = match &cycler_structs.persistent_state { - StructHierarchy::Struct { fields } => struct_hierarchy_to_token_stream( - "PersistentState", - fields, - quote! { #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, serialize_hierarchy::SerializeHierarchy)] }, - ) - .wrap_err("failed to generate struct `PersistentState`")?, - StructHierarchy::Optional { .. } => { - bail!("unexpected optional variant as root-struct") - } - StructHierarchy::Field { .. } => bail!("unexpected field variant as root-struct"), - }; - - Ok(quote! { - pub mod #cycler_module_identifier { - #main_outputs - #additional_outputs - #persistent_state - } - }) - }) - .collect::, _>>()?; - - let token_stream = quote! { - #configuration - #(#cyclers)* - }; - - write_token_stream("structs.rs", token_stream) - .wrap_err("failed to write perception databases structs")?; - - Ok(()) -} - -fn struct_hierarchy_to_token_stream( - struct_name: &str, - fields: &BTreeMap, - derives: TokenStream, -) -> Result { - let struct_name_identifier = format_ident!("{}", struct_name); - let struct_fields: Vec<_> = fields - .iter() - .map(|(name, struct_hierarchy)| { - let name_identifier = format_ident!("{}", name); - match struct_hierarchy { - StructHierarchy::Struct { .. } => { - let struct_name_identifier = - format_ident!("{}{}", struct_name, name.to_case(Case::Pascal)); - Ok(quote! { pub #name_identifier: #struct_name_identifier }) - } - StructHierarchy::Optional { child } => match &**child { - StructHierarchy::Struct { .. } => { - let struct_name_identifier = - format_ident!("{}{}", struct_name, name.to_case(Case::Pascal)); - Ok(quote! { pub #name_identifier: Option<#struct_name_identifier> }) - } - StructHierarchy::Optional { .. } => { - bail!("unexpected optional in an optional struct") - } - StructHierarchy::Field { data_type } => { - Ok(quote! { pub #name_identifier: Option<#data_type> }) - } - }, - StructHierarchy::Field { data_type } => { - Ok(quote! { pub #name_identifier: #data_type }) - } - } - }) - .collect::>() - .wrap_err("failed to generate struct fields")?; - let child_structs: Vec<_> = fields - .iter() - .map(|(name, struct_hierarchy)| match struct_hierarchy { - StructHierarchy::Struct { fields } => { - let struct_name = format!("{}{}", struct_name, name.to_case(Case::Pascal)); - struct_hierarchy_to_token_stream(&struct_name, fields, derives.clone()) - .wrap_err_with(|| format!("failed to generate struct `{struct_name}`")) - } - StructHierarchy::Optional { child } => match &**child { - StructHierarchy::Struct { fields } => { - let struct_name = format!("{}{}", struct_name, name.to_case(Case::Pascal)); - struct_hierarchy_to_token_stream(&struct_name, fields, derives.clone()) - .wrap_err_with(|| format!("failed to generate struct `{struct_name}`")) - } - StructHierarchy::Optional { .. } => { - bail!("unexpected optional in an optional struct") - } - StructHierarchy::Field { .. } => Ok(Default::default()), - }, - StructHierarchy::Field { .. } => Ok(Default::default()), - }) - .collect::>() - .wrap_err("failed to generate child structs")?; - - Ok(quote! { - #derives - pub struct #struct_name_identifier { - #(#struct_fields,)* - } - #(#child_structs)* - }) -} diff --git a/crates/vision/Cargo.toml b/crates/vision/Cargo.toml deleted file mode 100644 index 21166e3534..0000000000 --- a/crates/vision/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "vision" -version = "0.1.0" -edition = "2021" -license = "GPL-3.0-only" -homepage = "https://github.com/hulks/hulk" - -[dependencies] -approx = { workspace = true } -color-eyre = { workspace = true } -compiled-nn = { workspace = true } -context_attribute = { workspace = true } -itertools = { workspace = true } -filtering = { workspace = true } -framework = { workspace = true } -nalgebra = { workspace = true } -ordered-float = { workspace = true } -rand = { workspace = true } -types = { workspace = true } diff --git a/tools/pepsi/Cargo.toml b/tools/pepsi/Cargo.toml index 24c4fc83a3..db3655f69d 100644 --- a/tools/pepsi/Cargo.toml +++ b/tools/pepsi/Cargo.toml @@ -24,3 +24,4 @@ source_analyzer = { workspace = true } spl_network_messages = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } +toml = { workspace = true } diff --git a/tools/pepsi/src/analyze.rs b/tools/pepsi/src/analyze.rs index ffdb2dafcd..b2c9bd8e0a 100644 --- a/tools/pepsi/src/analyze.rs +++ b/tools/pepsi/src/analyze.rs @@ -5,7 +5,9 @@ use clap::Subcommand; use color_eyre::{eyre::WrapErr, Result}; use repository::Repository; -use source_analyzer::{parse_rust_file, Contexts, CyclerInstances, CyclerTypes, Nodes, Structs}; +use source_analyzer::{ + contexts::Contexts, cycler::Cyclers, node::parse_rust_file, structs::Structs, +}; #[derive(Subcommand)] #[allow(clippy::enum_variant_names)] @@ -20,14 +22,11 @@ pub enum Arguments { /// File path to a Rust file containing a module with context structs file_path: PathBuf, }, - DumpCyclerInstances, - DumpCyclerTypes, + DumpCyclers, DumpLatest { /// File name to dump (may contain wildcard characters usable by glob()) file_name: String, }, - DumpNodes, - DumpSortedNodes, DumpStructs, } @@ -51,22 +50,14 @@ pub async fn analyze(arguments: Arguments, repository: &Repository) -> Result<() .wrap_err("failed to print file")?; } Arguments::DumpContexts { file_path } => { - let file = parse_rust_file(&file_path).wrap_err("failed to parse rust file")?; - let context = Contexts::try_from_file(file_path, &file) - .wrap_err("failed to get contexts from rust file")?; - println!("{context:#?}"); + let file = parse_rust_file(file_path).wrap_err("failed to parse rust file")?; + let context = + Contexts::try_from_file(&file).wrap_err("failed to get contexts from rust file")?; + println!("{context}"); } - Arguments::DumpCyclerInstances => { - let cycler_instances = - CyclerInstances::try_from_crates_directory(repository.crates_directory()) - .wrap_err("failed to get cycler instances")?; - println!("{cycler_instances:#?}"); - } - Arguments::DumpCyclerTypes => { - let cycler_types = - CyclerTypes::try_from_crates_directory(repository.crates_directory()) - .wrap_err("failed to get cycler types")?; - println!("{cycler_types:#?}"); + Arguments::DumpCyclers => { + let cyclers = Cyclers::try_from_directory("crates/hulk/")?; + println!("{cyclers}"); } Arguments::DumpLatest { file_name } => { let file_path = repository @@ -82,20 +73,9 @@ pub async fn analyze(arguments: Arguments, repository: &Repository) -> Result<() .print() .wrap_err("failed to print file")?; } - Arguments::DumpNodes => { - let nodes = Nodes::try_from_crates_directory(repository.crates_directory()) - .wrap_err("failed to get nodes")?; - println!("{nodes:#?}"); - } - Arguments::DumpSortedNodes => { - let mut nodes = Nodes::try_from_crates_directory(repository.crates_directory()) - .wrap_err("failed to get nodes")?; - nodes.sort().wrap_err("failed to sort nodes")?; - println!("{nodes:#?}"); - } Arguments::DumpStructs => { - let structs = Structs::try_from_crates_directory(repository.crates_directory()) - .wrap_err("failed to get structs")?; + let cyclers = Cyclers::try_from_directory("crates/hulk/")?; + let structs = Structs::try_from_cyclers(&cyclers)?; println!("{structs:#?}"); } } diff --git a/tools/pepsi/src/main.rs b/tools/pepsi/src/main.rs index bc682edb5b..2fc390d8f6 100644 --- a/tools/pepsi/src/main.rs +++ b/tools/pepsi/src/main.rs @@ -143,7 +143,7 @@ struct Arguments { #[derive(Subcommand)] enum Command { - /// Analyze source code + // /// Analyze source code #[clap(subcommand)] Analyze(AnalyzeArguments), /// Get aliveness information from NAOs