Macros for frontend nRPC service generation
This commit is contained in:
parent
79a8e7e128
commit
570c194e82
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]
|
||||
name = "usdpl"
|
||||
version = "0.10.0"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/NGnius/usdpl-rs"
|
||||
readme = "README.md"
|
||||
[workspace]
|
||||
members = [
|
||||
"usdpl-core",
|
||||
"usdpl-front",
|
||||
"usdpl-back",
|
||||
"usdpl-build",
|
||||
]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
exclude = [
|
||||
"templates/decky/backend"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
|
@ -16,14 +17,3 @@ debug = false
|
|||
strip = true
|
||||
lto = true
|
||||
codegen-units = 4
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"usdpl-core",
|
||||
"usdpl-front",
|
||||
"usdpl-back",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"templates/decky/backend"
|
||||
]
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
[package]
|
||||
name = "usdpl-back"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/NGnius/usdpl-rs"
|
||||
readme = "README.md"
|
||||
readme = "../README.md"
|
||||
description = "Universal Steam Deck Plugin Library back-end"
|
||||
|
||||
[features]
|
||||
default = ["blocking", "translate"]
|
||||
default = ["blocking"]
|
||||
decky = ["usdpl-core/decky"]
|
||||
crankshaft = ["usdpl-core/crankshaft"]
|
||||
blocking = ["tokio", "tokio/rt", "tokio/rt-multi-thread"] # synchronous API for async functionality, using tokio
|
||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
||||
translate = ["usdpl-core/translate", "gettext-ng"]
|
||||
|
||||
[dependencies]
|
||||
usdpl-core = { version = "0.10", path = "../usdpl-core"}
|
||||
usdpl-core = { version = "0.11", path = "../usdpl-core"}
|
||||
|
||||
log = "0.4"
|
||||
|
||||
# gRPC/protobuf
|
||||
nrpc = "0.2"
|
||||
|
||||
# HTTP web framework
|
||||
warp = { version = "0.3" }
|
||||
bytes = { version = "1.1" }
|
||||
|
@ -34,4 +35,7 @@ obfstr = { version = "0.3", optional = true }
|
|||
hex = { version = "0.4", optional = true }
|
||||
|
||||
# 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> {
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
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"))))]
|
||||
let result = crate::api_decky::home().ok()
|
||||
.map(|x| PathBuf::from(x)
|
||||
|
@ -23,8 +21,6 @@ pub fn home() -> Option<PathBuf> {
|
|||
pub fn plugin() -> Option<PathBuf> {
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
let result = None; // TODO
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
let result = None; // TODO
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
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> {
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
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"))))]
|
||||
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]
|
||||
name = "usdpl-core"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/NGnius/usdpl-rs"
|
||||
readme = "README.md"
|
||||
readme = "../README.md"
|
||||
description = "Universal Steam Deck Plugin Library core"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
decky = []
|
||||
crankshaft = []
|
||||
encrypt = ["aes-gcm-siv"]
|
||||
translate = []
|
||||
|
||||
[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"
|
||||
|
|
|
@ -4,8 +4,6 @@ pub enum Platform {
|
|||
Any,
|
||||
/// Decky aka PluginLoader platform
|
||||
Decky,
|
||||
/// Crankshaft platform
|
||||
Crankshaft,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
|
@ -16,10 +14,6 @@ impl Platform {
|
|||
{
|
||||
Self::Decky
|
||||
}
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
{
|
||||
Self::Crankshaft
|
||||
}
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
{
|
||||
Self::Any
|
||||
|
@ -32,7 +26,6 @@ impl std::fmt::Display for Platform {
|
|||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
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(Vec<Packet>),
|
||||
/// Translation data dump
|
||||
#[cfg(feature = "translate")]
|
||||
Translations(Vec<(String, Vec<String>)>),
|
||||
/// Request translations for language
|
||||
#[cfg(feature = "translate")]
|
||||
Language(String),
|
||||
}
|
||||
|
||||
|
@ -59,9 +57,7 @@ impl Packet {
|
|||
Self::Unsupported => 6,
|
||||
Self::Bad => 7,
|
||||
Self::Many(_) => 8,
|
||||
#[cfg(feature = "translate")]
|
||||
Self::Translations(_) => 9,
|
||||
#[cfg(feature = "translate")]
|
||||
Self::Language(_) => 10,
|
||||
}
|
||||
}
|
||||
|
@ -93,12 +89,10 @@ impl Loadable for Packet {
|
|||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Many(obj), len)
|
||||
},
|
||||
#[cfg(feature = "translate")]
|
||||
9 => {
|
||||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Translations(obj), len)
|
||||
},
|
||||
#[cfg(feature = "translate")]
|
||||
10 => {
|
||||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Language(obj), len)
|
||||
|
@ -122,9 +116,7 @@ impl Dumpable for Packet {
|
|||
Self::Unsupported => Ok(0),
|
||||
Self::Bad => return Err(DumpError::Unsupported),
|
||||
Self::Many(v) => v.dump(buf),
|
||||
#[cfg(feature = "translate")]
|
||||
Self::Translations(tr) => tr.dump(buf),
|
||||
#[cfg(feature = "translate")]
|
||||
Self::Language(l) => l.dump(buf),
|
||||
}?;
|
||||
Ok(size1 + result)
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
[package]
|
||||
name = "usdpl-front"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/NGnius/usdpl-rs"
|
||||
readme = "README.md"
|
||||
readme = "../README.md"
|
||||
description = "Universal Steam Deck Plugin Library front-end designed for WASM"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["translate"]
|
||||
default = []
|
||||
decky = ["usdpl-core/decky"]
|
||||
crankshaft = ["usdpl-core/crankshaft"]
|
||||
debug = ["console_error_panic_hook"]
|
||||
encrypt = ["usdpl-core/encrypt", "obfstr", "hex"]
|
||||
translate = ["usdpl-core/translate"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
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
|
||||
# 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" }
|
||||
|
||||
async-channel = "1.8"
|
||||
|
||||
obfstr = { version = "0.3", 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]
|
||||
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)]
|
||||
|
||||
mod client_handler;
|
||||
mod connection;
|
||||
mod convert;
|
||||
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 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);
|
||||
|
||||
static mut CTX: UsdplContext = UsdplContext {
|
||||
port: 31337,
|
||||
port: 0,
|
||||
id: AtomicU64::new(0),
|
||||
#[cfg(feature = "encrypt")]
|
||||
key: Vec::new(),
|
||||
|
@ -27,7 +33,6 @@ static mut CTX: UsdplContext = UsdplContext {
|
|||
|
||||
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
||||
|
||||
#[cfg(feature = "translate")]
|
||||
static mut TRANSLATIONS: Option<std::collections::HashMap<String, Vec<String>>> = None;
|
||||
|
||||
#[cfg(feature = "encrypt")]
|
||||
|
|
Loading…
Reference in a new issue