Document, improve some API functionality

This commit is contained in:
NGnius (Graham) 2022-06-16 17:03:43 -04:00
parent 0a5323c927
commit d242cb9d70
25 changed files with 183 additions and 56 deletions

28
Cargo.lock generated
View file

@ -924,20 +924,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "usdpl"
version = "0.4.0"
dependencies = [
"console_error_panic_hook",
"js-sys",
"usdpl-core",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-sys",
"wee_alloc",
]
[[package]]
name = "usdpl-back"
version = "0.4.0"
@ -955,6 +941,20 @@ dependencies = [
"base64",
]
[[package]]
name = "usdpl-front"
version = "0.4.0"
dependencies = [
"console_error_panic_hook",
"js-sys",
"usdpl-core",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-sys",
"wee_alloc",
]
[[package]]
name = "usdpl-rs"
version = "0.1.0"

View file

@ -1,3 +1,7 @@
[![usdpl-back](https://img.shields.io/crates/v/usdpl-back?label=usdpl-back&style=flat-square)](https://crates.io/crates/usdpl-back)
[![usdpl-core](https://img.shields.io/crates/v/usdpl-core?label=usdpl-core&style=flat-square)](https://crates.io/crates/usdpl-core)
[![usdpl-front](https://img.shields.io/crates/v/usdpl-front?label=usdpl-front&style=flat-square)](https://crates.io/crates/usdpl-front)
# usdpl-rs
Universal Steam Deck Plugin Library
@ -5,11 +9,15 @@ Universal Steam Deck Plugin Library
A faster, lighter way to write plugins
### Goals
- [ ] Minimum viable plugin
- [ ] Call back-end API from front-end UI
- [ ] Async support
- [x] Minimum viable plugin
- [x] Call back-end API from front-end UI
- [x] External API documentation
- [ ] Internal API documentation
- [x] Async support
- [ ] Sync support
- [ ] PluginLoader/Decky support
- [ ] Crankshaft support
- [ ] Unnamed plugin system support
- [ ] Cross-framework tooling
- [ ] Other programming languages support (C bindings)

7
README.tpl Normal file
View file

@ -0,0 +1,7 @@
[![usdpl-back](https://img.shields.io/crates/v/usdpl-back?label=usdpl-back&style=flat-square)](https://crates.io/crates/usdpl-back)
[![usdpl-core](https://img.shields.io/crates/v/usdpl-core?label=usdpl-core&style=flat-square)](https://crates.io/crates/usdpl-core)
[![usdpl-front](https://img.shields.io/crates/v/usdpl-front?label=usdpl-front&style=flat-square)](https://crates.io/crates/usdpl-front)
# {{crate}}
{{readme}}

View file

@ -3,13 +3,17 @@
//! A faster, lighter way to write plugins
//!
//! ## Goals
//! - [ ] Minimum viable plugin
//! - [ ] Call back-end API from front-end UI
//! - [ ] Async support
//! - [x] Minimum viable plugin
//! - [x] Call back-end API from front-end UI
//! - [x] External API documentation
//! - [ ] Internal API documentation
//! - [x] Async support
//! - [ ] Sync support
//! - [ ] PluginLoader/Decky support
//! - [ ] Crankshaft support
//! - [ ] Unnamed plugin system support
//! - [ ] Cross-framework tooling
//! - [ ] Other programming languages support (C bindings)
//!
fn main() {
println!("Hello, USDPL!");

View file

@ -8,9 +8,10 @@ readme = "README.md"
description = "Universal Steam Deck Plugin Library back-end"
[features]
default = []
decky = []
crankshaft = []
default = ["blocking"]
decky = ["usdpl-core/decky"]
crankshaft = ["usdpl-core/crankshaft"]
blocking = ["tokio"] # synchronous API for async functionality, using tokio
[dependencies]
usdpl-core = { version = "0.4.0", path = "../usdpl-core" }
@ -18,4 +19,4 @@ usdpl-core = { version = "0.4.0", path = "../usdpl-core" }
# HTTP web framework
warp = { version = "0.3" }
bytes = { version = "1.1" }
tokio = { version = "1.19", features = ["rt", "rt-multi-thread"] }
tokio = { version = "1.19", features = ["rt", "rt-multi-thread"], optional = true }

View file

@ -1,9 +1,9 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-back?style=flat-square)](https://crates.io/crates/usdpl-back)
# usdpl-back
Back-end library for plugins.
Targets x86_64 (native Steam Deck ISA).
This is a minimalist TCP server for handling events from the front-end.
This is a minimalist web server for handling events from the front-end.
License: GPL-3.0-only

5
usdpl-back/README.tpl Normal file
View file

@ -0,0 +1,5 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-back?style=flat-square)](https://crates.io/crates/usdpl-back)
# {{crate}}
{{readme}}

View file

@ -1,5 +1,13 @@
use usdpl_core::serdes::Primitive;
/// A function which can be called from the front-end (remotely)
pub trait Callable: Send + Sync {
/// Invoke the function
fn call(&mut self, params: Vec<Primitive>) -> Vec<Primitive>;
}
impl<F: (FnMut(Vec<Primitive>) -> Vec<Primitive>) + Send + Sync> Callable for F {
fn call(&mut self, params: Vec<Primitive>) -> Vec<Primitive> {
(self)(params)
}
}

View file

@ -27,19 +27,31 @@ impl Instance {
}
}
/// Register a function which can be invoked by the front-end
/// Register a function which can be invoked by the front-end, builder style
pub fn register<S: std::convert::Into<String>, F: Callable + 'static>(
&mut self,
mut self,
name: S,
f: F,
) -> &mut Self {
//CALLS.lock().unwrap().insert(name.into(), Mutex::new(Box::new(f)));
) -> Self {
self.calls
.insert(name.into(), Arc::new(Mutex::new(Box::new(f))));
self
}
pub fn serve(&self) -> Result<(), ()> {
/// Register a function which can be invoked by the front-end, object style
pub fn register_mut<S: std::convert::Into<String>, F: Callable + 'static>(
&mut self,
name: S,
f: F,
) -> &mut Self {
self.calls
.insert(name.into(), Arc::new(Mutex::new(Box::new(f))));
self
}
/// Run the web server instance forever, blocking this thread
#[cfg(feature = "blocking")]
pub fn run_blocking(&self) -> Result<(), ()> {
let result = self.serve_internal();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
@ -48,6 +60,11 @@ impl Instance {
.block_on(result)
}
/// Run the web server forever, asynchronously
pub async fn run(&self) -> Result<(), ()> {
self.serve_internal().await
}
fn handle_call(
packet: socket::Packet,
handlers: &HashMap<String, WrappedCallable>,
@ -80,7 +97,7 @@ impl Instance {
}
/// Receive and execute callbacks forever
pub async fn serve_internal(&self) -> Result<(), ()> {
async fn serve_internal(&self) -> Result<(), ()> {
let handlers = self.calls.clone();
//self.calls = HashMap::new();
let calls = warp::post()

View file

@ -1,8 +1,9 @@
//! Back-end library for plugins.
//! Targets x86_64 (native Steam Deck ISA).
//!
//! This is a minimalist TCP server for handling events from the front-end.
//! This is a minimalist web server for handling events from the front-end.
//!
#![warn(missing_docs)]
mod callable;
//mod errors;
@ -12,6 +13,7 @@ pub use callable::Callable;
pub use instance::Instance;
//pub use errors::{ServerError, ServerResult};
/// usdpl-core re-export
pub mod core {
pub use usdpl_core::*;
}

View file

@ -7,7 +7,10 @@ repository = "https://github.com/NGnius/usdpl-rs"
readme = "README.md"
description = "Universal Steam Deck Plugin Library core"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = []
decky = []
crankshaft = []
[dependencies]
base64 = "0.13"

View file

@ -1,6 +1,6 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-core?style=flat-square)](https://crates.io/crates/usdpl-core)
# usdpl-core
Datatypes and constants core the back-end and front-end libraries' operation.
This contains serialization functionality and networking datatypes.
License: GPL-3.0-only

5
usdpl-core/README.tpl Normal file
View file

@ -0,0 +1,5 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-core?style=flat-square)](https://crates.io/crates/usdpl-core)
# {{crate}}
{{readme}}

View file

@ -1,10 +1,16 @@
/// Supported plugin platforms
pub enum Platform {
/// Generic platform
Any,
/// Decky aka PluginLoader platform
Decky,
/// Crankshaft platform
Crankshaft,
}
impl Platform {
/// The current platform that usdpl-core is configured to target.
/// This is determined by feature flags.
pub fn current() -> Self {
#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))]
{

View file

@ -1,5 +1,7 @@
//! Datatypes and constants core the back-end and front-end libraries' operation.
//! This contains serialization functionality and networking datatypes.
#![warn(missing_docs)]
mod remote_call;
#[cfg(not(any(feature = "decky", feature = "crankshaft")))]
@ -15,6 +17,8 @@ pub mod socket;
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")))]
pub use super::api_any::*;

View file

@ -2,8 +2,11 @@ use crate::serdes::{DumpError, Dumpable, LoadError, Loadable, Primitive};
/// Remote call packet representing a function to call on the back-end, sent from the front-end
pub struct RemoteCall {
/// The call id assigned by the front-end
pub id: u64,
/// The function's name
pub function: String,
/// The function's input parameters
pub parameters: Vec<Primitive>,
}
@ -34,7 +37,9 @@ impl Dumpable for RemoteCall {
/// Remote call response packet representing the response from a remote call after the back-end has executed it.
pub struct RemoteCallResponse {
/// The call id from the RemoteCall
pub id: u64,
/// The function's result
pub response: Vec<Primitive>,
}

View file

@ -3,19 +3,30 @@ use super::{DumpError, Dumpable, LoadError, Loadable};
/// Primitive types supported for communication between the USDPL back- and front-end.
/// These are used for sending over the TCP connection.
pub enum Primitive {
/// Null or unsupported object
Empty,
/// String-like
String(String),
/// f32
F32(f32),
/// f64
F64(f64),
/// u32
U32(u32),
/// u64
U64(u64),
/// i32
I32(i32),
/// i64
I64(i64),
/// boolean
Bool(bool),
/// Non-primitive in Json format
Json(String),
}
impl Primitive {
/// Discriminant -- first byte of a dumped primitive
const fn discriminant(&self) -> u8 {
match self {
Self::Empty => 1,

View file

@ -5,8 +5,11 @@ const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true);
/// Errors from Loadable::load
#[derive(Debug)]
pub enum LoadError {
/// Buffer smaller than expected
TooSmallBuffer,
/// Unexpected/corrupted data encountered
InvalidData,
/// Unimplemented
#[cfg(debug_assertions)]
Todo,
}
@ -28,6 +31,7 @@ pub trait Loadable: Sized {
/// If anything is wrong with the buffer, None should be returned.
fn load(buffer: &[u8]) -> Result<(Self, usize), LoadError>;
/// Load data from a base64-encoded buffer
fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> {
let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE];
let len = decode_config_slice(buffer, B64_CONF, &mut buffer2)
@ -39,8 +43,11 @@ pub trait Loadable: Sized {
/// Errors from Dumpable::dump
#[derive(Debug)]
pub enum DumpError {
/// Buffer not big enough to dump data into
TooSmallBuffer,
/// Data cannot be dumped
Unsupported,
/// Unimplemented
#[cfg(debug_assertions)]
Todo,
}
@ -62,6 +69,8 @@ pub trait Dumpable {
/// If anything is wrong, false should be returned.
fn dump(&self, buffer: &mut [u8]) -> Result<usize, DumpError>;
/// Dump data as base64-encoded.
/// Useful for transmitting data as text.
fn dump_base64(&self, buffer: &mut [u8]) -> Result<usize, DumpError> {
let mut buffer2 = [0u8; crate::socket::PACKET_BUFFER_SIZE];
let len = self.dump(&mut buffer2)?;

View file

@ -1,13 +1,18 @@
//! Web messaging
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use crate::serdes::{DumpError, Dumpable, LoadError, Loadable};
use crate::{RemoteCall, RemoteCallResponse};
pub const HOST_STR: &str = "127.0.0.1";
/// Host IP address for web browsers
pub const HOST_STR: &str = "localhost";
/// Host IP address
pub const HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
/// Standard max packet size
pub const PACKET_BUFFER_SIZE: usize = 1024;
/// Address and port
#[inline]
pub fn socket_addr(port: u16) -> SocketAddr {
SocketAddr::V4(SocketAddrV4::new(HOST, port))
@ -15,13 +20,21 @@ pub fn socket_addr(port: u16) -> SocketAddr {
/// Accepted Packet types and the data they contain
pub enum Packet {
/// A remote call
Call(RemoteCall),
/// A reponse to a remote call
CallResponse(RemoteCallResponse),
/// Unused
KeepAlive,
/// Invalid
Invalid,
/// General message
Message(String),
/// Response to an unsupported packet
Unsupported,
/// Broken packet type, useful for testing
Bad,
/// Many packets merged into one
Many(Vec<Packet>),
}

View file

@ -1,20 +1,21 @@
[package]
name = "usdpl"
name = "usdpl-front"
version = "0.4.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 = ["console_error_panic_hook"]
decky = []
crankshaft = []
default = []
decky = ["usdpl-core/decky"]
crankshaft = ["usdpl-core/crankshaft"]
debug = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2"
@ -40,14 +41,10 @@ web-sys = { version = "0.3", features = [
'RequestMode',
'Response',
'Window',
]}#["WebSocket", "MessageEvent", "ErrorEvent", "BinaryType"] }
]}
js-sys = { version = "0.3" }
usdpl-core = { version = "0.4.0", path = "../usdpl-core" }
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
#[profile.release]
# Tell `rustc` to optimize for small code size.
#opt-level = "s"
wasm-bindgen-test = { version = "0.3.13" }

View file

@ -0,0 +1,9 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-front?style=flat-square)](https://crates.io/crates/usdpl-front)
# usdpl-front-front
Front-end library to be called from Javascript.
Targets WASM.
In true Javascript tradition, this part of the library does not support error handling.

5
usdpl-front/README.tpl Normal file
View file

@ -0,0 +1,5 @@
[![Crates.io](https://img.shields.io/crates/v/usdpl-front?style=flat-square)](https://crates.io/crates/usdpl-front)
# {{crate}}-front
{{readme}}

View file

@ -1,11 +1,11 @@
import base64
if __name__ == "__main__":
print("Embedding WASM into udspl.js")
print("Embedding WASM into udspl_front.js")
# assumption: current working directory (relative to this script) is ../
# assumption: release wasm binary at ./pkg/usdpl_bg.wasm
with open("./pkg/usdpl_bg.wasm", mode="rb") as infile:
with open("./pkg/usdpl.js", mode="ab") as outfile:
with open("./pkg/usdpl_front_bg.wasm", mode="rb") as infile:
with open("./pkg/usdpl_front.js", mode="ab") as outfile:
outfile.write("\n\n// USDPL customization\nconst encoded = \"".encode())
encoded = base64.b64encode(infile.read())
outfile.write(encoded)
@ -32,6 +32,6 @@ export function init_embedded() {
return init(decode())
}
""".encode())
with open("./pkg/usdpl.d.ts", "a") as outfile:
with open("./pkg/usdpl_front.d.ts", "a") as outfile:
outfile.write("\n\n// USDPL customization\nexport function init_embedded();\n")
print("Done: Embedded WASM into udspl.js")
print("Done: Embedded WASM into udspl_front.js")

View file

@ -2,12 +2,15 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[cfg(feature = "debug")]
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str);
#[cfg(feature = "debug")]
#[wasm_bindgen(js_namespace = console, js_name = warn)]
pub fn console_warn(s: &str);
#[cfg(feature = "debug")]
#[wasm_bindgen(js_namespace = console, js_name = error)]
pub fn console_error(s: &str);
}

View file

@ -3,6 +3,7 @@
//!
//! In true Javascript tradition, this part of the library does not support error handling.
//!
#![warn(missing_docs)]
mod connection;
mod convert;
@ -17,9 +18,9 @@ use usdpl_core::{socket::Packet, RemoteCall};
static mut CTX: UsdplContext = UsdplContext { port: 31337, id: 1 };
#[wasm_bindgen]
//#[wasm_bindgen]
#[derive(Debug)]
pub struct UsdplContext {
struct UsdplContext {
port: u16,
id: u64,
}
@ -62,6 +63,7 @@ pub fn target() -> String {
/// Returns null (None) if this fails for any reason.
#[wasm_bindgen]
pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
#[cfg(feature = "debug")]
imports::console_log(&format!(
"call_backend({}, [params; {}])",
name,
@ -73,6 +75,7 @@ pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
params.push(convert::js_to_primitive(val));
}
let port = get_port();
#[cfg(feature = "debug")]
imports::console_log(&format!("USDPL: Got port {}", port));
let results = connection::send_js(
Packet::Call(RemoteCall {
@ -85,7 +88,9 @@ pub async fn call_backend(name: String, parameters: Vec<JsValue>) -> JsValue {
.await;
let results = match results {
Ok(x) => x,
#[allow(unused_variables)]
Err(e) => {
#[cfg(feature = "debug")]
imports::console_error(&format!("USDPL: Got error while calling {}: {:?}", name, e));
return JsValue::NULL;
}