Improve flexibility of WASM conversions in code gen
This commit is contained in:
parent
febaafe50c
commit
0b44ebc12b
33 changed files with 1028 additions and 545 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1546,7 +1546,6 @@ dependencies = [
|
|||
"nrpc 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"obfstr",
|
||||
"prost",
|
||||
"usdpl-build",
|
||||
"usdpl-core",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
usdpl_build::back::build()
|
||||
}
|
|
@ -5,14 +5,10 @@ use std::process::Command;
|
|||
|
||||
/// The home directory of the user currently running the Steam Deck UI (specifically: running gamescope).
|
||||
pub fn home() -> Option<PathBuf> {
|
||||
let who_out = Command::new("who")
|
||||
.output().ok()?;
|
||||
let who_out = Command::new("who").output().ok()?;
|
||||
let who_str = String::from_utf8_lossy(who_out.stdout.as_slice());
|
||||
for login in who_str.split("\n") {
|
||||
let username = login
|
||||
.split(" ")
|
||||
.next()?
|
||||
.trim();
|
||||
let username = login.split(" ").next()?.trim();
|
||||
let path = Path::new("/home").join(username);
|
||||
if path.is_dir() {
|
||||
return Some(path);
|
||||
|
|
|
@ -7,12 +7,10 @@ pub fn home() -> Option<PathBuf> {
|
|||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
let result = crate::api_any::dirs::home();
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
let result = crate::api_decky::home().ok()
|
||||
.map(|x| PathBuf::from(x)
|
||||
.join("..")
|
||||
.canonicalize()
|
||||
.ok()
|
||||
).flatten();
|
||||
let result = crate::api_decky::home()
|
||||
.ok()
|
||||
.map(|x| PathBuf::from(x).join("..").canonicalize().ok())
|
||||
.flatten();
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! Common low-level file operations
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write, self};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Write something to a file.
|
||||
|
@ -31,14 +31,12 @@ impl<E: std::error::Error> std::fmt::Display for ReadError<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: std::error::Error> std::error::Error for ReadError<E> {
|
||||
|
||||
}
|
||||
impl<E: std::error::Error> std::error::Error for ReadError<E> {}
|
||||
|
||||
/// Read something from a file.
|
||||
/// Useful for kernel configuration files.
|
||||
#[inline]
|
||||
pub fn read_single<P: AsRef<Path>, D: FromStr<Err=E>, E>(path: P) -> Result<D, ReadError<E>> {
|
||||
pub fn read_single<P: AsRef<Path>, D: FromStr<Err = E>, E>(path: P) -> Result<D, ReadError<E>> {
|
||||
let mut file = File::open(path).map_err(ReadError::Io)?;
|
||||
let mut string = String::new();
|
||||
file.read_to_string(&mut string).map_err(ReadError::Io)?;
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
//!
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
#[cfg(not(any(feature = "decky")))]
|
||||
mod api_any;
|
||||
mod api_common;
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
mod api_crankshaft;
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||
mod api_decky;
|
||||
|
||||
mod rpc;
|
||||
|
@ -27,16 +25,16 @@ pub mod api {
|
|||
pub use super::api_common::*;
|
||||
|
||||
/// Standard interfaces not specific to a single plugin loader
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
pub mod any { pub use super::super::api_any::*; }
|
||||
|
||||
/// Crankshaft-specific interfaces (FIXME)
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
pub mod crankshaft { pub use super::super::api_crankshaft::*; }
|
||||
#[cfg(not(any(feature = "decky")))]
|
||||
pub mod any {
|
||||
pub use super::super::api_any::*;
|
||||
}
|
||||
|
||||
/// Decky-specific interfaces
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
pub mod decky { pub use super::super::api_decky::*; }
|
||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||
pub mod decky {
|
||||
pub use super::super::api_decky::*;
|
||||
}
|
||||
}
|
||||
|
||||
/// usdpl-core re-export
|
||||
|
@ -49,9 +47,9 @@ pub mod nrpc {
|
|||
pub use nrpc::*;
|
||||
}
|
||||
|
||||
/// nRPC-generated exports
|
||||
/*/// nRPC-generated exports
|
||||
#[allow(missing_docs)]
|
||||
#[allow(dead_code)]
|
||||
pub mod services {
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use async_lock::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use async_lock::Mutex;
|
||||
|
||||
use nrpc::{ServerService, ServiceError};
|
||||
|
||||
|
@ -19,7 +19,12 @@ impl<'a> ServiceRegistry<'a> {
|
|||
format!("{}.{}", package, service)
|
||||
}*/
|
||||
|
||||
pub async fn call_descriptor(&self, descriptor: &str, method: &str, data: bytes::Bytes) -> Result<bytes::Bytes, ServiceError> {
|
||||
pub async fn call_descriptor(
|
||||
&self,
|
||||
descriptor: &str,
|
||||
method: &str,
|
||||
data: bytes::Bytes,
|
||||
) -> Result<bytes::Bytes, ServiceError> {
|
||||
if let Some(service) = self.entries.get(descriptor) {
|
||||
let mut output = bytes::BytesMut::new();
|
||||
let mut service_lock = service.lock_arc().await;
|
||||
|
@ -32,7 +37,8 @@ impl<'a> ServiceRegistry<'a> {
|
|||
|
||||
pub fn register<S: ServerService + Send + 'a>(&mut self, service: S) -> &mut Self {
|
||||
let key = service.descriptor().to_owned();
|
||||
self.entries.insert(key, Arc::new(Mutex::new(Box::new(service))));
|
||||
self.entries
|
||||
.insert(key, Arc::new(Mutex::new(Box::new(service))));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -61,16 +61,19 @@ impl WebsocketServer {
|
|||
runner.block_on(self.run())
|
||||
}
|
||||
|
||||
async fn connection_handler(services: ServiceRegistry<'static>, stream: TcpStream) -> Result<(), RatchetError> {
|
||||
async fn connection_handler(
|
||||
services: ServiceRegistry<'static>,
|
||||
stream: TcpStream,
|
||||
) -> Result<(), RatchetError> {
|
||||
let upgraded = ratchet_rs::accept_with(
|
||||
stream,
|
||||
WebSocketConfig::default(),
|
||||
DeflateExtProvider::default(),
|
||||
ProtocolRegistry::new(["usdpl-nrpc"])?,
|
||||
)
|
||||
.await?
|
||||
.upgrade()
|
||||
.await?;
|
||||
.await?
|
||||
.upgrade()
|
||||
.await?;
|
||||
|
||||
let request_path = upgraded.request.uri().path();
|
||||
|
||||
|
@ -82,19 +85,27 @@ impl WebsocketServer {
|
|||
let mut buf = BytesMut::new();
|
||||
loop {
|
||||
match websocket.read(&mut buf).await? {
|
||||
Message::Text => return Err(RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, "Websocket text messages are not accepted")),
|
||||
Message::Text => {
|
||||
return Err(RatchetError::with_cause(
|
||||
ratchet_rs::ErrorKind::Protocol,
|
||||
"Websocket text messages are not accepted",
|
||||
))
|
||||
}
|
||||
Message::Binary => {
|
||||
let response = services.call_descriptor(
|
||||
descriptor.service,
|
||||
descriptor.method,
|
||||
buf.clone().freeze()
|
||||
)
|
||||
let response = services
|
||||
.call_descriptor(
|
||||
descriptor.service,
|
||||
descriptor.method,
|
||||
buf.clone().freeze(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()))?;
|
||||
.map_err(|e| {
|
||||
RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string())
|
||||
})?;
|
||||
websocket.write_binary(response).await?;
|
||||
},
|
||||
}
|
||||
Message::Ping(x) => websocket.write_pong(x).await?,
|
||||
Message::Pong(_) => {},
|
||||
Message::Pong(_) => {}
|
||||
Message::Close(_) => break,
|
||||
}
|
||||
}
|
||||
|
@ -106,10 +117,7 @@ impl WebsocketServer {
|
|||
if let Some(service) = iter.next() {
|
||||
if let Some(method) = iter.next() {
|
||||
if iter.next().is_none() {
|
||||
return Ok(MethodDescriptor {
|
||||
service,
|
||||
method
|
||||
});
|
||||
return Ok(MethodDescriptor { service, method });
|
||||
} else {
|
||||
Err("URL path has too many separators")
|
||||
}
|
||||
|
|
|
@ -22,4 +22,6 @@ message LogMessage {
|
|||
string msg = 2;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
message Empty {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
pub fn build() {
|
||||
pub fn build(
|
||||
custom_protos: impl Iterator<Item = String>,
|
||||
custom_dirs: impl Iterator<Item = String>,
|
||||
) {
|
||||
crate::dump_protos_out().unwrap();
|
||||
nrpc_build::compile_servers(
|
||||
crate::all_proto_filenames(crate::proto_builtins_out_path()),
|
||||
crate::proto_out_paths()
|
||||
crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos),
|
||||
crate::proto_out_paths(custom_dirs),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,16 +7,24 @@ pub use service_generator::WasmServiceGenerator;
|
|||
mod shared_state;
|
||||
pub(crate) use shared_state::SharedState;
|
||||
|
||||
pub fn build() {
|
||||
pub fn build(
|
||||
custom_protos: impl Iterator<Item = String>,
|
||||
custom_dirs: impl Iterator<Item = String>,
|
||||
) {
|
||||
let shared_state = SharedState::new();
|
||||
crate::dump_protos_out().unwrap();
|
||||
nrpc_build::Transpiler::new(
|
||||
crate::all_proto_filenames(crate::proto_builtins_out_path()),
|
||||
crate::proto_out_paths()
|
||||
).unwrap()
|
||||
.generate_client()
|
||||
.with_preprocessor(nrpc_build::AbstractImpl::outer(WasmProtoPreprocessor::with_state(&shared_state)))
|
||||
.with_service_generator(nrpc_build::AbstractImpl::outer(WasmServiceGenerator::with_state(&shared_state)))
|
||||
.transpile()
|
||||
.unwrap()
|
||||
crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos),
|
||||
crate::proto_out_paths(custom_dirs),
|
||||
)
|
||||
.unwrap()
|
||||
.generate_client()
|
||||
.with_preprocessor(nrpc_build::AbstractImpl::outer(
|
||||
WasmProtoPreprocessor::with_state(&shared_state),
|
||||
))
|
||||
.with_service_generator(nrpc_build::AbstractImpl::outer(
|
||||
WasmServiceGenerator::with_state(&shared_state),
|
||||
))
|
||||
.transpile()
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
@ -18,10 +18,7 @@ impl WasmProtoPreprocessor {
|
|||
|
||||
impl IPreprocessor for WasmProtoPreprocessor {
|
||||
fn process(&mut self, fds: &mut FileDescriptorSet) -> proc_macro2::TokenStream {
|
||||
self.shared.lock()
|
||||
.expect("Cannot lock shared state")
|
||||
.fds = Some(fds.clone());
|
||||
quote::quote!{}
|
||||
self.shared.lock().expect("Cannot lock shared state").fds = Some(fds.clone());
|
||||
quote::quote! {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,9 +7,7 @@ pub struct SharedState(Arc<Mutex<SharedProtoData>>);
|
|||
|
||||
impl SharedState {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(Mutex::new(SharedProtoData {
|
||||
fds: None,
|
||||
})))
|
||||
Self(Arc::new(Mutex::new(SharedProtoData { fds: None })))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,4 +2,6 @@ pub mod back;
|
|||
pub mod front;
|
||||
|
||||
mod proto_files;
|
||||
pub use proto_files::{dump_protos, dump_protos_out, proto_out_paths, all_proto_filenames, proto_builtins_out_path};
|
||||
pub use proto_files::{
|
||||
all_proto_filenames, dump_protos, dump_protos_out, proto_builtins_out_path, proto_out_paths,
|
||||
};
|
||||
|
|
|
@ -17,26 +17,25 @@ const TRANSLATIONS_PROTO: IncludedFileStr<'static> = IncludedFileStr {
|
|||
contents: include_str!("../protos/translations.proto"),
|
||||
};
|
||||
|
||||
const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [
|
||||
DEBUG_PROTO,
|
||||
TRANSLATIONS_PROTO,
|
||||
];
|
||||
const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [DEBUG_PROTO, TRANSLATIONS_PROTO];
|
||||
|
||||
pub fn proto_builtins_out_path() -> PathBuf {
|
||||
PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")).join("protos")
|
||||
PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)"))
|
||||
.join("protos")
|
||||
}
|
||||
|
||||
pub fn proto_out_paths() -> impl Iterator<Item = String> {
|
||||
pub fn proto_out_paths(additionals: impl Iterator<Item = String>) -> impl Iterator<Item = String> {
|
||||
std::iter::once(proto_builtins_out_path())
|
||||
.map(|x| x.to_str().unwrap().to_owned())
|
||||
.chain(custom_protos_dirs().into_iter())
|
||||
.chain(custom_protos_dirs(additionals).into_iter())
|
||||
}
|
||||
|
||||
fn custom_protos_dirs() -> Vec<String> {
|
||||
fn custom_protos_dirs(additionals: impl Iterator<Item = String>) -> Vec<String> {
|
||||
let dirs = std::env::var(ADDITIONAL_PROTOBUFS_ENV_VAR).unwrap_or_else(|_| "".to_owned());
|
||||
dirs.split(':')
|
||||
.filter(|x| std::fs::read_dir(x).is_ok())
|
||||
.map(|x| x.to_owned())
|
||||
.chain(additionals)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -48,14 +47,27 @@ fn custom_protos_filenames() -> Vec<String> {
|
|||
.flat_map(|x| x.unwrap())
|
||||
.filter(|x| x.is_ok())
|
||||
.map(|x| x.unwrap().path())
|
||||
.filter(|x| if let Some(ext) = x.extension() { ext.to_ascii_lowercase() == "proto" && x.is_file() } else { false })
|
||||
.filter(|x| {
|
||||
if let Some(ext) = x.extension() {
|
||||
ext.to_ascii_lowercase() == "proto" && x.is_file()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.filter_map(|x| x.to_str().map(|x| x.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn all_proto_filenames(p: impl AsRef<Path> + 'static) -> impl Iterator<Item = String> {
|
||||
pub fn all_proto_filenames(
|
||||
p: impl AsRef<Path> + 'static,
|
||||
additionals: impl Iterator<Item = String>,
|
||||
) -> impl Iterator<Item = String> {
|
||||
//let p = p.as_ref();
|
||||
ALL_PROTOS.iter().map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned()).chain(custom_protos_filenames())
|
||||
ALL_PROTOS
|
||||
.iter()
|
||||
.map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned())
|
||||
.chain(custom_protos_filenames())
|
||||
.chain(additionals)
|
||||
}
|
||||
|
||||
pub fn dump_protos(p: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
|
||||
mod remote_call;
|
||||
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
#[cfg(not(any(feature = "decky")))]
|
||||
mod api_any;
|
||||
mod api_common;
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
mod api_crankshaft;
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||
mod api_decky;
|
||||
|
||||
pub mod serdes;
|
||||
|
@ -20,11 +18,9 @@ pub use remote_call::{RemoteCall, RemoteCallResponse};
|
|||
/// USDPL core API.
|
||||
/// This contains functionality used in both the back-end and front-end.
|
||||
pub mod api {
|
||||
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
|
||||
#[cfg(not(any(feature = "decky")))]
|
||||
pub use super::api_any::*;
|
||||
pub use super::api_common::*;
|
||||
#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))]
|
||||
pub use super::api_crankshaft::*;
|
||||
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
|
||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||
pub use super::api_decky::*;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn remote_call_idempotence_test() {
|
||||
let call = RemoteCall{
|
||||
let call = RemoteCall {
|
||||
id: 42,
|
||||
function: "something very long just in case this causes unexpected issues".into(),
|
||||
parameters: vec!["param1".into(), 42f64.into()],
|
||||
|
@ -88,7 +88,10 @@ mod tests {
|
|||
assert_eq!(len, loaded_len, "Expected load and dump lengths to match");
|
||||
|
||||
assert_eq!(loaded_call.id, call.id, "RemoteCall.id does not match");
|
||||
assert_eq!(loaded_call.function, call.function, "RemoteCall.function does not match");
|
||||
assert_eq!(
|
||||
loaded_call.function, call.function,
|
||||
"RemoteCall.function does not match"
|
||||
);
|
||||
if let Primitive::String(loaded) = &loaded_call.parameters[0] {
|
||||
if let Primitive::String(original) = &call.parameters[0] {
|
||||
assert_eq!(loaded, original, "RemoteCall.parameters[0] does not match");
|
||||
|
|
|
@ -26,43 +26,34 @@ impl<T: Dumpable> Dumpable for Vec<T> {
|
|||
|
||||
impl<T0: Dumpable, T1: Dumpable> Dumpable for (T0, T1) {
|
||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
||||
Ok(
|
||||
self.0.dump(buffer)?
|
||||
+ self.1.dump(buffer)?
|
||||
)
|
||||
Ok(self.0.dump(buffer)? + self.1.dump(buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable> Dumpable for (T0, T1, T2) {
|
||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
||||
Ok(
|
||||
self.0.dump(buffer)?
|
||||
+ self.1.dump(buffer)?
|
||||
+ self.2.dump(buffer)?
|
||||
)
|
||||
Ok(self.0.dump(buffer)? + self.1.dump(buffer)? + self.2.dump(buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable> Dumpable for (T0, T1, T2, T3) {
|
||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
||||
Ok(
|
||||
self.0.dump(buffer)?
|
||||
Ok(self.0.dump(buffer)?
|
||||
+ self.1.dump(buffer)?
|
||||
+ self.2.dump(buffer)?
|
||||
+ self.3.dump(buffer)?
|
||||
)
|
||||
+ self.3.dump(buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable for (T0, T1, T2, T3, T4) {
|
||||
impl<T0: Dumpable, T1: Dumpable, T2: Dumpable, T3: Dumpable, T4: Dumpable> Dumpable
|
||||
for (T0, T1, T2, T3, T4)
|
||||
{
|
||||
fn dump(&self, buffer: &mut dyn Write) -> Result<usize, DumpError> {
|
||||
Ok(
|
||||
self.0.dump(buffer)?
|
||||
Ok(self.0.dump(buffer)?
|
||||
+ self.1.dump(buffer)?
|
||||
+ self.2.dump(buffer)?
|
||||
+ self.3.dump(buffer)?
|
||||
+ self.4.dump(buffer)?
|
||||
)
|
||||
+ self.4.dump(buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ impl<T0: Loadable, T1: Loadable> Loadable for (T0, T1) {
|
|||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
||||
let (t0, len0) = T0::load(buffer)?;
|
||||
let (t1, len1) = T1::load(buffer)?;
|
||||
Ok((
|
||||
(t0, t1),
|
||||
len0 + len1
|
||||
))
|
||||
Ok(((t0, t1), len0 + len1))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,10 +51,7 @@ impl<T0: Loadable, T1: Loadable, T2: Loadable> Loadable for (T0, T1, T2) {
|
|||
let (t0, len0) = T0::load(buffer)?;
|
||||
let (t1, len1) = T1::load(buffer)?;
|
||||
let (t2, len2) = T2::load(buffer)?;
|
||||
Ok((
|
||||
(t0, t1, t2),
|
||||
len0 + len1 + len2
|
||||
))
|
||||
Ok(((t0, t1, t2), len0 + len1 + len2))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,24 +61,20 @@ impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable> Loadable for (T0, T
|
|||
let (t1, len1) = T1::load(buffer)?;
|
||||
let (t2, len2) = T2::load(buffer)?;
|
||||
let (t3, len3) = T3::load(buffer)?;
|
||||
Ok((
|
||||
(t0, t1, t2, t3),
|
||||
len0 + len1 + len2 + len3
|
||||
))
|
||||
Ok(((t0, t1, t2, t3), len0 + len1 + len2 + len3))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable for (T0, T1, T2, T3, T4) {
|
||||
impl<T0: Loadable, T1: Loadable, T2: Loadable, T3: Loadable, T4: Loadable> Loadable
|
||||
for (T0, T1, T2, T3, T4)
|
||||
{
|
||||
fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
||||
let (t0, len0) = T0::load(buffer)?;
|
||||
let (t1, len1) = T1::load(buffer)?;
|
||||
let (t2, len2) = T2::load(buffer)?;
|
||||
let (t3, len3) = T3::load(buffer)?;
|
||||
let (t4, len4) = T4::load(buffer)?;
|
||||
Ok((
|
||||
(t0, t1, t2, t3, t4),
|
||||
len0 + len1 + len2 + len3 + len4
|
||||
))
|
||||
Ok(((t0, t1, t2, t3, t4), len0 + len1 + len2 + len3 + len4))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::io::{Read, Write};
|
||||
use super::{DumpError, Dumpable, LoadError, Loadable};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Primitive types supported for communication between the USDPL back- and front-end.
|
||||
/// These are used for sending over the TCP connection.
|
||||
|
@ -47,7 +47,8 @@ impl Primitive {
|
|||
impl Loadable for Primitive {
|
||||
fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
||||
let mut discriminant_buf = [u8::MAX; 1];
|
||||
buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?;
|
||||
buf.read_exact(&mut discriminant_buf)
|
||||
.map_err(LoadError::Io)?;
|
||||
let mut result: (Self, usize) = match discriminant_buf[0] {
|
||||
//0 => (None, 0),
|
||||
1 => (Self::Empty, 0),
|
||||
|
@ -105,7 +106,7 @@ macro_rules! into_impl {
|
|||
Primitive::$variant(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
into_impl! {String, String}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::io::{Read, Write, Cursor};
|
||||
use base64::{decode_config_buf, encode_config_buf, Config};
|
||||
use std::io::{Cursor, Read, Write};
|
||||
|
||||
const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true);
|
||||
|
||||
|
@ -49,8 +49,7 @@ pub trait Loadable: Sized {
|
|||
/// Load data from a base64-encoded buffer
|
||||
fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
|
||||
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
||||
decode_config_buf(buffer, B64_CONF, &mut buffer2)
|
||||
.map_err(|_| LoadError::InvalidData)?;
|
||||
decode_config_buf(buffer, B64_CONF, &mut buffer2).map_err(|_| LoadError::InvalidData)?;
|
||||
let mut cursor = Cursor::new(buffer2);
|
||||
Self::load(&mut cursor)
|
||||
}
|
||||
|
@ -66,7 +65,9 @@ pub trait Loadable: Sized {
|
|||
base64::decode_config_buf(buffer, B64_CONF, &mut decoded_buf)
|
||||
.map_err(|_| LoadError::InvalidData)?;
|
||||
//println!("Decoded buf: {:?}", decoded_buf);
|
||||
cipher.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf).map_err(|_| LoadError::DecryptionError)?;
|
||||
cipher
|
||||
.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf)
|
||||
.map_err(|_| LoadError::DecryptionError)?;
|
||||
//println!("Decrypted buf: {:?}", decoded_buf);
|
||||
let mut cursor = Cursor::new(decoded_buf);
|
||||
Self::load(&mut cursor)
|
||||
|
@ -121,7 +122,12 @@ pub trait Dumpable {
|
|||
|
||||
/// Dump data as an encrypted base64-encoded buffer
|
||||
#[cfg(feature = "encrypt")]
|
||||
fn dump_encrypted(&self, buffer: &mut Vec<u8>, key: &[u8], nonce: &[u8]) -> Result<usize, DumpError> {
|
||||
fn dump_encrypted(
|
||||
&self,
|
||||
buffer: &mut Vec<u8>,
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
) -> Result<usize, DumpError> {
|
||||
let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
||||
let size = self.dump(&mut buffer2)?;
|
||||
buffer2.truncate(size);
|
||||
|
@ -129,7 +135,9 @@ pub trait Dumpable {
|
|||
let key = aes_gcm_siv::Key::from_slice(key);
|
||||
let cipher = aes_gcm_siv::Aes256GcmSiv::new(key);
|
||||
let nonce = aes_gcm_siv::Nonce::from_slice(nonce);
|
||||
cipher.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2).map_err(|_| DumpError::EncryptionError)?;
|
||||
cipher
|
||||
.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2)
|
||||
.map_err(|_| DumpError::EncryptionError)?;
|
||||
//println!("Encrypted slice: {:?}", &buffer2);
|
||||
let mut base64_buf = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE);
|
||||
encode_config_buf(buffer2.as_slice(), B64_CONF, &mut base64_buf);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Web messaging
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
|
||||
use crate::serdes::{DumpError, Dumpable, LoadError, Loadable};
|
||||
use crate::{RemoteCall, RemoteCallResponse};
|
||||
|
@ -66,7 +66,8 @@ impl Packet {
|
|||
impl Loadable for Packet {
|
||||
fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> {
|
||||
let mut discriminant_buf = [u8::MAX; 1];
|
||||
buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?;
|
||||
buf.read_exact(&mut discriminant_buf)
|
||||
.map_err(LoadError::Io)?;
|
||||
let mut result: (Self, usize) = match discriminant_buf[0] {
|
||||
//0 => (None, 0),
|
||||
1 => {
|
||||
|
@ -88,15 +89,15 @@ impl Loadable for Packet {
|
|||
8 => {
|
||||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Many(obj), len)
|
||||
},
|
||||
}
|
||||
9 => {
|
||||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Translations(obj), len)
|
||||
},
|
||||
}
|
||||
10 => {
|
||||
let (obj, len) = <_>::load(buf)?;
|
||||
(Self::Language(obj), len)
|
||||
},
|
||||
}
|
||||
_ => return Err(LoadError::InvalidData),
|
||||
};
|
||||
result.1 += 1;
|
||||
|
@ -130,24 +131,39 @@ mod tests {
|
|||
#[cfg(feature = "encrypt")]
|
||||
#[test]
|
||||
fn encryption_integration_test() {
|
||||
let key = hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265");
|
||||
let key =
|
||||
hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265");
|
||||
let nonce = [0u8; NONCE_SIZE];
|
||||
let packet = Packet::Call(RemoteCall{
|
||||
let packet = Packet::Call(RemoteCall {
|
||||
id: 42,
|
||||
function: "test".into(),
|
||||
parameters: Vec::new(),
|
||||
});
|
||||
let mut buffer = Vec::with_capacity(PACKET_BUFFER_SIZE);
|
||||
let len = packet.dump_encrypted(&mut buffer, &key, &nonce).unwrap();
|
||||
println!("buffer: {}", String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap());
|
||||
println!(
|
||||
"buffer: {}",
|
||||
String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap()
|
||||
);
|
||||
|
||||
let (packet_out, _len) = Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap();
|
||||
let (packet_out, _len) =
|
||||
Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap();
|
||||
|
||||
if let Packet::Call(call_out) = packet_out {
|
||||
if let Packet::Call(call_in) = packet {
|
||||
assert_eq!(call_in.id, call_out.id, "Input and output packets do not match");
|
||||
assert_eq!(call_in.function, call_out.function, "Input and output packets do not match");
|
||||
assert_eq!(call_in.parameters.len(), call_out.parameters.len(), "Input and output packets do not match");
|
||||
assert_eq!(
|
||||
call_in.id, call_out.id,
|
||||
"Input and output packets do not match"
|
||||
);
|
||||
assert_eq!(
|
||||
call_in.function, call_out.function,
|
||||
"Input and output packets do not match"
|
||||
);
|
||||
assert_eq!(
|
||||
call_in.parameters.len(),
|
||||
call_out.parameters.len(),
|
||||
"Input and output packets do not match"
|
||||
);
|
||||
} else {
|
||||
panic!("Packet in not a Call");
|
||||
}
|
||||
|
|
|
@ -50,6 +50,3 @@ prost = "0.11"
|
|||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { version = "0.3.13" }
|
||||
|
||||
[build-dependencies]
|
||||
usdpl-build = { version = "0.11", path = "../usdpl-build" }
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
usdpl_build::front::build()
|
||||
}
|
|
@ -1,20 +1,23 @@
|
|||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use nrpc::{ClientHandler, ServiceError, _helpers::bytes, _helpers::async_trait};
|
||||
use gloo_net::websocket::{Message, futures::WebSocket};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use gloo_net::websocket::{futures::WebSocket, Message};
|
||||
use nrpc::{ClientHandler, ServiceError, _helpers::async_trait, _helpers::bytes};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
static LAST_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Websocket client.
|
||||
/// In most cases, this shouldn't be used directly, but generated code will use this.
|
||||
pub struct WebSocketHandler {
|
||||
// TODO
|
||||
port: u16,
|
||||
}
|
||||
|
||||
async fn send_recv_ws(url: String, input: bytes::Bytes) -> Result<Vec<u8>, String> {
|
||||
let mut ws = WebSocket::open(&url).map_err(|e| e.to_string())?;
|
||||
ws.send(Message::Bytes(input.into())).await.map_err(|e| e.to_string())?;
|
||||
ws.send(Message::Bytes(input.into()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
read_next_incoming(ws).await
|
||||
}
|
||||
|
@ -42,7 +45,7 @@ impl std::fmt::Display for ErrorStr {
|
|||
impl std::error::Error for ErrorStr {}
|
||||
|
||||
impl WebSocketHandler {
|
||||
#[allow(dead_code)]
|
||||
/// Instantiate the web socket client for connecting on the specified port
|
||||
pub fn new(port: u16) -> Self {
|
||||
Self { port }
|
||||
}
|
||||
|
@ -50,33 +53,29 @@ impl WebSocketHandler {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl ClientHandler for WebSocketHandler {
|
||||
async fn call(&mut self,
|
||||
package: &str,
|
||||
service: &str,
|
||||
method: &str,
|
||||
input: bytes::Bytes,
|
||||
output: &mut bytes::BytesMut) -> Result<(), ServiceError> {
|
||||
async fn call(
|
||||
&mut self,
|
||||
package: &str,
|
||||
service: &str,
|
||||
method: &str,
|
||||
input: bytes::Bytes,
|
||||
output: &mut bytes::BytesMut,
|
||||
) -> Result<(), ServiceError> {
|
||||
let id = LAST_ID.fetch_add(1, Ordering::SeqCst);
|
||||
let url = format!(
|
||||
"ws://usdpl-ws-{}.localhost:{}/{}.{}/{}",
|
||||
id,
|
||||
self.port,
|
||||
package,
|
||||
service,
|
||||
method,
|
||||
id, self.port, package, service, method,
|
||||
);
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
spawn_local(async move {
|
||||
tx.send(send_recv_ws(
|
||||
url,
|
||||
input
|
||||
).await).await.unwrap_or(());
|
||||
tx.send(send_recv_ws(url, input).await).await.unwrap_or(());
|
||||
});
|
||||
|
||||
output.extend_from_slice(
|
||||
&rx.recv().await
|
||||
&rx.recv()
|
||||
.await
|
||||
.map_err(|e| ServiceError::Method(Box::new(e)))?
|
||||
.map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?
|
||||
.map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -14,26 +14,33 @@ use usdpl_core::serdes::{Dumpable, Loadable, Primitive};
|
|||
use usdpl_core::socket;
|
||||
|
||||
#[cfg(feature = "encrypt")]
|
||||
const NONCE: [u8; socket::NONCE_SIZE]= [0u8; socket::NONCE_SIZE];
|
||||
const NONCE: [u8; socket::NONCE_SIZE] = [0u8; socket::NONCE_SIZE];
|
||||
|
||||
pub async fn send_recv_packet(
|
||||
id: u64,
|
||||
packet: socket::Packet,
|
||||
port: u16,
|
||||
#[cfg(feature = "encrypt")]
|
||||
key: Vec<u8>,
|
||||
#[cfg(feature = "encrypt")] key: Vec<u8>,
|
||||
) -> Result<socket::Packet, JsValue> {
|
||||
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
opts.mode(RequestMode::Cors);
|
||||
|
||||
let url = format!("http://usdpl{}.{}:{}/usdpl/call", id, socket::HOST_STR, port);
|
||||
let url = format!(
|
||||
"http://usdpl{}.{}:{}/usdpl/call",
|
||||
id,
|
||||
socket::HOST_STR,
|
||||
port
|
||||
);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let (buffer, len) = dump_to_buffer(packet, #[cfg(feature = "encrypt")] key.as_slice())?;
|
||||
let (buffer, len) = dump_to_buffer(
|
||||
packet,
|
||||
#[cfg(feature = "encrypt")]
|
||||
key.as_slice(),
|
||||
)?;
|
||||
let string: String = String::from_utf8_lossy(buffer.as_slice()).into();
|
||||
#[cfg(feature="debug")]
|
||||
#[cfg(feature = "debug")]
|
||||
crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len));
|
||||
opts.body(Some(&string.into()));
|
||||
|
||||
|
@ -50,31 +57,46 @@ pub async fn send_recv_packet(
|
|||
let string: JsString = text.dyn_into()?;
|
||||
|
||||
let rust_str = string.as_string().unwrap();
|
||||
#[cfg(feature="debug")]
|
||||
crate::imports::console_log(&format!("Received base64 `{}` len:{}", rust_str, rust_str.len()));
|
||||
#[cfg(feature = "debug")]
|
||||
crate::imports::console_log(&format!(
|
||||
"Received base64 `{}` len:{}",
|
||||
rust_str,
|
||||
rust_str.len()
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "encrypt"))]
|
||||
{Ok(socket::Packet::load_base64(rust_str.as_bytes())
|
||||
.map_err(super::convert::str_to_js)?
|
||||
.0)}
|
||||
{
|
||||
Ok(socket::Packet::load_base64(rust_str.as_bytes())
|
||||
.map_err(super::convert::str_to_js)?
|
||||
.0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "encrypt")]
|
||||
{Ok(socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
|
||||
.map_err(super::convert::str_to_js)?
|
||||
.0)}
|
||||
{
|
||||
Ok(
|
||||
socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE)
|
||||
.map_err(super::convert::str_to_js)?
|
||||
.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_call(
|
||||
id: u64,
|
||||
packet: socket::Packet,
|
||||
port: u16,
|
||||
#[cfg(feature = "encrypt")]
|
||||
key: Vec<u8>,
|
||||
#[cfg(feature = "encrypt")] key: Vec<u8>,
|
||||
) -> Result<Vec<Primitive>, JsValue> {
|
||||
let packet = send_recv_packet(id, packet, port, #[cfg(feature = "encrypt")] key).await?;
|
||||
let packet = send_recv_packet(
|
||||
id,
|
||||
packet,
|
||||
port,
|
||||
#[cfg(feature = "encrypt")]
|
||||
key,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match packet
|
||||
{
|
||||
match packet {
|
||||
socket::Packet::CallResponse(resp) => Ok(resp.response),
|
||||
_ => {
|
||||
//imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url()));
|
||||
|
|
|
@ -6,13 +6,22 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
mod client_handler;
|
||||
pub use client_handler::WebSocketHandler;
|
||||
mod connection;
|
||||
mod convert;
|
||||
mod imports;
|
||||
pub mod wasm;
|
||||
|
||||
#[allow(missing_docs)] // existence is pain otherwise
|
||||
/*#[allow(missing_docs)] // existence is pain otherwise
|
||||
pub mod _nrpc_js_interop {
|
||||
include!(concat!(env!("OUT_DIR"), "/usdpl.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
}*/
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod _helpers {
|
||||
pub use js_sys;
|
||||
pub use wasm_bindgen;
|
||||
pub use wasm_bindgen_futures;
|
||||
}
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
@ -99,7 +108,11 @@ pub fn version_usdpl() -> String {
|
|||
#[wasm_bindgen]
|
||||
pub fn set_value(key: String, value: JsValue) -> JsValue {
|
||||
unsafe {
|
||||
CACHE.as_mut().unwrap().insert(key, value).unwrap_or(JsValue::NULL)
|
||||
CACHE
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(key, value)
|
||||
.unwrap_or(JsValue::NULL)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +120,12 @@ pub fn set_value(key: String, value: JsValue) -> JsValue {
|
|||
#[wasm_bindgen]
|
||||
pub fn get_value(key: String) -> JsValue {
|
||||
unsafe {
|
||||
CACHE.as_ref().unwrap().get(&key).map(|x| x.to_owned()).unwrap_or(JsValue::UNDEFINED)
|
||||
CACHE
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or(JsValue::UNDEFINED)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +156,7 @@ pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
|
|||
}),
|
||||
port,
|
||||
#[cfg(feature = "encrypt")]
|
||||
get_key()
|
||||
get_key(),
|
||||
)
|
||||
.await;
|
||||
let results = match results {
|
||||
|
@ -168,8 +186,10 @@ pub async fn init_tr(locale: String) {
|
|||
Packet::Language(locale.clone()),
|
||||
get_port(),
|
||||
#[cfg(feature = "encrypt")]
|
||||
get_key()
|
||||
).await {
|
||||
get_key(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Packet::Translations(translations)) => {
|
||||
#[cfg(feature = "debug")]
|
||||
imports::console_log(&format!("USDPL: Got translations for {}", locale));
|
||||
|
@ -179,12 +199,12 @@ pub async fn init_tr(locale: String) {
|
|||
tr_map.insert(key, val);
|
||||
}
|
||||
unsafe { TRANSLATIONS = Some(tr_map) }
|
||||
},
|
||||
}
|
||||
Ok(_) => {
|
||||
#[cfg(feature = "debug")]
|
||||
imports::console_error(&format!("USDPL: Got wrong packet response for init_tr"));
|
||||
unsafe { TRANSLATIONS = None }
|
||||
},
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
Err(e) => {
|
||||
#[cfg(feature = "debug")]
|
||||
|
|
94
usdpl-front/src/wasm/arrays.rs
Normal file
94
usdpl-front/src/wasm/arrays.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use js_sys::Array;
|
||||
|
||||
use super::{FromWasmable, IntoWasmable};
|
||||
|
||||
macro_rules! numbers_array {
|
||||
($num_ty: ident) => {
|
||||
impl FromWasmable<Array> for Vec<$num_ty> {
|
||||
fn from_wasm(js: Array) -> Self {
|
||||
let mut result = Vec::with_capacity(js.length() as usize);
|
||||
js.for_each(&mut |val, _index, _arr| {
|
||||
// according to MDN, this is guaranteed to be in order so index can be ignored
|
||||
if let Some(val) = val.as_f64() {
|
||||
result.push(val as $num_ty);
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Array> for Vec<$num_ty> {
|
||||
fn into_wasm(self) -> Array {
|
||||
let result = Array::new();
|
||||
for val in self {
|
||||
result.push(&val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
numbers_array! { f64 }
|
||||
numbers_array! { f32 }
|
||||
|
||||
numbers_array! { isize }
|
||||
numbers_array! { usize }
|
||||
|
||||
numbers_array! { i8 }
|
||||
numbers_array! { i16 }
|
||||
numbers_array! { i32 }
|
||||
numbers_array! { i64 }
|
||||
numbers_array! { i128 }
|
||||
|
||||
numbers_array! { u8 }
|
||||
numbers_array! { u16 }
|
||||
numbers_array! { u32 }
|
||||
numbers_array! { u64 }
|
||||
numbers_array! { u128 }
|
||||
|
||||
impl FromWasmable<Array> for Vec<String> {
|
||||
fn from_wasm(js: Array) -> Self {
|
||||
let mut result = Vec::with_capacity(js.length() as usize);
|
||||
js.for_each(&mut |val, _index, _arr| {
|
||||
// according to MDN, this is guaranteed to be in order so index can be ignored
|
||||
if let Some(val) = val.as_string() {
|
||||
result.push(val);
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Array> for Vec<String> {
|
||||
fn into_wasm(self) -> Array {
|
||||
let result = Array::new();
|
||||
for val in self {
|
||||
result.push(&val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWasmable<Array> for Vec<bool> {
|
||||
fn from_wasm(js: Array) -> Self {
|
||||
let mut result = Vec::with_capacity(js.length() as usize);
|
||||
js.for_each(&mut |val, _index, _arr| {
|
||||
// according to MDN, this is guaranteed to be in order so index can be ignored
|
||||
if let Some(val) = val.as_bool() {
|
||||
result.push(val);
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Array> for Vec<bool> {
|
||||
fn into_wasm(self) -> Array {
|
||||
let result = Array::new();
|
||||
for val in self {
|
||||
result.push(&val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
99
usdpl-front/src/wasm/maps.rs
Normal file
99
usdpl-front/src/wasm/maps.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use js_sys::Map;
|
||||
|
||||
use super::{FromWasmable, IntoWasmable};
|
||||
|
||||
macro_rules! numbers_map {
|
||||
($num_ty: ident) => {
|
||||
impl FromWasmable<Map> for HashMap<String, $num_ty> {
|
||||
fn from_wasm(js: Map) -> Self {
|
||||
let mut result = HashMap::with_capacity(js.size() as usize);
|
||||
js.for_each(&mut |key, val| {
|
||||
if let Some(key) = key.as_string() {
|
||||
if let Some(val) = val.as_f64() {
|
||||
result.insert(key, val as $num_ty);
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Map> for HashMap<String, $num_ty> {
|
||||
fn into_wasm(self) -> Map {
|
||||
let result = Map::new();
|
||||
for (key, val) in self {
|
||||
result.set(&key.into(), &val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
numbers_map! { f64 }
|
||||
numbers_map! { f32 }
|
||||
|
||||
numbers_map! { isize }
|
||||
numbers_map! { usize }
|
||||
|
||||
numbers_map! { i8 }
|
||||
numbers_map! { i16 }
|
||||
numbers_map! { i32 }
|
||||
numbers_map! { i64 }
|
||||
numbers_map! { i128 }
|
||||
|
||||
numbers_map! { u8 }
|
||||
numbers_map! { u16 }
|
||||
numbers_map! { u32 }
|
||||
numbers_map! { u64 }
|
||||
numbers_map! { u128 }
|
||||
|
||||
impl FromWasmable<Map> for HashMap<String, String> {
|
||||
fn from_wasm(js: Map) -> Self {
|
||||
let mut result = HashMap::with_capacity(js.size() as usize);
|
||||
js.for_each(&mut |key, val| {
|
||||
if let Some(key) = key.as_string() {
|
||||
if let Some(val) = val.as_string() {
|
||||
result.insert(key, val);
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Map> for HashMap<String, String> {
|
||||
fn into_wasm(self) -> Map {
|
||||
let result = Map::new();
|
||||
for (key, val) in self {
|
||||
result.set(&key.into(), &val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWasmable<Map> for HashMap<String, bool> {
|
||||
fn from_wasm(js: Map) -> Self {
|
||||
let mut result = HashMap::with_capacity(js.size() as usize);
|
||||
js.for_each(&mut |key, val| {
|
||||
if let Some(key) = key.as_string() {
|
||||
if let Some(val) = val.as_bool() {
|
||||
result.insert(key, val);
|
||||
}
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<Map> for HashMap<String, bool> {
|
||||
fn into_wasm(self) -> Map {
|
||||
let result = Map::new();
|
||||
for (key, val) in self {
|
||||
result.set(&key.into(), &val.into());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
7
usdpl-front/src/wasm/mod.rs
Normal file
7
usdpl-front/src/wasm/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
//! WASM <-> Rust interop utilities
|
||||
mod arrays;
|
||||
mod maps;
|
||||
mod trivials;
|
||||
mod wasm_traits;
|
||||
|
||||
pub use wasm_traits::*;
|
40
usdpl-front/src/wasm/trivials.rs
Normal file
40
usdpl-front/src/wasm/trivials.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use super::{FromWasmable, IntoWasmable};
|
||||
|
||||
macro_rules! trivial_convert {
|
||||
($ty: ty) => {
|
||||
impl FromWasmable<$ty> for $ty {
|
||||
fn from_wasm(js: $ty) -> Self {
|
||||
js
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoWasmable<$ty> for $ty {
|
||||
fn into_wasm(self) -> $ty {
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trivial_convert! { f64 }
|
||||
trivial_convert! { f32 }
|
||||
|
||||
trivial_convert! { isize }
|
||||
trivial_convert! { usize }
|
||||
|
||||
trivial_convert! { i8 }
|
||||
trivial_convert! { i16 }
|
||||
trivial_convert! { i32 }
|
||||
trivial_convert! { i64 }
|
||||
trivial_convert! { i128 }
|
||||
|
||||
trivial_convert! { u8 }
|
||||
trivial_convert! { u16 }
|
||||
trivial_convert! { u32 }
|
||||
trivial_convert! { u64 }
|
||||
trivial_convert! { u128 }
|
||||
|
||||
trivial_convert! { bool }
|
||||
trivial_convert! { String }
|
||||
|
||||
trivial_convert! { () }
|
40
usdpl-front/src/wasm/wasm_traits.rs
Normal file
40
usdpl-front/src/wasm/wasm_traits.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
/// A Rust type which supports Into/FromWasmAbi or WasmDescribe
|
||||
pub trait KnownWasmCompatible {}
|
||||
|
||||
/// Convert Rust type to WASM-compatible type
|
||||
pub trait IntoWasmable<T: KnownWasmCompatible> {
|
||||
/// Required method
|
||||
fn into_wasm(self) -> T;
|
||||
}
|
||||
|
||||
/// Convert WASM-compatible type to Rust-centric type
|
||||
pub trait FromWasmable<T: KnownWasmCompatible> {
|
||||
/// Required method
|
||||
fn from_wasm(js: T) -> Self;
|
||||
}
|
||||
|
||||
impl KnownWasmCompatible for f64 {}
|
||||
impl KnownWasmCompatible for f32 {}
|
||||
|
||||
impl KnownWasmCompatible for isize {}
|
||||
impl KnownWasmCompatible for usize {}
|
||||
|
||||
impl KnownWasmCompatible for i8 {}
|
||||
impl KnownWasmCompatible for i16 {}
|
||||
impl KnownWasmCompatible for i32 {}
|
||||
impl KnownWasmCompatible for i64 {}
|
||||
impl KnownWasmCompatible for i128 {}
|
||||
|
||||
impl KnownWasmCompatible for u8 {}
|
||||
impl KnownWasmCompatible for u16 {}
|
||||
impl KnownWasmCompatible for u32 {}
|
||||
impl KnownWasmCompatible for u64 {}
|
||||
impl KnownWasmCompatible for u128 {}
|
||||
|
||||
impl KnownWasmCompatible for bool {}
|
||||
impl KnownWasmCompatible for String {}
|
||||
|
||||
impl KnownWasmCompatible for () {}
|
||||
|
||||
impl KnownWasmCompatible for js_sys::Map {}
|
||||
impl KnownWasmCompatible for js_sys::Array {}
|
Loading…
Reference in a new issue