diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index ca15c755..461ddedc 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -73,7 +73,10 @@ pub struct TypeInfos { impl TypeInfos { #[must_use] - pub fn collect_from_functions(typedefs: &TypeDefArena, functions: &[Function]) -> Self { + pub fn collect_from_functions<'a>( + typedefs: &TypeDefArena, + functions: impl Iterator, + ) -> Self { let mut this = Self::default(); for func in functions { diff --git a/crates/gen-guest-js/src/lib.rs b/crates/gen-guest-js/src/lib.rs index 920125cb..7f16a9aa 100644 --- a/crates/gen-guest-js/src/lib.rs +++ b/crates/gen-guest-js/src/lib.rs @@ -23,7 +23,22 @@ pub struct Builder { impl GeneratorBuilder for Builder { fn build(self, interface: Interface) -> Box { - let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions); + let methods = interface + .typedefs + .iter() + .filter_map(|(_, typedef)| { + if let TypeDefKind::Resource(methods) = &typedef.kind { + Some(methods.iter()) + } else { + None + } + }) + .flatten(); + + let infos = TypeInfos::collect_from_functions( + &interface.typedefs, + interface.functions.iter().chain(methods), + ); let serde_utils = SerdeUtils::collect_from_functions(&interface.typedefs, &interface.functions); @@ -80,6 +95,7 @@ export async function {ident} ({params}) {{ fn print_resource( &self, + mod_ident: &str, docs: &str, ident: &str, functions: &[Function], @@ -91,13 +107,32 @@ export async function {ident} ({params}) {{ .iter() .map(|func| { let docs = self.print_docs(func); + let mod_ident = mod_ident.to_snake_case(); + let resource_ident = ident.to_snake_case(); let ident = func.ident.to_lower_camel_case(); let params = print_function_params(&func.params); + let deserialize_result = func + .result + .as_ref() + .map(|res| self.print_deserialize_function_result(res)) + .unwrap_or_default(); + + let serialize_params = func + .params + .iter() + .map(|(ident, ty)| self.print_serialize_ty(&ident.to_lower_camel_case(), ty)) + .collect::>() + .join(";\n"); + format!( - r#" -{docs} + r#"{docs} async {ident} ({params}) {{ + const out = [] + serializeU32(out, this.#id); + {serialize_params} + + await fetch('ipc://localhost/{mod_ident}::resource::{resource_ident}/{ident}', {{ method: "POST", body: Uint8Array.from(out), headers: {{ 'Content-Type': 'application/octet-stream' }} }}){deserialize_result} }} "# ) @@ -106,9 +141,9 @@ async {ident} ({params}) {{ let deserialize = if info.contains(TypeInfo::RESULT) { format!( - "deserialize(de) {{ + "static deserialize(de) {{ const self = new {ident}(); - self.#id = deserializeU64(de); + self.#id = deserializeU32(de); return self }}" ) @@ -117,7 +152,7 @@ async {ident} ({params}) {{ }; format!( - "{docs}\nclass {ident} {{ + "{docs}\nexport class {ident} {{ #id; {functions} {deserialize} @@ -292,7 +327,13 @@ impl Generate for JavaScript { .filter_map(|(id, typedef)| { let info = self.infos[id]; if let TypeDefKind::Resource(functions) = &typedef.kind { - Some(self.print_resource(&typedef.docs, &typedef.ident, functions, info)) + Some(self.print_resource( + &self.interface.ident, + &typedef.docs, + &typedef.ident, + functions, + info, + )) } else { None } diff --git a/crates/gen-guest-js/tests/lists.js b/crates/gen-guest-js/tests/lists.js index d83a34e1..77b7da0e 100644 --- a/crates/gen-guest-js/tests/lists.js +++ b/crates/gen-guest-js/tests/lists.js @@ -335,15 +335,18 @@ serializeS64(out, val.c4) }function serializeOtherVariant(out, val) { if (val.A) { serializeU32(out, 0); - return + + return } if (val.B) { serializeU32(out, 1); - return serializeU32(out, val.B) + serializeU32(out, val.B) + return } if (val.C) { serializeU32(out, 2); - return serializeString(out, val.C) + serializeString(out, val.C) + return } @@ -351,19 +354,23 @@ if (val.C) { }function serializeSomeVariant(out, val) { if (val.A) { serializeU32(out, 0); - return serializeString(out, val.A) + serializeString(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return + + return } if (val.C) { serializeU32(out, 2); - return serializeU32(out, val.C) + serializeU32(out, val.C) + return } if (val.D) { serializeU32(out, 3); - return serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D) + serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D) + return } diff --git a/crates/gen-guest-js/tests/resources.js b/crates/gen-guest-js/tests/resources.js index 8aaefc04..46f7e3c1 100644 --- a/crates/gen-guest-js/tests/resources.js +++ b/crates/gen-guest-js/tests/resources.js @@ -17,6 +17,68 @@ class Deserializer { return out } } +// function varint_max(bits) { +// const BITS_PER_BYTE = 8; +// const BITS_PER_VARINT_BYTE = 7; + +// const roundup_bits = bits + (BITS_PER_BYTE - 1); + +// return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); +// } + +const varint_max = { + 16: 3, + 32: 5, + 64: 10, + 128: 19 +} +function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; +} + +function de_varint(de, bits) { + let out = 0; + + for (let i = 0; i < varint_max[bits]; i++) { + const val = de.pop(); + const carry = val & 0x7F; + out |= carry << (7 * i); + + if ((val & 0x80) === 0) { + if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') +} + +function de_varint_big(de, bits) { + let out = 0n; + + for (let i = 0; i < varint_max[bits]; i++) { + const val = de.pop(); + const carry = BigInt(val) & 0x7Fn; + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') +} +function deserializeU32(de) { + return de_varint(de, 32) +} /** @@ -52,59 +114,102 @@ export async function constructorB () { } -class A { +export class A { #id; - -/** + /** */ async f1 () { -} + const out = [] + serializeU32(out, this.#id); + + await fetch('ipc://localhost/resources::resource::a/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) +} /** * @param {number} a */ async f2 (a) { -} + const out = [] + serializeU32(out, this.#id); + serializeU32(out, a) + await fetch('ipc://localhost/resources::resource::a/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) +} /** * @param {number} a * @param {number} b */ async f3 (a, b) { + const out = [] + serializeU32(out, this.#id); + serializeU32(out, a); +serializeU32(out, b) + + await fetch('ipc://localhost/resources::resource::a/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) } - deserialize(de) { + static deserialize(de) { const self = new A(); - self.#id = deserializeU64(de); + self.#id = deserializeU32(de); return self } } -class B { +export class B { #id; - -/** + /** * @returns {Promise} */ async f1 () { -} + const out = [] + serializeU32(out, this.#id); + + + await fetch('ipc://localhost/resources::resource::b/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) + return A.deserialize(de) + }) +} /** * @param {A} x * @returns {Promise>} */ async f2 (x) { -} + const out = [] + serializeU32(out, this.#id); + x.serialize(out) + await fetch('ipc://localhost/resources::resource::b/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) + + return deserializeResult(de, (de) => deserializeU32(de), () => {}) + }) +} /** * @param {A[] | null} x * @returns {Promise>} */ async f3 (x) { + const out = [] + serializeU32(out, this.#id); + serializeOption(out, (out, v) => serializeList(out, (out, v) => v.serialize(out), v), x) + + await fetch('ipc://localhost/resources::resource::b/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) + + return deserializeResult(de, (de) => A.deserialize(de), () => {}) + }) } - deserialize(de) { + static deserialize(de) { const self = new B(); - self.#id = deserializeU64(de); + self.#id = deserializeU32(de); return self } } \ No newline at end of file diff --git a/crates/gen-guest-js/tests/variants.js b/crates/gen-guest-js/tests/variants.js index a7aafef9..0845012a 100644 --- a/crates/gen-guest-js/tests/variants.js +++ b/crates/gen-guest-js/tests/variants.js @@ -431,31 +431,38 @@ case 1: }function serializeV1(out, val) { if (val.A) { serializeU32(out, 0); - return + + return } if (val.B) { serializeU32(out, 1); - return serializeU1(out, val.B) + serializeU1(out, val.B) + return } if (val.C) { serializeU32(out, 2); - return serializeE1(out, val.C) + serializeE1(out, val.C) + return } if (val.D) { serializeU32(out, 3); - return serializeString(out, val.D) + serializeString(out, val.D) + return } if (val.E) { serializeU32(out, 4); - return serializeEmpty(out, val.E) + serializeEmpty(out, val.E) + return } if (val.F) { serializeU32(out, 5); - return + + return } if (val.G) { serializeU32(out, 6); - return serializeU32(out, val.G) + serializeU32(out, val.G) + return } @@ -463,11 +470,13 @@ if (val.G) { }function serializeCasts1(out, val) { if (val.A) { serializeU32(out, 0); - return serializeS32(out, val.A) + serializeS32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeF32(out, val.B) + serializeF32(out, val.B) + return } @@ -475,11 +484,13 @@ if (val.B) { }function serializeCasts2(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF64(out, val.A) + serializeF64(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeF32(out, val.B) + serializeF32(out, val.B) + return } @@ -487,11 +498,13 @@ if (val.B) { }function serializeCasts3(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF64(out, val.A) + serializeF64(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeU64(out, val.B) + serializeU64(out, val.B) + return } @@ -499,11 +512,13 @@ if (val.B) { }function serializeCasts4(out, val) { if (val.A) { serializeU32(out, 0); - return serializeU32(out, val.A) + serializeU32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeS64(out, val.B) + serializeS64(out, val.B) + return } @@ -511,11 +526,13 @@ if (val.B) { }function serializeCasts5(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF32(out, val.A) + serializeF32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeS64(out, val.B) + serializeS64(out, val.B) + return } @@ -523,11 +540,13 @@ if (val.B) { }function serializeCasts6(out, val) { if (val.A) { serializeU32(out, 0); - return {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])} + {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])} + return } if (val.B) { serializeU32(out, 1); - return {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])} + {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])} + return } diff --git a/crates/gen-guest-rust/src/lib.rs b/crates/gen-guest-rust/src/lib.rs index b3fdc8a4..4c59b660 100644 --- a/crates/gen-guest-rust/src/lib.rs +++ b/crates/gen-guest-rust/src/lib.rs @@ -14,6 +14,7 @@ use tauri_bindgen_core::TypeInfo; use tauri_bindgen_core::TypeInfos; use tauri_bindgen_gen_rust::FnSig; use tauri_bindgen_gen_rust::{BorrowMode, RustGenerator}; +use wit_parser::TypeDefKind; use wit_parser::{Function, Interface}; #[derive(Default, Debug, Clone)] @@ -35,7 +36,22 @@ pub struct Builder { impl GeneratorBuilder for Builder { fn build(self, interface: Interface) -> Box { - let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions); + let methods = interface + .typedefs + .iter() + .filter_map(|(_, typedef)| { + if let TypeDefKind::Resource(methods) = &typedef.kind { + Some(methods.iter()) + } else { + None + } + }) + .flatten(); + + let infos = TypeInfos::collect_from_functions( + &interface.typedefs, + interface.functions.iter().chain(methods), + ); Box::new(RustWasm { opts: self, @@ -117,6 +133,7 @@ impl RustGenerator for RustWasm { fn print_resource( &self, + mod_ident: &str, docs: &str, ident: &proc_macro2::Ident, functions: &[Function], @@ -139,9 +156,17 @@ impl RustGenerator for RustWasm { &BorrowMode::Owned, ); + let mod_ident = format!("{mod_ident}::resource::{}", ident.to_string().to_snake_case()); + let ident = func.ident.to_snake_case(); + + let param_idents = func + .params + .iter() + .map(|(ident, _)| format_ident!("{}", ident)); + quote! { #sig { - todo!() + ::tauri_bindgen_guest_rust::invoke(#mod_ident, #ident, &(self.0, #(#param_idents),*)).await.unwrap() } } }); @@ -149,9 +174,7 @@ impl RustGenerator for RustWasm { quote! { #docs #additional_attrs - pub struct #ident { - id: u64 - } + pub struct #ident(u32); impl #ident { #(#functions)* diff --git a/crates/gen-guest-rust/tests/resources.rs b/crates/gen-guest-rust/tests/resources.rs index e486d626..473d50b5 100644 --- a/crates/gen-guest-rust/tests/resources.rs +++ b/crates/gen-guest-rust/tests/resources.rs @@ -3,34 +3,66 @@ pub mod resources { use ::tauri_bindgen_guest_rust::serde; use ::tauri_bindgen_guest_rust::bitflags; - #[derive(serde::Deserialize)] - pub struct A { - id: u64, - } + #[derive(serde::Serialize, serde::Deserialize)] + pub struct A(u32); impl A { pub async fn f1(&self) { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::a", + "f1", + &(self.0,), + ) + .await + .unwrap() } pub async fn f2(&self, a: u32) { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::a", + "f2", + &(self.0, a), + ) + .await + .unwrap() } pub async fn f3(&self, a: u32, b: u32) { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::a", + "f3", + &(self.0, a, b), + ) + .await + .unwrap() } } #[derive(serde::Deserialize)] - pub struct B { - id: u64, - } + pub struct B(u32); impl B { pub async fn f1(&self) -> A { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::b", + "f1", + &(self.0,), + ) + .await + .unwrap() } pub async fn f2(&self, x: A) -> Result { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::b", + "f2", + &(self.0, x), + ) + .await + .unwrap() } pub async fn f3(&self, x: Option<&'_ [A]>) -> Result { - todo!() + ::tauri_bindgen_guest_rust::invoke( + "resources::resource::b", + "f3", + &(self.0, x), + ) + .await + .unwrap() } } pub async fn constructor_a() -> A { diff --git a/crates/gen-guest-ts/src/lib.rs b/crates/gen-guest-ts/src/lib.rs index 3db55494..d5fbd2f3 100644 --- a/crates/gen-guest-ts/src/lib.rs +++ b/crates/gen-guest-ts/src/lib.rs @@ -31,7 +31,22 @@ pub struct Builder { impl GeneratorBuilder for Builder { fn build(self, interface: Interface) -> Box { - let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions); + let methods = interface + .typedefs + .iter() + .filter_map(|(_, typedef)| { + if let TypeDefKind::Resource(methods) = &typedef.kind { + Some(methods.iter()) + } else { + None + } + }) + .flatten(); + + let infos = TypeInfos::collect_from_functions( + &interface.typedefs, + interface.functions.iter().chain(methods), + ); let serde_utils = SerdeUtils::collect_from_functions(&interface.typedefs, &interface.functions); @@ -189,7 +204,9 @@ export async function {ident} ({params}) : {result} {{ TypeDefKind::Variant(cases) => self.print_variant(&docs, ident, cases), TypeDefKind::Enum(cases) => self.print_enum(&docs, ident, cases), TypeDefKind::Union(cases) => self.print_union(&docs, ident, cases), - TypeDefKind::Resource(functions) => self.print_resource(&docs, ident, functions), + TypeDefKind::Resource(functions) => { + self.print_resource(&self.interface.ident, &docs, ident, functions) + } } } @@ -293,25 +310,48 @@ export async function {ident} ({params}) : {result} {{ format!("{docs}\nexport type {ident} = {cases};\n") } - fn print_resource(&self, docs: &str, ident: &str, functions: &[Function]) -> String { + fn print_resource( + &self, + mod_ident: &str, + docs: &str, + ident: &str, + functions: &[Function], + ) -> String { let functions: String = functions .iter() .map(|func| { let docs = print_docs(&func.docs); - + let mod_ident = mod_ident.to_snake_case(); + let resource_ident = ident.to_snake_case(); let ident = func.ident.to_lower_camel_case(); let params = self.print_function_params(&func.params); let result = func .result .as_ref() - .map(|result| self.print_function_result(result)) + .map_or("void".to_string(), |result| self.print_function_result(result)); + + let deserialize_result = func + .result + .as_ref() + .map(|res| self.print_deserialize_function_result(res)) .unwrap_or_default(); + let serialize_params = func + .params + .iter() + .map(|(ident, ty)| self.print_serialize_ty(&ident.to_lower_camel_case(), ty)) + .collect::>() + .join(";\n"); + format!( - r#" -{docs} -async {ident} ({params}) {result} {{ + r#"{docs} +async {ident} ({params}) : {result} {{ + const out = [] + serializeU32(out, this.#id); + {serialize_params} + + await fetch('ipc://localhost/{mod_ident}::resource::{resource_ident}/{ident}', {{ method: "POST", body: Uint8Array.from(out), headers: {{ 'Content-Type': 'application/octet-stream' }} }}){deserialize_result} }} "# ) @@ -319,7 +359,7 @@ async {ident} ({params}) {result} {{ .collect(); format!( - "{docs}\nclass {ident} {{ + "{docs}\nexport class {ident} {{ #id: number; {functions} diff --git a/crates/gen-guest-ts/tests/lists.ts b/crates/gen-guest-ts/tests/lists.ts index d67b1411..c5d3803a 100644 --- a/crates/gen-guest-ts/tests/lists.ts +++ b/crates/gen-guest-ts/tests/lists.ts @@ -336,15 +336,18 @@ serializeS64(out, val.c4) }function serializeOtherVariant(out, val) { if (val.A) { serializeU32(out, 0); - return + + return } if (val.B) { serializeU32(out, 1); - return serializeU32(out, val.B) + serializeU32(out, val.B) + return } if (val.C) { serializeU32(out, 2); - return serializeString(out, val.C) + serializeString(out, val.C) + return } @@ -352,19 +355,23 @@ if (val.C) { }function serializeSomeVariant(out, val) { if (val.A) { serializeU32(out, 0); - return serializeString(out, val.A) + serializeString(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return + + return } if (val.C) { serializeU32(out, 2); - return serializeU32(out, val.C) + serializeU32(out, val.C) + return } if (val.D) { serializeU32(out, 3); - return serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D) + serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D) + return } diff --git a/crates/gen-guest-ts/tests/resources.ts b/crates/gen-guest-ts/tests/resources.ts index f633ef7f..34cfd4a5 100644 --- a/crates/gen-guest-ts/tests/resources.ts +++ b/crates/gen-guest-ts/tests/resources.ts @@ -18,39 +18,144 @@ class Deserializer { return out } } +// function varint_max(bits) { +// const BITS_PER_BYTE = 8; +// const BITS_PER_VARINT_BYTE = 7; +// const roundup_bits = bits + (BITS_PER_BYTE - 1); -class A { +// return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); +// } + +const varint_max = { + 16: 3, + 32: 5, + 64: 10, + 128: 19 +} +function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; +} + +function de_varint(de, bits) { + let out = 0; + + for (let i = 0; i < varint_max[bits]; i++) { + const val = de.pop(); + const carry = val & 0x7F; + out |= carry << (7 * i); + + if ((val & 0x80) === 0) { + if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') +} + +function de_varint_big(de, bits) { + let out = 0n; + + for (let i = 0; i < varint_max[bits]; i++) { + const val = de.pop(); + const carry = BigInt(val) & 0x7Fn; + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') +} +function deserializeU32(de) { + return de_varint(de, 32) +} + + +export class A { #id: number; +async f1 () : void { + const out = [] + serializeU32(out, this.#id); + -async f1 () { + await fetch('ipc://localhost/resources::resource::a/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) } +async f2 (a: number) : void { + const out = [] + serializeU32(out, this.#id); + serializeU32(out, a) -async f2 (a: number) { + await fetch('ipc://localhost/resources::resource::a/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) } +async f3 (a: number, b: number) : void { + const out = [] + serializeU32(out, this.#id); + serializeU32(out, a); +serializeU32(out, b) -async f3 (a: number, b: number) { + await fetch('ipc://localhost/resources::resource::a/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) } } -class B { +export class B { #id: number; +async f1 () : Promise { + const out = [] + serializeU32(out, this.#id); + -async f1 () Promise { + await fetch('ipc://localhost/resources::resource::b/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) + + return A.deserialize(de) + }) } +async f2 (x: A) : Promise> { + const out = [] + serializeU32(out, this.#id); + x.serialize(out) + + await fetch('ipc://localhost/resources::resource::b/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) -async f2 (x: A) Promise> { + return deserializeResult(de, (de) => deserializeU32(de), () => {}) + }) } +async f3 (x: A[] | null) : Promise> { + const out = [] + serializeU32(out, this.#id); + serializeOption(out, (out, v) => serializeList(out, (out, v) => v.serialize(out), v), x) + + await fetch('ipc://localhost/resources::resource::b/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(new Uint8Array(bytes)) -async f3 (x: A[] | null) Promise> { + return deserializeResult(de, (de) => A.deserialize(de), () => {}) + }) } } diff --git a/crates/gen-guest-ts/tests/variants.ts b/crates/gen-guest-ts/tests/variants.ts index dda3e051..08e590b9 100644 --- a/crates/gen-guest-ts/tests/variants.ts +++ b/crates/gen-guest-ts/tests/variants.ts @@ -433,31 +433,38 @@ case 1: }function serializeV1(out, val) { if (val.A) { serializeU32(out, 0); - return + + return } if (val.B) { serializeU32(out, 1); - return serializeU1(out, val.B) + serializeU1(out, val.B) + return } if (val.C) { serializeU32(out, 2); - return serializeE1(out, val.C) + serializeE1(out, val.C) + return } if (val.D) { serializeU32(out, 3); - return serializeString(out, val.D) + serializeString(out, val.D) + return } if (val.E) { serializeU32(out, 4); - return serializeEmpty(out, val.E) + serializeEmpty(out, val.E) + return } if (val.F) { serializeU32(out, 5); - return + + return } if (val.G) { serializeU32(out, 6); - return serializeU32(out, val.G) + serializeU32(out, val.G) + return } @@ -465,11 +472,13 @@ if (val.G) { }function serializeCasts1(out, val) { if (val.A) { serializeU32(out, 0); - return serializeS32(out, val.A) + serializeS32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeF32(out, val.B) + serializeF32(out, val.B) + return } @@ -477,11 +486,13 @@ if (val.B) { }function serializeCasts2(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF64(out, val.A) + serializeF64(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeF32(out, val.B) + serializeF32(out, val.B) + return } @@ -489,11 +500,13 @@ if (val.B) { }function serializeCasts3(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF64(out, val.A) + serializeF64(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeU64(out, val.B) + serializeU64(out, val.B) + return } @@ -501,11 +514,13 @@ if (val.B) { }function serializeCasts4(out, val) { if (val.A) { serializeU32(out, 0); - return serializeU32(out, val.A) + serializeU32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeS64(out, val.B) + serializeS64(out, val.B) + return } @@ -513,11 +528,13 @@ if (val.B) { }function serializeCasts5(out, val) { if (val.A) { serializeU32(out, 0); - return serializeF32(out, val.A) + serializeF32(out, val.A) + return } if (val.B) { serializeU32(out, 1); - return serializeS64(out, val.B) + serializeS64(out, val.B) + return } @@ -525,11 +542,13 @@ if (val.B) { }function serializeCasts6(out, val) { if (val.A) { serializeU32(out, 0); - return {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])} + {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])} + return } if (val.B) { serializeU32(out, 1); - return {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])} + {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])} + return } diff --git a/crates/gen-host/src/lib.rs b/crates/gen-host/src/lib.rs index ee9a4412..85cd0b9c 100644 --- a/crates/gen-host/src/lib.rs +++ b/crates/gen-host/src/lib.rs @@ -5,14 +5,13 @@ clippy::unused_self )] -use std::collections::HashSet; -use std::path::PathBuf; - use heck::ToKebabCase; use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; use quote::format_ident; use quote::quote; +use std::collections::HashSet; +use std::path::PathBuf; use tauri_bindgen_core::{Generate, GeneratorBuilder, TypeInfo, TypeInfos}; use tauri_bindgen_gen_rust::{print_generics, BorrowMode, FnSig, RustGenerator}; use wit_parser::{Function, FunctionResult, Interface, Type, TypeDefKind}; @@ -35,7 +34,22 @@ pub struct Builder { impl GeneratorBuilder for Builder { fn build(self, interface: Interface) -> Box { - let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions); + let methods = interface + .typedefs + .iter() + .filter_map(|(_, typedef)| { + if let TypeDefKind::Resource(methods) = &typedef.kind { + Some(methods.iter()) + } else { + None + } + }) + .flatten(); + + let infos = TypeInfos::collect_from_functions( + &interface.typedefs, + interface.functions.iter().chain(methods), + ); Box::new(Host { opts: self, @@ -86,6 +100,7 @@ impl RustGenerator for Host { fn print_resource( &self, + _mod_ident: &str, docs: &str, ident: &proc_macro2::Ident, functions: &[Function], @@ -293,6 +308,7 @@ impl Host { &self, mod_ident: &str, functions: impl Iterator, + methods: impl Iterator, ) -> TokenStream { let trait_ident = format_ident!("{}", mod_ident.to_upper_camel_case()); @@ -343,6 +359,61 @@ impl Host { } }); + let methods = methods.map(|(resource_name, method)| { + let func_name = method.ident.to_snake_case(); + let func_ident = format_ident!("{}", func_name); + + let params = self.print_function_params(&method.params, &BorrowMode::Owned); + + let param_idents = method + .params + .iter() + .map(|(ident, _)| format_ident!("{}", ident)); + + let result = match method.result.as_ref() { + Some(FunctionResult::Anon(ty)) => { + let ty = self.print_ty(ty, &BorrowMode::Owned); + + quote! { #ty } + } + Some(FunctionResult::Named(types)) if types.len() == 1 => { + let (_, ty) = &types[0]; + let ty = self.print_ty(ty, &BorrowMode::Owned); + + quote! { #ty } + } + Some(FunctionResult::Named(types)) => { + let types = types + .iter() + .map(|(_, ty)| self.print_ty(ty, &BorrowMode::Owned)); + + quote! { (#(#types),*) } + } + _ => quote! { () }, + }; + + let mod_name = format!("{mod_name}::resource::{resource_name}"); + let get_r_ident = format_ident!("get_{}", resource_name.to_snake_case()); + + quote! { + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router.func_wrap( + #mod_name, + #func_name, + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + #params + | -> ::tauri_bindgen_host::anyhow::Result<#result> { + let ctx = get_cx(ctx.data()); + let r = ctx.#get_r_ident(this_rid)?; + + Ok(r.#func_ident(#(#param_idents),*)) + }, + )?; + } + }); + quote! { pub fn add_to_router( router: &mut ::tauri_bindgen_host::ipc_router_wip::Router, @@ -354,6 +425,7 @@ impl Host { let wrapped_get_cx = ::std::sync::Arc::new(get_cx); #( #functions )* + #( #methods )* Ok(()) } @@ -365,22 +437,36 @@ impl Generate for Host { fn to_tokens(&mut self) -> TokenStream { let docs = self.print_docs(&self.interface.docs); - let ident = format_ident!("{}", self.interface.ident.to_snake_case()); + let iface_name = self.interface.ident.to_snake_case(); + let ident = format_ident!("{}", iface_name); let typedefs = self.print_typedefs( self.interface.typedefs.iter().map(|(id, _)| id), &BorrowMode::Owned, ); + let methods = self + .interface() + .typedefs + .iter() + .filter_map(|(_, typedef)| { + if let TypeDefKind::Resource(methods) = &typedef.kind { + Some(std::iter::repeat(typedef.ident.as_str()).zip(methods.iter())) + } else { + None + } + }) + .flatten(); + let resources = self.interface.typedefs.iter().filter_map(|(_, typedef)| { if let TypeDefKind::Resource(_) = &typedef.kind { let ident = format_ident!("{}", typedef.ident.to_upper_camel_case()); - let func_ident = format_ident!("get_{}_mut", typedef.ident.to_snake_case()); + let func_ident = format_ident!("get_{}", typedef.ident.to_snake_case()); Some(quote! { - type #ident: #ident; + type #ident: #ident + Send + Sync; - fn #func_ident(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::#ident; + fn #func_ident(&self, id: ::tauri_bindgen_host::ResourceId) -> ::tauri_bindgen_host::Result<::std::sync::Arc>; }) } else { None @@ -394,8 +480,11 @@ impl Generate for Host { true, ); - let add_to_router = - self.print_add_to_router(&self.interface.ident, self.interface.functions.iter()); + let add_to_router = self.print_add_to_router( + &self.interface.ident, + self.interface.functions.iter(), + methods, + ); quote! { #docs diff --git a/crates/gen-host/tests/resources.rs b/crates/gen-host/tests/resources.rs index 394be6d2..00d5c729 100644 --- a/crates/gen-host/tests/resources.rs +++ b/crates/gen-host/tests/resources.rs @@ -18,10 +18,16 @@ pub mod resources { ) -> Result<::tauri_bindgen_host::ResourceId, ()>; } pub trait Resources: Sized { - type A: A; - fn get_a_mut(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::A; - type B: B; - fn get_b_mut(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::B; + type A: A + Send + Sync; + fn get_a( + &self, + id: ::tauri_bindgen_host::ResourceId, + ) -> ::tauri_bindgen_host::Result<::std::sync::Arc>; + type B: B + Send + Sync; + fn get_b( + &self, + id: ::tauri_bindgen_host::ResourceId, + ) -> ::tauri_bindgen_host::Result<::std::sync::Arc>; fn constructor_a(&self) -> ::tauri_bindgen_host::ResourceId; fn constructor_b(&self) -> ::tauri_bindgen_host::ResourceId; } @@ -61,6 +67,99 @@ pub mod resources { Ok(ctx.constructor_b()) }, )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::a", + "f1", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + | -> ::tauri_bindgen_host::anyhow::Result<()> { + let ctx = get_cx(ctx.data()); + let r = ctx.get_a(this_rid)?; + Ok(r.f1()) + }, + )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::a", + "f2", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + a: u32, + | -> ::tauri_bindgen_host::anyhow::Result<()> { + let ctx = get_cx(ctx.data()); + let r = ctx.get_a(this_rid)?; + Ok(r.f2(a)) + }, + )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::a", + "f3", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + a: u32, + b: u32, + | -> ::tauri_bindgen_host::anyhow::Result<()> { + let ctx = get_cx(ctx.data()); + let r = ctx.get_a(this_rid)?; + Ok(r.f3(a, b)) + }, + )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::b", + "f1", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + | -> ::tauri_bindgen_host::anyhow::Result< + ::tauri_bindgen_host::ResourceId, + > { + let ctx = get_cx(ctx.data()); + let r = ctx.get_b(this_rid)?; + Ok(r.f1()) + }, + )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::b", + "f2", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + x: ::tauri_bindgen_host::ResourceId, + | -> ::tauri_bindgen_host::anyhow::Result> { + let ctx = get_cx(ctx.data()); + let r = ctx.get_b(this_rid)?; + Ok(r.f2(x)) + }, + )?; + let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx); + router + .func_wrap( + "resources::resource::b", + "f3", + move | + ctx: ::tauri_bindgen_host::ipc_router_wip::Caller, + this_rid: ::tauri_bindgen_host::ResourceId, + x: Option>, + | -> ::tauri_bindgen_host::anyhow::Result< + Result<::tauri_bindgen_host::ResourceId, ()>, + > { + let ctx = get_cx(ctx.data()); + let r = ctx.get_b(this_rid)?; + Ok(r.f3(x)) + }, + )?; Ok(()) } } diff --git a/crates/gen-js/src/lib.rs b/crates/gen-js/src/lib.rs index e5a230be..a9a9ff28 100644 --- a/crates/gen-js/src/lib.rs +++ b/crates/gen-js/src/lib.rs @@ -387,7 +387,8 @@ pub trait JavaScriptGenerator { format!( "if ({prop_access}) {{ serializeU32(out, {tag}); - return {inner} + {inner} + return }} " ) @@ -740,7 +741,7 @@ impl SerdeUtils { info |= Self::collect_type_info(typedefs, &case.ty); } } - TypeDefKind::Enum(_) => { + TypeDefKind::Enum(_) | TypeDefKind::Resource(_) => { info |= SerdeUtils::U32; } TypeDefKind::Flags(fields) => { @@ -752,7 +753,6 @@ impl SerdeUtils { wit_parser::Int::U128 => SerdeUtils::U128, }; } - TypeDefKind::Resource(_) => {} } log::debug!("collected info for {:?}: {:?}", typedefs[id].ident, info,); diff --git a/crates/gen-rust/src/lib.rs b/crates/gen-rust/src/lib.rs index de955391..cf804d06 100644 --- a/crates/gen-rust/src/lib.rs +++ b/crates/gen-rust/src/lib.rs @@ -19,6 +19,7 @@ pub trait RustGenerator { fn default_param_mode(&self) -> BorrowMode; fn print_resource( &self, + print_resource: &str, docs: &str, ident: &Ident, functions: &[Function], @@ -63,7 +64,7 @@ pub trait RustGenerator { self.print_union(docs, &ident, cases, info, &borrow_mode) } TypeDefKind::Resource(functions) => { - self.print_resource(docs, &ident, functions, info) + self.print_resource(&self.interface().ident, docs, &ident, functions, info) } }; diff --git a/crates/host/src/lib.rs b/crates/host/src/lib.rs index d80f7a5a..c036efee 100644 --- a/crates/host/src/lib.rs +++ b/crates/host/src/lib.rs @@ -1,34 +1,139 @@ -pub use tauri_bindgen_host_macro::*; +use std::sync::Arc; +use std::sync::RwLock; +use std::{any::Any, collections::HashMap}; -pub use generational_arena::Arena as ResourceTable; +pub use tauri_bindgen_host_macro::*; #[doc(hidden)] pub use {anyhow, async_trait::async_trait, bitflags, ipc_router_wip, serde, tauri, tracing}; +pub type Result = anyhow::Result; -pub type ResourceId = u64; +pub type ResourceId = u32; -pub type Result = anyhow::Result; +#[derive(Default)] +pub struct ResourceTable(RwLock); + +#[derive(Default)] +pub struct ResourceTableInner { + map: HashMap>, + next_rid: ResourceId, +} + +impl ResourceTable { + /// Create an empty table. + #[must_use] + pub fn new() -> Self { + Self(RwLock::new(ResourceTableInner { + map: HashMap::new(), + next_rid: 0, + })) + } + + /// Insert a resource at the next available index. + /// + /// # Errors + /// + /// Returns an error if the table is full. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn push(&self, a: Arc) -> Result { + let mut inner = self.0.write().unwrap(); + // NOTE: The performance of this new key calculation could be very bad once keys wrap + // around. + if inner.map.len() == u32::MAX as usize { + return Err(anyhow::anyhow!("table has no free keys")); + } + loop { + let key = inner.next_rid; + inner.next_rid += 1; + if inner.map.contains_key(&key) { + continue; + } + inner.map.insert(key, a); + return Ok(key); + } + } + + /// Check if the table has a resource at the given index. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn contains_key(&self, key: ResourceId) -> bool { + self.0.read().unwrap().map.contains_key(&key) + } + + /// Check if the resource at a given index can be downcast to a given type. + /// Note: this will always fail if the resource is already borrowed. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn is(&self, key: ResourceId) -> bool { + if let Some(r) = self.0.read().unwrap().map.get(&key) { + r.is::() + } else { + false + } + } + + /// Get an Arc reference to a resource of a given type at a given index. Multiple + /// immutable references can be borrowed at any given time. + /// + /// # Errors + /// + /// Returns an error if the resource is not of the given type. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn get(&self, key: ResourceId) -> Result> { + if let Some(r) = self.0.read().unwrap().map.get(&key).cloned() { + r.downcast::() + .map_err(|_| anyhow::anyhow!("element is a different type")) + } else { + Err(anyhow::anyhow!("key not in table")) + } + } + + /// Get a mutable reference to a resource of a given type at a given index. + /// + /// # Errors + /// + /// Returns an error if the resource is not of the given type or if the resource is already borrowed. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn get_mut(&mut self, key: ResourceId) -> Result<&mut T> { + let entry = self + .0 + .get_mut() + .unwrap() + .map + .get_mut(&key) + .ok_or(anyhow::anyhow!("key not in table"))?; + + let entry = + Arc::get_mut(entry).ok_or(anyhow::anyhow!("cannot mutably borrow shared file"))?; + + entry + .downcast_mut::() + .ok_or_else(|| anyhow::anyhow!("element is a different type")) + } -// #[derive(Debug)] -// pub struct ResourceId { -// id: generational_arena::Index, -// _m: PhantomData, -// } - -// impl Clone for ResourceId { -// fn clone(&self) -> Self { -// Self { -// id: self.id.clone(), -// _m: PhantomData, -// } -// } -// } - -// impl Copy for ResourceId {} - -// impl PartialEq for ResourceId { -// fn eq(&self, other: &Self) -> bool { -// self.id == other.id -// } -// } - -// impl Eq for ResourceId {} + /// Remove a resource at a given index from the table and returns it. + /// + /// # Panics + /// + /// Panics if the resource is already borrowed. + pub fn take(&self, key: ResourceId) -> Option> { + self.0 + .write() + .unwrap() + .map + .remove(&key) + .map(|r| r.downcast::().unwrap()) + } +} diff --git a/playground/netlify.toml b/playground/netlify.toml index b06c05b6..f6115c06 100644 --- a/playground/netlify.toml +++ b/playground/netlify.toml @@ -2,10 +2,10 @@ NPM_FLAGS = "--version" # prevent Netlify npm install [build] publish = 'dist' - command = """ + command = """ cd editor npx pnpm install --store=node_modules/.pnpm-store cd .. wget -qO- https://github.com/thedodd/trunk/releases/download/v0.16.0/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- ./trunk build - """ \ No newline at end of file + """