diff --git a/Cargo.lock b/Cargo.lock index fa6fbce..e827963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,7 +1546,6 @@ dependencies = [ "nrpc 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "obfstr", "prost", - "usdpl-build", "usdpl-core", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/usdpl-back/build.rs b/usdpl-back/build.rs deleted file mode 100644 index 36803b5..0000000 --- a/usdpl-back/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - usdpl_build::back::build() -} diff --git a/usdpl-back/src/api_any/dirs.rs b/usdpl-back/src/api_any/dirs.rs index 6ac1b5a..e35ba03 100644 --- a/usdpl-back/src/api_any/dirs.rs +++ b/usdpl-back/src/api_any/dirs.rs @@ -5,14 +5,10 @@ use std::process::Command; /// The home directory of the user currently running the Steam Deck UI (specifically: running gamescope). pub fn home() -> Option { - let who_out = Command::new("who") - .output().ok()?; + let who_out = Command::new("who").output().ok()?; let who_str = String::from_utf8_lossy(who_out.stdout.as_slice()); for login in who_str.split("\n") { - let username = login - .split(" ") - .next()? - .trim(); + let username = login.split(" ").next()?.trim(); let path = Path::new("/home").join(username); if path.is_dir() { return Some(path); diff --git a/usdpl-back/src/api_common/dirs.rs b/usdpl-back/src/api_common/dirs.rs index 033aee6..c27e12a 100644 --- a/usdpl-back/src/api_common/dirs.rs +++ b/usdpl-back/src/api_common/dirs.rs @@ -7,12 +7,10 @@ pub fn home() -> Option { #[cfg(not(any(feature = "decky", feature = "crankshaft")))] let result = crate::api_any::dirs::home(); #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] - let result = crate::api_decky::home().ok() - .map(|x| PathBuf::from(x) - .join("..") - .canonicalize() - .ok() - ).flatten(); + let result = crate::api_decky::home() + .ok() + .map(|x| PathBuf::from(x).join("..").canonicalize().ok()) + .flatten(); result } diff --git a/usdpl-back/src/api_common/files.rs b/usdpl-back/src/api_common/files.rs index 48b555f..f3085a3 100644 --- a/usdpl-back/src/api_common/files.rs +++ b/usdpl-back/src/api_common/files.rs @@ -1,8 +1,8 @@ //! Common low-level file operations use std::fmt::Display; -use std::path::Path; use std::fs::File; -use std::io::{Read, Write, self}; +use std::io::{self, Read, Write}; +use std::path::Path; use std::str::FromStr; /// Write something to a file. @@ -31,14 +31,12 @@ impl std::fmt::Display for ReadError { } } -impl std::error::Error for ReadError { - -} +impl std::error::Error for ReadError {} /// Read something from a file. /// Useful for kernel configuration files. #[inline] -pub fn read_single, D: FromStr, E>(path: P) -> Result> { +pub fn read_single, D: FromStr, E>(path: P) -> Result> { let mut file = File::open(path).map_err(ReadError::Io)?; let mut string = String::new(); file.read_to_string(&mut string).map_err(ReadError::Io)?; diff --git a/usdpl-back/src/lib.rs b/usdpl-back/src/lib.rs index cb2e8ce..769b0a6 100644 --- a/usdpl-back/src/lib.rs +++ b/usdpl-back/src/lib.rs @@ -5,12 +5,10 @@ //! #![warn(missing_docs)] -#[cfg(not(any(feature = "decky", feature = "crankshaft")))] +#[cfg(not(any(feature = "decky")))] mod api_any; mod api_common; -#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] -mod api_crankshaft; -#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] +#[cfg(all(feature = "decky", not(any(feature = "any"))))] mod api_decky; mod rpc; @@ -27,16 +25,16 @@ pub mod api { pub use super::api_common::*; /// Standard interfaces not specific to a single plugin loader - #[cfg(not(any(feature = "decky", feature = "crankshaft")))] - pub mod any { pub use super::super::api_any::*; } - - /// Crankshaft-specific interfaces (FIXME) - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - pub mod crankshaft { pub use super::super::api_crankshaft::*; } + #[cfg(not(any(feature = "decky")))] + pub mod any { + pub use super::super::api_any::*; + } /// Decky-specific interfaces - #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] - pub mod decky { pub use super::super::api_decky::*; } + #[cfg(all(feature = "decky", not(any(feature = "any"))))] + pub mod decky { + pub use super::super::api_decky::*; + } } /// usdpl-core re-export @@ -49,9 +47,9 @@ pub mod nrpc { pub use nrpc::*; } -/// nRPC-generated exports +/*/// nRPC-generated exports #[allow(missing_docs)] #[allow(dead_code)] pub mod services { include!(concat!(env!("OUT_DIR"), "/mod.rs")); -} +}*/ diff --git a/usdpl-back/src/rpc/registry.rs b/usdpl-back/src/rpc/registry.rs index 6ae165d..0560696 100644 --- a/usdpl-back/src/rpc/registry.rs +++ b/usdpl-back/src/rpc/registry.rs @@ -1,6 +1,6 @@ +use async_lock::Mutex; use std::collections::HashMap; use std::sync::Arc; -use async_lock::Mutex; use nrpc::{ServerService, ServiceError}; @@ -19,7 +19,12 @@ impl<'a> ServiceRegistry<'a> { format!("{}.{}", package, service) }*/ - pub async fn call_descriptor(&self, descriptor: &str, method: &str, data: bytes::Bytes) -> Result { + pub async fn call_descriptor( + &self, + descriptor: &str, + method: &str, + data: bytes::Bytes, + ) -> Result { if let Some(service) = self.entries.get(descriptor) { let mut output = bytes::BytesMut::new(); let mut service_lock = service.lock_arc().await; @@ -32,7 +37,8 @@ impl<'a> ServiceRegistry<'a> { pub fn register(&mut self, service: S) -> &mut Self { let key = service.descriptor().to_owned(); - self.entries.insert(key, Arc::new(Mutex::new(Box::new(service)))); + self.entries + .insert(key, Arc::new(Mutex::new(Box::new(service)))); self } diff --git a/usdpl-back/src/websockets.rs b/usdpl-back/src/websockets.rs index 9ad22f9..5fbed68 100644 --- a/usdpl-back/src/websockets.rs +++ b/usdpl-back/src/websockets.rs @@ -61,16 +61,19 @@ impl WebsocketServer { runner.block_on(self.run()) } - async fn connection_handler(services: ServiceRegistry<'static>, stream: TcpStream) -> Result<(), RatchetError> { + async fn connection_handler( + services: ServiceRegistry<'static>, + stream: TcpStream, + ) -> Result<(), RatchetError> { let upgraded = ratchet_rs::accept_with( stream, WebSocketConfig::default(), DeflateExtProvider::default(), ProtocolRegistry::new(["usdpl-nrpc"])?, ) - .await? - .upgrade() - .await?; + .await? + .upgrade() + .await?; let request_path = upgraded.request.uri().path(); @@ -82,19 +85,27 @@ impl WebsocketServer { let mut buf = BytesMut::new(); loop { match websocket.read(&mut buf).await? { - Message::Text => return Err(RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, "Websocket text messages are not accepted")), + Message::Text => { + return Err(RatchetError::with_cause( + ratchet_rs::ErrorKind::Protocol, + "Websocket text messages are not accepted", + )) + } Message::Binary => { - let response = services.call_descriptor( - descriptor.service, - descriptor.method, - buf.clone().freeze() - ) + let response = services + .call_descriptor( + descriptor.service, + descriptor.method, + buf.clone().freeze(), + ) .await - .map_err(|e| RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()))?; + .map_err(|e| { + RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()) + })?; websocket.write_binary(response).await?; - }, + } Message::Ping(x) => websocket.write_pong(x).await?, - Message::Pong(_) => {}, + Message::Pong(_) => {} Message::Close(_) => break, } } @@ -106,10 +117,7 @@ impl WebsocketServer { if let Some(service) = iter.next() { if let Some(method) = iter.next() { if iter.next().is_none() { - return Ok(MethodDescriptor { - service, - method - }); + return Ok(MethodDescriptor { service, method }); } else { Err("URL path has too many separators") } diff --git a/usdpl-build/protos/debug.proto b/usdpl-build/protos/debug.proto index c5b7168..86aaf0b 100644 --- a/usdpl-build/protos/debug.proto +++ b/usdpl-build/protos/debug.proto @@ -22,4 +22,6 @@ message LogMessage { string msg = 2; } -message Empty {} +message Empty { + bool ok = 1; +} diff --git a/usdpl-build/src/back/mod.rs b/usdpl-build/src/back/mod.rs index 21affc5..ec1b904 100644 --- a/usdpl-build/src/back/mod.rs +++ b/usdpl-build/src/back/mod.rs @@ -1,7 +1,10 @@ -pub fn build() { +pub fn build( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { crate::dump_protos_out().unwrap(); nrpc_build::compile_servers( - crate::all_proto_filenames(crate::proto_builtins_out_path()), - crate::proto_out_paths() + crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos), + crate::proto_out_paths(custom_dirs), ) } diff --git a/usdpl-build/src/front/mod.rs b/usdpl-build/src/front/mod.rs index 37efa4b..b9a6f29 100644 --- a/usdpl-build/src/front/mod.rs +++ b/usdpl-build/src/front/mod.rs @@ -7,16 +7,24 @@ pub use service_generator::WasmServiceGenerator; mod shared_state; pub(crate) use shared_state::SharedState; -pub fn build() { +pub fn build( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { let shared_state = SharedState::new(); crate::dump_protos_out().unwrap(); nrpc_build::Transpiler::new( - crate::all_proto_filenames(crate::proto_builtins_out_path()), - crate::proto_out_paths() - ).unwrap() - .generate_client() - .with_preprocessor(nrpc_build::AbstractImpl::outer(WasmProtoPreprocessor::with_state(&shared_state))) - .with_service_generator(nrpc_build::AbstractImpl::outer(WasmServiceGenerator::with_state(&shared_state))) - .transpile() - .unwrap() + crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos), + crate::proto_out_paths(custom_dirs), + ) + .unwrap() + .generate_client() + .with_preprocessor(nrpc_build::AbstractImpl::outer( + WasmProtoPreprocessor::with_state(&shared_state), + )) + .with_service_generator(nrpc_build::AbstractImpl::outer( + WasmServiceGenerator::with_state(&shared_state), + )) + .transpile() + .unwrap() } diff --git a/usdpl-build/src/front/preprocessor.rs b/usdpl-build/src/front/preprocessor.rs index dd3b4a2..bcaf763 100644 --- a/usdpl-build/src/front/preprocessor.rs +++ b/usdpl-build/src/front/preprocessor.rs @@ -18,10 +18,7 @@ impl WasmProtoPreprocessor { impl IPreprocessor for WasmProtoPreprocessor { fn process(&mut self, fds: &mut FileDescriptorSet) -> proc_macro2::TokenStream { - self.shared.lock() - .expect("Cannot lock shared state") - .fds = Some(fds.clone()); - quote::quote!{} + self.shared.lock().expect("Cannot lock shared state").fds = Some(fds.clone()); + quote::quote! {} } } - diff --git a/usdpl-build/src/front/service_generator.rs b/usdpl-build/src/front/service_generator.rs index 9c53698..584fc45 100644 --- a/usdpl-build/src/front/service_generator.rs +++ b/usdpl-build/src/front/service_generator.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; -use prost_build::Service; -use prost_types::{FileDescriptorSet, DescriptorProto, EnumDescriptorProto, FieldDescriptorProto}; use nrpc_build::IServiceGenerator; +use prost_build::Service; +use prost_types::{DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorSet}; use super::SharedState; @@ -18,79 +18,102 @@ impl WasmServiceGenerator { } } -fn generate_service_methods(service: &Service, fds: &FileDescriptorSet) -> proc_macro2::TokenStream { +fn generate_service_methods( + service: &Service, + fds: &FileDescriptorSet, +) -> proc_macro2::TokenStream { let mut gen_methods = Vec::with_capacity(service.methods.len()); for method in &service.methods { let method_name = quote::format_ident!("{}", method.name); let method_input = quote::format_ident!("{}{}", &service.name, method.input_type); - let method_output = quote::format_ident!("{}{}", &service.name, method.output_type); + let method_output = quote::format_ident!("{}{}Wasm", &service.name, method.output_type); + let method_output_as_in = quote::format_ident!("{}{}", &service.name, method.output_type); - let input_type = find_message_type(&method.input_type, &service.package, fds).expect("Protobuf message is used but not found"); + let input_type = find_message_type(&method.input_type, &service.package, fds) + .expect("Protobuf message is used but not found"); let mut input_params = Vec::with_capacity(input_type.field.len()); let mut params_to_fields = Vec::with_capacity(input_type.field.len()); for field in &input_type.field { //let param_name = quote::format_ident!("val{}", i.to_string()); - let type_name = ProtobufType::from_field(field, &service.name).to_tokens(); - let field_name = quote::format_ident!("{}", field.name.as_ref().expect("Protobuf message field needs a name")); - input_params.push(quote::quote!{ - #field_name: #type_name, + let type_enum = ProtobufType::from_field(field, &service.name, false); + //let rs_type_name = type_enum.to_tokens(); + let js_type_name = type_enum.to_wasm_tokens(); + let rs_type_name = type_enum.to_tokens(); + let field_name = quote::format_ident!( + "{}", + field + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + input_params.push(quote::quote! { + #field_name: #js_type_name, }); - params_to_fields.push(quote::quote!{ - #field_name,//: #field_name, + params_to_fields.push(quote::quote! { + #field_name: #rs_type_name::from_wasm(#field_name.into()),//: #field_name, }); } let params_to_fields_transformer = if input_type.field.len() == 1 { - let field_name = quote::format_ident!("{}", input_type.field[0].name.as_ref().expect("Protobuf message field needs a name")); - quote::quote!{ - let val = #field_name; + let field_name = quote::format_ident!( + "{}", + input_type.field[0] + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + quote::quote! { + let val = #method_input::from_wasm(#field_name.into()); } } else if input_type.field.is_empty() { - quote::quote!{ + quote::quote! { let val = #method_input {}; } } else { - quote::quote!{ + quote::quote! { let val = #method_input { #(#params_to_fields)* }; } }; - let special_fn_into_input = quote::format_ident!("{}_convert_into", method.input_type.split('.').last().unwrap().to_lowercase()); + gen_methods.push(quote::quote! { + #[wasm_bindgen] + pub async fn #method_name(&mut self, #(#input_params)*) -> Option<#method_output> { - let special_fn_from_output = quote::format_ident!("{}_convert_from", method.output_type.split('.').last().unwrap().to_lowercase()); + #params_to_fields_transformer - gen_methods.push( - quote::quote!{ - #[wasm_bindgen] - pub async fn #method_name(&mut self, #(#input_params)*) -> Option<#method_output> { - - #params_to_fields_transformer - - match self.service.#method_name(#special_fn_into_input(val)).await { - Ok(x) => Some(#special_fn_from_output(x)), - Err(_e) => { - // TODO log error - None - } + match self.service.#method_name(val.into()).await { + Ok(x) => { + let x2: #method_output_as_in = x.into(); + Some(x2.into_wasm()) + }, + Err(_e) => { + // TODO log error + None } } } - ); + }); } - quote::quote!{ + quote::quote! { #(#gen_methods)* } } -fn find_message_type<'a>(want_type: &str, want_package: &str, fds: &'a FileDescriptorSet) -> Option<&'a DescriptorProto> { +fn find_message_type<'a>( + want_type: &str, + want_package: &str, + fds: &'a FileDescriptorSet, +) -> Option<&'a DescriptorProto> { for file in &fds.file { - for message_type in &file.message_type { - if let Some(name) = &message_type.name { - if let Some(pkg) = &file.package { - if name == want_type && pkg == want_package { - return Some(message_type); + if let Some(pkg) = &file.package { + if pkg == want_package { + for message_type in &file.message_type { + if let Some(name) = &message_type.name { + if name == want_type { + return Some(message_type); + } } } } @@ -99,7 +122,11 @@ fn find_message_type<'a>(want_type: &str, want_package: &str, fds: &'a FileDescr None } -fn find_enum_type<'a>(want_type: &str, want_package: &str, fds: &'a FileDescriptorSet) -> Option<&'a EnumDescriptorProto> { +fn find_enum_type<'a>( + want_type: &str, + want_package: &str, + fds: &'a FileDescriptorSet, +) -> Option<&'a EnumDescriptorProto> { for file in &fds.file { for enum_type in &file.enum_type { if let Some(name) = &enum_type.name { @@ -114,7 +141,10 @@ fn find_enum_type<'a>(want_type: &str, want_package: &str, fds: &'a FileDescript None } -fn find_field<'a>(want_field: &str, descriptor: &'a DescriptorProto) -> Option<&'a FieldDescriptorProto> { +fn find_field<'a>( + want_field: &str, + descriptor: &'a DescriptorProto, +) -> Option<&'a FieldDescriptorProto> { for field in &descriptor.field { if let Some(name) = &field.name { if name == want_field { @@ -125,9 +155,47 @@ fn find_field<'a>(want_field: &str, descriptor: &'a DescriptorProto) -> Option<& None } -fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mut HashSet, handled_types: &mut HashSet, is_response_msg: bool, service: &str) -> proc_macro2::TokenStream { - let msg_name = quote::format_ident!("{}{}", service, descriptor.name.as_ref().expect("Protobuf message needs a name")); - let super_msg_name = quote::format_ident!("{}", descriptor.name.as_ref().expect("Protobuf message needs a name")); +fn is_known_map(field: &FieldDescriptorProto, known_maps: &HashSet) -> bool { + if let Some(type_name) = &field.type_name { + let name = type_name.split('.').last().unwrap(); + known_maps.contains(name) + } else { + false + } +} + +fn generate_wasm_struct_interop( + descriptor: &DescriptorProto, + handled_enums: &mut HashSet, + handled_types: &mut HashSet, + known_maps: &mut HashSet, + seen_super_enums: &mut HashSet, + is_response_msg: bool, + service: &str, +) -> proc_macro2::TokenStream { + let msg_name = quote::format_ident!( + "{}{}", + service, + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); + let msg_name_wasm = quote::format_ident!( + "{}{}Wasm", + service, + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); + let super_msg_name = quote::format_ident!( + "{}", + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); let mut gen_fields = Vec::with_capacity(descriptor.field.len()); let mut gen_into_fields = Vec::with_capacity(descriptor.field.len()); let mut gen_from_fields = Vec::with_capacity(descriptor.field.len()); @@ -137,126 +205,60 @@ fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mu let mut gen_enums = Vec::with_capacity(descriptor.enum_type.len()); if let Some(options) = &descriptor.options { + //dbg!(options); if let Some(map_entry) = options.map_entry { // TODO deal with options when necessary if map_entry { - let name = descriptor.name.clone().expect("Protobuf message needs a name"); - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); - let key_field = find_field("key", descriptor).expect("Protobuf map entry has no key field"); - let key_type = ProtobufType::from_field(&key_field, service); - let value_field = find_field("value", descriptor).expect("Protobuf map entry has no value field"); - let value_type = ProtobufType::from_field(&value_field, service); + //dbg!(descriptor); + let name = descriptor + .name + .clone() + .expect("Protobuf message needs a name"); + known_maps.insert(name.clone()); + let key_field = + find_field("key", descriptor).expect("Protobuf map entry has no key field"); + let key_type = ProtobufType::from_field(&key_field, service, false); + let value_field = + find_field("value", descriptor).expect("Protobuf map entry has no value field"); + let value_type = ProtobufType::from_field(&value_field, service, false); - let key_type_tokens = key_type.to_tokens(); - let value_type_tokens = value_type.to_tokens(); - - let (fn_from, fn_into) = match (key_type, value_type) { - (ProtobufType::String, ProtobufType::String) => ( - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: ::std::collections::HashMap<#key_type_tokens, #value_type_tokens>) -> #msg_name { - let map = #msg_name::new(); - for (key, val) in other.iter() { - map.set(&key.into(), &val.into()); - } - map - } - }, - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #msg_name) -> ::std::collections::HashMap<#key_type_tokens, #value_type_tokens> { - let mut output = ::std::collections::HashMap::<#key_type_tokens, #value_type_tokens>::new(); - this.for_each(&mut |key: ::wasm_bindgen::JsValue, val: ::wasm_bindgen::JsValue| { - if let Some(key) = key.as_string() { - if let Some(val) = val.as_string() { - output.insert(key, val); - } - } - }); - output - } - } - ), - (ProtobufType::String, ProtobufType::Double | ProtobufType::Float | ProtobufType::Int32| ProtobufType::Int64| ProtobufType::Uint32| ProtobufType::Uint64| ProtobufType::Sint32| ProtobufType::Sint64| ProtobufType::Fixed32| ProtobufType::Fixed64| ProtobufType::Sfixed32| ProtobufType::Sfixed64) => ( - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: ::std::collections::HashMap<#key_type_tokens, #value_type_tokens>) -> #msg_name { - let map = #msg_name::new(); - for (key, val) in other.iter() { - map.set(&key.into(), &(val as f64).into()); - } - map - } - }, - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #msg_name) -> ::std::collections::HashMap<#key_type_tokens, #value_type_tokens> { - let mut output = ::std::collections::HashMap::<#key_type_tokens, #value_type_tokens>::new(); - this.for_each(&mut |key: ::wasm_bindgen::JsValue, val: ::wasm_bindgen::JsValue| { - if let Some(key) = key.as_string() { - if let Some(val) = val.as_f64() { - output.insert(key, val as _); - } - } - }); - output - } - } - ), - (ProtobufType::String, ProtobufType::Bool) => ( - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: ::std::collections::HashMap<#key_type_tokens, #value_type_tokens>) -> #msg_name { - let map = #msg_name::new(); - for (key, val) in other.iter() { - map.set(&key.into(), &(val as f64).into()); - } - map - } - }, - quote::quote!{ - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #msg_name) -> ::std::collections::HashMap<#key_type_tokens, #value_type_tokens> { - let mut output = ::std::collections::HashMap::<#key_type_tokens, #value_type_tokens>::new(); - this.for_each(&mut |key: ::wasm_bindgen::JsValue, val: ::wasm_bindgen::JsValue| { - if let Some(key) = key.as_string() { - if let Some(val) = val.as_bool() { - output.insert(key, val); - } - } - }); - output - } - } - ), - (key_type, value_type) => panic!("Unsupported map type map<{:?}, {:?}>", key_type, value_type), + let map_type = ProtobufType::Map { + key: Box::new(key_type), + value: Box::new(value_type), }; - return quote::quote!{ - pub type #msg_name = ::js_sys::Map; + //dbg!("Generated map type", name); - #fn_from + let map_tokens = map_type.to_tokens(); + let wasm_tokens = map_type.to_wasm_tokens(); - #fn_into - } + return quote::quote! { + pub type #msg_name = #map_tokens; + pub type #msg_name_wasm = #wasm_tokens; + }; } - } else { - todo!("Deal with message options when necessary"); } + // TODO Deal with other message options when necessary } + //dbg!(&descriptor.options); + for n_type in &descriptor.nested_type { - let type_name = n_type.name.clone().expect("Protobuf nested message needs a name"); + let type_name = n_type + .name + .clone() + .expect("Protobuf nested message needs a name"); if !handled_types.contains(&type_name) { handled_types.insert(type_name); - gen_nested_types.push(generate_wasm_struct_interop(n_type, handled_enums, handled_types, is_response_msg, service)); + gen_nested_types.push(generate_wasm_struct_interop( + n_type, + handled_enums, + handled_types, + known_maps, + seen_super_enums, + is_response_msg, + service, + )); } } @@ -264,62 +266,55 @@ fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mu let type_name = e_type.name.clone().expect("Protobuf enum needs a name"); if !handled_enums.contains(&type_name) { handled_enums.insert(type_name); - gen_enums.push(generate_wasm_enum_interop(e_type, service)); + gen_enums.push(generate_wasm_enum_interop( + e_type, + service, + seen_super_enums, + )); } } - if descriptor.field.len() == 1 { - let field = &descriptor.field[0]; - let field_name = quote::format_ident!("{}", field.name.as_ref().expect("Protobuf message field needs a name")); - let type_name = ProtobufType::from_field(field, service).to_tokens(); - gen_fields.push(quote::quote!{ - pub #field_name: #type_name, - }); - if let Some(name) = &field.type_name { - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); - gen_into_fields.push( - quote::quote!{ - #field_name: #special_fn_into(this) - } - ); - gen_from_fields.push( - quote::quote!{ - #special_fn_from(other.#field_name) - } - ); - } else { - gen_into_fields.push( - quote::quote!{ - #field_name: this - } - ); + if descriptor.field.len() == 0 { + quote::quote! { + pub type #msg_name = (); + pub type #msg_name_wasm = #msg_name; - gen_from_fields.push( - quote::quote!{ - other.#field_name - } - ); + #(#gen_nested_types)* + + #(#gen_enums)* } + } else if descriptor.field.len() == 1 { + let field = &descriptor.field[0]; + //dbg!(descriptor, field); + let field_name = quote::format_ident!( + "{}", + field + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + let type_enum = ProtobufType::from_field(field, service, is_known_map(field, known_maps)); + let type_name = type_enum.to_tokens(); + let wasm_type_name = type_enum.to_wasm_tokens(); - let name = descriptor.name.clone().expect("Protobuf message needs a name"); - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); - - quote::quote!{ + quote::quote! { pub type #msg_name = #type_name; + pub type #msg_name_wasm = #wasm_type_name; - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: super::#super_msg_name) -> #msg_name { - #(#gen_from_fields)* + impl std::convert::Into for #msg_name { + #[inline] + fn into(self) -> super::#super_msg_name { + super::#super_msg_name { + #field_name: self + } + } } - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #msg_name) -> super::#super_msg_name { - super::#super_msg_name { - #(#gen_into_fields)* + impl std::convert::From for #msg_name { + #[inline] + #[allow(unused_variables)] + fn from(other: super::#super_msg_name) -> Self { + other.#field_name } } @@ -329,58 +324,60 @@ fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mu } } else { for field in &descriptor.field { - let field_name = quote::format_ident!("{}", field.name.as_ref().expect("Protobuf message field needs a name")); - let type_name = ProtobufType::from_field(field, service).to_tokens(); - gen_fields.push(quote::quote!{ + let field_name = quote::format_ident!( + "{}", + field + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + let type_enum = + ProtobufType::from_field(field, service, is_known_map(field, known_maps)); + let type_name = type_enum.to_tokens(); + //let wasm_type_name = type_enum.to_wasm_tokens(); + gen_fields.push(quote::quote! { pub #field_name: #type_name, }); - if let Some(name) = &field.type_name { - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); - gen_into_fields.push( - quote::quote!{ - #field_name: #special_fn_into(self.#field_name), - } - ); + gen_into_fields.push(quote::quote! { + #field_name: self.#field_name.into(), + }); - gen_from_fields.push( - quote::quote!{ - #field_name: #special_fn_from(other.#field_name), - } - ); - } else { - gen_into_fields.push( - quote::quote!{ - #field_name: self.#field_name, - } - ); - - gen_from_fields.push( - quote::quote!{ - #field_name: other.#field_name, - } - ); - } + gen_from_fields.push(quote::quote! { + #field_name: <_>::from(other.#field_name), + }); } - let name = descriptor.name.clone().expect("Protobuf message needs a name"); - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); + let wasm_attribute_maybe = + if (descriptor.field.len() == 1 || !is_response_msg) && !descriptor.field.is_empty() { + quote::quote! {} + } else { + quote::quote! { + #[wasm_bindgen] + } + }; - let wasm_attribute_maybe = if descriptor.field.len() == 1 || !is_response_msg { - quote::quote!{} - } else { - quote::quote!{ - #[wasm_bindgen] - } - }; - - quote::quote!{ + quote::quote! { #wasm_attribute_maybe pub struct #msg_name { #(#gen_fields)* } + impl KnownWasmCompatible for #msg_name {} + + impl IntoWasmable<#msg_name> for #msg_name { + fn into_wasm(self) -> Self { + self + } + } + + impl FromWasmable<#msg_name> for #msg_name { + fn from_wasm(x: Self) -> Self { + x + } + } + + type #msg_name_wasm = #msg_name; + impl std::convert::Into for #msg_name { #[inline] fn into(self) -> super::#super_msg_name { @@ -400,24 +397,11 @@ fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mu } } - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: super::#super_msg_name) -> #msg_name { - #msg_name::from(other) - } - - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #msg_name) -> super::#super_msg_name { - this.into() - } - #(#gen_nested_types)* #(#gen_enums)* } } - } #[derive(Debug)] @@ -437,6 +421,11 @@ enum ProtobufType { Bool, String, Bytes, + Repeated(Box), + Map { + key: Box, + value: Box, + }, Custom(String), } @@ -464,62 +453,131 @@ impl ProtobufType { fn from_id(id: i32) -> Self { match id { - //"double" => quote::quote!{f64}, + 1 => Self::Double, //"float" => quote::quote!{f32}, //"int32" => quote::quote!{i32}, //"int64" => quote::quote!{i64}, //"uint32" => quote::quote!{u32}, - //"uint64" => quote::quote!{u64}, + 4 => Self::Uint64, + 5 => Self::Int32, //"sint32" => quote::quote!{i32}, //"sint64" => quote::quote!{i64}, //"fixed32" => quote::quote!{u32}, //"fixed64" => quote::quote!{u64}, //"sfixed32" => quote::quote!{i32}, //"sfixed64" => quote::quote!{i64}, - //"bool" => quote::quote!{bool}, + 8 => Self::Bool, 9 => Self::String, //"bytes" => quote::quote!{Vec}, t => Self::Custom(format!("UnknownType{}", t)), } } - fn from_field(field: &FieldDescriptorProto, service: &str) -> Self { - if let Some(type_name) = &field.type_name { + fn from_field(field: &FieldDescriptorProto, service: &str, is_map: bool) -> Self { + let inner = if let Some(type_name) = &field.type_name { Self::from_str(type_name, service) } else { let number = field.r#type.unwrap(); Self::from_id(number) - } + }; + if let Some(label) = field.label { + match label { + 3 if !is_map => Self::Repeated(Box::new(inner)), // is also the label for maps for some reason... + _ => inner, + } + } else { + inner + } } fn to_tokens(&self) -> proc_macro2::TokenStream { match self { - Self::Double => quote::quote!{f64}, - Self::Float => quote::quote!{f32}, - Self::Int32 => quote::quote!{i32}, - Self::Int64 => quote::quote!{i64}, - Self::Uint32 => quote::quote!{u32}, - Self::Uint64 => quote::quote!{u64}, - Self::Sint32 => quote::quote!{i32}, - Self::Sint64 => quote::quote!{i64}, - Self::Fixed32 => quote::quote!{u32}, - Self::Fixed64 => quote::quote!{u64}, - Self::Sfixed32 => quote::quote!{i32}, - Self::Sfixed64 => quote::quote!{i64}, - Self::Bool => quote::quote!{bool}, - Self::String => quote::quote!{String}, - Self::Bytes => quote::quote!{Vec}, + Self::Double => quote::quote! {f64}, + Self::Float => quote::quote! {f32}, + Self::Int32 => quote::quote! {i32}, + Self::Int64 => quote::quote! {i64}, + Self::Uint32 => quote::quote! {u32}, + Self::Uint64 => quote::quote! {u64}, + Self::Sint32 => quote::quote! {i32}, + Self::Sint64 => quote::quote! {i64}, + Self::Fixed32 => quote::quote! {u32}, + Self::Fixed64 => quote::quote! {u64}, + Self::Sfixed32 => quote::quote! {i32}, + Self::Sfixed64 => quote::quote! {i64}, + Self::Bool => quote::quote! {bool}, + Self::String => quote::quote! {String}, + Self::Bytes => quote::quote! {Vec}, + Self::Repeated(t) => { + let inner = t.to_tokens(); + quote::quote! {Vec::<#inner>} + } + Self::Map { key, value } => { + let key = key.to_tokens(); + let value = value.to_tokens(); + quote::quote! {std::collections::HashMap<#key, #value>} + } Self::Custom(t) => { let ident = quote::format_ident!("{}", t); - quote::quote!{#ident} - }, + quote::quote! {#ident} + } + } + } + + fn to_wasm_tokens(&self) -> proc_macro2::TokenStream { + match self { + Self::Double => quote::quote! {f64}, + Self::Float => quote::quote! {f32}, + Self::Int32 => quote::quote! {i32}, + Self::Int64 => quote::quote! {i64}, + Self::Uint32 => quote::quote! {u32}, + Self::Uint64 => quote::quote! {u64}, + Self::Sint32 => quote::quote! {i32}, + Self::Sint64 => quote::quote! {i64}, + Self::Fixed32 => quote::quote! {u32}, + Self::Fixed64 => quote::quote! {u64}, + Self::Sfixed32 => quote::quote! {i32}, + Self::Sfixed64 => quote::quote! {i64}, + Self::Bool => quote::quote! {bool}, + Self::String => quote::quote! {String}, + Self::Bytes => quote::quote! {Vec}, + Self::Repeated(_) => quote::quote! {js_sys::Array}, + Self::Map { .. } => quote::quote! {js_sys::Map}, + Self::Custom(t) => { + let ident = quote::format_ident!("{}Wasm", t); + quote::quote! {#ident} + } } } } -fn generate_wasm_enum_interop(descriptor: &EnumDescriptorProto, service: &str) -> proc_macro2::TokenStream { - let enum_name = quote::format_ident!("{}{}", service, descriptor.name.as_ref().expect("Protobuf enum needs a name")); - let super_enum_name = quote::format_ident!("{}", descriptor.name.as_ref().expect("Protobuf enum needs a name")); +fn generate_wasm_enum_interop( + descriptor: &EnumDescriptorProto, + service: &str, + seen_super_enums: &mut HashSet, +) -> proc_macro2::TokenStream { + let enum_name = quote::format_ident!( + "{}{}", + service, + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); + let enum_name_wasm = quote::format_ident!( + "{}{}Wasm", + service, + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); + let super_enum_name = quote::format_ident!( + "{}", + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); let mut gen_values = Vec::with_capacity(descriptor.value.len()); let mut gen_into_values = Vec::with_capacity(descriptor.value.len()); let mut gen_from_values = Vec::with_capacity(descriptor.value.len()); @@ -528,42 +586,46 @@ fn generate_wasm_enum_interop(descriptor: &EnumDescriptorProto, service: &str) - todo!("Deal with enum options when necessary"); } for value in &descriptor.value { - let val_name = quote::format_ident!("{}", value.name.as_ref().expect("Protobuf enum value needs a name")); + let val_name = quote::format_ident!( + "{}", + value + .name + .as_ref() + .expect("Protobuf enum value needs a name") + ); if let Some(_val_options) = &value.options { // TODO deal with options when necessary todo!("Deal with enum value options when necessary"); } else { if let Some(number) = &value.number { - gen_values.push( - quote::quote!{ - #val_name = #number, - } - ); + gen_values.push(quote::quote! { + #val_name = #number, + }); } else { - gen_values.push( - quote::quote!{ - #val_name, - } - ); + gen_values.push(quote::quote! { + #val_name, + }); } - gen_into_values.push( - quote::quote!{ - Self::#val_name => super::#super_enum_name::#val_name, - } - ); + gen_into_values.push(quote::quote! { + Self::#val_name => super::#super_enum_name::#val_name, + }); - gen_from_values.push( - quote::quote!{ - super::#super_enum_name::#val_name => Self::#val_name, - } - ); + gen_from_values.push(quote::quote! { + super::#super_enum_name::#val_name => Self::#val_name, + }); } } - let name = descriptor.name.clone().expect("Protobuf message needs a name"); - let special_fn_from = quote::format_ident!("{}_convert_from", name.split('.').last().unwrap().to_lowercase()); - let special_fn_into = quote::format_ident!("{}_convert_into", name.split('.').last().unwrap().to_lowercase()); - quote::quote!{ + let impl_wasm_compat = if seen_super_enums.contains(descriptor.name.as_ref().unwrap()) { + quote::quote! {} + } else { + seen_super_enums.insert(descriptor.name.clone().unwrap()); + quote::quote! { + //impl KnownWasmCompatible for super::#super_enum_name {} + } + }; + + quote::quote! { #[wasm_bindgen] #[repr(i32)] #[derive(Clone, Copy)] @@ -571,6 +633,8 @@ fn generate_wasm_enum_interop(descriptor: &EnumDescriptorProto, service: &str) - #(#gen_values)* } + type #enum_name_wasm = #enum_name; + impl std::convert::Into for #enum_name { fn into(self) -> super::#super_enum_name { match self { @@ -587,66 +651,137 @@ fn generate_wasm_enum_interop(descriptor: &EnumDescriptorProto, service: &str) - } } - #[inline] - #[allow(dead_code)] - fn #special_fn_from(other: i32) -> #enum_name { - #enum_name::from(super::#super_enum_name::from_i32(other).unwrap()) + #impl_wasm_compat + + impl FromWasmable for #enum_name { + fn from_wasm(js: i32) -> Self { + #enum_name::from(super::#super_enum_name::from_i32(js).unwrap()) + } } - #[inline] - #[allow(dead_code)] - fn #special_fn_into(this: #enum_name) -> i32 { - this as i32 + impl IntoWasmable for #enum_name { + fn into_wasm(self) -> i32 { + self as i32 + } + } + + impl From for #enum_name { + fn from(other: i32) -> Self { + #enum_name::from(super::#super_enum_name::from_i32(other).unwrap()) + } + } + + impl Into for #enum_name { + fn into(self) -> i32 { + self as i32 + } } } } -fn generate_service_io_types(service: &Service, fds: &FileDescriptorSet) -> proc_macro2::TokenStream { +fn generate_service_io_types( + service: &Service, + fds: &FileDescriptorSet, +) -> proc_macro2::TokenStream { let mut gen_types = Vec::with_capacity(service.methods.len() * 2); let mut gen_enums = Vec::new(); let mut handled_enums = HashSet::new(); let mut handled_types = HashSet::new(); + let mut known_maps = HashSet::new(); + let mut seen_super_enums = HashSet::new(); for method in &service.methods { if let Some(input_message) = find_message_type(&method.input_type, &service.package, fds) { - let msg_name = input_message.name.clone().expect("Protobuf message name required"); + let msg_name = input_message + .name + .clone() + .expect("Protobuf message name required"); if !handled_types.contains(&msg_name) { handled_types.insert(msg_name); - gen_types.push(generate_wasm_struct_interop(input_message, &mut handled_enums, &mut handled_types, false, &service.name)); + gen_types.push(generate_wasm_struct_interop( + input_message, + &mut handled_enums, + &mut handled_types, + &mut known_maps, + &mut seen_super_enums, + false, + &service.name, + )); } } else if let Some(input_enum) = find_enum_type(&method.input_type, &service.package, fds) { - let enum_name = input_enum.name.clone().expect("Protobuf enum name required"); + let enum_name = input_enum + .name + .clone() + .expect("Protobuf enum name required"); if !handled_enums.contains(&enum_name) { handled_enums.insert(enum_name); - gen_types.push(generate_wasm_enum_interop(input_enum, &service.name)); + gen_enums.push(generate_wasm_enum_interop( + input_enum, + &service.name, + &mut seen_super_enums, + )); } } else { - panic!("Cannot find proto type {}/{}", service.package, method.input_type); + panic!( + "Cannot find input proto type/message {}/{} for method {}", + service.package, method.input_type, method.name + ); } - if let Some(output_message) = find_message_type(&method.output_type, &service.package, fds) { - let msg_name = output_message.name.clone().expect("Protobuf message name required"); + if let Some(output_message) = find_message_type(&method.output_type, &service.package, fds) + { + let msg_name = output_message + .name + .clone() + .expect("Protobuf message name required"); if !handled_types.contains(&msg_name) { handled_types.insert(msg_name); - gen_types.push(generate_wasm_struct_interop(output_message, &mut handled_enums, &mut handled_types, true, &service.name)); + gen_types.push(generate_wasm_struct_interop( + output_message, + &mut handled_enums, + &mut handled_types, + &mut known_maps, + &mut seen_super_enums, + true, + &service.name, + )); } - } else if let Some(output_enum) = find_enum_type(&method.output_type, &service.package, fds) { - let enum_name = output_enum.name.clone().expect("Protobuf enum name required"); + } else if let Some(output_enum) = find_enum_type(&method.output_type, &service.package, fds) + { + let enum_name = output_enum + .name + .clone() + .expect("Protobuf enum name required"); if !handled_enums.contains(&enum_name) { handled_enums.insert(enum_name); - gen_types.push(generate_wasm_enum_interop(output_enum, &service.name)); + gen_enums.push(generate_wasm_enum_interop( + output_enum, + &service.name, + &mut seen_super_enums, + )); } } else { - panic!("Cannot find proto type {}/{}", service.package, method.input_type); + panic!( + "Cannot find output proto type/message {}/{} for method {}", + service.package, method.output_type, method.name + ); } } // always generate all enums, since they aren't encountered (ever, afaik) when generating message structs for file in &fds.file { - for enum_type in &file.enum_type { - let enum_name = enum_type.name.clone().expect("Protobuf enum name required"); - if !handled_enums.contains(&enum_name) { - handled_enums.insert(enum_name); - gen_enums.push(generate_wasm_enum_interop(enum_type, &service.name)); + if let Some(pkg) = &file.package { + if pkg == &service.package { + for enum_type in &file.enum_type { + let enum_name = enum_type.name.clone().expect("Protobuf enum name required"); + if !handled_enums.contains(&enum_name) { + handled_enums.insert(enum_name); + gen_enums.push(generate_wasm_enum_interop( + enum_type, + &service.name, + &mut seen_super_enums, + )); + } + } } } } @@ -659,9 +794,9 @@ fn generate_service_io_types(service: &Service, fds: &FileDescriptorSet) -> proc impl IServiceGenerator for WasmServiceGenerator { fn generate(&mut self, service: Service) -> proc_macro2::TokenStream { - let lock = self.shared.lock() - .expect("Cannot lock shared state"); - let fds = lock.fds + let lock = self.shared.lock().expect("Cannot lock shared state"); + let fds = lock + .fds .as_ref() .expect("FileDescriptorSet required for WASM service generator"); let service_struct_name = quote::format_ident!("{}Client", service.name); @@ -669,11 +804,17 @@ impl IServiceGenerator for WasmServiceGenerator { let service_methods = generate_service_methods(&service, fds); let service_types = generate_service_io_types(&service, fds); let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase()); - quote::quote!{ + quote::quote! { mod #mod_name { - use wasm_bindgen::prelude::*; + #![allow(dead_code, unused_imports)] + use usdpl_front::_helpers::wasm_bindgen::prelude::*; + use usdpl_front::_helpers::wasm_bindgen; + use usdpl_front::_helpers::wasm_bindgen_futures; + use usdpl_front::_helpers::js_sys; - use crate::client_handler::WebSocketHandler; + use usdpl_front::wasm::*; + + use usdpl_front::WebSocketHandler; #service_types diff --git a/usdpl-build/src/front/shared_state.rs b/usdpl-build/src/front/shared_state.rs index 2175d1d..fb098f3 100644 --- a/usdpl-build/src/front/shared_state.rs +++ b/usdpl-build/src/front/shared_state.rs @@ -7,9 +7,7 @@ pub struct SharedState(Arc>); impl SharedState { pub fn new() -> Self { - Self(Arc::new(Mutex::new(SharedProtoData { - fds: None, - }))) + Self(Arc::new(Mutex::new(SharedProtoData { fds: None }))) } } diff --git a/usdpl-build/src/lib.rs b/usdpl-build/src/lib.rs index d543e54..3fd10ac 100644 --- a/usdpl-build/src/lib.rs +++ b/usdpl-build/src/lib.rs @@ -2,4 +2,6 @@ pub mod back; pub mod front; mod proto_files; -pub use proto_files::{dump_protos, dump_protos_out, proto_out_paths, all_proto_filenames, proto_builtins_out_path}; +pub use proto_files::{ + all_proto_filenames, dump_protos, dump_protos_out, proto_builtins_out_path, proto_out_paths, +}; diff --git a/usdpl-build/src/proto_files.rs b/usdpl-build/src/proto_files.rs index 58e4bed..d15570e 100644 --- a/usdpl-build/src/proto_files.rs +++ b/usdpl-build/src/proto_files.rs @@ -17,26 +17,25 @@ const TRANSLATIONS_PROTO: IncludedFileStr<'static> = IncludedFileStr { contents: include_str!("../protos/translations.proto"), }; -const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [ - DEBUG_PROTO, - TRANSLATIONS_PROTO, -]; +const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [DEBUG_PROTO, TRANSLATIONS_PROTO]; pub fn proto_builtins_out_path() -> PathBuf { - PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")).join("protos") + PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")) + .join("protos") } -pub fn proto_out_paths() -> impl Iterator { +pub fn proto_out_paths(additionals: impl Iterator) -> impl Iterator { std::iter::once(proto_builtins_out_path()) .map(|x| x.to_str().unwrap().to_owned()) - .chain(custom_protos_dirs().into_iter()) + .chain(custom_protos_dirs(additionals).into_iter()) } -fn custom_protos_dirs() -> Vec { +fn custom_protos_dirs(additionals: impl Iterator) -> Vec { let dirs = std::env::var(ADDITIONAL_PROTOBUFS_ENV_VAR).unwrap_or_else(|_| "".to_owned()); dirs.split(':') .filter(|x| std::fs::read_dir(x).is_ok()) .map(|x| x.to_owned()) + .chain(additionals) .collect() } @@ -48,14 +47,27 @@ fn custom_protos_filenames() -> Vec { .flat_map(|x| x.unwrap()) .filter(|x| x.is_ok()) .map(|x| x.unwrap().path()) - .filter(|x| if let Some(ext) = x.extension() { ext.to_ascii_lowercase() == "proto" && x.is_file() } else { false }) + .filter(|x| { + if let Some(ext) = x.extension() { + ext.to_ascii_lowercase() == "proto" && x.is_file() + } else { + false + } + }) .filter_map(|x| x.to_str().map(|x| x.to_owned())) .collect() } -pub fn all_proto_filenames(p: impl AsRef + 'static) -> impl Iterator { +pub fn all_proto_filenames( + p: impl AsRef + 'static, + additionals: impl Iterator, +) -> impl Iterator { //let p = p.as_ref(); - ALL_PROTOS.iter().map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned()).chain(custom_protos_filenames()) + ALL_PROTOS + .iter() + .map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned()) + .chain(custom_protos_filenames()) + .chain(additionals) } pub fn dump_protos(p: impl AsRef) -> std::io::Result<()> { diff --git a/usdpl-core/src/lib.rs b/usdpl-core/src/lib.rs index 7090ffa..f5453bc 100644 --- a/usdpl-core/src/lib.rs +++ b/usdpl-core/src/lib.rs @@ -4,12 +4,10 @@ mod remote_call; -#[cfg(not(any(feature = "decky", feature = "crankshaft")))] +#[cfg(not(any(feature = "decky")))] mod api_any; mod api_common; -#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] -mod api_crankshaft; -#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] +#[cfg(all(feature = "decky", not(any(feature = "any"))))] mod api_decky; pub mod serdes; @@ -20,11 +18,9 @@ pub use remote_call::{RemoteCall, RemoteCallResponse}; /// USDPL core API. /// This contains functionality used in both the back-end and front-end. pub mod api { - #[cfg(not(any(feature = "decky", feature = "crankshaft")))] + #[cfg(not(any(feature = "decky")))] pub use super::api_any::*; pub use super::api_common::*; - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - pub use super::api_crankshaft::*; - #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] + #[cfg(all(feature = "decky", not(any(feature = "any"))))] pub use super::api_decky::*; } diff --git a/usdpl-core/src/remote_call.rs b/usdpl-core/src/remote_call.rs index e189304..973f0db 100644 --- a/usdpl-core/src/remote_call.rs +++ b/usdpl-core/src/remote_call.rs @@ -73,7 +73,7 @@ mod tests { #[test] fn remote_call_idempotence_test() { - let call = RemoteCall{ + let call = RemoteCall { id: 42, function: "something very long just in case this causes unexpected issues".into(), parameters: vec!["param1".into(), 42f64.into()], @@ -88,7 +88,10 @@ mod tests { assert_eq!(len, loaded_len, "Expected load and dump lengths to match"); assert_eq!(loaded_call.id, call.id, "RemoteCall.id does not match"); - assert_eq!(loaded_call.function, call.function, "RemoteCall.function does not match"); + assert_eq!( + loaded_call.function, call.function, + "RemoteCall.function does not match" + ); if let Primitive::String(loaded) = &loaded_call.parameters[0] { if let Primitive::String(original) = &call.parameters[0] { assert_eq!(loaded, original, "RemoteCall.parameters[0] does not match"); diff --git a/usdpl-core/src/serdes/dump_impl.rs b/usdpl-core/src/serdes/dump_impl.rs index a74f08c..61551fe 100644 --- a/usdpl-core/src/serdes/dump_impl.rs +++ b/usdpl-core/src/serdes/dump_impl.rs @@ -26,43 +26,34 @@ impl Dumpable for Vec { impl Dumpable for (T0, T1) { fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - ) + Ok(self.0.dump(buffer)? + self.1.dump(buffer)?) } } impl Dumpable for (T0, T1, T2) { fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - + self.2.dump(buffer)? - ) + Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)?) } } impl Dumpable for (T0, T1, T2, T3) { fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? + Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)? - + self.3.dump(buffer)? - ) + + self.3.dump(buffer)?) } } -impl Dumpable for (T0, T1, T2, T3, T4) { +impl Dumpable + for (T0, T1, T2, T3, T4) +{ fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? + Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)? + self.3.dump(buffer)? - + self.4.dump(buffer)? - ) + + self.4.dump(buffer)?) } } diff --git a/usdpl-core/src/serdes/load_impl.rs b/usdpl-core/src/serdes/load_impl.rs index 2e37097..f2aa9bf 100644 --- a/usdpl-core/src/serdes/load_impl.rs +++ b/usdpl-core/src/serdes/load_impl.rs @@ -42,10 +42,7 @@ impl Loadable for (T0, T1) { fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { let (t0, len0) = T0::load(buffer)?; let (t1, len1) = T1::load(buffer)?; - Ok(( - (t0, t1), - len0 + len1 - )) + Ok(((t0, t1), len0 + len1)) } } @@ -54,10 +51,7 @@ impl Loadable for (T0, T1, T2) { let (t0, len0) = T0::load(buffer)?; let (t1, len1) = T1::load(buffer)?; let (t2, len2) = T2::load(buffer)?; - Ok(( - (t0, t1, t2), - len0 + len1 + len2 - )) + Ok(((t0, t1, t2), len0 + len1 + len2)) } } @@ -67,24 +61,20 @@ impl Loadable for (T0, T let (t1, len1) = T1::load(buffer)?; let (t2, len2) = T2::load(buffer)?; let (t3, len3) = T3::load(buffer)?; - Ok(( - (t0, t1, t2, t3), - len0 + len1 + len2 + len3 - )) + Ok(((t0, t1, t2, t3), len0 + len1 + len2 + len3)) } } -impl Loadable for (T0, T1, T2, T3, T4) { +impl Loadable + for (T0, T1, T2, T3, T4) +{ fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { let (t0, len0) = T0::load(buffer)?; let (t1, len1) = T1::load(buffer)?; let (t2, len2) = T2::load(buffer)?; let (t3, len3) = T3::load(buffer)?; let (t4, len4) = T4::load(buffer)?; - Ok(( - (t0, t1, t2, t3, t4), - len0 + len1 + len2 + len3 + len4 - )) + Ok(((t0, t1, t2, t3, t4), len0 + len1 + len2 + len3 + len4)) } } diff --git a/usdpl-core/src/serdes/primitive.rs b/usdpl-core/src/serdes/primitive.rs index d652ea1..9793bff 100644 --- a/usdpl-core/src/serdes/primitive.rs +++ b/usdpl-core/src/serdes/primitive.rs @@ -1,5 +1,5 @@ -use std::io::{Read, Write}; use super::{DumpError, Dumpable, LoadError, Loadable}; +use std::io::{Read, Write}; /// Primitive types supported for communication between the USDPL back- and front-end. /// These are used for sending over the TCP connection. @@ -47,7 +47,8 @@ impl Primitive { impl Loadable for Primitive { fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> { let mut discriminant_buf = [u8::MAX; 1]; - buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?; + buf.read_exact(&mut discriminant_buf) + .map_err(LoadError::Io)?; let mut result: (Self, usize) = match discriminant_buf[0] { //0 => (None, 0), 1 => (Self::Empty, 0), @@ -105,7 +106,7 @@ macro_rules! into_impl { Primitive::$variant(self) } } - } + }; } into_impl! {String, String} diff --git a/usdpl-core/src/serdes/traits.rs b/usdpl-core/src/serdes/traits.rs index 07bc2fd..b11b5db 100644 --- a/usdpl-core/src/serdes/traits.rs +++ b/usdpl-core/src/serdes/traits.rs @@ -1,5 +1,5 @@ -use std::io::{Read, Write, Cursor}; use base64::{decode_config_buf, encode_config_buf, Config}; +use std::io::{Cursor, Read, Write}; const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true); @@ -49,8 +49,7 @@ pub trait Loadable: Sized { /// Load data from a base64-encoded buffer fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> { let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - decode_config_buf(buffer, B64_CONF, &mut buffer2) - .map_err(|_| LoadError::InvalidData)?; + decode_config_buf(buffer, B64_CONF, &mut buffer2).map_err(|_| LoadError::InvalidData)?; let mut cursor = Cursor::new(buffer2); Self::load(&mut cursor) } @@ -66,7 +65,9 @@ pub trait Loadable: Sized { base64::decode_config_buf(buffer, B64_CONF, &mut decoded_buf) .map_err(|_| LoadError::InvalidData)?; //println!("Decoded buf: {:?}", decoded_buf); - cipher.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf).map_err(|_| LoadError::DecryptionError)?; + cipher + .decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf) + .map_err(|_| LoadError::DecryptionError)?; //println!("Decrypted buf: {:?}", decoded_buf); let mut cursor = Cursor::new(decoded_buf); Self::load(&mut cursor) @@ -121,7 +122,12 @@ pub trait Dumpable { /// Dump data as an encrypted base64-encoded buffer #[cfg(feature = "encrypt")] - fn dump_encrypted(&self, buffer: &mut Vec, key: &[u8], nonce: &[u8]) -> Result { + fn dump_encrypted( + &self, + buffer: &mut Vec, + key: &[u8], + nonce: &[u8], + ) -> Result { let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); let size = self.dump(&mut buffer2)?; buffer2.truncate(size); @@ -129,7 +135,9 @@ pub trait Dumpable { let key = aes_gcm_siv::Key::from_slice(key); let cipher = aes_gcm_siv::Aes256GcmSiv::new(key); let nonce = aes_gcm_siv::Nonce::from_slice(nonce); - cipher.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2).map_err(|_| DumpError::EncryptionError)?; + cipher + .encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2) + .map_err(|_| DumpError::EncryptionError)?; //println!("Encrypted slice: {:?}", &buffer2); let mut base64_buf = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE); encode_config_buf(buffer2.as_slice(), B64_CONF, &mut base64_buf); diff --git a/usdpl-core/src/socket.rs b/usdpl-core/src/socket.rs index 0f72a01..6b8940e 100644 --- a/usdpl-core/src/socket.rs +++ b/usdpl-core/src/socket.rs @@ -1,6 +1,6 @@ //! Web messaging -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::io::{Read, Write}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use crate::serdes::{DumpError, Dumpable, LoadError, Loadable}; use crate::{RemoteCall, RemoteCallResponse}; @@ -66,7 +66,8 @@ impl Packet { impl Loadable for Packet { fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> { let mut discriminant_buf = [u8::MAX; 1]; - buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?; + buf.read_exact(&mut discriminant_buf) + .map_err(LoadError::Io)?; let mut result: (Self, usize) = match discriminant_buf[0] { //0 => (None, 0), 1 => { @@ -88,15 +89,15 @@ impl Loadable for Packet { 8 => { let (obj, len) = <_>::load(buf)?; (Self::Many(obj), len) - }, + } 9 => { let (obj, len) = <_>::load(buf)?; (Self::Translations(obj), len) - }, + } 10 => { let (obj, len) = <_>::load(buf)?; (Self::Language(obj), len) - }, + } _ => return Err(LoadError::InvalidData), }; result.1 += 1; @@ -130,24 +131,39 @@ mod tests { #[cfg(feature = "encrypt")] #[test] fn encryption_integration_test() { - let key = hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265"); + let key = + hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265"); let nonce = [0u8; NONCE_SIZE]; - let packet = Packet::Call(RemoteCall{ + let packet = Packet::Call(RemoteCall { id: 42, function: "test".into(), parameters: Vec::new(), }); let mut buffer = Vec::with_capacity(PACKET_BUFFER_SIZE); let len = packet.dump_encrypted(&mut buffer, &key, &nonce).unwrap(); - println!("buffer: {}", String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap()); + println!( + "buffer: {}", + String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap() + ); - let (packet_out, _len) = Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap(); + let (packet_out, _len) = + Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap(); if let Packet::Call(call_out) = packet_out { if let Packet::Call(call_in) = packet { - assert_eq!(call_in.id, call_out.id, "Input and output packets do not match"); - assert_eq!(call_in.function, call_out.function, "Input and output packets do not match"); - assert_eq!(call_in.parameters.len(), call_out.parameters.len(), "Input and output packets do not match"); + assert_eq!( + call_in.id, call_out.id, + "Input and output packets do not match" + ); + assert_eq!( + call_in.function, call_out.function, + "Input and output packets do not match" + ); + assert_eq!( + call_in.parameters.len(), + call_out.parameters.len(), + "Input and output packets do not match" + ); } else { panic!("Packet in not a Call"); } diff --git a/usdpl-front/Cargo.toml b/usdpl-front/Cargo.toml index afffe0c..a28ea0e 100644 --- a/usdpl-front/Cargo.toml +++ b/usdpl-front/Cargo.toml @@ -50,6 +50,3 @@ prost = "0.11" [dev-dependencies] wasm-bindgen-test = { version = "0.3.13" } - -[build-dependencies] -usdpl-build = { version = "0.11", path = "../usdpl-build" } diff --git a/usdpl-front/build.rs b/usdpl-front/build.rs deleted file mode 100644 index 16e4f14..0000000 --- a/usdpl-front/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - usdpl_build::front::build() -} diff --git a/usdpl-front/src/client_handler.rs b/usdpl-front/src/client_handler.rs index b592370..7d8fe94 100644 --- a/usdpl-front/src/client_handler.rs +++ b/usdpl-front/src/client_handler.rs @@ -1,20 +1,23 @@ use std::sync::atomic::{AtomicU64, Ordering}; -use nrpc::{ClientHandler, ServiceError, _helpers::bytes, _helpers::async_trait}; -use gloo_net::websocket::{Message, futures::WebSocket}; -use wasm_bindgen_futures::spawn_local; use futures::{SinkExt, StreamExt}; +use gloo_net::websocket::{futures::WebSocket, Message}; +use nrpc::{ClientHandler, ServiceError, _helpers::async_trait, _helpers::bytes}; +use wasm_bindgen_futures::spawn_local; static LAST_ID: AtomicU64 = AtomicU64::new(0); +/// Websocket client. +/// In most cases, this shouldn't be used directly, but generated code will use this. pub struct WebSocketHandler { - // TODO port: u16, } async fn send_recv_ws(url: String, input: bytes::Bytes) -> Result, String> { let mut ws = WebSocket::open(&url).map_err(|e| e.to_string())?; - ws.send(Message::Bytes(input.into())).await.map_err(|e| e.to_string())?; + ws.send(Message::Bytes(input.into())) + .await + .map_err(|e| e.to_string())?; read_next_incoming(ws).await } @@ -42,7 +45,7 @@ impl std::fmt::Display for ErrorStr { impl std::error::Error for ErrorStr {} impl WebSocketHandler { - #[allow(dead_code)] + /// Instantiate the web socket client for connecting on the specified port pub fn new(port: u16) -> Self { Self { port } } @@ -50,33 +53,29 @@ impl WebSocketHandler { #[async_trait::async_trait] impl ClientHandler for WebSocketHandler { - async fn call(&mut self, - package: &str, - service: &str, - method: &str, - input: bytes::Bytes, - output: &mut bytes::BytesMut) -> Result<(), ServiceError> { + async fn call( + &mut self, + package: &str, + service: &str, + method: &str, + input: bytes::Bytes, + output: &mut bytes::BytesMut, + ) -> Result<(), ServiceError> { let id = LAST_ID.fetch_add(1, Ordering::SeqCst); let url = format!( "ws://usdpl-ws-{}.localhost:{}/{}.{}/{}", - id, - self.port, - package, - service, - method, + id, self.port, package, service, method, ); let (tx, rx) = async_channel::bounded(1); spawn_local(async move { - tx.send(send_recv_ws( - url, - input - ).await).await.unwrap_or(()); + tx.send(send_recv_ws(url, input).await).await.unwrap_or(()); }); output.extend_from_slice( - &rx.recv().await + &rx.recv() + .await .map_err(|e| ServiceError::Method(Box::new(e)))? - .map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))? + .map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?, ); Ok(()) } diff --git a/usdpl-front/src/connection.rs b/usdpl-front/src/connection.rs index a41979a..442e312 100644 --- a/usdpl-front/src/connection.rs +++ b/usdpl-front/src/connection.rs @@ -14,26 +14,33 @@ use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; use usdpl_core::socket; #[cfg(feature = "encrypt")] -const NONCE: [u8; socket::NONCE_SIZE]= [0u8; socket::NONCE_SIZE]; +const NONCE: [u8; socket::NONCE_SIZE] = [0u8; socket::NONCE_SIZE]; pub async fn send_recv_packet( id: u64, packet: socket::Packet, port: u16, - #[cfg(feature = "encrypt")] - key: Vec, + #[cfg(feature = "encrypt")] key: Vec, ) -> Result { - let mut opts = RequestInit::new(); opts.method("POST"); opts.mode(RequestMode::Cors); - let url = format!("http://usdpl{}.{}:{}/usdpl/call", id, socket::HOST_STR, port); + let url = format!( + "http://usdpl{}.{}:{}/usdpl/call", + id, + socket::HOST_STR, + port + ); #[allow(unused_variables)] - let (buffer, len) = dump_to_buffer(packet, #[cfg(feature = "encrypt")] key.as_slice())?; + let (buffer, len) = dump_to_buffer( + packet, + #[cfg(feature = "encrypt")] + key.as_slice(), + )?; let string: String = String::from_utf8_lossy(buffer.as_slice()).into(); - #[cfg(feature="debug")] + #[cfg(feature = "debug")] crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len)); opts.body(Some(&string.into())); @@ -50,31 +57,46 @@ pub async fn send_recv_packet( let string: JsString = text.dyn_into()?; let rust_str = string.as_string().unwrap(); - #[cfg(feature="debug")] - crate::imports::console_log(&format!("Received base64 `{}` len:{}", rust_str, rust_str.len())); + #[cfg(feature = "debug")] + crate::imports::console_log(&format!( + "Received base64 `{}` len:{}", + rust_str, + rust_str.len() + )); #[cfg(not(feature = "encrypt"))] - {Ok(socket::Packet::load_base64(rust_str.as_bytes()) - .map_err(super::convert::str_to_js)? - .0)} + { + Ok(socket::Packet::load_base64(rust_str.as_bytes()) + .map_err(super::convert::str_to_js)? + .0) + } #[cfg(feature = "encrypt")] - {Ok(socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE) - .map_err(super::convert::str_to_js)? - .0)} + { + Ok( + socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE) + .map_err(super::convert::str_to_js)? + .0, + ) + } } pub async fn send_call( id: u64, packet: socket::Packet, port: u16, - #[cfg(feature = "encrypt")] - key: Vec, + #[cfg(feature = "encrypt")] key: Vec, ) -> Result, JsValue> { - let packet = send_recv_packet(id, packet, port, #[cfg(feature = "encrypt")] key).await?; + let packet = send_recv_packet( + id, + packet, + port, + #[cfg(feature = "encrypt")] + key, + ) + .await?; - match packet - { + match packet { socket::Packet::CallResponse(resp) => Ok(resp.response), _ => { //imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url())); diff --git a/usdpl-front/src/lib.rs b/usdpl-front/src/lib.rs index dd173f2..96942aa 100644 --- a/usdpl-front/src/lib.rs +++ b/usdpl-front/src/lib.rs @@ -6,13 +6,22 @@ #![warn(missing_docs)] mod client_handler; +pub use client_handler::WebSocketHandler; mod connection; mod convert; mod imports; +pub mod wasm; -#[allow(missing_docs)] // existence is pain otherwise +/*#[allow(missing_docs)] // existence is pain otherwise pub mod _nrpc_js_interop { - include!(concat!(env!("OUT_DIR"), "/usdpl.rs")); + include!(concat!(env!("OUT_DIR"), "/mod.rs")); +}*/ + +#[allow(missing_docs)] +pub mod _helpers { + pub use js_sys; + pub use wasm_bindgen; + pub use wasm_bindgen_futures; } use std::sync::atomic::{AtomicU64, Ordering}; @@ -99,7 +108,11 @@ pub fn version_usdpl() -> String { #[wasm_bindgen] pub fn set_value(key: String, value: JsValue) -> JsValue { unsafe { - CACHE.as_mut().unwrap().insert(key, value).unwrap_or(JsValue::NULL) + CACHE + .as_mut() + .unwrap() + .insert(key, value) + .unwrap_or(JsValue::NULL) } } @@ -107,7 +120,12 @@ pub fn set_value(key: String, value: JsValue) -> JsValue { #[wasm_bindgen] pub fn get_value(key: String) -> JsValue { unsafe { - CACHE.as_ref().unwrap().get(&key).map(|x| x.to_owned()).unwrap_or(JsValue::UNDEFINED) + CACHE + .as_ref() + .unwrap() + .get(&key) + .map(|x| x.to_owned()) + .unwrap_or(JsValue::UNDEFINED) } } @@ -138,7 +156,7 @@ pub async fn call_backend(name: String, parameters: Vec) -> JsValue { }), port, #[cfg(feature = "encrypt")] - get_key() + get_key(), ) .await; let results = match results { @@ -168,8 +186,10 @@ pub async fn init_tr(locale: String) { Packet::Language(locale.clone()), get_port(), #[cfg(feature = "encrypt")] - get_key() - ).await { + get_key(), + ) + .await + { Ok(Packet::Translations(translations)) => { #[cfg(feature = "debug")] imports::console_log(&format!("USDPL: Got translations for {}", locale)); @@ -179,12 +199,12 @@ pub async fn init_tr(locale: String) { tr_map.insert(key, val); } unsafe { TRANSLATIONS = Some(tr_map) } - }, + } Ok(_) => { #[cfg(feature = "debug")] imports::console_error(&format!("USDPL: Got wrong packet response for init_tr")); unsafe { TRANSLATIONS = None } - }, + } #[allow(unused_variables)] Err(e) => { #[cfg(feature = "debug")] diff --git a/usdpl-front/src/wasm/arrays.rs b/usdpl-front/src/wasm/arrays.rs new file mode 100644 index 0000000..45dd935 --- /dev/null +++ b/usdpl-front/src/wasm/arrays.rs @@ -0,0 +1,94 @@ +use js_sys::Array; + +use super::{FromWasmable, IntoWasmable}; + +macro_rules! numbers_array { + ($num_ty: ident) => { + impl FromWasmable for Vec<$num_ty> { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_f64() { + result.push(val as $num_ty); + } + }); + result + } + } + + impl IntoWasmable for Vec<$num_ty> { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } + } + }; +} + +numbers_array! { f64 } +numbers_array! { f32 } + +numbers_array! { isize } +numbers_array! { usize } + +numbers_array! { i8 } +numbers_array! { i16 } +numbers_array! { i32 } +numbers_array! { i64 } +numbers_array! { i128 } + +numbers_array! { u8 } +numbers_array! { u16 } +numbers_array! { u32 } +numbers_array! { u64 } +numbers_array! { u128 } + +impl FromWasmable for Vec { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_string() { + result.push(val); + } + }); + result + } +} + +impl IntoWasmable for Vec { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } +} + +impl FromWasmable for Vec { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_bool() { + result.push(val); + } + }); + result + } +} + +impl IntoWasmable for Vec { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } +} diff --git a/usdpl-front/src/wasm/maps.rs b/usdpl-front/src/wasm/maps.rs new file mode 100644 index 0000000..d7dcbcd --- /dev/null +++ b/usdpl-front/src/wasm/maps.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use js_sys::Map; + +use super::{FromWasmable, IntoWasmable}; + +macro_rules! numbers_map { + ($num_ty: ident) => { + impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_f64() { + result.insert(key, val as $num_ty); + } + } + }); + result + } + } + + impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } + } + }; +} + +numbers_map! { f64 } +numbers_map! { f32 } + +numbers_map! { isize } +numbers_map! { usize } + +numbers_map! { i8 } +numbers_map! { i16 } +numbers_map! { i32 } +numbers_map! { i64 } +numbers_map! { i128 } + +numbers_map! { u8 } +numbers_map! { u16 } +numbers_map! { u32 } +numbers_map! { u64 } +numbers_map! { u128 } + +impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_string() { + result.insert(key, val); + } + } + }); + result + } +} + +impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } +} + +impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_bool() { + result.insert(key, val); + } + } + }); + result + } +} + +impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } +} diff --git a/usdpl-front/src/wasm/mod.rs b/usdpl-front/src/wasm/mod.rs new file mode 100644 index 0000000..6e9d7b1 --- /dev/null +++ b/usdpl-front/src/wasm/mod.rs @@ -0,0 +1,7 @@ +//! WASM <-> Rust interop utilities +mod arrays; +mod maps; +mod trivials; +mod wasm_traits; + +pub use wasm_traits::*; diff --git a/usdpl-front/src/wasm/trivials.rs b/usdpl-front/src/wasm/trivials.rs new file mode 100644 index 0000000..3187e26 --- /dev/null +++ b/usdpl-front/src/wasm/trivials.rs @@ -0,0 +1,40 @@ +use super::{FromWasmable, IntoWasmable}; + +macro_rules! trivial_convert { + ($ty: ty) => { + impl FromWasmable<$ty> for $ty { + fn from_wasm(js: $ty) -> Self { + js + } + } + + impl IntoWasmable<$ty> for $ty { + fn into_wasm(self) -> $ty { + self + } + } + }; +} + +trivial_convert! { f64 } +trivial_convert! { f32 } + +trivial_convert! { isize } +trivial_convert! { usize } + +trivial_convert! { i8 } +trivial_convert! { i16 } +trivial_convert! { i32 } +trivial_convert! { i64 } +trivial_convert! { i128 } + +trivial_convert! { u8 } +trivial_convert! { u16 } +trivial_convert! { u32 } +trivial_convert! { u64 } +trivial_convert! { u128 } + +trivial_convert! { bool } +trivial_convert! { String } + +trivial_convert! { () } diff --git a/usdpl-front/src/wasm/wasm_traits.rs b/usdpl-front/src/wasm/wasm_traits.rs new file mode 100644 index 0000000..ec1ec9f --- /dev/null +++ b/usdpl-front/src/wasm/wasm_traits.rs @@ -0,0 +1,40 @@ +/// A Rust type which supports Into/FromWasmAbi or WasmDescribe +pub trait KnownWasmCompatible {} + +/// Convert Rust type to WASM-compatible type +pub trait IntoWasmable { + /// Required method + fn into_wasm(self) -> T; +} + +/// Convert WASM-compatible type to Rust-centric type +pub trait FromWasmable { + /// Required method + fn from_wasm(js: T) -> Self; +} + +impl KnownWasmCompatible for f64 {} +impl KnownWasmCompatible for f32 {} + +impl KnownWasmCompatible for isize {} +impl KnownWasmCompatible for usize {} + +impl KnownWasmCompatible for i8 {} +impl KnownWasmCompatible for i16 {} +impl KnownWasmCompatible for i32 {} +impl KnownWasmCompatible for i64 {} +impl KnownWasmCompatible for i128 {} + +impl KnownWasmCompatible for u8 {} +impl KnownWasmCompatible for u16 {} +impl KnownWasmCompatible for u32 {} +impl KnownWasmCompatible for u64 {} +impl KnownWasmCompatible for u128 {} + +impl KnownWasmCompatible for bool {} +impl KnownWasmCompatible for String {} + +impl KnownWasmCompatible for () {} + +impl KnownWasmCompatible for js_sys::Map {} +impl KnownWasmCompatible for js_sys::Array {}