Make front codegen work with multi-field structs containing Strings, create decky template

This commit is contained in:
NGnius (Graham) 2024-10-23 22:35:20 -04:00
parent ed5f96361b
commit 9171682e4d
15 changed files with 606 additions and 601 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "templates/decky"]
path = templates/decky
url = https://git.ngni.us/NG-SD-Plugins/usdpl-decky-plugin-template

833
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,8 @@ members = [
]
exclude = [
"templates/decky/backend"
"templates/decky/backend",
"templates/decky/src/rust"
]
resolver = "2"

1
templates/decky Submodule

@ -0,0 +1 @@
Subproject commit b82d3edf77deba94beef53d4d32c705487da918c

View file

@ -11,8 +11,8 @@ description = "Universal Steam Deck Plugin Library back-end"
[features]
default = ["blocking"]
decky = ["usdpl-core/decky"]
any = []
blocking = [] # synchronous API for async functionality, using tokio
#encrypt = ["usdpl-core", "obfstr", "hex"]
[dependencies]
usdpl-core = { version = "1.0", path = "../usdpl-core"}
@ -35,10 +35,6 @@ tokio = { version = "1", features = [ "full" ]}
async-trait = "0.1.57"
async-recursion = "1.0.0"
# encryption helpers
obfstr = { version = "0.3", optional = true }
hex = { version = "0.4", optional = true }
# translations
gettext-ng = { version = "0.4.1" }

View file

@ -4,9 +4,9 @@ use std::path::PathBuf;
/// The home directory of the user currently running the Steam Deck UI.
pub fn home() -> Option<PathBuf> {
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
let result = crate::api_any::dirs::home();
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
let result = crate::api_decky::home()
.ok()
.map(|x| PathBuf::from(x).join("..").canonicalize().ok())
@ -17,9 +17,9 @@ pub fn home() -> Option<PathBuf> {
/// The plugin's root folder.
pub fn plugin() -> Option<PathBuf> {
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
let result = None; // TODO
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
let result = crate::api_decky::plugin_dir().ok().map(|x| x.into());
result
@ -27,9 +27,9 @@ pub fn plugin() -> Option<PathBuf> {
/// The recommended log directory
pub fn log() -> Option<PathBuf> {
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
#[cfg(not(any(feature = "decky")))]
let result = crate::api_any::dirs::log();
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
let result = crate::api_decky::log_dir().ok().map(|x| x.into());
result

View file

@ -55,7 +55,7 @@ fn generate_service_methods(
#field_name: #js_type_name,
});
params_to_fields.push(quote::quote! {
#field_name: #rs_type_name::from_wasm(#field_name.into()),//: #field_name,
#field_name: #rs_type_name::from_wasm(#field_name),//: #field_name,
});
}
let params_to_fields_transformer = if input_type.field.len() == 1 {
@ -67,11 +67,11 @@ fn generate_service_methods(
.expect("Protobuf message field needs a name")
);
quote::quote! {
let val = #method_input::from_wasm(#field_name.into());
let val = #method_input::from_wasm(#field_name);
}
} else if input_type.field.is_empty() {
quote::quote! {
let val = #method_input {};
let val = ();
}
} else {
quote::quote! {
@ -94,22 +94,8 @@ fn generate_service_methods(
},
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -145,22 +131,8 @@ fn generate_service_methods(
},
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -215,7 +187,7 @@ fn generate_service_methods(
}
} else if input_type.field.is_empty() {
quote::quote! {
let val = #method_input {};
let val = ();
}
} else {
quote::quote! {
@ -236,22 +208,8 @@ fn generate_service_methods(
match next_result {
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -293,22 +251,8 @@ fn generate_service_methods(
},
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -342,22 +286,8 @@ fn generate_service_methods(
match next_result {
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -399,22 +329,8 @@ fn generate_service_methods(
},
Err(e) => {
if let Some(e_handler) = &self.error_handler {
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(self.service.descriptor())
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(#method_name_str)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
let error_info = nrpc_error_to_jsobj(self.service.descriptor(), #method_name_str, &e);
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
// log error
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
@ -622,7 +538,21 @@ fn generate_wasm_struct_interop(
if descriptor.field.len() == 0 {
quote::quote! {
pub type #msg_name = ();
pub type #msg_name_wasm = #msg_name;
pub type #msg_name_wasm = JsValue;//#msg_name;
impl std::convert::Into<super::#super_msg_name> for #msg_name {
#[inline]
fn into(self) -> super::#super_msg_name {
super::#super_msg_name {}
}
}
impl std::convert::From<super::#super_msg_name> for #msg_name {
#[inline]
fn from(_: super::#super_msg_name) -> Self {
()
}
}
#(#gen_nested_types)*
@ -662,7 +592,8 @@ fn generate_wasm_struct_interop(
}
}
};*/
let into_converter_tokens = type_enum.to_into_prost_type();
let from_converter_tokens = type_enum.to_from_prost_type(&field_name);
quote::quote! {
pub type #msg_name = #type_name;
@ -672,7 +603,7 @@ fn generate_wasm_struct_interop(
#[inline]
fn into(self) -> super::#super_msg_name {
super::#super_msg_name {
#field_name: self
#field_name: self #into_converter_tokens
}
}
}
@ -681,7 +612,8 @@ fn generate_wasm_struct_interop(
#[inline]
#[allow(unused_variables)]
fn from(other: super::#super_msg_name) -> Self {
other.#field_name
#from_converter_tokens
//other.#field_name
}
}
@ -692,8 +624,11 @@ fn generate_wasm_struct_interop(
#(#gen_enums)*
}
} else {
// struct with more than one field
let mut gen_into_wasm_streamable_fields = Vec::with_capacity(descriptor.field.len());
let mut gen_from_wasm_streamable_fields = Vec::with_capacity(descriptor.field.len());
let mut gen_from_wasm_map_infallible = Vec::with_capacity(descriptor.field.len());
for field in &descriptor.field {
let field_name_str = field
@ -710,33 +645,29 @@ fn generate_wasm_struct_interop(
let into_wasm_streamable = type_enum.to_into_wasm_streamable(field_name_str, &js_map_name);
let from_wasm_streamable = type_enum.to_from_wasm_streamable(field_name_str, &js_map_name);
let from_wasm_map_infallible = type_enum.to_from_map_infallible(field_name_str, &js_map_name);
//let wasm_type_name = type_enum.to_wasm_tokens();
gen_fields.push(quote::quote! {
pub #field_name: #type_name,
});
let into_converter_tokens = type_enum.to_into_prost_type();
gen_into_fields.push(quote::quote!{
#field_name: self.#field_name.into(),
#field_name: self.#field_name #into_converter_tokens,
});
let from_converter_tokens = type_enum.to_from_prost_type(&field_name);
gen_from_fields.push(quote::quote!{
#field_name: <_>::from(other.#field_name),
#field_name: #from_converter_tokens,
});
gen_into_wasm_streamable_fields.push(into_wasm_streamable);
gen_from_wasm_streamable_fields.push(from_wasm_streamable);
gen_from_wasm_map_infallible.push(from_wasm_map_infallible);
}
let wasm_attribute_maybe =
if (descriptor.field.len() == 1 || !is_response_msg) && !descriptor.field.is_empty() {
quote::quote! {}
} else {
quote::quote! {
#[wasm_bindgen]
}
};
quote::quote! {
#wasm_attribute_maybe
#[derive(Default)]
pub struct #msg_name {
#(#gen_fields)*
}
@ -755,7 +686,24 @@ fn generate_wasm_struct_interop(
}
}
type #msg_name_wasm = #msg_name;
impl IntoWasmable<js_sys::Map> for #msg_name {
fn into_wasm(self) -> js_sys::Map {
let #js_map_name = js_sys::Map::new();
#(#gen_into_wasm_streamable_fields)*
#js_map_name
}
}
impl FromWasmable<js_sys::Map> for #msg_name {
fn from_wasm(js: js_sys::Map) -> Self {
let #js_map_name = js_sys::Map::from(js);
Self {
#(#gen_from_wasm_map_infallible)*
}
}
}
type #msg_name_wasm = js_sys::Map;//#msg_name;
impl std::convert::Into<super::#super_msg_name> for #msg_name {
#[inline]
@ -935,7 +883,7 @@ impl ProtobufType {
Self::Sfixed32 => quote::quote! {i32},
Self::Sfixed64 => quote::quote! {i64},
Self::Bool => quote::quote! {bool},
Self::String => quote::quote! {String},
Self::String => quote::quote! {js_sys::JsString},
Self::Bytes => quote::quote! {Vec<u8>},
Self::Repeated(_) => quote::quote! {js_sys::Array},
Self::Map { .. } => quote::quote! {js_sys::Map},
@ -958,6 +906,27 @@ impl ProtobufType {
quote::quote!{#field_ident: #type_tokens::from_wasm_streamable(#js_map_name.get(&JsValue::from(#field_name)))?,}
}
fn to_from_map_infallible(&self, field_name: &str, js_map_name: &syn::Ident) -> proc_macro2::TokenStream {
let type_tokens = self.to_tokens();
let field_ident = quote::format_ident!("{}", field_name);
quote::quote!{#field_ident: #type_tokens::from_wasm_streamable(#js_map_name.get(&JsValue::from(#field_name))).unwrap_or_default(),}
}
fn to_into_prost_type(&self) -> proc_macro2::TokenStream {
match self {
//Self::String => quote::quote!{.as_string().unwrap()},
_ => quote::quote!{.into()}
}
}
fn to_from_prost_type(&self, field_name: &syn::Ident) -> proc_macro2::TokenStream {
match self {
//Self::String => quote::quote!{#field_name: },
_ => quote::quote!{<_>::from(other.#field_name)}
}
}
/*fn is_already_wasm_streamable(&self) -> bool {
!matches!(self, Self::Custom(_))
}*/
@ -991,6 +960,9 @@ fn generate_wasm_enum_interop(
.as_ref()
.expect("Protobuf enum needs a name")
);
if descriptor.value.is_empty() {
panic!("Cannot generate enum {} with 0 variants because it is not possible to initialize", descriptor.name.as_ref().unwrap());
}
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());
@ -998,7 +970,10 @@ fn generate_wasm_enum_interop(
// TODO deal with options when necessary
todo!("Deal with enum options when necessary");
}
for value in &descriptor.value {
gen_values.push(quote::quote!{
#[default]
});
for value in descriptor.value.iter() {
let val_name = quote::format_ident!(
"{}",
value
@ -1041,7 +1016,7 @@ fn generate_wasm_enum_interop(
quote::quote! {
#[wasm_bindgen]
#[repr(i32)]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Default)]
pub enum #enum_name {
#(#gen_values)*
}
@ -1066,34 +1041,46 @@ fn generate_wasm_enum_interop(
#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 KnownWasmCompatible for #enum_name_wasm {}
impl FromWasmable<#enum_name_wasm> for #enum_name {
fn from_wasm(js: #enum_name_wasm) -> Self {
js
}
}
impl IntoWasmable<i32> for #enum_name {
fn into_wasm(self) -> i32 {
self as i32
impl IntoWasmable<#enum_name_wasm> for #enum_name {
fn into_wasm(self) -> #enum_name_wasm {
self
}
}
impl From<i32> for #enum_name {
fn from(other: i32) -> Self {
#enum_name::from(super::#super_enum_name::from_i32(other).unwrap())
impl std::convert::From<i32> for #enum_name {
fn from(x: i32) -> Self {
#enum_name::from(super::#super_enum_name::from_i32(x).unwrap())
}
}
impl Into<i32> for #enum_name {
impl std::convert::Into<i32> for #enum_name {
fn into(self) -> i32 {
self as i32
}
}
impl #enum_name {
fn from_f64(f: f64) -> Self {
Self::from(super::#super_enum_name::from_i32(f as i32).unwrap())
}
fn into_f64(self) -> f64 {
(self as i32).into()
}
}
impl ::usdpl_front::wasm::FromWasmStreamableType for #enum_name {
fn from_wasm_streamable(js: JsValue) -> Result<Self, ::usdpl_front::wasm::WasmStreamableConversionError> {
if let Some(float) = js.as_f64() {
Ok(Self::from_wasm(float as i32))
Ok(Self::from_f64(float))
} else {
Err(::usdpl_front::wasm::WasmStreamableConversionError::UnexpectedType {
expected: ::usdpl_front::wasm::JsType::Number,
@ -1105,7 +1092,7 @@ fn generate_wasm_enum_interop(
impl ::usdpl_front::wasm::IntoWasmStreamableType for #enum_name {
fn into_wasm_streamable(self) -> JsValue {
JsValue::from(self.into_wasm())
JsValue::from(self.into_f64())
}
}
}
@ -1224,6 +1211,49 @@ fn generate_service_io_types(
}
}
fn generate_error_to_jsobject_fn() -> proc_macro2::TokenStream {
quote::quote!{
#[allow(dead_code)]
fn nrpc_error_to_jsobj(descriptor: &str, method_name: &str, error: &usdpl_front::_helpers::nrpc::ServiceError) -> js_sys::Object {
let js_error = js_sys::Object::new();
js_sys::Reflect::set(
&js_error,
&JsValue::from("variant"),
&JsValue::from(error.variant())
).unwrap();
js_sys::Reflect::set(
&js_error,
&JsValue::from("name"),
&JsValue::from(error.variant_str())
).unwrap();
js_sys::Reflect::set(
&js_error,
&JsValue::from("message"),
&JsValue::from(error.to_string())
).unwrap();
let error_info = js_sys::Object::new();
js_sys::Reflect::set(
&error_info,
&JsValue::from("service"),
&JsValue::from(descriptor)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("method"),
&JsValue::from(method_name)
).unwrap();
js_sys::Reflect::set(
&error_info,
&JsValue::from("error"),
&JsValue::from("todo"/*TODO*/)
).unwrap();
error_info
}
}
}
impl IServiceGenerator for WasmServiceGenerator {
fn generate(&mut self, service: Service) -> proc_macro2::TokenStream {
let lock = self.shared.lock().expect("Cannot lock shared state");
@ -1236,6 +1266,7 @@ impl IServiceGenerator for WasmServiceGenerator {
let service_str_name = service.name.clone();
let service_methods = generate_service_methods(&service, fds);
let service_types = generate_service_io_types(&service, fds);
let error_translate_fn = generate_error_to_jsobject_fn();
let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase());
quote::quote! {
mod #mod_name {
@ -1254,6 +1285,8 @@ impl IServiceGenerator for WasmServiceGenerator {
use usdpl_front::WebSocketHandler;
#error_translate_fn
#service_types
/// WASM/JS-compatible wrapper of a Rust nRPC service

View file

@ -9,14 +9,12 @@ readme = "../README.md"
description = "Universal Steam Deck Plugin Library core designed for all architectures"
[features]
default = []
default = ["any"]
decky = []
encrypt = ["aes-gcm-siv"]
any = []
[dependencies]
base64 = "0.13"
aes-gcm-siv = { version = "0.10", optional = true, default-features = false, features = ["alloc", "aes"] }
# nrpc = "0.2"
[dev-dependencies]
hex-literal = "0.3.4"

View file

@ -10,11 +10,11 @@ impl Platform {
/// The current platform that usdpl-core is configured to target.
/// This is determined by feature flags.
pub fn current() -> Self {
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
{
Self::Decky
}
#[cfg(not(any(feature = "decky")))]
#[cfg(feature = "any")]
{
Self::Any
}

View file

@ -15,7 +15,6 @@ crate-type = ["cdylib", "rlib"]
default = []
decky = ["usdpl-core/decky"]
debug = ["console_error_panic_hook"]
#encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
[dependencies]
wasm-bindgen = "0.2"
@ -41,9 +40,6 @@ web-sys = { version = "0.3", features = [
]}
js-sys = { version = "0.3" }
obfstr = { version = "0.3", optional = true }
hex = { version = "0.4", optional = true }
nrpc = { version = "1.0", path = "../../nRPC/nrpc", default-features = false}
usdpl-core = { version = "1.0", path = "../usdpl-core" }
prost = "0.11"

View file

@ -32,11 +32,6 @@ const DEFAULT_LOGGER: console_logs::BuiltInLogger = console_logs::BuiltInLogger:
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
#[cfg(feature = "encrypt")]
fn encryption_key() -> Vec<u8> {
hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap()
}
static INIT_DONE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
/// Initialize the front-end library

View file

@ -2,6 +2,7 @@
mod arrays;
mod js_function_stream;
mod maps;
mod not_quite_trivial;
mod streaming;
mod trivials;
mod wasm_traits;

View file

@ -0,0 +1,25 @@
use super::{FromWasmable, IntoWasmable};
impl FromWasmable<js_sys::JsString> for String {
fn from_wasm(js: js_sys::JsString) -> Self {
js.as_string().unwrap()
}
}
impl IntoWasmable<js_sys::JsString> for String {
fn into_wasm(self) -> js_sys::JsString {
js_sys::JsString::from(self)
}
}
impl FromWasmable<wasm_bindgen::JsValue> for () {
fn from_wasm(_: wasm_bindgen::JsValue) -> Self {
()
}
}
impl IntoWasmable<wasm_bindgen::JsValue> for () {
fn into_wasm(self) -> wasm_bindgen::JsValue {
wasm_bindgen::JsValue::undefined()
}
}

View file

@ -35,6 +35,7 @@ trivial_convert! { u64 }
trivial_convert! { u128 }
trivial_convert! { bool }
trivial_convert! { String }
trivial_convert! { () }

View file

@ -38,3 +38,5 @@ impl KnownWasmCompatible for () {}
impl KnownWasmCompatible for js_sys::Map {}
impl KnownWasmCompatible for js_sys::Array {}
impl KnownWasmCompatible for wasm_bindgen::JsValue {}
impl KnownWasmCompatible for js_sys::JsString {}