From 5095dc6849f64b1b35fa849c3254959e7a7e0cd5 Mon Sep 17 00:00:00 2001 From: chavic Date: Fri, 31 May 2024 14:19:15 +0100 Subject: [PATCH 1/8] Minimal mergable futures example --- fixtures/futures/build.rs | 2 +- fixtures/futures/src/lib.rs | 172 +++++++++--------- fixtures/futures/test/futures_test.dart | 162 ++++++++--------- fixtures/futures/tests/mod.rs | 8 +- src/gen.rs | 68 +++++-- src/gen/functions.rs | 82 +++++---- src/gen/objects.rs | 31 +--- src/gen/oracle.rs | 53 +++++- src/gen/render/mod.rs | 2 + src/gen/types.rs | 224 ++++++++++++------------ 10 files changed, 453 insertions(+), 351 deletions(-) diff --git a/fixtures/futures/build.rs b/fixtures/futures/build.rs index da293da..17e3949 100644 --- a/fixtures/futures/build.rs +++ b/fixtures/futures/build.rs @@ -1,3 +1,3 @@ fn main() { - // uniffi_dart::generate_scaffolding("./src/api.udl".into()).unwrap(); + uniffi_dart::generate_scaffolding("./src/api.udl".into()).unwrap(); } diff --git a/fixtures/futures/src/lib.rs b/fixtures/futures/src/lib.rs index 3a8bc64..f8c5396 100644 --- a/fixtures/futures/src/lib.rs +++ b/fixtures/futures/src/lib.rs @@ -1,63 +1,64 @@ -// use uniffi; - -// use std::{ -// future::Future, -// pin::Pin, -// sync::{Arc, Mutex, MutexGuard}, -// task::{Context, Poll, Waker}, -// thread, -// time::Duration, -// }; - -// /// Non-blocking timer future. -// pub struct TimerFuture { -// shared_state: Arc>, -// } - -// struct SharedState { -// completed: bool, -// waker: Option, -// } - -// impl Future for TimerFuture { -// type Output = (); - -// fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { -// let mut shared_state = self.shared_state.lock().unwrap(); - -// if shared_state.completed { -// Poll::Ready(()) -// } else { -// shared_state.waker = Some(cx.waker().clone()); -// Poll::Pending -// } -// } -// } - -// impl TimerFuture { -// pub fn new(duration: Duration) -> Self { -// let shared_state = Arc::new(Mutex::new(SharedState { -// completed: false, -// waker: None, -// })); - -// let thread_shared_state = shared_state.clone(); - -// // Let's mimic an event coming from somewhere else, like the system. -// thread::spawn(move || { -// thread::sleep(duration); - -// let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); -// shared_state.completed = true; - -// if let Some(waker) = shared_state.waker.take() { -// waker.wake(); -// } -// }); - -// Self { shared_state } -// } -// } +use uniffi; + +use std::{ + future::Future, + pin::Pin, + sync::{Arc, Mutex, MutexGuard}, + task::{Context, Poll, Waker}, + thread, + time::Duration, +}; + + +/// Non-blocking timer future. +pub struct TimerFuture { + shared_state: Arc>, +} + +struct SharedState { + completed: bool, + waker: Option, +} + +impl Future for TimerFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut shared_state = self.shared_state.lock().unwrap(); + + if shared_state.completed { + Poll::Ready(()) + } else { + shared_state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} + +impl TimerFuture { + pub fn new(duration: Duration) -> Self { + let shared_state = Arc::new(Mutex::new(SharedState { + completed: false, + waker: None, + })); + + let thread_shared_state = shared_state.clone(); + + // Let's mimic an event coming from somewhere else, like the system. + thread::spawn(move || { + thread::sleep(duration); + + let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); + shared_state.completed = true; + + if let Some(waker) = shared_state.waker.take() { + waker.wake(); + } + }); + + Self { shared_state } + } +} // /// Non-blocking timer future. // pub struct BrokenTimerFuture { @@ -117,18 +118,18 @@ // } // } -// #[uniffi::export] -// pub fn greet(who: String) -> String { -// format!("Hello, {who}") -// } +#[uniffi::export] +pub fn greet(who: String) -> String { + format!("Hello, {who}") +} -// #[uniffi::export] -// pub async fn always_ready() -> bool { -// true -// } +#[uniffi::export] +pub async fn always_ready() -> bool { + true +} -// #[uniffi::export] -// pub async fn void_function() {} +#[uniffi::export] +pub async fn void_function() {} // #[uniffi::export] // pub async fn say() -> String { @@ -144,20 +145,22 @@ // format!("Hello, {who}!") // } -// #[uniffi::export] -// pub async fn sleep(ms: u16) -> bool { -// TimerFuture::new(Duration::from_millis(ms.into())).await; +#[uniffi::export] +pub async fn sleep(ms: u16) -> bool { + TimerFuture::new(Duration::from_millis(ms.into())).await; -// true -// } + true +} -// // Our error. +// Our error. +// Our error. // #[derive(uniffi::Error, Debug)] // pub enum MyError { // Foo, // } -// // An async function that can throw. +// An async function that can throw. +// An async function that can throw. // #[uniffi::export] // pub async fn fallible_me(do_fail: bool) -> Result { // if do_fail { @@ -167,12 +170,11 @@ // } // } -// #[uniffi::export(async_runtime = "tokio")] -// pub async fn say_after_with_tokio(ms: u16, who: String) -> String { -// tokio::time::sleep(Duration::from_millis(ms.into())).await; - -// format!("Hello, {who} (with Tokio)!") -// } +#[uniffi::export(async_runtime = "tokio")] +pub async fn say_after_with_tokio(ms: u16, who: String) -> String { + tokio::time::sleep(Duration::from_millis(ms.into())).await; + format!("Hello, {who} (with Tokio)!") +} // #[derive(uniffi::Record)] // pub struct MyRecord { @@ -193,5 +195,5 @@ // ) // .await; // } - -// uniffi::include_scaffolding!("api"); + +uniffi::include_scaffolding!("api"); diff --git a/fixtures/futures/test/futures_test.dart b/fixtures/futures/test/futures_test.dart index d815e86..ecae87c 100644 --- a/fixtures/futures/test/futures_test.dart +++ b/fixtures/futures/test/futures_test.dart @@ -10,104 +10,110 @@ Future measureTime(Future Function() action) async { void main() { final api = Api.load(); - + // initialize(); + // ensureInitialized(); + + test('greet', () async { + final result = await api.greet("Somebody"); + expect(result, "Hello, Somebody"); + }); + test('always_ready', () async { final time = await measureTime(() async { final result = await api.alwaysReady(); expect(result, true); }); - // Less than or equal to time - expect(time.compareTo(Duration(milliseconds: 4)) <= 0, true); - }); - test('void', () async { - final time = await measureTime(() async { - final result = await api.voidFunction(); - expect(result, null); - }); - // Less than or equal to time - expect(time.compareTo(Duration(milliseconds: 4)) <= 0, true); + expect(time.inMilliseconds < 200, true); }); + // test('void', () async { + // final time = await measureTime(() async { + // await voidFunction(); + // //expect(result, null); + // }); + // // Less than or equal to time + // expect(time.compareTo(Duration(milliseconds: 4)) <= 0, true); + // }); + test('sleep', () async { final time = await measureTime(() async { await api.sleep(200); }); - // within range + expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); }); - test('sequential_future', () async { - final time = await measureTime(() async { - final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); - final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); - expect(resultAlice, 'Hello, Alice!'); - expect(resultBob, 'Hello, Bob!'); - }); - expect(time.inMilliseconds > 300 && time.inMilliseconds < 400, true); - }); + // test('sequential_future', () async { + // final time = await measureTime(() async { + // final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); + // final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); + // expect(resultAlice, 'Hello, Alice!'); + // expect(resultBob, 'Hello, Bob!'); + // }); + // expect(time.inMilliseconds > 300 && time.inMilliseconds < 400, true); + // }); - test('concurrent_future', () async { - final time = await measureTime(() async { - final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); - final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); - expect(resultAlice, 'Hello, Alice!'); - expect(resultBob, 'Hello, Bob!'); - }); - expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); - }); + // test('concurrent_future', () async { + // final time = await measureTime(() async { + // final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); + // final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); + // expect(resultAlice, 'Hello, Alice!'); + // expect(resultBob, 'Hello, Bob!'); + // }); + // expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); + // }); + // test('with_tokio_runtime', () async { + // final time = await measureTime(() async { + // final resultAlice = await api.sayAfterWithTokio(Duration(milliseconds: 200), 'Alice'); + // expect(resultAlice, 'Hello, Alice (with Tokio)!'); + // }); + // expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); + // }); - test('with_tokio_runtime', () async { - final time = await measureTime(() async { - final resultAlice = await api.sayAfterWithTokio(Duration(milliseconds: 200), 'Alice'); - expect(resultAlice, 'Hello, Alice (with Tokio)!'); - }); - expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); - }); + // test('fallible_function_and_method', () async { + // final time1 = await measureTime(() { + // try { + // api.fallibleMe(false); + // expect(true, true); + // } catch (exception) { + // expect(false, true); // should never be reached + // } + // }); + // print('fallible function (with result): ${time1.inMilliseconds}ms'); + // expect(time1.compareTo(Duration(milliseconds: 100)), -1); - test('fallible_function_and_method', () async { - final time1 = await measureTime(() { - try { - api.fallibleMe(false); - expect(true, true); - } catch (exception) { - expect(false, true); // should never be reached - } - }); - print('fallible function (with result): ${time1.inMilliseconds}ms'); - expect(time1.compareTo(Duration(milliseconds: 100)), -1); - - final time2 = await measureTime(() { - try { - api.fallibleMe(true); - expect(false, true); // should never be reached - } catch (exception) { - expect(true, true); - } - }); - print('fallible function (with exception): ${time2.inMilliseconds}ms'); - expect(time2.compareTo(Duration(milliseconds: 100)), -1); - }); + // final time2 = await measureTime(() { + // try { + // api.fallibleMe(true); + // expect(false, true); // should never be reached + // } catch (exception) { + // expect(true, true); + // } + // }); + // print('fallible function (with exception): ${time2.inMilliseconds}ms'); + // expect(time2.compareTo(Duration(milliseconds: 100)), -1); + // }); - test('record', () async { - final time = await measureTime(() { - final result = api.newMyRecord('foo', 42); - expect(result.a, 'foo'); - expect(result.b, 42); - }); - print('record: ${time.inMilliseconds}ms'); - expect(time.compareTo(Duration(milliseconds: 100)), -1); - }); + // test('record', () async { + // final time = await measureTime(() { + // final result = api.newMyRecord('foo', 42); + // expect(result.a, 'foo'); + // expect(result.b, 42); + // }); + // print('record: ${time.inMilliseconds}ms'); + // expect(time.compareTo(Duration(milliseconds: 100)), -1); + // }); - test('broken_sleep', () async { - final time = await measureTime(() async { - await api.brokenSleep(100, 0); // calls the waker twice immediately - await api.sleep(100); // wait for possible failure + // test('broken_sleep', () async { + // final time = await measureTime(() async { + // await api.brokenSleep(100, 0); // calls the waker twice immediately + // await api.sleep(100); // wait for possible failure - await api.brokenSleep(100, 100); // calls the waker a second time after 1s - await api.sleep(200); // wait for possible failure - }); - expect(time.inMilliseconds < 400 && time.inMilliseconds > 600, true); - }); + // await api.brokenSleep(100, 100); // calls the waker a second time after 1s + // await api.sleep(200); // wait for possible failure + // }); + // expect(time.inMilliseconds < 400 && time.inMilliseconds > 600, true); + // }); } diff --git a/fixtures/futures/tests/mod.rs b/fixtures/futures/tests/mod.rs index 776cb8d..5cbb2ed 100644 --- a/fixtures/futures/tests/mod.rs +++ b/fixtures/futures/tests/mod.rs @@ -1,6 +1,6 @@ use anyhow::Result; -// #[test] -// fn futures() -> Result<()> { -// uniffi_dart::testing::run_test("futures", "src/api.udl", None) -// } +#[test] +fn futures() -> Result<()> { + uniffi_dart::testing::run_test("futures", "src/api.udl", None) +} diff --git a/src/gen.rs b/src/gen.rs index 41ea377..3da26f3 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -7,6 +7,7 @@ use genco::fmt; use genco::prelude::*; use serde::{Deserialize, Serialize}; // use uniffi_bindgen::MergeWith; +use crate::gen::oracle::DartCodeOracle; use uniffi_bindgen::{BindingGenerator, BindingsConfig, ComponentInterface}; use self::render::Renderer; @@ -99,7 +100,7 @@ impl BindingsConfig for Config { pub struct DartWrapper<'a> { config: &'a Config, - // ci: &'a ComponentInterface, + ci: &'a ComponentInterface, type_renderer: TypeHelpersRenderer<'a>, } @@ -107,17 +108,67 @@ impl<'a> DartWrapper<'a> { pub fn new(ci: &'a ComponentInterface, config: &'a Config) -> Self { let type_renderer = TypeHelpersRenderer::new(config, ci); DartWrapper { - // ci, + ci, config, type_renderer, } } + fn uniffi_function_definitions(&self) -> dart::Tokens { + let ci = self.ci; + let mut definitions = quote!(); + + for fun in ci.iter_ffi_function_definitions() { + let fun_name = fun.name(); + let (native_return_type, dart_return_type) = match fun.return_type() { + Some(return_type) => ( + quote! { $(DartCodeOracle::ffi_native_type_label(Some(&return_type))) }, + quote! { $(DartCodeOracle::ffi_type_label(Some(&return_type))) }, + ), + None => (quote! { Void }, quote! { void }), + }; + + let (native_args, dart_args) = { + let mut native_args = quote!(); + let mut dart_args = quote!(); + + for arg in fun.arguments() { + native_args.append( + quote!($(DartCodeOracle::ffi_native_type_label(Some(&arg.type_()))),), + ); + dart_args + .append(quote!($(DartCodeOracle::ffi_type_label(Some(&arg.type_()))),)); + } + + if fun.has_rust_call_status_arg() { + native_args.append(quote!(Pointer)); + dart_args.append(quote!(Pointer)); + } + + (native_args, dart_args) + }; + + let lookup_fn = quote! { + _dylib.lookupFunction< + $native_return_type Function($(&native_args)), + $(&dart_return_type) Function($(&dart_args)) + >($(format!("\"{fun_name}\""))) + }; + + definitions.append(quote! { + late final $dart_return_type Function($dart_args) $fun_name = $lookup_fn; + }); + } + + definitions + } + fn generate(&self) -> dart::Tokens { let package_name = self.config.package_name(); let libname = self.config.cdylib_name(); let (type_helper_code, functions_definitions) = &self.type_renderer.render(); + let uniffi_functions = self.uniffi_function_definitions(); quote! { library $package_name; @@ -125,16 +176,9 @@ impl<'a> DartWrapper<'a> { $(type_helper_code) // Imports, Types and Type Helper class Api { - final Pointer Function(String symbolName) - _lookup; - - Api(DynamicLibrary dynamicLibrary) - : _lookup = dynamicLibrary.lookup; + final DynamicLibrary _dylib; - Api.fromLookup( - Pointer Function(String symbolName) - lookup) - : _lookup = lookup; + Api(DynamicLibrary this._dylib); factory Api.loadStatic() { return Api(DynamicLibrary.executable()); @@ -161,6 +205,8 @@ impl<'a> DartWrapper<'a> { } } + $(uniffi_functions) + $(functions_definitions) } } diff --git a/src/gen/functions.rs b/src/gen/functions.rs index e72b7c4..0fd8b33 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -5,7 +5,6 @@ use crate::gen::oracle::DartCodeOracle; use crate::gen::render::AsRenderable; use super::render::TypeHelperRenderer; -use super::types::{generate_ffi_dart_type, generate_ffi_type}; use super::utils::{fn_name, var_name}; #[allow(unused_variables)] @@ -18,42 +17,59 @@ pub fn generate_function( let fn_name = fn_name(fun.name()); let args = quote!($(for arg in &fun.arguments() => $(&arg.as_renderable().render_type(&arg.as_type(), type_helper)) $(var_name(arg.name())),)); let ff_name = ffi.name(); - let inner = quote! { - rustCall(api, (res) => - _$(&fn_name)( - $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) - res) - ) - }; - let (ret, body) = if let Some(ret) = fun.return_type() { - ( - ret.as_renderable().render_type(ret, type_helper), - quote! { - return $(DartCodeOracle::type_lift_fn(ret, inner)); - }, + if fun.is_async() { + let (ret, body) = if let Some(ret) = fun.return_type() { + ( + ret.as_renderable().render_type(ret, type_helper), + quote! { + return $(DartCodeOracle::type_lift_fn(ret, quote!(res))); + }, + ) + } else { + (quote!(void), quote!(return null;)) + }; + quote!( + Future<$ret> $(DartCodeOracle::fn_name(fun.name()))($args) { + final api = $api; + return uniffiRustCallAsync( + () => $(DartCodeOracle::find_lib_instance()).$(fun.ffi_func().name())( + $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) + ), + $(DartCodeOracle::async_poll(fun, type_helper.get_ci())), + $(DartCodeOracle::async_complete(fun, type_helper.get_ci())), + $(DartCodeOracle::async_free(fun, type_helper.get_ci())), + (res) { + final api = $api; + $body + } + ); + } ) } else { - (quote!(void), quote!($inner;)) - }; + let inner = quote! { + rustCall(api, (res) => + $(DartCodeOracle::find_lib_instance()).$(fun.ffi_func().name())( + $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) + res) + ) + }; - quote! { - late final _$(&fn_name)Ptr = _lookup< - NativeFunction< - $(generate_ffi_type(ffi.return_type())) Function( - $(for arg in &ffi.arguments() => $(generate_ffi_type(Some(&arg.type_()))),) - Pointer - )>>($(format!("\"{ff_name}\""))); - - late final _$(&fn_name) = _$(&fn_name)Ptr.asFunction< - $(generate_ffi_dart_type(ffi.return_type())) Function( - $(for arg in &ffi.arguments() => $(generate_ffi_dart_type(Some(&arg.type_()))),) - Pointer - )>(); - - $ret $fn_name ($args) { - final api = $api; - $body + let (ret, body) = if let Some(ret) = fun.return_type() { + ( + ret.as_renderable().render_type(ret, type_helper), + quote! { + return $(DartCodeOracle::type_lift_fn(ret, inner)); + }, + ) + } else { + (quote!(void), quote!(return;)) + }; + quote! { + $ret $fn_name ($args) { + final api = $api; + $body + } } } } diff --git a/src/gen/objects.rs b/src/gen/objects.rs index b79d893..67e4f97 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -7,7 +7,6 @@ use crate::gen::render::AsRenderable; use crate::gen::render::{Renderable, TypeHelperRenderer}; -use super::types::{generate_ffi_dart_type, generate_ffi_type}; use super::utils::{class_name, fn_name, var_name}; #[derive(Debug)] @@ -72,21 +71,12 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da return $(cls_name)._(api, ptr); } - Pointer uniffiClonePointer() { - final _uniffiClonePointerPtr = _api._lookup< - NativeFunction< - Pointer Function(Pointer, Pointer)>>($(format!("\"{}\"", obj.ffi_object_clone().name()))); - final _uniffiClonePointer = _uniffiClonePointerPtr.asFunction Function(Pointer, Pointer)>(); - return rustCall(_api, (res) => _uniffiClonePointer(_ptr, res)); + Pointer uniffiClonePointer(Api, api) { + return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(obj.ffi_object_clone().name())(_ptr, status)); } - void drop() { - final _freePtr = _api._lookup< - NativeFunction< - Void Function(Pointer, Pointer)>>($(format!("\"{}\"", obj.ffi_object_free().name()))); - final _free = _freePtr.asFunction, Pointer)>(); - - rustCall(_api, (res) => _free(_ptr, res)); + void drop(Api api) { + rustCall(api, (status) => $(DartCodeOracle::find_lib_instance())..$(obj.ffi_object_free().name())(_ptr, status)); } $(for mt in &obj.methods() => $(generate_method(mt, type_helper))) @@ -122,19 +112,6 @@ pub fn generate_method(fun: &Method, type_helper: &dyn TypeHelperRenderer) -> da }; quote! { - late final _$(&fn_name)Ptr = _api._lookup< - NativeFunction< - $(generate_ffi_type(ffi.return_type())) Function( - $(for arg in &ffi.arguments() => $(generate_ffi_type(Some(&arg.type_()))),) - Pointer - )>>($(format!("\"{ff_name}\""))); - - late final _$(&fn_name) = _$(&fn_name)Ptr.asFunction< - $(generate_ffi_dart_type(ffi.return_type())) Function( - $(for arg in &ffi.arguments() => $(generate_ffi_dart_type(Some(&arg.type_()))),) - Pointer - )>(); - $ret $fn_name ($args) { final api = _api; $body diff --git a/src/gen/oracle.rs b/src/gen/oracle.rs index 2ad3ea7..0bc577e 100644 --- a/src/gen/oracle.rs +++ b/src/gen/oracle.rs @@ -3,7 +3,8 @@ use genco::quote; use heck::{ToLowerCamelCase, ToUpperCamelCase}; use uniffi_bindgen::backend::CodeType; -use uniffi_bindgen::interface::{AsType, FfiType, Type}; +use uniffi_bindgen::interface::{AsType, Callable, ExternalKind, FfiType, Type}; +use uniffi_bindgen::ComponentInterface; use crate::gen::primitives; @@ -62,6 +63,14 @@ impl DartCodeOracle { Self::sanitize_identifier(&nm.to_lower_camel_case()) } + /// Get the idiomatic Dart rendering of an FFI callback function name + fn ffi_callback_name(nm: &str) -> String { + format!( + "Pointer>", + nm.to_upper_camel_case() + ) + } + /// Get the idiomatic Dart rendering of an exception name // TODO: Refactor to be more idomatic to the way dart handles errors pub fn error_name(nm: &str) -> String { @@ -73,12 +82,42 @@ impl DartCodeOracle { } } + pub fn find_lib_instance() -> dart::Tokens { + quote!(api) + } + + pub fn async_poll(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { + let ffi_func = callable.ffi_rust_future_poll(ci); + quote!($(Self::find_lib_instance()).$ffi_func) + } + + pub fn async_complete(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { + let ffi_func = callable.ffi_rust_future_complete(ci); + let call = quote!($(Self::find_lib_instance()).$ffi_func); + let call = match callable.return_type() { + Some(Type::External { + kind: ExternalKind::DataClass, + name: _, + .. + }) => { + todo!("Need to convert the RustBuffer from our package to the RustBuffer of the external package") + } + _ => call, + }; + call + } + + pub fn async_free(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { + let ffi_func = callable.ffi_rust_future_free(ci); + quote!($(Self::find_lib_instance()).$ffi_func) + } + // TODO: Replace instances of `generate_ffi_dart_type` with ffi_type_label pub fn ffi_type_label(ffi_type: Option<&FfiType>) -> dart::Tokens { let Some(ret_type) = ffi_type else { return quote!(void); }; - match *ret_type { + match ret_type { FfiType::UInt8 | FfiType::UInt16 | FfiType::UInt32 @@ -86,13 +125,16 @@ impl DartCodeOracle { | FfiType::Int8 | FfiType::Int16 | FfiType::Int32 - | FfiType::Int64 => quote!(int), + | FfiType::Int64 + | FfiType::Handle => quote!(int), FfiType::Float32 | FfiType::Float64 => quote!(double), FfiType::RustBuffer(ref inner) => match inner { Some(i) => quote!($i), _ => quote!(RustBuffer), }, FfiType::RustArcPtr(_) => quote!(Pointer), + FfiType::ForeignBytes => quote!(ForeignBytes), + FfiType::Callback(name) => quote!($(Self::ffi_callback_name(name))), _ => todo!("FfiType::{:?}", ret_type), } } @@ -102,7 +144,7 @@ impl DartCodeOracle { let Some(ret_type) = ffi_type else { return quote!(Void); }; - match *ret_type { + match ret_type { FfiType::UInt8 => quote!(Uint8), FfiType::UInt16 => quote!(Uint16), FfiType::UInt32 => quote!(Uint32), @@ -113,11 +155,14 @@ impl DartCodeOracle { FfiType::Int64 => quote!(Int64), FfiType::Float32 => quote!(Float), FfiType::Float64 => quote!(Double), + FfiType::Handle => quote!(Uint64), FfiType::RustBuffer(ref inner) => match inner { Some(i) => quote!($i), _ => quote!(RustBuffer), }, + FfiType::ForeignBytes => quote!(ForeignBytes), FfiType::RustArcPtr(_) => quote!(Pointer), + FfiType::Callback(name) => quote!($(Self::ffi_callback_name(name))), _ => todo!("FfiType::{:?}", ret_type), } } diff --git a/src/gen/render/mod.rs b/src/gen/render/mod.rs index b643c2e..9372a61 100644 --- a/src/gen/render/mod.rs +++ b/src/gen/render/mod.rs @@ -2,6 +2,7 @@ use super::{compounds, enums, primitives, records}; use super::{objects, oracle::AsCodeType}; use genco::{lang::dart, quote}; use uniffi_bindgen::interface::{AsType, Enum, Object, Record, Type}; +use uniffi_bindgen::ComponentInterface; /// This trait will be used by any type that generates dart code according to some logic, pub trait Renderer { @@ -21,6 +22,7 @@ pub trait TypeHelperRenderer { fn get_object(&self, name: &str) -> Option<&Object>; fn get_enum(&self, name: &str) -> Option<&Enum>; fn get_record(&self, name: &str) -> Option<&Record>; + fn get_ci(&self) -> &ComponentInterface; } /// This trait is used by types that should be generated. The idea is to pass any struct that implements /// this type to another struct that generates much larger portions of according to some internal logic code diff --git a/src/gen/types.rs b/src/gen/types.rs index 9605b5e..e03df58 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -4,16 +4,14 @@ use std::{ }; use genco::prelude::*; -use uniffi_bindgen::{ - interface::{FfiType, Type}, - ComponentInterface, -}; +use uniffi_bindgen::{interface::Type, ComponentInterface}; use super::{enums, functions, objects, primitives, records}; use super::{ render::{AsRenderable, Renderer, TypeHelperRenderer}, Config, }; +use crate::gen::DartCodeOracle; type FunctionDefinition = dart::Tokens; @@ -97,10 +95,14 @@ impl TypeHelperRenderer for TypeHelpersRenderer<'_> { fn get_record(&self, name: &str) -> Option<&uniffi_bindgen::interface::Record> { self.ci.get_record_definition(name) } + + fn get_ci(&self) -> &ComponentInterface { + self.ci + } } impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { - // TODO: Implimient a two pass system where the first pass will render the main code, and the second pass will render the helper code + // TODO: Implement a two pass system where the first pass will render the main code, and the second pass will render the helper code // this is so the generator knows what helper code to include. fn render(&self) -> (dart::Tokens, dart::Tokens) { @@ -115,7 +117,9 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { // Render all the imports let imports: dart::Tokens = quote!(); - let function_definitions = quote!($( for fun in self.ci.function_definitions() => $(functions::generate_function("this", fun, self)))); + let function_definitions = quote!($( for fun in self.ci.function_definitions() => $( + functions::generate_function("this", fun, self) + ))); let helpers_definitions = quote! { $(for (_, ty) in self.get_include_names().iter() => $(ty.as_renderable().render_type_helper(self)) ) @@ -148,33 +152,37 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { const UniffiInternalError(this.errorCode, this.panicMessage); static UniffiInternalError panicked(String message) { - return UniffiInternalError(rustPanic, message); + return UniffiInternalError(rustPanic, message); + } + + static UniffiInternalError unexpectedCall() { + return UniffiInternalError(unexpectedRustCallError, null); } @override String toString() { - switch (errorCode) { - case bufferOverflow: - return "UniFfi::BufferOverflow"; - case incompleteData: - return "UniFfi::IncompleteData"; - case unexpectedOptionalTag: - return "UniFfi::UnexpectedOptionalTag"; - case unexpectedEnumCase: - return "UniFfi::UnexpectedEnumCase"; - case unexpectedNullPointer: - return "UniFfi::UnexpectedNullPointer"; - case unexpectedRustCallStatusCode: - return "UniFfi::UnexpectedRustCallStatusCode"; - case unexpectedRustCallError: - return "UniFfi::UnexpectedRustCallError"; - case unexpectedStaleHandle: - return "UniFfi::UnexpectedStaleHandle"; - case rustPanic: - return "UniFfi::rustPanic: $$panicMessage"; - default: - return "UniFfi::UnknownError: $$errorCode"; - } + switch (errorCode) { + case bufferOverflow: + return "UniFfi::BufferOverflow"; + case incompleteData: + return "UniFfi::IncompleteData"; + case unexpectedOptionalTag: + return "UniFfi::UnexpectedOptionalTag"; + case unexpectedEnumCase: + return "UniFfi::UnexpectedEnumCase"; + case unexpectedNullPointer: + return "UniFfi::UnexpectedNullPointer"; + case unexpectedRustCallStatusCode: + return "UniFfi::UnexpectedRustCallStatusCode"; + case unexpectedRustCallError: + return "UniFfi::UnexpectedRustCallError"; + case unexpectedStaleHandle: + return "UniFfi::UnexpectedStaleHandle"; + case rustPanic: + return "UniFfi::rustPanic: $$panicMessage"; + default: + return "UniFfi::UnknownError: $$errorCode"; + } } } @@ -209,25 +217,30 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { T rustCall(Api api, T Function(Pointer) callback) { var callStatus = RustCallStatus.allocate(); final returnValue = callback(callStatus); + final callStatusCheck = checkCallStatus(callStatus); + return returnValue; + } + void checkCallStatus(Pointer callStatus) { switch (callStatus.ref.code) { - case CALL_SUCCESS: - calloc.free(callStatus); - return returnValue; - case CALL_ERROR: - throw callStatus.ref.errorBuf; - case CALL_PANIC: - if (callStatus.ref.errorBuf.len > 0) { - final message = utf8.decoder.convert(callStatus.ref.errorBuf.asUint8List()); + case CALL_SUCCESS: calloc.free(callStatus); - throw UniffiInternalError.panicked(message); - } else { + break; + case CALL_ERROR: calloc.free(callStatus); - throw UniffiInternalError.panicked("Rust panic"); + throw UniffiInternalError.unexpectedCall(); + case CALL_PANIC: + if (callStatus.ref.errorBuf.len > 0) { + final message = utf8.decoder.convert(callStatus.ref.errorBuf.asUint8List()); + calloc.free(callStatus); + throw UniffiInternalError.panicked(message); + } else { + calloc.free(callStatus); + throw UniffiInternalError.panicked("Rust panic"); + } + default: + throw UniffiInternalError(callStatus.ref.code, null); } - default: - throw UniffiInternalError(callStatus.ref.code, null); - } } class RustBuffer extends Struct { @@ -240,29 +253,16 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { external Pointer data; static RustBuffer fromBytes(Api api, ForeignBytes bytes) { - final _fromBytesPtr = api._lookup< - NativeFunction< - RustBuffer Function(ForeignBytes, Pointer)>>($(format!("\"{}\"", self.ci.ffi_rustbuffer_from_bytes().name()))); - final _fromBytes = - _fromBytesPtr.asFunction)>(); - return rustCall(api, (res) => _fromBytes(bytes, res)); + return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_from_bytes().name())(bytes, status)); } - // Needed so that the foreign language bindings can create buffers in which to pass complex data types across the FFI in the future + // Needed so that the foreign language bindingsfunction_definitions can create buffers in which to pass complex data types across the FFI in the future static RustBuffer allocate(Api api, int size) { - final _allocatePtr = api._lookup< - NativeFunction< - RustBuffer Function(Int64, Pointer)>>($(format!("\"{}\"", self.ci.ffi_rustbuffer_alloc().name()))); - final _allocate = _allocatePtr.asFunction)>(); - return rustCall(api, (res) => _allocate(size, res)); + return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_alloc().name())(size, status)); } - void deallocate(Api api) { - final _freePtr = api._lookup< - NativeFunction< - Void Function(RustBuffer, Pointer)>>($(format!("\"{}\"", self.ci.ffi_rustbuffer_free().name()))); - final _free = _freePtr.asFunction)>(); - rustCall(api, (res) => _free(this, res)); + void free(Api api, ) { + rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_free().name())(this, status)); } Uint8List asUint8List() { @@ -303,59 +303,67 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { $(types_definitions) + const int UNIFFI_RUST_FUTURE_POLL_READY = 0; + const int UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1; + + typedef UniffiRustFutureContinuationCallback = Void Function(Uint64, Int8); + + Future uniffiRustCallAsync( + int Function() rustFutureFunc, + void Function(int, Pointer>, int) pollFunc, + F Function(int, Pointer) completeFunc, + void Function(int) freeFunc, + T Function(F) liftFunc, + ) async { + final rustFuture = rustFutureFunc(); + final completer = Completer(); + + late final NativeCallable callback; + + void poll() { + pollFunc( + rustFuture, + callback.nativeFunction, + 0, + ); + } + void onResponse(int _idx, int pollResult) { + if (pollResult == UNIFFI_RUST_FUTURE_POLL_READY) { + completer.complete(pollResult); + } else { + poll(); + } + } + callback = NativeCallable.listener(onResponse); + + try { + poll(); + await completer.future; + callback.close(); + + print("error is after"); + final status = calloc(); + try { + print("completer"); + final result = completeFunc(rustFuture, status); + print("checking status"); + // checkCallStatus(errorHandler ?? NullRustCallStatusErrorHandler(), status.ref); + print("lifting"); + return liftFunc(result); + } finally { + calloc.free(status); + } + } finally { + freeFunc(rustFuture); + } + } + }; (types_helper_code, function_definitions) } } -pub fn generate_ffi_type(ret: Option<&FfiType>) -> dart::Tokens { - let Some(ret_type) = ret else { - return quote!(Void); - }; - match *ret_type { - FfiType::UInt8 => quote!(Uint8), - FfiType::UInt16 => quote!(Uint16), - FfiType::UInt32 => quote!(Uint32), - FfiType::UInt64 => quote!(Uint64), - FfiType::Int8 => quote!(Int8), - FfiType::Int16 => quote!(Int16), - FfiType::Int32 => quote!(Int32), - FfiType::Int64 => quote!(Int64), - FfiType::Float32 => quote!(Float), - FfiType::Float64 => quote!(Double), - FfiType::RustBuffer(ref inner) => match inner { - Some(i) => quote!($i), - _ => quote!(RustBuffer), - }, - FfiType::RustArcPtr(_) => quote!(Pointer), - _ => todo!("FfiType::{:?}", ret_type), - } -} - -pub fn generate_ffi_dart_type(ret: Option<&FfiType>) -> dart::Tokens { - let Some(ret_type) = ret else { - return quote!(void); - }; - match *ret_type { - FfiType::UInt8 => quote!(int), - FfiType::UInt16 => quote!(int), - FfiType::UInt32 => quote!(int), - FfiType::UInt64 => quote!(int), - FfiType::Int8 => quote!(int), - FfiType::Int16 => quote!(int), - FfiType::Int32 => quote!(int), - FfiType::Int64 => quote!(int), - FfiType::Float32 | FfiType::Float64 => quote!(double), - FfiType::RustBuffer(ref inner) => match inner { - Some(i) => quote!($i), - _ => quote!(RustBuffer), - }, - FfiType::RustArcPtr(_) => quote!(Pointer), - _ => todo!("FfiType::{:?}", ret_type), - } -} - pub fn generate_type(ty: &Type) -> dart::Tokens { match ty { Type::UInt8 From 027d238218dd4ca9d1b76723ca377d18e38fa5c6 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 31 May 2024 14:56:54 +0100 Subject: [PATCH 2/8] additional tests --- fixtures/futures/src/lib.rs | 166 ++++++++++++------------ fixtures/futures/test/futures_test.dart | 79 +++++------ 2 files changed, 124 insertions(+), 121 deletions(-) diff --git a/fixtures/futures/src/lib.rs b/fixtures/futures/src/lib.rs index f8c5396..e5d1a89 100644 --- a/fixtures/futures/src/lib.rs +++ b/fixtures/futures/src/lib.rs @@ -9,7 +9,6 @@ use std::{ time::Duration, }; - /// Non-blocking timer future. pub struct TimerFuture { shared_state: Arc>, @@ -61,62 +60,62 @@ impl TimerFuture { } // /// Non-blocking timer future. -// pub struct BrokenTimerFuture { -// shared_state: Arc>, -// } +pub struct BrokenTimerFuture { + shared_state: Arc>, +} -// impl Future for BrokenTimerFuture { -// type Output = (); +impl Future for BrokenTimerFuture { + type Output = (); -// fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { -// let mut shared_state = self.shared_state.lock().unwrap(); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut shared_state = self.shared_state.lock().unwrap(); -// if shared_state.completed { -// Poll::Ready(()) -// } else { -// shared_state.waker = Some(cx.waker().clone()); -// Poll::Pending -// } -// } -// } + if shared_state.completed { + Poll::Ready(()) + } else { + shared_state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} -// impl BrokenTimerFuture { -// pub fn new(duration: Duration, fail_after: Duration) -> Self { -// let shared_state = Arc::new(Mutex::new(SharedState { -// completed: false, -// waker: None, -// })); - -// let thread_shared_state = shared_state.clone(); - -// // Let's mimic an event coming from somewhere else, like the system. -// thread::spawn(move || { -// thread::sleep(duration); - -// let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); -// shared_state.completed = true; - -// if let Some(waker) = shared_state.waker.take() { -// // Do not consume `waker`. -// waker.wake_by_ref(); - -// // And this is the important part. We are going to call -// // `wake()` a second time. That's incorrect, but that's on -// // purpose, to see how foreign languages will react. -// if fail_after.is_zero() { -// waker.wake(); -// } else { -// thread::spawn(move || { -// thread::sleep(fail_after); -// waker.wake(); -// }); -// } -// } -// }); - -// Self { shared_state } -// } -// } +impl BrokenTimerFuture { + pub fn new(duration: Duration, fail_after: Duration) -> Self { + let shared_state = Arc::new(Mutex::new(SharedState { + completed: false, + waker: None, + })); + + let thread_shared_state = shared_state.clone(); + + // Let's mimic an event coming from somewhere else, like the system. + thread::spawn(move || { + thread::sleep(duration); + + let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); + shared_state.completed = true; + + if let Some(waker) = shared_state.waker.take() { + // Do not consume `waker`. + waker.wake_by_ref(); + + // And this is the important part. We are going to call + // `wake()` a second time. That's incorrect, but that's on + // purpose, to see how foreign languages will react. + if fail_after.is_zero() { + waker.wake(); + } else { + thread::spawn(move || { + thread::sleep(fail_after); + waker.wake(); + }); + } + } + }); + + Self { shared_state } + } +} #[uniffi::export] pub fn greet(who: String) -> String { @@ -131,19 +130,19 @@ pub async fn always_ready() -> bool { #[uniffi::export] pub async fn void_function() {} -// #[uniffi::export] -// pub async fn say() -> String { -// TimerFuture::new(Duration::from_secs(2)).await; +#[uniffi::export] +pub async fn say() -> String { + TimerFuture::new(Duration::from_secs(2)).await; -// "Hello, Future!".to_string() -// } + "Hello, Future!".to_string() +} -// #[uniffi::export] -// pub async fn say_after(ms: u16, who: String) -> String { -// TimerFuture::new(Duration::from_millis(ms.into())).await; +#[uniffi::export] +pub async fn say_after(ms: u16, who: String) -> String { + TimerFuture::new(Duration::from_millis(ms.into())).await; -// format!("Hello, {who}!") -// } + format!("Hello, {who}!") +} #[uniffi::export] pub async fn sleep(ms: u16) -> bool { @@ -152,15 +151,14 @@ pub async fn sleep(ms: u16) -> bool { true } -// Our error. // Our error. // #[derive(uniffi::Error, Debug)] // pub enum MyError { // Foo, // } -// An async function that can throw. -// An async function that can throw. +// // An async function that can throw. +// // An async function that can throw. // #[uniffi::export] // pub async fn fallible_me(do_fail: bool) -> Result { // if do_fail { @@ -176,24 +174,24 @@ pub async fn say_after_with_tokio(ms: u16, who: String) -> String { format!("Hello, {who} (with Tokio)!") } -// #[derive(uniffi::Record)] -// pub struct MyRecord { -// pub a: String, -// pub b: u32, -// } +#[derive(uniffi::Record, Clone)] +pub struct MyRecord { + pub a: String, + pub b: u32, +} -// #[uniffi::export] -// pub async fn new_my_record(a: String, b: u32) -> MyRecord { -// MyRecord { a, b } -// } +#[uniffi::export] +pub async fn new_my_record(a: String, b: u32) -> MyRecord { + MyRecord { a, b } +} + +#[uniffi::export] +pub async fn broken_sleep(ms: u16, fail_after: u16) { + BrokenTimerFuture::new( + Duration::from_millis(ms.into()), + Duration::from_millis(fail_after.into()), + ) + .await; +} -// #[uniffi::export] -// pub async fn broken_sleep(ms: u16, fail_after: u16) { -// BrokenTimerFuture::new( -// Duration::from_millis(ms.into()), -// Duration::from_millis(fail_after.into()), -// ) -// .await; -// } - uniffi::include_scaffolding!("api"); diff --git a/fixtures/futures/test/futures_test.dart b/fixtures/futures/test/futures_test.dart index ecae87c..8e73b5f 100644 --- a/fixtures/futures/test/futures_test.dart +++ b/fixtures/futures/test/futures_test.dart @@ -27,14 +27,14 @@ void main() { expect(time.inMilliseconds < 200, true); }); - // test('void', () async { - // final time = await measureTime(() async { - // await voidFunction(); - // //expect(result, null); - // }); - // // Less than or equal to time - // expect(time.compareTo(Duration(milliseconds: 4)) <= 0, true); - // }); + test('void', () async { + final time = await measureTime(() async { + await api.voidFunction(); + //expect(result, null); + }); + // Less than or equal to time + expect(time.compareTo(Duration(milliseconds: 4)) <= 0, true); + }); test('sleep', () async { final time = await measureTime(() async { @@ -44,36 +44,41 @@ void main() { expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); }); - // test('sequential_future', () async { - // final time = await measureTime(() async { - // final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); - // final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); - // expect(resultAlice, 'Hello, Alice!'); - // expect(resultBob, 'Hello, Bob!'); - // }); - // expect(time.inMilliseconds > 300 && time.inMilliseconds < 400, true); - // }); + test('sequential_future', () async { + final time = await measureTime(() async { + final resultAlice = await api.sayAfter( + Duration(milliseconds: 100).inMilliseconds, 'Alice'); + final resultBob = + await api.sayAfter(Duration(milliseconds: 200).inMilliseconds, 'Bob'); + expect(resultAlice, 'Hello, Alice!'); + expect(resultBob, 'Hello, Bob!'); + }); + expect(time.inMilliseconds > 300 && time.inMilliseconds < 400, true); + }); // test('concurrent_future', () async { // final time = await measureTime(() async { - // final resultAlice = await api.sayAfter(Duration(milliseconds: 100), 'Alice'); - // final resultBob = await api.sayAfter(Duration(milliseconds: 200), 'Bob'); + // final resultAlice = await api.sayAfter( + // Duration(milliseconds: 100).inMilliseconds, 'Alice'); + // final resultBob = + // await api.sayAfter(Duration(milliseconds: 200).inMilliseconds, 'Bob'); // expect(resultAlice, 'Hello, Alice!'); // expect(resultBob, 'Hello, Bob!'); // }); // expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); // }); - // test('with_tokio_runtime', () async { - // final time = await measureTime(() async { - // final resultAlice = await api.sayAfterWithTokio(Duration(milliseconds: 200), 'Alice'); - // expect(resultAlice, 'Hello, Alice (with Tokio)!'); - // }); - // expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); - // }); + test('with_tokio_runtime', () async { + final time = await measureTime(() async { + final resultAlice = await api.sayAfterWithTokio( + Duration(milliseconds: 200).inMilliseconds, 'Alice'); + expect(resultAlice, 'Hello, Alice (with Tokio)!'); + }); + expect(time.inMilliseconds > 200 && time.inMilliseconds < 300, true); + }); // test('fallible_function_and_method', () async { - // final time1 = await measureTime(() { + // final time1 = await measureTime(() async { // try { // api.fallibleMe(false); // expect(true, true); @@ -84,7 +89,7 @@ void main() { // print('fallible function (with result): ${time1.inMilliseconds}ms'); // expect(time1.compareTo(Duration(milliseconds: 100)), -1); - // final time2 = await measureTime(() { + // final time2 = await measureTime(() async { // try { // api.fallibleMe(true); // expect(false, true); // should never be reached @@ -96,15 +101,15 @@ void main() { // expect(time2.compareTo(Duration(milliseconds: 100)), -1); // }); - // test('record', () async { - // final time = await measureTime(() { - // final result = api.newMyRecord('foo', 42); - // expect(result.a, 'foo'); - // expect(result.b, 42); - // }); - // print('record: ${time.inMilliseconds}ms'); - // expect(time.compareTo(Duration(milliseconds: 100)), -1); - // }); + test('record', () async { + final time = await measureTime(() async { + final result = await api.newMyRecord('foo', 42); + expect(result.a, 'foo'); + expect(result.b, 42); + }); + print('record: ${time.inMilliseconds}ms'); + expect(time.compareTo(Duration(milliseconds: 100)), -1); + }); // test('broken_sleep', () async { // final time = await measureTime(() async { From b8d6e3334ff8525b69d4516f2ca26e7c8d9c2c68 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 31 May 2024 16:00:34 +0100 Subject: [PATCH 3/8] Callable for methods, too --- src/gen/functions.rs | 158 +++++++++++++++++++++++++++++-------------- src/gen/objects.rs | 57 +++++----------- src/gen/types.rs | 43 ++++++------ 3 files changed, 143 insertions(+), 115 deletions(-) diff --git a/src/gen/functions.rs b/src/gen/functions.rs index 0fd8b33..9482e55 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -1,5 +1,5 @@ use genco::prelude::*; -use uniffi_bindgen::interface::{AsType, Function}; +use uniffi_bindgen::interface::{AsType, Callable, FfiFunction, Function}; use crate::gen::oracle::DartCodeOracle; use crate::gen::render::AsRenderable; @@ -7,69 +7,123 @@ use crate::gen::render::AsRenderable; use super::render::TypeHelperRenderer; use super::utils::{fn_name, var_name}; -#[allow(unused_variables)] pub fn generate_function( api: &str, fun: &Function, type_helper: &dyn TypeHelperRenderer, ) -> dart::Tokens { - let ffi = fun.ffi_func(); - let fn_name = fn_name(fun.name()); - let args = quote!($(for arg in &fun.arguments() => $(&arg.as_renderable().render_type(&arg.as_type(), type_helper)) $(var_name(arg.name())),)); - let ff_name = ffi.name(); + generate_for_callable(api, type_helper, fun, fn_name(fun.name()), fun.ffi_func()) +} + +pub fn generate_for_callable( + api: &str, + type_helper: &dyn TypeHelperRenderer, + fun: &impl Callable, + fn_name: String, + ffi: &FfiFunction, +) -> dart::Tokens { + let with_self = if fun.takes_self() { + quote!(uniffiClonePointer(),) + } else { + quote!() + }; + + let call_signature = quote!($fn_name($(for arg in &fun.arguments() => $( + &arg.as_renderable().render_type(&arg.as_type(), type_helper)) $(var_name(arg.name())),))); if fun.is_async() { - let (ret, body) = if let Some(ret) = fun.return_type() { - ( - ret.as_renderable().render_type(ret, type_helper), - quote! { - return $(DartCodeOracle::type_lift_fn(ret, quote!(res))); - }, - ) - } else { - (quote!(void), quote!(return null;)) - }; - quote!( - Future<$ret> $(DartCodeOracle::fn_name(fun.name()))($args) { - final api = $api; - return uniffiRustCallAsync( - () => $(DartCodeOracle::find_lib_instance()).$(fun.ffi_func().name())( - $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) - ), - $(DartCodeOracle::async_poll(fun, type_helper.get_ci())), - $(DartCodeOracle::async_complete(fun, type_helper.get_ci())), - $(DartCodeOracle::async_free(fun, type_helper.get_ci())), - (res) { - final api = $api; - $body - } - ); - } + generate_for_callable_async(api, type_helper, fun, call_signature, ffi.name(), with_self) + } else { + generate_for_callable_sync( + api, + type_helper, + fun, + call_signature, + ffi.name(), + with_self, + ffi.has_rust_call_status_arg(), + ) + } +} + +fn generate_for_callable_async( + api: &str, + type_helper: &dyn TypeHelperRenderer, + fun: &impl Callable, + fn_signature: dart::Tokens, + ffi_name: &str, + with_self: dart::Tokens, +) -> dart::Tokens { + let (ret, body) = if let Some(ret) = fun.return_type() { + ( + ret.as_renderable().render_type(&ret, type_helper), + quote! { + return $(DartCodeOracle::type_lift_fn(&ret, quote!(status))); + }, ) } else { - let inner = quote! { - rustCall(api, (res) => - $(DartCodeOracle::find_lib_instance()).$(fun.ffi_func().name())( - $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) - res) - ) - }; + (quote!(void), quote!(return null;)) + }; + quote!( + Future<$ret> $(fn_signature) { + final api = $api; + return uniffiRustCallAsync( + () => $(DartCodeOracle::find_lib_instance()).$(ffi_name)( + $(with_self) + $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) + ), + $(DartCodeOracle::async_poll(fun, type_helper.get_ci())), + $(DartCodeOracle::async_complete(fun, type_helper.get_ci())), + $(DartCodeOracle::async_free(fun, type_helper.get_ci())), + (status) { + $body + } + ); + } + ) +} - let (ret, body) = if let Some(ret) = fun.return_type() { - ( - ret.as_renderable().render_type(ret, type_helper), - quote! { - return $(DartCodeOracle::type_lift_fn(ret, inner)); - }, +fn generate_for_callable_sync( + api: &str, + type_helper: &dyn TypeHelperRenderer, + fun: &impl Callable, + fn_signature: dart::Tokens, + ffi_name: &str, + with_self: dart::Tokens, + has_rust_call_status_arg: bool, +) -> dart::Tokens { + let inner = if has_rust_call_status_arg { + quote! { + rustCall((status) => + $(DartCodeOracle::find_lib_instance()).$(ffi_name)( + $(with_self) + $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) + status) ) - } else { - (quote!(void), quote!(return;)) - }; + } + } else { quote! { - $ret $fn_name ($args) { - final api = $api; - $body - } + () => $(DartCodeOracle::find_lib_instance()).$(ffi_name)( + $(with_self) + $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) + ) } + }; + + let (ret, body) = if let Some(ret) = fun.return_type() { + ( + ret.as_renderable().render_type(&ret, type_helper), + quote! { + return $(DartCodeOracle::type_lift_fn(&ret, inner)); + }, + ) + } else { + (quote!(void), quote!(return;)) + }; + quote! { + $ret $(fn_signature) { + final api = $api; + $body + } } } diff --git a/src/gen/objects.rs b/src/gen/objects.rs index 67e4f97..3c5c376 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -1,13 +1,13 @@ use genco::prelude::*; use uniffi_bindgen::backend::{CodeType, Literal}; -use uniffi_bindgen::interface::{AsType, Method, Object}; +use uniffi_bindgen::interface::{Method, Object}; use crate::gen::oracle::DartCodeOracle; -use crate::gen::render::AsRenderable; use crate::gen::render::{Renderable, TypeHelperRenderer}; -use super::utils::{class_name, fn_name, var_name}; +use super::functions::generate_for_callable; +use super::utils::{class_name, fn_name}; #[derive(Debug)] pub struct ObjectCodeType { @@ -63,58 +63,33 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da final Api _api; final Pointer _ptr; - - $(cls_name)._(this._api, this._ptr); factory $(cls_name).lift(Api api, Pointer ptr) { return $(cls_name)._(api, ptr); } - Pointer uniffiClonePointer(Api, api) { - return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(obj.ffi_object_clone().name())(_ptr, status)); + Pointer uniffiClonePointer() { + return rustCall((status) => _api.$(obj.ffi_object_clone().name())(_ptr, status)); } - void drop(Api api) { - rustCall(api, (status) => $(DartCodeOracle::find_lib_instance())..$(obj.ffi_object_free().name())(_ptr, status)); + void drop() { + rustCall((status) => _api.$(obj.ffi_object_free().name())(_ptr, status)); } - $(for mt in &obj.methods() => $(generate_method(mt, type_helper))) + $(for mt in &obj.methods() => $( + generate_method(mt, type_helper)) + ) } } } -#[allow(unused_variables)] pub fn generate_method(fun: &Method, type_helper: &dyn TypeHelperRenderer) -> dart::Tokens { - let api = "_api"; - let ffi = fun.ffi_func(); - let fn_name = fn_name(fun.name()); - let args = quote!($(for arg in &fun.arguments() => $(&arg.as_renderable().render_type(&arg.as_type(), type_helper)) $(var_name(arg.name())),)); - let ff_name = ffi.name(); - let inner = quote! { - rustCall(api, (res) => - _$(&fn_name)( - uniffiClonePointer(), - $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) - res) + generate_for_callable( + "_api", + type_helper, + fun, + fn_name(fun.name()), + fun.ffi_func(), ) - }; - - let (ret, body) = if let Some(ret) = fun.return_type() { - ( - ret.as_renderable().render_type(ret, type_helper), - quote! { - return $(DartCodeOracle::type_lift_fn(ret, inner)); - }, - ) - } else { - (quote!(void), quote!($inner;)) - }; - - quote! { - $ret $fn_name ($args) { - final api = _api; - $body - } - } } diff --git a/src/gen/types.rs b/src/gen/types.rs index e03df58..18cacf1 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -117,9 +117,11 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { // Render all the imports let imports: dart::Tokens = quote!(); - let function_definitions = quote!($( for fun in self.ci.function_definitions() => $( - functions::generate_function("this", fun, self) - ))); + let function_definitions = quote!($( + for fun in self.ci.function_definitions() => $( + functions::generate_function("this", fun, self) + )) + ); let helpers_definitions = quote! { $(for (_, ty) in self.get_include_names().iter() => $(ty.as_renderable().render_type_helper(self)) ) @@ -207,17 +209,17 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { external RustBuffer errorBuf; static Pointer allocate({int count = 1}) => - calloc(count * sizeOf()).cast(); + calloc(count * sizeOf()).cast(); } T noop(T t) { return t; } - T rustCall(Api api, T Function(Pointer) callback) { + T rustCall(T Function(Pointer) callback) { var callStatus = RustCallStatus.allocate(); final returnValue = callback(callStatus); - final callStatusCheck = checkCallStatus(callStatus); + checkCallStatus(callStatus); return returnValue; } @@ -253,18 +255,23 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { external Pointer data; static RustBuffer fromBytes(Api api, ForeignBytes bytes) { - return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_from_bytes().name())(bytes, status)); + return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_from_bytes().name())(bytes, status)); } - // Needed so that the foreign language bindingsfunction_definitions can create buffers in which to pass complex data types across the FFI in the future + // Needed so that the foreign language bindings can create buffers in which to pass complex data types across the FFI in the future static RustBuffer allocate(Api api, int size) { - return rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_alloc().name())(size, status)); + return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_alloc().name())(size, status)); } - void free(Api api, ) { - rustCall(api, (status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_free().name())(this, status)); + RustBuffer reserve(Api api, int additionalCapacity) { + return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_reserve().name())(this, additionalCapacity, status)); } + void free(Api api) { + rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_free().name())(this, status)); + } + + Uint8List asUint8List() { return data.cast().asTypedList(len); } @@ -341,18 +348,10 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { await completer.future; callback.close(); - print("error is after"); final status = calloc(); - try { - print("completer"); - final result = completeFunc(rustFuture, status); - print("checking status"); - // checkCallStatus(errorHandler ?? NullRustCallStatusErrorHandler(), status.ref); - print("lifting"); - return liftFunc(result); - } finally { - calloc.free(status); - } + final result = completeFunc(rustFuture, status); + checkCallStatus(status); + return liftFunc(result); } finally { freeFunc(rustFuture); } From 062b3917e76f5cfb5f1827e0edfcda16f4226f78 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 31 May 2024 16:13:41 +0100 Subject: [PATCH 4/8] Simplifications and cleaning up --- src/gen.rs | 1 + src/gen/functions.rs | 21 ++++++++-- src/gen/oracle.rs | 26 ------------- src/gen/primitives.rs | 89 ------------------------------------------- src/gen/render/mod.rs | 4 +- src/gen/types.rs | 4 +- 6 files changed, 20 insertions(+), 125 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 3da26f3..9dd924b 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -157,6 +157,7 @@ impl<'a> DartWrapper<'a> { definitions.append(quote! { late final $dart_return_type Function($dart_args) $fun_name = $lookup_fn; + }); } diff --git a/src/gen/functions.rs b/src/gen/functions.rs index 9482e55..9fbb357 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -1,5 +1,6 @@ use genco::prelude::*; -use uniffi_bindgen::interface::{AsType, Callable, FfiFunction, Function}; +use uniffi_bindgen::backend::Type; +use uniffi_bindgen::interface::{AsType, Callable, ExternalKind, FfiFunction, Function}; use crate::gen::oracle::DartCodeOracle; use crate::gen::render::AsRenderable; @@ -64,6 +65,18 @@ fn generate_for_callable_async( } else { (quote!(void), quote!(return null;)) }; + let ci = type_helper.get_ci(); + + let async_complete = match fun.return_type() { + Some(Type::External { + kind: ExternalKind::DataClass, + name: _, + .. + }) => { + todo!("Need to convert the RustBuffer from our package to the RustBuffer of the external package") + } + _ => quote!($(DartCodeOracle::find_lib_instance()).$(fun.ffi_rust_future_complete(ci))), + }; quote!( Future<$ret> $(fn_signature) { final api = $api; @@ -72,9 +85,9 @@ fn generate_for_callable_async( $(with_self) $(for arg in &fun.arguments() => $(DartCodeOracle::type_lower_fn(&arg.as_type(), quote!($(var_name(arg.name()))))),) ), - $(DartCodeOracle::async_poll(fun, type_helper.get_ci())), - $(DartCodeOracle::async_complete(fun, type_helper.get_ci())), - $(DartCodeOracle::async_free(fun, type_helper.get_ci())), + $(DartCodeOracle::find_lib_instance()).$(fun.ffi_rust_future_poll(ci)), + $(async_complete), + $(DartCodeOracle::find_lib_instance()).$(fun.ffi_rust_future_free(ci)), (status) { $body } diff --git a/src/gen/oracle.rs b/src/gen/oracle.rs index 0bc577e..98e657a 100644 --- a/src/gen/oracle.rs +++ b/src/gen/oracle.rs @@ -86,32 +86,6 @@ impl DartCodeOracle { quote!(api) } - pub fn async_poll(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { - let ffi_func = callable.ffi_rust_future_poll(ci); - quote!($(Self::find_lib_instance()).$ffi_func) - } - - pub fn async_complete(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { - let ffi_func = callable.ffi_rust_future_complete(ci); - let call = quote!($(Self::find_lib_instance()).$ffi_func); - let call = match callable.return_type() { - Some(Type::External { - kind: ExternalKind::DataClass, - name: _, - .. - }) => { - todo!("Need to convert the RustBuffer from our package to the RustBuffer of the external package") - } - _ => call, - }; - call - } - - pub fn async_free(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { - let ffi_func = callable.ffi_rust_future_free(ci); - quote!($(Self::find_lib_instance()).$ffi_func) - } - // TODO: Replace instances of `generate_ffi_dart_type` with ffi_type_label pub fn ffi_type_label(ffi_type: Option<&FfiType>) -> dart::Tokens { let Some(ret_type) = ffi_type else { diff --git a/src/gen/primitives.rs b/src/gen/primitives.rs index 3e59b0d..5d4e369 100644 --- a/src/gen/primitives.rs +++ b/src/gen/primitives.rs @@ -78,92 +78,3 @@ impl_renderable_for_primitive!(UInt32CodeType, "int", "UInt32", 4); impl_renderable_for_primitive!(UInt64CodeType, "int", "UInt64", 8); impl_renderable_for_primitive!(Float32CodeType, "double", "Double32", 4); impl_renderable_for_primitive!(Float64CodeType, "double", "Double64", 8); - -pub fn generate_wrapper_lifters() -> dart::Tokens { - quote! { - class DataOffset { - final T? data; - final int offset; - DataOffset(this.data, this.offset); - } - - // Todo!: Make this guy handle varaible strings - DataOffset liftVaraibleLength( - Uint8List buf, T? Function(Uint8List) lifter, - [int offset = 1]) { - final length = buf.buffer.asByteData().getInt32(offset); // the length in Uint8 - final liftedData = lifter(buf.sublist(offset + 4)); - return DataOffset(liftedData, length); - } - - List liftSequence(Api api, Uint8List buf, Function(Uint8List, [int offset]) lifter, [int element_byte_size = 1,int offset = 0]) { - List res = []; - buf = buf.sublist(offset); - final length = buf.buffer.asByteData().getInt32(0); - buf = buf.sublist(4); - - final element_byte_size = (buf.length ~/ length); - offset = 0; - - for (var i = 0; i < length; i++) { - offset = element_byte_size * i; // Update the offset for the next loop - final item = lifter(buf, offset); - res.add(item); - } - - return res; - } - } -} - -pub fn generate_wrapper_lowerers() -> dart::Tokens { - quote! { - Uint8List createUint8ListFromInt(int value) { - int length = value.bitLength ~/ 8 + 1; - - // Ensure the length is either 4 or 8 - if (length != 4 && length != 8) { - length = (value < 0x100000000) ? 4 : 8; - } - - Uint8List uint8List = Uint8List(length); - - for (int i = length - 1; i >= 0; i--) { - uint8List[i] = value & 0xFF; - value >>= 8; - } - - return uint8List; - } - - Uint8List lowerVaraibleLength(Api api, T input, Uint8List Function(Api, T) lowerer) { - final lowered = lowerer(api, input); - final length = createUint8ListFromInt(lowered.length); - Uint8List res = Uint8List(lowered.length + length.length); - res.setAll(0, length); - res.setAll(length.length, lowered); - return res; - } - - - Uint8List lowerSequence(Api api, List input, Uint8List Function(Api, V) lowerer, int element_byte_size) { - int capacity = input.length * element_byte_size; - Uint8List items = Uint8List(capacity + 4); // Four bytes for the length - int offset = 0; - - // Set the length of the vec - items.setAll(offset, createUint8ListFromInt(capacity)); - offset += 4; - - for (var i = 0; i < input.length; i++) { - items.setRange( - offset, offset + element_byte_size, lowerer(api, input[i] as V)); - offset += element_byte_size; - } - - print("Items from sequence"); - print(items); - return items; - } - } -} diff --git a/src/gen/render/mod.rs b/src/gen/render/mod.rs index 9372a61..bdcdef5 100644 --- a/src/gen/render/mod.rs +++ b/src/gen/render/mod.rs @@ -83,9 +83,7 @@ pub trait Renderable { // AbiType::RefEnum(ty) => quote!(#(ty)), }; - if type_helper.include_once_check(&ty.as_codetype().canonical_name(), ty) { - println!("{} Added", &ty.as_codetype().canonical_name()); - } + type_helper.include_once_check(&ty.as_codetype().canonical_name(), ty); type_name } diff --git a/src/gen/types.rs b/src/gen/types.rs index 18cacf1..c3892ef 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -6,7 +6,7 @@ use std::{ use genco::prelude::*; use uniffi_bindgen::{interface::Type, ComponentInterface}; -use super::{enums, functions, objects, primitives, records}; +use super::{enums, functions, objects, records}; use super::{ render::{AsRenderable, Renderer, TypeHelperRenderer}, Config, @@ -295,8 +295,6 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { return RustBuffer.fromBytes(api, bytes.ref); } - $(primitives::generate_wrapper_lifters()) - $(primitives::generate_wrapper_lowerers()) class ForeignBytes extends Struct { @Int32() From fcb5f5a1a97e6a245831fc6605f10c5fa299fff0 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 4 Jun 2024 15:15:42 +0100 Subject: [PATCH 5/8] faster intToRustBuffer --- src/gen/compounds.rs | 2 +- src/gen/enums.rs | 2 +- src/gen/oracle.rs | 3 +-- src/gen/types.rs | 22 ++++++++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/gen/compounds.rs b/src/gen/compounds.rs index 3633cdd..0bf742d 100644 --- a/src/gen/compounds.rs +++ b/src/gen/compounds.rs @@ -81,7 +81,7 @@ macro_rules! impl_renderable_for_compound { static RustBuffer lower(Api api, $type_label value) { if (value == null) { - return toRustBuffer(api, Uint8List.fromList([0])); + return intToRustBuffer(api, 0); } final length = $cl_name.allocationSize(value); diff --git a/src/gen/enums.rs b/src/gen/enums.rs index 263fbaa..e9dccc4 100644 --- a/src/gen/enums.rs +++ b/src/gen/enums.rs @@ -80,7 +80,7 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: } static RustBuffer lower(Api api, $cls_name input) { - return toRustBuffer(api, createUint8ListFromInt(input.index + 1)); // So enums aren't zero indexed? + return intToRustBuffer(api, input.index + 1); // So enums aren't zero indexed? } } } diff --git a/src/gen/oracle.rs b/src/gen/oracle.rs index 98e657a..d105f4e 100644 --- a/src/gen/oracle.rs +++ b/src/gen/oracle.rs @@ -3,8 +3,7 @@ use genco::quote; use heck::{ToLowerCamelCase, ToUpperCamelCase}; use uniffi_bindgen::backend::CodeType; -use uniffi_bindgen::interface::{AsType, Callable, ExternalKind, FfiType, Type}; -use uniffi_bindgen::ComponentInterface; +use uniffi_bindgen::interface::{AsType, FfiType, Type}; use crate::gen::primitives; diff --git a/src/gen/types.rs b/src/gen/types.rs index c3892ef..bc01d1a 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -295,6 +295,28 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { return RustBuffer.fromBytes(api, bytes.ref); } + RustBuffer intToRustBuffer(Api api, int value) { + int length = value.bitLength ~/ 8 + 1; + + // Ensure the length is either 4 or 8 + if (length != 4 && length != 8) { + length = (value < 0x100000000) ? 4 : 8; + } + + + final Pointer frameData = calloc(length); // Allocate a pointer large enough. + final pointerList = frameData.asTypedList(length); // Create a list that uses our pointer and copy in the data. + + for (int i = length - 1; i >= 0; i--) { + pointerList[i] = value & 0xFF; + value >>= 8; + } + final bytes = calloc(); + bytes.ref.len = length; + bytes.ref.data = frameData; + return RustBuffer.fromBytes(api, bytes.ref); + } + class ForeignBytes extends Struct { @Int32() From a525e4c9e2eeec954fcc7989be25097ed219a7e4 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 4 Jun 2024 15:17:40 +0100 Subject: [PATCH 6/8] fix lints --- src/gen.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 9dd924b..581ef1c 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -122,8 +122,8 @@ impl<'a> DartWrapper<'a> { let fun_name = fun.name(); let (native_return_type, dart_return_type) = match fun.return_type() { Some(return_type) => ( - quote! { $(DartCodeOracle::ffi_native_type_label(Some(&return_type))) }, - quote! { $(DartCodeOracle::ffi_type_label(Some(&return_type))) }, + quote! { $(DartCodeOracle::ffi_native_type_label(Some(return_type))) }, + quote! { $(DartCodeOracle::ffi_type_label(Some(return_type))) }, ), None => (quote! { Void }, quote! { void }), }; From 580e876b6ee73d50963faa8cfbee596710be975c Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 4 Jun 2024 15:32:27 +0100 Subject: [PATCH 7/8] Minor docs and CI fixes --- README.md | 2 +- src/testing.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a32c601..88e9e4e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Dart frontend for UniFFI bindings Reference: [TODOs](./TODO.md) -## MSRV: 1.1.70 +## MSRV: 1.74 This project must always work on latest stable rust + version before. We are also testing it against 1.1.70.0 , which we consider the Minimum Support Rust Version (MSRV) at this point. Rust lower than that will probably not compile the project. diff --git a/src/testing.rs b/src/testing.rs index 0c2321a..dcc4494 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -72,7 +72,10 @@ pub fn run_test(fixture: &str, udl_path: &str, config_path: Option<&str>) -> Res let status = command.spawn()?.wait()?; if !status.success() { println!("FAILED"); - thread::sleep(Duration::from_secs(120)); + if std::env::var("CI").is_err() { + // skip in CI environment + thread::sleep(Duration::from_secs(120)); + } bail!("running `dart` to run test script failed ({:?})", command); } Ok(()) From 0e854cc53077801b36b09ac22a3da907938283c7 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 4 Jun 2024 15:39:22 +0100 Subject: [PATCH 8/8] fix overeagerness --- src/gen/compounds.rs | 2 +- src/gen/types.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gen/compounds.rs b/src/gen/compounds.rs index 0bf742d..3633cdd 100644 --- a/src/gen/compounds.rs +++ b/src/gen/compounds.rs @@ -81,7 +81,7 @@ macro_rules! impl_renderable_for_compound { static RustBuffer lower(Api api, $type_label value) { if (value == null) { - return intToRustBuffer(api, 0); + return toRustBuffer(api, Uint8List.fromList([0])); } final length = $cl_name.allocationSize(value); diff --git a/src/gen/types.rs b/src/gen/types.rs index bc01d1a..8eeb01e 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -181,9 +181,9 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { case unexpectedStaleHandle: return "UniFfi::UnexpectedStaleHandle"; case rustPanic: - return "UniFfi::rustPanic: $$panicMessage"; + return "UniFfi::rustPanic: $panicMessage"; default: - return "UniFfi::UnknownError: $$errorCode"; + return "UniFfi::UnknownError: $errorCode"; } } }