diff --git a/bridge/core/api/executing_context.cc b/bridge/core/api/executing_context.cc index 4e28e7ad8..98f1c46f8 100644 --- a/bridge/core/api/executing_context.cc +++ b/bridge/core/api/executing_context.cc @@ -132,4 +132,12 @@ void ExecutingContextWebFMethods::ClearInterval(ExecutingContext* context, WindowOrWorkerGlobalScope::clearInterval(context, interval_id, shared_exception_state->exception_state); } +void ExecutingContextWebFMethods::SetRunRustFutureTasks(ExecutingContext* context, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + + context->SetRunRustFutureTasks(callback_impl); +} + } // namespace webf diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 9432aeebe..03b1d76b9 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -392,6 +392,14 @@ void ExecutingContext::EnqueueMicrotask(MicrotaskCallback callback, void* data) JS_FreeValue(ctx(), proxy_data); } +void ExecutingContext::SetRunRustFutureTasks(const std::shared_ptr& run_future_task) { + run_rust_future_tasks_ = run_future_task; +} + +void ExecutingContext::RunRustFutureTasks() { + run_rust_future_tasks_->Invoke(this, 0, nullptr); +} + void ExecutingContext::DrainPendingPromiseJobs() { // should executing pending promise jobs. JSContext* pctx; diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 9f3766cde..8b69e988c 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -101,6 +101,8 @@ class ExecutingContext { void ReportError(JSValueConst error, char** rust_errmsg, uint32_t* rust_errmsg_length); void DrainMicrotasks(); void EnqueueMicrotask(MicrotaskCallback callback, void* data = nullptr); + void SetRunRustFutureTasks(const std::shared_ptr& run_rust_future_tasks); + void RunRustFutureTasks(); void DefineGlobalProperty(const char* prop, JSValueConst value); ExecutionContextData* contextData(); uint8_t* DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, uint64_t* bytecodeLength); @@ -225,6 +227,9 @@ class ExecutingContext { // Rust methods ptr should keep alive when ExecutingContext is disposing. const std::unique_ptr public_method_ptr_ = nullptr; + + // Rust future task queue run trigger + std::shared_ptr run_rust_future_tasks_; }; class ObjectProperty { diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 256e4fdb4..e26bc2998 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -39,6 +39,7 @@ void DOMTimer::Fire() { } } else if (auto* callback = DynamicTo(callback_.get())) { callback->Invoke(context_, 0, nullptr); + context_->RunRustFutureTasks(); } } diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index f1051ffab..16ebd7a83 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -92,6 +92,7 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, } context->dartIsolateContext()->profiler()->FinishTrackSteps(); context->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation(); + context->RunRustFutureTasks(); return return_value; } } diff --git a/bridge/core/native/native_loader.cc b/bridge/core/native/native_loader.cc index be44fb0ce..e431f785d 100644 --- a/bridge/core/native/native_loader.cc +++ b/bridge/core/native/native_loader.cc @@ -37,6 +37,7 @@ static void ExecuteNativeLibrary(PluginLibraryEntryPoint entry_point, native_library_load_context->context, native_library_load_context->context->publicMethodPtr(), native_library_load_context->context->status()}; void* result = entry_point(entry_data); + native_library_load_context->context->RunRustFutureTasks(); } delete native_library_load_context; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 5880f01bb..9ba9fe61d 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -122,6 +122,7 @@ NativeValue* WebFPage::invokeModuleEvent(SharedNativeString* native_module_name, NativeValue tmp = callback->Invoke(context_, 2, params); auto* return_value = static_cast(dart_malloc(sizeof(NativeValue))); memcpy(return_value, &tmp, sizeof(NativeValue)); + context_->RunRustFutureTasks(); return return_value; } } diff --git a/bridge/include/plugin_api/executing_context.h b/bridge/include/plugin_api/executing_context.h index ac39e4388..958563b71 100644 --- a/bridge/include/plugin_api/executing_context.h +++ b/bridge/include/plugin_api/executing_context.h @@ -41,6 +41,9 @@ using PublicContextSetInterval = int32_t (*)(ExecutingContext*, SharedExceptionState*); using PublicContextClearTimeout = void (*)(ExecutingContext*, int32_t, SharedExceptionState*); using PublicContextClearInterval = void (*)(ExecutingContext*, int32_t, SharedExceptionState*); +using PublicContextSetRunRustFutureTasks = void (*)(ExecutingContext*, + WebFNativeFunctionContext*, + SharedExceptionState*); // Memory aligned and readable from WebF side. // Only C type member can be included in this class, any C++ type and classes can is not allowed to use here. @@ -77,6 +80,9 @@ struct ExecutingContextWebFMethods { static void ClearInterval(ExecutingContext* context, int32_t interval_id, SharedExceptionState* shared_exception_state); + static void SetRunRustFutureTasks(ExecutingContext* context, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state); double version{1.0}; PublicContextGetDocument context_get_document{document}; @@ -92,6 +98,7 @@ struct ExecutingContextWebFMethods { PublicContextSetInterval context_set_interval{SetInterval}; PublicContextClearTimeout context_clear_timeout{ClearTimeout}; PublicContextClearInterval context_clear_interval{ClearInterval}; + PublicContextSetRunRustFutureTasks context_set_run_rust_future_tasks{SetRunRustFutureTasks}; }; } // namespace webf diff --git a/bridge/rusty_webf_sys/Cargo.toml b/bridge/rusty_webf_sys/Cargo.toml index 633aca08d..e1fa12264 100644 --- a/bridge/rusty_webf_sys/Cargo.toml +++ b/bridge/rusty_webf_sys/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" [dependencies] libc = "0.2.0" - +futures = "0.3" [dependencies.windows] version = "0.58.0" diff --git a/bridge/rusty_webf_sys/src/executing_context.rs b/bridge/rusty_webf_sys/src/executing_context.rs index 4a43a8851..61f198715 100644 --- a/bridge/rusty_webf_sys/src/executing_context.rs +++ b/bridge/rusty_webf_sys/src/executing_context.rs @@ -22,10 +22,12 @@ pub struct ExecutingContextRustMethods { pub set_interval: extern "C" fn(*const OpaquePtr, *const WebFNativeFunctionContext, c_int, *const OpaquePtr) -> c_int, pub clear_timeout: extern "C" fn(*const OpaquePtr, c_int, *const OpaquePtr), pub clear_interval: extern "C" fn(*const OpaquePtr, c_int, *const OpaquePtr), + pub set_run_rust_future_tasks: extern "C" fn(*const OpaquePtr, *const WebFNativeFunctionContext, *const OpaquePtr) -> c_void, } pub type TimeoutCallback = Box; pub type IntervalCallback = Box; +pub type RunRustFutureTasksCallback = Box; /// An environment contains all the necessary running states of a web page. /// @@ -259,6 +261,43 @@ impl ExecutingContext { } } + pub fn set_run_rust_future_tasks(&self, callback: RunRustFutureTasksCallback, exception_state: &ExceptionState) -> Result<(), String> { + let general_callback: WebFNativeFunction = Box::new(move |argc, argv| { + if argc != 0 { + println!("Invalid argument count for run rust future tasks callback"); + return NativeValue::new_null(); + } + callback(); + NativeValue::new_null() + }); + + let callback_data = Box::new(WebFNativeFunctionContextData { + func: general_callback, + }); + let callback_context_data_ptr = Box::into_raw(callback_data); + let callback_context = Box::new(WebFNativeFunctionContext { + callback: invoke_webf_native_function, + free_ptr: release_webf_native_function, + ptr: callback_context_data_ptr, + }); + let callback_context_ptr = Box::into_raw(callback_context); + + unsafe { + ((*self.method_pointer).set_run_rust_future_tasks)(self.ptr, callback_context_ptr, exception_state.ptr); + } + + if exception_state.has_exception() { + unsafe { + let _ = Box::from_raw(callback_context_ptr); + let _ = Box::from_raw(callback_context_data_ptr); + } + return Err(exception_state.stringify(self)); + } + + Ok(()) + + } + } impl Drop for ExecutingContext { @@ -271,3 +310,13 @@ impl Drop for ExecutingContext { } } } + +impl Clone for ExecutingContext { + fn clone(&self) -> Self { + ExecutingContext { + ptr: self.ptr, + method_pointer: self.method_pointer, + status: self.status, + } + } +} diff --git a/bridge/rusty_webf_sys/src/frame/async_storage.rs b/bridge/rusty_webf_sys/src/frame/async_storage.rs index aa6eb5044..09ac09db1 100644 --- a/bridge/rusty_webf_sys/src/frame/async_storage.rs +++ b/bridge/rusty_webf_sys/src/frame/async_storage.rs @@ -23,52 +23,58 @@ impl AsyncStorage { unsafe { &*self.context } } - pub fn get_item(&self, key: &str, callback: GetItemCallback, exception_state: &ExceptionState) { + pub fn get_item(&self, key: &str, exception_state: &ExceptionState) -> WebFNativeFuture { let key_string = NativeValue::new_string(key); + let future_for_return = WebFNativeFuture::::new(); + let future_in_callback = future_for_return.clone(); let general_callback: WebFNativeFunction = Box::new(move |argc, argv| { if argc == 1 { let error_string = unsafe { (*argv).clone() }; let error_string = error_string.to_string(); - callback(Err(error_string)); + future_in_callback.set_result(Err(error_string)); return NativeValue::new_null(); } if argc == 2 { let item_string = unsafe { (*argv.wrapping_add(1)).clone() }; if item_string.is_null() { - callback(Ok(None)); + future_in_callback.set_result(Ok(None)); return NativeValue::new_null(); } let item_string = item_string.to_string(); - callback(Ok(Some(item_string))); + future_in_callback.set_result(Ok(Some(item_string))); return NativeValue::new_null(); } println!("Invalid argument count for async storage callback"); NativeValue::new_null() }); self.context().webf_invoke_module_with_params_and_callback("AsyncStorage", "getItem", &key_string, general_callback, exception_state).unwrap(); + future_for_return } - pub fn set_item(&self, key: &str, value: &str, callback: SetItemCallback, exception_state: &ExceptionState) { + pub fn set_item(&self, key: &str, value: &str, exception_state: &ExceptionState) -> WebFNativeFuture { let key_string = NativeValue::new_string(key); let value_string = NativeValue::new_string(value); + let future_for_return = WebFNativeFuture::::new(); + let future_in_callback = future_for_return.clone(); let params_vec = vec![key_string, value_string]; let params = NativeValue::new_list(params_vec); let general_callback: WebFNativeFunction = Box::new(move |argc, argv| { if argc == 1 { let error_string = unsafe { (*argv).clone() }; let error_string = error_string.to_string(); - callback(Err(error_string)); + future_in_callback.set_result(Err(error_string)); return NativeValue::new_null(); } if argc == 2 { let result = unsafe { (*argv.wrapping_add(1)).clone() }; let result = result.to_string(); - callback(Ok(Some(result))); + future_in_callback.set_result(Ok(Some(result))); return NativeValue::new_null(); } println!("Invalid argument count for async storage callback"); NativeValue::new_null() }); self.context().webf_invoke_module_with_params_and_callback("AsyncStorage", "setItem", ¶ms, general_callback, exception_state).unwrap(); + future_for_return } } diff --git a/bridge/rusty_webf_sys/src/lib.rs b/bridge/rusty_webf_sys/src/lib.rs index 781e5de3d..e460959a2 100644 --- a/bridge/rusty_webf_sys/src/lib.rs +++ b/bridge/rusty_webf_sys/src/lib.rs @@ -16,6 +16,7 @@ pub mod native_value; pub mod script_value_ref; pub mod webf_event_listener; pub mod webf_function; +pub mod webf_future; pub use dom::*; pub use events::*; @@ -29,6 +30,7 @@ pub use native_value::*; pub use script_value_ref::*; pub use webf_event_listener::*; pub use webf_function::*; +pub use webf_future::*; #[repr(C)] pub struct OpaquePtr; diff --git a/bridge/rusty_webf_sys/src/webf_future.rs b/bridge/rusty_webf_sys/src/webf_future.rs new file mode 100644 index 000000000..019ff9509 --- /dev/null +++ b/bridge/rusty_webf_sys/src/webf_future.rs @@ -0,0 +1,92 @@ +use std::cell::RefCell; +use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, Waker}; + +use futures::task; + +type Task = Pin>>; + +pub struct FutureRuntime { + tasks: VecDeque, +} + +impl FutureRuntime { + pub fn new() -> FutureRuntime { + FutureRuntime { + tasks: VecDeque::new(), + } + } + + /// Spawn a future onto the mini-tokio instance. + pub fn spawn(&mut self, future: F) + where + F: Future + 'static, + { + self.tasks.push_back(Box::pin(future)); + } + + pub fn run(&mut self) { + let waker = task::noop_waker(); + let mut cx = Context::from_waker(&waker); + let mut unfinished_tasks = VecDeque::new(); + + while let Some(mut task) = self.tasks.pop_front() { + if task.as_mut().poll(&mut cx).is_pending() { + unfinished_tasks.push_back(task); + } + } + + self.tasks.append(&mut unfinished_tasks); + } +} + +pub struct WebFNativeFuture { + inner: Rc>>, +} + +struct Inner { + result: Option, String>>, +} + +impl WebFNativeFuture { + pub fn new() -> WebFNativeFuture { + WebFNativeFuture { + inner: Rc::new(RefCell::new(Inner { + result: None, + })), + } + } + + pub fn set_result(&self, result: Result, String>) { + let mut inner = self.inner.borrow_mut(); + inner.result = Some(result); + } +} + +impl Future for WebFNativeFuture +where + T: 'static, +{ + type Output = Result, String>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut inner = self.inner.borrow_mut(); + + if let Some(result) = inner.result.take() { + Poll::Ready(result) + } else { + Poll::Pending + } + } +} + +impl Clone for WebFNativeFuture { + fn clone(&self) -> Self { + WebFNativeFuture { + inner: self.inner.clone(), + } + } +} diff --git a/webf/example/rust_builder/rust/src/lib.rs b/webf/example/rust_builder/rust/src/lib.rs index 95a96dfce..638198d12 100644 --- a/webf/example/rust_builder/rust/src/lib.rs +++ b/webf/example/rust_builder/rust/src/lib.rs @@ -1,7 +1,10 @@ -use std::ffi::{c_void, CString}; +use std::cell::RefCell; +use std::ffi::c_void; +use std::rc::Rc; use webf_sys::event::Event; use webf_sys::executing_context::ExecutingContextRustMethods; -use webf_sys::{async_storage, element, initialize_webf_api, navigator, AddEventListenerOptions, EventMethods, EventTargetMethods, NativeValue, RustValue}; +use webf_sys::webf_future::FutureRuntime; +use webf_sys::{initialize_webf_api, AddEventListenerOptions, EventTargetMethods, RustValue}; use webf_sys::element::Element; use webf_sys::node::NodeMethods; @@ -36,25 +39,31 @@ pub extern "C" fn init_webf_app(handle: RustValue) local_storage.clear(&exception_state); - let async_storage_1 = context.async_storage(); + let context2 = context.clone(); - let async_storage_set_item_callback = Box::new(|value: Result, String>| { - match value { - Ok(value) => { - println!("Async Storage Set Item Success: {:?}", value); + let runtime = Rc::new(RefCell::new(FutureRuntime::new())); + + runtime.borrow_mut().spawn(async move { + let context = context2.clone(); + let exception_state = context.create_exception_state(); + let async_storage_2 = context.async_storage(); + + println!("Hello from Rust async context!"); + + let result = async_storage_2.set_item("a", "b", &exception_state).await; + + match result { + Ok(_) => { + println!("Async Storage Set Item Success"); }, Err(err) => { println!("Async Storage Set Item Failed: {:?}", err); } } - }); - async_storage_1.set_item("a", "b", async_storage_set_item_callback, &exception_state); + let result = async_storage_2.get_item("a", &exception_state).await; - let async_storage_2 = context.async_storage(); - - let async_storage_get_item_callback = Box::new(|value: Result, String>| { - match value { + match result { Ok(value) => { println!("Async Storage Get Item Success: {:?}", value); }, @@ -64,13 +73,11 @@ pub extern "C" fn init_webf_app(handle: RustValue) } }); - async_storage_2.get_item("a", async_storage_get_item_callback, &exception_state); - - let timer_callback = Box::new(move || { - println!("Timer Callback"); + let runtime_run_task_callback = Box::new(move || { + runtime.borrow_mut().run(); }); - context.set_timeout_with_callback_and_timeout(timer_callback, 1000, &exception_state).unwrap(); + context.set_run_rust_future_tasks(runtime_run_task_callback, &exception_state).unwrap(); let click_event = document.create_event("custom_click", &exception_state).unwrap(); document.dispatch_event(&click_event, &exception_state);