Skip to content

Commit

Permalink
Merge pull request #357 from cunarist/dart-shutdown-await
Browse files Browse the repository at this point in the history
Organize internal code
  • Loading branch information
temeddix authored Jun 8, 2024
2 parents 5fc3729 + a9a844e commit b1ee067
Show file tree
Hide file tree
Showing 20 changed files with 181 additions and 290 deletions.
86 changes: 29 additions & 57 deletions flutter_ffi_plugin/bin/src/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,9 @@ import 'package:rinf/rinf.dart';
use crate::tokio;
use prost::Message;
use rinf::send_rust_signal;
use rinf::DartSignal;
use rinf::SharedCell;
use std::cell::RefCell;
use rinf::{send_rust_signal, DartSignal};
use std::sync::Mutex;
use std::sync::OnceLock;
use tokio::sync::mpsc::unbounded_channel;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
''',
atFront: true,
);
Expand All @@ -272,38 +266,32 @@ use tokio::sync::mpsc::UnboundedSender;
await insertTextToFile(
rustPath,
'''
type ${messageName}Cell = SharedCell<(
type ${messageName}Cell = Mutex<Option<(
Option<UnboundedSender<DartSignal<${normalizePascal(messageName)}>>>,
Option<UnboundedReceiver<DartSignal<${normalizePascal(messageName)}>>>,
)>;
)>>;
pub static ${snakeName.toUpperCase()}_CHANNEL: ${messageName}Cell =
OnceLock::new();
Mutex::new(None);
impl ${normalizePascal(messageName)} {
pub fn get_dart_signal_receiver() -> UnboundedReceiver<DartSignal<Self>> {
let cell = ${snakeName.toUpperCase()}_CHANNEL
.get_or_init(|| {
let (sender, receiver) = unbounded_channel();
Mutex::new(RefCell::new(Some((Some(sender), Some(receiver)))))
})
.lock()
.unwrap();
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock().unwrap();
if guard.is_none() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
}
#[cfg(debug_assertions)]
{
// After Dart's hot restart,
// a sender from the previous run already exists
// which is now closed.
let borrowed = cell.borrow();
let pair = borrowed.as_ref().unwrap();
let is_closed = pair.0.as_ref().unwrap().is_closed();
drop(borrowed);
if is_closed {
if guard.as_ref().unwrap().0.as_ref().unwrap().is_closed() {
let (sender, receiver) = unbounded_channel();
cell.replace(Some((Some(sender), Some(receiver))));
guard.replace((Some(sender), Some(receiver)));
}
}
let pair = cell.take().unwrap();
cell.replace(Some((pair.0, None)));
let pair = guard.take().unwrap();
guard.replace((pair.0, None));
pair.1.expect("A receiver can be taken only once")
}
}
Expand Down Expand Up @@ -403,27 +391,24 @@ impl ${normalizePascal(messageName)} {
use crate::tokio;
use prost::Message;
use rinf::debug_print;
use rinf::DartSignal;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::OnceLock;
use tokio::sync::mpsc::unbounded_channel;
type SignalHandlers =
OnceLock<Mutex<HashMap<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send>>>>;
OnceLock<HashMap<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send + Sync>>>;
static SIGNAL_HANDLERS: SignalHandlers = OnceLock::new();
pub fn handle_dart_signal(
message_id: i32,
message_bytes: Vec<u8>,
binary: Vec<u8>
) {
let mutex = SIGNAL_HANDLERS.get_or_init(|| {
let mut hash_map =
let hash_map = SIGNAL_HANDLERS.get_or_init(|| {
let mut new_hash_map =
HashMap
::<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send + 'static>>
::<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send + Sync>>
::new();
''';
for (final entry in markedMessagesAll.entries) {
Expand All @@ -441,7 +426,7 @@ pub fn handle_dart_signal(
var modulePath = subpath.replaceAll("/", "::");
modulePath = modulePath == "::" ? "" : modulePath;
rustReceiveScript += '''
hash_map.insert(
new_hash_map.insert(
${markedMessage.id},
Box::new(|message_bytes: Vec<u8>, binary: Vec<u8>| {
use super::$modulePath$filename::*;
Expand All @@ -452,29 +437,22 @@ hash_map.insert(
message,
binary,
};
let cell = ${snakeName.toUpperCase()}_CHANNEL
.get_or_init(|| {
let (sender, receiver) = unbounded_channel();
Mutex::new(RefCell::new(Some((Some(sender), Some(receiver)))))
})
.lock()
.unwrap();
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock().unwrap();
if guard.is_none() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
}
#[cfg(debug_assertions)]
{
// After Dart's hot restart,
// a sender from the previous run already exists
// which is now closed.
let borrowed = cell.borrow();
let pair = borrowed.as_ref().unwrap();
let is_closed = pair.0.as_ref().unwrap().is_closed();
drop(borrowed);
if is_closed {
if guard.as_ref().unwrap().0.as_ref().unwrap().is_closed() {
let (sender, receiver) = unbounded_channel();
cell.replace(Some((Some(sender), Some(receiver))));
guard.replace((Some(sender), Some(receiver)));
}
}
let borrowed = cell.borrow();
let pair = borrowed.as_ref().unwrap();
let pair = guard.as_ref().unwrap();
let sender = pair.0.as_ref().unwrap();
let _ = sender.send(dart_signal);
}),
Expand All @@ -485,11 +463,10 @@ hash_map.insert(
}
}
rustReceiveScript += '''
Mutex::new(hash_map)
new_hash_map
});
let guard = mutex.lock().unwrap();
let signal_handler = guard.get(&message_id).unwrap();
let signal_handler = hash_map.get(&message_id).unwrap();
signal_handler(message_bytes, binary);
}
''';
Expand All @@ -510,11 +487,6 @@ Future<void> initializeRust({String? compiledLibPath}) async {
startRustLogic();
}
Future<void> finalizeRust() async {
stopRustLogic();
await Future.delayed(const Duration(milliseconds: 10));
}
final signalHandlers = <int, void Function(Uint8List, Uint8List)>{
''';
for (final entry in markedMessagesAll.entries) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
26 changes: 2 additions & 24 deletions flutter_ffi_plugin/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:example_app/messages/generated.dart';
import 'package:example_app/messages/counter_number.pb.dart';
Expand All @@ -7,31 +6,10 @@ import 'package:example_app/messages/fractal_art.pb.dart';
void main() async {
// Wait for Rust initialization to be completed first.
await initializeRust();
runApp(const MyApp());
runApp(MyApp());
}

class MyApp extends StatefulWidget {
const MyApp({super.key});

@override
State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final _appLifecycleListener = AppLifecycleListener(
onExitRequested: () async {
// Terminate Rust tasks before closing the Flutter app.
await finalizeRust();
return AppExitResponse.exit;
},
);

@override
void dispose() {
_appLifecycleListener.dispose();
super.dispose();
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
1 change: 1 addition & 0 deletions flutter_ffi_plugin/example/native/hub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
rinf = "6.11.1"
prost = "0.12.6"
# tokio = { version = "1", features = ["sync"] }
wasm-bindgen = "0.2.92" # Uncomment this line to target the web
tokio_with_wasm = "0.4.4" # Uncomment this line to target the web
sample_crate = { path = "../sample_crate" }
23 changes: 1 addition & 22 deletions flutter_ffi_plugin/example/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,8 @@

<title>example</title>
<link rel="manifest" href="manifest.json">

<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
18 changes: 4 additions & 14 deletions flutter_ffi_plugin/lib/rinf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,18 @@ export 'src/interface.dart' show RustSignal;
/// This function might not be necessary for major platforms
/// but can be useful when the app runs on embedded devices.
void setCompiledLibPath(String? path) {
setCompiledLibPathExtern(path);
setCompiledLibPathReal(path);
}

/// Prepares the native interface
/// needed to communicate with Rust.
Future<void> prepareInterface(HandleRustSignal handleRustSignal) async {
await prepareInterfaceExtern(handleRustSignal);
await prepareInterfaceReal(handleRustSignal);
}

/// Starts the `main` function in Rust.
void startRustLogic() async {
startRustLogicExtern();
}

/// Terminates all Rust tasks.
/// Calling this function before closing the Flutter app
/// can prevent potential resource leaks that may occur
/// if the Rust side is abruptly terminated.
/// Please note that on the web, this function does not have any effect,
/// as tasks are managed by the JavaScript runtime, not Rust.
void stopRustLogic() async {
stopRustLogicExtern();
startRustLogicReal();
}

/// Sends a signal to Rust.
Expand All @@ -43,7 +33,7 @@ void sendDartSignal(
Uint8List messageBytes,
Uint8List binary,
) async {
sendDartSignalExtern(
sendDartSignalReal(
messageId,
messageBytes,
binary,
Expand Down
20 changes: 6 additions & 14 deletions flutter_ffi_plugin/lib/src/interface_os.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import 'dart:isolate';
import 'interface.dart';
import 'dart:convert';

void setCompiledLibPathExtern(String? path) {
void setCompiledLibPathReal(String? path) {
setDynamicLibPath(path);
}

Future<void> prepareInterfaceExtern(
Future<void> prepareInterfaceReal(
HandleRustSignal handleRustSignal,
) async {
/// This should be called once at startup
Expand Down Expand Up @@ -53,27 +53,19 @@ Future<void> prepareInterfaceExtern(
});

// Make Rust prepare its isolate to send data to Dart.
prepareIsolateExtern(rustSignalPort.sendPort.nativePort);
prepareIsolateReal(rustSignalPort.sendPort.nativePort);
}

void startRustLogicExtern() {
void startRustLogicReal() {
final rustFunction =
rustLibrary.lookupFunction<Void Function(), void Function()>(
'start_rust_logic_extern',
);
rustFunction();
}

void stopRustLogicExtern() {
final rustFunction =
rustLibrary.lookupFunction<Void Function(), void Function()>(
'stop_rust_logic_extern',
);
rustFunction();
}

/// Sends bytes to Rust.
Future<void> sendDartSignalExtern(
Future<void> sendDartSignalReal(
int messageId,
Uint8List messageBytes,
Uint8List binary,
Expand Down Expand Up @@ -112,7 +104,7 @@ Future<void> sendDartSignalExtern(
malloc.free(binaryMemory);
}

void prepareIsolateExtern(int port) {
void prepareIsolateReal(int port) {
final rustFunction = rustLibrary.lookupFunction<
Void Function(
IntPtr,
Expand Down
Loading

0 comments on commit b1ee067

Please sign in to comment.