From 089853fcaebc4ad8b1b17f863e0954ef3ccb3394 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Sun, 21 Jan 2024 16:37:33 -0800 Subject: [PATCH] chore: ignore engine start if runtime is already up Thanks to the engine overhaul, we shut down the runtime static at the end of every session. We should check and make sure that's the case before letting the user start the engine again. Ideally the UX would handle this with not allowing the button to be active but eh. --- intiface-engine-flutter-bridge/src/api.rs | 22 +++++++----- .../src/bridge_generated.io.rs | 5 +++ .../src/bridge_generated.rs | 10 ++++++ ios/Runner/bridge_generated.h | 5 +-- lib/bloc/engine/engine_control_bloc.dart | 4 +++ lib/bloc/engine/engine_provider.dart | 1 + lib/bloc/engine/engine_repository.dart | 4 +++ ...reground_task_library_engine_provider.dart | 12 ++++++- lib/bloc/engine/library_engine_provider.dart | 13 ++++++- lib/bridge_generated.dart | 35 +++++++++++++++++++ macos/Runner/bridge_generated.h | 3 ++ 11 files changed, 101 insertions(+), 13 deletions(-) diff --git a/intiface-engine-flutter-bridge/src/api.rs b/intiface-engine-flutter-bridge/src/api.rs index c261dae..848a75e 100644 --- a/intiface-engine-flutter-bridge/src/api.rs +++ b/intiface-engine-flutter-bridge/src/api.rs @@ -77,6 +77,10 @@ pub struct _EngineOptionsExternal { pub repeater_remote_address: Option, } +pub fn runtime_started() -> bool { + RUNTIME.lock().unwrap().is_some() +} + pub fn run_engine(sink: StreamSink, args: EngineOptionsExternal) -> Result<()> { if RUN_STATUS.load(Ordering::Relaxed) { @@ -214,21 +218,21 @@ pub fn stop_engine() { } // Need to park ourselves real quick to let the other runtime threads finish out. // - // HACK The android JNI drop calls are slow and need quite a while to get everything disconnected. - // If they don't run to completion, the runtime won't shutdown properly and everything will stall. - // Waiting on this is not the optimal way to do it, but I also don't have a good way to know when - // shutdown is finished right now. So waiting it is. - #[cfg(target_os = "android")] - thread::sleep(Duration::from_millis(1000)); - #[cfg(not(target_os = "android"))] - thread::sleep(Duration::from_millis(1)); + // HACK The android JNI drop calls (and sometimes windows UWP calls) are slow (100ms+) and need + // quite a while to get everything disconnected if there are currently connected devices. If they + // don't run to completion, the runtime won't shutdown properly and everything will stall. Running + // runtime_shutdown() doesn't work here because these are all tasks that may be stalled at the OS + // level so we don't have enough info. Waiting on this is not the optimal way to do it, but I also + // don't have a good way to know when shutdown is finished right now. So waiting it is. 1s isn't + // super noticable from an UX standpoint. + thread::sleep(Duration::from_millis(500)); let runtime; { runtime = RUNTIME.lock().unwrap().take(); } if let Some(rt) = runtime { info!("Shutting down runtime"); - rt.shutdown_timeout(Duration::from_secs(5)); + rt.shutdown_timeout(Duration::from_secs(1)); info!("Runtime shutdown complete"); } RUN_STATUS.store(false, Ordering::Relaxed); diff --git a/intiface-engine-flutter-bridge/src/bridge_generated.io.rs b/intiface-engine-flutter-bridge/src/bridge_generated.io.rs index cb83228..6aa54a7 100644 --- a/intiface-engine-flutter-bridge/src/bridge_generated.io.rs +++ b/intiface-engine-flutter-bridge/src/bridge_generated.io.rs @@ -1,6 +1,11 @@ use super::*; // Section: wire functions +#[no_mangle] +pub extern "C" fn wire_runtime_started(port_: i64) { + wire_runtime_started_impl(port_) +} + #[no_mangle] pub extern "C" fn wire_run_engine(port_: i64, args: *mut wire_EngineOptionsExternal) { wire_run_engine_impl(port_, args) diff --git a/intiface-engine-flutter-bridge/src/bridge_generated.rs b/intiface-engine-flutter-bridge/src/bridge_generated.rs index af41d67..9d43ff7 100644 --- a/intiface-engine-flutter-bridge/src/bridge_generated.rs +++ b/intiface-engine-flutter-bridge/src/bridge_generated.rs @@ -22,6 +22,16 @@ use std::sync::Arc; // Section: wire functions +fn wire_runtime_started_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool, _>( + WrapInfo { + debug_name: "runtime_started", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Result::<_, ()>::Ok(runtime_started()), + ) +} fn wire_run_engine_impl( port_: MessagePort, args: impl Wire2Api + UnwindSafe, diff --git a/ios/Runner/bridge_generated.h b/ios/Runner/bridge_generated.h index 617b4a9..3705781 100644 --- a/ios/Runner/bridge_generated.h +++ b/ios/Runner/bridge_generated.h @@ -103,6 +103,8 @@ uintptr_t new_dart_opaque(Dart_Handle handle); intptr_t init_frb_dart_api_dl(void *obj); +void wire_runtime_started(int64_t port_); + void wire_run_engine(int64_t port_, struct wire_EngineOptionsExternal *args); void wire_send(int64_t port_, struct wire_uint_8_list *msg_json); @@ -148,10 +150,9 @@ struct wire_uint_8_list *new_uint_8_list_0(int32_t len); void free_WireSyncReturn(WireSyncReturn ptr); -jint JNI_OnLoad(JavaVM vm, const void *_res); - static int64_t dummy_method_to_enforce_bundling(void) { int64_t dummy_var = 0; + dummy_var ^= ((int64_t) (void*) wire_runtime_started); dummy_var ^= ((int64_t) (void*) wire_run_engine); dummy_var ^= ((int64_t) (void*) wire_send); dummy_var ^= ((int64_t) (void*) wire_stop_engine); diff --git a/lib/bloc/engine/engine_control_bloc.dart b/lib/bloc/engine/engine_control_bloc.dart index c0c7802..176a3e5 100644 --- a/lib/bloc/engine/engine_control_bloc.dart +++ b/lib/bloc/engine/engine_control_bloc.dart @@ -88,6 +88,10 @@ class EngineControlBloc extends Bloc { EngineControlBloc(this._repo) : super(EngineStoppedState()) { on((event, emit) async { + if (await _repo.runtimeStarted()) { + logWarning("Runtime already started, ignoring restart request."); + return; + } logInfo("Trying to start engine..."); await _repo.start(options: event.options); _isRunning = true; diff --git a/lib/bloc/engine/engine_provider.dart b/lib/bloc/engine/engine_provider.dart index e922eb2..b0b5b97 100644 --- a/lib/bloc/engine/engine_provider.dart +++ b/lib/bloc/engine/engine_provider.dart @@ -9,6 +9,7 @@ abstract class EngineProcessMessage {} abstract class EngineProvider { Future start({required EngineOptionsExternal options}); Future stop(); + Future runtimeStarted(); void cycleStream(); void onEngineStart(); diff --git a/lib/bloc/engine/engine_repository.dart b/lib/bloc/engine/engine_repository.dart index 67a21e5..d862c9d 100644 --- a/lib/bloc/engine/engine_repository.dart +++ b/lib/bloc/engine/engine_repository.dart @@ -62,6 +62,10 @@ class EngineRepository { await _provider.stop(); } + Future runtimeStarted() async { + return await _provider.runtimeStarted(); + } + void send(String msg) { _provider.send(msg); } diff --git a/lib/bloc/engine/foreground_task_library_engine_provider.dart b/lib/bloc/engine/foreground_task_library_engine_provider.dart index 8faf278..138f67d 100644 --- a/lib/bloc/engine/foreground_task_library_engine_provider.dart +++ b/lib/bloc/engine/foreground_task_library_engine_provider.dart @@ -77,7 +77,12 @@ class IntifaceEngineTaskHandler extends TaskHandler { _sendProviderLog("INFO", "Starting engine"); _sendProviderLog("INFO", "Starting library internal engine with the following arguments: $engineOptions"); - _stream = api!.runEngine(args: engineOptions); + try { + _stream = api!.runEngine(args: engineOptions); + } catch (e) { + _sendProviderLog("ERROR", "Engine start failed!"); + return; + } _sendProviderLog("INFO", "Engine started"); _stream!.listen((element) { try { @@ -147,6 +152,11 @@ class ForegroundTaskLibraryEngineProvider implements EngineProvider { await _startForegroundTask(); } + @override + Future runtimeStarted() async { + return await api!.runtimeStarted(); + } + @override void cycleStream() { _processMessageStream.close(); diff --git a/lib/bloc/engine/library_engine_provider.dart b/lib/bloc/engine/library_engine_provider.dart index 2a96704..6b68f54 100644 --- a/lib/bloc/engine/library_engine_provider.dart +++ b/lib/bloc/engine/library_engine_provider.dart @@ -10,7 +10,13 @@ class LibraryEngineProvider implements EngineProvider { @override Future start({required EngineOptionsExternal options}) async { logInfo("Starting library internal engine with the following arguments: $options"); - _stream = api!.runEngine(args: options); + try { + _stream = api!.runEngine(args: options); + } catch (e) { + logError("Engine start failed!"); + stop(); + return; + } logInfo("Engine started"); _stream!.listen((element) { try { @@ -22,6 +28,11 @@ class LibraryEngineProvider implements EngineProvider { }).onError((e) => logError(e.anyhow)); } + @override + Future runtimeStarted() async { + return await api!.runtimeStarted(); + } + @override void cycleStream() { _processMessageStream.close(); diff --git a/lib/bridge_generated.dart b/lib/bridge_generated.dart index adf6467..becb4a6 100644 --- a/lib/bridge_generated.dart +++ b/lib/bridge_generated.dart @@ -11,6 +11,10 @@ import 'package:uuid/uuid.dart'; import 'dart:ffi' as ffi; abstract class IntifaceEngineFlutterBridge { + Future runtimeStarted({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kRuntimeStartedConstMeta; + Stream runEngine({required EngineOptionsExternal args, dynamic hint}); FlutterRustBridgeTaskConstMeta get kRunEngineConstMeta; @@ -180,6 +184,23 @@ class IntifaceEngineFlutterBridgeImpl implements IntifaceEngineFlutterBridge { factory IntifaceEngineFlutterBridgeImpl.wasm(FutureOr module) => IntifaceEngineFlutterBridgeImpl(module as ExternalLibrary); IntifaceEngineFlutterBridgeImpl.raw(this._platform); + Future runtimeStarted({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_runtime_started(port_), + parseSuccessData: _wire2api_bool, + parseErrorData: null, + constMeta: kRuntimeStartedConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kRuntimeStartedConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "runtime_started", + argNames: [], + ); + Stream runEngine( {required EngineOptionsExternal args, dynamic hint}) { var arg0 = _platform.api2wire_box_autoadd_engine_options_external(args); @@ -870,6 +891,20 @@ class IntifaceEngineFlutterBridgeWire implements FlutterRustBridgeWireBase { late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr .asFunction)>(); + void wire_runtime_started( + int port_, + ) { + return _wire_runtime_started( + port_, + ); + } + + late final _wire_runtime_startedPtr = + _lookup>( + 'wire_runtime_started'); + late final _wire_runtime_started = + _wire_runtime_startedPtr.asFunction(); + void wire_run_engine( int port_, ffi.Pointer args, diff --git a/macos/Runner/bridge_generated.h b/macos/Runner/bridge_generated.h index 3defb22..3705781 100644 --- a/macos/Runner/bridge_generated.h +++ b/macos/Runner/bridge_generated.h @@ -103,6 +103,8 @@ uintptr_t new_dart_opaque(Dart_Handle handle); intptr_t init_frb_dart_api_dl(void *obj); +void wire_runtime_started(int64_t port_); + void wire_run_engine(int64_t port_, struct wire_EngineOptionsExternal *args); void wire_send(int64_t port_, struct wire_uint_8_list *msg_json); @@ -150,6 +152,7 @@ void free_WireSyncReturn(WireSyncReturn ptr); static int64_t dummy_method_to_enforce_bundling(void) { int64_t dummy_var = 0; + dummy_var ^= ((int64_t) (void*) wire_runtime_started); dummy_var ^= ((int64_t) (void*) wire_run_engine); dummy_var ^= ((int64_t) (void*) wire_send); dummy_var ^= ((int64_t) (void*) wire_stop_engine);