Improve flexibility of WASM conversions in code gen

This commit is contained in:
NGnius (Graham) 2023-06-04 14:05:33 -04:00
parent febaafe50c
commit 0b44ebc12b
33 changed files with 1028 additions and 545 deletions

1
Cargo.lock generated
View file

@ -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",

View file

@ -1,3 +0,0 @@
fn main() {
usdpl_build::back::build()
}

View file

@ -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<PathBuf> {
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);

View file

@ -7,12 +7,10 @@ pub fn home() -> Option<PathBuf> {
#[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()
let result = crate::api_decky::home()
.ok()
).flatten();
.map(|x| PathBuf::from(x).join("..").canonicalize().ok())
.flatten();
result
}

View file

@ -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,9 +31,7 @@ impl<E: std::error::Error> std::fmt::Display for ReadError<E> {
}
}
impl<E: std::error::Error> std::error::Error for ReadError<E> {
}
impl<E: std::error::Error> std::error::Error for ReadError<E> {}
/// Read something from a file.
/// Useful for kernel configuration files.

View file

@ -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"));
}
}*/

View file

@ -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<bytes::Bytes, ServiceError> {
pub async fn call_descriptor(
&self,
descriptor: &str,
method: &str,
data: bytes::Bytes,
) -> Result<bytes::Bytes, ServiceError> {
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<S: ServerService + Send + 'a>(&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
}

View file

@ -61,7 +61,10 @@ 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(),
@ -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(
let response = services
.call_descriptor(
descriptor.service,
descriptor.method,
buf.clone().freeze()
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")
}

View file

@ -22,4 +22,6 @@ message LogMessage {
string msg = 2;
}
message Empty {}
message Empty {
bool ok = 1;
}

View file

@ -1,7 +1,10 @@
pub fn build() {
pub fn build(
custom_protos: impl Iterator<Item = String>,
custom_dirs: impl Iterator<Item = String>,
) {
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),
)
}

View file

@ -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<Item = String>,
custom_dirs: impl Iterator<Item = String>,
) {
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()
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)))
.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()
}

View file

@ -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());
self.shared.lock().expect("Cannot lock shared state").fds = Some(fds.clone());
quote::quote! {}
}
}

View file

@ -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,32 +18,52 @@ 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"));
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: #type_name,
#field_name: #js_type_name,
});
params_to_fields.push(quote::quote! {
#field_name,//: #field_name,
#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"));
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 val = #method_input::from_wasm(#field_name.into());
}
} else if input_type.field.is_empty() {
quote::quote! {
@ -57,49 +77,56 @@ fn generate_service_methods(service: &Service, fds: &FileDescriptorSet) -> proc_
}
};
let special_fn_into_input = quote::format_ident!("{}_convert_into", method.input_type.split('.').last().unwrap().to_lowercase());
let special_fn_from_output = quote::format_ident!("{}_convert_from", method.output_type.split('.').last().unwrap().to_lowercase());
gen_methods.push(
quote::quote!{
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)),
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! {
#(#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 {
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 let Some(pkg) = &file.package {
if name == want_type && pkg == want_package {
if name == want_type {
return Some(message_type);
}
}
}
}
}
}
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<String>, handled_types: &mut HashSet<String>, 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<String>) -> 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<String>,
handled_types: &mut HashSet<String>,
known_maps: &mut HashSet<String>,
seen_super_enums: &mut HashSet<String>,
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),
};
//dbg!("Generated map type", name);
let map_tokens = map_type.to_tokens();
let wasm_tokens = map_type.to_wasm_tokens();
return quote::quote! {
pub type #msg_name = ::js_sys::Map;
pub type #msg_name = #map_tokens;
pub type #msg_name_wasm = #wasm_tokens;
};
}
}
// TODO Deal with other message options when necessary
}
#fn_from
#fn_into
}
}
} else {
todo!("Deal with 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 {
if descriptor.field.len() == 0 {
quote::quote! {
pub type #msg_name = ();
pub type #msg_name_wasm = #msg_name;
#(#gen_nested_types)*
#(#gen_enums)*
}
} else 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)
}
//dbg!(descriptor, field);
let field_name = quote::format_ident!(
"{}",
field
.name
.as_ref()
.expect("Protobuf message field needs a name")
);
gen_from_fields.push(
quote::quote!{
#special_fn_from(other.#field_name)
}
);
} else {
gen_into_fields.push(
quote::quote!{
#field_name: this
}
);
gen_from_fields.push(
quote::quote!{
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 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();
quote::quote! {
pub type #msg_name = #type_name;
pub type #msg_name_wasm = #wasm_type_name;
impl std::convert::Into<super::#super_msg_name> for #msg_name {
#[inline]
#[allow(dead_code)]
fn #special_fn_from(other: super::#super_msg_name) -> #msg_name {
#(#gen_from_fields)*
fn into(self) -> super::#super_msg_name {
super::#super_msg_name {
#field_name: self
}
}
}
impl std::convert::From<super::#super_msg_name> for #msg_name {
#[inline]
#[allow(dead_code)]
fn #special_fn_into(this: #msg_name) -> super::#super_msg_name {
super::#super_msg_name {
#(#gen_into_fields)*
#[allow(unused_variables)]
fn from(other: super::#super_msg_name) -> Self {
other.#field_name
}
}
@ -329,45 +324,31 @@ 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();
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 {
let wasm_attribute_maybe =
if (descriptor.field.len() == 1 || !is_response_msg) && !descriptor.field.is_empty() {
quote::quote! {}
} else {
quote::quote! {
@ -381,6 +362,22 @@ fn generate_wasm_struct_interop(descriptor: &DescriptorProto, handled_enums: &mu
#(#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<super::#super_msg_name> 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<ProtobufType>),
Map {
key: Box<ProtobufType>,
value: Box<ProtobufType>,
},
Custom(String),
}
@ -464,31 +453,40 @@ 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<u8>},
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
}
}
@ -509,17 +507,77 @@ impl ProtobufType {
Self::Bool => quote::quote! {bool},
Self::String => quote::quote! {String},
Self::Bytes => quote::quote! {Vec<u8>},
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}
},
}
}
}
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 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<u8>},
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,
seen_super_enums: &mut HashSet<String>,
) -> 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,40 +586,44 @@ 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!{
gen_values.push(quote::quote! {
#val_name = #number,
}
);
});
} else {
gen_values.push(
quote::quote!{
gen_values.push(quote::quote! {
#val_name,
});
}
);
}
gen_into_values.push(
quote::quote!{
gen_into_values.push(quote::quote! {
Self::#val_name => super::#super_enum_name::#val_name,
}
);
});
gen_from_values.push(
quote::quote!{
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());
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]
@ -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<super::#super_enum_name> 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 {
#impl_wasm_compat
impl FromWasmable<i32> for #enum_name {
fn from_wasm(js: i32) -> Self {
#enum_name::from(super::#super_enum_name::from_i32(js).unwrap())
}
}
impl IntoWasmable<i32> for #enum_name {
fn into_wasm(self) -> i32 {
self as i32
}
}
impl From<i32> for #enum_name {
fn from(other: i32) -> Self {
#enum_name::from(super::#super_enum_name::from_i32(other).unwrap())
}
}
#[inline]
#[allow(dead_code)]
fn #special_fn_into(this: #enum_name) -> i32 {
this as i32
impl Into<i32> 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 {
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));
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);
@ -671,9 +806,15 @@ impl IServiceGenerator for WasmServiceGenerator {
let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase());
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

View file

@ -7,9 +7,7 @@ pub struct SharedState(Arc<Mutex<SharedProtoData>>);
impl SharedState {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(SharedProtoData {
fds: None,
})))
Self(Arc::new(Mutex::new(SharedProtoData { fds: None })))
}
}

View file

@ -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,
};

View file

@ -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<Item = String> {
pub fn proto_out_paths(additionals: impl Iterator<Item = String>) -> impl Iterator<Item = String> {
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<String> {
fn custom_protos_dirs(additionals: impl Iterator<Item = String>) -> Vec<String> {
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<String> {
.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<Path> + 'static) -> impl Iterator<Item = String> {
pub fn all_proto_filenames(
p: impl AsRef<Path> + 'static,
additionals: impl Iterator<Item = String>,
) -> impl Iterator<Item = String> {
//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<Path>) -> std::io::Result<()> {

View file

@ -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::*;
}

View file

@ -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");

View file

@ -26,43 +26,34 @@ impl<T: Dumpable> Dumpable for Vec<T> {
impl<T0: Dumpable, T1: Dumpable> Dumpable for (T0, T1) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
Ok(
self.0.dump(buffer)?
+ self.1.dump(buffer)?
)
Ok(self.0.dump(buffer)? + self.1.dump(buffer)?)
}
}
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable> Dumpable for (T0, T1, T2) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
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<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable> Dumpable for (T0, T1, T2, T3) {
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
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<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable for (T0, T1, T2, T3, T4) {
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable
for (T0, T1, T2, T3, T4)
{
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
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)?)
}
}

View file

@ -42,10 +42,7 @@ impl<T0: Loadable, T1: Loadable> 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<T0: Loadable, T1: Loadable, T2: Loadable> 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<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable> 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<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable for (T0, T1, T2, T3, T4) {
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> 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))
}
}

View file

@ -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}

View file

@ -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<u8>, key: &[u8], nonce: &[u8]) -> Result<usize, DumpError> {
fn dump_encrypted(
&self,
buffer: &mut Vec<u8>,
key: &[u8],
nonce: &[u8],
) -> Result<usize, DumpError> {
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);

View file

@ -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,7 +131,8 @@ 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 {
id: 42,
@ -139,15 +141,29 @@ mod tests {
});
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");
}

View file

@ -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" }

View file

@ -1,3 +0,0 @@
fn main() {
usdpl_build::front::build()
}

View file

@ -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<Vec<u8>, 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,
async fn call(
&mut self,
package: &str,
service: &str,
method: &str,
input: bytes::Bytes,
output: &mut bytes::BytesMut) -> Result<(), ServiceError> {
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(())
}

View file

@ -20,18 +20,25 @@ pub async fn send_recv_packet(
id: u64,
packet: socket::Packet,
port: u16,
#[cfg(feature = "encrypt")]
key: Vec<u8>,
#[cfg(feature = "encrypt")] key: Vec<u8>,
) -> Result<socket::Packet, JsValue> {
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")]
crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len));
@ -51,30 +58,45 @@ pub async fn send_recv_packet(
let rust_str = string.as_string().unwrap();
#[cfg(feature = "debug")]
crate::imports::console_log(&format!("Received base64 `{}` len:{}", rust_str, rust_str.len()));
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())
{
Ok(socket::Packet::load_base64(rust_str.as_bytes())
.map_err(super::convert::str_to_js)?
.0)}
.0)
}
#[cfg(feature = "encrypt")]
{Ok(socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
{
Ok(
socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
.map_err(super::convert::str_to_js)?
.0)}
.0,
)
}
}
pub async fn send_call(
id: u64,
packet: socket::Packet,
port: u16,
#[cfg(feature = "encrypt")]
key: Vec<u8>,
#[cfg(feature = "encrypt")] key: Vec<u8>,
) -> Result<Vec<Primitive>, 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()));

View file

@ -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>) -> 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")]

View file

@ -0,0 +1,94 @@
use js_sys::Array;
use super::{FromWasmable, IntoWasmable};
macro_rules! numbers_array {
($num_ty: ident) => {
impl FromWasmable<Array> 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<Array> 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<Array> for Vec<String> {
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<Array> for Vec<String> {
fn into_wasm(self) -> Array {
let result = Array::new();
for val in self {
result.push(&val.into());
}
result
}
}
impl FromWasmable<Array> for Vec<bool> {
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<Array> for Vec<bool> {
fn into_wasm(self) -> Array {
let result = Array::new();
for val in self {
result.push(&val.into());
}
result
}
}

View file

@ -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<Map> for HashMap<String, $num_ty> {
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<Map> for HashMap<String, $num_ty> {
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<Map> for HashMap<String, String> {
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<Map> for HashMap<String, String> {
fn into_wasm(self) -> Map {
let result = Map::new();
for (key, val) in self {
result.set(&key.into(), &val.into());
}
result
}
}
impl FromWasmable<Map> for HashMap<String, bool> {
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<Map> for HashMap<String, bool> {
fn into_wasm(self) -> Map {
let result = Map::new();
for (key, val) in self {
result.set(&key.into(), &val.into());
}
result
}
}

View file

@ -0,0 +1,7 @@
//! WASM <-> Rust interop utilities
mod arrays;
mod maps;
mod trivials;
mod wasm_traits;
pub use wasm_traits::*;

View file

@ -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! { () }

View file

@ -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<T: KnownWasmCompatible> {
/// Required method
fn into_wasm(self) -> T;
}
/// Convert WASM-compatible type to Rust-centric type
pub trait FromWasmable<T: KnownWasmCompatible> {
/// 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 {}