Macros for frontend nRPC service generation
This commit is contained in:
parent
79a8e7e128
commit
570c194e82
25 changed files with 1612 additions and 262 deletions
896
Cargo.lock
generated
896
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
30
Cargo.toml
30
Cargo.toml
|
@ -1,13 +1,14 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "usdpl"
|
members = [
|
||||||
version = "0.10.0"
|
"usdpl-core",
|
||||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
"usdpl-front",
|
||||||
edition = "2021"
|
"usdpl-back",
|
||||||
license = "GPL-3.0-only"
|
"usdpl-build",
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
]
|
||||||
readme = "README.md"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
exclude = [
|
||||||
|
"templates/decky/backend"
|
||||||
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
@ -16,14 +17,3 @@ debug = false
|
||||||
strip = true
|
strip = true
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 4
|
codegen-units = 4
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = [
|
|
||||||
"usdpl-core",
|
|
||||||
"usdpl-front",
|
|
||||||
"usdpl-back",
|
|
||||||
]
|
|
||||||
|
|
||||||
exclude = [
|
|
||||||
"templates/decky/backend"
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
[package]
|
[package]
|
||||||
name = "usdpl-back"
|
name = "usdpl-back"
|
||||||
version = "0.10.1"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://github.com/NGnius/usdpl-rs"
|
||||||
readme = "README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library back-end"
|
description = "Universal Steam Deck Plugin Library back-end"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["blocking", "translate"]
|
default = ["blocking"]
|
||||||
decky = ["usdpl-core/decky"]
|
decky = ["usdpl-core/decky"]
|
||||||
crankshaft = ["usdpl-core/crankshaft"]
|
|
||||||
blocking = ["tokio", "tokio/rt", "tokio/rt-multi-thread"] # synchronous API for async functionality, using tokio
|
blocking = ["tokio", "tokio/rt", "tokio/rt-multi-thread"] # synchronous API for async functionality, using tokio
|
||||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
||||||
translate = ["usdpl-core/translate", "gettext-ng"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
usdpl-core = { version = "0.10", path = "../usdpl-core"}
|
usdpl-core = { version = "0.11", path = "../usdpl-core"}
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
|
# gRPC/protobuf
|
||||||
|
nrpc = "0.2"
|
||||||
|
|
||||||
# HTTP web framework
|
# HTTP web framework
|
||||||
warp = { version = "0.3" }
|
warp = { version = "0.3" }
|
||||||
bytes = { version = "1.1" }
|
bytes = { version = "1.1" }
|
||||||
|
@ -34,4 +35,7 @@ obfstr = { version = "0.3", optional = true }
|
||||||
hex = { version = "0.4", optional = true }
|
hex = { version = "0.4", optional = true }
|
||||||
|
|
||||||
# translations
|
# translations
|
||||||
gettext-ng = { version = "0.4.1", optional = true }
|
gettext-ng = { version = "0.4.1" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
usdpl-build = { version = "0.11", path = "../usdpl-build" }
|
||||||
|
|
3
usdpl-back/build.rs
Normal file
3
usdpl-back/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
usdpl_build::back::build()
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ use std::path::PathBuf;
|
||||||
pub fn home() -> Option<PathBuf> {
|
pub fn home() -> Option<PathBuf> {
|
||||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||||
let result = crate::api_any::dirs::home();
|
let result = crate::api_any::dirs::home();
|
||||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
|
||||||
let result = None; // TODO
|
|
||||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||||
let result = crate::api_decky::home().ok()
|
let result = crate::api_decky::home().ok()
|
||||||
.map(|x| PathBuf::from(x)
|
.map(|x| PathBuf::from(x)
|
||||||
|
@ -23,8 +21,6 @@ pub fn home() -> Option<PathBuf> {
|
||||||
pub fn plugin() -> Option<PathBuf> {
|
pub fn plugin() -> Option<PathBuf> {
|
||||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||||
let result = None; // TODO
|
let result = None; // TODO
|
||||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
|
||||||
let result = None; // TODO
|
|
||||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||||
let result = crate::api_decky::plugin_dir().ok().map(|x| x.into());
|
let result = crate::api_decky::plugin_dir().ok().map(|x| x.into());
|
||||||
|
|
||||||
|
@ -35,8 +31,6 @@ pub fn plugin() -> Option<PathBuf> {
|
||||||
pub fn log() -> Option<PathBuf> {
|
pub fn log() -> Option<PathBuf> {
|
||||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||||
let result = crate::api_any::dirs::log();
|
let result = crate::api_any::dirs::log();
|
||||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
|
||||||
let result = None; // TODO
|
|
||||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||||
let result = crate::api_decky::log_dir().ok().map(|x| x.into());
|
let result = crate::api_decky::log_dir().ok().map(|x| x.into());
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
compile_error!("Crankshaft unsupported (project no longer maintained)");
|
|
0
usdpl-back/src/rpc/mod.rs
Normal file
0
usdpl-back/src/rpc/mod.rs
Normal file
18
usdpl-build/Cargo.toml
Normal file
18
usdpl-build/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "usdpl-build"
|
||||||
|
version = "0.11.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nrpc-build = "0.5"
|
||||||
|
prost-build = "0.11"
|
||||||
|
prost-types = "0.11"
|
||||||
|
|
||||||
|
# code gen
|
||||||
|
prettyplease = "0.2"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = "2.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
|
25
usdpl-build/protos/debug.proto
Normal file
25
usdpl-build/protos/debug.proto
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package usdpl;
|
||||||
|
|
||||||
|
// The translation service
|
||||||
|
service DevTools {
|
||||||
|
// Retrieves all translations for the provided 4-letter code
|
||||||
|
rpc Log (LogMessage) returns (Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLevel {
|
||||||
|
Trace = 0;
|
||||||
|
Debug = 1;
|
||||||
|
Info = 2;
|
||||||
|
Warn = 3;
|
||||||
|
Error = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the log message
|
||||||
|
message LogMessage {
|
||||||
|
LogLevel level = 1;
|
||||||
|
string msg = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {}
|
19
usdpl-build/protos/translations.proto
Normal file
19
usdpl-build/protos/translations.proto
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package usdpl;
|
||||||
|
|
||||||
|
// The translation service
|
||||||
|
service Translations {
|
||||||
|
// Retrieves all translations for the provided 4-letter code
|
||||||
|
rpc GetLanguage (LanguageRequest) returns (TranslationsReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the language code
|
||||||
|
message LanguageRequest {
|
||||||
|
string lang = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing all translations for the language
|
||||||
|
message TranslationsReply {
|
||||||
|
map<string, string> translations = 1;
|
||||||
|
}
|
7
usdpl-build/src/back/mod.rs
Normal file
7
usdpl-build/src/back/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub fn build() {
|
||||||
|
crate::dump_protos_out().unwrap();
|
||||||
|
nrpc_build::compile_servers(
|
||||||
|
crate::all_proto_filenames().map(|n| crate::proto_out_path().clone().join(n)),
|
||||||
|
[crate::proto_out_path()]
|
||||||
|
)
|
||||||
|
}
|
22
usdpl-build/src/front/mod.rs
Normal file
22
usdpl-build/src/front/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
mod preprocessor;
|
||||||
|
pub use preprocessor::WasmProtoPreprocessor;
|
||||||
|
|
||||||
|
mod service_generator;
|
||||||
|
pub use service_generator::WasmServiceGenerator;
|
||||||
|
|
||||||
|
mod shared_state;
|
||||||
|
pub(crate) use shared_state::SharedState;
|
||||||
|
|
||||||
|
pub fn build() {
|
||||||
|
let shared_state = SharedState::new();
|
||||||
|
crate::dump_protos_out().unwrap();
|
||||||
|
nrpc_build::Transpiler::new(
|
||||||
|
crate::all_proto_filenames().map(|n| crate::proto_out_path().clone().join(n)),
|
||||||
|
[crate::proto_out_path()]
|
||||||
|
).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()
|
||||||
|
}
|
27
usdpl-build/src/front/preprocessor.rs
Normal file
27
usdpl-build/src/front/preprocessor.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use nrpc_build::IPreprocessor;
|
||||||
|
//use prost_build::{Service, ServiceGenerator};
|
||||||
|
use prost_types::FileDescriptorSet;
|
||||||
|
|
||||||
|
use super::SharedState;
|
||||||
|
|
||||||
|
pub struct WasmProtoPreprocessor {
|
||||||
|
shared: SharedState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmProtoPreprocessor {
|
||||||
|
pub fn with_state(state: &SharedState) -> Self {
|
||||||
|
Self {
|
||||||
|
shared: state.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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!{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
592
usdpl-build/src/front/service_generator.rs
Normal file
592
usdpl-build/src/front/service_generator.rs
Normal file
|
@ -0,0 +1,592 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use prost_build::Service;
|
||||||
|
use prost_types::{FileDescriptorSet, DescriptorProto, EnumDescriptorProto, FieldDescriptorProto};
|
||||||
|
use nrpc_build::IServiceGenerator;
|
||||||
|
|
||||||
|
use super::SharedState;
|
||||||
|
|
||||||
|
pub struct WasmServiceGenerator {
|
||||||
|
shared: SharedState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmServiceGenerator {
|
||||||
|
pub fn with_state(state: &SharedState) -> Self {
|
||||||
|
Self {
|
||||||
|
shared: state.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 = translate_type(field, &service.name);
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
params_to_fields.push(quote::quote!{
|
||||||
|
#field_name,//: #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;
|
||||||
|
}
|
||||||
|
} else if input_type.field.is_empty() {
|
||||||
|
quote::quote!{
|
||||||
|
let val = #method_input {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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());
|
||||||
|
|
||||||
|
let special_fn_from_output = quote::format_ident!("{}_convert_from", method.output_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> {
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
quote::quote!{
|
||||||
|
#(#gen_methods)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if let Some(pkg) = &file.package {
|
||||||
|
if name == want_type && pkg == want_package {
|
||||||
|
return Some(enum_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Some(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_type(field: &FieldDescriptorProto, service: &str) -> proc_macro2::TokenStream {
|
||||||
|
if let Some(type_name) = &field.type_name {
|
||||||
|
translate_type_name(type_name, service)
|
||||||
|
} else {
|
||||||
|
let number = field.r#type.unwrap();
|
||||||
|
translate_type_known(number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
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());
|
||||||
|
|
||||||
|
let mut gen_nested_types = Vec::with_capacity(descriptor.nested_type.len());
|
||||||
|
|
||||||
|
let mut gen_enums = Vec::with_capacity(descriptor.enum_type.len());
|
||||||
|
|
||||||
|
if let Some(options) = &descriptor.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 = translate_type(&key_field, service);
|
||||||
|
let value_field = find_field("value", descriptor).expect("Protobuf map entry has no value field");
|
||||||
|
let value_type = translate_type(&value_field, service);
|
||||||
|
return quote::quote!{
|
||||||
|
pub type #msg_name = ::js_sys::Map;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn #special_fn_from(other: ::std::collections::HashMap<#key_type, #value_type>) -> #msg_name {
|
||||||
|
let map = #msg_name::new();
|
||||||
|
for (key, val) in other.iter() {
|
||||||
|
map.set(&key.into(), &val.into());
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn #special_fn_into(this: #msg_name) -> ::std::collections::HashMap<#key_type, #value_type> {
|
||||||
|
let mut output = ::std::collections::HashMap::<#key_type, #value_type>::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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("Deal with message options when necessary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for n_type in &descriptor.nested_type {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for e_type in &descriptor.enum_type {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 = translate_type(field, service);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
quote::quote!{
|
||||||
|
pub type #msg_name = #type_name;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn #special_fn_from(other: super::#super_msg_name) -> #msg_name {
|
||||||
|
#(#gen_from_fields)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn #special_fn_into(this: #msg_name) -> super::#super_msg_name {
|
||||||
|
super::#super_msg_name {
|
||||||
|
#(#gen_into_fields)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#(#gen_nested_types)*
|
||||||
|
|
||||||
|
#(#gen_enums)*
|
||||||
|
}
|
||||||
|
} 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 = translate_type(field, service);
|
||||||
|
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_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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
quote::quote!{}
|
||||||
|
} else {
|
||||||
|
quote::quote!{
|
||||||
|
#[wasm_bindgen]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote::quote!{
|
||||||
|
#wasm_attribute_maybe
|
||||||
|
pub struct #msg_name {
|
||||||
|
#(#gen_fields)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::Into<super::#super_msg_name> for #msg_name {
|
||||||
|
#[inline]
|
||||||
|
fn into(self) -> super::#super_msg_name {
|
||||||
|
super::#super_msg_name {
|
||||||
|
#(#gen_into_fields)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<super::#super_msg_name> for #msg_name {
|
||||||
|
#[inline]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn from(other: super::#super_msg_name) -> Self {
|
||||||
|
#msg_name {
|
||||||
|
#(#gen_from_fields)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_type_name(name: &str, service: &str) -> proc_macro2::TokenStream {
|
||||||
|
match name {
|
||||||
|
"double" => quote::quote!{f64},
|
||||||
|
"float" => quote::quote!{f32},
|
||||||
|
"int32" => quote::quote!{i32},
|
||||||
|
"int64" => quote::quote!{i64},
|
||||||
|
"uint32" => quote::quote!{u32},
|
||||||
|
"uint64" => quote::quote!{u64},
|
||||||
|
"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},
|
||||||
|
"string" => quote::quote!{String},
|
||||||
|
"bytes" => quote::quote!{Vec<u8>},
|
||||||
|
t => {
|
||||||
|
let ident = quote::format_ident!("{}{}", service, t.split('.').last().unwrap());
|
||||||
|
quote::quote!{#ident}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_type_known(id: i32) -> proc_macro2::TokenStream {
|
||||||
|
match id {
|
||||||
|
//"double" => quote::quote!{f64},
|
||||||
|
//"float" => quote::quote!{f32},
|
||||||
|
//"int32" => quote::quote!{i32},
|
||||||
|
//"int64" => quote::quote!{i64},
|
||||||
|
//"uint32" => quote::quote!{u32},
|
||||||
|
//"uint64" => quote::quote!{u64},
|
||||||
|
//"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},
|
||||||
|
9 => quote::quote!{String},
|
||||||
|
//"bytes" => quote::quote!{Vec<u8>},
|
||||||
|
t => {
|
||||||
|
let ident = quote::format_ident!("UnknownType{}", t.to_string());
|
||||||
|
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"));
|
||||||
|
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());
|
||||||
|
if let Some(_options) = &descriptor.options {
|
||||||
|
// TODO deal with options when necessary
|
||||||
|
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"));
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
gen_values.push(
|
||||||
|
quote::quote!{
|
||||||
|
#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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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!{
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum #enum_name {
|
||||||
|
#(#gen_values)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::Into<super::#super_enum_name> for #enum_name {
|
||||||
|
fn into(self) -> super::#super_enum_name {
|
||||||
|
match self {
|
||||||
|
#(#gen_into_values)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<super::#super_enum_name> for #enum_name {
|
||||||
|
fn from(other: super::#super_enum_name) -> Self {
|
||||||
|
match other {
|
||||||
|
#(#gen_from_values)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn #special_fn_from(other: i32) -> #enum_name {
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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");
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
} 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");
|
||||||
|
if !handled_enums.contains(&enum_name) {
|
||||||
|
handled_enums.insert(enum_name);
|
||||||
|
gen_types.push(generate_wasm_enum_interop(input_enum, &service.name));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find proto type {}/{}", service.package, method.input_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Cannot find proto type {}/{}", service.package, method.input_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quote::quote! {
|
||||||
|
#(#gen_types)*
|
||||||
|
|
||||||
|
#(#gen_enums)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.as_ref()
|
||||||
|
.expect("FileDescriptorSet required for WASM service generator");
|
||||||
|
let service_struct_name = quote::format_ident!("{}Client", service.name);
|
||||||
|
let service_js_name = quote::format_ident!("{}", service.name);
|
||||||
|
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!{
|
||||||
|
mod #mod_name {
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use crate::client_handler::WebSocketHandler;
|
||||||
|
|
||||||
|
#service_types
|
||||||
|
|
||||||
|
/// WASM/JS-compatible wrapper of the Rust nRPC service
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct #service_js_name {
|
||||||
|
//#[wasm_bindgen(skip)]
|
||||||
|
service: super::#service_struct_name<WebSocketHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl #service_js_name {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(port: u16) -> Self {
|
||||||
|
let implementation = super::#service_struct_name::new(
|
||||||
|
WebSocketHandler::new(port)
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
service: implementation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#service_methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
usdpl-build/src/front/shared_state.rs
Normal file
26
usdpl-build/src/front/shared_state.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use prost_types::FileDescriptorSet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharedState(Arc<Mutex<SharedProtoData>>);
|
||||||
|
|
||||||
|
impl SharedState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(SharedProtoData {
|
||||||
|
fds: None,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for SharedState {
|
||||||
|
type Target = Arc<Mutex<SharedProtoData>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SharedProtoData {
|
||||||
|
pub fds: Option<FileDescriptorSet>,
|
||||||
|
}
|
5
usdpl-build/src/lib.rs
Normal file
5
usdpl-build/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod back;
|
||||||
|
pub mod front;
|
||||||
|
|
||||||
|
mod proto_files;
|
||||||
|
pub use proto_files::{dump_protos, dump_protos_out, proto_out_path, all_proto_filenames};
|
44
usdpl-build/src/proto_files.rs
Normal file
44
usdpl-build/src/proto_files.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
struct IncludedFileStr<'a> {
|
||||||
|
filename: &'a str,
|
||||||
|
contents: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEBUG_PROTO: IncludedFileStr<'static> = IncludedFileStr {
|
||||||
|
filename: "debug.proto",
|
||||||
|
contents: include_str!("../protos/debug.proto"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const TRANSLATIONS_PROTO: IncludedFileStr<'static> = IncludedFileStr {
|
||||||
|
filename: "translations.proto",
|
||||||
|
contents: include_str!("../protos/translations.proto"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [
|
||||||
|
DEBUG_PROTO,
|
||||||
|
TRANSLATIONS_PROTO,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn proto_out_path() -> PathBuf {
|
||||||
|
PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")).join("protos")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_proto_filenames() -> impl Iterator<Item = &'static str> {
|
||||||
|
ALL_PROTOS.iter().map(|x| x.filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_protos(p: impl AsRef<Path>) -> std::io::Result<()> {
|
||||||
|
let p = p.as_ref();
|
||||||
|
for f in ALL_PROTOS {
|
||||||
|
let fullpath = p.join(f.filename);
|
||||||
|
std::fs::write(fullpath, f.contents)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_protos_out() -> std::io::Result<()> {
|
||||||
|
let path = proto_out_path();
|
||||||
|
std::fs::create_dir_all(&path)?;
|
||||||
|
dump_protos(&path)
|
||||||
|
}
|
|
@ -1,22 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "usdpl-core"
|
name = "usdpl-core"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://github.com/NGnius/usdpl-rs"
|
||||||
readme = "README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library core"
|
description = "Universal Steam Deck Plugin Library core"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
decky = []
|
decky = []
|
||||||
crankshaft = []
|
|
||||||
encrypt = ["aes-gcm-siv"]
|
encrypt = ["aes-gcm-siv"]
|
||||||
translate = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
aes-gcm-siv = { version = "0.10", optional = true, default-features = false, features = ["alloc", "aes"] }
|
aes-gcm-siv = { version = "0.10", optional = true, default-features = false, features = ["alloc", "aes"] }
|
||||||
|
# nrpc = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.3.4"
|
hex-literal = "0.3.4"
|
||||||
|
|
|
@ -4,8 +4,6 @@ pub enum Platform {
|
||||||
Any,
|
Any,
|
||||||
/// Decky aka PluginLoader platform
|
/// Decky aka PluginLoader platform
|
||||||
Decky,
|
Decky,
|
||||||
/// Crankshaft platform
|
|
||||||
Crankshaft,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform {
|
impl Platform {
|
||||||
|
@ -16,10 +14,6 @@ impl Platform {
|
||||||
{
|
{
|
||||||
Self::Decky
|
Self::Decky
|
||||||
}
|
}
|
||||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
|
||||||
{
|
|
||||||
Self::Crankshaft
|
|
||||||
}
|
|
||||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||||
{
|
{
|
||||||
Self::Any
|
Self::Any
|
||||||
|
@ -32,7 +26,6 @@ impl std::fmt::Display for Platform {
|
||||||
match self {
|
match self {
|
||||||
Self::Any => write!(f, "any"),
|
Self::Any => write!(f, "any"),
|
||||||
Self::Decky => write!(f, "decky"),
|
Self::Decky => write!(f, "decky"),
|
||||||
Self::Crankshaft => write!(f, "crankshaft"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -40,10 +40,8 @@ pub enum Packet {
|
||||||
/// Many packets merged into one
|
/// Many packets merged into one
|
||||||
Many(Vec<Packet>),
|
Many(Vec<Packet>),
|
||||||
/// Translation data dump
|
/// Translation data dump
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Translations(Vec<(String, Vec<String>)>),
|
Translations(Vec<(String, Vec<String>)>),
|
||||||
/// Request translations for language
|
/// Request translations for language
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Language(String),
|
Language(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,9 +57,7 @@ impl Packet {
|
||||||
Self::Unsupported => 6,
|
Self::Unsupported => 6,
|
||||||
Self::Bad => 7,
|
Self::Bad => 7,
|
||||||
Self::Many(_) => 8,
|
Self::Many(_) => 8,
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Self::Translations(_) => 9,
|
Self::Translations(_) => 9,
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Self::Language(_) => 10,
|
Self::Language(_) => 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,12 +89,10 @@ impl Loadable for Packet {
|
||||||
let (obj, len) = <_>::load(buf)?;
|
let (obj, len) = <_>::load(buf)?;
|
||||||
(Self::Many(obj), len)
|
(Self::Many(obj), len)
|
||||||
},
|
},
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
9 => {
|
9 => {
|
||||||
let (obj, len) = <_>::load(buf)?;
|
let (obj, len) = <_>::load(buf)?;
|
||||||
(Self::Translations(obj), len)
|
(Self::Translations(obj), len)
|
||||||
},
|
},
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
10 => {
|
10 => {
|
||||||
let (obj, len) = <_>::load(buf)?;
|
let (obj, len) = <_>::load(buf)?;
|
||||||
(Self::Language(obj), len)
|
(Self::Language(obj), len)
|
||||||
|
@ -122,9 +116,7 @@ impl Dumpable for Packet {
|
||||||
Self::Unsupported => Ok(0),
|
Self::Unsupported => Ok(0),
|
||||||
Self::Bad => return Err(DumpError::Unsupported),
|
Self::Bad => return Err(DumpError::Unsupported),
|
||||||
Self::Many(v) => v.dump(buf),
|
Self::Many(v) => v.dump(buf),
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Self::Translations(tr) => tr.dump(buf),
|
Self::Translations(tr) => tr.dump(buf),
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
Self::Language(l) => l.dump(buf),
|
Self::Language(l) => l.dump(buf),
|
||||||
}?;
|
}?;
|
||||||
Ok(size1 + result)
|
Ok(size1 + result)
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
[package]
|
[package]
|
||||||
name = "usdpl-front"
|
name = "usdpl-front"
|
||||||
version = "0.10.1"
|
version = "0.11.0"
|
||||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/NGnius/usdpl-rs"
|
repository = "https://github.com/NGnius/usdpl-rs"
|
||||||
readme = "README.md"
|
readme = "../README.md"
|
||||||
description = "Universal Steam Deck Plugin Library front-end designed for WASM"
|
description = "Universal Steam Deck Plugin Library front-end designed for WASM"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["translate"]
|
default = []
|
||||||
decky = ["usdpl-core/decky"]
|
decky = ["usdpl-core/decky"]
|
||||||
crankshaft = ["usdpl-core/crankshaft"]
|
|
||||||
debug = ["console_error_panic_hook"]
|
debug = ["console_error_panic_hook"]
|
||||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
||||||
translate = ["usdpl-core/translate"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
gloo-net = { version = "0.2", features = ["websocket"] }
|
||||||
|
futures = "0.3"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
@ -39,10 +39,17 @@ web-sys = { version = "0.3", features = [
|
||||||
]}
|
]}
|
||||||
js-sys = { version = "0.3" }
|
js-sys = { version = "0.3" }
|
||||||
|
|
||||||
|
async-channel = "1.8"
|
||||||
|
|
||||||
obfstr = { version = "0.3", optional = true }
|
obfstr = { version = "0.3", optional = true }
|
||||||
hex = { version = "0.4", optional = true }
|
hex = { version = "0.4", optional = true }
|
||||||
|
|
||||||
usdpl-core = { version = "0.10", path = "../usdpl-core" }
|
nrpc = "0.2"
|
||||||
|
usdpl-core = { version = "0.11", path = "../usdpl-core" }
|
||||||
|
prost = "0.11"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = { version = "0.3.13" }
|
wasm-bindgen-test = { version = "0.3.13" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
usdpl-build = { version = "0.11", path = "../usdpl-build" }
|
||||||
|
|
3
usdpl-front/build.rs
Normal file
3
usdpl-front/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
usdpl_build::front::build()
|
||||||
|
}
|
81
usdpl-front/src/client_handler.rs
Normal file
81
usdpl-front/src/client_handler.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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};
|
||||||
|
|
||||||
|
static LAST_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
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())?;
|
||||||
|
|
||||||
|
read_next_incoming(ws).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_next_incoming(mut ws: WebSocket) -> Result<Vec<u8>, String> {
|
||||||
|
if let Some(msg) = ws.next().await {
|
||||||
|
match msg.map_err(|e| e.to_string())? {
|
||||||
|
Message::Bytes(b) => Ok(b),
|
||||||
|
Message::Text(_) => Err("Message::Text not allowed".into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("No response received".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ErrorStr(String);
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorStr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Error message: {}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ErrorStr {}
|
||||||
|
|
||||||
|
impl WebSocketHandler {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new(port: u16) -> Self {
|
||||||
|
Self { port }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ClientHandler for WebSocketHandler {
|
||||||
|
async fn call(&mut self,
|
||||||
|
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,
|
||||||
|
service,
|
||||||
|
method,
|
||||||
|
);
|
||||||
|
let (tx, rx) = async_channel::bounded(1);
|
||||||
|
spawn_local(async move {
|
||||||
|
tx.send(send_recv_ws(
|
||||||
|
url,
|
||||||
|
input
|
||||||
|
).await).await.unwrap_or(());
|
||||||
|
});
|
||||||
|
|
||||||
|
output.extend_from_slice(
|
||||||
|
&rx.recv().await
|
||||||
|
.map_err(|e| ServiceError::Method(Box::new(e)))?
|
||||||
|
.map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,16 @@
|
||||||
//!
|
//!
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
mod client_handler;
|
||||||
mod connection;
|
mod connection;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod imports;
|
mod imports;
|
||||||
|
|
||||||
|
#[allow(missing_docs)] // existence is pain otherwise
|
||||||
|
pub mod _nrpc_js_interop {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/usdpl.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
|
@ -19,7 +25,7 @@ use usdpl_core::{socket::Packet, RemoteCall};
|
||||||
//const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337);
|
//const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337);
|
||||||
|
|
||||||
static mut CTX: UsdplContext = UsdplContext {
|
static mut CTX: UsdplContext = UsdplContext {
|
||||||
port: 31337,
|
port: 0,
|
||||||
id: AtomicU64::new(0),
|
id: AtomicU64::new(0),
|
||||||
#[cfg(feature = "encrypt")]
|
#[cfg(feature = "encrypt")]
|
||||||
key: Vec::new(),
|
key: Vec::new(),
|
||||||
|
@ -27,7 +33,6 @@ static mut CTX: UsdplContext = UsdplContext {
|
||||||
|
|
||||||
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
||||||
|
|
||||||
#[cfg(feature = "translate")]
|
|
||||||
static mut TRANSLATIONS: Option<std::collections::HashMap<String, Vec<String>>> = None;
|
static mut TRANSLATIONS: Option<std::collections::HashMap<String, Vec<String>>> = None;
|
||||||
|
|
||||||
#[cfg(feature = "encrypt")]
|
#[cfg(feature = "encrypt")]
|
||||||
|
|
Loading…
Reference in a new issue