From 65f1ee7300ec552d415006bdc711322897240302 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 13:40:00 +0900 Subject: [PATCH 01/55] Shorten first guide sentences --- README.md | 2 +- documentation/overrides/home.html | 3 +-- flutter_package/README.md | 2 +- rust_crate/README.md | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cd5c0fbf..6dd3d226 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ![Preview](https://github.com/cunarist/rinf/assets/66480156/5c9a7fb6-e566-4c4e-bd77-d72c1c064d6c) -Rinf is a production-ready framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! +Rinf is a framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! ## 🎮 Demo diff --git a/documentation/overrides/home.html b/documentation/overrides/home.html index d597b887..0e2b4ddb 100644 --- a/documentation/overrides/home.html +++ b/documentation/overrides/home.html @@ -104,8 +104,7 @@

- Production-ready framework for creating beautiful and performant - cross-platform apps + Create beautiful and performant cross-platform apps

Rinf

RUST IN FLUTTER

diff --git a/flutter_package/README.md b/flutter_package/README.md index cd5c0fbf..6dd3d226 100644 --- a/flutter_package/README.md +++ b/flutter_package/README.md @@ -10,7 +10,7 @@ ![Preview](https://github.com/cunarist/rinf/assets/66480156/5c9a7fb6-e566-4c4e-bd77-d72c1c064d6c) -Rinf is a production-ready framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! +Rinf is a framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! ## 🎮 Demo diff --git a/rust_crate/README.md b/rust_crate/README.md index cd5c0fbf..6dd3d226 100644 --- a/rust_crate/README.md +++ b/rust_crate/README.md @@ -10,7 +10,7 @@ ![Preview](https://github.com/cunarist/rinf/assets/66480156/5c9a7fb6-e566-4c4e-bd77-d72c1c064d6c) -Rinf is a production-ready framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! +Rinf is a framework for creating beautiful and performant cross-platform apps using Flutter and Rust with batteries fully included. Simply add this framework to your app project, and you're all set to write Flutter and Rust together! ## 🎮 Demo From 524c728727dfc5ceec0ca43cb3459415b0ac46ae Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 13:45:41 +0900 Subject: [PATCH 02/55] Move `communicate` to another Rust module --- .../example/native/hub/src/sample_functions.rs | 2 +- flutter_package/template/native/hub/src/lib.rs | 17 +++-------------- .../template/native/hub/src/sample_functions.rs | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 flutter_package/template/native/hub/src/sample_functions.rs diff --git a/flutter_package/example/native/hub/src/sample_functions.rs b/flutter_package/example/native/hub/src/sample_functions.rs index 3165ae02..7fd1c3d8 100644 --- a/flutter_package/example/native/hub/src/sample_functions.rs +++ b/flutter_package/example/native/hub/src/sample_functions.rs @@ -1,4 +1,4 @@ -//! This crate is written for Rinf demonstrations. +//! This module is written for Rinf demonstrations. use crate::common::*; use crate::messages::*; diff --git a/flutter_package/template/native/hub/src/lib.rs b/flutter_package/template/native/hub/src/lib.rs index 4c2a559a..becc53ee 100644 --- a/flutter_package/template/native/hub/src/lib.rs +++ b/flutter_package/template/native/hub/src/lib.rs @@ -3,9 +3,9 @@ mod common; mod messages; +mod sample_functions; -use crate::common::*; -use crate::messages::*; +use common::*; // use tokio_with_wasm::alias as tokio; // Uncomment this line to target the web. rinf::write_interface!(); @@ -18,7 +18,7 @@ rinf::write_interface!(); #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { // Spawn the concurrent tasks. - tokio::spawn(communicate()); + tokio::spawn(sample_functions::communicate()); // Get the shutdown receiver from Rinf. // This receiver will await a signal from Dart shutdown. @@ -28,14 +28,3 @@ async fn main() -> Result<()> { Ok(()) } -async fn communicate() { - // Send signals to Dart like below. - SmallNumber { number: 7 }.send_signal_to_dart(); - - // Get receivers that listen to Dart signals like below. - let receiver = SmallText::get_dart_signal_receiver(); - while let Some(dart_signal) = receiver.recv().await { - let message: SmallText = dart_signal.message; - rinf::debug_print!("{message:?}"); - } -} diff --git a/flutter_package/template/native/hub/src/sample_functions.rs b/flutter_package/template/native/hub/src/sample_functions.rs new file mode 100644 index 00000000..75c609fe --- /dev/null +++ b/flutter_package/template/native/hub/src/sample_functions.rs @@ -0,0 +1,15 @@ +//! This module is written for Rinf demonstrations. + +use crate::messages::*; + +async fn communicate() { + // Send signals to Dart like below. + SmallNumber { number: 7 }.send_signal_to_dart(); + + // Get receivers that listen to Dart signals like below. + let receiver = SmallText::get_dart_signal_receiver(); + while let Some(dart_signal) = receiver.recv().await { + let message: SmallText = dart_signal.message; + rinf::debug_print!("{message:?}"); + } +} From c593312ecb1a6761589afe158c1e21b14a2e16ed Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 13:50:26 +0900 Subject: [PATCH 03/55] Remove unneeded line --- flutter_package/template/native/hub/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter_package/template/native/hub/src/lib.rs b/flutter_package/template/native/hub/src/lib.rs index becc53ee..6783c377 100644 --- a/flutter_package/template/native/hub/src/lib.rs +++ b/flutter_package/template/native/hub/src/lib.rs @@ -27,4 +27,3 @@ async fn main() -> Result<()> { Ok(()) } - From c89c92528ea66a2fcca4327882164d03e009954f Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 13:51:31 +0900 Subject: [PATCH 04/55] Fix a small bug --- flutter_package/template/native/hub/src/sample_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_package/template/native/hub/src/sample_functions.rs b/flutter_package/template/native/hub/src/sample_functions.rs index 75c609fe..3ec6b5fb 100644 --- a/flutter_package/template/native/hub/src/sample_functions.rs +++ b/flutter_package/template/native/hub/src/sample_functions.rs @@ -2,7 +2,7 @@ use crate::messages::*; -async fn communicate() { +pub async fn communicate() { // Send signals to Dart like below. SmallNumber { number: 7 }.send_signal_to_dart(); From d43db43f5ba33b4bb71055ff9e8a177559c0d376 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 14:02:29 +0900 Subject: [PATCH 05/55] Organize `Cargo.toml` --- rust_crate/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_crate/Cargo.toml b/rust_crate/Cargo.toml index 607a1c22..d9caedd9 100644 --- a/rust_crate/Cargo.toml +++ b/rust_crate/Cargo.toml @@ -16,12 +16,12 @@ bevy = ["bevy_ecs"] bevy_ecs = { version = "0.14", optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] +allo-isolate = "0.1.25" os-thread-local = "0.1.3" -backtrace = { version = "0.3.69", optional = true } protoc-prebuilt = "0.3.0" home = "0.5.9" which = "6.0.0" -allo-isolate = "0.1.25" +backtrace = { version = "0.3.69", optional = true } [target.'cfg(target_family = "wasm")'.dependencies] js-sys = "0.3.70" From f49794eddd25ab873c0660833a40d91424ff8f08 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 14:49:39 +0900 Subject: [PATCH 06/55] Remove unused derive --- rust_crate/src/channel.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust_crate/src/channel.rs b/rust_crate/src/channel.rs index 3c049b90..03c2af32 100644 --- a/rust_crate/src/channel.rs +++ b/rust_crate/src/channel.rs @@ -8,7 +8,6 @@ use std::task::{Context, Poll, Waker}; /// It is clonable, and multiple senders can be created to send messages into /// the same queue. Each message is sent to a receiver, but only the currently /// active receiver can receive messages. -#[derive(Clone)] pub struct SignalSender { inner: Arc>>, } From f1b920d2b2120a0662c11b35ea59329ad34ee027 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 15:04:10 +0900 Subject: [PATCH 07/55] Replace `OnceLock` with `LazyLock` --- rust_crate/src/interface_os.rs | 5 +---- rust_crate/src/shutdown.rs | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index 48f6f563..8c327e2a 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -1,8 +1,6 @@ use crate::error::RinfError; use crate::shutdown::{create_shutdown_channel, SHUTDOWN_SENDER}; use allo_isolate::{IntoDart, Isolate, ZeroCopyBuffer}; -use os_thread_local::ThreadLocal; -use std::cell::RefCell; use std::sync::Mutex; use std::thread; @@ -60,8 +58,7 @@ where #[no_mangle] pub extern "C" fn stop_rust_logic_extern() { - let sender_lock = SHUTDOWN_SENDER.get_or_init(move || ThreadLocal::new(|| RefCell::new(None))); - let sender_option = sender_lock.with(|cell| cell.take()); + let sender_option = SHUTDOWN_SENDER.with(|cell| cell.take()); if let Some(shutdown_sender) = sender_option { // Dropping the sender tells the async runtime to stop running. // Also, it blocks the main thread until diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 7d6bad05..ae76b3ac 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, Mutex, OnceLock}; +use std::sync::{Arc, Condvar, LazyLock, Mutex}; use std::task::{Context, Poll, Waker}; // We use `os_thread_local` so that when the program fails @@ -14,8 +14,9 @@ use std::task::{Context, Poll, Waker}; // Without this solution, // zombie threads inside the async runtime might outlive the app. // This `ThreadLocal` is intended to be used only on the main thread. -type ShutdownSenderLock = OnceLock>>>; -pub static SHUTDOWN_SENDER: ShutdownSenderLock = OnceLock::new(); +type ShutdownSenderLock = LazyLock>>>; +pub static SHUTDOWN_SENDER: ShutdownSenderLock = + LazyLock::new(|| ThreadLocal::new(|| RefCell::new(None))); type ShutdownReceiverLock = Mutex>; pub static SHUTDOWN_RECEIVER: ShutdownReceiverLock = Mutex::new(None); @@ -35,8 +36,7 @@ pub fn get_shutdown_receiver() -> Result { pub fn create_shutdown_channel() -> Result { let (shutdown_sender, shutdown_receiver, shutdown_reporter) = shutdown_channel(); - let sender_lock = SHUTDOWN_SENDER.get_or_init(move || ThreadLocal::new(|| RefCell::new(None))); - sender_lock.with(|cell| cell.replace(Some(shutdown_sender))); + SHUTDOWN_SENDER.with(|cell| cell.replace(Some(shutdown_sender))); let mut reciver_lock = SHUTDOWN_RECEIVER .lock() From 0ccc2f9680142b5df12e4ead1c559f6355c0ce87 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 15:11:27 +0900 Subject: [PATCH 08/55] Replace `OnceLock` with `LazyLock` --- flutter_package/bin/src/message.dart | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 553c5c2f..bfb4cd9f 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -426,19 +426,12 @@ use prost::Message; use rinf::{debug_print, signal_channel, DartSignal, RinfError}; use std::collections::HashMap; use std::error::Error; -use std::sync::OnceLock; +use std::sync::LazyLock; type Handler = dyn Fn(&[u8], &[u8]) -> Result<(), RinfError> + Send + Sync; type DartSignalHandlers = HashMap>; -static DART_SIGNAL_HANDLERS: OnceLock = OnceLock::new(); - -pub fn assign_dart_signal( - message_id: i32, - message_bytes: &[u8], - binary: &[u8] -) -> Result<(), RinfError> { - let hash_map = DART_SIGNAL_HANDLERS.get_or_init(|| { - let mut new_hash_map: DartSignalHandlers = HashMap::new(); +static DART_SIGNAL_HANDLERS: LazyLock = LazyLock::new(|| { + let mut hash_map: DartSignalHandlers = HashMap::new(); '''; for (final entry in markedMessagesAll.entries) { final subpath = entry.key; @@ -455,7 +448,7 @@ pub fn assign_dart_signal( var modulePath = subpath.replaceAll('/', '::'); modulePath = modulePath == '::' ? '' : modulePath; rustReceiveScript += ''' -new_hash_map.insert( +hash_map.insert( ${markedMessage.id}, Box::new(|message_bytes: &[u8], binary: &[u8]| { use super::$modulePath$filename::*; @@ -476,10 +469,15 @@ new_hash_map.insert( } } rustReceiveScript += ''' - new_hash_map - }); + hash_map +}); - let signal_handler = match hash_map.get(&message_id) { +pub fn assign_dart_signal( + message_id: i32, + message_bytes: &[u8], + binary: &[u8] +) -> Result<(), RinfError> { + let signal_handler = match DART_SIGNAL_HANDLERS.get(&message_id) { Some(inner) => inner, None => return Err(RinfError::NoSignalHandler), }; From 6ca1f32e5189973648f8013368591e86d8271b17 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 15:18:34 +0900 Subject: [PATCH 09/55] Remove unused imports from code generation --- flutter_package/bin/src/message.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index bfb4cd9f..5c307e6e 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -290,8 +290,7 @@ import 'package:rinf/rinf.dart'; use prost::Message; use rinf::{ debug_print, send_rust_signal, signal_channel, - DartSignal, RinfError, SignalReceiver, - SignalSender, + DartSignal, SignalReceiver, SignalSender, }; use std::sync::LazyLock; @@ -422,10 +421,10 @@ impl ${normalizePascal(messageName)} { #![allow(unused_imports)] #![allow(unused_mut)] +use super::*; use prost::Message; -use rinf::{debug_print, signal_channel, DartSignal, RinfError}; +use rinf::{DartSignal, RinfError}; use std::collections::HashMap; -use std::error::Error; use std::sync::LazyLock; type Handler = dyn Fn(&[u8], &[u8]) -> Result<(), RinfError> + Send + Sync; @@ -437,7 +436,6 @@ static DART_SIGNAL_HANDLERS: LazyLock = LazyLock::new(|| { final subpath = entry.key; final files = entry.value; for (final entry in files.entries) { - final filename = entry.key; final markedMessages = entry.value; for (final markedMessage in markedMessages) { final markType = markedMessage.markType; @@ -451,7 +449,6 @@ static DART_SIGNAL_HANDLERS: LazyLock = LazyLock::new(|| { hash_map.insert( ${markedMessage.id}, Box::new(|message_bytes: &[u8], binary: &[u8]| { - use super::$modulePath$filename::*; let message = ${normalizePascal(messageName)}::decode(message_bytes) .map_err(|_| RinfError::DecodeMessage)?; From ab3f427ddad0ffd2d064253d6d6c8b1bb9d148d3 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 15:43:44 +0900 Subject: [PATCH 10/55] Remove unneeded errors --- rust_crate/src/error.rs | 8 -------- rust_crate/src/interface_os.rs | 13 +++++-------- rust_crate/src/shutdown.rs | 14 ++++++++------ 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/rust_crate/src/error.rs b/rust_crate/src/error.rs index ea528f57..42c25f63 100644 --- a/rust_crate/src/error.rs +++ b/rust_crate/src/error.rs @@ -3,9 +3,7 @@ use std::fmt; #[derive(Debug)] pub enum RinfError { - LockDartIsolate, NoDartIsolate, - LockShutdownReceiver, NoShutdownReceiver, DecodeMessage, NoSignalHandler, @@ -14,15 +12,9 @@ pub enum RinfError { impl fmt::Display for RinfError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - RinfError::LockDartIsolate => { - write!(f, "Could not acquire the Dart isolate lock.") - } RinfError::NoDartIsolate => { write!(f, "Dart isolate for Rust signals was not created.") } - RinfError::LockShutdownReceiver => { - write!(f, "Could not acquire the shutdown receiver lock.") - } RinfError::NoShutdownReceiver => { write!(f, "Shutdown receiver was not created.") } diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index 8c327e2a..e636e0b4 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -11,11 +11,7 @@ pub extern "C" fn prepare_isolate_extern(port: i64) { let dart_isolate = Isolate::new(port); let mut guard = match DART_ISOLATE.lock() { Ok(inner) => inner, - Err(_) => { - let error = RinfError::LockDartIsolate; - println!("{error}"); - return; - } + Err(poisoned) => poisoned.into_inner(), }; guard.replace(dart_isolate); } @@ -74,9 +70,10 @@ pub fn send_rust_signal_real( ) -> Result<(), RinfError> { // When `DART_ISOLATE` is not initialized, just return the error. // This can happen when running test code in Rust. - let guard = DART_ISOLATE - .lock() - .map_err(|_| RinfError::LockDartIsolate)?; + let guard = match DART_ISOLATE.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; let dart_isolate = guard.as_ref().ok_or(RinfError::NoDartIsolate)?; // If a `Vec` is empty, we can't just simply send it to Dart diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index ae76b3ac..989250b1 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -27,9 +27,10 @@ pub static SHUTDOWN_RECEIVER: ShutdownReceiverLock = Mutex::new(None); /// is necessary to prevent the async runtime in Rust from /// finishing immediately. pub fn get_shutdown_receiver() -> Result { - let mut reciver_lock = SHUTDOWN_RECEIVER - .lock() - .map_err(|_| RinfError::LockShutdownReceiver)?; + let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { + Ok(inner) => inner, + Err(poisned) => poisned.into_inner(), + }; reciver_lock.take().ok_or(RinfError::NoShutdownReceiver) } @@ -38,9 +39,10 @@ pub fn create_shutdown_channel() -> Result { SHUTDOWN_SENDER.with(|cell| cell.replace(Some(shutdown_sender))); - let mut reciver_lock = SHUTDOWN_RECEIVER - .lock() - .map_err(|_| RinfError::LockShutdownReceiver)?; + let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { + Ok(inner) => inner, + Err(poisned) => poisned.into_inner(), + }; reciver_lock.replace(shutdown_receiver); Ok(shutdown_reporter) From 87989441d3a94806e5aeb02d18519f95fa0b2c86 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 15:45:42 +0900 Subject: [PATCH 11/55] Fix typos --- rust_crate/src/shutdown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 989250b1..55ac9770 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -29,7 +29,7 @@ pub static SHUTDOWN_RECEIVER: ShutdownReceiverLock = Mutex::new(None); pub fn get_shutdown_receiver() -> Result { let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { Ok(inner) => inner, - Err(poisned) => poisned.into_inner(), + Err(poisoned) => poisoned.into_inner(), }; reciver_lock.take().ok_or(RinfError::NoShutdownReceiver) } @@ -41,7 +41,7 @@ pub fn create_shutdown_channel() -> Result { let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { Ok(inner) => inner, - Err(poisned) => poisned.into_inner(), + Err(poisoned) => poisoned.into_inner(), }; reciver_lock.replace(shutdown_receiver); From 00b92bf1d891d30e4138c8bf77bf1466e775f5d9 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 23:02:12 +0900 Subject: [PATCH 12/55] Use event-based shutdown procedure --- flutter_package/example/native/hub/src/lib.rs | 11 +- .../template/native/hub/src/common.rs | 7 - .../template/native/hub/src/lib.rs | 11 +- rust_crate/src/channel.rs | 18 +- rust_crate/src/error.rs | 4 - rust_crate/src/interface_os.rs | 59 +++-- rust_crate/src/interface_web.rs | 4 - rust_crate/src/lib.rs | 2 +- rust_crate/src/shutdown.rs | 225 +++++++++--------- 9 files changed, 169 insertions(+), 172 deletions(-) delete mode 100644 flutter_package/template/native/hub/src/common.rs diff --git a/flutter_package/example/native/hub/src/lib.rs b/flutter_package/example/native/hub/src/lib.rs index d2b44ae5..1a04f3d6 100644 --- a/flutter_package/example/native/hub/src/lib.rs +++ b/flutter_package/example/native/hub/src/lib.rs @@ -5,7 +5,6 @@ mod common; mod messages; mod sample_functions; -use common::*; use tokio_with_wasm::alias as tokio; rinf::write_interface!(); @@ -16,16 +15,12 @@ rinf::write_interface!(); // If you really need to use blocking code, // use `tokio::task::spawn_blocking`. #[tokio::main(flavor = "current_thread")] -async fn main() -> Result<()> { +async fn main() { // Spawn the concurrent tasks. tokio::spawn(sample_functions::tell_numbers()); tokio::spawn(sample_functions::stream_fractal()); tokio::spawn(sample_functions::run_debug_tests()); - // Get the shutdown receiver from Rinf. - // This receiver will await a signal from Dart shutdown. - let shutdown_receiver = rinf::get_shutdown_receiver()?; - shutdown_receiver.await; - - Ok(()) + // Keep the main function running until Dart shutdown. + rinf::dart_shutdown().await; } diff --git a/flutter_package/template/native/hub/src/common.rs b/flutter_package/template/native/hub/src/common.rs deleted file mode 100644 index 44f453a4..00000000 --- a/flutter_package/template/native/hub/src/common.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::error::Error; - -/// Using this `Result` type alias allows -/// handling any error type that implements the `Error` trait. -/// This approach eliminates the need -/// to depend on external crates for error handling. -pub type Result = std::result::Result>; diff --git a/flutter_package/template/native/hub/src/lib.rs b/flutter_package/template/native/hub/src/lib.rs index 6783c377..109781c7 100644 --- a/flutter_package/template/native/hub/src/lib.rs +++ b/flutter_package/template/native/hub/src/lib.rs @@ -5,7 +5,6 @@ mod common; mod messages; mod sample_functions; -use common::*; // use tokio_with_wasm::alias as tokio; // Uncomment this line to target the web. rinf::write_interface!(); @@ -16,14 +15,10 @@ rinf::write_interface!(); // If you really need to use blocking code, // use `tokio::task::spawn_blocking`. #[tokio::main(flavor = "current_thread")] -async fn main() -> Result<()> { +async fn main() { // Spawn the concurrent tasks. tokio::spawn(sample_functions::communicate()); - // Get the shutdown receiver from Rinf. - // This receiver will await a signal from Dart shutdown. - let shutdown_receiver = rinf::get_shutdown_receiver()?; - shutdown_receiver.await; - - Ok(()) + // Keep the main function running until Dart shutdown. + rinf::dart_shutdown().await; } diff --git a/rust_crate/src/channel.rs b/rust_crate/src/channel.rs index 03c2af32..338c2e73 100644 --- a/rust_crate/src/channel.rs +++ b/rust_crate/src/channel.rs @@ -37,7 +37,7 @@ impl SignalSender { pub fn send(&self, msg: T) { let mut inner = match self.inner.lock() { Ok(inner) => inner, - Err(_) => return, // Do not consider poisoned mutex + Err(poisoned) => poisoned.into_inner(), }; // Enqueue the message @@ -69,7 +69,10 @@ impl Clone for SignalReceiver { /// original receiver will no longer receive messages after this clone. /// This ensures only the most recent receiver can access the message queue. fn clone(&self) -> Self { - let mut inner = self.inner.lock().unwrap(); + let mut inner = match self.inner.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; let new_receiver = SignalReceiver { inner: self.inner.clone(), id: inner.active_receiver_id + 1, // Increment ID for new receiver @@ -100,7 +103,7 @@ impl Future for RecvFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut inner = match self.inner.lock() { Ok(inner) => inner, - Err(_) => return Poll::Ready(None), // Return None on poisoned mutex + Err(poisoned) => poisoned.into_inner(), }; // Only allow the current active receiver to receive messages @@ -117,7 +120,8 @@ impl Future for RecvFuture { Poll::Pending } } else { - Poll::Ready(None) // Return None if this receiver is not the current active one + // Return None if this receiver is not the current active one + Poll::Ready(None) } } } @@ -127,10 +131,12 @@ impl Future for RecvFuture { /// asynchronously. Only one receiver is active at a time, and new receivers /// are created by cloning the original receiver. pub fn signal_channel() -> (SignalSender, SignalReceiver) { + let start_receiver_id = 0; + let channel = Arc::new(Mutex::new(SignalChannel { queue: VecDeque::new(), waker: None, - active_receiver_id: 0, // Start with receiver ID 0 + active_receiver_id: start_receiver_id, })); let sender = SignalSender { @@ -138,7 +144,7 @@ pub fn signal_channel() -> (SignalSender, SignalReceiver) { }; let receiver = SignalReceiver { inner: channel, - id: 0, + id: start_receiver_id, }; (sender, receiver) } diff --git a/rust_crate/src/error.rs b/rust_crate/src/error.rs index 42c25f63..3f1a8b46 100644 --- a/rust_crate/src/error.rs +++ b/rust_crate/src/error.rs @@ -4,7 +4,6 @@ use std::fmt; #[derive(Debug)] pub enum RinfError { NoDartIsolate, - NoShutdownReceiver, DecodeMessage, NoSignalHandler, } @@ -15,9 +14,6 @@ impl fmt::Display for RinfError { RinfError::NoDartIsolate => { write!(f, "Dart isolate for Rust signals was not created.") } - RinfError::NoShutdownReceiver => { - write!(f, "Shutdown receiver was not created.") - } RinfError::DecodeMessage => { write!(f, "Could not decode the message.") } diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index e636e0b4..1042fe39 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -1,7 +1,9 @@ use crate::error::RinfError; -use crate::shutdown::{create_shutdown_channel, SHUTDOWN_SENDER}; +use crate::shutdown::SHUTDOWN_EVENTS; use allo_isolate::{IntoDart, Isolate, ZeroCopyBuffer}; +use os_thread_local::ThreadLocal; use std::sync::Mutex; +use std::sync::OnceLock; use std::thread; static DART_ISOLATE: Mutex> = Mutex::new(None); @@ -16,11 +18,30 @@ pub extern "C" fn prepare_isolate_extern(port: i64) { guard.replace(dart_isolate); } +// We use `os_thread_local` so that when the program fails +// and the main thread exits unexpectedly, +// the whole Rust async runtime shuts down accordingly. +// Without this solution, +// zombie threads inside the Rust async runtime might outlive the app. +// This `ThreadLocal` is intended to be used only on the main thread, +type ShutdownDropperLock = OnceLock>; +static SHUTDOWN_DROPPER: ShutdownDropperLock = OnceLock::new(); + +/// Notifies Rust that Dart thread has exited when dropped. +pub struct ShutdownDropper; + +impl Drop for ShutdownDropper { + fn drop(&mut self) { + SHUTDOWN_EVENTS.dart_stopped.set(); + SHUTDOWN_EVENTS.rust_stopped.wait(); + } +} + pub fn start_rust_logic_real(main_fn: F) -> Result<(), RinfError> where F: Fn() -> T + Send + 'static, { - // Enable backtrace output for panics. + // Enable console output for panics. #[cfg(debug_assertions)] { #[cfg(not(feature = "backtrace"))] @@ -38,15 +59,29 @@ where } } - // Prepare the channel that will help notify async runtime to shutdown - // after the main Dart thread has gone. - let shutdown_reporter = create_shutdown_channel()?; + // Prepare the shutdown dropper that will notify the Rust async runtime + // after Dart thread has exited. + // This function assumes that this is the main thread. + let thread_local = ThreadLocal::new(|| ShutdownDropper); + let _ = SHUTDOWN_DROPPER.set(thread_local); - // Run the async runtime. + // Spawn the thread holding the async runtime. thread::spawn(move || { + // In debug mode, shutdown events could have been set + // after Dart's hot restart. + #[cfg(debug_assertions)] + { + // Terminates the previous async runtime threads in Rust. + SHUTDOWN_EVENTS.dart_stopped.set(); + // Clears the shutdown events as if the app has started fresh. + SHUTDOWN_EVENTS.dart_stopped.clear(); + SHUTDOWN_EVENTS.rust_stopped.clear(); + } + // Long-blocking function that runs throughout the app lifecycle. main_fn(); - // After the runtime is closed, tell the main thread to stop waiting. - drop(shutdown_reporter); + // After the Rust async runtime is closed, + // tell the main Dart thread to stop blocking before exit. + SHUTDOWN_EVENTS.rust_stopped.set(); }); Ok(()) @@ -54,13 +89,7 @@ where #[no_mangle] pub extern "C" fn stop_rust_logic_extern() { - let sender_option = SHUTDOWN_SENDER.with(|cell| cell.take()); - if let Some(shutdown_sender) = sender_option { - // Dropping the sender tells the async runtime to stop running. - // Also, it blocks the main thread until - // it gets the report that async runtime is dropped. - drop(shutdown_sender); - } + SHUTDOWN_EVENTS.dart_stopped.set(); } pub fn send_rust_signal_real( diff --git a/rust_crate/src/interface_web.rs b/rust_crate/src/interface_web.rs index af29fc5c..69f1af9e 100644 --- a/rust_crate/src/interface_web.rs +++ b/rust_crate/src/interface_web.rs @@ -1,5 +1,4 @@ use crate::error::RinfError; -use crate::shutdown::create_shutdown_channel; use js_sys::Uint8Array; use wasm_bindgen::prelude::*; @@ -15,9 +14,6 @@ where })); } - // Prepare the channel to match the behavior of native platforms. - let _ = create_shutdown_channel(); - // Run the main function. main_fn(); diff --git a/rust_crate/src/lib.rs b/rust_crate/src/lib.rs index 059c6325..dc8debfe 100644 --- a/rust_crate/src/lib.rs +++ b/rust_crate/src/lib.rs @@ -12,4 +12,4 @@ mod interface_web; pub use channel::{signal_channel, SignalReceiver, SignalSender}; pub use error::RinfError; pub use interface::{send_rust_signal, start_rust_logic, DartSignal}; -pub use shutdown::get_shutdown_receiver; +pub use shutdown::dart_shutdown; diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 55ac9770..aff36305 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -1,150 +1,137 @@ -use crate::error::RinfError; -use os_thread_local::ThreadLocal; -use std::cell::RefCell; use std::future::Future; use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Condvar, LazyLock, Mutex}; use std::task::{Context, Poll, Waker}; -// We use `os_thread_local` so that when the program fails -// and the main thread exits unexpectedly, -// the whole async runtime can shut down as well -// by receiving a signal via the shutdown channel. -// Without this solution, -// zombie threads inside the async runtime might outlive the app. -// This `ThreadLocal` is intended to be used only on the main thread. -type ShutdownSenderLock = LazyLock>>>; -pub static SHUTDOWN_SENDER: ShutdownSenderLock = - LazyLock::new(|| ThreadLocal::new(|| RefCell::new(None))); - -type ShutdownReceiverLock = Mutex>; -pub static SHUTDOWN_RECEIVER: ShutdownReceiverLock = Mutex::new(None); +type ShutdownEventsLock = LazyLock; +pub static SHUTDOWN_EVENTS: ShutdownEventsLock = LazyLock::new(|| ShutdownEvents { + dart_stopped: Event::new(), + rust_stopped: Event::new(), +}); + +/// A collection of shutdown events +/// expected to occur one by one on app close. +pub struct ShutdownEvents { + pub dart_stopped: Event, + pub rust_stopped: Event, +} /// Retrieves the shutdown receiver that listens for /// the Dart runtime's closure. /// Awaiting this receiver in the async main Rust function /// is necessary to prevent the async runtime in Rust from /// finishing immediately. -pub fn get_shutdown_receiver() -> Result { - let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - reciver_lock.take().ok_or(RinfError::NoShutdownReceiver) +pub async fn dart_shutdown() { + SHUTDOWN_EVENTS.dart_stopped.wait_async().await; } -pub fn create_shutdown_channel() -> Result { - let (shutdown_sender, shutdown_receiver, shutdown_reporter) = shutdown_channel(); - - SHUTDOWN_SENDER.with(|cell| cell.replace(Some(shutdown_sender))); - - let mut reciver_lock = match SHUTDOWN_RECEIVER.lock() { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - reciver_lock.replace(shutdown_receiver); - - Ok(shutdown_reporter) +/// Synchronization primitive that allows +/// threads or async tasks to wait until a condition is met. +pub struct Event { + flag: Arc>, + condvar: Arc, + wakers: Arc>>, // Store multiple wakers } -type ChannelTuple = (ShutdownSender, ShutdownReceiver, ShutdownReporter); -fn shutdown_channel() -> ChannelTuple { - let should_shutdown = Arc::new(AtomicBool::new(false)); - let waker = Arc::new(Mutex::new(None)); - let did_shutdown = Arc::new(Mutex::new(false)); - let is_done = Arc::new(Condvar::new()); - - let sender = ShutdownSender { - should_shutdown: should_shutdown.clone(), - waker: waker.clone(), - did_shutdown: did_shutdown.clone(), - is_done: is_done.clone(), - }; - let receiver = ShutdownReceiver { - should_shutdown, - waker, - }; - let reporter = ShutdownReporter { - did_shutdown, - is_done, - }; - - (sender, receiver, reporter) -} +impl Event { + /// Creates a new `Event` with the initial state of the flag set to `false`. + pub fn new() -> Self { + Event { + flag: Arc::new(Mutex::new(false)), + condvar: Arc::new(Condvar::new()), + wakers: Arc::new(Mutex::new(Vec::new())), // Initialize as an empty Vec + } + } -pub struct ShutdownSender { - should_shutdown: Arc, - waker: Arc>>, - did_shutdown: Arc>, - is_done: Arc, -} + /// Sets the flag to `true` and notifies all waiting threads. + /// This will wake up any threads waiting on the condition variable. + pub fn set(&self) { + let mut flag = match self.flag.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + *flag = true; + self.condvar.notify_all(); + + // Wake all wakers when the event is set + let mut wakers = match self.wakers.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + for waker in wakers.drain(..) { + waker.wake(); + } + } -impl Drop for ShutdownSender { - fn drop(&mut self) { - self.should_shutdown.store(true, Ordering::SeqCst); - if let Ok(mut guard) = self.waker.lock() { - if let Some(waker) = guard.take() { - waker.wake(); - } + /// Clears the flag, setting it to `false`. + /// This does not affect any waiting threads, but subsequent calls to `wait` will + /// block until the flag is set again. + pub fn clear(&self) { + let mut flag = match self.flag.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + *flag = false; + } + + /// Blocks the current thread until the flag is set to `true`. + /// If the flag is already set, this method will return immediately. Otherwise, it + /// will block until `set` is called by another thread. + pub fn wait(&self) { + let mut flag = match self.flag.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + while !*flag { + flag = match self.condvar.wait(flag) { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; } - while let Ok(guard) = self.did_shutdown.lock() { - if *guard { - break; - } else { - let _unused = self.is_done.wait(guard); - } + } + + /// Creates a future that will be resolved when the flag is set to `true`. + pub fn wait_async(&self) -> WaitFuture { + WaitFuture { + flag: self.flag.clone(), + wakers: self.wakers.clone(), } } } -pub struct ShutdownReceiver { - should_shutdown: Arc, - waker: Arc>>, +/// Future that resolves when the `Event` flag is set to `true`. +pub struct WaitFuture { + flag: Arc>, + wakers: Arc>>, // Store multiple wakers } -impl Future for ShutdownReceiver { +impl Future for WaitFuture { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if !self.should_shutdown.load(Ordering::SeqCst) { - if let Ok(mut guard) = self.waker.lock() { - guard.replace(cx.waker().clone()); - } - Poll::Pending - } else { - Poll::Ready(()) - } - } -} -pub struct ShutdownReporter { - did_shutdown: Arc>, - is_done: Arc, -} + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let flag = match self.flag.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; -impl Drop for ShutdownReporter { - fn drop(&mut self) { - if let Ok(mut guard) = self.did_shutdown.lock() { - *guard = true; - } - self.is_done.notify_all(); - } -} + if *flag { + Poll::Ready(()) + } else { + let mut wakers = match self.wakers.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + + // Remember the current waker if not in the list + let waker = cx.waker(); + let is_unique = !wakers + .iter() + .any(|existing_waker| existing_waker.will_wake(waker)); + if is_unique { + wakers.push(waker.clone()); + } -// `os_thread_local` is only available on native platforms, -// Let's simply mimic `ThreadLocal` on the web. -#[cfg(target_family = "wasm")] -mod os_thread_local { - pub struct ThreadLocal { - inner: T, - } - unsafe impl Sync for ThreadLocal {} - impl ThreadLocal { - pub fn new T>(inner: F) -> ThreadLocal { - ThreadLocal { inner: inner() } - } - pub fn with R>(&self, f: F) { - f(&self.inner); + Poll::Pending } } } From 4eae6c17b8c7c7321b1f3119ee4475da9e060c5f Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 23:11:38 +0900 Subject: [PATCH 13/55] Update comments --- flutter_package/example/native/hub/src/common.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flutter_package/example/native/hub/src/common.rs b/flutter_package/example/native/hub/src/common.rs index 44f453a4..3115f963 100644 --- a/flutter_package/example/native/hub/src/common.rs +++ b/flutter_package/example/native/hub/src/common.rs @@ -1,7 +1,7 @@ use std::error::Error; -/// Using this `Result` type alias allows -/// handling any error type that implements the `Error` trait. -/// This approach eliminates the need -/// to depend on external crates for error handling. +/// This `Result` type alias allows handling any error type +/// that implements the `Error` trait. +/// In practice, it is recommended to use custom solutions +/// or crates dedicated to error handling. pub type Result = std::result::Result>; From 4beeb309c30c5c60c77cec6e0e7d171b0dda589b Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 23:26:00 +0900 Subject: [PATCH 14/55] Update HTML --- documentation/overrides/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/overrides/home.html b/documentation/overrides/home.html index 0e2b4ddb..8fb680b2 100644 --- a/documentation/overrides/home.html +++ b/documentation/overrides/home.html @@ -104,7 +104,7 @@

- Create beautiful and performant cross-platform apps + For beautiful and performant cross-platform apps

Rinf

RUST IN FLUTTER

From 3bcf93cbc63870471fc756f43560d71cb9db561e Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 23:40:25 +0900 Subject: [PATCH 15/55] Remove unneeded comments --- rust_crate/src/shutdown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index aff36305..7604b63a 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -30,7 +30,7 @@ pub async fn dart_shutdown() { pub struct Event { flag: Arc>, condvar: Arc, - wakers: Arc>>, // Store multiple wakers + wakers: Arc>>, } impl Event { @@ -102,7 +102,7 @@ impl Event { /// Future that resolves when the `Event` flag is set to `true`. pub struct WaitFuture { flag: Arc>, - wakers: Arc>>, // Store multiple wakers + wakers: Arc>>, } impl Future for WaitFuture { From 6cb33247c52b33168e1d13f5483da5799e1d7f6d Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sat, 14 Sep 2024 23:44:01 +0900 Subject: [PATCH 16/55] Revert "Remove unneeded comments" This reverts commit 3bcf93cbc63870471fc756f43560d71cb9db561e. --- rust_crate/src/shutdown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 7604b63a..aff36305 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -30,7 +30,7 @@ pub async fn dart_shutdown() { pub struct Event { flag: Arc>, condvar: Arc, - wakers: Arc>>, + wakers: Arc>>, // Store multiple wakers } impl Event { @@ -102,7 +102,7 @@ impl Event { /// Future that resolves when the `Event` flag is set to `true`. pub struct WaitFuture { flag: Arc>, - wakers: Arc>>, + wakers: Arc>>, // Store multiple wakers } impl Future for WaitFuture { From 354636764a7736e44b282e5ea4ac783fb78b5b1a Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:07:02 +0900 Subject: [PATCH 17/55] Add a field `started_session` to `WaitFuture` of `Event` --- rust_crate/src/shutdown.rs | 40 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index aff36305..b607701b 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -28,32 +28,33 @@ pub async fn dart_shutdown() { /// Synchronization primitive that allows /// threads or async tasks to wait until a condition is met. pub struct Event { - flag: Arc>, + flag: Arc>, // Tuple for flag and session count condvar: Arc, wakers: Arc>>, // Store multiple wakers } impl Event { - /// Creates a new `Event` with the initial state of the flag set to `false`. + /// Creates a new `Event` with the initial flag state. pub fn new() -> Self { Event { - flag: Arc::new(Mutex::new(false)), + flag: Arc::new(Mutex::new((false, 0))), condvar: Arc::new(Condvar::new()), - wakers: Arc::new(Mutex::new(Vec::new())), // Initialize as an empty Vec + wakers: Arc::new(Mutex::new(Vec::new())), } } /// Sets the flag to `true` and notifies all waiting threads. /// This will wake up any threads waiting on the condition variable. pub fn set(&self) { - let mut flag = match self.flag.lock() { + let mut state = match self.flag.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - *flag = true; - self.condvar.notify_all(); + state.0 = true; // Set the flag + state.1 += 1; // Increment the count - // Wake all wakers when the event is set + // Wake all threads and async tasks when the event is set + self.condvar.notify_all(); let mut wakers = match self.wakers.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), @@ -71,7 +72,7 @@ impl Event { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - *flag = false; + flag.0 = false; // Clear the flag } /// Blocks the current thread until the flag is set to `true`. @@ -82,7 +83,7 @@ impl Event { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - while !*flag { + while !flag.0 { flag = match self.condvar.wait(flag) { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), @@ -92,7 +93,13 @@ impl Event { /// Creates a future that will be resolved when the flag is set to `true`. pub fn wait_async(&self) -> WaitFuture { + let flag = match self.flag.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + let started_session = flag.1; WaitFuture { + started_session, flag: self.flag.clone(), wakers: self.wakers.clone(), } @@ -101,7 +108,8 @@ impl Event { /// Future that resolves when the `Event` flag is set to `true`. pub struct WaitFuture { - flag: Arc>, + started_session: usize, + flag: Arc>, wakers: Arc>>, // Store multiple wakers } @@ -109,20 +117,26 @@ impl Future for WaitFuture { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Lock the flag to get the current state and session count. let flag = match self.flag.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - if *flag { + // Check if the flag is set or if the session count has changed. + // If the flag is true or the session count is different + // because a new event session has started, stop polling. + if flag.0 || self.started_session != flag.1 { Poll::Ready(()) } else { + // Lock the wakers to manage the list of waiting wakers. let mut wakers = match self.wakers.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - // Remember the current waker if not in the list + // Check if the current waker is already in the list of wakers. + // If the waker is unique (not already in the list), add it to the list. let waker = cx.waker(); let is_unique = !wakers .iter() From 4c57cae8f78ab346eb6c7d1e9aee9edf28a38e02 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:11:45 +0900 Subject: [PATCH 18/55] Organize `Event` code --- rust_crate/src/shutdown.rs | 84 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index b607701b..cd5d6f6a 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -28,38 +28,32 @@ pub async fn dart_shutdown() { /// Synchronization primitive that allows /// threads or async tasks to wait until a condition is met. pub struct Event { - flag: Arc>, // Tuple for flag and session count + inner: Arc>, condvar: Arc, - wakers: Arc>>, // Store multiple wakers } impl Event { /// Creates a new `Event` with the initial flag state. pub fn new() -> Self { Event { - flag: Arc::new(Mutex::new((false, 0))), + inner: Arc::new(Mutex::new(EventInner::new())), condvar: Arc::new(Condvar::new()), - wakers: Arc::new(Mutex::new(Vec::new())), } } /// Sets the flag to `true` and notifies all waiting threads. /// This will wake up any threads waiting on the condition variable. pub fn set(&self) { - let mut state = match self.flag.lock() { + let mut inner = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - state.0 = true; // Set the flag - state.1 += 1; // Increment the count + inner.flag = true; // Set the flag + inner.session += 1; // Increment the session count // Wake all threads and async tasks when the event is set self.condvar.notify_all(); - let mut wakers = match self.wakers.lock() { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - for waker in wakers.drain(..) { + for waker in inner.wakers.drain(..) { waker.wake(); } } @@ -68,23 +62,23 @@ impl Event { /// This does not affect any waiting threads, but subsequent calls to `wait` will /// block until the flag is set again. pub fn clear(&self) { - let mut flag = match self.flag.lock() { + let mut inner = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - flag.0 = false; // Clear the flag + inner.flag = false; // Clear the flag } /// Blocks the current thread until the flag is set to `true`. /// If the flag is already set, this method will return immediately. Otherwise, it /// will block until `set` is called by another thread. pub fn wait(&self) { - let mut flag = match self.flag.lock() { + let mut inner = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - while !flag.0 { - flag = match self.condvar.wait(flag) { + while !inner.flag { + inner = match self.condvar.wait(inner) { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; @@ -92,33 +86,46 @@ impl Event { } /// Creates a future that will be resolved when the flag is set to `true`. - pub fn wait_async(&self) -> WaitFuture { - let flag = match self.flag.lock() { + pub fn wait_async(&self) -> EventFuture { + let inner = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - let started_session = flag.1; - WaitFuture { - started_session, - flag: self.flag.clone(), - wakers: self.wakers.clone(), + EventFuture { + started_session: inner.session, + inner: self.inner.clone(), + } + } +} + +/// Internal state for the `Event` synchronization primitive. +struct EventInner { + flag: bool, // Current flag state + session: usize, // Session count to detect changes + wakers: Vec, // List of wakers to be notified +} + +impl EventInner { + fn new() -> Self { + EventInner { + flag: false, + session: 0, + wakers: Vec::new(), } } } /// Future that resolves when the `Event` flag is set to `true`. -pub struct WaitFuture { +pub struct EventFuture { started_session: usize, - flag: Arc>, - wakers: Arc>>, // Store multiple wakers + inner: Arc>, // Use the combined inner state } -impl Future for WaitFuture { +impl Future for EventFuture { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Lock the flag to get the current state and session count. - let flag = match self.flag.lock() { + let mut inner = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; @@ -126,23 +133,18 @@ impl Future for WaitFuture { // Check if the flag is set or if the session count has changed. // If the flag is true or the session count is different // because a new event session has started, stop polling. - if flag.0 || self.started_session != flag.1 { + if inner.flag || self.started_session != inner.session { Poll::Ready(()) } else { - // Lock the wakers to manage the list of waiting wakers. - let mut wakers = match self.wakers.lock() { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - // Check if the current waker is already in the list of wakers. // If the waker is unique (not already in the list), add it to the list. let waker = cx.waker(); - let is_unique = !wakers + if !inner + .wakers .iter() - .any(|existing_waker| existing_waker.will_wake(waker)); - if is_unique { - wakers.push(waker.clone()); + .any(|existing_waker| existing_waker.will_wake(waker)) + { + inner.wakers.push(waker.clone()); } Poll::Pending From 9106ee00b326138b79bb484875f04fe8906238be Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:47:07 +0900 Subject: [PATCH 19/55] Fix a comment --- rust_crate/src/interface_os.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index 1042fe39..aef1a2bb 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -61,7 +61,7 @@ where // Prepare the shutdown dropper that will notify the Rust async runtime // after Dart thread has exited. - // This function assumes that this is the main thread. + // This code assumes that this is the main thread. let thread_local = ThreadLocal::new(|| ShutdownDropper); let _ = SHUTDOWN_DROPPER.set(thread_local); From 3f01f3f2b5ac6c38c02899d05fadb080315dd494 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:50:25 +0900 Subject: [PATCH 20/55] Improve comments --- rust_crate/src/interface_os.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index aef1a2bb..694941e6 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -65,22 +65,28 @@ where let thread_local = ThreadLocal::new(|| ShutdownDropper); let _ = SHUTDOWN_DROPPER.set(thread_local); - // Spawn the thread holding the async runtime. + // Spawn a new thread to run the async runtime. thread::spawn(move || { - // In debug mode, shutdown events could have been set - // after Dart's hot restart. + // In debug mode, handle potential shutdown events + // caused by Dart's hot restart. #[cfg(debug_assertions)] { - // Terminates the previous async runtime threads in Rust. + // Notify that Dart has stopped + // to terminate previous async runtime threads. SHUTDOWN_EVENTS.dart_stopped.set(); - // Clears the shutdown events as if the app has started fresh. + // Reset shutdown events to prepare for a fresh start. SHUTDOWN_EVENTS.dart_stopped.clear(); SHUTDOWN_EVENTS.rust_stopped.clear(); } - // Long-blocking function that runs throughout the app lifecycle. + + // Execute the long-running function that will block the thread + // for the entire lifecycle of the app. + // This function runs the async Rust runtime. main_fn(); + // After the Rust async runtime is closed, - // tell the main Dart thread to stop blocking before exit. + // notify the main Dart thread to stop blocking + // and allow the application to exit. SHUTDOWN_EVENTS.rust_stopped.set(); }); From d1ac5f0ed6f53773c2b10b7101f5d583c51ff3a2 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:51:28 +0900 Subject: [PATCH 21/55] Check for inner flag from `wait` --- rust_crate/src/shutdown.rs | 54 +++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index cd5d6f6a..6b1b15b7 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -73,16 +73,8 @@ impl Event { /// If the flag is already set, this method will return immediately. Otherwise, it /// will block until `set` is called by another thread. pub fn wait(&self) { - let mut inner = match self.inner.lock() { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - while !inner.flag { - inner = match self.condvar.wait(inner) { - Ok(inner) => inner, - Err(poisoned) => poisoned.into_inner(), - }; - } + let event_blocking = EventBlocking::new(self.inner.clone(), self.condvar.clone()); + event_blocking.wait(); } /// Creates a future that will be resolved when the flag is set to `true`. @@ -115,6 +107,48 @@ impl EventInner { } } +/// Struct to handle waiting with session tracking. +struct EventBlocking { + inner: Arc>, + condvar: Arc, + started_session: usize, +} + +impl EventBlocking { + fn new(inner: Arc>, condvar: Arc) -> Self { + let guard = match inner.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + let start_session = guard.session; + EventBlocking { + inner: inner.clone(), + condvar, + started_session: start_session, + } + } + + pub fn wait(&self) { + let mut guard; + // Lock the inner state + guard = match self.inner.lock() { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + loop { + // Wait on the condition variable and reassign the guard + guard = match self.condvar.wait(guard) { + Ok(inner) => inner, + Err(poisoned) => poisoned.into_inner(), + }; + // Check if the condition is met + if guard.flag || guard.session != self.started_session { + break; + } + } + } +} + /// Future that resolves when the `Event` flag is set to `true`. pub struct EventFuture { started_session: usize, From c4b49d90533b318f25fa72a6aa335d948943a4c9 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 00:58:27 +0900 Subject: [PATCH 22/55] Organize code --- rust_crate/src/shutdown.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 6b1b15b7..71a4cbd6 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -129,22 +129,21 @@ impl EventBlocking { } pub fn wait(&self) { - let mut guard; - // Lock the inner state - guard = match self.inner.lock() { + // Lock the inner state and wait on the condition variable + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; loop { + // Check if the condition is met + if guard.flag || guard.session != self.started_session { + break; + } // Wait on the condition variable and reassign the guard guard = match self.condvar.wait(guard) { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - // Check if the condition is met - if guard.flag || guard.session != self.started_session { - break; - } } } } From 94afa5a8b7fee89b94ff24fee9835ad2331e68b4 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 01:15:04 +0900 Subject: [PATCH 23/55] Organize code --- rust_crate/src/shutdown.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 71a4cbd6..81a76ec9 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -79,12 +79,12 @@ impl Event { /// Creates a future that will be resolved when the flag is set to `true`. pub fn wait_async(&self) -> EventFuture { - let inner = match self.inner.lock() { + let guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; EventFuture { - started_session: inner.session, + started_session: guard.session, inner: self.inner.clone(), } } @@ -120,11 +120,10 @@ impl EventBlocking { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - let start_session = guard.session; EventBlocking { inner: inner.clone(), condvar, - started_session: start_session, + started_session: guard.session, } } From d7dfcf458fec97f360f4fdf20391afabce4353a8 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 01:16:40 +0900 Subject: [PATCH 24/55] Fix a comment --- rust_crate/src/shutdown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 81a76ec9..89d03c2e 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -42,7 +42,7 @@ impl Event { } /// Sets the flag to `true` and notifies all waiting threads. - /// This will wake up any threads waiting on the condition variable. + /// This will wake up any threads or async tasks. pub fn set(&self) { let mut inner = match self.inner.lock() { Ok(inner) => inner, From 4430b1170630638aaf97442cddaa8de41c9a350c Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 01:20:22 +0900 Subject: [PATCH 25/55] Fix errors in other platforms --- rust_crate/src/shutdown.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 89d03c2e..9c2ce7c2 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -43,6 +43,7 @@ impl Event { /// Sets the flag to `true` and notifies all waiting threads. /// This will wake up any threads or async tasks. + #[cfg(not(target_family = "wasm"))] pub fn set(&self) { let mut inner = match self.inner.lock() { Ok(inner) => inner, @@ -61,6 +62,7 @@ impl Event { /// Clears the flag, setting it to `false`. /// This does not affect any waiting threads, but subsequent calls to `wait` will /// block until the flag is set again. + #[cfg(all(not(target_family = "wasm"), debug_assertions))] pub fn clear(&self) { let mut inner = match self.inner.lock() { Ok(inner) => inner, @@ -72,6 +74,7 @@ impl Event { /// Blocks the current thread until the flag is set to `true`. /// If the flag is already set, this method will return immediately. Otherwise, it /// will block until `set` is called by another thread. + #[cfg(not(target_family = "wasm"))] pub fn wait(&self) { let event_blocking = EventBlocking::new(self.inner.clone(), self.condvar.clone()); event_blocking.wait(); From 3916505beda258945f9848d46b8dce5f7a61acfc Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 01:21:58 +0900 Subject: [PATCH 26/55] Remove wrong code --- flutter_package/template/native/hub/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter_package/template/native/hub/src/lib.rs b/flutter_package/template/native/hub/src/lib.rs index 109781c7..cd3729a2 100644 --- a/flutter_package/template/native/hub/src/lib.rs +++ b/flutter_package/template/native/hub/src/lib.rs @@ -1,7 +1,6 @@ //! This `hub` crate is the //! entry point of the Rust logic. -mod common; mod messages; mod sample_functions; From 2690257ec71cbddc1a23b98b1bf271166d37cd36 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 11:56:25 +0900 Subject: [PATCH 27/55] Organize `poll` method code --- rust_crate/src/shutdown.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 9c2ce7c2..c2fbe15c 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -160,7 +160,7 @@ impl Future for EventFuture { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; @@ -168,18 +168,18 @@ impl Future for EventFuture { // Check if the flag is set or if the session count has changed. // If the flag is true or the session count is different // because a new event session has started, stop polling. - if inner.flag || self.started_session != inner.session { + if guard.flag || guard.session != self.started_session { Poll::Ready(()) } else { // Check if the current waker is already in the list of wakers. // If the waker is unique (not already in the list), add it to the list. let waker = cx.waker(); - if !inner + if !guard .wakers .iter() .any(|existing_waker| existing_waker.will_wake(waker)) { - inner.wakers.push(waker.clone()); + guard.wakers.push(waker.clone()); } Poll::Pending From 8617515975507ad53b21c8ea4b07588f5718a3e0 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 12:04:07 +0900 Subject: [PATCH 28/55] Rename an error variant --- rust_crate/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_crate/src/error.rs b/rust_crate/src/error.rs index 3f1a8b46..489c8f86 100644 --- a/rust_crate/src/error.rs +++ b/rust_crate/src/error.rs @@ -4,7 +4,7 @@ use std::fmt; #[derive(Debug)] pub enum RinfError { NoDartIsolate, - DecodeMessage, + CannotDecodeMessage, NoSignalHandler, } @@ -14,7 +14,7 @@ impl fmt::Display for RinfError { RinfError::NoDartIsolate => { write!(f, "Dart isolate for Rust signals was not created.") } - RinfError::DecodeMessage => { + RinfError::CannotDecodeMessage => { write!(f, "Could not decode the message.") } RinfError::NoSignalHandler => { From 5f29d42097c98323b159010bffd17143a2b632ea Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 12:07:59 +0900 Subject: [PATCH 29/55] Rename some variables --- rust_crate/src/channel.rs | 24 ++++++++++++------------ rust_crate/src/shutdown.rs | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/rust_crate/src/channel.rs b/rust_crate/src/channel.rs index 338c2e73..7a41c7df 100644 --- a/rust_crate/src/channel.rs +++ b/rust_crate/src/channel.rs @@ -35,15 +35,15 @@ impl SignalSender { /// message, it will be woken up. This method does not fail if the mutex /// is poisoned but simply ignores the failure. pub fn send(&self, msg: T) { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; // Enqueue the message - inner.queue.push_back(msg); + guard.queue.push_back(msg); // Wake up the previous receiver making it receive `None`, if any - if let Some(waker) = inner.waker.take() { + if let Some(waker) = guard.waker.take() { waker.wake(); } } @@ -69,16 +69,16 @@ impl Clone for SignalReceiver { /// original receiver will no longer receive messages after this clone. /// This ensures only the most recent receiver can access the message queue. fn clone(&self) -> Self { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; let new_receiver = SignalReceiver { inner: self.inner.clone(), - id: inner.active_receiver_id + 1, // Increment ID for new receiver + id: guard.active_receiver_id + 1, // Increment ID for new receiver }; - inner.active_receiver_id = new_receiver.id; - if let Some(waker) = inner.waker.take() { + guard.active_receiver_id = new_receiver.id; + if let Some(waker) = guard.waker.take() { waker.wake(); } new_receiver @@ -101,22 +101,22 @@ impl Future for RecvFuture { /// a message is sent. If this receiver is not the active receiver, it will /// return `None`. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; // Only allow the current active receiver to receive messages - if inner.active_receiver_id == self.receiver_id { - if let Some(msg) = inner.queue.pop_front() { + if guard.active_receiver_id == self.receiver_id { + if let Some(msg) = guard.queue.pop_front() { // Check if more messages are in the queue - if !inner.queue.is_empty() { + if !guard.queue.is_empty() { // If so, wake the current task immediately cx.waker().wake_by_ref(); } Poll::Ready(Some(msg)) } else { - inner.waker = Some(cx.waker().clone()); + guard.waker = Some(cx.waker().clone()); Poll::Pending } } else { diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index c2fbe15c..7e2046b3 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -45,16 +45,16 @@ impl Event { /// This will wake up any threads or async tasks. #[cfg(not(target_family = "wasm"))] pub fn set(&self) { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - inner.flag = true; // Set the flag - inner.session += 1; // Increment the session count + guard.flag = true; // Set the flag + guard.session += 1; // Increment the session count // Wake all threads and async tasks when the event is set self.condvar.notify_all(); - for waker in inner.wakers.drain(..) { + for waker in guard.wakers.drain(..) { waker.wake(); } } @@ -64,11 +64,11 @@ impl Event { /// block until the flag is set again. #[cfg(all(not(target_family = "wasm"), debug_assertions))] pub fn clear(&self) { - let mut inner = match self.inner.lock() { + let mut guard = match self.inner.lock() { Ok(inner) => inner, Err(poisoned) => poisoned.into_inner(), }; - inner.flag = false; // Clear the flag + guard.flag = false; // Clear the flag } /// Blocks the current thread until the flag is set to `true`. From 6e028d27b0a86896d4c7511b5b5249b3c82693b2 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 12:10:57 +0900 Subject: [PATCH 30/55] Change the homepage text --- documentation/overrides/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/overrides/home.html b/documentation/overrides/home.html index 8fb680b2..9774f97b 100644 --- a/documentation/overrides/home.html +++ b/documentation/overrides/home.html @@ -104,7 +104,7 @@

- For beautiful and performant cross-platform apps + Build beautiful and performant cross-platform apps

Rinf

RUST IN FLUTTER

From 819bf86b84df7358a0420e0dda5b869efe2cebba Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:17:21 +0900 Subject: [PATCH 31/55] Fix a small error --- flutter_package/bin/src/message.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 5c307e6e..475df2dc 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -451,7 +451,7 @@ hash_map.insert( Box::new(|message_bytes: &[u8], binary: &[u8]| { let message = ${normalizePascal(messageName)}::decode(message_bytes) - .map_err(|_| RinfError::DecodeMessage)?; + .map_err(|_| RinfError::CannotDecodeMessage)?; let dart_signal = DartSignal { message, binary: binary.to_vec(), From 50419bdc167453dab30020faa53ea20b1997f285 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:23:09 +0900 Subject: [PATCH 32/55] Use maximum line length of 80 in Rust --- .../native/sample_crate/src/fractal.rs | 30 ++++++++++++++----- .../example/native/sample_crate/src/lib.rs | 9 ++++-- rust_crate/src/interface_os.rs | 4 ++- rust_crate/src/interface_web.rs | 6 +++- rust_crate/src/macros.rs | 15 +++++++--- rust_crate/src/main.rs | 4 +-- rust_crate/src/shutdown.rs | 12 ++++---- rustfmt.toml | 1 + 8 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 rustfmt.toml diff --git a/flutter_package/example/native/sample_crate/src/fractal.rs b/flutter_package/example/native/sample_crate/src/fractal.rs index af555c97..12ad1423 100644 --- a/flutter_package/example/native/sample_crate/src/fractal.rs +++ b/flutter_package/example/native/sample_crate/src/fractal.rs @@ -33,7 +33,13 @@ pub fn draw_fractal_image(scale: f64) -> Result, ExampleError> { } } -fn render(buffer: &mut [u8], height: u32, point_x: f64, point_y: f64, scale: f64) { +fn render( + buffer: &mut [u8], + height: u32, + point_x: f64, + point_y: f64, + scale: f64, +) { for y in 0..height { let (line, line_number) = render_line(y, point_x, point_y, scale); write_line(buffer, &line, line_number); @@ -42,20 +48,30 @@ fn render(buffer: &mut [u8], height: u32, point_x: f64, point_y: f64, scale: f64 fn write_line(buffer: &mut [u8], line: &[u8], line_number: u32) { for i in 0..WIDTH { - buffer[(((line_number * WIDTH) + i) * 3) as usize] = line[(i * 3) as usize]; - buffer[((((line_number * WIDTH) + i) * 3) + 1) as usize] = line[((i * 3) + 1) as usize]; - buffer[((((line_number * WIDTH) + i) * 3) + 2) as usize] = line[((i * 3) + 2) as usize]; + buffer[(((line_number * WIDTH) + i) * 3) as usize] = + line[(i * 3) as usize]; + buffer[((((line_number * WIDTH) + i) * 3) + 1) as usize] = + line[((i * 3) + 1) as usize]; + buffer[((((line_number * WIDTH) + i) * 3) + 2) as usize] = + line[((i * 3) + 2) as usize]; } } -fn render_line(line_number: u32, px: f64, py: f64, scale: f64) -> (Vec, u32) { +fn render_line( + line_number: u32, + px: f64, + py: f64, + scale: f64, +) -> (Vec, u32) { let line_size = WIDTH * 3; let mut line: Vec = vec![0; line_size as usize]; for x in 0..WIDTH { // Calculate the offset from the center for x and y - let center_offset_x = (x as f64 - WIDTH as f64 / 2.0) / (WIDTH as f64 / 2.0); - let center_offset_y = (line_number as f64 - HEIGHT as f64 / 2.0) / (HEIGHT as f64 / 2.0); + let center_offset_x = + (x as f64 - WIDTH as f64 / 2.0) / (WIDTH as f64 / 2.0); + let center_offset_y = + (line_number as f64 - HEIGHT as f64 / 2.0) / (HEIGHT as f64 / 2.0); let (nx, ny) = ( SIZE * center_offset_x * scale + px, diff --git a/flutter_package/example/native/sample_crate/src/lib.rs b/flutter_package/example/native/sample_crate/src/lib.rs index 43135346..dd1af987 100644 --- a/flutter_package/example/native/sample_crate/src/lib.rs +++ b/flutter_package/example/native/sample_crate/src/lib.rs @@ -10,7 +10,8 @@ pub use fractal::draw_fractal_image; // `machineid_rs` only supports desktop platforms. #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] pub fn get_hardward_id() -> Result { - let mut builder = machineid_rs::IdBuilder::new(machineid_rs::Encryption::MD5); + let mut builder = + machineid_rs::IdBuilder::new(machineid_rs::Encryption::MD5); builder .add_component(machineid_rs::HWIDComponent::SystemID) .add_component(machineid_rs::HWIDComponent::CPUCores); @@ -19,7 +20,11 @@ pub fn get_hardward_id() -> Result { .map_err(|error| ExampleError(error.into()))?; Ok(hwid) } -#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "linux" +)))] pub fn get_hardward_id() -> Result { Ok(String::from("UNSUPPORTED")) } diff --git a/rust_crate/src/interface_os.rs b/rust_crate/src/interface_os.rs index 694941e6..4873ee40 100644 --- a/rust_crate/src/interface_os.rs +++ b/rust_crate/src/interface_os.rs @@ -54,7 +54,9 @@ where { std::panic::set_hook(Box::new(|panic_info| { let backtrace = backtrace::Backtrace::new(); - crate::debug_print!("A panic occurred in Rust.\n{panic_info}\n{backtrace:?}"); + crate::debug_print!( + "A panic occurred in Rust.\n{panic_info}\n{backtrace:?}" + ); })); } } diff --git a/rust_crate/src/interface_web.rs b/rust_crate/src/interface_web.rs index 69f1af9e..22b9fd69 100644 --- a/rust_crate/src/interface_web.rs +++ b/rust_crate/src/interface_web.rs @@ -23,7 +23,11 @@ where #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = rinf)] - pub fn send_rust_signal_extern(resource: i32, message_bytes: Uint8Array, binary: Uint8Array); + pub fn send_rust_signal_extern( + resource: i32, + message_bytes: Uint8Array, + binary: Uint8Array, + ); } pub fn send_rust_signal_real( diff --git a/rust_crate/src/macros.rs b/rust_crate/src/macros.rs index 51bca9a0..47d9a56c 100644 --- a/rust_crate/src/macros.rs +++ b/rust_crate/src/macros.rs @@ -33,9 +33,11 @@ macro_rules! write_interface { binary_size: usize, ) { use std::slice::from_raw_parts; - let message_bytes = unsafe { from_raw_parts(message_pointer, message_size) }; + let message_bytes = + unsafe { from_raw_parts(message_pointer, message_size) }; let binary = unsafe { from_raw_parts(binary_pointer, binary_size) }; - let result = messages::assign_dart_signal(message_id, message_bytes, binary); + let result = + messages::assign_dart_signal(message_id, message_bytes, binary); if let Err(error) = result { rinf::debug_print!("{error}"); } @@ -43,10 +45,15 @@ macro_rules! write_interface { #[cfg(target_family = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen] - pub fn send_dart_signal_extern(message_id: i32, message_bytes: &[u8], binary: &[u8]) { + pub fn send_dart_signal_extern( + message_id: i32, + message_bytes: &[u8], + binary: &[u8], + ) { let message_bytes = message_bytes; let binary = binary; - let result = messages::assign_dart_signal(message_id, message_bytes, binary); + let result = + messages::assign_dart_signal(message_id, message_bytes, binary); if let Err(error) = result { rinf::debug_print!("{error}"); } diff --git a/rust_crate/src/main.rs b/rust_crate/src/main.rs index 108cef1e..0942a6c7 100644 --- a/rust_crate/src/main.rs +++ b/rust_crate/src/main.rs @@ -15,8 +15,8 @@ fn main() -> Result<(), Box> { .to_path_buf() } else { // Install Protobuf compiler and get the path. - let home_path = - home::home_dir().ok_or("Could not get home directory for `protoc` installation.")?; + let home_path = home::home_dir() + .ok_or("Could not get home directory for `protoc` installation.")?; let out_path = home_path.join(".local").join("bin"); fs::create_dir_all(&out_path)?; env::set_var( diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 7e2046b3..55968070 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -4,10 +4,11 @@ use std::sync::{Arc, Condvar, LazyLock, Mutex}; use std::task::{Context, Poll, Waker}; type ShutdownEventsLock = LazyLock; -pub static SHUTDOWN_EVENTS: ShutdownEventsLock = LazyLock::new(|| ShutdownEvents { - dart_stopped: Event::new(), - rust_stopped: Event::new(), -}); +pub static SHUTDOWN_EVENTS: ShutdownEventsLock = + LazyLock::new(|| ShutdownEvents { + dart_stopped: Event::new(), + rust_stopped: Event::new(), + }); /// A collection of shutdown events /// expected to occur one by one on app close. @@ -76,7 +77,8 @@ impl Event { /// will block until `set` is called by another thread. #[cfg(not(target_family = "wasm"))] pub fn wait(&self) { - let event_blocking = EventBlocking::new(self.inner.clone(), self.condvar.clone()); + let event_blocking = + EventBlocking::new(self.inner.clone(), self.condvar.clone()); event_blocking.wait(); } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..56abb065 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 # This matches the maximum line length used in Dart From 9a536382a17ed6cd13eb416b890d8e23ed8da826 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:26:10 +0900 Subject: [PATCH 33/55] Split long comments into multiple lines --- rust_crate/src/shutdown.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rust_crate/src/shutdown.rs b/rust_crate/src/shutdown.rs index 55968070..cd85991a 100644 --- a/rust_crate/src/shutdown.rs +++ b/rust_crate/src/shutdown.rs @@ -61,7 +61,8 @@ impl Event { } /// Clears the flag, setting it to `false`. - /// This does not affect any waiting threads, but subsequent calls to `wait` will + /// This does not affect any waiting threads, + /// but subsequent calls to `wait` will /// block until the flag is set again. #[cfg(all(not(target_family = "wasm"), debug_assertions))] pub fn clear(&self) { @@ -73,8 +74,9 @@ impl Event { } /// Blocks the current thread until the flag is set to `true`. - /// If the flag is already set, this method will return immediately. Otherwise, it - /// will block until `set` is called by another thread. + /// If the flag is already set, + /// this method will return immediately. + /// Otherwise, it will block until `set` is called by another thread. #[cfg(not(target_family = "wasm"))] pub fn wait(&self) { let event_blocking = @@ -82,7 +84,8 @@ impl Event { event_blocking.wait(); } - /// Creates a future that will be resolved when the flag is set to `true`. + /// Creates a future that will be resolved + /// when the flag is set to `true`. pub fn wait_async(&self) -> EventFuture { let guard = match self.inner.lock() { Ok(inner) => inner, @@ -174,7 +177,8 @@ impl Future for EventFuture { Poll::Ready(()) } else { // Check if the current waker is already in the list of wakers. - // If the waker is unique (not already in the list), add it to the list. + // If the waker is unique (not already in the list), + // add it to the list. let waker = cx.waker(); if !guard .wakers From 85f185f08bdfca0107e4478d86ce4cd6096c95f7 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:29:47 +0900 Subject: [PATCH 34/55] Hide some Rust items from the docs --- rust_crate/src/channel.rs | 1 + rust_crate/src/interface.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/rust_crate/src/channel.rs b/rust_crate/src/channel.rs index 7a41c7df..a71c6d2b 100644 --- a/rust_crate/src/channel.rs +++ b/rust_crate/src/channel.rs @@ -130,6 +130,7 @@ impl Future for RecvFuture { /// used to send messages, and the receiver can be used to receive them /// asynchronously. Only one receiver is active at a time, and new receivers /// are created by cloning the original receiver. +#[doc(hidden)] pub fn signal_channel() -> (SignalSender, SignalReceiver) { let start_receiver_id = 0; diff --git a/rust_crate/src/interface.rs b/rust_crate/src/interface.rs index 4e6be5d6..19234c7b 100644 --- a/rust_crate/src/interface.rs +++ b/rust_crate/src/interface.rs @@ -25,6 +25,7 @@ pub struct DartSignal { /// Even in a single-threaded (current-thread) runtime, /// the `Runtime` object itself might be moved between threads, /// along with all the tasks it manages. +#[doc(hidden)] #[cfg(not(target_family = "wasm"))] pub fn start_rust_logic(main_fn: F) -> Result<(), RinfError> where @@ -45,6 +46,7 @@ where } /// Send a signal to Dart. +#[doc(hidden)] pub fn send_rust_signal( message_id: i32, message_bytes: Vec, From 6bb8d1524fff91cb6420b8a4e6435c690a98ae0e Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:33:50 +0900 Subject: [PATCH 35/55] Rename `rustfmt.toml` to `.rustfmt.toml` --- rustfmt.toml => .rustfmt.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rustfmt.toml => .rustfmt.toml (100%) diff --git a/rustfmt.toml b/.rustfmt.toml similarity index 100% rename from rustfmt.toml rename to .rustfmt.toml From 7c74e3d03ea5d586251887aa6ed6fc6166bae92c Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 14:51:00 +0900 Subject: [PATCH 36/55] Write Clippy rules --- rust_crate/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust_crate/Cargo.toml b/rust_crate/Cargo.toml index d9caedd9..588afcb2 100644 --- a/rust_crate/Cargo.toml +++ b/rust_crate/Cargo.toml @@ -27,3 +27,7 @@ backtrace = { version = "0.3.69", optional = true } js-sys = "0.3.70" wasm-bindgen = "0.2.93" wasm-bindgen-futures = "0.4.43" + +[lints.clippy] +unwrap_used = "deny" +expect_used = "deny" From a7c8c16d7b883faaf8953490c855749dc8ca6f4f Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 15:01:08 +0900 Subject: [PATCH 37/55] Clarify error messages of `main.rs` --- rust_crate/src/main.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/rust_crate/src/main.rs b/rust_crate/src/main.rs index 0942a6c7..4c6347e8 100644 --- a/rust_crate/src/main.rs +++ b/rust_crate/src/main.rs @@ -1,5 +1,5 @@ #[cfg(not(target_family = "wasm"))] -fn main() -> Result<(), Box> { +fn main() -> Result<(), String> { use std::env; use std::fs; use std::path; @@ -18,7 +18,9 @@ fn main() -> Result<(), Box> { let home_path = home::home_dir() .ok_or("Could not get home directory for `protoc` installation.")?; let out_path = home_path.join(".local").join("bin"); - fs::create_dir_all(&out_path)?; + fs::create_dir_all(&out_path).map_err(|_| { + "Could not create the folder for `protoc` installation." + })?; env::set_var( "OUT_DIR", out_path @@ -41,14 +43,19 @@ fn main() -> Result<(), Box> { // Find the path where Dart executables are located. #[cfg(target_family = "windows")] - let pub_cache_bin_path = path::PathBuf::from(env::var("LOCALAPPDATA")?) - .join("Pub") - .join("Cache") - .join("bin"); + let pub_cache_bin_path = path::PathBuf::from( + env::var("LOCALAPPDATA") + .map_err(|_| "Could not get `LOCALAPPDATA` path.")?, + ) + .join("Pub") + .join("Cache") + .join("bin"); #[cfg(target_family = "unix")] - let pub_cache_bin_path = path::PathBuf::from(env::var("HOME")?) - .join(".pub-cache") - .join("bin"); + let pub_cache_bin_path = path::PathBuf::from( + env::var("HOME").map_err(|_| "Could get find `HOME` path.")?, + ) + .join(".pub-cache") + .join("bin"); // Add some folders to PATH for various commands to work correctly. let mut path_var = match env::var_os("PATH") { @@ -57,23 +64,28 @@ fn main() -> Result<(), Box> { }; path_var.push(protoc_path); path_var.push(pub_cache_bin_path); - env::set_var("PATH", env::join_paths(path_var)?); + env::set_var( + "PATH", + env::join_paths(path_var) + .map_err(|_| "Could not push required values to `PATH`.")?, + ); // Get command-line arguments excluding the program name. let dart_command_args: Vec = env::args().skip(1).collect(); // Run the Dart script. - let dart_path = which::which("dart")?; + let dart_path = which::which("dart") + .map_err(|_| "Could not find where Dart is located.")?; let mut command = process::Command::new(dart_path); command.args(["run", "rinf"]); command.args(&dart_command_args); - command.status()?; + command.status().map_err(|_| "Rinf command has failed.")?; Ok(()) } #[cfg(target_family = "wasm")] -fn main() -> Result<(), Box> { +fn main() -> Result<(), String> { // Dummy function to make the linter happy. Ok(()) } From 21e806da800270a68776292733ac5807cf68b654 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 15:04:46 +0900 Subject: [PATCH 38/55] Use `const` for simple constants --- flutter_package/example/native/hub/src/sample_functions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_package/example/native/hub/src/sample_functions.rs b/flutter_package/example/native/hub/src/sample_functions.rs index 7fd1c3d8..416071b6 100644 --- a/flutter_package/example/native/hub/src/sample_functions.rs +++ b/flutter_package/example/native/hub/src/sample_functions.rs @@ -8,9 +8,9 @@ use std::time::Duration; // Using the `cfg` macro enables conditional statement. #[cfg(debug_assertions)] -static IS_DEBUG_MODE: bool = true; +const IS_DEBUG_MODE: bool = true; #[cfg(not(debug_assertions))] -static IS_DEBUG_MODE: bool = false; +const IS_DEBUG_MODE: bool = false; // Business logic for the counter widget. pub async fn tell_numbers() { From 7c4b58e08b40bdaa2ae279c6b7b9494d47f70168 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Sun, 15 Sep 2024 15:07:27 +0900 Subject: [PATCH 39/55] Split long line of macro code --- flutter_package/example/native/hub/src/sample_functions.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flutter_package/example/native/hub/src/sample_functions.rs b/flutter_package/example/native/hub/src/sample_functions.rs index 416071b6..0b96af27 100644 --- a/flutter_package/example/native/hub/src/sample_functions.rs +++ b/flutter_package/example/native/hub/src/sample_functions.rs @@ -193,7 +193,10 @@ pub async fn run_debug_tests() -> Result<()> { prime_count += 1; } } - format!("There are {prime_count} primes from {count_from} to {count_to}.") + format!( + "There are {} primes from {} to {}.", + prime_count, count_from, count_to, + ) }); join_handles.push(join_handle); } From 669249a957c90e615e4062350ec5d8300c905c12 Mon Sep 17 00:00:00 2001 From: temeddix Date: Mon, 16 Sep 2024 00:16:06 +0900 Subject: [PATCH 40/55] Use transparent and static webpage header --- documentation/overrides/home.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/overrides/home.html b/documentation/overrides/home.html index 9774f97b..b617e33b 100644 --- a/documentation/overrides/home.html +++ b/documentation/overrides/home.html @@ -1,8 +1,12 @@ -{% extends "main.html" %} +{% extends "base.html" %} {% block tabs %}